本文转载自:http://eyehere.net/2011/python-pygame-novice-professional-16/
经历了长年的艰苦卓绝的披星戴月的惨绝人寰的跋山涉水,我们终于接近了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图像!