vtj-camera-system

star 144

Configures third-person camera systems with collision avoidance. Use when adjusting follow behavior, implementing camera modes, or handling cave/obstacle detection.

hexianWeb By hexianWeb schedule Updated 2/2/2026

name: vtj-camera-system description: Configures third-person camera systems with collision avoidance. Use when adjusting follow behavior, implementing camera modes, or handling cave/obstacle detection.

vite-threejs Camera System

Overview

本项目采用 第三人称相机系统,核心组件:

  • Camera:主相机类,管理模式切换
  • CameraRig:第三人称跟随逻辑,处理平滑跟随、避障、bobbing 效果

核心原则:相机通过锚点跟随玩家,使用 lerp 平滑过渡,通过方块检测避免穿模。

When to Use

  • 修改相机跟随行为
  • 调整相机偏移和平滑度
  • 实现新的相机模式
  • 处理相机与地形的碰撞

相机架构

┌─────────────────────────────────────────────────────────────┐
│                         Camera                               │
│  - mode: 'third-person' | 'bird-perspective'                │
│  - perspectiveCamera: THREE.PerspectiveCamera                │
│  - orbitControls: OrbitControls (鸟瞰模式用)                 │
│  - rig: CameraRig (第三人称用)                               │
└─────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────┐
│                       CameraRig                              │
│  - group: THREE.Group (跟随玩家位置)                         │
│  - cameraAnchor: THREE.Object3D (相机位置锚点)               │
│  - targetAnchor: THREE.Object3D (看向目标锚点)               │
│  - _smoothedPosition: 平滑后的位置                           │
│  - _smoothedLookAtTarget: 平滑后的看向点                     │
└─────────────────────────────────────────────────────────────┘

CameraRig 工作原理

锚点系统

// 锚点附着在 group 上,group 跟随玩家
this.group = new THREE.Group()
this.cameraAnchor = new THREE.Object3D()   // 相机实际位置
this.targetAnchor = new THREE.Object3D()   // 相机看向的点

// 锚点相对于玩家的偏移
this.cameraAnchor.position.copy(this.config.follow.offset)       // (2, 1.5, 3)
this.targetAnchor.position.copy(this.config.follow.targetOffset) // (0, 1.5, -5.5)

this.group.add(this.cameraAnchor)
this.group.add(this.targetAnchor)

平滑跟随

update() {
  const playerPos = this.target.position
  const facingAngle = this.target.facingAngle
  
  // 平滑位置插值
  this._smoothedPosition.lerp(playerPos, this.config.follow.smoothSpeed)
  this.group.position.copy(this._smoothedPosition)
  
  // 同步角色朝向
  this.group.rotation.y = facingAngle
  
  // 获取世界坐标
  const cameraPos = this.cameraAnchor.getWorldPosition(new THREE.Vector3())
  const targetPos = this.targetAnchor.getWorldPosition(new THREE.Vector3())
  
  // 平滑看向点
  this._smoothedLookAtTarget.lerp(targetPos, this.config.follow.lookAtSmoothSpeed)
  
  return { cameraPos, targetPos: this._smoothedLookAtTarget, fov: this._currentFov }
}

配置参数

// src/js/camera/camera-rig-config.js
export const CAMERA_RIG_CONFIG = {
  follow: {
    offset: new THREE.Vector3(2, 1.5, 3.0),        // 相机位置偏移
    targetOffset: new THREE.Vector3(0, 1.5, -5.5), // 看向点偏移
    smoothSpeed: 0.1,           // 位置平滑系数 (0-1)
    lookAtSmoothSpeed: 0.45,    // 看向平滑系数
    mouseTargetY: {
      sensitivity: 0.030,       // 鼠标 Y 轴灵敏度
      maxOffset: 4.5,           // 最大俯仰偏移
      returnSpeed: 1.5,         // 回中速度
      damping: 3.5,             // 阻尼
    },
  },
  trackingShot: {
    fov: { baseFov: 55, maxFov: 85, speedThreshold: 3.0 },
    bobbing: {
      verticalFrequency: 4.0,
      verticalAmplitude: 0.025,
      horizontalFrequency: 4.0,
      horizontalAmplitude: 0.015,
      rollFrequency: 4.0,
      rollAmplitude: 0.005,
      idleBreathing: { enabled: true, frequency: 0.7, amplitude: 0.015 },
    },
  },
}

洞穴避障(非射线方式)

本项目使用 方块检测 而非射线检测来处理相机穿模:

