【Cocos Creator 3.8】LanternLight の実装:アタッチするだけで暗闇を照らし、燃料制限付きランタンを実現する汎用スクリプト
このガイドでは、2Dゲームの暗闇エリアで使える「LanternLight」コンポーネントを実装します。
PointLight2D を自動で制御し、
- ランタンの明るさ・半径・色を Inspector から調整
- オプションで「燃料制限」を付けて、時間経過で徐々に暗くなる
- 燃料切れ時のコールバック(イベントの代わりに、指定ノードの有効/無効切り替え)
などを、スクリプトをアタッチするだけで実現します。
外部の GameManager やシングルトンには一切依存せず、このコンポーネント単体で完結する設計です。
コンポーネントの設計方針
1. 実装する機能の要件整理
- このコンポーネントをアタッチしたノードに PointLight2D を持たせ、暗闇を照らす「ランタン」にする。
- Inspector から以下を調整できるようにする:
- 初期の明るさ(intensity)と半径(range)、色(color)
- ランタンの「燃料制限」を有効/無効にするフラグ
- 最大燃料量・現在燃料量(秒単位)
- 燃料消費速度(1秒あたりの消費量)
- 燃料が減るにつれてライトの明るさ・半径をどこまで落とすか(最小値)
- 燃料が切れたときに有効/無効を切り替えるターゲットノード(任意)
- 外部スクリプトへの依存は禁止:
- 燃料の開始・停止・リセットは public メソッド で提供し、他のスクリプトがあればそこから呼べるが、なくても自己完結して動く。
- UI 表示などはこのコンポーネントでは持たず、純粋に「ライトと燃料」を管理する。
- 防御的実装:
- PointLight2D がノードに存在しない場合は
getComponent(PointLight2D)で取得を試み、なければ警告ログを出してスクリプトを無効化。 - 燃料関連の値が不正(負数など)の場合は、起動時に補正する。
- PointLight2D がノードに存在しない場合は
2. Inspector で設定可能なプロパティ設計
LanternLight コンポーネントで Inspector に表示する主なプロパティと役割は次の通りです。
- ライト設定系
enabledOnStart: boolean
– 初期状態でライトを ON にするかどうか。
– false にすると、開始時は消灯状態からスタート。baseIntensity: number
– 燃料がフルのときの PointLight2D のintensity。
– 例: 1.0 ~ 2.0 程度。ゲームの明るさに応じて調整。minIntensity: number
– 燃料がゼロに近づいたときの最小のintensity。
– 0 にすると完全に暗くなる。0.1 などにすれば「かすかに残る」表現も可能。baseRange: number
– 燃料がフルのときの PointLight2D のrange(照らす半径)。minRange: number
– 燃料がゼロのときのrange。
– 0 にすると完全に届かなくなる。小さな値にすれば「足元だけうっすら見える」表現も可能。lightColor: Color
– ランタンの光の色。
– 暖色系(例: RGBA(255, 230, 180, 255))にするとランタンらしい雰囲気。
- 燃料システム系
useFuel: boolean
– 燃料制限を使うかどうか。
– false の場合、燃料は無限で、ライトの明るさは常にbaseIntensity / baseRangeのまま。maxFuelSeconds: number
– 最大燃料量(秒)。
– 例: 60 にすると、フルから 60 秒で燃料が尽きる設計。startFuelSeconds: number
– ゲーム開始時の燃料量(秒)。
– 0 ~ maxFuelSeconds の範囲で指定。超えていた場合は自動的にクランプ。consumePerSecond: number
– 1 秒あたりに消費する燃料量(秒)。
– 例: 1.0 なら現実時間 60 秒で 60 減少。2.0 なら 30 秒で 60 減少。autoStartConsume: boolean
– シーン開始と同時に自動で燃料消費を開始するかどうか。
– false の場合、startConsume()を呼ぶまで燃料は減らない。
- 燃料切れ時の挙動系
disableLightOnEmpty: boolean
– 燃料が完全に 0 になったときに、PointLight2D コンポーネント自体をenabled = falseにするかどうか。toggleNodeOnEmpty: Node | null
– 燃料切れ時に有効/無効を切り替えたいターゲットノード(任意)。
– 例: 「ゲームオーバーUI」「警告アイコン」などを指定しておく。toggleNodeActiveState: boolean
–toggleNodeOnEmptyに指定したノードのactiveを true/false どちらにするか。
– true: 燃料切れ時にnode.active = true(表示する)
– false: 燃料切れ時にnode.active = false(非表示にする)
これらのプロパティをすべて Inspector から設定できるようにし、他のスクリプトに一切依存せず、単体で動作するようにします。
TypeScriptコードの実装
以下が完成した LanternLight.ts の全コードです。
import { _decorator, Component, Node, Color, PointLight2D, clamp, math } from 'cc';
const { ccclass, property } = _decorator;
/**
* LanternLight
* - 2D ランタン用ライト制御コンポーネント
* - PointLight2D を使って周囲を照らし、オプションで燃料制限をかける
*/
@ccclass('LanternLight')
export class LanternLight extends Component {
// -----------------------------
// ライト基本設定
// -----------------------------
@property({
tooltip: 'シーン開始時にライトを有効にするかどうか。\nOFF にすると消灯状態からスタートします。'
})
public enabledOnStart: boolean = true;
@property({
tooltip: '燃料がフルのときのライトの明るさ (PointLight2D.intensity)。\n値を大きくするとより明るくなります。'
})
public baseIntensity: number = 1.5;
@property({
tooltip: '燃料がゼロのときの最小の明るさ。\n0 にすると完全に暗くなります。'
})
public minIntensity: number = 0.0;
@property({
tooltip: '燃料がフルのときのライトの照射範囲 (PointLight2D.range)。\n値を大きくするとより広い範囲を照らします。'
})
public baseRange: number = 800;
@property({
tooltip: '燃料がゼロのときの最小の照射範囲。\n0 にするとまったく届かなくなります。'
})
public minRange: number = 0;
@property({
tooltip: 'ランタンの光の色。\n暖色系 (例: 255, 230, 180) にするとランタンらしい雰囲気になります。'
})
public lightColor: Color = new Color(255, 230, 180, 255);
// -----------------------------
// 燃料システム設定
// -----------------------------
@property({
tooltip: '燃料システムを使用するかどうか。\nOFF にすると燃料は無限になり、明るさは常に一定になります。'
})
public useFuel: boolean = true;
@property({
tooltip: '最大燃料量(秒)。\n例: 60 にすると、フルから 60 秒分の燃焼時間を持ちます。'
})
public maxFuelSeconds: number = 60;
@property({
tooltip: '開始時の燃料量(秒)。\n0 ~ 最大燃料量の範囲に自動でクランプされます。'
})
public startFuelSeconds: number = 60;
@property({
tooltip: '1 秒あたりに消費する燃料量(秒)。\n例: 1.0 なら 60 秒で 60 消費、2.0 なら 30 秒で 60 消費します。'
})
public consumePerSecond: number = 1.0;
@property({
tooltip: 'シーン開始時に自動で燃料消費を開始するかどうか。\nOFF の場合、手動で startConsume() を呼ぶまで燃料は減りません。'
})
public autoStartConsume: boolean = true;
// -----------------------------
// 燃料切れ時の挙動
// -----------------------------
@property({
tooltip: '燃料が完全に 0 になったときにライトコンポーネント (PointLight2D) を無効化するかどうか。'
})
public disableLightOnEmpty: boolean = true;
@property({
tooltip: '燃料切れ時に有効/無効を切り替えたいノード(任意)。\n例: ゲームオーバーUI、警告アイコンなど。'
})
public toggleNodeOnEmpty: Node | null = null;
@property({
tooltip: 'toggleNodeOnEmpty に指定したノードの active を true/false どちらにするか。\ntrue: 燃料切れ時に表示 / false: 燃料切れ時に非表示。'
})
public toggleNodeActiveState: boolean = true;
// =============================
// 内部状態
// =============================
/** 実際の PointLight2D コンポーネント参照 */
private _light: PointLight2D | null = null;
/** 現在の燃料量(秒) */
private _currentFuel: number = 0;
/** 燃料消費中かどうか */
private _isConsuming: boolean = false;
/** 燃料がすでに 0 になって「燃料切れ処理」を実行済みかどうか */
private _isEmptyNotified: boolean = false;
// -----------------------------
// ライフサイクル
// -----------------------------
onLoad() {
// 必要なコンポーネント PointLight2D を取得
this._light = this.getComponent(PointLight2D);
if (!this._light) {
console.error('[LanternLight] PointLight2D コンポーネントが見つかりません。このノードに PointLight2D を追加してください。');
// ライトがないと意味がないので、このコンポーネントを無効化
this.enabled = false;
return;
}
// 各種パラメータを防御的に補正
if (this.maxFuelSeconds <= 0) {
console.warn('[LanternLight] maxFuelSeconds が 0 以下だったため、10 に補正しました。');
this.maxFuelSeconds = 10;
}
if (this.consumePerSecond < 0) {
console.warn('[LanternLight] consumePerSecond が負数だったため、0 に補正しました。');
this.consumePerSecond = 0;
}
if (this.baseIntensity < 0) {
console.warn('[LanternLight] baseIntensity が負数だったため、0 に補正しました。');
this.baseIntensity = 0;
}
if (this.minIntensity < 0) {
console.warn('[LanternLight] minIntensity が負数だったため, 0 に補正しました。');
this.minIntensity = 0;
}
if (this.baseRange < 0) {
console.warn('[LanternLight] baseRange が負数だったため, 0 に補正しました。');
this.baseRange = 0;
}
if (this.minRange < 0) {
console.warn('[LanternLight] minRange が負数だったため, 0 に補正しました。');
this.minRange = 0;
}
// 開始時の燃料量をクランプ
this._currentFuel = clamp(this.startFuelSeconds, 0, this.maxFuelSeconds);
// ライトの初期設定
this._applyLightEnabled(this.enabledOnStart);
this._applyLightColor();
this._updateLightByFuel(); // 初期燃料に応じて明るさを反映
}
start() {
// autoStartConsume が true かつ useFuel が true なら自動で燃料消費開始
if (this.useFuel && this.autoStartConsume) {
this.startConsume();
}
}
update(deltaTime: number) {
if (!this._light) {
return;
}
// 燃料システムを使用しない場合は何もしない(一定の明るさを維持)
if (!this.useFuel) {
return;
}
// 燃料を消費していない場合も何もしない
if (!this._isConsuming) {
return;
}
if (this._currentFuel > 0 && this.consumePerSecond > 0) {
// 時間経過に応じて燃料を減らす
this._currentFuel -= this.consumePerSecond * deltaTime;
if (this._currentFuel <= 0) {
this._currentFuel = 0;
this._onFuelEmpty();
}
}
// 燃料量に応じてライトの明るさ・範囲を更新
this._updateLightByFuel();
}
// -----------------------------
// 内部処理
// -----------------------------
/**
* ライトの有効/無効を反映
*/
private _applyLightEnabled(enabled: boolean) {
if (!this._light) return;
this._light.enabled = enabled;
}
/**
* ライトの色を反映
*/
private _applyLightColor() {
if (!this._light) return;
this._light.color = this.lightColor;
}
/**
* 現在の燃料量に応じてライトの intensity / range を更新する
*/
private _updateLightByFuel() {
if (!this._light) return;
if (!this.useFuel) {
// 燃料システムを使わない場合は常にベース値
this._light.intensity = this.baseIntensity;
this._light.range = this.baseRange;
return;
}
// 燃料割合 (0.0 ~ 1.0)
const ratio = this.maxFuelSeconds > 0 ? clamp(this._currentFuel / this.maxFuelSeconds, 0, 1) : 0;
// 線形補間で intensity, range を決める
const intensity = math.lerp(this.minIntensity, this.baseIntensity, ratio);
const range = math.lerp(this.minRange, this.baseRange, ratio);
this._light.intensity = intensity;
this._light.range = range;
}
/**
* 燃料が 0 になったときに 1 度だけ呼ばれる
*/
private _onFuelEmpty() {
if (this._isEmptyNotified) {
return;
}
this._isEmptyNotified = true;
this._isConsuming = false;
// ライトを無効化する設定なら無効化
if (this.disableLightOnEmpty) {
this._applyLightEnabled(false);
}
// 指定ノードの active を切り替え
if (this.toggleNodeOnEmpty) {
this.toggleNodeOnEmpty.active = this.toggleNodeActiveState;
}
}
// -----------------------------
// 公開 API(任意で他スクリプトから呼べる)
// -----------------------------
/**
* 現在の燃料量(秒)を取得
*/
public getCurrentFuel(): number {
return this._currentFuel;
}
/**
* 最大燃料量(秒)を取得
*/
public getMaxFuel(): number {
return this.maxFuelSeconds;
}
/**
* 燃料消費を開始する
*/
public startConsume() {
if (!this.useFuel) {
console.warn('[LanternLight] useFuel が false のため、startConsume() は効果がありません。');
return;
}
if (this._currentFuel <= 0) {
// すでに燃料ゼロなら何もしない
this._onFuelEmpty();
return;
}
this._isConsuming = true;
}
/**
* 燃料消費を一時停止する
*/
public stopConsume() {
this._isConsuming = false;
}
/**
* 燃料を指定量だけ追加する(秒単位)
* @param amount 追加する燃料量(秒)。負数は無視。
*/
public addFuel(amount: number) {
if (amount <= 0) return;
this._currentFuel = clamp(this._currentFuel + amount, 0, this.maxFuelSeconds);
this._isEmptyNotified = false; // 再び燃料が入ったので「燃料切れフラグ」を解除
this._updateLightByFuel();
// 燃料が回復したとき、ライトを再度有効にするかどうかは任意
// ここでは、enabledOnStart に従って再度有効化する挙動にしておく
if (this.enabledOnStart && this._light) {
this._light.enabled = true;
}
}
/**
* 燃料を最大値までリセットする
*/
public refillFuel() {
this._currentFuel = this.maxFuelSeconds;
this._isEmptyNotified = false;
this._updateLightByFuel();
if (this.enabledOnStart && this._light) {
this._light.enabled = true;
}
}
/**
* ライトの ON/OFF を手動で切り替える
* @param enabled 有効にするかどうか
*/
public setLightEnabled(enabled: boolean) {
this.enabledOnStart = enabled; // 次回リセット時の基準にもなる
this._applyLightEnabled(enabled);
}
/**
* 燃料の利用を一時的に ON/OFF する
* @param useFuel 燃料システムを使うかどうか
*/
public setUseFuel(useFuel: boolean) {
this.useFuel = useFuel;
if (!this.useFuel) {
// 燃料システムを無効化したら、一定の明るさに固定
this._isConsuming = false;
}
this._updateLightByFuel();
}
}
コードの主要なポイント解説
- onLoad()
- 同じノードにアタッチされている
PointLight2Dを取得。 - 見つからない場合は
console.errorを出し、このコンポーネント自体をenabled = falseにして防御的に停止。 - 燃料やライト設定の値を負数チェックなどで補正。
_currentFuelをstartFuelSecondsから初期化し、ライトの色・明るさ・範囲を初期反映。
- 同じノードにアタッチされている
- start()
useFuel && autoStartConsumeのときだけ、ゲーム開始と同時に燃料消費を開始。
- update(deltaTime)
- 燃料システムを使わない場合や、消費中でなければ何もしない。
- 消費中なら
consumePerSecond * deltaTimeだけ燃料を減らす。 - 燃料が 0 以下になったら
_onFuelEmpty()を 1 度だけ呼び出し、ライト無効化やノードの active 切り替えを行う。 - 毎フレーム
_updateLightByFuel()を呼び、燃料残量に応じて intensity / range を線形補間(lerp)で更新。
- _updateLightByFuel()
- 燃料割合
ratio = current / maxを 0.0 ~ 1.0 にクランプ。 math.lerp(minIntensity, baseIntensity, ratio)で明るさを補間。math.lerp(minRange, baseRange, ratio)で照射範囲を補間。useFuel = falseの場合は常に base 値で固定。
- 燃料割合
- 公開 API
startConsume(),stopConsume(),addFuel(),refillFuel(),setLightEnabled(),setUseFuel()などを用意し、必要なら他スクリプトから制御できるようにしています。- ただし、これらを呼ばなくても Inspector 設定だけで動作する ため、外部依存はありません。
使用手順と動作確認
1. スクリプトファイルの作成
- エディタ左下の Assets パネルで、スクリプトを置きたいフォルダ(例:
assets/scripts)を選択します。 - フォルダ上で右クリック → Create → TypeScript を選択します。
- 新しく作成されたスクリプトファイルの名前を
LanternLight.tsに変更します。 LanternLight.tsをダブルクリックしてエディタ(VSCode など)で開き、先ほどのコードを 全て貼り付けて保存 します。
2. ランタン用ノードと PointLight2D の準備
- Hierarchy パネルで、ランタンを配置したい場所(例: 2D カメラの子ノード)を選択します。
- 右クリック → Create → Empty Node を選択して、新しいノードを作成します。
- ノード名を分かりやすく
Lanternなどに変更します。 Lanternノードを選択した状態で、Inspector の Add Component ボタンをクリックします。- 検索欄に
PointLight2Dと入力し、PointLight2D を追加します。 - PointLight2D の
Typeが Point になっていることを確認してください(通常はデフォルトで Point です)。
※ PointLight2D を追加し忘れると、ゲーム開始時にコンソールにエラーが表示され、LanternLight コンポーネントは自動で無効化されます。
3. LanternLight コンポーネントのアタッチ
- 再び
Lanternノードを選択します。 - Inspector の Add Component ボタンをクリックします。
- Custom → LanternLight を選択してアタッチします。
4. Inspector でプロパティを設定
LanternLight コンポーネントを選択した状態で、Inspector に表示される各プロパティを設定します。まずは基本的な例を紹介します。
例1:60秒で完全に消えるランタン
- ライト設定
- Enabled On Start:
true - Base Intensity:
1.5 - Min Intensity:
0 - Base Range:
800 - Min Range:
0 - Light Color:
(R:255, G:230, B:180, A:255)
- Enabled On Start:
- 燃料システム
- Use Fuel:
true - Max Fuel Seconds:
60 - Start Fuel Seconds:
60 - Consume Per Second:
1 - Auto Start Consume:
true
- Use Fuel:
- 燃料切れ時
- Disable Light On Empty:
true - Toggle Node On Empty:
null(未設定でOK) - Toggle Node Active State:
true(未使用)
- Disable Light On Empty:
この設定では、ゲーム開始から 60 秒かけて徐々に暗くなり、60 秒経過すると完全に消灯します。
例2:燃料が切れても「足元だけうっすら見える」ランタン
- Base Intensity:
1.5 - Min Intensity:
0.2 - Base Range:
800 - Min Range:
150 - Disable Light On Empty:
false
この場合、燃料がゼロになってもライトは完全には消えず、わずかな光で足元だけ照らし続ける表現になります。
例3:燃料切れ時にゲームオーバーUIを表示
- Hierarchy でゲームオーバー用の UI ノード(例:
GameOverUI)を作成し、最初はactive = falseにしておきます。 Lanternノードを選択し、Inspector の LanternLight の- Toggle Node On Empty:
GameOverUIノードをドラッグ&ドロップで割り当て - Toggle Node Active State:
true
- Toggle Node On Empty:
これで燃料がゼロになった瞬間に GameOverUI.active = true となり、ゲームオーバー画面を表示できます。
外部の GameManager スクリプトを用意する必要はありません。
5. シーンでの動作確認
- メインメニューの上部にある Play ボタン(▶)をクリックしてゲームを再生します。
- Scene / Game ビューで、ランタンの周囲が PointLight2D によって照らされていることを確認します。
- 燃料制限を有効にしている場合、時間経過とともに
- 明るさ(intensity)が徐々に下がる
- 照射範囲(range)が徐々に狭くなる
様子を観察します。
- 燃料がゼロになったときに、
- Disable Light On Empty = true の場合:ライトが完全に消える
- Toggle Node On Empty に設定したノードの active が切り替わる
ことを確認します。
6. (任意)他スクリプトからの制御例
外部依存は不要ですが、必要であれば他のスクリプトから燃料を回復させたり、消費を一時停止したりできます。
例として、プレイヤーが燃料アイテムを拾ったときに 10 秒分燃料を追加するコードは以下のようになります。
// 例: Player.ts 内など
import { _decorator, Component, Node } from 'cc';
import { LanternLight } from './LanternLight';
const { ccclass, property } = _decorator;
@ccclass('Player')
export class Player extends Component {
@property({
tooltip: 'プレイヤーに追従するランタンノード(LanternLight がアタッチされたノード)'
})
public lanternNode: Node | null = null;
private _lantern: LanternLight | null = null;
start() {
if (this.lanternNode) {
this._lantern = this.lanternNode.getComponent(LanternLight);
}
}
// 何らかのトリガーで呼ばれる想定
onPickupFuelItem() {
if (this._lantern) {
this._lantern.addFuel(10); // 10秒分燃料を追加
}
}
}
このように、LanternLight は単体で完結しつつも、必要に応じて他スクリプトから柔軟に連携できるよう設計しています。
まとめ
- LanternLight コンポーネントは、PointLight2D と組み合わせることで
- 暗闇の中で周囲を照らす 2D ランタン
- 時間経過とともに弱まる「燃料制限付きライト」
- 燃料切れ時に UI や演出をトリガーする仕組み
を、アタッチするだけで実現します。
- 外部の GameManager やシングルトンには一切依存せず、必要な設定はすべて Inspector の
@propertyから行えるため、どのプロジェクトにも簡単に組み込めます。 - 燃料システムを OFF にすれば「常時点灯ライト」としても使えるため、
- ダンジョン探索ゲームのランタン
- ホラーゲームの懐中電灯(色や範囲を変えるだけ)
- 夜間の街灯やキャンプファイヤー
など、さまざまなシーンで再利用できます。
このコンポーネントをベースに、
- 点滅やゆらぎ(ノイズ)を加えた「揺れる炎」の表現
- 燃料残量に応じた SE 再生や画面エフェクト
- 複数のランタンをシーン内に配置してステージギミックにする
といった発展も簡単に行えます。
まずは本記事の LanternLight をプロジェクトに組み込み、暗闇表現のベースとして活用してみてください。




