【Cocos Creator】アタッチするだけ!Minimap (ミニマップ)の実装方法【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】Minimapコンポーネントの実装:アタッチするだけで上空からのミニマップ映像をUI右上に表示する汎用スクリプト

このガイドでは、SubViewport(サブビューポート)を使った上空カメラ映像を、UIの右上にミニマップとして表示する汎用コンポーネント Minimap を実装します。

シーン内の任意のノードにこのコンポーネントをアタッチし、インスペクタで数項目を設定するだけで、上から見下ろす専用カメラ+RenderTexture+UI表示 を自動的に構築します。外部の GameManager などには一切依存せず、このコンポーネント単体で完結する設計です。


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

1. 機能要件の整理

  • SubViewport(RenderTexture)を使い、上空からのカメラ映像を取得する。
  • 取得した映像を、UIの右上に表示されるミニマップとして描画する。
  • コンポーネントをアタッチしただけで、必要なノード・コンポーネント(Camera・Sprite・UITransform など)を自動生成する。
  • ミニマップのサイズ・表示位置・カメラの高さ・向きなどを、すべてインスペクタから調整可能にする。
  • 他のカスタムスクリプトに依存せず、このスクリプト単体で完結させる。
  • 必要な標準コンポーネントが見つからない場合は、防御的にエラーログを出すか、自動追加する。

2. ノード構成のイメージ

このコンポーネントは、アタッチされたノードの子として、以下を自動的に生成します。

MinimapRoot(このスクリプトをアタッチしたノード)
├─ MinimapCameraNode(上空カメラを持つノード)
└─ MinimapUINode(ミニマップ画像を表示するUIノード)
    └─ Sprite (RenderTexture を表示)
  • MinimapCameraNodeCamera コンポーネントを追加し、RenderTexture に描画します。
  • MinimapUINodeUITransformSprite を追加し、ミニマップを UI として表示します。

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

すべての調整はインスペクタから行えるようにします。

  • minimapWidth: number
    ミニマップUIの横幅(ピクセル)。
    例: 200 にすると 200px 幅のミニマップになります。
  • minimapHeight: number
    ミニマップUIの縦幅(ピクセル)。
    例: 200 にすると 200px 高さのミニマップになります。
  • marginRight: number
    画面右端からミニマップまでの余白(ピクセル)。
    例: 20 にすると右端から 20px 内側に表示されます。
  • marginTop: number
    画面上端からミニマップまでの余白(ピクセル)。
    例: 20 にすると上端から 20px 下に表示されます。
  • cameraHeight: number
    ミニマップ用カメラをシーンの上空にどれだけ離すか(Y座標)。
    例: 50 にすると Y=50 の高さから下向きに撮影します。
  • cameraOrthographicSize: number
    カメラの直交投影サイズ。値が大きいほど、より広い範囲がミニマップに映ります。
    例: 20〜50 程度から調整。
  • cameraRotationX: number
    カメラの X 軸回転角度(度)。
    90 にすると真下を向き、60 にするとやや斜め上からの俯瞰になります。
  • useScreenSpaceOverlay: boolean
    Canvas を使わずに、ワールド空間に張り付けるミニマップにするかどうか。
    • true: 2D UI(Screen 空間)として表示(通常はこちらを推奨)。
    • false: ノードのローカル空間にそのまま Sprite を配置。
  • backgroundColor: Color
    ミニマップカメラのクリアカラー。背景色として表示されます。
  • renderTextureWidth: number
    RenderTexture の横解像度。ミニマップ表示の解像度に影響。
    通常は minimapWidth と同じか、少し大きめにすると綺麗に見えます。
  • renderTextureHeight: number
    RenderTexture の縦解像度。
    通常は minimapHeight と同じか、少し大きめに設定します。
  • autoFollowNode: Node | null
    任意のノードを指定すると、そのノードを中心にミニマップカメラが追従します。
    例: プレイヤーキャラクターのノードをドラッグ&ドロップ。

