【Cocos Creator 3.8】LimitSetter(移動制限)の実装:アタッチするだけでタイルマップに合わせた Camera2D の移動限界を自動設定する汎用スクリプト
本記事では、タイルマップのサイズに合わせて Camera2D の移動範囲(limit)を自動計算して設定するコンポーネント「LimitSetter」を実装します。
タイルマップベースの 2D ゲームで、カメラがマップ外の余白を映さないようにするための典型的な処理を、「カメラノードにアタッチするだけ」で完結させることが目的です。
外部の GameManager やシングルトンには一切依存せず、必要な情報はすべてインスペクタで設定可能なプロパティから受け取る構成にします。
また、必須コンポーネント(Camera2D・TiledMap)が足りない場合は、自動で取得を試みつつ、ログで明示的に警告するように実装します。
コンポーネントの設計方針
実現したい機能の整理
- ターゲットとなる タイルマップ(TiledMap) のサイズ(タイル数とタイルサイズ)から、ワールド座標系でのマップの幅・高さを算出する。
- そのマップサイズに合わせて、Camera2D コンポーネントの移動制限(limit)を自動設定する。
- カメラの表示サイズ(画面の大きさ+ズーム)を考慮し、カメラが絶対にマップ外を映さないようにする。
- 処理は 初期化時(start)に一度だけ行うオプションと、画面サイズやズーム変更に応じて毎フレーム再計算するオプションを切り替え可能にする。
- 外部スクリプトには依存せず、インスペクタのプロパティ設定だけで完結する。
前提と座標系の考え方
- タイルマップは
cc.TiledMapコンポーネントを持つノードとしてシーン上に存在する。 - カメラは
cc.Cameraとcc.Camera2Dを持つノード(通常は 2D 用カメラ)であり、このノードに LimitSetter をアタッチする想定。 - タイルマップノードの 左下を原点(0,0)とし、そこから右方向に幅、上方向に高さが伸びるとみなす(Cocos の TiledMap の標準的な配置)。
- Camera2D の
cameraプロパティからorthoHeightを取得し、画面の半分の高さ(ワールド単位)として利用する。- 画面の半分の幅 =
orthoHeight * aspectRatioで計算。 aspectRatio = screenWidth / screenHeightはview.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をマイナス値にする、など。
- 左側に 1 タイル分だけ余白を取りたい場合、タイルサイズが 32 なら
- タイプ:
- 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()
- 同一ノードから
Camera2DとCameraを取得します。 targetMapに設定されたノードからTiledMapを取得します。- 見つからない場合は
errorまたはwarnでログを出し、後続処理で安全に中断できるようにします。
- 同一ノードから
- start()
applyOnStartがtrueのときだけapplyLimit()を呼び出し、初期化時に一度だけリミットを設定します。
- update()
updateEveryFrameがtrueの場合にのみapplyLimit()を呼び出し、画面サイズやズームの変化に追従します。
- applyLimit()
- タイルマップから マップのワールド幅・高さを取得。
- Camera の
orthoHeightと画面のアスペクト比から、カメラの半分の幅・高さを算出。 - マップ端とカメラ半径から、カメラ中心が動ける最小・最大座標を計算。
- マップが小さすぎて
min > maxになる場合は、マップ中央に固定するように補正。 - 計算した値を
Camera2D.limitに設定。 debugLogがtrueの場合は、詳細な計算結果をログ出力。
使用手順と動作確認
1. スクリプトファイルの作成
- Editor の Assets パネルで、任意のフォルダ(例:
assets/scripts)を右クリックします。 - Create → TypeScript を選択し、ファイル名を
LimitSetter.tsとします。 - 自動生成されたファイルをダブルクリックして開き、内容をすべて削除して、前述の TypeScript コード全文 を貼り付けて保存します。
2. タイルマップとカメラの準備
- タイルマップの用意
- Hierarchy で右クリック → Create → 2D Object → TiledMap を選択し、タイルマップノードを作成します。
- Inspector で
TiledMapコンポーネントにタイルセットやマップデータ(.tmx など)を設定します。 - このノード名をわかりやすく
Mapなどにしておくと後で指定しやすくなります。
- 2D カメラの用意
- Hierarchy で右クリック → Create → 2D Object → Camera を選択し、2D 用カメラノードを作成します。
- 作成されたカメラノードには通常
CameraとCamera2Dが自動で追加されます。- もし
Camera2Dが無い場合は、Inspector の Add Component → Rendering → Camera2D から追加してください。
- もし
- このカメラノードに対して、次のステップで LimitSetter をアタッチします。
3. LimitSetter コンポーネントのアタッチと設定
- Hierarchy で先ほどの カメラノード を選択します。
- Inspector で Add Component → Custom → LimitSetter を選択し、コンポーネントを追加します。
- 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 = 32Margin Right = 32
- マップぴったりにカメラを止めたいなら、すべて
- Debug Log: 計算結果を確認したい場合は
trueにします(慣れてきたらfalse推奨)。
- Target Map: 先ほど作成したタイルマップノード(例:
4. 動作確認
- Editor 上部の Play ボタン(▶)を押してゲームを実行します。
- ゲームビューで、キーボード操作やプレイヤーの移動などによりカメラが動くようになっている場合、
- カメラがマップ外(真っ黒な領域や背景色のみの領域)を映さず、マップの端でピタッと止まることを確認します。
- 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 - もしカメラが動かない/制限が効いていない場合は、以下をチェックしてください。
- カメラノードに Camera2D がアタッチされているか。
- LimitSetter の Target Map に、TiledMap コンポーネントを持つノードが正しく指定されているか。
- Console にエラーログ(
[LimitSetter]で始まる)が出ていないか。
まとめ
LimitSetter コンポーネントを導入することで、タイルマップのサイズ変更や差し替えに対して、カメラの移動制限を自動で追従させることができます。
マップを作り直したり、別ステージ用のマップを読み込んでも、カメラ側のスクリプトを一切触らずに済むため、ステージ数が増えるほど効果を発揮します。
また、本コンポーネントは
- インスペクタからターゲットマップを差し替えるだけで再利用可能
- マージン値の調整で「少しだけマップ外を見せる」「HUD 分だけ上に余裕を持たせる」などの演出も簡単
updateEveryFrameを活用すれば、動的なズーム・画面リサイズにも対応可能
といった特徴を持ち、このスクリプト単体で完結した汎用コンポーネントとして、さまざまな 2D プロジェクトでそのまま活用できます。
カメラ周りのコードを各シーンごとに書き直す手間を省きたい場合は、ぜひ LimitSetter をベースに自分のプロジェクトに合わせた拡張も試してみてください。




