本页尚未翻译,当前显示英文版本。
Collision
把变换绑定到障碍物地图,并声明接触时发生什么。 Collision 模块运行在平台的模拟内部。你声明碰撞体、层和响应,再订阅接触事件。
何时使用
- 移动中的实体(entity)需要在服务器端处理接触(滑动、停止、反弹),又不想手写偏转逻辑。
- 玩法要对触碰做出反应:重叠时拾取物品,触发区域驱动实体的状态机。
- Locomotion 和投射物需要针对地图的障碍物集合做扫掠检测。
- 放置预览或瞄准需要查询“这里放得下吗?”,以及查询体积重叠。
- 无需使用:如果游戏里没有任何物体发生物理接触。基于记录的请求/响应式玩法用普通的 Data & Subscriptions 即可。
分工
| 主体 | 在本页中的职责 |
|---|---|
room-owner | 声明碰撞体、层和响应;查询重叠和接触 |
适用于哪些房间
本模块只在由平台推进模拟的地方运行,即声明了 Host = "Backend" 的房间(room)。如果模拟归你自己的游戏服务器所有(PlayServ 作为元服务器),移动、碰撞和预测都留在引擎一侧,本页描述的是平台托管这一备选方案,而不是必须遵守的要求。
概览
形状、层和接触时的行为都直接声明在碰撞体上,不存在在别处单独声明的层对:
坦克上的胶囊体
Body,位于 vehicles 层:沿墙滑动,穿过可拾取物品,与箱子接触时逐次决定[Entity("tank")]
public class Tank
{
[Sync] public Vector3 Position;
[Body(Shape.Capsule, Radius = 0.6f, Layer = "vehicles")]
[CollidesWith("walls", Response.Slide)]
[CollidesWith("pickups", Response.Pass)] // reported, motion passes through
public Body Body;
}@Entity('tank')
export class Tank {
@Sync() position!: Vector3;
@Body({ shape: 'capsule', radius: 0.6, layer: 'vehicles' })
@CollidesWith('walls', Response.Slide)
@CollidesWith('pickups', Response.Pass) // reported, motion passes through
body: Body;
}@entity("tank")
class Tank:
position: Vector3 = sync()
body = collision.body(shape="capsule", radius=0.6, layer="vehicles",
collides_with=[
("walls", Response.SLIDE),
("pickups", Response.PASS), # reported, motion passes through
])即将推出 —— Go
Go 中基于特性的声明方式尚未确定(O-1),确定写法后会补上示例。
// the declaration rides inside the engine's own reflection macros, in the specifier position —
// UHT reads it from the header text, and the member is a reflected property at the same time
UCLASS(PSEntity = "tank")
class UTank : public UObject
{
GENERATED_BODY()
UPROPERTY(PSSync)
FVector3f Position;
// walls slide, pickups report the contact and let motion pass through —
// multi-entry values are one quoted list (a specifier value is a single token)
UPROPERTY(PSBody = (Shape = "Capsule", Radius = "0.6", Layer = "vehicles"),
PSCollidesWith = "walls:Slide, pickups:Pass")
FPSBody Body;
};
// declarations compile into the same pushed model — playserv push from the UE project or CI[Entity("tank")]
public class Tank
{
[Sync] public Vector3 Position;
[Body(Shape.Capsule, Radius = 0.6f, Layer = "vehicles")]
[CollidesWith("walls", Response.Slide)]
[CollidesWith("pickups", Response.Pass)] // reported, motion passes through
public Body Body;
}接触是一个事件(event),模块订阅它。接触上没有 hook:接触出现时,这一步已经把它处理完了,没 有什么可以拒绝的。如果工作室需要不同的规则,可以用自己的实现覆盖可达性检查和路径检查(见 Extensibility),响应仍然由声明决定。
模型
碰撞体声明什么。
| 声明项 | 含义 |
|---|---|
shape | 取自封闭集合的原语(primitive):球体、胶囊体、盒体,尺寸由声明给出。不支持任意网格,限制和原因都与地图中服务器的世界模型相同 |
所在位置 | 与变换一起放在同一个 aspect 上:aspect 是策略的单位,碰撞体与它的变换同生共死 |
路径检查方式 | stepwise:只检查这一步的最终位置,速度快。swept:检查两个位置之间的线段,开销更大,但能排除一步之内的穿透(tunneling)。检查方式由声明决定,实现不会根据速度自行选择 |
参与的区域 | 碰撞体计入哪些区域体积 |
与美术模型的关系 | 不要求有关系:碰撞体只是简化形状,在声明的范围内允许与美术模型不一致 |
高速的 stepwise 碰撞体会穿过薄障碍 物
stepwise 只检查一步结束时的位置,所以高速碰撞体可能直接穿过薄障碍物。swept 检查两个位置之间的线段,排除一步之内的穿透。投射物能不能飞穿墙壁,是你的游戏自身的属性,由你来声明。
响应声明在一对组合上,即“障碍物的通行类型 × 碰撞体类型”,取值来自封闭集合:
| 响应 | 含义 |
|---|---|
stop | 移动停在最后一个可达位置 |
slide | 沿障碍物继续移动,保留其中可达的分量 |
bounce | 方向反射,速度乘以声明的系数 |
damp | 继续移动,速度乘以声明的比例 |
pass | 障碍物不影响移动,但接触仍然可以观察到 |
cease to exist | 实体消失,例如投射物撞上墙壁 |
这些系数是声明的值,而不是根据质量和材质算出来的:这份契约里既没有质量,也没有材质。
每次检查都成立的规则。
| 始终成立 | 含义 |
|---|---|
每一对组合 | 都有响应:缺少某一对属于声明(declaration)缺陷,部署 时就会拒绝,不会等到战斗中才暴露 |
响应表 | 客户端可读:它就是权威(authority)一方计算时用的那张表,所以拿到同一份声明的客户端和服务器,对同一个接触给出相同的结果 |
在同一权威方内可复现,跨平台不保证 | 在同一个进程、同一个 build 内,相同顺序的相同输入得到相同结果。不保证不同平台、不同 build 之间结果逐位一致 |
同时性 | 由声明决定:两个移动的碰撞体在同一步内相撞时,处理顺序是声明好的、确定的。存储遍历顺序、输入到达顺序和随机数都不能作为依据 |
一次接触,一个事实 | 两个碰撞体之间的接触,双方观察到的是带同一个标识符的同一个事实,而不是两个独立事件 |
扩展点在步进上,不在接触上 | 步进之前可以修改变换,步进之后只能观察。覆盖(override)后的可达性检查和路径检查也必须对客户端可用 |
本模块 | 自己不移动任何东西:它只回答某个位置是否可达、响应是什么,真正执行由移动负责 |
接触是事件 | 所以模块之间靠订阅而不是耦合:陷阱的状态机把状态转换绑定到进入触发区域,掉落物在重叠时被拾取,投射物通过本模块的扫掠检测判定命中 |
错误
- “不可达”是一个回答,而不是错误,并且会指明是三种原因中的哪一种:超出边界、被静态障碍物占据,或被其他实体的碰撞体占据。客户端对这三种情况的反应各不相同:掉头、绕行或等待。
- 声明错误在部署时暴露,不会拖到第一次接触:形状不在封闭集合内的碰撞体、放在没有变换的 aspect 上的碰撞体,以及没有声明响应的组合,都会在部署时被拒绝。
- 找不到实体或位置时返回 not found,重试没有意义。
- 超出检查频率时返回限流类错误,并附带一个时间点,在此之前重试没有意义。
限制
具体数值会随平台限制一章给出;每个上限触发后的行为现在就已确定。
- 房间内的碰撞体数量:再声明一个会以冲突被拒绝;不会为了腾出空间而移除已有的碰撞体。
- 每步的接触数量:超出部分绝不静默丢弃。要么拒绝这一步,要么按声明的顺序截断。
- 碰撞体尺寸与地图网格步长:部署时就会拒绝,因为比高度场步长还小的碰撞体会从地形中掉下去。
- 一个碰撞体可同时所在的区域数量:超出时在部署时被拒绝。
- 每个主体的检查频率:限流,并附带时间点。
- “这个区域里有谁”的回答中的碰撞体数量:按声明的顺序截断,并且必须带截断标志。