見出し画像

pyxelでガンシューティングを作ってみる

好きなゲームのジャンルはいろいろありますが、ゲーセンの民だった自分としては最近下火のガンシューを応援したいところです。
というわけで、このnoteではpyxelを使ってガンシューを作ってみます。pyxelはpythonという言語を使うゲームエンジンですが、使い方がシンプルで個人的にはおすすめです。
まず、ガンシュー(ガンシューティング)というゲームジャンルですが、よくゲームセンターにある画面に向かって銃を撃つ形でプレイするゲームです。コントローラーが銃を模していて、ハウスオブザデッドやタイムクライシスなどが有名なタイトルですね。基本的には一人称で、決まったルートを動きます。レールシューティングとか呼ばれることもあるようです。

さて、前置きはこれくらいにしてコードを載せていこうと思います。

from random import randint
import pyxel

pyxelとrandomをインポートします。

class App:
   def __init__(self):
       self.game_start = False
       self.game_end = False
       self.game_over = False
       self.score = 0
       self.shot_flug = 0
       self.hit_flug = 0
       self.damage_flug = False
       self.damage_count = 0
       self.lifes = []
       self.life_count = 0
       for i in range(4):
           self.life_count = self.life_count + 1
           new_life = Life(self.life_count)
           self.lifes.append(new_life)
       self.bangs = []
       self.enemys = []
       for i in range(1):
           new_enemy = Enemy()
           self.enemys.append(new_enemy)
       pyxel.init(240, 200, caption="Shoot Pyxel")
       pyxel.load("C:/pyxel/image/shoot.pyxel")
       pyxel.run(self.update, self.draw)

変数を初期化しています。lifes(プレイヤーライフ)、bangs(射撃エフェクト)、enemys(敵)はリストで管理します。プレイヤーライフは4個にしました。

    def update(self):
        if pyxel.btnp(pyxel.KEY_Q):
            pyxel.quit()
        if pyxel.btnp(pyxel.KEY_R):
            self.game_start = False
        if pyxel.btnp(pyxel.KEY_S):
            self.game_start = True
            self.retry()
            
        #敵のアップデート
        if self.game_end == False:
          if self.score < 1000:
              if pyxel.frame_count % 60 == 0:
                 enemy_flug = 1
                 for e in range(enemy_flug):
                     new_enemy = Enemy()
                     self.enemys.append(new_enemy)
          elif self.score >= 1000:
              if pyxel.frame_count % 60 == 0:
                  enemy_flug = randint(1, 4)
                  for e in range(enemy_flug):
                      new_enemy = Enemy()
                      self.enemys.append(new_enemy)
          elif self.score >= 8000:
              if pyxel.frame_count % 60 == 0:
                  enemy_flug = randint(1, 6)
                  for e in range(enemy_flug):
                      new_enemy = Enemy()
                      self.enemys.append(new_enemy)
          elif self.score >= 15000:
              if pyxel.frame_count % 60 == 0:
                  enemy_flug = randint(1, 10)
                  for e in range(enemy_flug):
                      new_enemy = Enemy()
                      self.enemys.append(new_enemy)

次にゲームコントロール系のキー制御と、敵の出現パターンを決めていきます。Qで終了、Rでリトライ、Sでゲームスタートとします。敵はスコアによって出現パターンを変えて、高スコアになればなるほど一度に多くの敵が出現するようにしています。出現感覚は60フレーム経過ごとです。出現のトリガーをゲーム起動からのフレーム経過で見ているので、ゲームスタートのタイミングによっては、いきなり敵が出現してしまいます。ちょっと微妙ですが、今回は放置しました。

        #敵の攻撃
       enemy_count = len(self.enemys)
       for e in range(enemy_count):
           #プレイヤーがダメージを受けた際の疑似的な無敵処理
           if self.damage_flug == True:
               self.enemys[e].ene_f = 0
               self.enemys[e].ene_s = False
           else:
               self.enemys[e].ene_f = self.enemys[e].ene_f + 1
           #射撃体勢に入ったら5フレで攻撃
           if self.enemys[e].ene_s == True:
               if self.enemys[e].ene_f > 5:
                   self.damage_flug = True
                   self.enemys[e].ene_s = False
                   self.enemys[e].ene_f = 0
                   self.life_count = self.life_count - 1
                   break
                   
           #出現から一定フレーム経過で射撃体勢に移行
           if ((self.enemys[e].ene_v == 2) or (self.enemys[e].ene_v == 3)):
               if self.enemys[e].ene_f > 60:
                   self.enemys[e].ene_s = True
                   self.enemys[e].ene_f = 0
           else:
               if self.enemys[e].ene_f > 120:
                   self.enemys[e].ene_s = True
                   self.enemys[e].ene_f = 0