これらのプロパティを通じて、ミニマップの見た目・サイズ・追従対象を柔軟に調整できるようにします。


TypeScriptコードの実装

以下が、完成した Minimap.ts コンポーネントの全コードです。


import {
    _decorator,
    Component,
    Node,
    Camera,
    RenderTexture,
    Sprite,
    SpriteFrame,
    UITransform,
    Vec3,
    Color,
    view,
    Canvas,
    Layers,
    Vec2,
    math,
} from 'cc';
const { ccclass, property, type } = _decorator;

@ccclass('Minimap')
export class Minimap extends Component {

    @property({
        tooltip: 'ミニマップUIの横幅(ピクセル)。'
    })
    public minimapWidth: number = 200;

    @property({
        tooltip: 'ミニマップUIの縦幅(ピクセル)。'
    })
    public minimapHeight: number = 200;

    @property({
        tooltip: '画面右端からの余白(ピクセル)。Screen Space UI のみ有効。'
    })
    public marginRight: number = 20;

    @property({
        tooltip: '画面上端からの余白(ピクセル)。Screen Space UI のみ有効。'
    })
    public marginTop: number = 20;

    @property({
        tooltip: 'ミニマップカメラの高さ(Y座標)。'
    })
    public cameraHeight: number = 50;

    @property({
        tooltip: 'ミニマップカメラの直交投影サイズ(Orthographic Size)。値が大きいほど広範囲を表示。'
    })
    public cameraOrthographicSize: number = 30;

    @property({
        tooltip: 'カメラのX軸回転角度(度)。90で真下、60でやや斜め俯瞰。'
    })
    public cameraRotationX: number = 90;

    @property({
        tooltip: 'true: 2D UI(SCREEN_SPACE)として右上に表示 / false: このノードのローカル空間に直接配置'
    })
    public useScreenSpaceOverlay: boolean = true;

    @property({
        tooltip: 'ミニマップカメラの背景色(クリアカラー)。'
    })
    public backgroundColor: Color = new Color(30, 30, 30, 255);

    @property({
        tooltip: 'RenderTextureの横解像度。通常はミニマップの幅と同程度か少し大きめに。'
    })
    public renderTextureWidth: number = 256;

    @property({
        tooltip: 'RenderTextureの縦解像度。通常はミニマップの高さと同程度か少し大きめに。'
    })
    public renderTextureHeight: number = 256;

    @property({
        type: Node,
        tooltip: 'ミニマップの中心として追従させるノード(例: プレイヤー)。未指定ならこのノードの位置を中心にします。'
    })
    public autoFollowNode: Node | null = null;

    // 内部用
    private _cameraNode: Node | null = null;
    private _camera: Camera | null = null;
    private _renderTexture: RenderTexture | null = null;
    private _minimapUINode: Node | null = null;
    private _sprite: Sprite | null = null;

    onLoad() {
        // 防御的: 必要な構成をすべてここで組み立てる
        this._createMinimapCamera();
        this._createMinimapUI();
    }

    start() {
        // 初期位置の更新(autoFollowNode があればそちらに合わせる)
        this._updateCameraPosition();
    }

    update(deltaTime: number) {
        // 毎フレーム、追従対象があればカメラ位置を更新
        this._updateCameraPosition();
    }

