大数跨境
0
0

开源|一个组件解决摄像机穿模,让3D场景更专业

开源|一个组件解决摄像机穿模,让3D场景更专业 EcoCosy优可丝
2025-07-24
1
导读:即插即用的摄像机弹簧臂,轻松提升游戏高级感。

如视频所示,这是个控制摄像机的组件。

当摄像机过于贴近其他物体的时候,摄像机就会像弹簧一样被挤压到一起,避免摄像机穿模的尴尬情况。

不使用弹簧臂的情况对比如下。

组件使用

为了逻辑解耦和易用,功能被我封装成了两个组件,包括弹簧臂组件弹簧臂调试组件

弹簧臂组件

每个参数都有详细的注释以及 Tooltip,方便快速上手。

把弹簧臂组件挂载到玩家角色身上,设置跟随目标和摄像机,这个目标通常是角色的头。

使用调试组件后,在编辑器里调整弹簧臂参数时,能够实时预览。

弹簧臂调试组件

在我的示例项目中,调试组件挂载到了旁观者角色身上。

这样运行时按 Q 键即可切换到旁观者,以自由视角观察玩家位置,方便调试。

实现原理

了解了组件使用后,再简单介绍下它的实现原理。

计算位置

弹簧臂计算摄像机位置分为三段。

  • 目标偏移(targetOffset)

用角色位置加上偏移得到目标偏移位置。

let worldTargetOffset = Vec3.transformMat4(v3(), this.targetOffset, rotation);
this.targetOffsetPosition = Vec3.add(v3(), trans.worldPosition, worldTargetOffset);
  • 弹簧臂终点

用弹簧臂长度乘以角色背后方向,可以得到弹簧臂尺寸。

用目标偏移点加弹簧臂尺寸,得到最后的弹簧臂终点。

let v3_1 = Vec3.negate(v3(), trans.forward);
let v3_2 = Vec3.multiplyScalar(v3(), v3_1, this.armLength);
let v3_3 = Vec3.add(v3(), v3_2, this.targetOffsetPosition);
  • 插槽偏移点

用弹簧臂终点加插槽偏移,得到插槽偏移点。

let worldSocketOffset = Vec3.transformMat4(v3(), this.socketOffset, rotation);
this.socketOffsetPosition = Vec3.add(v3(), v3_3, worldSocketOffset);

计算碰撞

组件通过射线扫掠的方式,检测摄像机跟场景中的其他物体是否产生碰撞。

射线扫掠是 Cocos Creator 3.8 中新增的一套射线 API 。

它能够使用一个形状往指定的方向扫掠过去,过程中形成的轨迹碰撞到物体就会被记录。

const phySys = PhysicsSystem.instance;

let sweepRay = geometry.Ray.create(
    this.targetOffsetPosition.x,
    this.targetOffsetPosition.y,
    this.targetOffsetPosition.z,
    this.raycastDirection.x,
    this.raycastDirection.y,
    this.raycastDirection.z
);

let isSweep = phySys.sweepSphereClosest(
    sweepRay,
    this.collisionProbeSize,
    this.collisionLayerMask,
    this.armLength,
    false
);

扫掠检测到碰撞后,会再做一次从目标偏移点到插槽偏移点的射线检测,把这次碰撞到的位置设置为摄像机位置

这么做是因为扫掠是一个几何形状,碰撞的地方可能是几何体边缘,而摄像机的位置应该在几何体正中心。

if (isSweep) {
    let ray = geometry.Ray.create(
        this.targetOffsetPosition.x,
        this.targetOffsetPosition.y,
        this.targetOffsetPosition.z,
        this.raycastDirection.x,
        this.raycastDirection.y,
        this.raycastDirection.z
    );

    if (phySys.raycastClosest(
        ray,
        this.collisionLayerMask,
        this._armLength,
        false
    )) {
        this.socketOffsetPosition = phySys.raycastClosestResult.hitPoint;
    }
}

弹簧臂调试

弹簧臂调试使用了引擎自带组件 Line

设置完了基础参数后,使用目标偏移点和插槽偏移点绘制出一条线。

let line = this.getComponent(Line);
line.worldSpace = true;
line.width.mode = CurveRange.Mode.Constant;
line.width.constant = 0.05;
line.color.mode = GradientRange.Mode.Color;
line.color.color = math.Color.RED;

line.positions = [
    this.springArm.targetOffsetPosition,
    this.springArm.socketOffsetPosition
];

写在最后

后续我会继续完善这个组件,例如加上位置和旋转延迟,来支持更多情景的角色摄像机需求。

有需要的朋友,可以点击【阅读原文】获取源码。仓库接受社区提交代码,期待大家的 PR。

有任何问题或建议,欢迎在评论区留言交流!

作者介绍:狍子,常年活跃于社区,热衷参加社区活动,拥有多年游戏客户端开发经验。

【声明】内容源于网络
0
0
EcoCosy优可丝
1234
内容 703
粉丝 0
EcoCosy优可丝 1234
总阅读42
粉丝0
内容703