この記事は2025年1月に部内向けに書いたブログを外部公開向けに書き直したものです。
そのため、古い情報があるかもしれませんがご了承ください。
はじめに
こんにちは。koukawa_ppです。
さて、traQ(部内SNS)の方で一度書いたのですが、Pythonで使えるゲームエンジン「Pyxel」を試してみたいということで、なにかゲームを作ろうかと画策していました。
しかし、このPyxelというのはレトロゲームに特化しており、そのレトロゲームを作成するためのうまいネタが見つからず、かといって研究活動でなかなか思うように時間が取れず、四苦八苦していたところ、あることに思いいたりました。
皆さん、Scratchというのはご存じでしょうか。
はい、当時のマサチューセッツ工科大学のミッチェル・レズニック教授が作り、その後Scratch財団というところに運用主体が移動した、視覚的にゲームプログラミングができるという、当時としては画期的なツールでした。
というか今でもこのビジュアルプログラミングという意味では、とくにゲーム分野ではデファクトスタンダードではないでしょうか。
このScratchは、僕が小学生のころに親に紹介され、せっかくだからやってみたらどうか、ということで最初は何となくで始めたのですが、その後ものの見事にドハマりしまして、今でも思い出せるものから果ては思い出したくない黒歴史まで様々なものを作ったわけですが、その思い出せるものの一つとして、僕が今でもゲーム作りを行うときに頭にとどめるものであり、またはプレイするときでも好きなタイプのゲームでもあるといった、僕の根幹をなすようなゲームがあります。
今回はそれをPyxelに移植して、もしよろしければ遊んでみてください、と言うものです。
なお、使用するファイルはこちらのGitHubリポジトリにあります。
開発環境
- Windows 11 Home
- Visual Studio Code
移植するゲームの概要
タイトルはどう考えても黒歴史なのですが、「ビーム対戦ゲーム」と呼んでいました。
これについては、様々な派生バージョンがあるのですが、今回はその中でも「EX.」バージョンと「SuperEX.」バージョンを移植します。

ルールは非常に単純だと思います。
左側にいる赤いネコを矢印キーで操作して、右側にいる青いネコをビームで倒すというシンプルなものです。
ビームはスペースキーで打つことができ、直進するビームモードと青いネコに向けるビームモードはfキーで変えられます。
で、僕のゲームの根幹となっているものが何かというと、基本的にある一つのゲームシステムがあって、そのうえで様々な応用を考えるというものです。
今回のゲームにおけるゲームシステムは単純で、相手からのビームをよけながら、自分のビームで相手を倒す、というあるあるなものです。
それだけではもちろん退屈なので、ここに様々な変形を加えていきます。
例えば、相手のビームの打ち方が変わったらどうだろう?と言った感じです。
スピードが変わるとか、頻度が変わるとか、またはダメージが変わるとかです。
他に、相手の動き方が変わったらどうだろう?というのもあります。
上下の往復だけでもいいですが、フィールドのランダムなところに現れるなどです。
または、相手やこちらのライフの初期値が変わったとしたら、などです。
これで各レベルを作成し、すべてクリアできればゲームクリアという形です。
Pyxelとは?
Pyxelとは、Pythonベースで記述できる、レトロゲームエンジンです。

