【Cocos Creator 3.8】SpeedLineEffect(集中線)の実装:アタッチするだけで「高速移動時に画面端へ集中線エフェクト」を自動表示する汎用スクリプト
このガイドでは、任意のノードにアタッチするだけで「ある速度以上になったときに、画面端に集中線(スピードライン)エフェクトを表示する」汎用コンポーネント SpeedLineEffect を実装します。
速度の参照先は Rigidbody2D / Rigidbody / CharacterController などに限定せず、「任意の速度値(Vector3)」をインスペクタから受け取り、その値に応じて集中線を出す設計にします。実際のゲームでは、プレイヤーの移動スクリプトなどから setCurrentVelocity() を呼び出すだけで連携できます。
集中線の描画は、フルスクリーンの ColorRect(UI Sprite)+カスタムマテリアル(シェーダ)で行い、速度に応じて濃さ・長さを変化させます。マテリアルもインスペクタから設定できるため、プロジェクトごとに自由に見た目をカスタマイズできます。
コンポーネントの設計方針
1. 要件整理
- 任意のノードにアタッチ可能。
- 指定した「速度ベクトル」の大きさ(magnitude)が閾値を超えたときに集中線を表示する。
- 速度が遅くなると集中線は自動でフェードアウトする。
- 集中線は画面全体にかかる UI ノードとして自動生成する(Canvas の子としてフルスクリーン表示)。
- 集中線の見た目は ColorRect(Sprite)+シェーダ(Material)で表現し、インスペクタからマテリアルを差し替え可能。
- 外部スクリプトへの依存禁止:速度値は
- インスペクタの「テスト用」パラメータで自動変化させるモード
- 他スクリプトからの
setCurrentVelocity()呼び出し
の 2 通りで供給できるようにする。
- Canvas が存在しない/UI Camera が無いなどの場合はエラーログで警告し、安全に失敗する。
2. シェーダ(マテリアル)の前提
本コンポーネントでは、以下のようなプロパティを持つカスタムマテリアルを想定します(名前は任意ですが、例として示します)。
_Intensity: 集中線の強さ(0.0〜1.0)_Length: 線の長さ(0.0〜1.0)_MainColor: 線の色
SpeedLineEffect はこれらのプロパティを Material.setProperty() で更新し、速度に応じて見た目を動的に変化させます。マテリアルの実装自体はプロジェクトごとに異なるため、このガイドでは「プロパティ名を合わせておく」前提で進めます。
3. インスペクタで設定可能なプロパティ
以下のようなプロパティを設計します。
- enableAutoTest (boolean)
- ツールチップ: 「自動テストモード。オンにすると、速度を自動で増減させてエフェクトを確認できます。」
- true の場合、update 内でテスト用の速度を生成し、それに基づいて集中線を表示する。
- testMaxSpeed (number)
- ツールチップ: 「自動テストモード時の最大速度(単位は任意)。この値付近で最大強度になります。」
- enableAutoTest が true のときのみ意味を持つ。
- speedThreshold (number)
- ツールチップ: 「この速度以上で集中線エフェクトが有効になります。」
- 現在速度の大きさがこの値を下回ると、エフェクトはフェードアウトする。
- maxIntensity (number)
- ツールチップ: 「速度が十分に速いときの最大強度(0〜1)。」
- マテリアルの
_Intensityに反映。
- maxLength (number)
- ツールチップ: 「速度が十分に速いときの線の最大長さ(0〜1)。」
- マテリアルの
_Lengthに反映。
- fadeInSpeed (number)
- ツールチップ: 「エフェクトが出現するときの補間速度。大きいほど素早く切り替わります。」
- 0.0〜10.0 程度の値を推奨。
- fadeOutSpeed (number)
- ツールチップ: 「エフェクトが消えるときの補間速度。大きいほど素早く消えます。」
- 0.0〜10.0 程度の値を推奨。
- speedToEffectScale (number)
- ツールチップ: 「速度をエフェクト強度に変換するスケール係数。」
- 速度の大きさ × この値 = 強度のベースとなる値(0〜1にクランプ)。
- speedLineMaterial (Material)
- ツールチップ: 「集中線用マテリアル。_Intensity, _Length, _MainColor プロパティを持つことを推奨。」
- Sprite に適用するマテリアルの参照。
- lineColor (Color)
- ツールチップ: 「集中線の基本色。マテリアルの _MainColor に反映されます。」
- canvasNode (Node)
- ツールチップ: 「UI を描画する Canvas ノード。未設定の場合はシーン内から自動検出を試みます。」
- 自動検出に失敗した場合はエラーログを出す。
- uiLayerIndex (number)
- ツールチップ: 「生成される集中線 UI ノードのレイヤー(0〜31)。通常は UI_2D レイヤー (1<<25) に設定します。」
- レイヤーマスクの設定に用いる。
4. 外部依存をなくすためのアプローチ
- 集中線用の UI ノード(Sprite)は、SpeedLineEffect 自身が動的に生成する。
- ユーザーは事前に専用ノードを用意する必要なし。
- Canvas が見つからない場合は、ログで明示的に警告し、エフェクトを無効化する。
- 速度は
- 自動テストモード(enableAutoTest = true)
- 他スクリプトから
setCurrentVelocity(vec3)を呼ぶ
のどちらかで供給できるため、外部の GameManager などは不要。
TypeScriptコードの実装
以下が完成した SpeedLineEffect コンポーネントの全コードです。
import {
_decorator,
Component,
Node,
Vec3,
UITransform,
Sprite,
Color,
Material,
Canvas,
director,
math,
Layers,
warn,
error,
} from 'cc';
const { ccclass, property } = _decorator;
/**
* SpeedLineEffect
* 高速移動時に画面端へ集中線(スピードライン)エフェクトを表示する汎用コンポーネント。
*
* 特徴:
* - 任意のノードにアタッチ可能
* - 速度ベクトルの大きさに応じてエフェクト強度を自動調整
* - ColorRect (Sprite) + カスタムマテリアルで描画
* - Canvas / Sprite ノードは自動生成(外部依存なし)
*
* 速度の供給方法:
* - enableAutoTest = true の場合、内部でテスト用の速度を自動生成
* - ゲーム側から setCurrentVelocity(vec3) を呼び出してもよい
*/
@ccclass('SpeedLineEffect')
export class SpeedLineEffect extends Component {
@property({
tooltip: '自動テストモード。オンにすると、速度を自動で増減させてエフェクトを確認できます。',
})
public enableAutoTest: boolean = true;
@property({
tooltip: '自動テストモード時の最大速度(単位は任意)。この値付近で最大強度になります。',
min: 0,
})
public testMaxSpeed: number = 30;
@property({
tooltip: 'この速度以上で集中線エフェクトが有効になります。',
min: 0,
})
public speedThreshold: number = 10;
@property({
tooltip: '速度が十分に速いときの最大強度(0〜1)。',
min: 0,
max: 1,
step: 0.01,
})
public maxIntensity: number = 1.0;
@property({
tooltip: '速度が十分に速いときの線の最大長さ(0〜1)。',
min: 0,
max: 1,
step: 0.01,
})
public maxLength: number = 1.0;
@property({
tooltip: 'エフェクトが出現するときの補間速度。大きいほど素早く切り替わります。',
min: 0,
max: 20,
})
public fadeInSpeed: number = 8.0;
@property({
tooltip: 'エフェクトが消えるときの補間速度。大きいほど素早く消えます。',
min: 0,
max: 20,
})
public fadeOutSpeed: number = 5.0;
@property({
tooltip: '速度をエフェクト強度に変換するスケール係数。速度の大きさ × この値 = 強度のベース(0〜1にクランプ)。',
min: 0,
step: 0.001,
})
public speedToEffectScale: number = 0.03;
@property({
type: Material,
tooltip: '集中線用マテリアル。_Intensity, _Length, _MainColor プロパティを持つことを推奨。',
})
public speedLineMaterial: Material | null = null;
@property({
tooltip: '集中線の基本色。マテリアルの _MainColor に反映されます。',
})
public lineColor: Color = new Color(255, 255, 255, 255);
@property({
type: Node,
tooltip: 'UI を描画する Canvas ノード。未設定の場合はシーン内から自動検出を試みます。',
})
public canvasNode: Node | null = null;
@property({
tooltip: '生成される集中線 UI ノードのレイヤー(0〜31)。通常は UI_2D レイヤー (25) などに設定します。',
min: 0,
max: 31,
})
public uiLayerIndex: number = 25; // デフォルトで UI_2D (1 << 25) を想定
// 現在の速度ベクトル(ゲーム側から setCurrentVelocity で更新可能)
private _currentVelocity: Vec3 = new Vec3();
// 内部で生成する集中線用ノードとコンポーネント
private _effectNode: Node | null = null;
private _effectSprite: Sprite | null = null;
private _effectMaterialInstance: Material | null = null;
// 現在・目標の強度/長さ
private _currentIntensity: number = 0;
private _targetIntensity: number = 0;
private _currentLength: number = 0;
private _targetLength: number = 0;
// 自動テスト用の時間
private _testTime: number = 0;
/**
* ゲーム側から現在の速度を設定するための公開メソッド。
* 例: player 移動スクリプトの update で呼び出し:
* speedLineEffect.setCurrentVelocity(playerVelocity);
*/
public setCurrentVelocity(v: Vec3) {
this._currentVelocity.set(v);
}
onLoad() {
// Canvas の自動検出
if (!this.canvasNode) {
const scene = director.getScene();
if (scene) {
const canvas = scene.getComponentInChildren(Canvas);
if (canvas) {
this.canvasNode = canvas.node;
} else {
warn('[SpeedLineEffect] Canvas がシーン内に見つかりませんでした。canvasNode をインスペクタから指定してください。');
}
} else {
warn('[SpeedLineEffect] シーンがロードされていません。Canvas の自動検出に失敗しました。');
}
}
// マテリアルが未設定の場合は警告
if (!this.speedLineMaterial) {
warn('[SpeedLineEffect] speedLineMaterial が設定されていません。集中線エフェクトは表示されません。');
}
}
start() {
// Canvas が無い場合はこれ以上進めない
if (!this.canvasNode) {
error('[SpeedLineEffect] Canvas ノードが見つからないため、集中線エフェクトを初期化できません。');
return;
}
// 集中線用の UI ノードを生成
this._createEffectNode();
// 初期状態は非表示(強度 0)
this._currentIntensity = 0;
this._targetIntensity = 0;
this._currentLength = 0;
this._targetLength = 0;
this._applyToMaterial(0, 0);
}
update(dt: number) {
// Canvas / エフェクトノードが準備できていなければ何もしない
if (!this.canvasNode || !this._effectNode || !this._effectSprite || !this._effectMaterialInstance) {
return;
}
// 1. 速度の更新(自動テストモードの場合)
if (this.enableAutoTest) {
this._updateTestVelocity(dt);
}
// 2. 速度の大きさから目標強度/長さを計算
const speed = this._currentVelocity.length();
this._updateTargetBySpeed(speed);
// 3. 現在値を目標値へ補間(フェードイン/アウト)
this._updateCurrentValues(dt);
// 4. マテリアルへ適用
this._applyToMaterial(this._currentIntensity, this._currentLength);
// 5. 表示/非表示の切り替え(強度がほぼ 0 のときは無効化)
const active = this._currentIntensity > 0.001;
this._effectNode.active = active;
}
/**
* 自動テストモード用の速度更新。
* sin 波で 0 〜 testMaxSpeed を往復するような速度を生成する。
*/
private _updateTestVelocity(dt: number) {
this._testTime += dt;
const t = this._testTime;
// 0〜1 を行き来する値
const wave = (Math.sin(t) + 1) * 0.5;
const speed = wave * this.testMaxSpeed;
// 任意の方向に速度を与える(ここでは X 軸正方向)
this._currentVelocity.set(speed, 0, 0);
}
/**
* 速度の大きさに基づいて目標強度/長さを計算する。
*/
private _updateTargetBySpeed(speed: number) {
if (speed <= this.speedThreshold) {
// 閾値以下ならエフェクトオフ方向へ
this._targetIntensity = 0;
this._targetLength = 0;
return;
}
// 閾値を超えた分を 0〜1 に正規化するようなイメージでスケーリング
let base = speed * this.speedToEffectScale;
base = math.clamp01(base);
this._targetIntensity = base * this.maxIntensity;
this._targetLength = base * this.maxLength;
}
/**
* 現在値を目標値へ補間する(フェードイン/アウト)。
*/
private _updateCurrentValues(dt: number) {
const isIncreasing = this._targetIntensity > this._currentIntensity;
const lerpFactor = isIncreasing
? math.clamp01(this.fadeInSpeed * dt)
: math.clamp01(this.fadeOutSpeed * dt);
this._currentIntensity = math.lerp(this._currentIntensity, this._targetIntensity, lerpFactor);
this._currentLength = math.lerp(this._currentLength, this._targetLength, lerpFactor);
// 数値誤差を抑えるためのクランプ
this._currentIntensity = math.clamp01(this._currentIntensity);
this._currentLength = math.clamp01(this._currentLength);
}
/**
* マテリアルへ強度/長さ/色を反映する。
*/
private _applyToMaterial(intensity: number, length: number) {
if (!this._effectMaterialInstance) {
return;
}
try {
// カスタムシェーダ側で定義しているプロパティ名に合わせる
this._effectMaterialInstance.setProperty('_Intensity', intensity);
} catch (e) {
// プロパティ名が存在しない場合もあるので、エラーは握りつぶすが一度だけ警告してもよい
}
try {
this._effectMaterialInstance.setProperty('_Length', length);
} catch (e) {}
try {
this._effectMaterialInstance.setProperty('_MainColor', this.lineColor);
} catch (e) {}
}
/**
* 集中線用の UI ノードを作成し、Canvas の子としてフルスクリーン配置する。
*/
private _createEffectNode() {
if (!this.canvasNode) {
return;
}
// すでに存在していれば再利用
if (this._effectNode && this._effectSprite) {
return;
}
const effectNode = new Node('SpeedLineEffectNode');
this._effectNode = effectNode;
// レイヤー設定
const layerBit = 1 << this.uiLayerIndex;
effectNode.layer = layerBit;
// Canvas の子に追加
this.canvasNode.addChild(effectNode);
// UITransform を追加してフルスクリーンにする
const canvasTransform = this.canvasNode.getComponent(UITransform);
if (!canvasTransform) {
error('[SpeedLineEffect] Canvas ノードに UITransform がありません。集中線エフェクトを初期化できません。');
return;
}
const uiTransform = effectNode.addComponent(UITransform);
uiTransform.setAnchorPoint(0.5, 0.5);
uiTransform.setContentSize(canvasTransform.contentSize);
effectNode.setPosition(0, 0, 0);
// Sprite コンポーネントを追加(ColorRect 的に使用)
const sprite = effectNode.addComponent(Sprite);
this._effectSprite = sprite;
// マテリアルインスタンスを設定
if (this.speedLineMaterial) {
// インスタンス化してから適用(他のノードと共有しないため)
this._effectMaterialInstance = new Material();
this._effectMaterialInstance.copy(this.speedLineMaterial);
sprite.customMaterial = this._effectMaterialInstance;
} else {
warn('[SpeedLineEffect] speedLineMaterial が設定されていないため、Sprite にマテリアルを適用できません。');
}
// 初期は非表示
effectNode.active = false;
}
onDestroy() {
// 自動生成したノードをクリーンアップ
if (this._effectNode && this._effectNode.isValid) {
this._effectNode.destroy();
}
this._effectNode = null;
this._effectSprite = null;
this._effectMaterialInstance = null;
}
}
コードのポイント解説
- onLoad
- Canvas ノードが未指定の場合、シーン内から
Canvasコンポーネントを自動検出します。 - speedLineMaterial が未設定の場合は警告ログを出します(なくてもゲームは落ちない)。
- Canvas ノードが未指定の場合、シーン内から
- start
- Canvas が見つからない場合は
errorログを出し、以降の処理を中止します。 _createEffectNode()で集中線用の UI ノード(Sprite)を自動生成し、Canvas の子にフルスクリーンで配置します。- 初期状態では強度 0(非表示)に設定します。
- Canvas が見つからない場合は
- update(dt)
- 必要なノードやマテリアルが揃っていなければ何もしません(防御的)。
- enableAutoTest が true の場合、
_updateTestVelocity()でテスト用速度を生成します。 - 速度の大きさから
_updateTargetBySpeed()で目標強度・長さを計算します。 _updateCurrentValues()で現在値を目標値へ補間(フェードイン/アウト)します。_applyToMaterial()でマテリアルのプロパティ_Intensity,_Length,_MainColorを更新します。- 強度がほぼ 0 のときは
_effectNode.active = falseにして描画コストを抑えます。
- setCurrentVelocity(v: Vec3)
- ゲーム側から速度を渡すための公開 API です。
- プレイヤー移動スクリプトなどから毎フレーム呼び出すことで、実際の移動速度に基づいた集中線表示ができます。
- _createEffectNode()
- Canvas の子として
SpeedLineEffectNodeを生成し、UITransformで Canvas と同じサイズに設定します。 - Sprite コンポーネントを追加し、
speedLineMaterialのコピーをcustomMaterialに設定します。 - レイヤーは
uiLayerIndexから1 << uiLayerIndexを計算して設定します。
- Canvas の子として
使用手順と動作確認
1. シェーダ(マテリアル)の準備
まず、集中線を描画するためのカスタムマテリアルを用意します。
- Assets パネルで右クリック → Create → Effect を選択し、例として
speed-line.effectを作成します。 - 作成した Effect をダブルクリックし、シェーダコード内で以下のようなプロパティを定義します(擬似コード例):
uniform float _Intensity;uniform float _Length;uniform vec4 _MainColor;
これらを用いて、中心から外側へ伸びる放射状の線を描画するロジックを書きます。
- Effect を保存したら、Assets パネルで右クリック → Create → Material から
SpeedLine.matを作成し、Effect に先ほどのspeed-line.effectを指定します。 SpeedLine.matのインスペクタで、_MainColor などの初期値を設定しておきます。
ここまでで、「集中線の見た目」を表すマテリアルが完成します。
2. SpeedLineEffect.ts スクリプトの作成
- Assets パネルで右クリック → Create → TypeScript を選択し、ファイル名を
SpeedLineEffect.tsとします。 - 自動生成されたコードをすべて削除し、本記事の「TypeScriptコードの実装」にあるコードを丸ごと貼り付けて保存します。
3. Canvas とシーンの準備
- Hierarchy パネルで、シーンに Canvas が存在することを確認します。
- もし無ければ、メニューバーから Create → UI → Canvas を選択して追加します。
- Canvas に UITransform コンポーネントが付いていることを確認します(通常は自動で付いています)。
- UI Camera を使っている場合は、Canvas の Render Mode や Camera 設定が正しく行われているかも確認してください。
4. テスト用ノードに SpeedLineEffect をアタッチ
- Hierarchy パネルで右クリック → Create → Empty Node を選択し、名前を
SpeedLineTesterなどに変更します。 SpeedLineTesterノードを選択し、Inspector の Add Component ボタンをクリックします。- Custom → SpeedLineEffect を選択してコンポーネントを追加します。
5. インスペクタでプロパティを設定
SpeedLineTester ノードの Inspector に表示される SpeedLineEffect のプロパティを次のように設定してみます。
- Enable Auto Test: チェックを入れる(true)
- Test Max Speed: 30
- Speed Threshold: 10
- Max Intensity: 1.0
- Max Length: 1.0
- Fade In Speed: 8.0
- Fade Out Speed: 5.0
- Speed To Effect Scale: 0.03
- Speed Line Material: 先ほど作成した
SpeedLine.matをドラッグ&ドロップ - Line Color: お好みの色(例: 白のまま)
- Canvas Node: シーン内の Canvas ノードをドラッグ&ドロップ(未設定でも自動検出を試みます)
- UI Layer Index: 25(デフォルトの UI_2D を想定)
6. 再生して動作確認
- エディタ右上の Play ボタンを押してゲームを再生します。
- enableAutoTest が true になっているため、何も操作しなくても時間経過に応じて内部のテスト速度が変化し、速度が
Speed Thresholdを超えたあたりで画面に集中線が現れ、速度が下がるとフェードアウトする様子が確認できます。 - もし何も表示されない場合は、以下を確認してください。
- Console に
[SpeedLineEffect]のエラーログが出ていないか。 - Canvas がシーン内に存在するか。
- Speed Line Material が正しく設定されているか(Effect のプロパティ名が _Intensity, _Length, _MainColor になっているか)。
- UI Camera のレイヤーマスクに
1 << UI Layer Indexが含まれているか。
- Console に
7. 実ゲームへの組み込み(速度を外部から渡す)
自動テストではなく、プレイヤーの実際の移動速度に連動させる場合は、次のようにします。
- SpeedLineEffect を、プレイヤー関連のノード(または任意の管理ノード)にアタッチします。
- プレイヤーの移動スクリプト(例:
PlayerMovement.ts)で、SpeedLineEffect を参照し、毎フレーム速度を渡します。
例(PlayerMovement.ts 内の抜粋):
// PlayerMovement.ts 側の例
import { _decorator, Component, Vec3 } from 'cc';
import { SpeedLineEffect } from './SpeedLineEffect';
const { ccclass, property } = _decorator;
@ccclass('PlayerMovement')
export class PlayerMovement extends Component {
@property(SpeedLineEffect)
public speedLineEffect: SpeedLineEffect | null = null;
private _velocity: Vec3 = new Vec3();
update(dt: number) {
// ここで _velocity を更新する(入力や物理など)
// 例: _velocity.set(現在の速度ベクトル);
if (this.speedLineEffect) {
this.speedLineEffect.setCurrentVelocity(this._velocity);
}
}
}
このように、SpeedLineEffect 自体は他スクリプトに依存せず、速度ベクトルさえ渡してもらえれば機能する設計になっています。
まとめ
このガイドでは、Cocos Creator 3.8 / TypeScript で「高速移動時に画面端へ集中線エフェクトを表示する」汎用コンポーネント SpeedLineEffect を実装しました。
- 任意のノードにアタッチするだけで、Canvas 配下に集中線用 UI ノードを自動生成。
- 速度ベクトルの大きさに応じて、シェーダの
_Intensity/_Lengthを更新し、エフェクトの強さ・長さを制御。 - インスペクタから閾値・フェード速度・スケール係数・マテリアル・色などを細かく調整可能。
- 自動テストモードで、他スクリプトと連携せずに単体動作を確認できる。
- 実ゲームでは
setCurrentVelocity()を呼ぶだけで連携でき、外部の GameManager やシングルトンに依存しない。
このような「外部依存を排した汎用コンポーネント」を積み重ねていくことで、シーンごと・プロジェクトごとに簡単に再利用できるエフェクト集を作ることができ、ゲーム開発の効率が大きく向上します。
本コンポーネントをベースに、以下のような拡張も検討できます。
- カメラの向きに合わせて集中線の方向を変える。
- ブースト中だけ色を変える(例: 青 → 赤)。
- モバイル端末では強度上限を自動的に下げてパフォーマンスを確保する。
まずはこの記事のコードをそのまま導入し、自分のゲームのスピード感を一段階引き上げてみてください。




