mcpField
C
camera

camera

github.com/drawcall-ai開発124 回インストール
リポジトリ

camera について

ゲームカメラを構築するためのスキルです。viverse のキャラクター向けカメラと、独自の一人称・三人称リグを扱い、画面揺れや速度に応じた FOV 効果も紹介します。

主な機能

  1. 01キャラクターの旋回・衝突・ズーム
  2. 02一人称・三人称リグの構築
  3. 03アイドル時の静止姿勢への復帰
  4. 04画面揺れ効果
  5. 05速度に応じた FOV 調整

活用シーン

  1. 01プレイヤーキャラクターのカメラ設定
  2. 02独自の一人称・旋回カメラの構築
  3. 03アイドル時にカメラ角度を静止姿勢へ戻す
  4. 04ゲームカメラに揺れや速度表現を追加

インストール

コマンドをコピーしてターミナルで実行します。

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

インストール後、Claude Code で/cameraを入力して読み込まれたか確認します。

詳細

カメラ

ゲームカメラは、ピッチとヨーを制限しながらターゲットを追従し、衝突判定とズームにも対応します。まず、どちらの方式にするか選ぶ必要があります。プレイヤーキャラクターには、すぐに使える viverse の CharacterCameraBehavior をそのまま使います。完全にカスタムなリグが必要な場合に限り、カメラを一から構築してください。キャラクターカメラを独自に実装すると、上下に狙えないヨー固定や壁抜けなど、組み込み機能ですでに解決されている同じバグを再現しがちです。

カスタムリグでは、共通の規約として 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] のような肩越しのオフセットは、カメラを回転させても視点に対する肩の位置を保ちます。一方、垂直方向のみのオフセット(デフォルト、および一人称の場合)はヨーの影響を受けません。フレーミングを安定させるために、キャラクターがカメラの方を向く必要はありません。

カメラを一から構築する

完全にカスタムなリグには、最小限の 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 の速度連動という 2 つのエフェクトを、どのカメラにも追加できます。

画面の揺れ

カメラの位置に適用される、ランダムなオフセットが徐々に減衰する揺れです。

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 の両方のオフセットが減衰します。

視野角の速度効果

プレイヤーの速度が上がるにつれて視野角を広げ、速度感を表現します。

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() を呼び出してください。そうしないと、新しい値が反映されません。

コメント