【Cocos Creator 3.8】MousePeek(マウス視点)の実装:アタッチするだけで「キーを押している間だけマウス方向にカメラをずらす」を実現する汎用スクリプト
このコンポーネントは、カメラノードにアタッチしておくだけで、「特定のキーを押している間だけ、マウスカーソルの方向にカメラをスッとずらす」視点操作を実現します。
2Dアクションや見下ろし型ゲームで、プレイヤーの視界を少し先まで見せたいときに便利で、外部の GameManager などに一切依存せず、このスクリプト単体で完結します。
コンポーネントの設計方針
1. 機能要件の整理
- このコンポーネントを カメラノード にアタッチすると、指定したキーを押している間だけ、カメラがマウス位置の方向へオフセット移動する。
- キーを離すと、カメラは元の位置にスムーズに戻る。
- マウスの絶対座標ではなく、画面中心からマウス方向への「相対ベクトル」 を使ってカメラをずらす。
- ずらす距離や反応のスムーズさ、対象キーなどはすべてインスペクタから調整できる。
- 他のカスタムスクリプトには一切依存せず、このコンポーネント単体で完結する。
2. 外部依存をなくすためのアプローチ
- Camera コンポーネントの自動取得
アタッチされたノードにCameraがある前提とし、onLoad()でgetComponent(Camera)を試みます。
見つからなかった場合はerrorログを出し、処理を無効化します(防御的実装)。 - イベント登録の自己管理
マウスとキーボードの入力はsystemEvent(3.8 ではsystemEvent/input)を使い、onEnable()で登録、onDisable()で解除します。他スクリプトとは共有しません。 - 元位置の保持
カメラの「基準位置」を_basePositionとして内部に保持し、その周りをオフセットさせます。
他ノード(プレイヤーなど)に追従させたい場合は、そのロジックは別に書き、このコンポーネントは「基準位置からの視点ずらし」だけを担当する、という設計にします。
3. インスペクタで設定可能なプロパティ
以下のプロパティを用意します:
enabledPeek: boolean
– デフォルト:true
– このフラグがfalseのときは、キーを押してもカメラをずらさない(デバッグ用の一時無効化などに便利)。peekKey: KeyCode
– デフォルト:KeyCode.SHIFT_LEFT
– 「このキーを押している間だけ視点をずらす」ためのキー。
– 例:KeyCode.SPACE,KeyCode.SHIFT_LEFT,KeyCode.MOUSE_BUTTON_RIGHTなど。maxOffset: number
– デフォルト:200
– 画面中心からマウス方向へのオフセットの最大距離(ワールド座標ベース)。
– 数値を大きくすると、より遠くまでカメラを動かせます。lerpSpeed: number
– デフォルト:8
– カメラが「現在位置 → 目標位置」へ追従する速度。
– 値が大きいほど素早く追従し、小さいほどゆっくり滑らかに動きます。useWorldSpace: boolean
– デフォルト:true
–true: カメラの ワールド座標 を直接動かす(多くのケースでおすすめ)。
–false: カメラの ローカル座標 を動かす(カメラが他ノードの子になっている特殊な構造で使いたい場合)。lockX: boolean
– デフォルト:false
–trueの場合、X方向のオフセットを無効化し、Y方向だけずらす。lockY: boolean
– デフォルト:false
–trueの場合、Y方向のオフセットを無効化し、X方向だけずらす。debugDrawGizmo: boolean
– デフォルト:false
–trueにすると、editor上でカメラの基準位置と現在位置、オフセット方向を簡易的にログで確認できる(実行時の挙動確認用)。
– 実際のゲームビルドには影響しません。
TypeScriptコードの実装
以下が完成した MousePeek.ts の全コードです。
import { _decorator, Component, Node, Vec2, Vec3, Camera, input, Input, EventMouse, EventKeyboard, KeyCode, view, math, systemEvent, SystemEventType } from 'cc';
const { ccclass, property } = _decorator;
/**
* MousePeek
* カメラにアタッチして使用する、マウス方向への視点ずらしコンポーネント。
*/
@ccclass('MousePeek')
export class MousePeek extends Component {
@property({
tooltip: 'このフラグが false の場合、キー入力に関係なく視点ずらしを行いません。'
})
public enabledPeek: boolean = true;
@property({
tooltip: 'このキーを押している間だけ、マウス方向へカメラをずらします。\n例: KeyCode.SHIFT_LEFT, KeyCode.SPACE など。'
})
public peekKey: KeyCode = KeyCode.SHIFT_LEFT;
@property({
tooltip: '画面中心からマウス方向へのオフセットの最大距離(ワールド座標)。\n値を大きくすると、より遠くまでカメラを移動させます。'
})
public maxOffset: number = 200;
@property({
tooltip: 'カメラが目標位置へ追従するスピード。\n値が大きいほど素早く追従し、小さいほどゆっくり移動します。'
})
public lerpSpeed: number = 8;
@property({
tooltip: 'true: カメラのワールド座標を移動します。\nfalse: カメラノードのローカル座標を移動します。'
})
public useWorldSpace: boolean = true;
@property({
tooltip: 'true の場合、X方向のオフセットを無効化します(上下方向のみの視点ずらし)。'
})
public lockX: boolean = false;
@property({
tooltip: 'true の場合、Y方向のオフセットを無効化します(左右方向のみの視点ずらし)。'
})
public lockY: boolean = false;
@property({
tooltip: 'true にすると、実行中にオフセット情報をログ出力します(デバッグ用)。\nゲームビルドには影響しません。'
})
public debugDrawGizmo: boolean = false;
private _camera: Camera | null = null;
private _basePosition: Vec3 = new Vec3(); // カメラの基準位置
private _targetOffset: Vec3 = new Vec3(); // 現在目標としているオフセット
private _currentOffset: Vec3 = new Vec3(); // 実際に適用中のオフセット(補間用)
private _isPeekKeyPressed: boolean = false; // 視点ずらしキーが押下中かどうか
private _lastMousePos: Vec2 = new Vec2(); // 直近のマウス座標(スクリーン座標)
onLoad() {
// カメラコンポーネントを取得
this._camera = this.getComponent(Camera);
if (!this._camera) {
console.error('[MousePeek] このコンポーネントは Camera コンポーネントを持つノードにアタッチしてください。');
}
// 初期位置を基準位置として保存
if (this.useWorldSpace) {
this._basePosition.set(this.node.worldPosition);
} else {
this._basePosition.set(this.node.position);
}
this._currentOffset.set(Vec3.ZERO);
this._targetOffset.set(Vec3.ZERO);
}
onEnable() {
// 入力イベント登録
input.on(Input.EventType.MOUSE_MOVE, this._onMouseMove, this);
input.on(Input.EventType.MOUSE_DOWN, this._onMouseDown, this);
input.on(Input.EventType.MOUSE_UP, this._onMouseUp, this);
input.on(Input.EventType.KEY_DOWN, this._onKeyDown, this);
input.on(Input.EventType.KEY_UP, this._onKeyUp, this);
}
onDisable() {
// 入力イベント解除
input.off(Input.EventType.MOUSE_MOVE, this._onMouseMove, this);
input.off(Input.EventType.MOUSE_DOWN, this._onMouseDown, this);
input.off(Input.EventType.MOUSE_UP, this._onMouseUp, this);
input.off(Input.EventType.KEY_DOWN, this._onKeyDown, this);
input.off(Input.EventType.KEY_UP, this._onKeyUp, this);
}
start() {
// 実行開始時点でもう一度基準位置を更新しておく(他スクリプトで位置が変わっている可能性に備える)
if (this.useWorldSpace) {
this._basePosition.set(this.node.worldPosition);
} else {
this._basePosition.set(this.node.position);
}
}
update(deltaTime: number) {
if (!this._camera) {
return;
}
// 毎フレーム、基準位置を更新しておくことで、
// 他のスクリプトでカメラを移動させつつ、その位置からのオフセットとして MousePeek を適用できる。
if (this.useWorldSpace) {
this._basePosition.set(this.node.worldPosition);
} else {
this._basePosition.set(this.node.position);
}
// 視点ずらしが有効で、キーが押されている場合のみ、ターゲットオフセットを更新
if (this.enabledPeek && this._isPeekKeyPressed) {
this._updateTargetOffsetFromMouse();
} else {
// キーが離されている場合は、ターゲットオフセットをゼロに戻す
this._targetOffset.set(Vec3.ZERO);
}
// 現在のオフセットをターゲットオフセットへ補間(滑らかに移動)
Vec3.lerp(this._currentOffset, this._currentOffset, this._targetOffset, math.clamp01(this.lerpSpeed * deltaTime));
// 実際のカメラ位置を反映
const newPos = new Vec3(
this._basePosition.x + this._currentOffset.x,
this._basePosition.y + this._currentOffset.y,
this._basePosition.z + this._currentOffset.z
);
if (this.useWorldSpace) {
this.node.setWorldPosition(newPos);
} else {
this.node.setPosition(newPos);
}
if (this.debugDrawGizmo) {
// 簡易的なデバッグ出力(毎フレーム出すと多いので、必要に応じてコメントアウトしてください)
// console.log('[MousePeek] base=', this._basePosition, 'offset=', this._currentOffset);
}
}
/**
* マウス移動イベント
*/
private _onMouseMove(event: EventMouse) {
event.getLocation(this._lastMousePos);
}
/**
* マウスボタン押下イベント
* MOUSE_BUTTON_LEFT / RIGHT などを peekKey に設定した場合に対応
*/
private _onMouseDown(event: EventMouse) {
const buttonCode = this._convertMouseButtonToKeyCode(event.getButton());
if (buttonCode === this.peekKey) {
this._isPeekKeyPressed = true;
}
event.getLocation(this._lastMousePos);
}
private _onMouseUp(event: EventMouse) {
const buttonCode = this._convertMouseButtonToKeyCode(event.getButton());
if (buttonCode === this.peekKey) {
this._isPeekKeyPressed = false;
}
event.getLocation(this._lastMousePos);
}
/**
* キーボード押下イベント
*/
private _onKeyDown(event: EventKeyboard) {
if (event.keyCode === this.peekKey) {
this._isPeekKeyPressed = true;
}
}
private _onKeyUp(event: EventKeyboard) {
if (event.keyCode === this.peekKey) {
this._isPeekKeyPressed = false;
}
}
/**
* 現在のマウス位置から、カメラのオフセット目標を計算する。
* 画面中心からマウス位置へのベクトルを正規化し、maxOffset を掛ける。
*/
private _updateTargetOffsetFromMouse() {
// 画面サイズを取得
const size = view.getVisibleSize();
const screenCenter = new Vec2(size.width * 0.5, size.height * 0.5);
// 画面中心 → マウス位置 へのベクトル
const dir = new Vec2(
this._lastMousePos.x - screenCenter.x,
this._lastMousePos.y - screenCenter.y
);
if (dir.lengthSqr() <= 0.0001) {
// ほぼ中心ならオフセットなし
this._targetOffset.set(Vec3.ZERO);
return;
}
dir.normalize();
// ロック設定に応じて軸を制限
if (this.lockX) {
dir.x = 0;
}
if (this.lockY) {
dir.y = 0;
}
// 再度、ゼロベクトルでないか確認
if (dir.lengthSqr() <= 0.0001) {
this._targetOffset.set(Vec3.ZERO);
return;
}
// 2Dゲーム想定のため、Z はそのまま(0 のまま)
this._targetOffset.set(
dir.x * this.maxOffset,
dir.y * this.maxOffset,
0
);
}
/**
* マウスボタンインデックスを KeyCode に変換するヘルパー。
* - 左クリック: KeyCode.MOUSE_BUTTON_LEFT
* - 右クリック: KeyCode.MOUSE_BUTTON_RIGHT
* - 中クリック: KeyCode.MOUSE_BUTTON_MIDDLE
*/
private _convertMouseButtonToKeyCode(button: number): KeyCode {
switch (button) {
case EventMouse.BUTTON_LEFT:
return KeyCode.MOUSE_BUTTON_LEFT;
case EventMouse.BUTTON_RIGHT:
return KeyCode.MOUSE_BUTTON_RIGHT;
case EventMouse.BUTTON_MIDDLE:
return KeyCode.MOUSE_BUTTON_MIDDLE;
default:
return KeyCode.NONE;
}
}
}
コードのポイント解説
- onLoad()
– アタッチされたノードからCameraコンポーネントを取得し、存在しなければconsole.errorを出して処理を止めます。
– カメラの現在位置を_basePositionとして保存し、オフセット計算の基準にします。 - onEnable() / onDisable()
–input.on(...)でマウス・キーボードイベントを登録/解除します。
– このコンポーネント単体で入力を完結させるため、外部の InputManager などには依存しません。 - update(deltaTime)
– 毎フレーム、カメラの「基準位置」を更新(他スクリプトで移動させられていても、その位置からオフセットを足す設計)。
–enabledPeekと_isPeekKeyPressedをチェックし、必要に応じて_updateTargetOffsetFromMouse()で目標オフセットを更新。
–Vec3.lerpで_currentOffsetを_targetOffsetに補間し、スムーズなカメラ移動を実現。 - _updateTargetOffsetFromMouse()
–view.getVisibleSize()で画面サイズを取得し、画面中心座標を計算。
– 「画面中心 → マウス位置」のベクトルを正規化し、maxOffsetを掛けてオフセット量に変換。
–lockX/lockYが有効な場合は、その軸を 0 にして一方向だけの視点ずらしにできます。 - マウスボタン対応
–peekKeyにKeyCode.MOUSE_BUTTON_RIGHTなどを設定した場合に備え、
_convertMouseButtonToKeyCode()でマウスボタンをKeyCodeに変換して扱っています。
使用手順と動作確認
1. スクリプトファイルの作成
- エディタの Assets パネルで任意のフォルダ(例:
assets/scripts)を右クリックします。 - Create → TypeScript を選択し、ファイル名を
MousePeek.tsにします。 - 自動生成された
MousePeek.tsをダブルクリックして開き、内容をすべて削除してから、前述のコードをそのまま貼り付けて保存します。
2. テスト用シーンとカメラの準備
- Hierarchy パネルで、テスト用のシーンを開くか、新規シーンを作成します。
- すでに Main Camera が存在する場合は、それを利用します。存在しない場合は:
- Hierarchy パネルの空白部分を右クリック → Create → 3D Object → Camera を選択してカメラを作成します。
- カメラノードを選択し、Inspector パネルで
Cameraコンポーネントが付いていることを確認します。
もし付いていなければ、Add Component → Rendering → Camera から追加してください。
3. MousePeek コンポーネントのアタッチ
- Hierarchy でカメラノード(例:
Main Camera)を選択します。 - Inspector パネルの下部で Add Component → Custom → MousePeek を選択し、コンポーネントを追加します。
4. プロパティの設定例
Inspector 上で、MousePeek コンポーネントの各プロパティを以下のように設定してみます:
- Enabled Peek:
true - Peek Key:
KeyCode.SHIFT_LEFT(一覧から選択) - Max Offset:
250 - Lerp Speed:
10 - Use World Space:
true - Lock X:
false - Lock Y:
false - Debug Draw Gizmo:
false(最初はオフでOK)
5. プレビューでの動作確認
- エディタ右上の Play ボタン(再生)を押して、ゲームビューで再生します。
- 再生中に、マウスカーソルをゲームビュー内でぐるぐる動かしてみます。この時点では、まだカメラは動きません。
- 左Shiftキー(Peek Key で指定したキー)を押しっぱなしにします。
- 左Shiftを押したままマウスを動かすと、画面中心からマウスの方向へ、カメラがスッとオフセットされていきます。
- マウスを画面の右上に持っていくと、カメラも右上方向へ移動します。
- キーを離すと、カメラは元の位置にスムーズに戻ります。
6. よくある調整パターン
- オフセットが大きすぎる/小さすぎる
– Max Offset を調整します。
– 2Dアクションなら 150〜300 程度、見下ろしマップなら 300〜600 程度を目安にするとよいです。 - 動きがカクカクする/遅すぎる
– Lerp Speed を上げると、目標位置への追従が速くなります(例: 5 → 15)。
– ピタッと追従させたい場合は 20 以上にしてみてください。 - 左右だけ or 上下だけに動かしたい
– 左右だけ → Lock Y をtrueに。
– 上下だけ → Lock X をtrueに。 - 右クリックで視点ずらししたい
– Peek Key をKeyCode.MOUSE_BUTTON_RIGHTに変更します。
まとめ
MousePeek コンポーネントは、
- カメラノードにアタッチするだけで、
- 特定キーを押している間だけマウス方向へ視点をずらし、
- キーを離すとスムーズに元の位置に戻る
という「マウス視点移動」を、外部スクリプトに一切依存せずに実現します。
このスクリプト単体で完結しているため、
- プレイヤー追従カメラに「視界を少し先まで伸ばす」機能を後付けする
- 探索ゲームで「Shiftキーを押している間だけ遠くを見渡す」ギミックを簡単に追加する
- 右クリックを押している間だけ「敵のいる方向を少し覗き込む」などの演出を作る
といった用途に、そのまま再利用できます。
プロパティから挙動を細かく調整できるようにしているので、ゲームごとに「どのくらい動かすか」「どのキーで発動するか」を変えながら、同じコンポーネントを使い回せます。
新しいプロジェクトでも MousePeek.ts を 1 ファイルコピーしてカメラにアタッチするだけで、同じ視点操作をすぐに導入できるはずです。




