经历了长年的艰苦卓绝的披星戴月的惨绝人寰的跋山涉水,我们终于接近了AI之旅的尾声(好吧,实际上我们这才是刚刚开始)。这一次真正展示一下这几回辛勤工作的结果,最后的画面会是这个样子:
下面给出完整代码(注意需要gameobjects库才可以运行,参考之前的向量篇):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 |
SCREEN_SIZE = (640, 480) NEST_POSITION = (320, 240) ANT_COUNT = 20 NEST_SIZE = 100. import pygame from pygame.locals import * from random import randint, choice from gameobjects.vector2 import Vector2 class State(object): def __init__(self, name): self.name = name def do_actions(self): pass def check_conditions(self): pass def entry_actions(self): pass def exit_actions(self): pass class StateMachine(object): def __init__(self): self.states = {} self.active_state = None def add_state(self, state): self.states[state.name] = state def think(self): if self.active_state is None: return self.active_state.do_actions() new_state_name = self.active_state.check_conditions() if new_state_name is not None: self.set_state(new_state_name) def set_state(self, new_state_name): if self.active_state is not None: self.active_state.exit_actions() self.active_state = self.states[new_state_name] self.active_state.entry_actions() class World(object): def __init__(self): self.entities = {} self.entity_id = 0 self.background = pygame.surface.Surface(SCREEN_SIZE).convert() self.background.fill((255, 255, 255)) pygame.draw.circle(self.background, (200, 255, 200), NEST_POSITION, int(NEST_SIZE)) def add_entity(self, entity): self.entities[self.entity_id] = entity entity.id = self.entity_id self.entity_id += 1 def remove_entity(self, entity): del self.entities[entity.id] def get(self, entity_id): if entity_id in self.entities: return self.entities[entity_id] else: return None def process(self, time_passed): time_passed_seconds = time_passed / 1000.0 for entity in self.entities.values(): entity.process(time_passed_seconds) def render(self, surface): surface.blit(self.background, (0, 0)) for entity in self.entities.itervalues(): entity.render(surface) def get_close_entity(self, name, location, range=100.): location = Vector2(*location) for entity in self.entities.itervalues(): if entity.name == name: distance = location.get_distance_to(entity.location) if distance < range: return entity return None class GameEntity(object): def __init__(self, world, name, image): self.world = world self.name = name self.image = image self.location = Vector2(0, 0) self.destination = Vector2(0, 0) self.speed = 0. self.brain = StateMachine() self.id = 0 def render(self, surface): x, y = self.location w, h = self.image.get_size() surface.blit(self.image, (x-w/2, y-h/2)) def process(self, time_passed): self.brain.think() if self.speed > 0. and self.location != self.destination: vec_to_destination = self.destination - self.location distance_to_destination = vec_to_destination.get_length() heading = vec_to_destination.get_normalized() travel_distance = min(distance_to_destination, time_passed * self.speed) self.location += travel_distance * heading class Leaf(GameEntity): def __init__(self, world, image): GameEntity.__init__(self, world, "leaf", image) class Spider(GameEntity): def __init__(self, world, image): GameEntity.__init__(self, world, "spider", image) self.dead_image = pygame.transform.flip(image, 0, 1) self.health = 25 self.speed = 50. + randint(-20, 20) def bitten(self): self.health -= 1 if self.health <= 0: self.speed = 0. self.image = self.dead_image self.speed = 140. def render(self, surface): GameEntity.render(self, surface) x, y = self.location w, h = self.image.get_size() bar_x = x - 12 bar_y = y + h/2 surface.fill( (255, 0, 0), (bar_x, bar_y, 25, 4)) surface.fill( (0, 255, 0), (bar_x, bar_y, self.health, 4)) def process(self, time_passed): x, y = self.location if x > SCREEN_SIZE[0] + 2: self.world.remove_entity(self) return GameEntity.process(self, time_passed) class Ant(GameEntity): def __init__(self, world, image): GameEntity.__init__(self, world, "ant", image) exploring_state = AntStateExploring(self) seeking_state = AntStateSeeking(self) delivering_state = AntStateDelivering(self) hunting_state = AntStateHunting(self) self.brain.add_state(exploring_state) self.brain.add_state(seeking_state) self.brain.add_state(delivering_state) self.brain.add_state(hunting_state) self.carry_image = None def carry(self, image): self.carry_image = image def drop(self, surface): if self.carry_image: x, y = self.location w, h = self.carry_image.get_size() surface.blit(self.carry_image, (x-w, y-h/2)) self.carry_image = None def render(self, surface): GameEntity.render(self, surface) if self.carry_image: x, y = self.location w, h = self.carry_image.get_size() surface.blit(self.carry_image, (x-w, y-h/2)) class AntStateExploring(State): def __init__(self, ant): State.__init__(self, "exploring") self.ant = ant def random_destination(self): w, h = SCREEN_SIZE self.ant.destination = Vector2(randint(0, w), randint(0, h)) def do_actions(self): if randint(1, 20) == 1: self.random_destination() def check_conditions(self): leaf = self.ant.world.get_close_entity("leaf", self.ant.location) if leaf is not None: self.ant.leaf_id = leaf.id return "seeking" spider = self.ant.world.get_close_entity("spider", NEST_POSITION, NEST_SIZE) if spider is not None: if self.ant.location.get_distance_to(spider.location) < 100.: self.ant.spider_id = spider.id return "hunting" return None def entry_actions(self): self.ant.speed = 120. + randint(-30, 30) self.random_destination() class AntStateSeeking(State): def __init__(self, ant): State.__init__(self, "seeking") self.ant = ant self.leaf_id = None def check_conditions(self): leaf = self.ant.world.get(self.ant.leaf_id) if leaf is None: return "exploring" if self.ant.location.get_distance_to(leaf.location) < 5.0: self.ant.carry(leaf.image) self.ant.world.remove_entity(leaf) return "delivering" return None def entry_actions(self): leaf = self.ant.world.get(self.ant.leaf_id) if leaf is not None: self.ant.destination = leaf.location self.ant.speed = 160. + randint(-20, 20) class AntStateDelivering(State): def __init__(self, ant): State.__init__(self, "delivering") self.ant = ant def check_conditions(self): if Vector2(*NEST_POSITION).get_distance_to(self.ant.location) < NEST_SIZE: if (randint(1, 10) == 1): self.ant.drop(self.ant.world.background) return "exploring" return None def entry_actions(self): self.ant.speed = 60. random_offset = Vector2(randint(-20, 20), randint(-20, 20)) self.ant.destination = Vector2(*NEST_POSITION) + random_offset class AntStateHunting(State): def __init__(self, ant): State.__init__(self, "hunting") self.ant = ant self.got_kill = False def do_actions(self): spider = self.ant.world.get(self.ant.spider_id) if spider is None: return self.ant.destination = spider.location if self.ant.location.get_distance_to(spider.location) < 15.: if randint(1, 5) == 1: spider.bitten() if spider.health <= 0: self.ant.carry(spider.image) self.ant.world.remove_entity(spider) self.got_kill = True def check_conditions(self): if self.got_kill: return "delivering" spider = self.ant.world.get(self.ant.spider_id) if spider is None: return "exploring" if spider.location.get_distance_to(NEST_POSITION) > NEST_SIZE * 3: return "exploring" return None def entry_actions(self): self.speed = 160. + randint(0, 50) def exit_actions(self): self.got_kill = False def run(): pygame.init() screen = pygame.display.set_mode(SCREEN_SIZE, 0, 32) world = World() w, h = SCREEN_SIZE clock = pygame.time.Clock() ant_image = pygame.image.load("ant.png").convert_alpha() leaf_image = pygame.image.load("leaf.png").convert_alpha() spider_image = pygame.image.load("spider.png").convert_alpha() for ant_no in xrange(ANT_COUNT): ant = Ant(world, ant_image) ant.location = Vector2(randint(0, w), randint(0, h)) ant.brain.set_state("exploring") world.add_entity(ant) while True: for event in pygame.event.get(): if event.type == QUIT: return time_passed = clock.tick(30) if randint(1, 10) == 1: leaf = Leaf(world, leaf_image) leaf.location = Vector2(randint(0, w), randint(0, h)) world.add_entity(leaf) if randint(1, 100) == 1: spider = Spider(world, spider_image) spider.location = Vector2(-50, randint(0, h)) spider.destination = Vector2(w+50, randint(0, h)) world.add_entity(spider) world.process(time_passed) world.render(screen) pygame.display.update() if __name__ == "__main__": run() |
这个程序的长度超过了以往任何一个,甚至可能比我们写的加起来都要长一些。然而它可以展现给我们的也前所未有的惊喜。无数勤劳的小蚂蚁在整个地图上到处觅食,随机出现的叶子一旦被蚂蚁发现,就会搬回巢穴,而蜘蛛一旦出现在巢穴范围之内,就会被蚂蚁们群起而攻之,直到被驱逐出地图范围或者挂了,蜘蛛的尸体也会被带入巢穴。
这个代码写的不够漂亮,没有用太高级的语法,甚至都没有注释天哪……基本代码都在前面出现了,只是新引入了四个新的状态,AntStateExploring、AntStateSeeking、AntStateDelivering和AntStateHunting,意义的话前面已经说明。比如说AntStateExploring,继承了基本的Stat,这个状态的动作平时就是让蚂蚁以一个随机的速度走向屏幕随机一个点,在此过程中,check_conditions会不断检查周围的环境,发现了树叶或蜘蛛都会采取相应的措施(进入另外一个状态)。
游戏设计艺术中,创建一个漂亮的AI是非常有挑战性也非常有趣的事情。好的AI能让玩家沉浸其中,而糟糕的AI则让人感到非常乏味(有的时候AI中的一些bug被当作秘籍使用,也挺有意思的,不过如果到处是“秘籍”,可就惨了)。而且,AI是否足够聪明有时候并不与代码量直接相关,看看我们这个演示,感觉上去蚂蚁会合作攻击蜘蛛,而实际上它们都是独立行动的,不过就结果而言蚂蚁们看起来都很聪明。
对AI而已,状态机是个很有力的工具(当然状态机不仅仅用在这里),因为状态机可以把复杂的系统分割成几个容易实现的小段。而这每一小部分都是对一些简单思考或动作的模拟,即便不是那么容易转化为代码,也很容易模拟。在游戏中,我们只需要模拟就足够了。
我们这几次讲述的东西相当有用,尽管不是那么直观,但对于游戏设计至关重要,而此次的蚁巢演示,也给我们揭示了AI系统的种种,至少这个系统式可以运作的了,不错不错~ 参天大树也是从小树苗开始的。
本次使用的图像资源:
叶子:leaf.png
蚂蚁:ant.png
蜘蛛:spider.png
下一次,我们开始更加激动人心的项目,3D图像!
哥 你太强悍啦 顶起
请问有编译好的exe吗,博主用什么编译exe呢,我试了几个都不好用
python 语言不需要编译,建议先看一下python的概念。
基本的操作方法,安装python, 然后运行python main.py
跟着例子修修改改,终于运行起来了,效果不错。:)
很有意思啊。赞。
代码311行中,spider.destination = Vector2(w+50, randint(0, h)),其中(w+50),这个50是的功能是啥??
这个是保证蜘蛛能爬出屏幕外。
不知道有没有人和我有一样的错误。
pYthon版本3.3,提示字典在操作过程中长度发生了变化。
解决方法:
import copy
在出错行前写entities2 = copy.copy(self.entity)
将下面的self.entity改成 entities2
这样就避免了直接操作在循环内会改变的字典,而是使用了一个拷贝。
如果直接用赋值语句的话可能会因为内存位置没变而导致还是一样的问题。
遇到你说的问题了,谢了!
感谢,不过不是把那个函数改成这个样子吗?
def process(self, time_passed):
time_passed_seconds = time_passed / 1000.0
entities2 = copy.copy(self.entities)
for entity in entities2.values():
entity.process(time_passed_seconds)
需要再定义下entities2 写成self.entities2 = copy.copy(self.entities)
我的是这样解决的,不然会报错
真的强 我的天 谢了 这解决方案不错
distance = location.get_distance_to(entity.location) 这个我一直报错,’Vector2′ object has no attribute ‘get_distance_to’
我的Vector2确实没有这个方法,能给下这方法的代码么?初学,好多东西都不明白
很明显就是距离的意思嘛
Import个math
使用math.sqrt 和**2就解决问题了
用之前精通(9)里面提到的一个适用于3.x的库,里面的get_distance,等同于这个
哈哈哈 好好玩啊
Py2EXE打包报错:
Traceback (most recent call last):
File “C:\Users\Administrator\Desktop\python\pygame\PyGame_Py2exe.py”, line 172, in
BuildExe().run()
File “C:\Users\Administrator\Desktop\python\pygame\PyGame_Py2exe.py”, line 163, in run
data_files = extra_datas,
File “C:\Python27\lib\distutils\core.py”, line 169, in setup
raise SystemExit, “error: ” + str(msg)
SystemExit: error: command ‘C:\Python27\pythonw.exe’ failed with exit status 1
这是什么意思啊?
第276行 self.speed 改为 self.ant.speed.
感谢作者,有趣的例子。
Traceback (most recent call last):
File “E:\Python27\ant_game.py”, line 325, in
run()
File “E:\Python27\ant_game.py”, line 319, in run
world.process(time_passed)
File “E:\Python27\ant_game.py”, line 75, in process
entity.process(time_passed_seconds)
File “E:\Python27\ant_game.py”, line 111, in process
self.brain.think()
File “E:\Python27\ant_game.py”, line 40, in think
new_state_name = self.active_state.check_conditions()
File “E:\Python27\ant_game.py”, line 197, in check_conditions
leaf = self.ant.world.get_close_entity(“leaf”, self.ant.location)
File “E:\Python27\ant_game.py”, line 83, in get_close_entity
location = Vector2(*location)
TypeError: type object argument after * must be a sequence, not Vector2
请问楼主,这是什么错啊
不知道是否因为库有更新还是阁下输入有误造成的,代码的83行,location必须是个序列,而不能是Vector2类型
我就是你运行上面的16章中的这段代码,运行时码报的错,win7 64位系统。这个*location本来就是将location的坐标展开。之前的代码中这样用比如把鼠标位置的点展开传给Vector2( *pygame.mouse.get_pos() )也没有报错。在这里报错了我实在理解不了。请楼主帮忙分析一下
我修改了一下Vector2的代码,上面的问题解决了。但是在 函数迭代的时候报错:运行一会儿之后就会报错
def render(self, surface):
surface.blit(self.background, (0, 0))
for entity in self.entities.itervalues():
entity.render(surface)
File “E:\Python27\test_ant.py”, line 63, in process
for entity in self.entities.itervalues():
RuntimeError: dictionary changed size during iteration
这样看不出来哦,只看提示的话dict在遍历的时候被修改了?
转list,我这个就行了
list(self.entities.values())
楼主很厉害,不过我看的画面有点恶心,讨厌虫子
感谢上面的评论,让我这个小白在python3.5下也弄成功了
下面总结一下修改的地方:
1. 出错AttributeError: ‘dict’ object has no attribute ‘itervalues’
80、75行(附近)修改为:for entity in self.entities.values(): #去掉inter即可
2.出错RuntimeError: dictionary changed size during iteration
解决方法:(借用上面评论的大哥)
import copy
在出错行前写entities2 = copy.copy(self.entities) ——70行附近
将下面的self.entity改成 entities2
这样就避免了直接操作在循环内会改变的字典,而是使用了一个拷贝。
3.还有一个xrange 改为range就好了
感谢!!!!
能帮忙提供下gameobjects的库吗?我的也是3.5的版本,谢谢361018806@qq.com。
Pingback: 用Python和Pygame写游戏从入门到精通(16) | 演道网
按照楼主和各位大侠的代码,运行的时候只有蜘蛛在运动,蚂蚁不动,请问谁知道是怎么回事?谢谢
一个状态机的check_conditions函数名写错了,造成没有调用,汗~~~~~
我也是这个,AntStateHunting状态机中的函数写成了check_conditons,导致每次蚂蚁咬死蜘蛛后,好多蚂蚁就和蜘蛛一起over了。就少了个字母“i”,对着源码看了好几遍没有看出来。最后是下载了个对比软件才找出来。my god
python3.6使用pyinstaller发布:
首先,安装pyinstaller,直接pip3 install pyinstaller即可。
然后按以下步骤:
1、需要在py中添加函数:
# 用来加载图片的函数,后续所有需要用到图片的地方,都通过该函数来加载。
def resource_path(relative):
if hasattr(sys, “_MEIPASS”):
return os.path.join(sys._MEIPASS, relative)
return os.path.join(relative)
2、需要加载图片的地方,以前直接用的路径,现在都使用:
resource_path(os.path.join(data_dir, ‘ant.png’))
PS:data_dir是指和脚本同级的用来存放图片的路径,一般使用images。
3、执行pyi-makespec 参数 要打包的py脚本
如:pyi-makespec -F game1.py
4、修改生成的xxx.spec文件
指定要添加的图片:
a.datas += [(‘images\\ant.png’,”D:\\PyCharm\\PycharmProjects\\pygame\\images\\ant.png”,’DATA’),(‘images\\leaf.png’,”D:\\PyCharm\\PycharmProjects\\pygame\\images\\leaf.png”,’DATA’),(‘images\\spider.png’,”D:\\PyCharm\\PycharmProjects\\pygame\\images\\spider.png”,’DATA’)]
如果有多个图片,则可以考虑使用:
dict_tree = Tree(“D:\PyCharm\PycharmProjects\pygame\images”, prefix = “images”)
a.datas += dict_tree
说明:Tree(“实际存放图片的文件夹”,prefix = “代码中图片文件的上级目录。”)
比如说,在代码中希望load(’images/aaa.jpg’),aaa.jpg在本地实际存储路径是:d:\images\
则Tree(“d:\\images”,prefix = “images”)
5、执行pyinstaller xxx.spec
NameError: name ‘time_passed’ is not defined
为什会这样啊?代码检查了好多遍,和上面一样啊
这个好难懂,, 晦涩难懂…..
libpng warning: iCCP: too many profiles,这个怎么解决
不知道欸,图片有没有什么问题?
博主 我一直想知道 (x-w/2,y-h/2)和(x,y-h/2)的区别 get_size()不是得到图片左上角的坐标吗 为什么会被当成中心点坐标
他会一直无限的生成树叶,蜘蛛,内存会炸吧,
教程大赞,花了两三天才搞完这个小AI,我这智商感人~
不过还是很谢谢楼主大人的教程的。