インベーダー

【python】Pyxelでシューティングゲームを作る(続)

前回ソースをとりあえず丸ごと乗っけてみたので、今回からは各部分に分けて内容をメモしていこうと思います。

from random import randint
import pyxel

モジュールをインポートします。pyxelを使うのでpyxelは当然ですね。あとはランダム関数を使いたいのでrandamからrandintをインポートします。

WINDOW_H = 120
WINDOW_W = 160
SHIP_H = 16
SHIP_W = 16

これは…ウィンドウサイズと宇宙船のサイズを決めたんですよね、たぶん。

class APP:
   def __init__(self):
       self.IMG_ID0 = 0
       self.IMG_ID1 = 1
       self.IMG_ID0_X = 60
       self.IMG_ID0_Y = 65
       self.game_over = False
       self.game_end = False
       self.boss_flug = False
       self.boss_count = 1
       self.score = 0
       self.shots = []
       self.enemys = []
       self.boss_hp = 500
       self.bombs = []
       self.p_ship = Ship()
       

メインのクラスとなるAPPクラスです。最初はいろいろな変数を定義します。[]になっているところはリストです。shots(自機弾)、enemys(敵)、bombs(爆発エフェクト)はいっぱい出てくるので、リストで管理します。

       pyxel.init(WINDOW_W, WINDOW_H, caption="Hello Pyxel")
       # ドット絵を読み込む
       pyxel.load("/Users/xxxxxxxx/pyxel/image/sample.pyxel")
       
       pyxel.mouse(False)
       
       pyxel.run(self.update, self.draw)

pyxel.initでメインウィンドウを作成します。その後、付属のpyxeleditorで作ったドット絵を読み込んでいます。ファイルパスは該当の.pyxelファイルを直接指定しています。pyxeleditorについては別の機会にまとめてみたいと思います。mouseをfalseにしてカーソルを非表示、runでゲームが開始されます。ゲームはupdateとdrawの繰り返しで進行していきます。
(例)自機が移動する:位置情報を更新(update)→描画(draw)

   def update(self):
       if pyxel.btnp(pyxel.KEY_Q):
           pyxel.quit()
           
       #自機の更新
       if self.game_over == False:
           self.p_ship.update(pyxel.mouse_x, pyxel.mouse_y)
       
       shot_count = len(self.shots)
       for j in range (shot_count):
           if self.shots[j].pos_y > 10:
               self.shots[j].pos_y = self.shots[j].pos_y - 3
           else:
               del self.shots[j]
               break

update関数に入ります。最初のif文ではQが押された場合にゲームを終了できるようにしています。その後、自機の位置をマウス座標で更新して、自機弾の更新に移ります。
弾の更新はfor文で行っています。y座標は下に行くほど大きくなるので、上に進ませたい場合はマイナスする必要があります。画面内のすべての弾に対して-3をして、もしもy座標が10より小さくなったら、弾のオブジェクトを破棄しています。ウィンドウの天井に近づいたら消える処理です。

         #当たり判定
           #敵と弾
           shot_hit = len(self.shots)
           for h in range (shot_hit):
               enemy_hit = len(self.enemys)
               for e in range (enemy_hit):
                   if ((self.enemys[e].ene_x <= self.shots[h].pos_x 
                       <= self.enemys[e].ene_x + 20)and(self.enemys[e].ene_y 
                       <= self.shots[h].pos_y <= self.enemys[e].ene_y + 20)):
                       #敵に当たったらその座標に爆発を乗せる
                       new_bomb = Bomb(self.enemys[e].ene_x, 
                                       self.enemys[e].ene_y)
                       self.bombs.append(new_bomb)
                       del self.enemys[e]
                       if self.boss_flug == False:
                           self.score = self.score + 100
                       break#敵に当たったらbreak
               else:
                   continue
               break#敵に当たったらbreak
           
                  
           
       #敵と自機
       enemy_atk = len(self.enemys)
       for e in range (enemy_atk):
           if (((self.enemys[e].ene_x >= self.p_ship.ship_x) and
              (self.enemys[e].ene_x <= self.p_ship.ship_x + 15) and
              (self.enemys[e].ene_y >= self.p_ship.ship_y) and
              (self.enemys[e].ene_y <= self.p_ship.ship_y + 15))or
              ((self.enemys[e].ene_x + 15 >= self.p_ship.ship_x) and
              (self.enemys[e].ene_x + 15 <= self.p_ship.ship_x + 15) and
              (self.enemys[e].ene_y + 15 >= self.p_ship.ship_y) and
              (self.enemys[e].ene_y + 15 <= self.p_ship.ship_y + 15))):
                   self.game_over = True
                   new_bomb = Bomb(self.p_ship.ship_x, self.p_ship.ship_y)  
                   self.bombs.append(new_bomb)                        

