Unityを触り始めたころ、「とりあえず全部 Update に書いてしまう」という実装をしがちですよね。プレイヤーの移動、アニメーションの切り替え、エフェクトの制御、UIの更新……すべてが1つのスクリプトの Update に詰め込まれていくと、あっという間に数百行の Godクラスができあがってしまいます。
そうなると、
- どのコードがどのオブジェクトの見た目を変えているのか分かりづらい
- ちょっとした演出(例えば「ふわふわ浮かせたい」)を追加したいだけなのに、既存コードに手を入れる必要が出てくる
- プレハブごとに挙動を変えたいとき、条件分岐だらけになる
といった問題が出てきます。
この記事では「ふわふわ浮かせる」というよくある演出を、1つの小さなコンポーネントに切り出します。
オブジェクトにアタッチするだけで、親オブジェクトの position.y を正弦波(Mathf.Sin)で揺らして、空中に浮いているように見せる SineWaveHover コンポーネントを実装していきましょう。
【Unity】ふわっと浮かせて存在感アップ!「SineWaveHover」コンポーネント
今回作る SineWaveHover は、
- オブジェクトの「元の高さ」を基準に
- 時間経過に応じて
Mathf.Sinで上下に揺らす - 振幅(どれくらい上下するか)と周期(どれくらいの速さで揺れるか)をインスペクターから調整できる
という、小さくまとまった「浮遊アニメーション専用」コンポーネントです。
フルコード:SineWaveHover.cs
using UnityEngine;
/// <summary>
/// 正弦波で Transform の Y 座標を上下させて、ふわふわ浮いているように見せるコンポーネント。
/// ・元の高さを基準に上下に揺らす
/// ・振幅、高さオフセット、速度などをインスペクターから調整可能
/// ・Update に全部書くのではなく、「浮遊だけ」を担当する小さなコンポーネントとして設計
/// </summary>
[DisallowMultipleComponent] // 同じコンポーネントを重ね付けしてバグるのを防止
public class SineWaveHover : MonoBehaviour
{
// --- 設定項目(インスペクターから編集) ---
[Header("浮遊設定")]
[Tooltip("上下にどれくらい揺らすか(振幅)。0.25〜0.5 くらいが扱いやすいです。")]
[SerializeField] private float amplitude = 0.25f;
[Tooltip("1秒あたりの揺れの速さ。2〜4 くらいで「ふわふわ」感が出ます。")]
[SerializeField] private float frequency = 2.0f;
[Tooltip("ベースの高さに足すオフセット。浮遊開始位置を少し持ち上げたいときに使います。")]
[SerializeField] private float heightOffset = 0.0f;
[Header("挙動オプション")]
[Tooltip("ワールド座標ではなくローカル座標の Y を揺らす場合は ON。")]
[SerializeField] private bool useLocalPosition = false;
[Tooltip("再生開始時にランダムな位相を与えて、複数オブジェクトの揺れをずらす。")]
[SerializeField] private bool randomizePhaseOnStart = true;
[Tooltip("位相のランダム範囲(-pi〜pi を基準にスケール)。1 で ±π、0.5 で ±π/2 程度。")]
[SerializeField] [Range(0f, 1f)] private float phaseRandomRange = 1.0f;
// --- 内部状態 ---
// 開始時の基準位置(Y以外も保持しておき、X/Zは変えないようにする)
private Vector3 _basePosition;
// 正弦波の位相オフセット(複数オブジェクトの揺れをずらすため)
private float _phaseOffset;
// 経過時間を自前で管理したいとき用(Time.time でも良いが、制御しやすいように)
private float _time;
private void Awake()
{
// 開始時の位置を基準位置として保存しておく
_basePosition = useLocalPosition ? transform.localPosition : transform.position;
// 位相オフセットを初期化
_phaseOffset = 0f;
}
private void Start()
{
// 複数並べたときに同じタイミングで揺れていると「機械的」に見えるので、
// オプションでランダム位相を与えて少しずらします。
if (randomizePhaseOnStart && phaseRandomRange > 0f)
{
// -π〜+π の範囲でランダムな値を取り、その範囲を phaseRandomRange でスケール
float maxPhase = Mathf.PI * phaseRandomRange;
_phaseOffset = Random.Range(-maxPhase, maxPhase);
}
}
private void Update()
{
// 経過時間を進める
// Time.time を直接使っても良いですが、将来的に一時停止やスロー演出を
// 実装したくなったとき、自前カウンタの方が制御しやすいです。
_time += Time.deltaTime;
// 正弦波の値を計算(-1〜+1 の範囲)
// sin(ωt + φ) の ω が frequency(角速度に相当)、φ が位相オフセット
float sine = Mathf.Sin(_time * frequency + _phaseOffset);
// 振幅を掛けて「どれくらい上下させるか」を決める
float hoverOffset = sine * amplitude;
// 実際に適用する高さ(基準位置 + オフセット + 正弦波)
float targetY = _basePosition.y + heightOffset + hoverOffset;
if (useLocalPosition)
{
// ローカル座標での Y のみを変更
Vector3 localPos = transform.localPosition;
localPos.y = targetY;
transform.localPosition = localPos;
}
else
{
// ワールド座標での Y のみを変更
Vector3 worldPos = transform.position;
worldPos.y = targetY;
transform.position = worldPos;
}
}
/// <summary>
/// 実行中にベース位置をリセットしたいときに呼ぶヘルパー。
/// 例えば、エディタ上で高さを調整した後に「ここを新しい基準にしたい」場合など。
/// </summary>
public void ResetBasePosition()
{
_basePosition = useLocalPosition ? transform.localPosition : transform.position;
}
/// <summary>
/// 実行中に揺れの強さを変えたいとき用のセッター。
/// </summary>
public void SetAmplitude(float newAmplitude)
{
amplitude = Mathf.Max(0f, newAmplitude);
}
/// <summary>
/// 実行中に揺れの速さを変えたいとき用のセッター。
/// </summary>
public void SetFrequency(float newFrequency)
{
frequency = Mathf.Max(0f, newFrequency);
}
}
使い方の手順
ここでは、代表的な使用例として「浮遊するアイテム」「ふわふわしている敵」「上下する足場」の3パターンを想定して手順を説明します。
-
スクリプトをプロジェクトに追加する
- Project ウィンドウで任意のフォルダ(例:
Scripts/Effects)を右クリック →Create > C# Script。 - 名前を
SineWaveHoverに変更。 - 自動生成された中身を、上記のフルコードで丸ごと置き換えて保存。
- Project ウィンドウで任意のフォルダ(例:
-
浮かせたいオブジェクトにアタッチする
例として「浮遊するアイテム」を作る場合:- Hierarchy で
Right Click > 3D Object > Cubeなどで仮のアイテムを作成。 - その GameObject に
SineWaveHoverコンポーネントをドラッグ&ドロップで追加。 - インスペクターで以下のように調整してみましょう:
Amplitude:0.25Frequency:2.0Height Offset:0.5(少し持ち上げたい場合)Use Local Position: オフ(ワールド空間で上下させる)Randomize Phase On Start: オン(複数のアイテムが同時に揺れないように)
- Hierarchy で
-
敵キャラやエフェクトにもそのまま流用する
例:ふわふわ浮いている敵キャラ- 敵キャラのプレハブ(例:
Enemy_Flying)を開く。 - ルートの GameObject、もしくは「見た目用」の子オブジェクトに
SineWaveHoverを追加。 - 敵の移動ロジックは別コンポーネント(例:
EnemyPatrol)に分けておき、このコンポーネントは「浮遊の見た目」だけを担当させるとシンプルです。 Use Local Positionをオンにしておくと、敵のルートが移動しても、そのローカル位置を基準にふわふわしてくれます。
- 敵キャラのプレハブ(例:
-
動く床(足場)として使う
例:上下する足場- 3D オブジェクト(Cube など)で足場を作成。
SineWaveHoverをアタッチ。- 振幅を少し大きめ(例:
Amplitude = 1.0)に設定し、Frequency = 1.0程度でゆっくり揺らすと、上下する足場として機能します。 - この足場に別のコンポーネントで「左右移動」や「回転」を付け足すこともできますが、それぞれを別コンポーネントに分けておくと、プレハブの組み合わせだけで多様なギミックを作れて便利です。
メリットと応用
SineWaveHover のように「浮遊演出だけ」を担当するコンポーネントを用意しておくと、プレハブ管理やレベルデザインがかなり楽になります。
- 見た目の演出をロジックから完全に切り離せる
敵の AI やプレイヤーの操作ロジックに「ふわふわさせるコード」を混ぜないので、挙動の修正・調整がしやすくなります。
例えば「この敵は浮遊させないでいいや」と思ったら、コンポーネントを外すだけで済みます。 - プレハブの再利用性が高まる
同じアイテムプレハブに対して、シーンごとに振幅や高さオフセットを変えるだけで、違う雰囲気の浮遊演出を簡単に作れます。
「このステージではゆっくり」「ボス部屋では激しく」など、レベルデザイナーがインスペクターだけで調整可能です。 - 組み合わせるだけで多様なギミックを作れる
水平方向の移動コンポーネント、回転コンポーネント、色変更コンポーネントなどと組み合わせることで、コードを書き足さなくても多様な動きを作れます。
「動く床 + 浮遊」「回転するコイン + 浮遊」など、足し算でギミックを増やしていけるのがコンポーネント指向の良さですね。
応用として、例えば「特定のタイミングで浮遊を一時停止する」機能を追加したい場合、SineWaveHover を直接いじらず、別コンポーネントから制御するのもアリです。
以下は「一定時間だけ浮遊を止める」簡単な改造案の例です。
using UnityEngine;
/// <summary>
/// SineWaveHover を一時停止・再開する簡単な制御コンポーネントの例。
/// 同じ GameObject に SineWaveHover が付いている前提です。
/// </summary>
[RequireComponent(typeof(SineWaveHover))]
public class SineWaveHoverPauser : MonoBehaviour
{
[SerializeField] private float pauseDuration = 1.0f;
private SineWaveHover _hover;
private bool _isPaused;
private float _pauseTimer;
private void Awake()
{
_hover = GetComponent<SineWaveHover>();
}
private void Update()
{
if (!_isPaused) return;
_pauseTimer += Time.deltaTime;
if (_pauseTimer >= pauseDuration)
{
ResumeHover();
}
}
/// <summary>
/// 外部から呼び出して浮遊を一時停止させる。
/// 実際の停止方法は、frequency を 0 にするだけのシンプルなもの。
/// </summary>
public void PauseHover()
{
if (_isPaused) return;
_isPaused = true;
_pauseTimer = 0f;
_hover.SetFrequency(0f);
}
private void ResumeHover()
{
_isPaused = false;
// 再開時の周波数はお好みで。ここでは 2.0 を固定値で戻しています。
_hover.SetFrequency(2.0f);
}
}
SineWaveHover 自体はあくまで「正弦波で揺らすだけ」の小さな責務にとどめておき、
停止や同期、イベント連動などの複雑な制御は、別コンポーネントで組み合わせていくと、結果として保守しやすいプロジェクトになります。
Update に全部を書きがちな処理を、こうした小さなコンポーネントに分割していく習慣をつけていきましょう。
