mcpField
C
camera

camera

github.com/drawcall-ai开发124 次安装
代码仓库

关于 camera

用于构建游戏摄像机的技能,介绍 viverse 内置角色摄像机及自定义第一或第三人称方案,也涵盖屏幕震动和随速度变化的视野效果。

主要功能

  1. 01角色环绕、碰撞与缩放
  2. 02自定义第一及第三人称摄像机
  3. 03空闲时返回静止姿态
  4. 04屏幕震动效果
  5. 05根据速度调整视野

适用场景

  1. 01设置玩家角色摄像机
  2. 02构建自定义第一人称或环绕摄像机
  3. 03空闲时将摄像机角度恢复至静止姿态
  4. 04为游戏摄像机添加震动或速度效果

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/drawcall-ai/skills --skill camera

安装后在 Claude Code 中输入/camera确认已加载。

详细介绍

摄像机

游戏摄像机跟随目标,以受限的俯仰角和偏航角环绕,并支持碰撞检测和缩放。首先需要做一个选择:对于玩家角色,直接使用 viverse 提供的现成 CharacterCameraBehavior;只有在需要完全自定义的摄像机 rig 时,才从头构建摄像机。手动实现角色摄像机往往会重现相同的问题——偏航角锁定(无法上下瞄准)或穿墙,而内置实现已经解决了这些问题。

所有自定义 rig 都遵循同一约定:camera.rotation.set(pitch, yaw, 0, 'YXZ'),其中正俯仰角表示向上看,正偏航角表示向左看。'YXZ' 欧拉角顺序可确保俯仰角和偏航角正确组合,因此这里只说明一次,后文均默认遵循。

以下各节分别是:推荐:CharacterCameraBehavior(角色的内置实现)、从头构建摄像机(以 ECS 系统形式实现第一人称、第三人称和静止位置变体),以及效果(屏幕震动和 FOV 速度)。交叉参考:@pmndrs/viverse,以及 acta、physics、entity-component-system 和 math 技能。

推荐:CharacterCameraBehavior(用于角色)

对于玩家角色,优先使用此方案,而不是手动实现。CharacterCameraBehavior 会绕两个轴(俯仰角和偏航角,均有限制)旋转,检测摄像机与世界的碰撞,并处理缩放。

import { CharacterCameraBehavior, FirstPersonCharacterCameraBehavior } from '@pmndrs/viverse'

const cameraBehavior = new CharacterCameraBehavior() // third-person orbit + collision + zoom

// each frame, after moving the character:
cameraBehavior.update(camera, characterModel, delta, (ray, far) => world.raycast(ray, far)?.distance)

每帧在移动角色后调用 update。碰撞检测通过射线回调 (ray, far) => world.raycast(ray, far)?.distance 防止摄像机进入墙体。

update(camera, target, deltaTime, raycast?, options?) 就是完整的 API——没有 setOptions 方法。通过第 5 个 options 参数调整行为,每帧传入相同结构的参数:

  • options.rotation:minPitch / maxPitch / minYaw / maxYaw / speed
  • options.zoom:minDistance / maxDistance / speed
  • options.collision
  • options.characterBaseOffset

实例公开了 rotationPitch、rotationYaw 和 zoomDistance。在首次调用 update 之前设置这些属性,即可设定初始朝向和缩放距离。

FirstPersonCharacterCameraBehavior 并非独立类,而是一个现成的选项对象。将其作为第 5 个参数传入,即可切换到第一人称视角:

cameraBehavior.update(camera, characterModel, delta, raycast, FirstPersonCharacterCameraBehavior)

此方案可与 BvhCharacterPhysics(移动)和 Acta(动画)配合使用——参见 acta 和 physics 技能。移动和瞄准方向根据摄像机朝向,通过 camera.getWorldDirection 推导(正确包含俯仰角和横向移动)。信息流向为:输入 → Acta → physics。

characterBaseOffset 是相对于摄像机的:其水平部分会随摄像机偏航角旋转。因此,[0.5, 1.5, 0] 这样的越肩偏移量会在环绕时始终相对于视角保持在肩部上方,而纯垂直偏移(默认值以及第一人称情况)不受偏航角影响。角色无需面向摄像机,也能保持稳定的构图。

从头构建摄像机

对于完全自定义的 rig,可将第一人称和第三人称摄像机构建为精简的 ECS 系统——参见 entity-component-system 技能。

