经历了长年的艰苦卓绝的披星戴月的惨绝人寰的跋山涉水,我们终于接近了AI之旅的尾声(好吧,实际上我们这才是刚刚开始)。这一次真正展示一下这几回辛勤工作的结果,最后的画面会是这个样子:
下面给出完整代码(注意需要gameobjects库才可以运行,参考之前的向量篇):
Python
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