崩壊しないシステマティックなゲーム開発がしたい
ということで、Python 製のミニマルなゲーム開発用ライブラリ Pigframe を作りました。
主には、Pygame, Pyxel などの Python 製のゲーム開発エンジンを使ってゲームを開発したいと思っている、開発を始めた個人開発者・学習者向けになります。
依存している外部ライブラリはありません。
自分がゲーム開発で使っている/使いたい Pygame や Pyxel などの開発エンジンと併せて使うことを主な用途として想定しています。
さっさと使い方を見たい方はこちら: 日本語版 README
◼ 一体全体なにができるの?
Pigframe は以下のような仕様を持っています。
機能 | 対応 | 備考 |
---|---|---|
画面の描画 | x | ゲームエンジンに任せる |
サウンドの取り扱い | x | ゲームエンジンに任せる |
テキストの描画 | x | ゲームエンジンに任せる |
RPGツクールができるようなゲームに共通したシステム、コンポーネントの実装 | x | ゲームエンジンに任せる |
ほとんど何もしてくれませんね。ブヒッ。
Pigframe には開発するゲームによって使ったり使わなかったりする機能は一切実装されていません。その代わり、どんなゲーム開発でも共通している(と思う)基本的な機能を提供しています。以下はその例です。
▼ ゲームに登場させる登場人物やオブジェクト(以下、エンティティー)を ID で管理する。
app = App()
...
entity = app.create_entity() # -> int: entity ID
▼ エンティティーが保有すべき情報(位置、速度、体力など)はエンティティーのID がキーとなっている辞書で管理する。
コンポーネントのインスタンスは内部で自動的に作成されます。(テンプレート)
app.add_component_to_entity(entity, PositionComponent, 0, 0)
# Parameters: entity ID, 紐付けるコンポーネントの型、コンポーネントのパラメータ
▼ すべての処理を System
, Event
, Screen
の3つに切り分ける。
ゲーム上の各シーン・レベルで実行する一連の処理を System (各シーンで常時実行する処理)
, Event (何かがトリガーになって1度だけ実行する処理)
, Screen (システムとは関係ない、画面に描画する用の処理)
の3種類に分けて、それぞれの System
, Event
, Screen
は単一の処理しか行わないようにしています。単一責任の法則を守りやすくしています。
システムのインスタンスは内部で自動的に作成されます。(テンプレート)
app.add_system_to_scene(SysPlayerControl, "game", priority = 0, some_system_args)
# Parameters: システムタイプ, そのシステムを実行するシーン, 実行優先度, 任意のシステムパラメータ
for system in self.scene_systems[self.current_scene]:
system.process() # そのシーンで実行するシステムのみが優先度順に実行される。イベント、スクリーンも同様。
以上はシステムの例ですがイベント、スクリーンも同様。
▼ ステート駆動。必要最低限の状態管理・遷移機能(シーン・レベルマネージャーとして使えるもの)を実装。
以下は、ユーザーがスペースキーを押すとゲームのシーンが "launch"
から "game"
に遷移する例です。
app = App()
...
app.current_scene = "launch"
# シーン・ステート遷移は遷移元、遷移先、トリガーを与えて設定する。
app.add_scene_transition(scene_from = "launch", scene_to = "game", triger = lambda: pyxel.btn(pyxel.KEY_SPACE))
# シーンで常時処理するわけではないイベントも同様。
app.add_event_to_scene(EvChangeBallColor, scene = "game", triger = lambda: pyxel.btnp(pyxel.KEY_C), priority = 0)
以上から分かるように極論言うとこのフレームワークが無くても開発はできますが、あれば仕様上の決まりごとに従って綺麗に開発ができるのではないかと思います。
◼ 使うといいのは、どんなとき?
例として、話題になったスイカゲームのフォロワーゲームを作り始めたとしましょう。
- 大きさ、テクスチャの異なる幾つかの円の中から1つをランダムに選ぶ。
- 選ばれた円が画面上部にセットされる。このとき次にセットされる円もランダムに予め選ばれる。
- マウス・タップによるドラッグ操作により、画面上部で円のx座標を更新する。
- マウス・タップが離れるとx座標は固定され落下し始める。
- 落下を始めた円は操作できず、他のすでに落下していた円を含めて接触判定がなされる。(円の境界で接触を判定)
- ...
みたいな仕様のもとに作り始めたとき、もしかすると
仕様 3 と仕様 4 を同じシステムとして実装してしまうかもしれません。また仕様 3 や仕様 4 でやりたい大事な処理はユーザーのインプットがトリガーになっているので、イベントハンドリングもシステム内に実装するかもしれません。
そうすると、もしユーザーのインプットをマウスだけじゃなくてコントローラーでもやりたくなったときや、3 と 4 の間に新たな処理を追加したくなったときに悲しくなる気がします。
またイベントハンドリングは使うゲーム開発エンジンにほとんどが依存する一方で、裏側で動いているシステムは非依存なことが多いです。ゲームを移植しようと考えるときに、ユーザーのインプットを処理する箇所、画面描画、サウンドは書き直す必要があるけど、システムはそのままでいける、みたいなのができたら良さそうです。
そんなことを踏まえると、ゲームの状態管理・遷移
, 状態に応じて実行するシステムの管理
, ゲームエンジンに依存する描画の管理
, ユーザーのインプット処理
, 登場人物、アイテムなどのリソース、コンポーネント、エンティティーの管理
が完全に切り離されていると良さそうです。
作ったライブラリは、これら機能の切り分けを仕様面からやりやすくして、またそれら機能間の連携を代わりにやってくれる感じです。
(system.py
, event.py
, screen.py
, component.py
, entity.py
のように自然と切り離したくなるような仕様になっています。)
Python の場合で数百~千行くらいで書くゲーム程度であればこれは逆に冗長かもしれませんが、プロジェクトの規模が大きくなっていくとコードが滅茶苦茶になるのを防ぐのに使えるフレームだと制作者は考えています。
他の言語 (例えば Rust) では Bevy のようなライブラリがありますが、Python にはあまりないなーと思って作ってみた感じです。
◼ さいごに
Python で書ける軽量なレトロゲーム開発エンジン Pyxel と Pigframe を使用した例を作ったので、気になった人は覗いてみてください。
Pygame と一緒に使用した例を以下に追加しました。
また主な使い方は、日本語版 README を御覧ください。
以上、ご覧下さりありがとうございました。
まだまだ至らない点があることは認識しており、バージョンアップで破壊的変更が行われる可能性があります。