【Cocos Creator 3.8】ShadowCaster の実装:アタッチするだけで「足元に自動で影を出し、ジャンプ高さで影が縮む」汎用スクリプト
このコンポーネントは、キャラクターやオブジェクトの「足元の影」を自動で生成・制御するための汎用スクリプトです。
ShadowCaster を対象ノードにアタッチするだけで、足元に楕円形の影スプライトを生成し、ノードのジャンプ(Y座標の変化)に応じて影の大きさと濃さを自動調整できます。
別の GameManager や専用のジャンプ制御スクリプトに依存せず、インスペクタで必要なパラメータを設定するだけで動作します。
目次
コンポーネントの設計方針
実現したい機能の整理
- 対象ノード(キャラクターなど)の足元位置に「楕円形の影」を表示する。
- 影は対象ノードの子として自動生成される(外部ノードへの依存なし)。
- 対象ノードがジャンプして Y 座標が高くなるほど、影が小さく・薄くなる。
- 着地(基準高さ)に近いほど、影が大きく・濃くなる。
- Inspector で見た目や挙動を調整できるようにする。
外部依存をなくすためのアプローチ
- 影用ノードは ShadowCaster 自身が自動生成 する。
- 影のスプライト画像は Inspector から SpriteFrame を指定 する。
- ジャンプの高さは、親ノードのローカル Y 座標 を参照するだけとし、他スクリプトの状態には依存しない。
- 基準高さ(着地時の Y 座標)を Inspector で指定し、その高さとの差分を「ジャンプ高さ」として扱う。
Inspector で設定可能なプロパティ設計
ShadowCaster コンポーネントに用意する @property は以下の通りです。
- shadowSpriteFrame: SpriteFrame | null
影として使用するスプライト画像。
楕円形の黒い PNG などを指定します。未設定の場合はエラーログを出し、影は生成しません。 - baseHeight: number
「着地しているとみなす Y 座標」の基準値(ローカル座標)。
例:キャラクターが地面に立っているときの Y 座標が 0 なら 0 のままでOK。
実行時にはcurrentHeight = node.position.y - baseHeightとして「ジャンプ高さ」を計算します。 - maxJumpHeight: number
影の縮小・透明化を計算するための「最大ジャンプ高さ」の想定値。
例:キャラクターが最大で Y=200 くらいまでジャンプするなら 200。
実際の高さがこれを超える場合は、影サイズ・透明度は最小値にクランプされます。 - baseScale: number
基準高さのときの影のスケール(X,Yともにこの値を基準とする)。
例:1.0 なら、影の元画像サイズそのまま。1.5 なら 1.5 倍。 - minScale: number
ジャンプが最大高さに達したときの影の 最小スケール。
例:0.3 なら、maxJumpHeight 以上の高さのときに 0.3 倍まで縮小。 - baseOpacity: number
基準高さ(着地時)の影の不透明度(0〜255)。
例:200 なら、やや透けた影。 - minOpacity: number
最大ジャンプ高さのときの影の不透明度(0〜255)。
例:50 なら、高くジャンプしたときにかなり薄くなる。 - shadowOffsetY: number
影の表示位置を親ノードの足元からどれだけ下げるか(ローカル Y オフセット)。
例:-10 なら、親の中心から 10 下に影を配置。 - smooth: boolean
影サイズ・透明度の変化をスムージングするかどうか。
true の場合、Lerp を使ってなめらかに変化。 - smoothFactor: number
スムージング係数(0〜1 推奨)。
0.1〜0.3 くらいが扱いやすい。値が大きいほど追従が速くなる。 - debugLog: boolean
true の場合、基礎的な状態(高さ・スケールなど)をログ出力してデバッグしやすくする。
TypeScriptコードの実装
import { _decorator, Component, Node, Sprite, SpriteFrame, UITransform, Vec3, Color, log, warn, clamp01 } from 'cc';
const { ccclass, property } = _decorator;
/**
* ShadowCaster
* 親ノードの足元に楕円形の影スプライトを生成し、
* 親ノードのジャンプ高さ (Y座標) に応じて縮小・透明化するコンポーネント。
*/
@ccclass('ShadowCaster')
export class ShadowCaster extends Component {
@property({
type: SpriteFrame,
tooltip: '影として使用するスプライト画像(楕円形の黒など)'
})
public shadowSpriteFrame: SpriteFrame | null = null;
@property({
tooltip: '着地しているとみなすローカルY座標(例: 0)'
})
public baseHeight: number = 0;
@property({
tooltip: '想定する最大ジャンプ高さ(これ以上は影サイズ・透明度が最小にクランプされる)'
})
public maxJumpHeight: number = 200;
@property({
tooltip: '着地時(基準高さ)の影のスケール(X,Y)'
})
public baseScale: number = 1.0;
@property({
tooltip: '最大ジャンプ高さのときの影の最小スケール'
})
public minScale: number = 0.3;
@property({
tooltip: '着地時(基準高さ)の影の不透明度(0〜255)'
})
public baseOpacity: number = 200;
@property({
tooltip: '最大ジャンプ高さのときの影の最小不透明度(0〜255)'
})
public minOpacity: number = 50;
@property({
tooltip: '親ノードの足元からどれだけ下に影を表示するか(ローカルYオフセット)'
})
public shadowOffsetY: number = -10;
@property({
tooltip: '影の変化をスムージングするかどうか'
})
public smooth: boolean = true;
@property({
tooltip: 'スムージング係数(0〜1 推奨、値が大きいほど追従が速い)'
})
public smoothFactor: number = 0.2;
@property({
tooltip: 'デバッグ用ログを出力するかどうか'
})
public debugLog: boolean = false;
// 内部用:生成した影ノードとそのコンポーネント
private _shadowNode: Node | null = null;
private _shadowSprite: Sprite | null = null;
// 内部用:現在のスケールと不透明度(スムージング用)
private _currentScale: number = 1.0;
private _currentOpacity: number = 255;
onLoad() {
// パラメータの簡易バリデーション
if (this.maxJumpHeight <= 0) {
warn('[ShadowCaster] maxJumpHeight が 0 以下です。1 に補正します。');
this.maxJumpHeight = 1;
}
if (this.baseScale <= 0) {
warn('[ShadowCaster] baseScale が 0 以下です。0.1 に補正します。');
this.baseScale = 0.1;
}
if (this.minScale < 0) {
warn('[ShadowCaster] minScale が 0 未満です。0 に補正します。');
this.minScale = 0;
}
if (this.baseOpacity < 0) this.baseOpacity = 0;
if (this.baseOpacity > 255) this.baseOpacity = 255;
if (this.minOpacity < 0) this.minOpacity = 0;
if (this.minOpacity > 255) this.minOpacity = 255;
// 影スプライトが設定されていない場合は警告を出して終了
if (!this.shadowSpriteFrame) {
warn('[ShadowCaster] shadowSpriteFrame が設定されていません。影は生成されません。');
return;
}
// 影ノードを生成(既に存在していないかチェック)
// ノード名で検索して、二重生成を防止
const existing = this.node.getChildByName('ShadowCaster_Shadow');
if (existing) {
this._shadowNode = existing;
this._shadowSprite = existing.getComponent(Sprite);
if (!this._shadowSprite) {
warn('[ShadowCaster] 既存の影ノードに Sprite コンポーネントがありません。自動で追加します。');
this._shadowSprite = existing.addComponent(Sprite);
}
} else {
this._shadowNode = new Node('ShadowCaster_Shadow');
this.node.addChild(this._shadowNode);
this._shadowSprite = this._shadowNode.addComponent(Sprite);
}
if (!this._shadowSprite) {
warn('[ShadowCaster] Sprite コンポーネントの取得・追加に失敗しました。');
return;
}
// Sprite 設定
this._shadowSprite.spriteFrame = this.shadowSpriteFrame;
this._shadowSprite.color = new Color(0, 0, 0, this.baseOpacity); // 黒+不透明度
// UITransform がなければ追加(2D UI/Canvas 上で使う想定)
let uiTrans = this._shadowNode.getComponent(UITransform);
if (!uiTrans) {
uiTrans = this._shadowNode.addComponent(UITransform);
}
// 影ノードの初期位置・スケール
this._shadowNode.setPosition(new Vec3(0, this.shadowOffsetY, 0));
this._currentScale = this.baseScale;
this._currentOpacity = this.baseOpacity;
this._applyScaleAndOpacityInstant();
if (this.debugLog) {
log('[ShadowCaster] onLoad 完了。影ノードを生成しました。');
}
}
start() {
// 特に初期化は onLoad で完了しているが、必要であればここで追加処理
if (this.debugLog) {
log('[ShadowCaster] start 呼び出し。');
}
}
update(deltaTime: number) {
// 影ノードやスプライトが用意できていない場合は何もしない
if (!this._shadowNode || !this._shadowSprite) {
return;
}
// 親ノードのローカルY座標からジャンプ高さを計算
const currentY = this.node.position.y;
const jumpHeight = currentY - this.baseHeight;
// 0〜1 に正規化した高さ(0: 着地, 1: 最大ジャンプ)
const tRaw = jumpHeight / this.maxJumpHeight;
const t = clamp01(tRaw);
// 高さに応じてスケールと不透明度を線形補間で計算
const targetScale = this._lerp(this.baseScale, this.minScale, t);
const targetOpacity = this._lerp(this.baseOpacity, this.minOpacity, t);
if (this.smooth) {
// スムージングしながら追従
const s = clamp01(this.smoothFactor);
this._currentScale = this._lerp(this._currentScale, targetScale, s);
this._currentOpacity = this._lerp(this._currentOpacity, targetOpacity, s);
} else {
// 即時反映
this._currentScale = targetScale;
this._currentOpacity = targetOpacity;
}
// 影ノードの位置(X,Zは親に追従、Yはオフセット固定)
const parentPos = this.node.position;
this._shadowNode.setPosition(new Vec3(parentPos.x, this.shadowOffsetY, parentPos.z));
// 計算したスケールと不透明度を適用
this._applyScaleAndOpacityInstant();
if (this.debugLog) {
// あまり毎フレーム大量に出したくない場合は条件を絞るなど調整してください
// ここでは簡易的に高さとスケールだけをログ
log(`[ShadowCaster] jumpHeight=${jumpHeight.toFixed(1)}, scale=${this._currentScale.toFixed(2)}, opacity=${Math.round(this._currentOpacity)}`);
}
}
/**
* スケールと不透明度を現在値で即時適用
*/
private _applyScaleAndOpacityInstant() {
if (!this._shadowNode || !this._shadowSprite) {
return;
}
this._shadowNode.setScale(new Vec3(this._currentScale, this._currentScale, 1));
const alpha = Math.round(this._currentOpacity);
// 既存の色のRGBは維持しつつAだけ変更
const c = this._shadowSprite.color;
this._shadowSprite.color = new Color(c.r, c.g, c.b, alpha);
}
/**
* 線形補間
*/
private _lerp(a: number, b: number, t: number): number {
return a + (b - a) * t;
}
}
コードのポイント解説
- onLoad
- Inspector から設定されたパラメータの簡易バリデーション(maxJumpHeight, baseScale など)を行います。
shadowSpriteFrameが未設定の場合は警告を出し、影生成をスキップします。- 子ノードに
ShadowCaster_Shadowという名前の影ノードを自動生成(既にある場合は再利用)し、Sprite と UITransform を付与します。 - 影の初期位置(
shadowOffsetY)とスケール・不透明度を設定します。
- start
- 現状ではログ出力のみですが、ゲームによっては start でさらに初期化を追加できます。
- update
- 毎フレーム、親ノードのローカル Y 座標から「ジャンプ高さ」を計算します。
jumpHeight / maxJumpHeightを 0〜1 にクランプし、これをもとにスケールと不透明度を線形補間します。smoothが true の場合はsmoothFactorを使って Lerp でなめらかに変化させます。- 影ノードの位置は、X/Z は親に合わせつつ、Y は常に
shadowOffsetYに固定することで「足元に貼り付いている」ような見た目にします。
- _applyScaleAndOpacityInstant
- 内部で保持している
_currentScaleと_currentOpacityを実際のノード・スプライトに反映します。 - 不透明度は Color の alpha 値として適用します。
- 内部で保持している
使用手順と動作確認
1. スクリプトファイルの作成
- Editor の Assets パネルで右クリックします。
- Create → TypeScript を選択します。
- 作成されたスクリプトの名前を ShadowCaster.ts に変更します。
- ダブルクリックしてエディタ(VSCode など)で開き、先ほどのコードを すべて貼り付けて保存 します。
2. 影用スプライト画像の用意
- 黒〜グレーの楕円形 PNG 画像を用意します(背景は透過)。
例:512×256 の黒い楕円など。 - その画像を Cocos Creator の Assets パネルにドラッグ&ドロップしてインポートします。
- インポートされた画像をクリックし、Inspector で SpriteFrame が生成されている ことを確認します(通常は自動)。
3. テスト用ノードの作成
- Hierarchy パネルで右クリック → Create → 2D Object → Sprite を選択し、テスト用キャラクターノードを作成します。
- 作成された Sprite ノードの名前を Player など分かりやすいものに変更します。
- Inspector で Sprite コンポーネントの SpriteFrame に、任意のキャラクター画像を設定します(なければデフォルトでも構いません)。
- Player ノードの Position.Y を一旦 0 にしておきます(baseHeight=0 の前提でテストしやすくするため)。
4. ShadowCaster コンポーネントのアタッチ
- Hierarchy で Player ノードを選択します。
- Inspector の下部にある Add Component ボタンをクリックします。
- Custom → ShadowCaster を選択し、コンポーネントを追加します。
5. Inspector でプロパティを設定
Player ノードに追加された ShadowCaster コンポーネントの各プロパティを以下のように設定してみます。
- Shadow Sprite Frame: 先ほどインポートした楕円形の影画像の SpriteFrame をドラッグ&ドロップ
- Base Height: 0(Player の Y=0 を着地とみなす)
- Max Jump Height: 200(Y=200 までジャンプする想定)
- Base Scale: 1.0(影画像そのままのサイズ)
- Min Scale: 0.3(最大ジャンプ時に 30% の大きさ)
- Base Opacity: 200(少し透けた影)
- Min Opacity: 50(高くジャンプしたときにかなり薄く)
- Shadow Offset Y: -20(Player の中心から 20 下に影を表示)
- Smooth: チェックを入れる(true)
- Smooth Factor: 0.2
- Debug Log: 最初はオフでOK。挙動確認したいときはオンに。
6. シーンの再生と動作確認
- シーンを保存します(Ctrl+S / Cmd+S)。
- エディタ上部の Play ボタンを押してゲームを再生します。
- 再生中に Hierarchy から Player ノードを選択し、Inspector の Position.Y を手動で変更してみてください。
期待される挙動:
- Y=0(Base Height と同じ)のとき:影が Base Scale(1.0)で、Base Opacity(200)の濃さで表示される。
- Y=100(Max Jump Height の半分)のとき:影は Base Scale と Min Scale の中間 の大きさになり、透明度も中間値になる。
- Y=200(Max Jump Height)以上のとき:影は Min Scale(0.3)まで小さくなり、Min Opacity(50)まで薄くなる。
- Smooth が true の場合:Y を急に変えても、影は少し遅れてなめらかに追従する。
もし影が表示されない場合は、以下を確認してください。
- shadowSpriteFrame が正しく設定されているか。
- Hierarchy で Player ノードの子に ShadowCaster_Shadow ノードが生成されているか。
- Canvas(2D UI 環境)上に配置しているか(UITransform を使うため)。
- カメラの表示範囲内に影があるか。
7. 実際のジャンプ処理と組み合わせる
実際のゲームでは、Player ノードに別のジャンプ制御スクリプトを付けて Y 座標を動かすだけで、ShadowCaster が自動的に影の大きさと濃さを調整してくれます。
ShadowCaster は 他のスクリプトに依存せず、単に node.position.y を見るだけなので、どんな移動ロジックとも組み合わせ可能です。
まとめ
- ShadowCaster は、親ノードの足元に楕円形の影を自動生成し、ジャンプ高さに応じて 影のサイズと透明度を自動調整 する汎用コンポーネントです。
- 影用ノードの生成から Sprite 設定までをコンポーネント内で完結させているため、他のカスタムスクリプトやノード構成に依存しません。
- Inspector から 影画像・基準高さ・最大ジャンプ高さ・スケール・透明度・スムージング を細かく調整でき、さまざまなキャラクターやゲームに簡単に再利用できます。
- このコンポーネントをプロジェクトに一つ用意しておけば、新しいキャラクターを追加するたびに ShadowCaster をアタッチして SpriteFrame を指定するだけで、自然な足元の影表現をすぐに導入できます。
このように、単一コンポーネントで完結する汎用スクリプトを積み重ねていくことで、ゲーム開発のスピードと保守性を大きく向上させることができます。ShadowCaster をベースに、影のぼかし具合や色を変えるバリエーションを作るのも簡単ですので、ぜひ自分のプロジェクト向けにカスタマイズしてみてください。
