【Cocos Creator】アタッチするだけ!LimitSetter (移動制限)の実装方法【TypeScript】

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

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

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

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

Unity 6 C#スクリプト 100本ノック

新品価格
¥1,230から
(2025/12/29 10:28時点)

Cocos Creator100本ノックTypeScriptで書く!

新品価格
¥1,250から
(2025/12/29 10:32時点)

【Cocos Creator 3.8】LimitSetter(移動制限)の実装:アタッチするだけでタイルマップに合わせた Camera2D の移動限界を自動設定する汎用スクリプト

本記事では、タイルマップのサイズに合わせて Camera2D の移動範囲(limit)を自動計算して設定するコンポーネント「LimitSetter」を実装します。
タイルマップベースの 2D ゲームで、カメラがマップ外の余白を映さないようにするための典型的な処理を、「カメラノードにアタッチするだけ」で完結させることが目的です。

外部の GameManager やシングルトンには一切依存せず、必要な情報はすべてインスペクタで設定可能なプロパティから受け取る構成にします。
また、必須コンポーネント(Camera2D・TiledMap)が足りない場合は、自動で取得を試みつつ、ログで明示的に警告するように実装します。


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

実現したい機能の整理

  • ターゲットとなる タイルマップ(TiledMap) のサイズ(タイル数とタイルサイズ)から、ワールド座標系でのマップの幅・高さを算出する。
  • そのマップサイズに合わせて、Camera2D コンポーネントの移動制限(limit)を自動設定する。
  • カメラの表示サイズ(画面の大きさ+ズーム)を考慮し、カメラが絶対にマップ外を映さないようにする。
  • 処理は 初期化時(start)に一度だけ行うオプションと、画面サイズやズーム変更に応じて毎フレーム再計算するオプションを切り替え可能にする。
  • 外部スクリプトには依存せず、インスペクタのプロパティ設定だけで完結する。

前提と座標系の考え方

  • タイルマップは cc.TiledMap コンポーネントを持つノードとしてシーン上に存在する。
  • カメラは cc.Cameracc.Camera2D を持つノード(通常は 2D 用カメラ)であり、このノードに LimitSetter をアタッチする想定。
  • タイルマップノードの 左下を原点(0,0)とし、そこから右方向に幅、上方向に高さが伸びるとみなす(Cocos の TiledMap の標準的な配置)。
  • Camera2D の camera プロパティから orthoHeight を取得し、画面の半分の高さ(ワールド単位)として利用する。
    • 画面の半分の幅 = orthoHeight * aspectRatio で計算。
    • aspectRatio = screenWidth / screenHeightview.getVisibleSize() から取得。

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

LimitSetter コンポーネントに用意する @property 一覧と役割は以下の通りです。

  • targetMap: Node
    • タイプ: Node
    • 説明: 対象となるタイルマップノード。TiledMap コンポーネントを持つノードをドラッグ&ドロップで指定します。
    • 役割: マップのタイルサイズ・マップサイズを取得するために使用。
  • applyOnStart: boolean
    • タイプ: boolean
    • 初期値: true
    • 説明: true の場合、start() のタイミングで一度だけリミット計算&設定を行う。
  • updateEveryFrame: boolean
    • タイプ: boolean
    • 初期値: false
    • 説明: true にすると、update() 内で毎フレームリミットを再計算して反映する。
      • 画面リサイズや、カメラズームを動的に変更するゲームで便利。
      • 通常は false のまま(パフォーマンス節約)。
  • marginLeft / marginRight / marginTop / marginBottom: number
    • タイプ: number
    • 初期値: すべて 0
    • 説明: カメラが移動できる範囲を、マップ端からどれだけ内側/外側にずらすかの余白(ワールド単位)。
    • 例:
      • 左側に 1 タイル分だけ余白を取りたい場合、タイルサイズが 32 なら marginLeft = 32
      • マップ右端の少し外まで見せたい場合は marginRight をマイナス値にする、など。
  • debugLog: boolean
    • タイプ: boolean
    • 初期値: false
    • 説明: 有効にすると、計算されたマップサイズやリミット値を console.log で出力して確認できる。

Camera2D コンポーネント自体は、LimitSetter 側で this.getComponent(Camera2D) により取得を試みます。
見つからない場合は エラーログを出して処理を中断し、エディタで Camera2D を追加するよう促します。


TypeScriptコードの実装

以下が完成した LimitSetter コンポーネントの TypeScript コードです。


import { _decorator, Component, Node, Camera2D, Camera, TiledMap, Vec2, Vec3, Rect, view, log, warn, error } from 'cc';
const { ccclass, property } = _decorator;

