衝突判定
Pyxenはグリッドベースの衝突判定システムを使用しています。衝突ボディは空間グリッド上に配置され、エンジンが毎フレームそれらの重なりを検出します。これにより衝突判定がシンプル、高速、かつ決定的に保たれます。
グリッドマップとグリッドボディ
GridMapは空間ワールドを定義します — 指定されたセルサイズを持つタイルのグリッドです。GridBodyはそのグリッド内に存在する衝突矩形です。
GridMapを持つエンティティはGridBodyを持つエンティティの親でなければなりません。エンジンは単一のGridMap内のすべての衝突を独立して解決するため、同じワールド内で複数のGridMapを実行でき、それらは互いに干渉しません。
level = world.spawn(
map=GridMap(rows=20, columns=20, size=(16, 16), image="tileset")
)
player = world.spawn(
parent=level,
body=GridBody(pos=(32, 32), size=(16, 16), tag=1, mask=(2,))
)
エンティティの位置
エンティティの位置(x、y)はGridMapの衝突解決から自動的に導出されます。エンジンがボディの移動を解決するとき、エンティティのトランスフォームをボディの解決済み位置に合わせて更新します。GridBodyを持つエンティティのx/yを手動で設定する必要はありません — 衝突システムがそれらの位置を管理します。
ボディの移動
ボディを持つエンティティを移動するには、ボディのmoveプロパティを設定します。エンジンが移動を試み、衝突を解決します:
def update():
player.body.move = (0, 0)
if input.keyboard.right.down:
player.body.move = (2, 0)
エンジンがボディを移動し、衝突をチェックし、最終位置を調整します。
タグとマスク
タグとマスクはどのボディが互いに衝突できるかを制御します。
- タグ — このボディが何であるかを識別する数値(例:1 = プレイヤー、2 = 敵、3 = 壁)
- マスク — このボディが衝突すべきタグ
移動中のボディがマスクに含まれるタグを持つ別のボディやタイルに遭遇すると、通り抜けるのではなくスライドします。これにより、壁や障害物に沿ったスムーズな移動を伴うソリッドな衝突が実現されます。
player = world.spawn(
parent=level,
body=GridBody(pos=(0, 0), size=(16, 16), tag=1, mask=(2, 3))
)
enemy = world.spawn(
parent=level,
body=GridBody(pos=(0, 0), size=(16, 16), tag=2, mask=(1,))
)
プレイヤーはタグ2と3(敵と壁)に衝突します。敵はタグ1(プレイヤー)に衝突します。タグ0のボディやマスクが一致しないボディは互いにすり抜けます。
衝突コールバック
ボディがタイルに衝突すると、エンジンはon_hit_tileを呼び出します:
def on_hit_tile(entity, tile):
# エンティティがソリッドタイルに衝突した
pass
2つのボディが重なると、エンジンはon_hit_bodyを呼び出します:
def on_hit_body(entity, other):
if entity == player:
entity.health.value -= 1
これらは衝突ステップ中、update()の後かつレンダリングの前に実行されます。
タイルマップの衝突
GridMapはタイルのグリッドを定義し、タイルタグを使ってタイルをソリッドとしてマークできます:
level.map.set(row=0, column=5, tile=(1, 0), tag=2)
マスクにそのタグを含むボディはタイルに対してスライドします。
仕組み
衝突システムはグリッドベースであり、ピクセルパーフェクトではありません。ボディはグリッド内を移動する軸平行矩形です。シミュレーションはGridMapごとに分離されます — 各GridMapエンティティが独自の独立した衝突解決を実行します。これにより衝突判定は:
- 決定的 — 同じ入力は常に同じ衝突を生成
- 高速 — グリッド検索は定数時間
- 予測可能 — 浮動小数点のエッジケースなし
- 分離 — 複数のGridMapが互いに干渉しない