碰撞
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):
# entity hit a solid tile
pass
当两个碰撞体重叠时,引擎调用 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 互不干扰