当たり判定です。ごちゃごちゃになってしまいました…。基本的には弾と敵でも、敵と自機でも、x座標とy座標が対象の領域に入ったかを見ています。もっといい書き方がある気がするのですが…難しいです。当たった場合には爆発を乗っけています。敵は倒すと100点加算です。

        #enemy update
       if self.game_end == False:
           if self.boss_flug == False:
               if pyxel.frame_count % 20 == 0:
                   new_enemy = Enemy()
                   self.enemys.append(new_enemy)
           else:
               if pyxel.frame_count % 8 == 0:
                   new_enemy = Enemy()
                   self.enemys.append(new_enemy)
       
       enemy_count = len(self.enemys)
       for e in range (enemy_count):
           enemy_vec1 = randint(0, 7)
           enemy_vec2 = enemy_vec1 % 2
           if self.enemys[e].ene_y < 115:
               if enemy_vec2 > 0:
                   self.enemys[e].ene_x = self.enemys[e].ene_x + 4
                   self.enemys[e].ene_y = self.enemys[e].ene_y + 1.5
               else:
                   self.enemys[e].ene_x = self.enemys[e].ene_x - 4
                   self.enemys[e].ene_y = self.enemys[e].ene_y + 1.5
               
           else:
               del self.enemys[e]
               break
       
       #画面の爆発が3以上になったら古いものから消していく
       if len(self.bombs) > 3:
           del self.bombs[0]  

敵の更新です。frame.countが20で割り切れる場合に敵を追加で降らせます。20フレームごとに敵が出てくる感じですね。敵は左右にランダムに揺れながら下に降りてきます。ウィンドウの底に近づくと消えます。
boss_flugがTrue(ボス出現時)になると敵はそのままボスの弾になり、発生間隔が8フレームごとになります。ボスの弾は敵とは別に作ってみたいですね。

        #ボス出現フラグ    
       if self.boss_flug == False: #ボス未出現の状態で
           if self.score != 0:     #ゲーム開始直後ではなく
               if self.score % 2000 == 0: #スコアxxxx点に達したら
                   if self.game_end == False: #ゲームクリアフラグがない場合にボス発生
                       self.boss_flug = True
                       self.boss_hp = 200 * self.boss_count
       #ボスの当たり判定
       if self.boss_flug == True:        
           shot_hit = len(self.shots)        
           for h in range (shot_hit):
               if ((70 <= self.shots[h].pos_x <= 85)and
                   (10 <= self.shots[h].pos_y <= 20)):
                   self.boss_hp = self.boss_hp - 1
                   new_bomb = Bomb(self.shots[h].pos_x, self.shots[h].pos_y)
                   self.bombs.append(new_bomb)    
       #ボス消滅
       if self.boss_hp <= 0:
           if self.boss_flug == True:
               self.score = self.score + 5000
               pyxel.cls(0)
               self.boss_flug = False
               #self.game_end = True
               self.boss_count = self.boss_count + 1

ボス関連の部分です。2000点ごとにボスが出現します。インベーダーは1体100点なので、20体倒すごとですね。自機弾をfor文で処理してボスの弱点に当たったか判定しています。ちょっとここは手抜きになってしまいましたね…。座標も固定ですし。
ボスを倒したら、5000点加算して、フラグをfalseに戻します。また、boss_countを+1します。ボスの体力は200×boss_countなので、だんだんタフになる仕組みです。

ちょっと長くなりましたので、次のnoteに分けます。次は描画のdraw関数と、各オブジェクトについて書きたいと思います。


ここまで読んでいただきありがとうございます!