示されている通り、使える色は16色、同時に再生できる音は4音までです。
しかしこれは裏を返せば、Pyxelの範疇で何かを作るのであれば、それ以上のことを考える必要がないということです。
そういう意味で、このエンジンを利用したゲーム作りは、気軽にゲーム作りを行うことができるという意味で画期的だと思います。
ライブラリの作者が日本の方なので、日本語のドキュメントが詳しく整備されている、ないし日本語の記事が多いのもうま味です。
基本的なpyxelの使い方として、次のようなコードを考えるのが分かりやすいと思います。
import pyxel
class App:
def __init__(self):
pyxel.init(160, 120)
self.x = 0
pyxel.run(self.update, self.draw)
def update(self):
self.x = (self.x + 1) % pyxel.width
def draw(self):
pyxel.cls(0)
pyxel.rect(self.x, 0, 8, 8, 9)
App()
まず、pyxelライブラリを pip install しておきます。
その後、アプリを表すクラスを定義しておきます。
イニシャライザで、pyxel.init() を呼びます。
この二つの引数はゲーム画面のサイズを表します。
そしてイニシャライザの最後で pyxel.run() を呼ぶことで、アプリを開始させます。
第一引数には処理を行う関数を渡し、第二引数には描画を行う関数を渡します。
この違いとして、ここでの update 関数は毎フレーム呼ばれるところ、draw 関数は処理が重い場合、毎フレーム呼ばれることが保障されていないようです。
移植の過程
では、移植していきましょう。
Ex.とSuperEX.ではとくにゲームのシステムとして異なる部分はないので、
共通部分は一つのファイルにまとめておくべきです。
そして、各々のふるまい方は、個々で分ければよいというわけです。
変更したこと(base_game.py)
ここで少し、上の画像にも映っている要素の中で、大きく変更した点をご紹介します。
キャラクター描画
これがそこそこ大変でした。
Scratchなら超絶便利な「ネコ」がいたわけですが、ここではそれをそのまま使うわけにはいかないので、新しいキャラクターを用意する必要がありました。
最初リソースクリエータ(後述します)から作成しようかと思ったのですが、その時点では使い方が分からなかったので、キャラクターの中心の座標が与えられたら、そこからの相対座標を用いてキャラクターの場所に点を打つという形にしました。
base_game.pydef make_position(self, x: int, y: int) -> list[tuple[int, int]]:
return [
(x-1, y-3),
(x, y-4),
(x+1, y-3),
(x, y-2),
(x, y-3),
(x, y-2),
(x, y-1),
(x, y),
(x, y+1),
(x, y+2),
(x-1, y-1),
(x+1, y-1),
(x-1, y+3),
(x+1, y+3),
]
これがその該当部分です。
ビーム周り
実はこの部分が一番大きかったです。
結論から言うと、まずScratch版では、ビームの形は長方形でした。
しかし、今回は簡略化のため円形にしました。
これは、描画難易度はあまり問題ないです。
最初は長方形を回転させて描画する関数はないものかと探していたのですが、多分なさそうという感じです。
なぜなら、これがピクセル単位のゲームである以上、そんな厳密に回転した長方形を描画できるような関数は用意しないはずだからです。
それでも描画がそんなに面倒ではないのは、ビームの中心の位置と角度、およびビームの縦横の長ささえ与えられれば描画できるからです。

この長方形がビームだと思ってください。
そして、ビームの横幅を , 縦幅を とします。
ビームの中心の位置をここではいったん原点とします。
また、角度は とします。
このとき、例えば長方形の頂点の一つである点Aの座標を求めます。
はい、OAの長さは三平方の定理より簡単に求められます。
そして、OAが 軸となす角に関しても、この と先ほど三平方の定理を適用した直角三角形の角度を足せば求められます。
その角度というのも、単純に の逆正接 (arctan) を求めるだけです。
pyxelには atan2 という、逆正接を求めるための関数があるので、これをそのまま適用して、その結果を例えば とすれば、
Aの 座標は ,
Aの 座標は で与えられます。
もちろん他の頂点も同様に求められます。
これで頂点ごとに線を描画すれば、外枠は描けます。
pyxel.line 関数で描画することができます。
ただし、中を塗りつぶすことができていません。
しかし、これについても非常に便利な関数があります。
pyxel.fill とすると、指定した座標と、周囲の同じ色の部分を、指定した色で塗りつぶしてくれます。
まさにペイントアプリなどの「塗りつぶし」と同じです。
ここで、ビームの色と、ビームの中心座標を指定すれば、ビームの中身も塗りつぶせます。
では何が困難だったのかと言いますと、こういったシューティングゲームにおいて必須ともいえる、敵ないし味方とビームの当たり判定です。
Scratchでは「○○に触れた」というのを感知するためのブロックがあります。
Unityでもコライダーがあるので、これを使うことでクリアできます。
しかし、pyxelではないようなので、自力で実装することになります。
実際シューティングゲームのサンプルコード が公開されていますが、こちらでも当たり判定は自力で実装されているみたいですね……。
手っ取り早いと思える方法は、あるピクセルの色を取得できる pyxel.pget() 関数です。
しかしこの関数を使うには、ビームとキャラクターについて、それぞれどのピクセルに存在しているかをすべて把握しなければなりません。
これは、先ほどの方法でビームを書いていた場合にとっては致命傷です。
これがある意味では pyxel.fill の難しいところではあります。
その後、いろいろ考えてみました。
例えばスクリーンを都度リセットして、一つのビームを描画して、今キャラクターがいるところと被っているかとか、ビームが囲んでいる四角形の中にキャラクターが存在しているかを調べるアルゴリズムを作るなど、こういったことをやってみればよさそうとは思うのですが、一つ目の方法はどう考えてもスクリーン描画の手間が明らかに無駄です。
もう一つ、このビームの当たり判定は update でやらなければならないのに、update の中で描画して判定しているのも何か気持ち悪さを感じます。
二つ目の方に関しては、頭がこんがらがりそうになったのでやめておきました。
結果は円です。
円で描画しておけば、当たり判定は非常に簡単です。
中心とキャラクターの位置の距離が、円の半径以下なら触れていると言えます。
簡単ですね。
そして、ビームの管理方法ですが、これまではScratchのクローンを使って管理していました。
このクローンも、Scratch2以前は確か無かったのですが、これを作成したのが10年前くらいでクローンが登場してきていたので、使ってみたという形になっています。
ただクローンには不慣れだったみたいで、ステージクリアになってもなぜかクローンしたビームが残っているという不手際もありました。
これはたぶん、いったんステージ終了のフラグでも管理しておいて、ステージが終了したらこのクローンを削除するとかしないと消えなさそうですね。
もちろんpyxelにはクローンという概念はないと思うので、自前でこれを管理することになります。
管理と言ってもそんな変なことをするわけではなく、単純に打たれたビームをリストに入れて管理しておくといった具合です。
そしてキャラクターにあたったビームから削除していくといった単純なものです。
また、ビームを打つ間隔も、Scratchでは秒数で管理していましたが、この部分はpyxelに移植するにあたり大幅に修正しました。
なのでもとのステージと異なっている部分があります。
ただこの部分については、EX. および SuperEX. それぞれのプログラムで反映されているものなので、base_game.py ではその内容はほとんど見ることはできないはずです。
さらに、もとのEX.版では、なぜかライフが一定値を下回ると、打たれるビームに回復弾が入るという余計なお世話仕様があったのですが、これは今回移植するにあたり完全に削除しました。
ポーズ画面の追加
ゲームプレイ中にPキーを押したら、ゲームが一時停止するように変更しました。
これはPキーを再び押すことで解除できます。
こういった一刻を争うシューティングゲームでは、ポーズはあってしかるべきと思いながら、Scratchにはあまりポーズをサポートする仕組みがないよな、と思っていたのですが、とりあえず今回は実装しました。
BGMの追加、SEの変更
さて、もともとのゲームにおいては、SEとBGMがありました。
ビームを打つ際の音と、ステージ開始時の音です。
ここでは、ステージ開始時の音を削除し、ビームを打つ際の音とゲーム中のBGMを新しく用意することにしました。
というのも、キャラクター画像を導入する際に挫折していた、リソースクリエータの使い方を知りたかったからですね。
あと、このゲームエンジンの世界観に合わせるにあたり、やはりクリエータがあるならそれに則って作っていこうと、そう考えたのもあります。
使い方としては、次のように使えます。
$ pyxel edit <リソースファイルの名称>
こうすると、リソースクリエータが開きます。

