【Cocos Creator】アタッチするだけ!MouseRotator (マウス追従回転)の実装方法【TypeScript】

Godot 4ゲーム制作 実践ドリル 100本ノック

新品価格
¥1,250から
(2025/12/13 21:27時点)

脱・初心者!Godot 4 ゲーム開発の「2歩目」

新品価格
¥831から
(2025/12/13 21:39時点)

Godot4ローグライク入門 ~ダンジョン自動生成~

新品価格
¥831から
(2025/12/13 21:44時点)

Godot4& GDScriptではじめる 2Dゲーム開発レシピ

新品価格
¥590から
(2025/12/13 21:46時点)

【Cocos Creator 3.8】MouseRotator の実装:アタッチするだけで「ノードをマウスカーソルの方向へ常に回転」させる汎用スクリプト

このコンポーネント MouseRotator を任意のノードにアタッチするだけで、そのノード(正確には「親の向き」=ノードの回転)が常にマウスカーソルの方向を向くようになります。2Dシューティングのプレイヤー機やタレット、キャラクターの顔の向きなど、「とにかくマウスの方向を向かせたい」場面でそのまま使える汎用スクリプトです。

外部の GameManager やシングルトンに一切依存せず、インスペクタからの設定だけで完結するように設計します。


コンポーネントの設計方針

基本方針

  • このコンポーネントをアタッチしたノードの 回転 を、毎フレーム マウスカーソルの位置に向ける。
  • 座標系は 2Dゲーム(Canvas / UI / 2Dカメラ) を想定しつつ、任意のカメラ を指定できるようにする。
  • 他のノードやカスタムスクリプトには依存せず、インスペクタのプロパティだけで完結 させる。
  • 防御的実装として、必要な設定(カメラなど)が不足している場合は console.error で警告し、挙動を止める。

座標変換の考え方

  1. マウスのスクリーン座標(ピクセル)を取得。
  2. 指定されたカメラで screenToWorld を使い、ワールド座標 に変換。
  3. 対象ノードのワールド座標を取得。
  4. 「ノード → マウス」のベクトルから角度(ラジアン → 度)を計算。
  5. その角度をノードの回転に反映する(Z軸回転)。

インスペクタで設定可能なプロパティ

以下のプロパティを設計します。

  • cameraCamera
    • マウス座標の変換に使用するカメラ。
    • 通常は 2Dゲームなら Canvas 配下の Main Camera を指定。
    • 未設定の場合は console.error を出して動作を停止。
  • rotationOffsetnumber
    • ノードの見た目の「正面」と、数学的な「右向き(0度)」とのズレを補正するための角度(度)。
    • 例:ノードの Sprite が右向きで正面なら 0、上向きが正面なら 90 など。
    • 正の値で反時計回りオフセット。
  • smoothboolean
    • false:瞬時にマウス方向を向く。
    • true:一定速度で追従する(なめらか回転)。
  • rotateSpeednumber
    • なめらか回転時の角速度(度/秒)。
    • smooth = true のときのみ使用。
    • 例:360 なら 1秒で1回転の速度。
  • limitMinAngleboolean
    • 回転角度の下限(最小角)を制限するかどうか。
    • キャラクターが「背中を向けすぎない」ように制限したいときなどに使用。
  • minAnglenumber
    • 下限角度(度)。
    • limitMinAngle = true のときのみ有効。
  • limitMaxAngleboolean
    • 回転角度の上限(最大角)を制限するかどうか。
  • maxAnglenumber
    • 上限角度(度)。
    • limitMaxAngle = true のときのみ有効。
  • enableOnStartboolean
    • truestart() 時に自動で有効化。
    • false:無効状態で開始し、必要に応じて enabled を切り替える(将来の拡張用)。

角度制限は、「キャラクターが左右 60度 までしか首を振らない」といった表現に便利です。制限しない場合はフル 360度 回転します。


TypeScriptコードの実装

以下が完成版の MouseRotator.ts です。


import { _decorator, Component, Node, Camera, input, Input, EventMouse, Vec3, math } from 'cc';
const { ccclass, property } = _decorator;