    /**
     * ミニマップ用カメラノードと Camera コンポーネントを作成・設定
     */
    private _createMinimapCamera() {
        // すでに作成されている場合は何もしない
        if (this._cameraNode && this._camera) {
            return;
        }

        // カメラノードを作成して、MinimapRoot(このノード)の子にする
        this._cameraNode = new Node('MinimapCameraNode');
        this.node.addChild(this._cameraNode);

        // カメラコンポーネントを追加
        this._camera = this._cameraNode.addComponent(Camera);
        if (!this._camera) {
            console.error('[Minimap] Camera コンポーネントの追加に失敗しました。');
            return;
        }

        // カメラ設定: 直交投影で上から見下ろす
        this._camera.projection = Camera.ProjectionType.ORTHO;
        this._camera.orthoHeight = this.cameraOrthographicSize;
        this._camera.clearColor = this.backgroundColor;
        this._camera.clearFlag = Camera.ClearFlag.COLOR_DEPTH_STENCIL;

        // カメラの向きと高さを設定
        const euler = new Vec3(this.cameraRotationX, 0, 0);
        this._cameraNode.setRotationFromEuler(euler.x, euler.y, euler.z);

        const pos = this.node.worldPosition.clone();
        pos.y += this.cameraHeight;
        this._cameraNode.setWorldPosition(pos);

        // RenderTexture を作成してターゲットに設定
        this._renderTexture = new RenderTexture();
        this._renderTexture.reset({
            width: this.renderTextureWidth,
            height: this.renderTextureHeight,
        });

        this._camera.targetTexture = this._renderTexture;
    }

    /**
     * ミニマップ表示用の UI ノードと Sprite を作成・設定
     */
    private _createMinimapUI() {
        // すでに作成されている場合は何もしない
        if (this._minimapUINode && this._sprite) {
            return;
        }

        // UIノードを作成
        this._minimapUINode = new Node('MinimapUI');
        this._minimapUINode.layer = Layers.Enum.UI_2D; // UIレイヤーに設定

        // UITransform を追加
        const uiTransform = this._minimapUINode.addComponent(UITransform);
        uiTransform.setContentSize(this.minimapWidth, this.minimapHeight);

        // Sprite を追加
        this._sprite = this._minimapUINode.addComponent(Sprite);
        if (!this._sprite) {
            console.error('[Minimap] Sprite コンポーネントの追加に失敗しました。');
            return;
        }

        if (!this._renderTexture) {
            console.error('[Minimap] RenderTexture がまだ生成されていません。');
            return;
        }

        // RenderTexture を SpriteFrame に設定
        const spriteFrame = new SpriteFrame();
        spriteFrame.texture = this._renderTexture;
        this._sprite.spriteFrame = spriteFrame;

        if (this.useScreenSpaceOverlay) {
            // Screen Space Canvas の子として配置(UIの右上に表示)
            const canvas = this._findOrCreateCanvas();
            if (!canvas) {
                console.error('[Minimap] Canvas が見つからず、作成にも失敗しました。ミニマップUIを配置できません。');
                return;
            }
            canvas.node.addChild(this._minimapUINode);

            // 右上に配置
            this._placeUINodeTopRight(this._minimapUINode, uiTransform);
        } else {
            // このノード(MinimapRoot)の子としてローカルに配置
            this.node.addChild(this._minimapUINode);
            this._minimapUINode.setPosition(0, 0, 0);
        }
    }

    /**
     * 追従対象に合わせてカメラ位置を更新
     */
    private _updateCameraPosition() {
        if (!this._cameraNode) {
            return;
        }

        const target = this.autoFollowNode ? this.autoFollowNode : this.node;
        if (!target) {
            return;
        }

        const targetPos = target.worldPosition;
        const newPos = new Vec3(targetPos.x, targetPos.y + this.cameraHeight, targetPos.z);
        this._cameraNode.setWorldPosition(newPos);
    }

    /**
     * シーン内の Canvas を探し、なければ新規作成して返す
     */
    private _findOrCreateCanvas(): Canvas | null {
        // 既存の Canvas を探す
        const scene = this.node.scene;
        if (!scene) {
            console.error('[Minimap] シーンが取得できませんでした。');
            return null;
        }

        let canvasNode: Node | null = null;
        const children = scene.children;
        for (let i = 0; i < children.length; i++) {
            const c = children[i];
            const canvasComp = c.getComponent(Canvas);
            if (canvasComp) {
                return canvasComp;
            }
        }

        // 見つからなければ新規作成
        canvasNode = new Node('Canvas');
        scene.addChild(canvasNode);
        const canvas = canvasNode.addComponent(Canvas);
        if (!canvas) {
            console.error('[Minimap] Canvas コンポーネントの追加に失敗しました。');
            return null;
        }
        canvasNode.layer = Layers.Enum.UI_2D;

        return canvas;
    }

