用Python和Pygame写游戏-从入门到精通(16)

13 篇文章 0 订阅
2 篇文章 0 订阅

 

经历了长年的艰苦卓绝的披星戴月的惨绝人寰的跋山涉水,我们终于接近了AI之旅的尾声(好吧,实际上我们这才是刚刚开始)。这一次真正展示一下这几回辛勤工作的结果,最后的画面会是这个样子:

蚁巢系统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()

这个程序的长度超过了以往任何一个,甚至可能比我们写的加起来都要长一些。然而它可以展现给我们的也前所未有的惊喜。无数勤劳的小蚂蚁在整个地图上到处觅食,随机出现的叶子一旦被蚂蚁发现,就会搬回巢穴,而蜘蛛一旦出现在巢穴范围之内,就会被蚂蚁们群起而攻之,直到被驱逐出地图范围或者挂了,蜘蛛的尸体也会被带入巢穴。

这个代码写的不够漂亮,没有用太高级的语法,甚至都没有注释天哪……基本代码都在前面出现了,只是新引入了四个新的状态,AntStateExploringAntStateSeekingAntStateDeliveringAntStateHunting,意义的话前面已经说明。比如说AntStateExploring,继承了基本的Stat,这个状态的动作平时就是让蚂蚁以一个随机的速度走向屏幕随机一个点,在此过程中,check_conditions会不断检查周围的环境,发现了树叶或蜘蛛都会采取相应的措施(进入另外一个状态)。

 

游戏设计艺术中,创建一个漂亮的AI是非常有挑战性也非常有趣的事情。好的AI能让玩家沉浸其中,而糟糕的AI则让人感到非常乏味(有的时候AI中的一些bug被当作秘籍使用,也挺有意思的,不过如果到处是“秘籍”,可就惨了)。而且,AI是否足够聪明有时候并不与代码量直接相关,看看我们这个演示,感觉上去蚂蚁会合作攻击蜘蛛,而实际上它们都是独立行动的,不过就结果而言蚂蚁们看起来都很聪明。

对AI而已,状态机是个很有力的工具(当然状态机不仅仅用在这里),因为状态机可以把复杂的系统分割成几个容易实现的小段。而这每一小部分都是对一些简单思考或动作的模拟,即便不是那么容易转化为代码,也很容易模拟。在游戏中,我们只需要模拟就足够了。

我们这几次讲述的东西相当有用,尽管不是那么直观,但对于游戏设计至关重要,而此次的蚁巢演示,也给我们揭示了AI系统的种种,至少这个系统式可以运作的了,不错不错~ 参天大树也是从小树苗开始的。

本次使用的图像资源:

叶子:leaf.png
蚂蚁:ant.png
蜘蛛:spider.png

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值