【Cocos Creator 3.8】DistanceActivator の実装:アタッチするだけで「プレイヤーが近づくまでノードを眠らせる」汎用スクリプト
このガイドでは、Cocos Creator 3.8.7 + TypeScript で、プレイヤーが一定距離まで近づくまでノードの処理や物理演算を無効化しておき、近づいたらまとめて有効化するための汎用コンポーネント DistanceActivator を実装します。
敵やギミック、パーティクルなどを「プレイヤーの近くに来たときだけ動かしたい」「遠くにあるオブジェクトは処理を止めて軽くしたい」といった場面で、このコンポーネントをアタッチして距離しきい値とプレイヤーノードを指定するだけで使えるようにします。
コンポーネントの設計方針
実現したい機能
- このコンポーネントをアタッチしたノード(以下「対象ノード」)は、最初は非アクティブ状態(処理・物理演算などを停止した状態)として扱う。
- 指定した「プレイヤーノード」が対象ノードに一定距離まで近づいたら、対象ノードの各種コンポーネントを有効化する。
- 一度有効化したら、その後は再び距離が離れても無効化には戻さない(デフォルト)。
- 距離チェックの対象は 2D / 3D を問わず、ワールド座標ベースの 3D 距離で行う。
- 処理負荷を抑えるため、距離チェックは毎フレームではなく、任意の間隔(秒)ごとに行えるようにする。
外部依存をなくすための設計
- プレイヤーノードは Inspector から指定する @property とし、GameManager やシングルトンには依存しない。
- 有効化・無効化の対象とするコンポーネント(
RigidBody2D/RigidBody/Collider2D/Collider/Animation/SkeletalAnimation/ParticleSystem2D/ParticleSystem/AudioSourceなど)は、自動で探して制御する。 - 存在しないコンポーネントは単にスキップし、ログエラーにはしない(任意で利用できるようにするため)。
- 開始時に「無効化フラグ」がオンの場合は、対象コンポーネントを強制的に停止・無効化してから距離監視を開始する。
インスペクタで設定可能なプロパティ
- playerNode: Node | null
- プレイヤー(または距離の基準とする)ノード。
- Hierarchy からドラッグ&ドロップで指定。
- 未設定の場合は警告ログを出し、距離チェックを行わない。
- activateDistance: number
- プレイヤーノードがこの距離以下まで近づいたらアクティブ化を行う。
- 単位はワールド座標系での距離(メートル)。
- 例: 10 にすると、10m 以内に入った時点で有効化。
- checkInterval: number
- 距離をチェックする間隔(秒)。
- 0 以下にすると毎フレーム
updateでチェック。 - 例: 0.2 にすると、0.2 秒ごとに距離を測定して判定。
- deactivateOnStart: boolean
- true の場合、開始時に対象ノードの各種コンポーネントを停止/無効化状態にしておく。
- 敵 AI やパーティクルを「プレイヤーが近づくまで完全に止めておく」用途に便利。
- reactivateWhenFar: boolean
- true の場合、距離がしきい値より遠くなったら再び無効化する。
- false の場合、一度有効化したらそのまま有効のまま(デフォルト挙動)。
- hysteresisDistance: number
reactivateWhenFarが true のときだけ使用。- 「再び無効化する距離」のしきい値。
- 例:
activateDistance = 10、hysteresisDistance = 15とすると、10m 以内で有効化し、15m 以上離れたら無効化に戻る。 - 0 以下の場合は
activateDistanceと同じ値として扱う。
- affectChildren: boolean
- true の場合、子ノード以下も含めて各種コンポーネントの有効・無効を切り替える。
- false の場合、このノード自身に付いているコンポーネントのみ制御する。
- logDebug: boolean
- true の場合、距離判定や状態変更のタイミングで
console.logを出力する。 - 挙動確認やデバッグに便利。リリースビルドでは false 推奨。
- true の場合、距離判定や状態変更のタイミングで
TypeScriptコードの実装
import {
_decorator,
Component,
Node,
Vec3,
RigidBody2D,
RigidBody,
Collider2D,
Collider,
Animation,
SkeletalAnimation,
ParticleSystem2D,
ParticleSystem,
AudioSource,
game,
} from 'cc';
const { ccclass, property } = _decorator;
/**
* DistanceActivator
* プレイヤーが一定距離に近づくまで、対象ノードの処理や物理演算を無効化しておき、
* 近づいたらまとめて有効化する汎用コンポーネント。
*/
@ccclass('DistanceActivator')
export class DistanceActivator extends Component {
@property({
tooltip: '距離判定の基準となるプレイヤーノード。\nここで指定したノードとの距離を監視します。'
})
public playerNode: Node | null = null;
@property({
tooltip: 'プレイヤーがこの距離(メートル)以内に近づいたら有効化します。'
})
public activateDistance: number = 10;
@property({
tooltip: '距離チェックを行う間隔(秒)。\n0 以下にすると毎フレームチェックします。'
})
public checkInterval: number = 0.2;
@property({
tooltip: '開始時に対象ノードの処理や物理演算を無効化しておくかどうか。'
})
public deactivateOnStart: boolean = true;
@property({
tooltip: 'プレイヤーが離れたときに再び無効化するかどうか。\ntrue の場合はヒステリシス距離も利用します。'
})
public reactivateWhenFar: boolean = false;
@property({
tooltip: '再び無効化する距離(メートル)。\n0 以下の場合は activateDistance と同じ値として扱います。'
})
public hysteresisDistance: number = 0;
@property({
tooltip: 'true の場合、子ノード以下も含めてコンポーネントの有効・無効を切り替えます。'
})
public affectChildren: boolean = true;
@property({
tooltip: 'デバッグ用ログを出力するかどうか。'
})
public logDebug: boolean = false;
/** 現在アクティブ化されているかどうか */
private _isActive: boolean = false;
/** 距離チェック用の経過時間カウンタ */
private _timeAccumulator: number = 0;
/** 有効化・無効化の対象とするノード一覧(自身+必要なら子孫) */
private _targetNodes: Node[] = [];
onLoad() {
// 対象ノード一覧を構築
this._buildTargetNodeList();
// 起動時に無効化しておく設定なら、ここで一括停止
if (this.deactivateOnStart) {
this._applyActiveState(false);
this._isActive = false;
if (this.logDebug) {
console.log(`[DistanceActivator] Deactivated on start for node: ${this.node.name}`);
}
} else {
// 無効化しない場合は、現在の状態を「アクティブ」として扱う
this._isActive = true;
}
// プレイヤーノード未設定の場合は警告のみ出す
if (!this.playerNode) {
console.warn(`[DistanceActivator] playerNode is not assigned on node: ${this.node.name}. Distance check will be skipped.`);
}
// checkInterval が負の場合の防御
if (this.checkInterval < 0) {
console.warn(`[DistanceActivator] checkInterval is negative on node: ${this.node.name}. Using 0 (every frame).`);
this.checkInterval = 0;
}
// hysteresisDistance の補正
if (this.hysteresisDistance <= 0) {
this.hysteresisDistance = this.activateDistance;
}
}
start() {
// 特に処理は不要だが、ライフサイクルの流れを明示
if (this.logDebug) {
console.log(`[DistanceActivator] start on node: ${this.node.name}`);
}
}
update(deltaTime: number) {
// プレイヤーノードが設定されていなければ何もしない
if (!this.playerNode) {
return;
}
// ゲームが一時停止中の場合は処理をスキップ(任意)
if (game.isPaused()) {
return;
}
// 距離チェックの間隔制御
if (this.checkInterval > 0) {
this._timeAccumulator += deltaTime;
if (this._timeAccumulator < this.checkInterval) {
return;
}
this._timeAccumulator = 0;
}
// 実際の距離チェック
this._checkDistanceAndUpdateState();
}
/**
* 自身および(必要なら)子孫ノードの一覧を構築。
*/
private _buildTargetNodeList() {
this._targetNodes.length = 0;
this._targetNodes.push(this.node);
if (this.affectChildren) {
this._collectChildrenRecursive(this.node);
}
}
private _collectChildrenRecursive(node: Node) {
for (const child of node.children) {
this._targetNodes.push(child);
this._collectChildrenRecursive(child);
}
}
/**
* プレイヤーとの距離を計算して、有効/無効状態を更新する。
*/
private _checkDistanceAndUpdateState() {
const selfWorldPos = new Vec3();
const playerWorldPos = new Vec3();
this.node.getWorldPosition(selfWorldPos);
this.playerNode!.getWorldPosition(playerWorldPos);
const distance = Vec3.distance(selfWorldPos, playerWorldPos);
if (this.logDebug) {
console.log(`[DistanceActivator] distance = ${distance.toFixed(2)} (node: ${this.node.name})`);
}
// まだアクティブでない場合、近づいたら有効化
if (!this._isActive && distance <= this.activateDistance) {
this._isActive = true;
this._applyActiveState(true);
if (this.logDebug) {
console.log(`[DistanceActivator] Activated (distance ${distance.toFixed(2)}) on node: ${this.node.name}`);
}
return;
}
// すでにアクティブで、離れたら無効化する設定の場合
if (this._isActive && this.reactivateWhenFar && distance >= this.hysteresisDistance) {
this._isActive = false;
this._applyActiveState(false);
if (this.logDebug) {
console.log(`[DistanceActivator] Deactivated (distance ${distance.toFixed(2)}) on node: ${this.node.name}`);
}
}
}
/**
* 対象ノード群のコンポーネントを有効/無効にする。
* - 物理ボディ(2D/3D)の有効化・無効化
* - コライダー(2D/3D)の有効化・無効化
* - アニメーションの再生/停止
* - パーティクルの再生/停止
* - オーディオの再生/停止
*/
private _applyActiveState(activate: boolean) {
for (const node of this._targetNodes) {
// RigidBody2D
const rb2d = node.getComponent(RigidBody2D);
if (rb2d) {
rb2d.enabled = activate;
}
// RigidBody (3D)
const rb3d = node.getComponent(RigidBody);
if (rb3d) {
rb3d.enabled = activate;
}
// Collider2D
const col2d = node.getComponent(Collider2D);
if (col2d) {
col2d.enabled = activate;
}
// Collider (3D)
const col3d = node.getComponent(Collider);
if (col3d) {
col3d.enabled = activate;
}
// Animation
const anim = node.getComponent(Animation);
if (anim) {
if (activate) {
// すでに再生中でなければ再生
if (!anim.getState(anim.defaultClip?.name || '')) {
anim.play();
} else if (!anim.getState(anim.defaultClip!.name).isPlaying) {
anim.play();
}
} else {
anim.stop();
}
}
// SkeletalAnimation
const skelAnim = node.getComponent(SkeletalAnimation);
if (skelAnim) {
if (activate) {
if (skelAnim.defaultClip) {
skelAnim.play(skelAnim.defaultClip.name);
} else {
skelAnim.play();
}
} else {
skelAnim.stop();
}
}
// ParticleSystem2D
const ps2d = node.getComponent(ParticleSystem2D);
if (ps2d) {
if (activate) {
ps2d.resetSystem();
} else {
ps2d.stopSystem();
}
}
// ParticleSystem (3D)
const ps3d = node.getComponent(ParticleSystem);
if (ps3d) {
if (activate) {
ps3d.play();
} else {
ps3d.stop();
}
}
// AudioSource
const audio = node.getComponent(AudioSource);
if (audio) {
if (activate) {
if (!audio.playing) {
audio.play();
}
} else {
audio.stop();
}
}
// ここで必要に応じて、他のコンポーネントの有効/無効も追加可能
// 例: CustomAI や ScriptComponent など
// const custom = node.getComponent(CustomAI);
// if (custom) custom.enabled = activate;
}
}
}
コードの要点解説
- onLoad
_buildTargetNodeList()で、制御対象とするノード一覧(自身+必要なら子孫)を構築。deactivateOnStartが true の場合、_applyActiveState(false)で各種コンポーネントを停止・無効化。playerNodeが未設定なら警告ログを出し、距離チェックはスキップされる。checkIntervalとhysteresisDistanceの値を防御的に補正。
- update
checkIntervalに応じて距離チェックの頻度を制御。- 間隔が経過したタイミングで
_checkDistanceAndUpdateState()を呼び出す。
- _checkDistanceAndUpdateState
- プレイヤーと対象ノードのワールド座標を取得し、
Vec3.distanceで距離を算出。 - 距離が
activateDistance以下で、まだアクティブでない場合 → 有効化。 reactivateWhenFarが true の場合、距離がhysteresisDistance以上で、アクティブ中 → 無効化。
- プレイヤーと対象ノードのワールド座標を取得し、
- _applyActiveState
- 対象ノード群をループし、代表的なコンポーネントをまとめて有効/無効にする。
- 物理ボディ・コライダーは
enabledフラグを切り替え。 - アニメーション・パーティクル・オーディオは
play()/stop()や相当のメソッドで制御。 - 存在しないコンポーネントは無視するため、どのノードにアタッチしても安全に動作。
使用手順と動作確認
1. スクリプトファイルの作成
- エディタ左下の Assets パネルで任意のフォルダ(例:
assets/scripts)を右クリック。 - Create → TypeScript を選択します。
- ファイル名を
DistanceActivator.tsに変更します。 - 作成した
DistanceActivator.tsをダブルクリックし、エディタ(VSCode など)で開きます。 - 中身をすべて削除し、本記事の
DistanceActivatorのコードをそのまま貼り付けて保存します。
2. テスト用シーンの準備
ここでは 2D シーンを例にしますが、3D でも同様です。
- Scene を開き、まだカメラや Canvas がない場合は 2D シーンテンプレートを使用して作成します。
- Hierarchy パネルで右クリック → Create → 2D Object → Sprite を選び、Player という名前に変更します。
- これが
playerNodeに指定するプレイヤー役のノードになります。
- これが
- 同様に、右クリック → Create → 2D Object → Sprite で別のスプライトを作成し、Enemy という名前に変更します。
- この Enemy ノードに
DistanceActivatorをアタッチします。
- この Enemy ノードに
3. DistanceActivator コンポーネントのアタッチ
- Hierarchy で Enemy ノードを選択します。
- Inspector の下部にある Add Component ボタンをクリック。
- Custom カテゴリの中から DistanceActivator を選択します。
- 見つからない場合は、スクリプトを保存した後に Cocos Creator を一度フォーカスし直して再インポートさせてください。
4. プロパティの設定
Enemy ノードに追加された DistanceActivator の各プロパティを設定します。
- Player Node(
playerNode)- Hierarchy から Player ノードをドラッグして、このフィールドにドロップします。
- Activate Distance(
activateDistance)- 例として 5 と入力します。
- これで、プレイヤーが Enemy から 5m 以内に近づいたときに有効化されます。
- Check Interval(
checkInterval)- 例として 0.2 と入力します。
- 0.2 秒ごとに距離チェックを行うため、負荷を抑えつつ十分な反応速度が得られます。
- Deactivate On Start(
deactivateOnStart)- 敵の動きや物理演算を最初は止めておきたいので、チェックを入れたまま(true)にします。
- Reactivate When Far(
reactivateWhenFar)- 一度有効化したらそのままにしたい場合は チェックを外す(false)。
- プレイヤーが離れたら再び止めたい場合は チェックを入れる(true)。
- Hysteresis Distance(
hysteresisDistance)reactivateWhenFarを true にした場合のみ意味があります。- 例:
activateDistance = 5、hysteresisDistance = 8とすると、5m 以内で有効化し、8m 以上離れたら再び無効化されます。
- Affect Children(
affectChildren)- Enemy に子ノード(当たり判定、エフェクトなど)がある場合は チェックを入れる(true) ことで、まとめて制御できます。
- Enemy 自身のコンポーネントだけを制御したい場合はチェックを外します。
- Log Debug(
logDebug)- 挙動確認のために チェックを入れておくと、コンソールに距離と状態変化が表示されます。
- 問題がなければ最終的にはオフにしておくと良いでしょう。
5. 動作確認
- シーンを保存し、エディタ右上の Play ボタンで実行します。
- ゲームビュー上で Player ノードをドラッグして移動させ、Enemy に近づけてみます。
- 開始時点では、Enemy に付いている
RigidBody2DやAnimationなどが停止しているはずです(deactivateOnStart = true の場合)。 - プレイヤーが
activateDistance以内に入ると、Enemy のコンポーネントが有効化され、物理挙動やアニメーションが動き始めます。
- 開始時点では、Enemy に付いている
reactivateWhenFar = true+hysteresisDistance > activateDistanceに設定している場合は、プレイヤーを遠ざけて しきい値より離れると再び無効化されることも確認できます。logDebug = trueにしている場合は、Console パネルに[DistanceActivator] distance = ...[DistanceActivator] Activated ...[DistanceActivator] Deactivated ...
といったログが出力され、距離判定のタイミングが分かります。
まとめ
DistanceActivator は、
- プレイヤーが近づくまで敵やギミックを「眠らせておく」
- 画面外や遠方のオブジェクトの物理演算・アニメーション・パーティクル・サウンドを止めておき、パフォーマンスを向上させる
- プレイヤーとの距離ベースで「出現」「消滅」「起動」「休止」を制御する
といった用途にそのまま使える、汎用性の高いコンポーネントです。
本コンポーネントは、
- 外部の GameManager やシングルトンに一切依存せず、必要な情報はすべて Inspector の @property から受け取る設計
- 物理・アニメーション・パーティクル・オーディオなど、代表的なコンポーネントをまとめて制御
- 子ノードも含めた一括制御や、チェック間隔・ヒステリシスなどの細かい調整が可能
という特徴を持っているため、任意のノードにアタッチするだけで簡単に「距離アクティブ」機構を導入できます。
応用としては、
- DistanceActivator を複数の敵・オブジェクトに付けて、プレイヤー周辺だけが動くワールドを構築する
- プレイヤーではなくカメラノードを
playerNodeに指定して、画面外のオブジェクトを自動で休止させる - 特定のチェックポイントノードを基準に、ステージの一部を段階的に起動していく
など、さまざまなゲーム設計にそのまま組み込めます。まずはシンプルな敵やギミックに試しにアタッチして、距離しきい値やチェック間隔を調整しながら、自分のプロジェクトに最適な設定を見つけてみてください。