    /**
     * 与えられた UI ノードを、画面右上に配置する
     */
    private _placeUINodeTopRight(node: Node, uiTransform: UITransform) {
        const visibleSize = view.getVisibleSize();
        const width = visibleSize.width;
        const height = visibleSize.height;

        const halfW = uiTransform.width * 0.5;
        const halfH = uiTransform.height * 0.5;

        // 右上基準の座標(Canvas の原点が中央の場合を想定)
        const x = width * 0.5 - halfW - this.marginRight;
        const y = height * 0.5 - halfH - this.marginTop;

        node.setPosition(x, y, 0);
    }
}

コードの主要なポイント解説

  • onLoad()
    _createMinimapCamera()_createMinimapUI() を呼び出し、必要なノード構造をすべて自動生成します。
    – 他のスクリプトへの依存は一切ありません。
  • start()
    – 初期フレームで _updateCameraPosition() を呼び出し、追従対象(autoFollowNode または this.node)の位置を反映します。
  • update(deltaTime)
    – 毎フレーム _updateCameraPosition() を呼び出し、プレイヤーなどの追従対象に合わせてカメラを移動させます。
  • _createMinimapCamera()
    MinimapCameraNode を作成し、Camera コンポーネントを追加。
    – カメラを直交投影(ORTHO)に設定し、cameraOrthographicSize で表示範囲を制御。
    cameraRotationX で俯瞰角度、cameraHeight で高さを設定。
    RenderTexture を生成し、camera.targetTexture に割り当てます。
  • _createMinimapUI()
    MinimapUI ノードを作成し、UITransform でサイズを minimapWidth / minimapHeight に設定。
    Sprite を追加し、RenderTexture を張った SpriteFrame を設定。
    useScreenSpaceOverlay が true の場合は Canvas を探す(なければ自動生成)し、その子として追加。
    _placeUINodeTopRight() で画面右上に配置します。
  • _findOrCreateCanvas()
    – シーン直下のノードから Canvas コンポーネントを検索。
    – 見つからなければ新たに Canvas ノードを作成し、UIレイヤーに設定。
    – これにより、ユーザーが事前に Canvas を用意していなくてもミニマップが機能します。
  • _placeUINodeTopRight()
    view.getVisibleSize() で画面サイズを取得。
    – Canvas の原点が中央にある前提で、右上から marginRight / marginTop だけ内側にミニマップを配置します。

使用手順と動作確認

ここからは、実際に Cocos Creator 3.8.7 エディタでこのコンポーネントを使う手順を説明します。

1. スクリプトの作成

  1. Assets パネルで右クリック → Create → TypeScript を選択します。
  2. ファイル名を Minimap.ts として作成します。
  3. 作成された Minimap.ts をダブルクリックして開き、中身をすべて削除してから、上記の TypeScript コードを丸ごと貼り付けます。
  4. 保存して、Cocos Creator に戻ります。

2. テスト用シーンの準備

ミニマップで確認できるよう、簡単なオブジェクトを配置しておきます。

  1. Hierarchy パネルで右クリック → Create → 3D Object → Box などを選び、テスト用のオブジェクトをシーンに配置します。
  2. 必要に応じて複数の Box を並べておくと、ミニマップでの見え方が分かりやすくなります。
  3. プレイヤー的なノードを用意したい場合は、Create → 3D Object → Capsule などで別ノードを作成し、分かりやすい位置に置いておきます。

