forked from Throde/KT_7
-
Notifications
You must be signed in to change notification settings - Fork 2
/
enemy.py
5470 lines (5051 loc) · 245 KB
/
enemy.py
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
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
"""
enemy.py:
Define all kinds of monsters and impediments in game.
Classes in this module is organised in the order of chapters.
"""
import pygame
from pygame.image import load
from pygame.transform import flip # this function is non-destructive to images
from pygame.sprite import collide_mask
import math
from random import choice, random, randint
from database import MB, NB, DMG_FREQ
from util import InanimSprite, HPBar
from util import getPos, rot_center, generateShadow, getCld
# -------------------------------------------------
# some helpful functions for this module
def cldList(subject, objList):
'''Check whether subject collides with any object in the list:
if yes, invoke hitted() of the collided object.'''
for each in objList:
if ( collide_mask( subject, each ) ):
each.hitted( subject.damage, subject.push, subject.dmgType )
if hasattr(subject, "dmgType") and subject.dmgType=="freezing":
each.freeze(1)
return each # 返回碰撞对象表示发生碰撞,否则函数最后返回默认值None
def createCanvas(size, colorKey=(0,0,0)):
'''为调用者生成一个画布对象。接受参数为画布尺寸和透明色信息(RGB无A),返回画布的surface及其rect。'''
canvas = pygame.Surface( size ).convert()
canvas.set_colorkey( colorKey ) # set black as transparent color, generally make the canvas transparent
cRect = canvas.get_rect()
cRect.left = 0
cRect.top = 0
return (canvas, cRect)
def createImgList(*paths):
'''为调用者生成一个含左右图片列表的字典并返回。接受参数为左向的图片路径列表。注意使用了收集参数的机制,
必须要把所有图片路径按顺序传入。如果实际只有一个图片传入,字典中的元素也会是列表,该表中只有一张图。'''
imgDic = { "left":[], "right":[] }
# 先建立left图片列表,再依次变换至right图片列表
for path in paths:
imgDic["left"].append( load(path).convert_alpha() )
for img in imgDic["left"]:
imgDic["right"].append( flip(img, True, False) )
return imgDic
def getShadLib(imgLib):
'''根据所给的imgLib转化出相应的阴影shadLib,并返回(适用于monsters)'''
shadLib = {}
for name in imgLib: # 对于每一项动作名称
shadLib[name] = { "left":[], "right":[] }
for dir in imgLib[name]: # 对于该动作的每一方向
for img in imgLib[name][dir]:
shadLib[name][dir].append( generateShadow(img) )
return shadLib
# ========================================================================
# Basic class for all monsters:
# 此类将提供所有enemy应有的方法和属性,子类可在此基础上扩展
# ========================================================================
class Monster(InanimSprite):
# NOTE: 所有的怪物类别都应维护自己的一些媒体静态变量。它们应该只加载一次,然后所有实例都可以使用这些媒体对象,如图片、声音。
# 以下是monster大类共享的2个共享静态变量。在模式初始化时,由model对象来对这2个值进行初始化。(1表示原生命)
healthBonus = 1
# model的spurtcanvas对象,每个monster均应能快速访问,来实现多种效果
spurtCanvas = None
# monster 眩晕图片列表
stun_img = []
# a msgList of the model: could be visited by all monsters
msgList = None
def __init__(self, cate, bldColor, push, weight, onlayer,
sideGroup=None, shadOffset=8, bar=False, debri=()):
'''debri: pair (debriType, num)'''
InanimSprite.__init__(self, cate) # 首先它应该是一个InanimSprite
if not Monster.stun_img:
Monster.stun_img = [load("image/stun_1.png"), load("image/stun_2.png"),
load("image/stun_3.png"), load("image/stun_4.png")]
Monster.stun_rect = Monster.stun_img[0].get_rect()
self.bldColor = bldColor # tuple类型
self.health = round( MB[cate].health * self.healthBonus )
self.full = self.health
self.coin = MB[cate].coin
self.damage = MB[cate].damage
self.dmgType = MB[cate].dmgType # 伤害类型:大部分为物理伤害。
self.manner = MB[cate].manner # 行为方式:大部分为地面类型。
self.armor = MB[cate].armor # 护甲(伤害减免):大部分无减伤。
self.pushList = (-push, push)
self.push = push
self.weight = weight
self.hitBack = 0
self.realDmgRate = 1-self.armor
self.shadOffset = shadOffset
self.onlayer = int(onlayer) # 怪物的onlayer是其所处的砖块的层数。
self.direction = "left" # 默认初始朝left。若需自定义,可在monster子类的构造函数中修改本值。
self.gravity = 0
self.obstacle = pygame.sprite.Group()
if sideGroup:
self.renewObstacle(sideGroup)
# The followings are parameters about HP bar.
self.bar = HPBar(self.full, blockVol=200, barH=10) if bar else None
self.debri = debri
self.stun_time = 0
def alterSpeed(self, speed):
self.speed = speed
if speed > 0:
self.direction = "right"
elif speed < 0:
self.direction = "left"
self.push = self.pushList[0] if (self.direction=="left") else self.pushList[1]
def checkHitBack(self, obstacle=False):
if abs(self.hitBack)>0:
# NOTE:和fall的处理方式不同。如果和参数中的物体相撞,则尝试保持原位置不变并直接消除hitback效果,而不是移至物体之外。
if pygame.sprite.spritecollide(self, self.obstacle, False, collide_mask):
self.hitBack = 0
return False
self.rect.left += self.hitBack
self.hitBack -= self.hitBack//abs(self.hitBack) # 大于0取1,小于零取-1。
return True
return False
def fall(self, keyLine, groupList, GRAVITY):
if self.gravity<GRAVITY:
self.gravity += 1
self.rect.bottom += self.gravity
while ( pygame.sprite.spritecollide(self, self.wallList, False, collide_mask) ): # 如果和参数中的物体相撞,则尝试纵坐标-1
self.rect.bottom -= 1
self.gravity = 0
if self.rect.top >= keyLine:
self.onlayer = max(self.onlayer-2, -1)
self.initLayer( groupList[str(self.onlayer)], groupList["0"], wallChosen=True )
# 更新obstacle砖块
self.renewObstacle(groupList["0"])
def initLayer(self, lineGroup, sideGroup, wallChosen=False):
'''
用于确定新一行中的位置及scope。
param lineGroup: 该行的所有lineWall。
param sideGroup: Add sidewalls that are of same layer to avoid monsters fall from the top layer.
'''
self.wallList = [] # 存储本行的所有砖块。每次初始化一个新实例时,清空此类的wallList
posList = [] # 辅助列表,用于暂时存储本行砖块的位置(左边线)
# 若已确定位置,是在下落过程中重新更新layer。
if wallChosen:
wall = None # 此刻monster正下方的wall
for aWall in lineGroup: # 由于spriteGroup不好进行索引/随机选择操作,因此将其中的sprite逐个存入列表中存储
if aWall.category in ("lineWall","specialWall","baseWall"):
self.wallList.append(aWall)
posList.append(aWall.rect.left)
if aWall.rect.left<getPos(self,0.5,0)[0]<aWall.rect.right: # 可以落到下一行上,有砖接着
wall = aWall
if not wall: # 没wall接着,直接返回,继续下落吧!
return None
# 否则,是首次生成,直接随机选择生成地。
else:
for aWall in lineGroup: # 由于spriteGroup不好进行索引/随机选择操作,因此将其中的sprite逐个存入列表中存储
self.wallList.append(aWall)
posList.append(aWall.rect.left)
wall = choice(self.wallList)
# 到了这一步,已经得到了wall,好,可以开始计算scope了。
leftMax = wall.rect.left
rightMax = wall.rect.right # note:此处砖块的右坐标即下一砖块的左坐标
while True:
if leftMax in posList: # warmachine比较宽,可以占两格行进
leftMax -= wall.rect.width
else:
leftMax += wall.rect.width # 将多减的加回来
break
while True:
if rightMax in posList:
rightMax += wall.rect.width
else:
break
self.scope = (leftMax, rightMax)
# Add sidewalls of the same layer to wallList
for each in sideGroup:
if each.coord[1]==self.onlayer:
self.wallList.append(each)
return wall
def renewObstacle(self, sideGroup):
# 更新obstacle砖块
self.obstacle.empty()
for each in sideGroup:
if each.coord[1] == self.onlayer+1:
self.obstacle.add(each)
def detectHero(self, heroes):
for hero in heroes:
# 如果有英雄在同一层,则将速度改为朝英雄方向。
if (hero.onlayer-1)==self.onlayer and ( self.scope[0]<=getPos(hero,0.5,0)[0]<=self.scope[1] ):
if self.speed*( getPos(hero,0.5,0)[0]-getPos(self,0.5,0)[0] ) < 0:
self.alterSpeed(-self.speed)
return hero
return None
def paint(self, surface):
'''所有monster都应有paint()函数,供外界调用,将自身画在传来的surface上。NOTE:此函数不负责绘制生命条,应该由怪物管理类主动绘制'''
# 画阴影
shadRect = self.rect.copy()
shadRect.left -= self.shadOffset
surface.blit(self.shad, shadRect)
surface.blit( self.image, self.rect )
# 画打击阴影
if self.hitBack:
surface.blit( self.shad, self.rect )
# stun(): similar to hero's freeze()
def stun(self, duration):
self.stun_time = duration
self.reset()
def reset(self): # to be override by specific monster class
pass
def count_stun(self):
if self.stun_time>0:
self.stun_time -= 1
def setImg(self, name, indx=0): # 根据状态名称切换图片。如果是列表,应给出indx值。
self.image = self.imgLib[name][self.direction][indx]
self.shad = self.shadLib[name][self.direction][indx]
def drawHealth(self, surface):
if self.bar:
self.bar.paint(self, surface)
# 画眩晕
if self.stun_time > 0:
self.stun_rect.bottom = self.rect.top
self.stun_rect.left = self.rect.left +self.rect.width//2 -self.stun_rect.width//2
surface.blit(self.stun_img[self.stun_time//3%4], self.stun_rect)
def assignGoalie(self, HPInc):
# Redefine its health. All settings will be inherited.
self.full = self.health = round( self.health * HPInc )
if self.bar:
self.bar = HPBar(self.full, blockVol=self.bar.blockVol, barH=self.bar.barH, color="goldRed", icon=True)
def hitted(self, damage, pushed, dmgType):
# decrease health
true_dmg = round(damage*self.realDmgRate)
self.health -= true_dmg
self.msgList.append( [getPos(self,0.5,0.5), str(true_dmg), 60] )
if self.health <= 0: # dead
self.health = 0
if self.debri:
self.spurtCanvas.addPebbles(self, self.debri[1], type=self.debri[0])
self.kill()
return True
if pushed>0: # 向右击退
self.hitBack = max( pushed-self.weight, 0 )
elif pushed<0: # 向左击退
self.hitBack = min( pushed+self.weight, 0 )
def recover(self, heal):
if self.health<=0:
return
#self.spurtCanvas.addSpatters(8, (2,3,4), (20,22,24), (10,240,10), getPos(self,0.5,0.4) )
self.health += heal
self.msgList.append( [getPos(self,0.5,0.5), "+"+str(heal), 60, "green"] )
if (self.health > self.full):
self.health = self.full
# Boss: 比普通Monster更高级一些,拥有更多属性和特殊方法
class Boss(Monster):
'''Boss相比于Monster,拥有一些特殊的行为和属性'''
def __init__(self, font, cate, bldColor, push, weight, onlayer, sideGroup=None, shadOffset=8, bar=True):
Monster.__init__(self, cate, bldColor, push, weight, onlayer, sideGroup, shadOffset)
self.activated = False
self.font = font # For showing outscreen pos
self.bar = HPBar(self.full, blockVol=200, barH=12)
def _tipPosition(self, canvas):
if (self.rect.top > canvas.rect.height):
txt = self.font.render( "▼ "+str(self.rect.top-canvas.rect.height), True, (255,255,255) )
canvas.txtList.append( [txt, "BOTTOM"] )
elif (self.rect.bottom < 0):
txt = self.font.render( "▲ "+str(-self.rect.bottom), True, (255,255,255) )
canvas.txtList.append( [txt, "TOP"] )
def initLayer(self, groupList):
"""Boss 一般体型较大,有其特殊的处理行砖方式"""
self.wallList = []
posList = [] # 辅助列表,用于暂时存储本行砖块的位置(左边线)
while True: # in case that wallList of chosen layer is empty
for aWall in groupList[str(self.onlayer)]: # 由于spriteGroup不好进行索引/随机选择操作,因此将其中的sprite逐个存入列表中存储
self.wallList.append(aWall)
posList.append(aWall.rect.left)
if not self.wallList:
self.onlayer -= 2
else:
break
wall = choice(self.wallList)
self.initPos = getPos(wall, 0.5, 0) # 新点,居中
leftMax = wall.rect.left
rightMax = wall.rect.right
while True:
if (leftMax in posList) or (leftMax-wall.rect.width in posList): # Chicheng比较宽,可以占两格行进
leftMax -= wall.rect.width
else:
leftMax += wall.rect.width
break
while True:
if (rightMax in posList) or (rightMax+wall.rect.width in posList):
rightMax += wall.rect.width
else:
break
self.scope = (leftMax-wall.rect.width//2, rightMax+wall.rect.width//2)
# Add sidewalls of the same layer to wallList
for each in groupList["0"]:
if each.coord[1]==self.onlayer:
self.wallList.append(each)
return wall
# Ajunction: 一个Boss往往由一个Boss主体和多个Ajunction构成,如Dragon=身体本身(Boss类)+翅膀+头+尾巴(后三个均为Ajunction)
class Ajunction(pygame.sprite.Sprite):
'''此类应附属于某个monster主体而存在。由于是附属物,此类只提供供主物移动位置、替换image的函数接口,需要主物来执行相应的删除工作。'''
def __init__(self, img, pos):
pygame.sprite.Sprite.__init__(self)
self.image = img
self.rect = self.image.get_rect()
self.rect.left = pos[0] - self.rect.width//2
self.rect.bottom = pos[1] - self.rect.height//2
self.mask = pygame.mask.from_surface(self.image)
def updatePos(self, pos):
self.rect.left = pos[0] - self.rect.width//2
self.rect.top = pos[1] - self.rect.height//2
def updateImg(self, img):
trPos = [ self.rect.left + self.rect.width//2, self.rect.top + self.rect.height//2 ]
self.image = img
self.rect = self.image.get_rect()
self.rect.left = trPos[0]-self.rect.width//2
self.rect.bottom = trPos[1]
self.mask = pygame.mask.from_surface(self.image)
# ========================================================================
# ------------------------- CP 0 (endless mode)---------------------------
# ========================================================================
class BiteChest(Monster):
imgLib = None
shadLib = None
def __init__(self, wallGroup, sideGroup, onlayer):
if not self.imgLib:
BiteChest.imgLib = {
"iList": createImgList("image/stg0/biteChest1.png","image/stg0/biteChest0.png","image/stg0/biteChest0.png",
"image/stg0/biteChest0.png","image/stg0/biteChest0.png","image/stg0/biteChest0.png",
"image/stg0/biteChest1.png")
}
BiteChest.shadLib = getShadLib(BiteChest.imgLib)
# calculate its position
Monster.__init__(self, "biteChest", (250,210,160), 4, 3, onlayer, sideGroup)
wall = self.initLayer(wallGroup, sideGroup)
self.coin = randint(6,10)
# initialize the sprite
self.imgIndx = 0
self.setImg("iList",0)
self.mask = pygame.mask.from_surface(self.image)
self.rect = self.image.get_rect()
self.rect.left = wall.rect.left
self.rect.bottom = wall.rect.top
self.reset()
self.restCnt = self.resetCntFull = 8
self.alterSpeed( choice([-1,1]) )
self.rise = ( 1, -8, -13, -16, -13, -8, 1 )
def move(self, delay, sprites):
self.checkHitBack(obstacle=True)
self.count_stun()
if self.imgIndx<len(self.rise)-1: # 在空中时水平移动
if self.stun_time==0:
self.rect.left += self.speed
if (getPos(self,0.8,0)[0]>=self.scope[1] and self.speed>0) or (getPos(self,0.2,0)[0]<=self.scope[0] and self.speed<0):
self.alterSpeed( -self.speed )
if not (delay % 4 ):
trPos = [ self.rect.left + self.rect.width//2, self.rect.bottom-self.rise[self.imgIndx] ] # 为保证图片位置正确,临时存储之前的位置信息
self.imgIndx += 1
self.setImg("iList",self.imgIndx)
self.mask = pygame.mask.from_surface(self.image)
self.rect = self.image.get_rect()
self.rect.left = trPos[0]-self.rect.width//2
self.rect.bottom = trPos[1] + self.rise[self.imgIndx]
else:
if self.stun_time==0:
# rise最后一下落地,0.33秒停顿
self.restCnt -= 1
if self.restCnt<0:
self.restCnt = self.resetCntFull
self.imgIndx = 0
if self.stun_time==0:
# Bite
if ( self.coolDown==0 ):
for each in sprites:
if collide_mask(self, each):
self.coolDown = 36
elif (self.coolDown > 0):
self.coolDown -= 1
if ( self.coolDown == 20 ):
cldList( self, sprites )
def reset(self):
self.coolDown = 0
def level(self, dist):
self.rect.left += dist
self.scope = (self.scope[0]+dist, self.scope[1]+dist)
# ========================================================================
# --------------------------------- CP 1 ---------------------------------
# ========================================================================
class InfernoFire(InanimSprite):
def __init__(self, bg_size):
InanimSprite.__init__(self, "infernoFire")
self.ori_imgList = [load("image/stg1/infernoFire0.png").convert_alpha(),
load("image/stg1/infernoFire1.png").convert_alpha(),
load("image/stg1/infernoFire2.png").convert_alpha()]
self.snd = pygame.mixer.Sound("audio/infernoFire.wav")
self.width = bg_size[0]
self.height = bg_size[1]
self.damage = NB["infernoFire"]["damage"]
self.dmgType = "fire"
self._reset()
def update(self, delay, sprites, canvas):
if self.rect.top<self.height:
self.rect.left += self.speed[0]
self.rect.top += self.speed[1]
color = choice( [(60,10,0,210), (120,40,0,210)] )
canvas.addTrails( [3,5,7], [21,24,27], color, getPos(self, 0.3+random()*0.4, 0.5+random()*0.1) )
else:
self._reset()
return
if not delay%DMG_FREQ:
for each in sprites:
if ( collide_mask( self, each ) ):
if each.rect.left+each.rect.width//2 > self.rect.left+self.rect.width//2:
each.hitted(self.damage, 3, self.dmgType)
canvas.addSpatters( 4, (2,4,6), (6,7,8), (255,240,0,230), getPos(self, 0.5+random()*0.5, 0.2+random()*0.2), True )
else:
each.hitted(self.damage, -3, self.dmgType)
canvas.addSpatters( 4, (2,4,6), (6,7,8), (255,240,0,230), getPos(self, random()*0.5, 0.2+random()*0.2), False )
if not (delay % 5): # 切换图片
self.imgIndx = ( self.imgIndx+1 ) % len(self.imgList)
self.image = self.imgList[self.imgIndx]
self.mask = pygame.mask.from_surface(self.image)
def _reset(self):
self.snd.play(0)
self.speed = [choice([-5, 5]), 3]
rot = -115 if self.speed[0]<0 else 115
self.imgList = [pygame.transform.rotate(each, -rot) for each in self.ori_imgList]
self.image = self.imgList[0]
self.imgIndx = 0
self.rect = self.image.get_rect()
self.mask = pygame.mask.from_surface(self.image)
if self.speed[0]<0:
self.rect.left = self.width
else:
self.rect.right = 0
self.rect.bottom = randint(-60, self.height//2)
# -----------------------------------
class Tizilla(Monster):
imgLib = None
shadLib = None
def __init__(self, wallGroup, sideGroup, blockSize, onlayer):
if not self.imgLib:
Tizilla.imgLib = {
"iList": createImgList( "image/stg1/tizilla0.png", "image/stg1/tizilla1.png",
"image/stg1/tizilla0.png", "image/stg1/tizilla2.png" ),
"attList": createImgList( "image/stg1/tizillaAtt1.png",
"image/stg1/tizillaAtt2.png",
"image/stg1/tizillaAtt3.png",
"image/stg1/tizillaAtt4.png")
}
Tizilla.shadLib = getShadLib(Tizilla.imgLib)
# calculate its position
Monster.__init__(self, "tizilla", (255,0,0,240), 6, 1, onlayer, sideGroup)
wall = self.initLayer(wallGroup, sideGroup)
# initialize the sprite
self.reset()
self.mask = pygame.mask.from_surface(self.image)
self.rect = self.image.get_rect()
self.rect.left = wall.rect.left
self.rect.bottom = wall.rect.top
self.alterSpeed( choice([1,-1]) )
def move(self, delay, sprites):
self.checkHitBack(obstacle=True)
self.count_stun()
self.rect.bottom += self.gravity
if self.stun_time==0:
self.rect.left += self.speed
if (getPos(self,0.8,0)[0] >= self.scope[1] and self.speed > 0) or (getPos(self,0.2,0)[0] <= self.scope[0] and self.speed < 0):
self.alterSpeed(-self.speed)
if not (delay % 8):
self.imgIndx = (self.imgIndx+1) % len(self.imgLib["iList"]["left"])
self.setImg("iList",self.imgIndx)
if ( self.coolDown==0 ):
for each in sprites:
if collide_mask(self, each):
self.coolDown = 40
if (self.coolDown > 0):
self.coolDown -= 1
self.cratch( sprites )
# Check Hero and Turn speed
self.detectHero(sprites)
def cratch(self, sprites):
if (self.coolDown <= 22):
return
if (self.coolDown >= 36):
self.attIndx = 0
elif (self.coolDown >= 32):
self.attIndx = 1
elif (self.coolDown >= 29):
self.attIndx = 2
elif (self.coolDown >= 26):
self.attIndx = 3
if ( self.coolDown == 26 ):
cldList( self, sprites )
trPos = [ self.rect.left + self.rect.width//2, self.rect.bottom ]
self.setImg("attList",self.attIndx)
self.mask = pygame.mask.from_surface(self.image)
self.rect = self.image.get_rect()
self.rect.left = trPos[0]-self.rect.width//2
self.rect.bottom = trPos[1]
def reset(self):
self.imgIndx = 0
self.attIndx = 0
self.setImg("iList",0)
self.coolDown = 0
def level(self, dist):
self.rect.left += dist
self.scope = (self.scope[0]+dist, self.scope[1]+dist)
# -----------------------------------
class MegaTizilla(Monster):
imgLib = None
shadLib = None
fireSnd = None
def __init__(self, wallGroup, sideGroup, blockSize, onlayer):
if not self.imgLib:
MegaTizilla.imgLib = {
"iList": createImgList( "image/stg1/megaTizilla0.png", "image/stg1/megaTizilla1.png",
"image/stg1/megaTizilla0.png", "image/stg1/megaTizilla2.png" ),
"att": createImgList( "image/stg1/megaTizillaAtt.png"),
"alarm": createImgList("image/stg1/megaTizillaAlarm.png")
}
MegaTizilla.shadLib = getShadLib(MegaTizilla.imgLib)
MegaTizilla.fireSnd = pygame.mixer.Sound("audio/megaTizFire.wav")
# calculate its position
Monster.__init__(self, "megaTizilla", (255,0,0,240), 6, 3, onlayer, sideGroup)
wall = self.initLayer(wallGroup, sideGroup)
# initialize the sprite
self.reset()
self.mask = pygame.mask.from_surface(self.image)
self.rect = self.image.get_rect()
self.rect.left = wall.rect.left
self.rect.bottom = wall.rect.top
self.alterSpeed( choice([1,-1]) )
self.hitAccum = 0
def move(self, delay, sprites, canvas):
self.checkHitBack(obstacle=True)
self.count_stun()
if self.stun_time==0:
if (self.airCnt==0):
if not (delay%2):
self.rect.left += self.speed
self.rect.bottom += self.gravity
# touch the edge and turn around
if (getPos(self,0.75,0)[0] >= self.scope[1] and self.speed > 0) or (getPos(self,0.25,0)[0] <= self.scope[0] and self.speed < 0):
self.alterSpeed(-self.speed)
# renew image
if not (delay % 10):
trPos = [ self.rect.left+self.rect.width//2, self.rect.bottom ]
self.imgIndx = (self.imgIndx+1) % len(self.imgLib["iList"]["left"])
self.setImg("iList",self.imgIndx)
self.rect = self.image.get_rect()
self.rect.left = trPos[0]-self.rect.width//2
self.rect.bottom = trPos[1]
for hero in sprites:
heroPos = getPos(hero,0.5,0)
myPos = getPos(self,0.5,0)
# 如果有英雄在同一层,则将速度改为朝英雄方向。
if (hero.onlayer-1)==self.onlayer:
# 判断是否需要转向
if ( self.scope[0]<=heroPos[0]<=self.scope[1] ):
if self.speed*( heroPos[0]-myPos[0] ) < 0:
self.alterSpeed(-self.speed)
break # ***这里碰到第一个英雄符合条件就退出了。因此,如果两个英雄同时在一层中,P1总是会被针对,而P2永远不会被选中为目标。
# 判断是否需要攻击
if (self.speed<0 and -225<=heroPos[0]-myPos[0]<0) or (self.speed>0 and 0<heroPos[0]-myPos[0]<225):
self.airCnt = 76
self.fireSnd.play(0)
elif self.airCnt>0:
self.airCnt -= 1
if self.airCnt>60:
self.setImg("alarm")
else:
self.setImg("att")
if self.speed <= 0:
spd = [ choice([-3,-4]), choice([-2, -1, 1, 2]) ]
startX = 0.34
elif self.speed > 0:
spd = [ choice([3,4]), choice([-2, -1, 1, 2]) ]
startX = 0.66
# 每次刷新均吐出2个气团
canvas.addAirAtoms( self, 2, getPos(self, startX, 0.37), spd, sprites, "fire" )
def reportHit(self, tgt):
self.hitAccum += 1
if self.hitAccum>=12:
self.hitAccum = 0
tgt.hitted( self.damage, self.push, "fire" )
def reset(self):
self.imgIndx = 0
self.attIndx = 0
self.setImg("iList",0)
self.airCnt = 0 # indicate if spitting!
def level(self, dist):
self.rect.left += dist
self.scope = (self.scope[0]+dist, self.scope[1]+dist)
# -----------------------------------
class Dragon(Monster):
imgLib = None
shadLib = None
fireSnd = None
def __init__(self, wallHeight, onlayer, boundaries):
if not self.imgLib:
Dragon.imgLib = {
"iList": createImgList("image/stg1/dragonLeft0.png","image/stg1/dragonLeft1.png",
"image/stg1/dragonLeft2.png","image/stg1/dragonLeft3.png",
"image/stg1/dragonLeft2.png","image/stg1/dragonLeft1.png")
}
Dragon.shadLib = getShadLib(Dragon.imgLib)
Dragon.fireSnd = pygame.mixer.Sound("audio/dragonFire.wav")
# calculate its position
Monster.__init__(self, "dragon", (255,0,0,240), 0, 1, onlayer)
self.boundaries = boundaries
# initialize the sprite
self.reset()
self.mask = pygame.mask.from_surface(self.image)
self.rect = self.image.get_rect()
self.rect.left = randint(boundaries[0], boundaries[1]-self.rect.width)
self.rect.top = wallHeight - 190
self.alterSpeed( choice([-1, 1]) )
def move(self, delay):
self.checkHitBack()
self.count_stun()
if not (delay % 20):
self.rect.top += self.upDown
self.upDown = - self.upDown
if not (delay % 6 ):
self.imgIndx = (self.imgIndx+1) % len(self.imgLib["iList"]["left"])
self.setImg("iList",self.imgIndx)
if self.stun_time==0:
self.rect.left += self.speed
if (self.rect.left<=self.boundaries[0] and self.speed < 0) or (self.rect.right>=self.boundaries[1] and self.speed > 0):
self.alterSpeed(-self.speed)
# randomly fire
self.coolDown -= 1
if self.coolDown<=0:
self.fireSnd.play(0)
self.coolDown = randint(120,480)
return Fire(getPos(self, 0, 1), self.onlayer, -2, 0) if (self.direction=="left") else Fire(getPos(self,0.95,1), self.onlayer, 2, 0)
return None
def reset(self):
self.imgIndx = 0
self.setImg("iList",0)
self.coolDown = randint(240,480)
self.upDown = 2
def level(self, dist):
self.rect.left += dist
self.boundaries = (self.boundaries[0]+dist, self.boundaries[1]+dist)
class Fire(InanimSprite):
def __init__(self, pos, layer, speed, iniG):
InanimSprite.__init__(self, "fire")
self.ori_image = load("image/stg1/fire.png").convert_alpha()
self.image = self.ori_image.copy()
self.rect = self.image.get_rect()
self.rect.left = pos[0]
self.rect.bottom = pos[1]
self.mask = pygame.mask.from_surface(self.image)
self.damage = MB["dragon"].damage
self.dmgType = MB["dragon"].dmgType
self.onlayer = int(layer)
self.gravity = iniG
self.speed = speed
self.angle = 0
if speed>0:
self.push = 6
else:
self.push = -6
def update(self, delay, sideWalls, downWalls, keyLine, sprites, canvas, bg_size):
self.rect.left += self.speed
self.rect.bottom += self.gravity
canvas.addTrails( [4,5,6], [8,10,12], (240,190,0,220), getPos(self, 0.4+random()*0.3, 0.4+random()*0.3) )
if cldList( self, sprites ): # 命中英雄
self._explode(canvas)
return None
if ( pygame.sprite.spritecollide(self, downWalls, False, collide_mask) ) or ( pygame.sprite.spritecollide(self, sideWalls, False, collide_mask) ) or self.rect.top>=bg_size[1] or self.rect.right<=0 or self.rect.left>=bg_size[0]:
self._explode(canvas)
return None
if not (delay % 6):
if self.speed <= 0:
self.angle = (self.angle+40) % 360
else:
self.angle = (self.angle-40) % 360
self.image = rot_center(self.ori_image, self.angle)
if (self.gravity < 5):
self.gravity += 1
if ( self.rect.top >= keyLine ): # 因为只有大于检查,因此只有初始行之下的砖块会与之碰撞
self.onlayer = max(self.onlayer-2, -1)
return None
def _explode(self, canvas):
canvas.addSpatters( 5, [4,5,6], [18,20,22], (20,10,10,255), getPos(self,0.5,0.8), True )
canvas.addSmoke(5, (6,8,10), 6, (10,10,10,180), getPos(self,0.5,0.8), 10)
self.kill()
del self
#------------------------------------
class DragonEgg(Monster):
imgLib = None
shadLib = None
fireSnd = None
crushSnd = None
def __init__(self, wallGroup, sideGroup, onlayer):
if not self.imgLib:
DragonEgg.imgLib = {
"iList": createImgList("image/stg1/dragonEgg.png", "image/stg1/dragonEggBroken.png"),
"baby": createImgList("image/stg1/eggBaby.png", "image/stg1/eggBabyAtt.png")
}
DragonEgg.shadLib = getShadLib(DragonEgg.imgLib)
DragonEgg.fireSnd = pygame.mixer.Sound("audio/dragonFire.wav")
DragonEgg.crushSnd = pygame.mixer.Sound("audio/wizard/hit.wav")
# calculate its position
Monster.__init__(self, "dragonEgg", (250,0,80,240), 0, 4, onlayer, sideGroup)
# note: 这里bowler的onlayer,以及其stone的onlayer值均为砖的行数,并非自身的行数,使用时不需要-1操作
self.wallList = [] # 存储本行的所有砖块;
for aWall in wallGroup: # 由于spriteGroup不好进行索引/随机选择操作,因此将其中的sprite逐个存入列表中存储
self.wallList.append(aWall)
wall = choice(self.wallList)
# initialize the sprite
self.imgIndx = 0
self.setImg("iList",0)
self.attCnt = 0
self.mask = pygame.mask.from_surface(self.image)
self.rect = self.image.get_rect()
self.rect.left = wall.rect.left
self.rect.bottom = wall.rect.top
# Inner baby dragon
self.babyR = { "left":[ (0.5,0.04), (0.52,0.06) ], "right":[ (0.5,0.04), (0.48,0.06) ] } # 分别为左和右时的位置信息
self.baby = Ajunction(
self.imgLib["baby"][self.direction][0],
getPos(self, self.babyR[self.direction][0][0],
self.babyR[self.direction][0][1])
)
self.barOffset = 30
def move(self, delay, sprites):
if self.health<=0:
return
# checkhitback: 仅计算时间,不进行位移
if abs(self.hitBack)>0:
self.hitBack -= self.hitBack//abs(self.hitBack)
# update baby image. Note: 因为有壳保护,龙宝不会受到stun效果影响。
if self.attCnt>0:
self.attCnt -= 1
r = self.babyR[self.direction][1]
bimg = self.imgLib["baby"][self.direction][1]
else:
r = self.babyR[self.direction][0]
bimg = self.imgLib["baby"][self.direction][0]
self.baby.updateImg( bimg )
self.baby.updatePos( getPos(self, r[0], r[1]) )
if not (delay % 30 ):
heroX = choice(sprites).rect.left
if self.rect.left > heroX:
self.direction = "left"
else:
self.direction = "right"
# randomly fire
if not (delay % 60) and random()<0.4: # Controlling the frequency of fire.
self.attCnt = 12
self.fireSnd.play(0)
if self.direction=="left":
return Fire(getPos(self,0.2,0), self.onlayer+2, randint(-3,-1), -2)
else:
return Fire(getPos(self,0.8,0), self.onlayer+2, randint(1,3), -2)
def stun(self, duration):
pass
def hitted(self, damage, pushed, dmgType):
# decrease health & no hitback
if self.health<=0:
return False
true_dmg = round(damage*self.realDmgRate)
self.health -= true_dmg
self.msgList.append( [getPos(self,0.5,0.5), str(true_dmg), 60] )
if self.health <= 0: # broken
self.health = 0
self.crushSnd.play(0)
self.spurtCanvas.addPebbles(self, 6, type="eggDebri")
return True
def paint(self, screen):
# draw shadow
shadRect = self.rect.copy()
shadRect.left -= 8
screen.blit(self.shad, shadRect)
# draw baby
if self.health>0:
screen.blit( self.baby.image, self.baby.rect )
screen.blit( self.image, self.rect )
# Hit highlight
if self.hitBack:
screen.blit( self.shad, self.rect )
# Goalie ----------------------------
class HellHound(Monster):
imgLib = None
shadLib = None
def __init__(self, wallGroup, sideGroup, blockSize, onlayer):
if not self.imgLib:
HellHound.imgLib = {
"iList": createImgList("image/stg1/hellHound0.png", "image/stg1/hellHound1.png"),
"jump": createImgList("image/stg1/hellHound_Att.png"),
"knock": createImgList("image/stg1/hellHound_Land.png")
}
HellHound.shadLib = getShadLib(HellHound.imgLib)
HellHound.mockSnd = pygame.mixer.Sound("audio/wolf.wav")
HellHound.knockSnd = pygame.mixer.Sound("audio/chichengKnock.wav")
# calculate its position
Monster.__init__(self, "hellHound", (255,0,0,240), 6, 3, onlayer, sideGroup, bar=True)
#self.bar = HPBar(self.full, blockVol=200, barH=12)
wall = self.initLayer(wallGroup, sideGroup)
# initialize the sprite
self.reset()
self.mask = pygame.mask.from_surface(self.image)
self.rect = self.image.get_rect()
self.rect.left = wall.rect.left
self.rect.bottom = wall.rect.top
self.alterSpeed( choice([1,-1]) )
self.jumping = False
self.dealt = False
self.cnt = 350 # count for the loop of shift position
self.knockCnt = 0
self.max_spd = 5 # horrizontal
def move(self, sprites, canvas, groupList, knock, GRAVITY):
self.checkHitBack()
self.count_stun()
# when knockcnt>0, dealing damage
if self.knockCnt>0:
self.knockCnt -= 1
if not self.dealt:
if cldList(self, sprites):
self.dealt = True
else:
if knock: # on landing, start dealing damage
self.setImg("knock")
self.weaponIndx = 2
self.knockCnt = 18
self.dealt = False
self.push = self.pushList[0] if self.direction=="left" else self.pushList[1]
for i in (0.2,0.4,0.6,0.8):
canvas.addSpatters(randint(2,6), [3,5,7], [28,32,36], (30,20,20,210), getPos(self,i,0.99), True)
# knockCnt = 0的情况,可能的状态有很多
elif self.combo>0 and not self.jumping:
self.takeOff(sprites, groupList, GRAVITY)
self.combo -= 1
elif self.jumping: # jumping
self.setImg("jump")
else: # end dealing damage, back to waiting state
# update image.
if not (self.cnt % 10):
self.imgIndx = (self.imgIndx+1) % len(self.imgLib["iList"]["left"])
self.setImg("iList",self.imgIndx)
self.mask = pygame.mask.from_surface(self.image)
# flickering yellow
canvas.addSmoke( 1, (1,2,4), 5, (210,140,20,210), getPos(self,0.5,random()), 60 )
# count down for rage actions.
self.cnt -= 1
if self.cnt<=0:
self.cnt = randint(280,300)
if self.jumping:
self.rect.left += self.speed # Only consider x here. y is acted by fall()
elif not self.jumping and self.cnt==240:
if self.stun_time==0:
self.takeOff(sprites, groupList, GRAVITY)
self.combo = randint(1,3)
def takeOff(self, sprites, groupList, GRAVITY):
self.mockSnd.play(0)
tgt = choice(sprites)
self.jumping = True
tgtPos, myPos = getPos(tgt,0.5,1), getPos(self,0.5,1)
# 目标在下方
if tgt.onlayer-1<self.onlayer:
self.onlayer -= 2
self.gravity = -14
self.alarmTime = self.estimateTime(14, tgtPos[1]-myPos[1], GRAVITY)
# 否则目标在上方或同行
else:
if (tgt.onlayer-1>self.onlayer) and (str(self.onlayer+2) in groupList):
# need to confirm that new layer is in groupList (in case of hero jump to the highest layer)
self.onlayer += 2
self.gravity = -18
self.alarmTime = self.estimateTime(18, tgtPos[1]-myPos[1], GRAVITY)
while True:
try:
self.initLayer(groupList[str(self.onlayer)], groupList["0"])
except:
self.onlayer -= 2
else:
break
x_spd = round( (tgtPos[0]-myPos[0])/self.alarmTime )
if x_spd<-self.max_spd:
x_spd = -self.max_spd
elif x_spd>self.max_spd:
x_spd = self.max_spd
self.alterSpeed( x_spd )
def estimateTime(self, upwardSpd, absVertical, GRAVITY):
absDistY = 0
for y in range(upwardSpd+1): # 计算得上升总高度(此过程用时upwardSod减至0)
absDistY += y
downY = abs(absVertical) + absDistY # 下降总位移
anaSpd = 0
estTime = upwardSpd
while downY>0:
absDistY += anaSpd
downY -= anaSpd
anaSpd = min(anaSpd+1, GRAVITY)
estTime += 1
return estTime
def fall(self, keyLine, groupList, GRAVITY):
if self.gravity<GRAVITY:
self.gravity += 1
self.rect.bottom += self.gravity
if self.gravity<0: # Still in upward action, don't check fall!
return
while ( pygame.sprite.spritecollide(self, self.wallList, False, collide_mask) ): # 如果和参数中的物体相撞,则尝试纵坐标-1
self.rect.bottom -= 1
self.gravity = 0
if self.jumping:
self.knockSnd.play(0)
self.jumping = False
return "vib"
if getPos(self, 0, 0.5)[1] > keyLine:
self.onlayer = max(self.onlayer-2, -1)
self.initLayer( groupList[str(self.onlayer)], groupList["0"] )
# 更新obstacle砖块