-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmusou_kokaton.py
483 lines (402 loc) · 16.9 KB
/
musou_kokaton.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
import math
import random
import sys
import time
import pygame as pg
from pygame.sprite import AbstractGroup
WIDTH = 1600 # ゲームウィンドウの幅
HEIGHT = 900 # ゲームウィンドウの高さ
def check_bound(obj: pg.Rect) -> tuple[bool, bool]:
"""
オブジェクトが画面内か画面外かを判定し,真理値タプルを返す
引数 obj:オブジェクト(爆弾,こうかとん,ビーム)SurfaceのRect
戻り値:横方向,縦方向のはみ出し判定結果(画面内:True/画面外:False)
"""
yoko, tate = True, True
if obj.left < 0 or WIDTH < obj.right: # 横方向のはみ出し判定
yoko = False
if obj.top < 0 or HEIGHT < obj.bottom: # 縦方向のはみ出し判定
tate = False
return yoko, tate
def calc_orientation(org: pg.Rect, dst: pg.Rect) -> tuple[float, float]:
"""
orgから見て,dstがどこにあるかを計算し,方向ベクトルをタプルで返す
引数1 org:爆弾SurfaceのRect
引数2 dst:こうかとんSurfaceのRect
戻り値:orgから見たdstの方向ベクトルを表すタプル
"""
x_diff, y_diff = dst.centerx-org.centerx, dst.centery-org.centery
norm = math.sqrt(x_diff**2+y_diff**2)
return x_diff/norm, y_diff/norm
class Bird(pg.sprite.Sprite):
"""
ゲームキャラクター(こうかとん)に関するクラス
"""
delta = { # 押下キーと移動量の辞書
pg.K_UP: (0, -1),
pg.K_DOWN: (0, +1),
pg.K_LEFT: (-1, 0),
pg.K_RIGHT: (+1, 0),
}
def __init__(self, num: int, xy: tuple[int, int]):
"""
こうかとん画像Surfaceを生成する
引数1 num:こうかとん画像ファイル名の番号
引数2 xy:こうかとん画像の位置座標タプル
"""
super().__init__()
img0 = pg.transform.rotozoom(pg.image.load(f"ex04/fig/{num}.png"), 0, 2.0)
img = pg.transform.flip(img0, True, False) # デフォルトのこうかとん
self.imgs = {
(+1, 0): img, # 右
(+1, -1): pg.transform.rotozoom(img, 45, 1.0), # 右上
(0, -1): pg.transform.rotozoom(img, 90, 1.0), # 上
(-1, -1): pg.transform.rotozoom(img0, -45, 1.0), # 左上
(-1, 0): img0, # 左
(-1, +1): pg.transform.rotozoom(img0, 45, 1.0), # 左下
(0, +1): pg.transform.rotozoom(img, -90, 1.0), # 下
(+1, +1): pg.transform.rotozoom(img, -45, 1.0), # 右下
}
self.dire = (+1, 0)
self.image = self.imgs[self.dire]
self.rect = self.image.get_rect()
self.rect.center = xy
self.speed = 10
self.state = "normal"
self.hyper_life = 0
self.boosted_speed = 20 #ブースト時の速度
def change_img(self, num: int, screen: pg.Surface):
"""
こうかとん画像を切り替え,画面に転送する
引数1 num:こうかとん画像ファイル名の番号
引数2 screen:画面Surface
"""
self.image = pg.transform.rotozoom(pg.image.load(f"ex04/fig/{num}.png"), 0, 2.0)
screen.blit(self.image, self.rect)
def change_state(self,state:str,hyper_life:int):
"""
引数1 hyperモードとnormalモードを切り替える
引数2 hyperモードの時間 500,normal -1
"""
self.state = state
self.hyper_life = hyper_life
def update(self, key_lst: list[bool], screen: pg.Surface):
"""
押下キーに応じてこうかとんを移動させる
引数1 key_lst:押下キーの真理値リスト
引数2 screen:画面Surface
"""
sum_mv = [0, 0]
for k, mv in __class__.delta.items():
if key_lst[k] and key_lst[pg.K_LSHIFT]:
#if key_lst[pg.K_LSHIFT]: #左shiftキーを押された場合、速度ブーストを適用する
self.rect.move_ip(self.boosted_speed * mv[0], self.boosted_speed * mv[1])
sum_mv[0] += mv[0]
sum_mv[1] += mv[1]
elif key_lst[k]:
self.rect.move_ip(self.speed * mv[0], self.speed * mv[1])
sum_mv[0] += mv[0]
sum_mv[1] += mv[1]
if check_bound(self.rect) != (True, True):
for k, mv in __class__.delta.items():
if key_lst[k]:
self.rect.move_ip(-self.speed*mv[0], -self.speed*mv[1])
if not (sum_mv[0] == 0 and sum_mv[1] == 0):
self.dire = tuple(sum_mv)
self.image = self.imgs[self.dire]
if self.state == "hyper":
self.image = pg.transform.laplacian(self.image)
self.hyper_life -= 1
if self.hyper_life < 0:
self.change_state("normal",-1)
screen.blit(self.image, self.rect)
def get_direction(self) -> tuple[int, int]:
return self.dire
def get_speed(self) -> int:
if self.imgs == (0, 0): # 方向が停止している場合は0を返す
return 0
elif self.dire == (1, 0): # 右方向の場合はブースト時の速度を返す
return self.boosted_speed
else: # それ以外の方向の場合は通常の速度を返す
return self.speed
class Bomb(pg.sprite.Sprite):
"""
爆弾に関するクラス
"""
colors = [(255, 0, 0), (0, 255, 0), (0, 0, 255), (255, 255, 0), (255, 0, 255), (0, 255, 255)]
def __init__(self, emy: "Enemy", bird: Bird):
"""
爆弾円Surfaceを生成する
引数1 emy:爆弾を投下する敵機
引数2 bird:攻撃対象のこうかとん
"""
super().__init__()
rad = random.randint(10, 50) # 爆弾円の半径:10以上50以下の乱数
color = random.choice(__class__.colors) # 爆弾円の色:クラス変数からランダム選択
self.image = pg.Surface((2*rad, 2*rad))
pg.draw.circle(self.image, color, (rad, rad), rad)
self.image.set_colorkey((0, 0, 0))
self.rect = self.image.get_rect()
# 爆弾を投下するemyから見た攻撃対象のbirdの方向を計算
self.vx, self.vy = calc_orientation(emy.rect, bird.rect)
self.rect.centerx = emy.rect.centerx
self.rect.centery = emy.rect.centery+emy.rect.height/2
self.speed = 6
def update(self):
"""
爆弾を速度ベクトルself.vx, self.vyに基づき移動させる
引数 screen:画面Surface
"""
self.rect.move_ip(+self.speed*self.vx, +self.speed*self.vy)
if check_bound(self.rect) != (True, True):
self.kill()
class Beam(pg.sprite.Sprite):
"""
ビームに関するクラス
"""
def __init__(self, bird: Bird, deg=0):
"""
ビーム画像Surfaceを生成する
引数 bird:ビームを放つこうかとん
deg: 回転角度
"""
super().__init__()
self.vx, self.vy = bird.get_direction()
angle = math.degrees(math.atan2(-self.vy, self.vx))
# こうかとんの向きに合わせての角度 + ビームの角度
self.image = pg.transform.rotozoom(pg.image.load(f"ex04/fig/beam.png"), angle+deg, 2.0)
self.vx = math.cos(math.radians(angle+deg))
self.vy = -math.sin(math.radians(angle+deg))
self.rect = self.image.get_rect()
self.rect.centery = bird.rect.centery+bird.rect.height*self.vy
self.rect.centerx = bird.rect.centerx+bird.rect.width*self.vx
self.speed = 10
def update(self):
"""
ビームを速度ベクトルself.vx, self.vyに基づき移動させる
引数 screen:画面Surface
"""
self.rect.move_ip(+self.speed*self.vx, +self.speed*self.vy)
if check_bound(self.rect) != (True, True):
self.kill()
class NeoBeam(pg.sprite.Sprite):
"""
多方向に多数ビームを出せるようにするクラス
"""
def __init__(self, bird :Bird, num):
"""
Args:
bird (Bird): ビームを放つこうかとん_
num (_type_): ビームの数
"""
self.num = num
self.bird = bird
self.beam_lst = []
def gen_beams(self):
# num個ビームを作る 0~100度を割って50度引く
for i in range(self.num):
self.beam_lst.append(Beam(self.bird, (100*i/self.num+1)-50))
return self.beam_lst
class Explosion(pg.sprite.Sprite):
"""
爆発に関するクラス
"""
def __init__(self, obj: "Bomb|Enemy", life: int):
"""
爆弾が爆発するエフェクトを生成する
引数1 obj:爆発するBombまたは敵機インスタンス
引数2 life:爆発時間
"""
super().__init__()
img = pg.image.load("ex04/fig/explosion.gif")
self.imgs = [img, pg.transform.flip(img, 1, 1)]
self.image = self.imgs[0]
self.rect = self.image.get_rect(center=obj.rect.center)
self.life = life
def update(self):
"""
爆発時間を1減算した爆発経過時間_lifeに応じて爆発画像を切り替えることで
爆発エフェクトを表現する
"""
self.life -= 1
self.image = self.imgs[self.life//10%2]
if self.life < 0:
self.kill()
class Enemy(pg.sprite.Sprite):
"""
敵機に関するクラス
"""
imgs = [pg.image.load(f"ex04/fig/alien{i}.png") for i in range(1, 4)]
def __init__(self):
super().__init__()
self.image = random.choice(__class__.imgs)
self.rect = self.image.get_rect()
self.rect.center = random.randint(0, WIDTH), 0
self.vy = +6
self.bound = random.randint(50, HEIGHT/2) # 停止位置
self.state = "down" # 降下状態or停止状態
self.interval = random.randint(50, 300) # 爆弾投下インターバル
def update(self):
"""
敵機を速度ベクトルself.vyに基づき移動(降下)させる
ランダムに決めた停止位置_boundまで降下したら,_stateを停止状態に変更する
引数 screen:画面Surface
"""
if self.rect.centery > self.bound:
self.vy = 0
self.state = "stop"
self.rect.centery += self.vy
class Shield(pg.sprite.Sprite):
def __init__(self, bird: Bird, life: int):
"""
birdこうかとん防御壁発動
life発動時間
"""
super().__init__()
self.image = pg.Surface((20, bird.rect.height*2))
vx, vy = bird.get_direction()
angle = math.degrees(math.atan2(-vy, vx))
self.image = pg.transform.rotozoom(self.image, angle, 1.0)
vx = math.cos(math.radians(angle))
vy = -math.sin(math.radians(angle))
pg.draw.rect(self.image, (0, 0, 0), pg.Rect(0, 0, 20, bird.rect.height*2))
self.rect = self.image.get_rect()
self.rect.centerx = bird.rect.centerx+bird.rect.width*vx
self.rect.centery = bird.rect.centery+bird.rect.height*vy
self.life = life
def update(self):
"""
発動時間を1減算
"""
self.life -= 1
if self.life < 0:
self.kill()
class Score:
"""
打ち落とした爆弾,敵機の数をスコアとして表示するクラス
爆弾:1点
敵機:10点
"""
def __init__(self):
self.font = pg.font.Font(None, 50)
self.color = (0, 0, 255)
self.score = 0
self.image = self.font.render(f"Score: {self.score}", 0, self.color)
self.rect = self.image.get_rect()
self.rect.center = 100, HEIGHT-50
def score_up(self, add):
self.score += add
def update(self, screen: pg.Surface):
self.image = self.font.render(f"Score: {self.score}", 0, self.color)
screen.blit(self.image, self.rect)
def score_down(self,down):#score減少関数
self.score -= down
class Gravity(pg.sprite.Sprite):
"""重力球"""
def __init__(self,bird,size,life):
super().__init__()
self.bird = bird
self.size = size
self.life = life
self.image = pg.Surface((size, size)) # 透明なサーフェスを作成
self.image.set_alpha(200)
self.image.set_colorkey((0,0,0))
pg.draw.circle(self.image, (10,10,10), (size/2, size/2), size/2)
self.rect = self.image.get_rect(center=bird.rect.center) # 重力球の位置を鳥の中心に設定
def update(self):
self.life -= 1
if self.life <= 0:
self.kill()
def main():
pg.display.set_caption("真!こうかとん無双")
screen = pg.display.set_mode((WIDTH, HEIGHT))
bg_img = pg.image.load("ex04/fig/pg_bg.jpg")
score = Score()
bird = Bird(3, (900, 400))
bombs = pg.sprite.Group()
beams = pg.sprite.Group()
exps = pg.sprite.Group()
emys = pg.sprite.Group()
gravity = pg.sprite.Group()
super_bird = pg.sprite.Group()
super_bird.add(Bird(3, (900, 400)))
shields = pg.sprite.Group()
tmr = 0
clock = pg.time.Clock()
while True:
key_lst = pg.key.get_pressed()
for event in pg.event.get():
if event.type == pg.QUIT:
return 0
if pg.key.get_pressed()[pg.K_SPACE] and pg.key.get_pressed()[pg.K_LSHIFT]:
beams.add(NeoBeam(bird, 5).gen_beams())
elif event.type == pg.KEYDOWN and event.key == pg.K_SPACE:
beams.add(Beam(bird))
if event.type == pg.KEYDOWN and event.key == pg.K_TAB and score.score>=50:
score.score_up(-50)
gravity.add(Gravity(bird,200,500))
if event.type == pg.KEYDOWN and event.key == pg.K_RSHIFT:
if score.score >= 100:#ここの数字を変動すれば発動条件が変化
score.score_down(100)
bird.change_state("hyper",500)
if event.type == pg.KEYDOWN and event.key == pg.K_CAPSLOCK and len(shields) == 0:
if score.score >= 50:
shields.add(Shield(bird, 400))
score.score_up(-50)
screen.blit(bg_img, [0, 0])
if tmr%200 == 0: # 200フレームに1回,敵機を出現させる
emys.add(Enemy())
for emy in emys:
if emy.state == "stop" and tmr%emy.interval == 0:
# 敵機が停止状態に入ったら,intervalに応じて爆弾投下
bombs.add(Bomb(emy, bird))
for emy in pg.sprite.groupcollide(emys, beams, True, True).keys():
exps.add(Explosion(emy, 100)) # 爆発エフェクト
score.score_up(10) # 10点アップ
bird.change_img(6, screen) # こうかとん喜びエフェクト
for bomb in pg.sprite.groupcollide(bombs, beams, True, True).keys():
exps.add(Explosion(bomb, 50)) # 爆発エフェクト
score.score_up(1) # 1点アップ
for bomb in pg.sprite.spritecollide(bird,bombs,True):
if bird.state == "hyper":
exps.add(Explosion(bomb, 50)) # 爆発エフェクト
score.score_up(1) # 1点アップ
else:
bird.change_img(8, screen) # こうかとん悲しみエフェクト
score.update(screen)
pg.display.update()
time.sleep(2)
return
for bomb in pg.sprite.groupcollide(bombs, shields, True, False).keys():
exps.add(Explosion(bomb, 50)) # 爆発エフェクト
score.score_up(1) # 1点アップ
if len(pg.sprite.spritecollide(bird, bombs, True)) != 0:
bird.change_img(8, screen) # こうかとん悲しみエフェクト
score.update(screen)
pg.display.update()
time.sleep(2)
return
for bomb in pg.sprite.groupcollide(bombs,gravity,True,False).keys():
exps.add(Explosion(bomb,50))
gravity.update()
gravity.draw(screen)
bird.update(key_lst, screen)
beams.update()
beams.draw(screen)
emys.update()
emys.draw(screen)
bombs.update()
bombs.draw(screen)
exps.update()
exps.draw(screen)
score.update(screen)
shields.update()
shields.draw(screen)
pg.display.update()
tmr += 1
clock.tick(50)
if __name__ == "__main__":
pg.init()
main()
pg.quit()
sys.exit()