Skip to content

Physics — 服务端物理与碰撞

服务端物理 ≠ 客户端渲染物理。服务端只做游戏规则验证:射线检测、范围判定、碰撞查询。

来源源头:BigWorld Physics2 的完整服务端物理边界。 KBEngine 基本没有完整服务端物理层,只能作为轻量缺口参考。


0.5 引擎实现对照与取舍

BigWorld 是怎么实现的

BigWorld 有完整的服务端物理子系统:
  - 物理查询
  - 碰撞体管理
  - 与空间和 Chunk 资产联动

KBEngine 是怎么实现的

KBEngine 基本没有完整服务端物理层,
通常更依赖脚本逻辑、简化几何和外部组件。

优缺点

BigWorld 的优点:
  - 能把规则验证下沉到引擎层
  - 与空间和导航系统协同更紧

KBEngine 的优点:
  - 轻
  - 少一层系统复杂度

共同缺点:
  - 真正完整的服务端物理成本很高

theseed 的取舍

theseed 只保留服务端物理查询边界,
不把完整 Physics2 作为 MVP 目标。

1. 为什么服务端需要物理

服务端物理的刚需场景:
  1. 射线检测:技能弹道是否命中?视线是否被遮挡?
  2. 范围判定:AOE 技能影响哪些实体?
  3. 碰撞查询:角色能不能穿墙?移动是否合法?
  4. 掉落检测:物品掉落在地面哪个位置?
  5. NavMesh 依赖:寻路需要碰撞几何体

2. IPhysicsQuery 接口

cpp
class IPhysicsQuery {
public:
    // 射线检测
    virtual RaycastResult raycast(const Vector3& src, const Vector3& dir,
                                  float maxDistance, CollisionFilter filter = {}) = 0;
    virtual RaycastResult raycastBetween(const Vector3& src, const Vector3& dst,
                                         CollisionFilter filter = {}) = 0;

    // 范围查询
    virtual std::vector<OverlapResult> overlapSphere(const Vector3& center, float radius,
                                                      CollisionFilter filter = {}) = 0;
    virtual std::vector<OverlapResult> overlapBox(const Vector3& center, const Vector3& halfExtents,
                                                   CollisionFilter filter = {}) = 0;

    // 可见性检测
    virtual bool lineOfSight(const Vector3& from, const Vector3& to) = 0;

    // 地面投影
    virtual std::optional<Vector3> projectToGround(const Vector3& position, float maxDrop = 1000.f) = 0;

    // 导航区域检测
    virtual bool isNavigable(const Vector3& position) = 0;
};

3. 脚本层 API

python
import theseed.physics

class Avatar(BaseEntity):
    def attack(self, targetId, skillId):
        target = theseed.getEntity(targetId)
        if not theseed.physics.lineOfSight(self.position, target.position):
            return  # 被墙壁遮挡

        if skillId == SKILL_FIREBALL:
            results = theseed.physics.overlapSphere(
                target.position, radius=5.0,
                filter={"includeTags": ["enemy"]}
            )
            for hit in results:
                self.dealDamage(hit.entityId, calculateDamage(skillId))

    def move(self, position, rotation):
        if not theseed.physics.isNavigable(position):
            return
        result = theseed.physics.raycastBetween(self.position, position)
        if result.hit:
            return  # 路径上有障碍物
        self.position = position

4. 对比

能力BigWorldKBEnginetheseed
射线检测Physics2 collide()IPhysicsQuery::raycast()
范围查询QuadTreeoverlapSphere/Box()
视线检测raycast with callbacklineOfSight()
碰撞数据WorldTriangle + QuadTreeNavMesh + 碰撞网格
物理模拟无(服务端不模拟)不做