第一人称摄像机

第一人称摄像机会将自身定位在玩家头部,并根据累积的俯仰角和偏航角旋转。

export class FirstPersonCameraSystem extends createSystem({}) {
  private pitch = 0
  private yaw = 0
  private headOffset = new Vector3(0, 1.7, 0)

  init() {
    RotatePitchAction.subscribe(
      (delta) => {
        this.pitch = Math.max(-Math.PI / 2, Math.min(Math.PI / 2, this.pitch + delta))
      },
      { signal },
    )
    RotateYawAction.subscribe(
      (delta) => {
        this.yaw += delta
      },
      { signal },
    )
  }

  update() {
    // TODO: retrieve player position
    camera.position.copy(playerPosition).add(this.headOffset)
    camera.rotation.set(this.pitch, this.yaw, 0, 'YXZ')
  }
}

俯仰角限制在 [-PI/2, PI/2];偏航角不受限制(this.yaw += delta)。订阅时传入 { signal } 以便清理。

第三人称摄像机

第三人称摄像机使用 Spherical 环绕玩家——参见 math 技能。

export class ThirdPersonCameraSystem extends createSystem({}) {
  private pitch = 0.3
  private yaw = 0
  private radius = 5
  private headOffset = new Vector3(0, 1.7, 0)
  private spherical = new Spherical()

  init() {
    RotatePitchAction.subscribe(
      (delta) => {
        this.pitch = Math.max(0.1, Math.min(Math.PI / 2, this.pitch + delta))
      },
      { signal },
    )
    RotateYawAction.subscribe(
      (delta) => {
        this.yaw += delta
      },
      { signal },
    )
  }

  update() {
    // TODO: retrieve player position
    this.spherical.set(this.radius, this.pitch, this.yaw)
    camera.position.setFromSpherical(this.spherical).add(playerPosition).add(headOffset)
    camera.lookAt(target)
  }
}

俯仰角限制在 [0.1, PI/2]——请注意,其下限不同于第一人称的 -PI/2,以防摄像机绕到地面以下。偏航角不受限制,订阅时也使用相同的 { signal } 模式。

静止位置

玩家空闲时,摄像机可以平滑插值回静止姿势。由于 pitch 和 yaw 是摄像机系统的私有状态,静止姿势插值应放在拥有这些状态的系统内部——单独的系统无法访问并修改它们。

// add these fields to the FirstPerson/ThirdPerson camera system:
private restPitch = 0
private restYaw = 0

update() {
  // ... position the camera from this.pitch / this.yaw as above ...
  if (playerIsIdle) { // TODO: derive idle from input or velocity
    this.pitch = MathUtils.lerp(this.pitch, this.restPitch, 0.05)
    this.yaw = MathUtils.lerp(this.yaw, this.restYaw, 0.05)
  }
}

效果

两种效果可叠加到任意摄像机上:屏幕震动和 FOV 速度。

屏幕震动

向摄像机位置应用一阵逐渐衰减的随机偏移。

export class ScreenShakeSystem extends createSystem({}) {
  private intensity = 0
  private remaining = 0

  shake(intensity: number, duration: number) {
    this.intensity = intensity
    this.remaining = duration
  }

  update(delta: number) {
    if (this.remaining <= 0) return
    this.remaining -= delta * 1000
    const t = this.remaining / 1000
    camera.position.x += (Math.random() - 0.5) * this.intensity * t
    camera.position.y += (Math.random() - 0.5) * this.intensity * t
  }
}

shake(intensity, duration) 会启动一阵震动。注意单位处理:delta 的单位是秒,而 remaining 以毫秒为单位跟踪,因此 update 会减去 delta * 1000。系数 t = remaining / 1000 会同时缩放 x 和 y 方向的偏移,使震动在其持续期间逐渐衰减。

FOV 速度效果

玩家移动越快,视野范围越大,以传达速度感。

export class FOVSpeedSystem extends createSystem({}) {
  private baseFOV = 75
  private maxFOV = 100
  private maxSpeed = 50

  update() {
    // TODO: compute/retrieve player speed
    camera.fov = MathUtils.lerp(this.baseFOV, this.maxFOV, Math.min(speed / this.maxSpeed, 1))
    camera.updateProjectionMatrix()
  }
}

更改 fov 后,务必调用 camera.updateProjectionMatrix(),否则新值不会生效。

评论

支持 Markdown