【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 を表示)
- MinimapCameraNode に
Cameraコンポーネントを追加し、RenderTextureに描画します。 - MinimapUINode に
UITransformとSpriteを追加し、ミニマップを 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. スクリプトの作成
- Assets パネルで右クリック → Create → TypeScript を選択します。
- ファイル名を
Minimap.tsとして作成します。 - 作成された
Minimap.tsをダブルクリックして開き、中身をすべて削除してから、上記の TypeScript コードを丸ごと貼り付けます。 - 保存して、Cocos Creator に戻ります。
2. テスト用シーンの準備
ミニマップで確認できるよう、簡単なオブジェクトを配置しておきます。
- Hierarchy パネルで右クリック → Create → 3D Object → Box などを選び、テスト用のオブジェクトをシーンに配置します。
- 必要に応じて複数の Box を並べておくと、ミニマップでの見え方が分かりやすくなります。
- プレイヤー的なノードを用意したい場合は、Create → 3D Object → Capsule などで別ノードを作成し、分かりやすい位置に置いておきます。
3. Minimap コンポーネントをアタッチ
- Hierarchy パネルで右クリック → Create → Empty Node を選択し、名前を
MinimapRootなどに変更します。 MinimapRootノードを選択した状態で、Inspector パネルの Add Component ボタンをクリックします。- Custom → Minimap を選択して、コンポーネントを追加します。
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. 再生してミニマップを確認
- エディタ右上の Play ボタンを押してゲームを実行します。
- ゲーム画面の 右上 に、設定したサイズのミニマップが表示されていることを確認します。
- シーンビューでプレイヤー(autoFollowNode に指定したノード)を移動させるか、ゲーム内ロジックで動かすと、ミニマップカメラがその位置に追従していることが分かります。
- ミニマップに映る範囲を変えたい場合は、
cameraOrthographicSizeを変更して再生し直してみてください。 - 俯瞰角度を変えたい場合は、
cameraRotationXを 60〜80 などに変え、斜め上からのミニマップにすることもできます。
6. Canvas が無い場合の挙動について
- このコンポーネントは、Screen Space UI を自分で構築します。
- シーンに
Canvasが存在しない場合、自動的に Canvas ノードを作成し、そこにミニマップ UI を配置します。 - すでに自分で Canvas を作成している場合は、その Canvas の子にミニマップが追加されます。
7. ワールド空間にミニマップを貼り付けたい場合
UI ではなく、例えばゲーム内の巨大な看板やスクリーン上にミニマップを貼り付けたい場合は、以下のように設定します。
MinimapRootを、ミニマップを表示したい位置に移動(例: 看板ノードの子にする)。- Inspector で
useScreenSpaceOverlayのチェックを外し(false にする)、再生します。 - この場合、
MinimapUIノードはMinimapRootの子としてローカル座標 (0,0,0) に配置されます。
まとめ
この Minimap コンポーネントは、
- SubViewport(RenderTexture)+専用カメラ+UI表示 をすべて自動で構築し、
- インスペクタから サイズ・位置・カメラ高さ・俯瞰角度・追従対象 を調整でき、
- 他のカスタムスクリプトに一切依存せず、この1ファイルだけで完結します。
応用例としては、
- プレイヤー追従型のミニマップ(autoFollowNode にプレイヤーを指定)。
- 拠点や街全体を常に表示する固定ミニマップ(autoFollowNode 未設定でルートノードを中心に)。
- 巨大なスクリーンや看板に貼り付ける「監視カメラ風ミニマップ」(useScreenSpaceOverlay を false に)。
といった使い方が可能です。
このように、アタッチするだけでよくある UI 機能を丸ごと提供する汎用コンポーネントを作っておくと、プロジェクトごとに再利用でき、ゲーム開発の初期セットアップやプロトタイピングが大きく効率化されます。
必要に応じて、本コンポーネントをベースに「ミニマップ用アイコン表示」「プレイヤーの向き矢印」「ズームイン/アウト機能」などを追加していくことで、よりリッチなミニマップシステムへ拡張していくことも容易です。