/**
 * MouseRotator
 * 任意のノードをマウスカーソルの方向へ回転させる汎用コンポーネント。
 * - 他スクリプトへの依存なし
 * - カメラをインスペクタから指定
 * - スムーズ回転 / 角度制限に対応
 */
@ccclass('MouseRotator')
export class MouseRotator extends Component {

    @property({
        type: Camera,
        tooltip: 'マウス座標の変換に使用するカメラ。\n' +
                 '通常は Canvas 配下の 2D カメラを指定してください。'
    })
    public camera: Camera | null = null;

    @property({
        tooltip: 'ノードの見た目の正面と、数学的な右向き(0度)とのズレを補正する角度(度)。\n' +
                 '例: 上向きが正面なら 90、下向きが正面なら -90 など。'
    })
    public rotationOffset: number = 0;

    @property({
        tooltip: 'true の場合、一定速度でなめらかに回転します。\nfalse の場合、即座にマウス方向を向きます。'
    })
    public smooth: boolean = false;

    @property({
        tooltip: 'なめらか回転時の角速度(度/秒)。\n's +
                 'smooth が true のときのみ使用されます。'
    })
    public rotateSpeed: number = 360;

    @property({
        tooltip: '最小角度の制限を有効にするかどうか。'
    })
    public limitMinAngle: boolean = false;

    @property({
        tooltip: '最小角度(度)。limitMinAngle が true のときのみ有効です。'
    })
    public minAngle: number = -180;

    @property({
        tooltip: '最大角度の制限を有効にするかどうか。'
    })
    public limitMaxAngle: boolean = false;

    @property({
        tooltip: '最大角度(度)。limitMaxAngle が true のときのみ有効です。'
    })
    public maxAngle: number = 180;

    @property({
        tooltip: 'true の場合、start 時に自動で有効化します。'
    })
    public enableOnStart: boolean = true;

    // 内部状態
    private _isActive: boolean = false;
    private _lastMouseWorldPos: Vec3 = new Vec3();

    onLoad() {
        // 必要なカメラが設定されているかをチェック
        if (!this.camera) {
            console.error('[MouseRotator] camera が設定されていません。' +
                          'Inspector で使用する Camera を指定してください。', this.node);
        }

        // マウス移動イベントを購読して、最後のマウス位置を保持
        input.on(Input.EventType.MOUSE_MOVE, this._onMouseMove, this);
    }

    start() {
        this._isActive = this.enableOnStart;

        if (!this.camera) {
            // カメラ未設定の場合は動作を停止
            this._isActive = false;
        }
    }

    onDestroy() {
        // イベント購読の解除
        input.off(Input.EventType.MOUSE_MOVE, this._onMouseMove, this);
    }

    /**
     * マウス移動イベントハンドラ
     * 画面上のマウス座標をワールド座標に変換して保持する。
     */
    private _onMouseMove(event: EventMouse) {
        if (!this.camera) {
            return;
        }

        const screenPos = event.getLocation(); // Vec2 (x, y)
        const out = this._lastMouseWorldPos;
        // z は 0 (2D) として扱う
        this.camera.screenToWorld(new Vec3(screenPos.x, screenPos.y, 0), out);
    }

    update(deltaTime: number) {
        if (!this._isActive) {
            return;
        }

        if (!this.camera) {
            // カメラがない場合はエラーを出して停止(onLoad でもチェック済みだが念のため)
            console.error('[MouseRotator] camera が設定されていないため、回転処理を中止します。', this.node);
            this._isActive = false;
            return;
        }

        // ノードの現在のワールド位置を取得
        const nodeWorldPos = this.node.worldPosition;

        // ノードからマウスへの方向ベクトル
        const dir = new Vec3(
            this._lastMouseWorldPos.x - nodeWorldPos.x,
            this._lastMouseWorldPos.y - nodeWorldPos.y,
            0
        );

        if (dir.lengthSqr() === 0) {
            // 同一位置にいる場合、回転不要
            return;
        }

        // atan2(y, x) でラジアン角を取得し、度に変換
        let angleDeg = math.toDegree(Math.atan2(dir.y, dir.x));

        // 見た目の正面とのズレを補正
        angleDeg += this.rotationOffset;

        // 角度制限
        angleDeg = this._applyAngleLimit(angleDeg);

        // 現在角度
        const currentEuler = this.node.eulerAngles;
        const currentZ = currentEuler.z;

        let newZ = angleDeg;

        if (this.smooth) {
            // スムーズ回転: 現在角度から目標角度へ、rotateSpeed * deltaTime 以内で補間
            const maxStep = this.rotateSpeed * deltaTime;
            newZ = this._moveAngleTowards(currentZ, angleDeg, maxStep);
        }

        // Z軸回転のみ変更(X, Y は維持)
        this.node.setRotationFromEuler(currentEuler.x, currentEuler.y, newZ);
    }

