Python(Pyxel)でインベーダーゲームを作ってみた

Pyxelとは?

github.com

Pyxel (ピクセル) はPython向けのレトロゲームエンジンです。 (引用)
Pythonレトロゲームが作れちゃうよ、という代物です。

作ってみたもの

f:id:Tiratom:20190101095736p:plain
Pyxelで作ったインベーダーゲームです。攻撃をくらいかけています

インベーダーゲームもどきです。 戦闘機を左や右に操って敵の攻撃を避けながら攻撃を繰り出し、敵を全滅させればクリアです。
左矢印・右矢印キーで戦闘機の左右移動、スペースキーで攻撃です。一度に5弾しか打てません。
攻撃を1発でも食らったり、敵に防衛ラインを超えられたらゲームオーバーです。
音楽や背景については、手抜きで作成してはいません…。
また、1面しかありません(笑)

実際のソース

github.com

クラス概要(べた書き・メモ書き)

  • Shotクラス
    敵・戦闘機の攻撃弾のベースとなるクラス。
    x座標(x)、y座標(y)、色(color)、状態(status)のフィールドを持っています。
  • FighterShotクラス
    戦闘機の先頭部分の座標を初期の座標として持ちます。
    updateメソッド で、移動(y座標を弾のスピード分ずつ減らす)します。
    drawメソッド で、弾の描画と、弾の残像(83行目)の描画を行います。
  • EnemyShotクラス
    基本的な部分はFighterShotクラスと似ています。
    ただ、FighterShotと異なり上から下に移動するため、移動時はy座標を増やしています。
  • Fighterクラス
    x座標(x)、y座標(y)、弾を打つ位置(shot_x)、状態(status)のフィールドを持っています。
    drawメソッドで、クラスで持つx座標、y座標の値をもとに戦闘機を描画します。
    戦闘機の絵自体は、pyxeleditorを利用してresource_file.pyxelファイル上に作成しているため、そこからの読み出しで描画しています。
    move_rightメソッド , move_leftメソッド で、戦闘機を左右に動かす際に必要な、x座標の値の更新を行います。 壁接触時にはx座標の更新ができないようにしています。
    explosion_drawメソッド は、敵の攻撃を食らった等戦闘機が爆発した際の描画を行います。
  • Enemyクラス
    コンストラクター内(init)において、引数として渡されたlevelの値に応じて初期値の設定を行います。
    x,yは座標、hpはHP(体力)、picture_position_x・picture_position_yは、resource_file.pyxelファイル内のイメージバンクにおいて、どの座標から敵の絵を描いたかの座標情報を持っています。
    scoreは撃墜時の得点、levelはレベル、shot_speedは弾のスピードを表します。
    updateメソッド で、その時点のフレーム数に応じて左右に移動します。
    drawメソッド で、敵の描画を行います。Fighterクラスのdrawメソッドと同様の描画方法です。
    explosion_draw で、爆発時の描画を行います。
  • EnemyListクラス
    前述のEnemyクラスをたくさん格納するクラスとなっています。
    __init__メソッド 内で敵(Enemyクラス)の初期化を行っています。
    drawメソッド で、保有する各Enemyクラスの描画処理を行います。内情は、各Enemyクラスのdrawメソッドを呼び出しているだけです。
    updateメソッド で、一定フレーム数経過後に戦闘機側に近寄ってくる処理を行います。
    また、各Enemyクラスのupdateメソッド(左右移動)の呼び出しも行います。
  • Appクラス
    このゲームのメインとなるクラスです。
    __init__メソッド(コンストラクター) で、ウィンドウの設定(pyxel.init)、リソースファイルの読み込み(pyxel.load)、各フィールド値の初期設定、アプリの開始(pyxel.run)を行います。
    shot_listは敵および戦闘機から発射された弾の情報を格納するフィールド、scoreは得点、explosion_listは、爆発描画を行う場所の情報を格納するフィールド、finish_frame_countは、ゲーム終了時のフレーム数を保有するフィールドです。
    did_enemy_shot_fighterメソッド は、敵の攻撃によって戦闘機が撃ち落されたかどうかのチェックを行います。
    did_fighter_shot_enemyメソッド は、戦闘機の攻撃によって敵を撃ち落としたかどうかのチェックを行います。
    did_enemy_hit_fighterメソッド は、敵が戦闘機とぶつかったかどうかをチェックします。
    shot_hit_checkメソッド は、弾同士がぶつかったかどうかをチェックします。ぶつかった場合には、その弾のステータスをInvalidに変更します。
    hit_checkメソッド は、前述のshot_hit_checkメソッドなどを呼び出します。弾や戦闘機・敵の接触についてのチェック処理及びstatusの更新、hpの更新などを行います。
    enemy_atackメソッド は、敵の攻撃に関する処理です。
    その時点での戦闘機のx座標を取得し、それに近いx座標を持つ敵(正確には敵の列)を3体抽出します。その3体のうちからランダム(np.random.choice)で攻撃を行う敵(の列)を決定します。
    選ばれた敵の列から、最も戦闘機に近い位置にいる(レベルが最も低い)敵を、攻撃を行う者として決定します。
    その攻撃者の座標などの情報をもとに、EnemyShotクラスを初期化し、shot_listに追加します。
    攻撃を行うかどうかはこのメソッドでは判断しません。
    updateメソッド では、まずゲームが終わっていないかチェックします。(290行目付近)終わっている場合には、Qボタンの押下チェックのみを行います。
    ゲームが終わっていない場合には、様々な更新処理を行います。
    ボタン(スペースキー、左・右キー)の押下処理や、敵の攻撃が行われるかどうかのチェック(敵の残り数に応じて攻撃可能性を変えています。)、衝突判定、弾や敵の更新処理、無効となった弾や敵の削除処理を行います。
    drawメソッド では、画面の基本的な表示や、explosion_listに値が存在する場合の爆発の描画、ゲーム終了時の文言表示を行います。

感想

Unityだとアッという間に作れちゃうのだろう と思いつつも、Pythonの書き方をいろいろ学べたのはよかったと思っています。 (ソースにおいて甘い・わかりづらい箇所はたくさんあると思いますが・・・)
次は業務でも使う可能性が高いC#で何か作ってみたいと思います。