_checkBlockAbovePlayer(playerPos) {
  const checkHeights = [2, 3]        // 玩家头顶上方
  const checkRange = [-1, 0, 1]      // 3x3 XZ 区域
  let blockCount = 0
  
  for (const heightOffset of checkHeights) {
    for (const dx of checkRange) {
      for (const dz of checkRange) {
        const block = terrainManager.getBlockWorld(
          playerPos.x + dx,
          playerPos.y + heightOffset,
          playerPos.z + dz
        )
        if (block && block.id !== 0) blockCount++
        if (blockCount >= 4) return true  // 判定为在洞穴中
      }
    }
  }
  return false
}

洞穴模式处理

if (isInCave) {
  // 切换到近距离偏移
  this._caveOffset = new THREE.Vector3(0.0, 1.5, 1.0)
  this._caveTargetOffset = new THREE.Vector3(0, 1.5, -1.5)
  
  // 淡化玩家模型
  this.target.setOpacity(0.1)
} else {
  // 恢复正常偏移
  this.target.setOpacity(1.0)
}

肩膀切换(左右视角)

toggleSide() {
  this._currentSide *= -1  // 1 → -1 或 -1 → 1
  
  gsap.to(this, {
    _sideFactor: this._currentSide,
    duration: 0.6,
    ease: 'power2.inOut',
  })
}

// 应用到 X 偏移
this.config.follow.offset.x = this._targetOffset.x * this._sideFactor

触发方式

emitter.on('input:toggle_camera_side', () => this.toggleSide())

动态 FOV(速度感)

_updateDynamicFov(speed) {
  const { baseFov, maxFov, speedThreshold } = this.config.trackingShot.fov
  
  const speedRatio = Math.min(speed / speedThreshold, 1.0)
  const targetFov = baseFov + (maxFov - baseFov) * speedRatio
  
  this._currentFov += (targetFov - this._currentFov) * smoothSpeed
}

事件监听

CameraRig 监听以下事件:

constructor() {
  emitter.on('input:mouse_move', ({ movementY }) => {
    this.mouseYVelocity += movementY * this.config.sensitivity
  })
  
  emitter.on('input:wheel', ({ deltaY }) => {
    this._normalOffset.y += deltaY * sensitivity
  })
  
  emitter.on('input:toggle_camera_side', () => this.toggleSide())
  
  emitter.on('settings:camera-rig-changed', ({ fov, bobbing }) => {
    // 更新配置
  })
}

访问相机

// 在组件中
this.camera = this.experience.camera           // Camera 类实例
this.cameraInstance = this.experience.camera.instance  // THREE.Camera

// Camera 类提供的方法
this.experience.camera.switchMode('bird-perspective')
this.experience.camera.attachRig(player)

添加新相机模式

Step 1: 定义模式

// camera.js
this.cameraModes = {
  THIRD_PERSON: 'third-person',
  BIRD_PERSPECTIVE: 'bird-perspective',
  FIRST_PERSON: 'first-person',  // 新增
}

Step 2: 实现切换逻辑

switchMode(mode) {
  this.currentMode = mode
  
  switch (mode) {
    case 'first-person':
      this.rig?.disable()
      this.orbitControls.enabled = false
      // 第一人称特殊处理
      break
    // ...
  }
}

Common Mistakes

❌ 直接操作相机位置

// BAD: 直接设置相机位置
this.camera.instance.position.set(0, 5, 10)

// GOOD: 通过 CameraRig 控制
this.camera.rig.setOffset(new THREE.Vector3(0, 5, 10))

❌ 忘记平滑过渡

// BAD: 直接赋值(相机会跳跃)
this.group.position.copy(playerPos)

// GOOD: 使用 lerp 平滑
this._smoothedPosition.lerp(playerPos, smoothSpeed)
this.group.position.copy(this._smoothedPosition)

❌ 使用射线检测碰撞

// BAD: 复杂的射线碰撞检测
this.raycaster.set(cameraPos, direction)
const intersects = this.raycaster.intersectObjects(terrain)

// GOOD: 使用方块检测(本项目特有)
const isInCave = this._checkBlockAbovePlayer(playerPos)

Quick Reference

需求 方法
获取相机实例 this.experience.camera.instance
切换相机模式 this.experience.camera.switchMode('mode')
调整跟随平滑度 修改 config.follow.smoothSpeed
切换左右肩膀 rig.toggleSide()
响应设置变化 监听 settings:camera-rig-changed
配置项 说明
follow.offset 相机相对玩家的位置偏移
follow.targetOffset 看向点相对玩家的偏移
follow.smoothSpeed 位置平滑系数 (0-1)
trackingShot.fov 动态 FOV 配置
trackingShot.bobbing 行走晃动配置
Install via CLI
npx skills add https://github.com/hexianWeb/Third-Person-MC --skill vtj-camera-system
Repository Details
star Stars 144
call_split Forks 37
navigation Branch main
article Path SKILL.md
More from Creator