3. Minimap コンポーネントをアタッチ

  1. Hierarchy パネルで右クリック → Create → Empty Node を選択し、名前を MinimapRoot などに変更します。
  2. MinimapRoot ノードを選択した状態で、Inspector パネルの Add Component ボタンをクリックします。
  3. CustomMinimap を選択して、コンポーネントを追加します。

4. インスペクタでプロパティを設定

MinimapRoot ノードを選択すると、Inspector に Minimap コンポーネントが表示されます。以下のように設定してみましょう。

  • minimapWidth: 200
  • minimapHeight: 200
  • marginRight: 20
  • marginTop: 20
  • cameraHeight: 50
  • cameraOrthographicSize: 30
  • cameraRotationX: 90(真上から見下ろす)
  • useScreenSpaceOverlay: チェックを入れる(true)
  • backgroundColor: デフォルトのまま(暗めのグレー)か、好みで変更
  • renderTextureWidth: 256
  • renderTextureHeight: 256
  • autoFollowNode:
    – プレイヤーとして使いたいノード(例: Capsule)を Hierarchy からドラッグ&ドロップ。
    – とりあえず未設定でも、MinimapRoot の位置を中心に表示されます。

5. 再生してミニマップを確認

  1. エディタ右上の Play ボタンを押してゲームを実行します。
  2. ゲーム画面の 右上 に、設定したサイズのミニマップが表示されていることを確認します。
  3. シーンビューでプレイヤー(autoFollowNode に指定したノード)を移動させるか、ゲーム内ロジックで動かすと、ミニマップカメラがその位置に追従していることが分かります。
  4. ミニマップに映る範囲を変えたい場合は、cameraOrthographicSize を変更して再生し直してみてください。
  5. 俯瞰角度を変えたい場合は、cameraRotationX を 60〜80 などに変え、斜め上からのミニマップにすることもできます。

6. Canvas が無い場合の挙動について

  • このコンポーネントは、Screen Space UI を自分で構築します。
  • シーンに Canvas が存在しない場合、自動的に Canvas ノードを作成し、そこにミニマップ UI を配置します。
  • すでに自分で Canvas を作成している場合は、その Canvas の子にミニマップが追加されます。

7. ワールド空間にミニマップを貼り付けたい場合

UI ではなく、例えばゲーム内の巨大な看板やスクリーン上にミニマップを貼り付けたい場合は、以下のように設定します。

  1. MinimapRoot を、ミニマップを表示したい位置に移動(例: 看板ノードの子にする)。
  2. Inspector で useScreenSpaceOverlay のチェックを外し(false にする)、再生します。
  3. この場合、MinimapUI ノードは MinimapRoot の子としてローカル座標 (0,0,0) に配置されます。

まとめ

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

  • SubViewport(RenderTexture)+専用カメラ+UI表示 をすべて自動で構築し、
  • インスペクタから サイズ・位置・カメラ高さ・俯瞰角度・追従対象 を調整でき、
  • 他のカスタムスクリプトに一切依存せず、この1ファイルだけで完結します。

応用例としては、

  • プレイヤー追従型のミニマップ(autoFollowNode にプレイヤーを指定)。
  • 拠点や街全体を常に表示する固定ミニマップ(autoFollowNode 未設定でルートノードを中心に)。
  • 巨大なスクリーンや看板に貼り付ける「監視カメラ風ミニマップ」(useScreenSpaceOverlay を false に)。

といった使い方が可能です。

このように、アタッチするだけでよくある UI 機能を丸ごと提供する汎用コンポーネントを作っておくと、プロジェクトごとに再利用でき、ゲーム開発の初期セットアップやプロトタイピングが大きく効率化されます。

必要に応じて、本コンポーネントをベースに「ミニマップ用アイコン表示」「プレイヤーの向き矢印」「ズームイン/アウト機能」などを追加していくことで、よりリッチなミニマップシステムへ拡張していくことも容易です。

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をコピーしました!