これは画像用です。

これはタイルマップ用のようです。

これはサウンド用です。
入力されているのは、僕がすでに入力したものです。
サウンドは64個登録することができます。

上記のように作っておきます。
下の「EFX」で、エフェクトを指定しておきます。
こちらのコード で、ton, vol, efxでどういったものを設定できるかを調べられます。
まあ一応こちらのファイルも共有するので、その時確認していただくのでもOKです。

これはミュージック用です。
BGMとかはここで作ります。
先ほどの「サウンド」で保存したものを、シーケンスのように組み合わせて作ります。

チャンネルは4つなので、今回は音楽はch0~2を、SEはch3を使用しています。
サウンドは8つ登録できます。
ちなみに今回のBGMは4種類あり、ランダムに流れます。
play_bgm() 関数で流します。
EX. および SuperEX. のプログラムをする
こちらに関しては、base_game.py に記述している、BaseBeamWarGameApp クラスを継承することでプログラムしています。
そのファイルとしては、ex_game.py および super_ex_game.py です。
主に継承するのは、
init_life()opposite_movement()shoot_beam_behaviour()
です。SuperEX. バージョンではそこにさらに oppo_touch_beam() を継承し、ダメージを受けたときの仕返しがプログラムされています。
それ以外の細かな関数は、上記の継承した関数から呼び出しているものだけです。
どういった処理を行っているのかは、実際にプレイしてみていただきながら確認するのが手っ取り早いかと思います。
実際にプレイする
プレイの仕方ですが、Windowsの場合はexeファイルを実行していただくだけでOKです。
MacOSの場合は、HTMLファイルを実行していただくのが手っ取り早いと思います。
確実にプレイしたい場合は、添付しているpyxappを、pyxel play コマンドで実行していただくのが早いと思います。
ただもちろんこの場合は、PythonおよびPyxelのインストールが必要です。
ルールは簡単で、
- 矢印キーで操作
- スペースキーでビーム発射
- fキーでビームモード切替
- pキーでポーズ
になります。
相手のライフを先に0にすればステージクリアで、全ての用意されたステージをクリアできればゲームクリアです。
ぜひご感想をお聞かせください。
おわりに
本日は、昔にScratchで作成したゲームを、Pyxelに移植してきました。
かなりPyxelは使いやすかったです。また何か作りたいなとは思っています。
最後までお読みいただきありがとうございました。