ゲームループ
Pyxenはゲームを固定の毎秒30フレームで実行します。メインループを書く必要はありません — 2つの関数を定義するだけで、Pyxenが呼び出します。
start()とupdate()
def start():
# 最初に1回呼び出される
pass
def update():
# 毎フレーム、1秒に30回呼び出される
pass
start()はワールドをセットアップする場所です:エンティティの生成、配置、シーンの設定。
update()はゲームロジックが実行される場所です:入力の読み取り、エンティティの移動、条件のチェック、状態の更新。update()の各呼び出し後、エンジンがフレームをレンダリングします。
固定フレームレート
各フレームは正確に同じ時間がかかります:1/30秒(約33ms)。つまり:
- ゲームロジックは決定論的 — 同じ入力は同じ結果を生む
- 可変デルタタイムの代わりにフレームを直接カウントできる(
time.frame) - タイムスクラバーとフレームインスペクタが確実に動作する(各フレームがクリーンなスナップショット)
時間ベースの移動を好む場合はtime.dt(デルタタイム)も使えますが、常に1/30です。
フレームの順序
各フレームで、Pyxenはこの順序でステップを実行します:
- 入力 — キーボード、マウス、タッチ、ゲームパッドの状態を読み取る
- update() — あなたのコードが実行される
- 衝突 — エンジンがグリッドボディを解決し、衝突コールバックを呼び出す
- レンダリング — エンジンがすべての可視エンティティを描画
これは、update()で入力を読むとき値が最新であることを意味します。エンティティを移動すると、衝突解決がその直後に行われます。
衝突コールバック
トップレベルでon_collisionやon_tile_collisionを定義すると、エンジンが衝突ステップ中にそれらを呼び出します:
def on_collision(entity, other):
# 2つのグリッドボディが重なった
pass
def on_tile_collision(entity, tile):
# グリッドボディがタイルマップのタイルに当たった
pass
これらはupdate()の後、レンダリングの前に実行されます。
タイミング
pyxen.timeモジュールが提供するもの:
| プロパティ | 値 | 用途 |
|---|---|---|
time.frame | 0, 1, 2, ... | 開始からのフレームカウンター |
time.dt | 0.0333... | デルタタイム(常に1/30) |
time.t | 0.0, 0.033, 0.066, ... | 経過時間(秒) |
time.fps | 30 | フレーム毎秒 |
タイミングイベントにはどちらのアプローチも使えます:
# フレームベース:60フレーム(2秒)ごとに発火
if time.frame % 60 == 0:
fire()
# タイムベース:同じこと
if int(time.t * 30) % 60 == 0:
fire()
フレームカウントの方が通常シンプルで予測可能です。