敵の攻撃はene_fとene_sの変数で管理します。これらの変数は各enemyオブジェクトが持っています。
ene_fは1フレーム経過で1加算されます。敵の出現から増えていき一定フレーム経過で攻撃準備になります。今回は敵の種類によって、60フレームと120フレームに分けました。攻撃準備になった段階でene_fは一度0になります。
ene_sは攻撃準備のフラグです。このフラグが立つと、画面上の敵に「!」が表示され、5フレーム後に攻撃してきます。イメージはバーチャコップ的なものを目指してみました。
動きとしては「敵の出現(ene_fカウントアップ開始)」→「一定フレーム経過(ene_fを0に戻す。ene_s=True)」→「5フレーム経過で攻撃」の流れです。プレイヤーがダメージを受けた際はene_fとene_sを初期化して、無敵処理っぽくしています。

        #射撃エフェクトの制御
       self.shot_flug = 0
       
       #射撃処理
       if pyxel.btnp(pyxel.MOUSE_LEFT_BUTTON)==True:
           mouse_x = pyxel.mouse_x
           mouse_y = pyxel.mouse_y
           enemy_count = len(self.enemys)
           self.shot_flug = self.shot_flug + 1
           #敵との当たり判定
           for e in range (enemy_count):
               if ((self.enemys[e].ene_x <= mouse_x 
                  <= self.enemys[e].ene_x + 16)and(self.enemys[e].ene_y 
                  <= mouse_y <= self.enemys[e].ene_y + 32)):
                   #ヒットエフェクト
                   new_bang = Bang(self.enemys[e].ene_x, 
                                     self.enemys[e].ene_y)
                   self.bangs.append(new_bang)
                   self.hit_flug = 0
                   del self.enemys[e]
                   self.score = self.score + 100
                   break#敵に当たったらbreak
               
       
       #ヒットエフェクトの制御
       self.hit_flug = self.hit_flug + 1
       if self.hit_flug > 5:
           if len(self.bangs) > 0:
               del self.bangs[0]
               self.hit_flug = 0
       
       #ダメージ時のフラグ管理
       if self.damage_flug == True:
           self.damage_count = self.damage_count + 1
       if self.damage_count > 10:
           self.damage_count = 0
           self.damage_flug = False
           
       #ゲームオーバー判定
       if self.life_count < 1:
           self.game_over = True

