camera について
ゲームカメラを構築するためのスキルです。viverse のキャラクター向けカメラと、独自の一人称・三人称リグを扱い、画面揺れや速度に応じた FOV 効果も紹介します。
主な機能
- 01キャラクターの旋回・衝突・ズーム
- 02一人称・三人称リグの構築
- 03アイドル時の静止姿勢への復帰
- 04画面揺れ効果
- 05速度に応じた FOV 調整
活用シーン
- 01プレイヤーキャラクターのカメラ設定
- 02独自の一人称・旋回カメラの構築
- 03アイドル時にカメラ角度を静止姿勢へ戻す
- 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/speedoptions.zoom:minDistance/maxDistance/speedoptions.collisionoptions.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() を呼び出してください。そうしないと、新しい値が反映されません。