/**
 * LimitSetter
 * タイルマップのサイズから Camera2D の移動制限(limit)を自動計算して設定するコンポーネント。
 * 
 * このスクリプトは Camera ノードにアタッチして使用します。
 * - targetMap に TiledMap を持つノードを指定してください。
 * - Camera2D は同じノードにアタッチされている必要があります。
 */
@ccclass('LimitSetter')
export class LimitSetter extends Component {

    @property({
        type: Node,
        tooltip: '移動制限の基準となるタイルマップノード(TiledMap コンポーネントを持つ必要があります)。'
    })
    public targetMap: Node | null = null;

    @property({
        tooltip: 'true の場合、start() 時に一度だけリミットを計算して適用します。'
    })
    public applyOnStart: boolean = true;

    @property({
        tooltip: 'true の場合、毎フレーム update() でリミットを再計算して適用します(画面リサイズ・ズーム変更対応)。'
    })
    public updateEveryFrame: boolean = false;

    @property({
        tooltip: 'マップ左端からどれだけ内側(+)または外側(-)にカメラの移動制限をずらすか(ワールド単位)。'
    })
    public marginLeft: number = 0;

    @property({
        tooltip: 'マップ右端からどれだけ内側(+)または外側(-)にカメラの移動制限をずらすか(ワールド単位)。'
    })
    public marginRight: number = 0;

    @property({
        tooltip: 'マップ上端からどれだけ内側(+)または外側(-)にカメラの移動制限をずらすか(ワールド単位)。'
    })
    public marginTop: number = 0;

    @property({
        tooltip: 'マップ下端からどれだけ内側(+)または外側(-)にカメラの移動制限をずらすか(ワールド単位)。'
    })
    public marginBottom: number = 0;

    @property({
        tooltip: 'true にすると、計算されたマップサイズやリミット値をログ出力します。'
    })
    public debugLog: boolean = false;

    private _camera2D: Camera2D | null = null;
    private _camera: Camera | null = null;
    private _tiledMap: TiledMap | null = null;

    onLoad() {
        // Camera2D の取得
        this._camera2D = this.getComponent(Camera2D);
        if (!this._camera2D) {
            error('[LimitSetter] Camera2D コンポーネントが見つかりません。このスクリプトは Camera2D を持つノードにアタッチしてください。');
        }

        // Camera の取得(Camera2D から参照される)
        this._camera = this.getComponent(Camera);
        if (!this._camera) {
            warn('[LimitSetter] Camera コンポーネントが見つかりません。2D カメラとして使用する場合は Camera を追加してください。');
        }

        // TiledMap の取得(targetMap から)
        if (this.targetMap) {
            this._tiledMap = this.targetMap.getComponent(TiledMap);
            if (!this._tiledMap) {
                error('[LimitSetter] targetMap に TiledMap コンポーネントが見つかりません。TiledMap を持つノードを指定してください。');
            }
        } else {
            warn('[LimitSetter] targetMap が設定されていません。インスペクタで TiledMap ノードを指定してください。');
        }
    }

    start() {
        if (this.applyOnStart) {
            this.applyLimit();
        }
    }

    update(deltaTime: number) {
        if (this.updateEveryFrame) {
            this.applyLimit();
        }
    }