プレイヤーの射撃処理関連です。今回はマウスで動かして左クリックで射撃にしました。リロード処理はつけていません。いろいろ変数を使っていますが、やりたいこととしては「射撃時に画面を一瞬白くする」・「敵に当たったらエフェクトをつける」・「被弾時のエフェクトをつける」の3つです。
shot_flugは射撃時の画面エフェクトを制御する変数です。基本的には0でクリック時に1になります。1の時には画面が白で塗りつぶされます。
hit_flugは敵にヒットした際のエフェクト制御です。フレーム経過でカウントアップされ5フレーム経つとヒットエフェクトが消えるようになっています。
damage_flugはプレイヤーのダメージ制御です。Trueの場合はダメージ描画がされ、damage_countが加算されていきます。damage_count>10でFalseに戻ります。10フレームの無敵時間を作りたかったんですが、たぶんできてると思います。
ライフがなくなったらゲームオーバーです。

    def draw(self):
       pyxel.cls(4)
       
       #タイルマップの描画
       pyxel.bltm(0, 0, 0, 0, 0, 30, 25)
       
       #ライフの描画
       for l in range(self.life_count):
           pyxel.blt(16*self.lifes[l].life_c, 183, 2, 0, 16, 16, 16)
       
       #敵の描画
       enemy_count = len(self.enemys)
       for e in range(enemy_count):
           pyxel.blt(self.enemys[e].ene_x, self.enemys[e].ene_y,
                     0, 16*self.enemys[e].ene_v, 0, 16, 32, 13)
           if self.enemys[e].ene_s == True:
               pyxel.blt(self.enemys[e].ene_x+10, self.enemys[e].ene_y-2,
                     1, 24, 0, 8, 16, 13)
               
       #ヒットエフェクトの描画
       for i in self.bangs:
           pyxel.blt(i.bang_x, i.bang_y, 1, 0, 0, 16, 16, 13) 
       
       #射撃エフェクトの描画
       if self.shot_flug > 0:
           pyxel.cls(6)
       
       #照準の描画
       pyxel.blt(pyxel.mouse_x, pyxel.mouse_y, 1, 16, 0, 2, 2, 13) 
       
       #被ダメージ描写
       if self.damage_flug == True:
           pyxel.bltm(0, 0, 0, 0, 25, 30, 25, 0)
           
       #得点描写
       pyxel.text(1, 2, "SCORE:" + str(self.score), pyxel.frame_count % 16)
       
       #ゲームオーバー描画
       if self.game_over == True:
           pyxel.cls(0)
           pyxel.text(100, 60, "GAME OVER!! ", pyxel.frame_count % 16)
           pyxel.text(100, 70, "SCORE:"+ str(self.score),
                      pyxel.frame_count % 16)
           pyxel.text(100, 80, "R = RETRY ", pyxel.frame_count % 16)
           pyxel.text(100, 90, "Q = QUIT ", pyxel.frame_count % 16)
           
       #ゲームスタート画面
       if self.game_start == False:
           pyxel.cls(0)
           pyxel.text(100, 60, "READY? ", pyxel.frame_count % 16)
           pyxel.text(100, 70, "S = START ", pyxel.frame_count % 16)
           pyxel.text(100, 80, "Q = QUIT ", pyxel.frame_count % 16)

描画部分はupdate関数で設定した変数を使って、画像を表示させるだけなので単純です。ゲームオーバー時には画面を黒で塗りつぶして、スコアなどを表示します。ゲームがスタートしていない場合も同様に黒で塗りつぶします。

    def retry(self): #リトライ時のリセット関数
       self.game_end = False
       self.game_over = False
       self.score = 0
       self.shot_flug = 0
       self.hit_flug = 0
       self.damage_flug = False
       self.damage_count = 0
       self.lifes = []
       self.life_count = 0
       for i in range(4):
           self.life_count = self.life_count + 1
           new_life = Life(self.life_count)
           self.lifes.append(new_life)
       self.bangs = []
       self.enemys = []
       for i in range(1):
           new_enemy = Enemy()
           self.enemys.append(new_enemy)

リトライ時に変数を初期化する関数です。

class Life:
   def __init__(self, x):
       self.life_c = x
       
class Enemy:
   def __init__(self):
       self.ene_x = randint(20, 170)
       self.ene_y = randint(10, 130)
       self.ene_v = randint(0, 3)
       self.ene_f = 0
       self.ene_s = False
       
class Bang:
 def __init__(self, x, y):
     self.bang_x = x
     self.bang_y = y   

App()

各クラス(ライフ、敵、ヒットエフェクト)です。最後にApp()でゲームを起動させて、完成です。

画像1

画像のつくりは適当ですが、まあまあ形になったのではないでしょうか。もう少し改造して、敵の種類を増やしたり、場面を変えたりしてみたかったのですが、疲れたのでひとまず完成です。
今回使用したpyxelファイルも載せておきます。誰かの参考になれば幸いです。


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