    /**
     * 角度制限を適用する
     */
    private _applyAngleLimit(angle: number): number {
        let result = angle;

        if (this.limitMinAngle && result < this.minAngle) {
            result = this.minAngle;
        }

        if (this.limitMaxAngle && result > this.maxAngle) {
            result = this.maxAngle;
        }

        return result;
    }

    /**
     * 角度 a から b へ、最大 step 度だけ近づける(-180〜180 の範囲を考慮)
     */
    private _moveAngleTowards(a: number, b: number, step: number): number {
        // 差分を [-180, 180] に正規化
        let delta = this._deltaAngle(a, b);

        if (Math.abs(delta) <= step) {
            return b;
        }

        // 符号に応じて step 分だけ進める
        return a + Math.sign(delta) * step;
    }

    /**
     * 2つの角度の差を [-180, 180] に正規化して返す
     */
    private _deltaAngle(current: number, target: number): number {
        let delta = (target - current) % 360;
        if (delta > 180) delta -= 360;
        if (delta < -180) delta += 360;
        return delta;
    }

    /**
     * 外部から有効/無効を切り替えるためのメソッド(将来の拡張用)
     */
    public setActive(active: boolean) {
        this._isActive = active && !!this.camera;
    }
}

コードのポイント解説

  • onLoad
    • カメラが設定されているかチェックし、未設定なら console.error を出力。
    • input.on(Input.EventType.MOUSE_MOVE, ...) でマウス移動イベントを購読し、最後に検出したマウスのワールド座標を _lastMouseWorldPos に保持。
  • start
    • enableOnStart に応じて _isActive を設定。
    • カメラ未設定の場合は強制的に _isActive = false にして、回転処理を止める。
  • update
    • _isActivefalse の場合は何もしない。
    • ノードのワールド位置とマウスのワールド位置から方向ベクトルを計算し、atan2 で角度に変換。
    • rotationOffset で見た目の正面を補正し、_applyAngleLimit で角度制限を適用。
    • smoothtrue の場合は _moveAngleTowards により、rotateSpeed * deltaTime を上限としたスムーズな補間。
    • 最終的に setRotationFromEuler で Z軸回転のみを更新。
  • 角度補助メソッド
    • _deltaAngle:2つの角度の差を [-180, 180] に正規化し、最短回転方向を求める。
    • _moveAngleTowards:現在角度から目標角度へ、1フレームあたり最大 step 度だけ近づける。

使用手順と動作確認

1. スクリプトファイルの作成

  1. エディタ左下の Assets パネルで、任意のフォルダ(例:assets/scripts)を右クリックします。
  2. Create → TypeScript を選択します。
  3. 作成されたファイル名を MouseRotator.ts に変更します。
  4. ダブルクリックして開き、内容をすべて削除して、前述の TypeScript コードを貼り付けて保存します。

2. テスト用ノードの作成

ここでは 2Dシーンで、マウス方向を向くタレットのようなノードを例にします。

  1. Hierarchy パネルで右クリック → Create → 2D Object → Sprite を選択し、テスト用のスプライトノードを作成します。
    • 名前は例として Turret にしておきます。
  2. 選択した Turret ノードの Inspector で、Sprite に適当な画像を設定します。
    • 画像の「向き」がどちらを正面とするかを覚えておいてください(後で rotationOffset を設定するため)。