    /**
     * タイルマップサイズとカメラの表示範囲から Camera2D.limit を計算して適用します。
     */
    private applyLimit() {
        if (!this._camera2D) {
            // onLoad でエラー済み
            return;
        }
        if (!this._camera) {
            // Camera がないと正確な表示範囲が取れないので警告のみ出して中断
            warn('[LimitSetter] Camera コンポーネントが無いため、表示範囲の計算ができません。Camera を追加してください。');
            return;
        }
        if (!this._tiledMap || !this.targetMap) {
            warn('[LimitSetter] TiledMap または targetMap が設定されていません。インスペクタを確認してください。');
            return;
        }

        // --- 1. タイルマップからマップのワールドサイズを計算 ---
        const mapSize = this._tiledMap.getMapSize();     // タイル数 (width, height)
        const tileSize = this._tiledMap.getTileSize();   // 1タイルのサイズ (width, height)

        const mapWidth = mapSize.width * tileSize.width;
        const mapHeight = mapSize.height * tileSize.height;

        // targetMap のワールド座標(左下基準と仮定)
        const mapWorldPos = this.targetMap.worldPosition;

        // マップのワールド矩形(左下 x,y と 幅・高さ)
        const mapRect = new Rect(
            mapWorldPos.x,
            mapWorldPos.y,
            mapWidth,
            mapHeight
        );

        // --- 2. カメラの表示範囲(半分の幅・高さ)を計算 ---
        const visibleSize = view.getVisibleSize();
        const aspectRatio = visibleSize.width / visibleSize.height;

        // orthoHeight はカメラの「半分の高さ」(ワールド単位)
        const orthoHeight = this._camera.orthoHeight;
        const halfHeight = orthoHeight;
        const halfWidth = orthoHeight * aspectRatio;

        // --- 3. マップ端からカメラ中心が移動できる最小・最大座標を計算 ---
        // カメラ中心がこれ以上左に行くと画面左端がマップの外になる → mapRect.xMin + halfWidth
        let minX = mapRect.xMin + halfWidth + this.marginLeft;
        let maxX = mapRect.xMax - halfWidth - this.marginRight;
        let minY = mapRect.yMin + halfHeight + this.marginBottom;
        let maxY = mapRect.yMax - halfHeight - this.marginTop;

        // マップがカメラより小さい場合、min > max になりうるので、その場合は中心を固定する
        if (minX > maxX) {
            const centerX = (mapRect.xMin + mapRect.xMax) * 0.5;
            minX = maxX = centerX;
        }
        if (minY > maxY) {
            const centerY = (mapRect.yMin + mapRect.yMax) * 0.5;
            minY = maxY = centerY;
        }

        // --- 4. Camera2D.limit に設定 ---
        const limit = this._camera2D.limit;
        // limit は Rect ではなく Vec4/Vec2 の配列ではないため、
        // Cocos Creator 3.8 の Camera2D.limit は Vec4(xMin, yMin, xMax, yMax)として扱われます。
        // ここでは Vec4 の代わりに、プロパティに直接代入する形を取ります。
        // ※バージョンによっては limit が readonly の場合があるため、その場合は clone して代入してください。

        // ここでは簡易的に new Rect を使って値をまとめ、xMin, yMin, xMax, yMax を設定する例を示します。
        // 実際の型定義に合わせて適宜調整してください。
        // @ts-ignore - limit が読み取り専用でない前提の実装
        this._camera2D.limit = new Rect(minX, minY, maxX - minX, maxY - minY);

        if (this.debugLog) {
            log('[LimitSetter] ----------------------------');
            log('[LimitSetter] Map size (tiles):', mapSize.width, 'x', mapSize.height);
            log('[LimitSetter] Tile size:', tileSize.width, 'x', tileSize.height);
            log('[LimitSetter] Map world rect:',
                'xMin=', mapRect.xMin.toFixed(2),
                'yMin=', mapRect.yMin.toFixed(2),
                'xMax=', mapRect.xMax.toFixed(2),
                'yMax=', mapRect.yMax.toFixed(2));
            log('[LimitSetter] Camera half size:',
                'halfWidth=', halfWidth.toFixed(2),
                'halfHeight=', halfHeight.toFixed(2));
            log('[LimitSetter] Margins:',
                'L=', this.marginLeft,
                'R=', this.marginRight,
                'T=', this.marginTop,
                'B=', this.marginBottom);
            log('[LimitSetter] Calculated limit:',
                'minX=', minX.toFixed(2),
                'minY=', minY.toFixed(2),
                'maxX=', maxX.toFixed(2),
                'maxY=', maxY.toFixed(2));
        }
    }
}

コードのポイント解説

  • onLoad()
    • 同一ノードから Camera2DCamera を取得します。
    • targetMap に設定されたノードから TiledMap を取得します。
    • 見つからない場合は error または warn でログを出し、後続処理で安全に中断できるようにします。
  • start()
    • applyOnStarttrue のときだけ applyLimit() を呼び出し、初期化時に一度だけリミットを設定します。
  • update()
    • updateEveryFrametrue の場合にのみ applyLimit() を呼び出し、画面サイズやズームの変化に追従します。
  • applyLimit()
    • タイルマップから マップのワールド幅・高さを取得。
    • Camera の orthoHeight と画面のアスペクト比から、カメラの半分の幅・高さを算出。
    • マップ端とカメラ半径から、カメラ中心が動ける最小・最大座標を計算。
    • マップが小さすぎて min > max になる場合は、マップ中央に固定するように補正。
    • 計算した値を Camera2D.limit に設定。
    • debugLogtrue の場合は、詳細な計算結果をログ出力。

使用手順と動作確認

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

  1. Editor の Assets パネルで、任意のフォルダ(例: assets/scripts)を右クリックします。
  2. Create → TypeScript を選択し、ファイル名を LimitSetter.ts とします。
  3. 自動生成されたファイルをダブルクリックして開き、内容をすべて削除して、前述の TypeScript コード全文 を貼り付けて保存します。

2. タイルマップとカメラの準備

  1. タイルマップの用意
    1. Hierarchy で右クリック → Create → 2D Object → TiledMap を選択し、タイルマップノードを作成します。
    2. Inspector で TiledMap コンポーネントにタイルセットやマップデータ(.tmx など)を設定します。
    3. このノード名をわかりやすく Map などにしておくと後で指定しやすくなります。
  2. 2D カメラの用意
    1. Hierarchy で右クリック → Create → 2D Object → Camera を選択し、2D 用カメラノードを作成します。
    2. 作成されたカメラノードには通常 CameraCamera2D が自動で追加されます。
      • もし Camera2D が無い場合は、Inspector の Add ComponentRendering → Camera2D から追加してください。
    3. このカメラノードに対して、次のステップで LimitSetter をアタッチします。

3. LimitSetter コンポーネントのアタッチと設定

  1. Hierarchy で先ほどの カメラノード を選択します。
  2. Inspector で Add Component → Custom → LimitSetter を選択し、コンポーネントを追加します。
  3. Inspector 上に表示された LimitSetter のプロパティを設定します。
    • Target Map: 先ほど作成したタイルマップノード(例: Map)をドラッグ&ドロップします。
    • Apply On Start: 通常は true のままで OK です。
    • Update Every Frame:
      • カメラズームや画面サイズが固定であれば false(デフォルト)のままで OK。
      • ゲーム中にズームイン/アウトする場合は true にしておくと、常に正しいリミットが維持されます。
    • Margin Left / Right / Top / Bottom:
      • マップぴったりにカメラを止めたいなら、すべて 0 のままで OK。
      • 例えば「左右 1 タイル分だけ内側に制限をかけたい」場合、タイルサイズが 32 なら
        • Margin Left = 32
        • Margin Right = 32
    • Debug Log: 計算結果を確認したい場合は true にします(慣れてきたら false 推奨)。

4. 動作確認

  1. Editor 上部の Play ボタン(▶)を押してゲームを実行します。
  2. ゲームビューで、キーボード操作やプレイヤーの移動などによりカメラが動くようになっている場合、
    • カメラがマップ外(真っ黒な領域や背景色のみの領域)を映さず、マップの端でピタッと止まることを確認します。
  3. Debug Log を有効にしている場合は、Console パネルに以下のようなログが出力されているはずです。
    [LimitSetter] Map size (tiles): 100 x 50
    [LimitSetter] Tile size: 32 x 32
    [LimitSetter] Map world rect: xMin=0.00 yMin=0.00 xMax=3200.00 yMax=1600.00
    [LimitSetter] Camera half size: halfWidth=640.00 halfHeight=360.00
    [LimitSetter] Margins: L=0 R=0 T=0 B=0
    [LimitSetter] Calculated limit: minX=640.00 minY=360.00 maxX=2560.00 maxY=1240.00
    
  4. もしカメラが動かない/制限が効いていない場合は、以下をチェックしてください。
    • カメラノードに Camera2D がアタッチされているか。
    • LimitSetter の Target Map に、TiledMap コンポーネントを持つノードが正しく指定されているか。
    • Console にエラーログ([LimitSetter] で始まる)が出ていないか。

まとめ

LimitSetter コンポーネントを導入することで、タイルマップのサイズ変更や差し替えに対して、カメラの移動制限を自動で追従させることができます。
マップを作り直したり、別ステージ用のマップを読み込んでも、カメラ側のスクリプトを一切触らずに済むため、ステージ数が増えるほど効果を発揮します。

また、本コンポーネントは

  • インスペクタからターゲットマップを差し替えるだけで再利用可能
  • マージン値の調整で「少しだけマップ外を見せる」「HUD 分だけ上に余裕を持たせる」などの演出も簡単
  • updateEveryFrame を活用すれば、動的なズーム・画面リサイズにも対応可能

といった特徴を持ち、このスクリプト単体で完結した汎用コンポーネントとして、さまざまな 2D プロジェクトでそのまま活用できます。
カメラ周りのコードを各シーンごとに書き直す手間を省きたい場合は、ぜひ LimitSetter をベースに自分のプロジェクトに合わせた拡張も試してみてください。

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

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

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

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

Unity 6 C#スクリプト 100本ノック

新品価格
¥1,230から
(2025/12/29 10:28時点)

Cocos Creator100本ノックTypeScriptで書く!

新品価格
¥1,250から
(2025/12/29 10:32時点)

URLをコピーしました!