3. カメラの確認

  1. 2Dプロジェクトの場合、通常は Canvas 配下に Main Camera があります。
    • Hierarchy で Main Camera を選択し、Inspector で Camera コンポーネントが付いているか確認します。
    • なければ Add Component → Rendering → Camera で追加してください。

4. MouseRotator コンポーネントのアタッチ

  1. Hierarchy で、先ほど作成した Turret ノードを選択します。
  2. Inspector の下部で Add Component ボタンをクリックします。
  3. Custom Component(または Custom カテゴリ)から MouseRotator を選択します。
    • 一覧に見つからない場合は、スクリプトを保存したかどうか、コンパイルエラーが出ていないか確認してください。

5. プロパティの設定

Turret ノードの Inspector に表示された MouseRotator の各プロパティを設定します。

  1. Camera
    • 右側の丸いピッカーをクリックし、Main Camera を選択します。
    • または Hierarchy から Main Camera をドラッグ&ドロップしてもOKです。
  2. Rotation Offset
    • スプライトの画像が「右向き」を正面として描かれている場合:0 のままでOK。
    • スプライトが「上向き」を正面として描かれている場合:90 を設定。
    • スプライトが「左向き」を正面として描かれている場合:180 または -180 を設定。
    • スプライトが「下向き」を正面として描かれている場合:-90 を設定。
  3. Smooth
    • カクッと即座に向きを変えたい:false(デフォルト)
    • なめらかに追従させたい:true
  4. Rotate Speed
    • Smooth = true の場合にのみ有効。
    • 試しに 360(1秒で1回転)あたりから調整してください。
  5. Limit Min Angle / Min Angle(任意)
    • 例えば「-60度以下には向かせたくない」場合:
      • Limit Min Angle = true
      • Min Angle = -60
  6. Limit Max Angle / Max Angle(任意)
    • 例えば「60度以上には向かせたくない」場合:
      • Limit Max Angle = true
      • Max Angle = 60
  7. Enable On Start
    • 基本は true のままでOKです。
    • 将来、別スクリプトから setActive(false) で一時停止したい場合などに使えます。

6. 動作確認

  1. 上部メニューから Project → Preview をクリックし、ブラウザプレビューを起動します。
  2. ゲーム画面上でマウスカーソルを動かしてみてください。
  3. Turret ノードが、常にマウスカーソルの方向を向いて回転しているはずです。
  4. もし動かない場合は、以下を確認してください。
    • コンソールに [MouseRotator] camera が設定されていません などのエラーが出ていないか。
    • MouseRotatorCamera プロパティに正しくカメラが設定されているか。
    • プレビューのゲームウィンドウをアクティブにしてマウスを動かしているか。

まとめ

この MouseRotator コンポーネントは、

  • 任意のノードをマウスカーソルの方向へ常に回転させる
  • カメラ参照・回転オフセット・スムーズ回転・角度制限をすべてインスペクタから設定可能
  • 他のスクリプトやシングルトンに一切依存しない完全な独立コンポーネント

という特徴を持っています。

2Dシューティングのプレイヤー機、タレット、キャラクターの顔の向き、ポインティングUIなど、「マウス方向を向く」あらゆる場面にそのまま再利用できます。プロジェクトの assets/scripts/common のような場所に置いておけば、今後のゲームでもドラッグ&ドロップで即戦力として使い回せるはずです。

このように、汎用性の高い挙動をコンポーネントとして切り出し、インスペクタでパラメータ化しておくことで、ゲームごとの実装コストを大きく削減できます。ぜひ自分のプロジェクト用に微調整しつつ、再利用可能なコンポーネント群として育てていってください。

Godot 4ゲーム制作 実践ドリル 100本ノック

新品価格
¥1,250から
(2025/12/13 21:27時点)

脱・初心者!Godot 4 ゲーム開発の「2歩目」

新品価格
¥831から
(2025/12/13 21:39時点)

Godot4ローグライク入門 ~ダンジョン自動生成~

新品価格
¥831から
(2025/12/13 21:44時点)

Godot4& GDScriptではじめる 2Dゲーム開発レシピ

新品価格
¥590から
(2025/12/13 21:46時点)

URLをコピーしました!