
マイケル
みなさんこんばんは!
マイケルです!!
マイケルです!!

エレキベア
クマ〜〜〜〜

マイケル
今こんなシューティングゲームを作ってるんだけど、
何が足りないか分かるかい?
何が足りないか分かるかい?


エレキベア
いろいろ足りない気がするクマ

マイケル
そう、迫力さ・・・!!
このゲームには迫力が足りないんだ!
このゲームには迫力が足りないんだ!

エレキベア
(言ってないクマ・・・)

エレキベア
でも確かに何か物足りないクマね
敵を倒した時の振動 とかあれば迫力出るのクマかね〜〜
敵を倒した時の振動 とかあれば迫力出るのクマかね〜〜

マイケル
そ、それだーーー!!!

エレキベア
(まさかの的中クマ・・・)
揺らす方法3選

マイケル
というわけで今回は、Unityで物を揺らす方法
についていくつか試していくよ!
についていくつか試していくよ!

エレキベア
どうやって実装するのがいいのクマかね〜〜

マイケル
調べた感じ、
・乱数を使用する方法
・パーリンノイズを使用する方法
・DOTweenを使用する方法
の3つが有名みたいだから、実装して比較してみます!
・乱数を使用する方法
・パーリンノイズを使用する方法
・DOTweenを使用する方法
の3つが有名みたいだから、実装して比較してみます!

マイケル
ちなみに今回のサンプルもGitHubに上げてるので
よかったらこちらも見てみてくださいね
よかったらこちらも見てみてくださいね

エレキベア
やったるクマ〜〜〜〜
1. 乱数を使用した実装

マイケル
まずは乱数を使用した実装から!
こちらは Random.Range() で乱数を取得して揺らす方法になります。
こちらは Random.Range() で乱数を取得して揺らす方法になります。
using UnityEngine;
using Random = UnityEngine.Random;
/// <summary>
/// ランダム値を使用した揺れ
/// </summary>
public class ShakeByRandom : MonoBehaviour
{
/// <summary>
/// 揺れ情報
/// </summary>
private struct ShakeInfo
{
public ShakeInfo(float duration, float strength, float vibrato)
{
Duration = duration;
Strength = strength;
Vibrato = vibrato;
}
public float Duration { get; } // 時間
public float Strength { get; } // 揺れの強さ
public float Vibrato { get; } // どのくらい振動するか
}
private ShakeInfo _shakeInfo;
private Vector3 _initPosition; // 初期位置
private bool _isDoShake; // 揺れ実行中か?
private float _totalShakeTime; // 揺れ経過時間
private void Start()
{
// 初期位置を保持
_initPosition = gameObject.transform.position;
}
private void Update()
{
if (!_isDoShake) return;
// 揺れ位置情報更新
gameObject.transform.position = UpdateShakePosition(
gameObject.transform.position,
_shakeInfo,
_totalShakeTime,
_initPosition);
// duration分の時間が経過したら揺らすのを止める
_totalShakeTime += Time.deltaTime;
if (_totalShakeTime >= _shakeInfo.Duration)
{
_isDoShake = false;
_totalShakeTime = 0.0f;
// 初期位置に戻す
gameObject.transform.position = _initPosition;
}
}
/// <summary>
/// 更新後の揺れ位置を取得
/// </summary>
/// <param name="currentPosition">現在の位置</param>
/// <param name="shakeInfo">揺れ情報</param>
/// <param name="totalTime">経過時間</param>
/// <param name="initPosition">初期位置</param>
/// <returns>更新後の揺れ位置</returns>>
private Vector3 UpdateShakePosition(Vector3 currentPosition, ShakeInfo shakeInfo, float totalTime, Vector3 initPosition)
{
// -strength ~ strength の値で揺れの強さを取得
var strength = shakeInfo.Strength;
var randomX = Random.Range(-1.0f * strength, strength);
var randomY = Random.Range(-1.0f * strength, strength);
// 現在の位置に加える
var position = currentPosition;
position.x += randomX;
position.y += randomY;
// 初期位置-vibrato ~ 初期位置+vibrato の間に収める
var vibrato = shakeInfo.Vibrato;
var ratio = 1.0f - totalTime / shakeInfo.Duration;
vibrato *= ratio; // フェードアウトさせるため、経過時間により揺れの量を減衰
position.x = Mathf.Clamp(position.x, initPosition.x - vibrato, initPosition.x + vibrato);
position.y = Mathf.Clamp(position.y, initPosition.y - vibrato, initPosition.y + vibrato);
return position;
}
/// <summary>
/// 揺れ開始
/// </summary>
/// <param name="duration">時間</param>
/// <param name="strength">揺れの強さ</param>
/// <param name="vibrato">どのくらい振動するか</param>
public void StartShake(float duration, float strength, float vibrato)
{
// 揺れ情報を設定して開始
_shakeInfo = new ShakeInfo(duration, strength, vibrato);
_isDoShake = true;
_totalShakeTime = 0.0f;
}
}

エレキベア
意外と長いクマね

マイケル
簡単に内容について解説するよ!

マイケル
まず揺らす情報として
・時間(duration)
・揺れの強さ(strength)
・どのくらい振動するか(vibrato)
を構造体として定義します。
・時間(duration)
・揺れの強さ(strength)
・どのくらい振動するか(vibrato)
を構造体として定義します。
/// <summary>
/// 揺れ情報
/// </summary>
private struct ShakeInfo
{
public ShakeInfo(float duration, float strength, float vibrato)
{
Duration = duration;
Strength = strength;
Vibrato = vibrato;
}
public float Duration { get; } // 時間
public float Strength { get; } // 揺れの強さ
public float Vibrato { get; } // どのくらい振動するか
}
private ShakeInfo _shakeInfo;
↑揺らす情報の構造体定義
マイケル
そしてこの情報を元に位置を更新するのが下記処理になります。
現在の位置に乱数を加える 形で位置を調整しています。
現在の位置に乱数を加える 形で位置を調整しています。
/// <summary>
/// 更新後の揺れ位置を取得
/// </summary>
/// <param name="currentPosition">現在の位置</param>
/// <param name="shakeInfo">揺れ情報</param>
/// <param name="totalTime">経過時間</param>
/// <param name="initPosition">初期位置</param>
/// <returns>更新後の揺れ位置</returns>>
private Vector3 UpdateShakePosition(Vector3 currentPosition, ShakeInfo shakeInfo, float totalTime, Vector3 initPosition)
{
// -strength ~ strength の値で揺れの強さを取得
var strength = shakeInfo.Strength;
var randomX = Random.Range(-1.0f * strength, strength);
var randomY = Random.Range(-1.0f * strength, strength);
// 現在の位置に加える
var position = currentPosition;
position.x += randomX;
position.y += randomY;
// 初期位置-vibrato ~ 初期位置+vibrato の間に収める
var vibrato = shakeInfo.Vibrato;
var ratio = 1.0f - totalTime / shakeInfo.Duration;
vibrato *= ratio; // フェードアウトさせるため、経過時間により揺れの量を減衰
position.x = Mathf.Clamp(position.x, initPosition.x - vibrato, initPosition.x + vibrato);
position.y = Mathf.Clamp(position.y, initPosition.y - vibrato, initPosition.y + vibrato);
return position;
}
↑位置を調整する
マイケル
徐々に振動を少なくするため、経過時間により減衰させるようにもしています。

マイケル
あとはこの処理をUpdate文の中で呼ぶだけ!
揺らすのが終わったら初期位置に戻す必要がある 点にはご注意ください。
揺らすのが終わったら初期位置に戻す必要がある 点にはご注意ください。
private Vector3 _initPosition; // 初期位置
private bool _isDoShake; // 揺れ実行中か?
private float _totalShakeTime; // 揺れ経過時間
private void Start()
{
// 初期位置を保持
_initPosition = gameObject.transform.position;
}
private void Update()
{
if (!_isDoShake) return;
// 揺れ位置情報更新
gameObject.transform.position = UpdateShakePosition(
gameObject.transform.position,
_shakeInfo,
_totalShakeTime,
_initPosition);
// duration分の時間が経過したら揺らすのを止める
_totalShakeTime += Time.deltaTime;
if (_totalShakeTime >= _shakeInfo.Duration)
{
_isDoShake = false;
_totalShakeTime = 0.0f;
// 初期位置に戻す
gameObject.transform.position = _initPosition;
}
}
// ↓揺らす際にはこの関数を実行
/// <summary>
/// 揺れ開始
/// </summary>
/// <param name="duration">時間</param>
/// <param name="strength">揺れの強さ</param>
/// <param name="vibrato">どのくらい振動するか</param>
public void StartShake(float duration, float strength, float vibrato)
{
// 揺れ情報を設定して開始
_shakeInfo = new ShakeInfo(duration, strength, vibrato);
_isDoShake = true;
_totalShakeTime = 0.0f;
}
}
↑Update内で呼び出す

マイケル
実行するとこのように揺らすことができました!

エレキベア
ちゃんと揺れたクマ〜〜〜〜
2. パーリンノイズを使用した実装

マイケル
次にパーリンノイズ値を使用した実装について!
こちらはChinemachineでのカメラ揺れでも使われている方法です。
こちらはChinemachineでのカメラ揺れでも使われている方法です。

エレキベア
パーリンノイズって何クマ?

マイケル
パーリンノイズは 完全にランダムでなく入力値によって徐々に変化する値
を生成するのに適しているんだ!
Unityに Mathf.PerlinNoise で搭載されていて、XYの入力値から値を生成することができるよ。
を生成するのに適しているんだ!
Unityに Mathf.PerlinNoise で搭載されていて、XYの入力値から値を生成することができるよ。
Mathf.PerlinNoise – Unityスクリプトリファレンス


エレキベア
完全なランダムではなくなるクマね

マイケル
揺らす処理の他にも、地形生成やざらざら質感の表現 等にもよく用いられているんだ!
これを使えば乱数よりは自然な揺れを作ることができそうだ。
これを使えば乱数よりは自然な揺れを作ることができそうだ。

マイケル
これを用いた実装は以下になります。
using System;
using UnityEngine;
using Random = UnityEngine.Random;
/// <summary>
/// パーリンノイズ値を使用した揺れ
/// </summary>
public class ShakeByPerlinNoise : MonoBehaviour
{
/// <summary>
/// 揺れ情報
/// </summary>
private struct ShakeInfo
{
public ShakeInfo(float duration, float strength, float vibrato, Vector2 randomOffset)
{
Duration = duration;
Strength = strength;
Vibrato = vibrato;
RandomOffset = randomOffset;
}
public float Duration { get; } // 時間
public float Strength { get; } // 揺れの強さ
public float Vibrato { get; } // どのくらい振動するか
public Vector2 RandomOffset { get; } // ランダムオフセット値
}
private ShakeInfo _shakeInfo;
private Vector3 _initPosition; // 初期位置
private bool _isDoShake; // 揺れ実行中か?
private float _totalShakeTime; // 揺れ経過時間
private void Start()
{
// 初期位置を保持
_initPosition = gameObject.transform.position;
}
private void Update()
{
if (!_isDoShake) return;
// 揺れ位置情報更新
gameObject.transform.position = GetUpdateShakePosition(
_shakeInfo,
_totalShakeTime,
_initPosition);
// duration分の時間が経過したら揺らすのを止める
_totalShakeTime += Time.deltaTime;
if (_totalShakeTime >= _shakeInfo.Duration)
{
_isDoShake = false;
_totalShakeTime = 0.0f;
// 初期位置に戻す
gameObject.transform.position = _initPosition;
}
}
/// <summary>
/// 更新後の揺れ位置を取得
/// </summary>
/// <param name="shakeInfo">揺れ情報</param>
/// <param name="totalTime">経過時間</param>
/// <param name="initPosition">初期位置</param>
/// <returns>更新後の揺れ位置</returns>
private Vector3 GetUpdateShakePosition(ShakeInfo shakeInfo, float totalTime, Vector3 initPosition)
{
// パーリンノイズ値(-1.0〜1.0)を取得
var strength = shakeInfo.Strength;
var randomOffset = shakeInfo.RandomOffset;
var randomX = GetPerlinNoiseValue(randomOffset.x, strength, totalTime);
var randomY = GetPerlinNoiseValue(randomOffset.y, strength, totalTime);
// -strength ~ strength の値に変換
randomX *= strength;
randomY *= strength;
// -vibrato ~ vibrato の値に変換
var vibrato = shakeInfo.Vibrato;
var ratio = 1.0f - totalTime / shakeInfo.Duration;
vibrato *= ratio; // フェードアウトさせるため、経過時間により揺れの量を減衰
randomX = Mathf.Clamp(randomX, -vibrato, vibrato);
randomY = Mathf.Clamp(randomY, -vibrato, vibrato);
// 初期位置に加える形で設定する
var position = initPosition;
position.x += randomX;
position.y += randomY;
return position;
}
/// <summary>
/// パーリンノイズ値を取得
/// </summary>
/// <param name="offset">オフセット値</param>
/// <param name="speed">速度</param>
/// <param name="time">時間</param>
/// <returns>パーリンノイズ値(-1.0〜1.0)</returns>
private float GetPerlinNoiseValue(float offset, float speed, float time)
{
// パーリンノイズ値を取得する
// X: オフセット値 + 速度 * 時間
// Y: 0.0固定
var perlinNoise = Mathf.PerlinNoise(offset + speed * time, 0.0f);
// 0.0〜1.0 -> -1.0〜1.0に変換して返却
return (perlinNoise - 0.5f) * 2.0f;
}
/// <summary>
/// 揺れ開始
/// </summary>
/// <param name="duration">時間</param>
/// <param name="strength">揺れの強さ</param>
/// <param name="vibrato">どのくらい振動するか</param>
public void StartShake(float duration, float strength, float vibrato)
{
// 揺れ情報を設定して開始
var randomOffset = new Vector2(Random.Range(0.0f, 100.0f), Random.Range(0.0f, 100.0f)); // ランダム値はとりあえず0〜100で設定
_shakeInfo = new ShakeInfo(duration, strength, vibrato, randomOffset);
_isDoShake = true;
_totalShakeTime = 0.0f;
}
}

マイケル
先ほどと全体的な構成は変わりありませんが、
主に下記の部分に違いがあります。
主に下記の部分に違いがあります。
/// <summary>
/// 更新後の揺れ位置を取得
/// </summary>
/// <param name="shakeInfo">揺れ情報</param>
/// <param name="totalTime">経過時間</param>
/// <param name="initPosition">初期位置</param>
/// <returns>更新後の揺れ位置</returns>
private Vector3 GetUpdateShakePosition(ShakeInfo shakeInfo, float totalTime, Vector3 initPosition)
{
// パーリンノイズ値(-1.0〜1.0)を取得
var strength = shakeInfo.Strength;
var randomOffset = shakeInfo.RandomOffset;
var randomX = GetPerlinNoiseValue(randomOffset.x, strength, totalTime);
var randomY = GetPerlinNoiseValue(randomOffset.y, strength, totalTime);
// -strength ~ strength の値に変換
randomX *= strength;
randomY *= strength;
// -vibrato ~ vibrato の値に変換
var vibrato = shakeInfo.Vibrato;
var ratio = 1.0f - totalTime / shakeInfo.Duration;
vibrato *= ratio; // フェードアウトさせるため、経過時間により揺れの量を減衰
randomX = Mathf.Clamp(randomX, -vibrato, vibrato);
randomY = Mathf.Clamp(randomY, -vibrato, vibrato);
// 初期位置に加える形で設定する
var position = initPosition;
position.x += randomX;
position.y += randomY;
return position;
}
/// <summary>
/// パーリンノイズ値を取得
/// </summary>
/// <param name="offset">オフセット値</param>
/// <param name="speed">速度</param>
/// <param name="time">時間</param>
/// <returns>パーリンノイズ値(-1.0〜1.0)</returns>
private float GetPerlinNoiseValue(float offset, float speed, float time)
{
// パーリンノイズ値を取得する
// X: オフセット値 + 速度 * 時間
// Y: 0.0固定
var perlinNoise = Mathf.PerlinNoise(offset + speed * time, 0.0f);
// 0.0〜1.0 -> -1.0〜1.0に変換して返却
return (perlinNoise - 0.5f) * 2.0f;
}
↑パーリンノイズ値を使用した位置更新
マイケル
「速度*時間」をパーリンノイズの入力値とすることで変動させています。
また、ノイズ値を-1.0〜1.0の値に変換するため、初期位置に加える形 で設定することができます。
また、ノイズ値を-1.0〜1.0の値に変換するため、初期位置に加える形 で設定することができます。

エレキベア
これは期待できそうクマね

マイケル
また揺れの情報としても、新たにランダムなオフセット値を追加しています。
/// <summary>
/// パーリンノイズ値を使用した揺れ
/// </summary>
public class ShakeByPerlinNoise : MonoBehaviour
{
/// <summary>
/// 揺れ情報
/// </summary>
private struct ShakeInfo
{
public ShakeInfo(float duration, float strength, float vibrato, Vector2 randomOffset)
{
Duration = duration;
Strength = strength;
Vibrato = vibrato;
RandomOffset = randomOffset;
}
public float Duration { get; } // 時間
public float Strength { get; } // 揺れの強さ
public float Vibrato { get; } // どのくらい振動するか
public Vector2 RandomOffset { get; } // ランダムオフセット値
}
private ShakeInfo _shakeInfo;
↑オフセット値の追加
マイケル
実行結果は下記の通り!


エレキベア
さっきより自然な気がするクマ〜〜〜
3. DOTWeenを使用した実装

マイケル
最後はDOTweenを使用した揺れの実装について!

エレキベア
DOTweenは有名なアセットクマね

マイケル
揺れの処理は用意されていて、DOShakePositionを使用することで実装できます!
アセット版には有償版もありますが、無償版で使用することができます。
アセット版には有償版もありますが、無償版で使用することができます。

マイケル
実装は下記の通り!
using DG.Tweening;
using UnityEngine;
/// <summary>
/// DOTweenを使用した揺れ
/// </summary>
public class ShakeByDOTween : MonoBehaviour
{
private Tweener _shakeTweener;
private Vector3 _initPosition;
private void Start()
{
// 初期位置を保持
_initPosition = transform.position;
}
/// <summary>
/// 揺れ開始
/// </summary>
/// <param name="duration">時間</param>
/// <param name="strength">揺れの強さ</param>
/// <param name="vibrato">どのくらい振動するか</param>
/// <param name="randomness">ランダム度合(0〜180)</param>
/// <param name="fadeOut">フェードアウトするか</param>
public void StartShake(float duration, float strength, int vibrato, float randomness, bool fadeOut)
{
// 前回の処理が残っていれば停止して初期位置に戻す
if (_shakeTweener != null)
{
_shakeTweener.Kill();
gameObject.transform.position = _initPosition;
}
// 揺れ開始
_shakeTweener = gameObject.transform.DOShakePosition(duration, strength, vibrato, randomness, fadeOut);
}
}

エレキベア
簡単クマ〜〜〜
今までの苦労はいかに・・・
今までの苦労はいかに・・・

マイケル
これまで定義していた時間、揺れの強さ、どのくらい振動するか
の他に、ランダム度合い、フェードアウトするかも指定することができます!
の他に、ランダム度合い、フェードアウトするかも指定することができます!

エレキベア
多機能すぎて勝てないクマ・・・

マイケル
ちなみに、揺らされる度に前回の処理を停止させないと、
連打されたら位置がどんどん変わってしまう ため注意です!
連打されたら位置がどんどん変わってしまう ため注意です!

マイケル
実行結果は下記の通り!


エレキベア
揺れもしっかりしてるクマ〜〜〜
揺れ方比較

マイケル
それでは実装した3種類の揺れを比較してみましょう!
下記のような4パターンのテスト実行スクリプトを書いて動かしてみます!
下記のような4パターンのテスト実行スクリプトを書いて動かしてみます!
using UnityEngine;
/// <summary>
/// 揺れテスト用
/// </summary>
public class ShakeTest : MonoBehaviour
{
[SerializeField] private ShakeByRandom shakeByRandom;
[SerializeField] private ShakeByPerlinNoise shakeByPerlinNoise;
[SerializeField] private ShakeByDOTween shakeByDoTween;
/// <summary>
/// 短い揺れテスト
/// </summary>
public void PushButton1()
{
var duration = 1.0f;
var strength = 30.0f;
var vibrato = 30.0f;
shakeByRandom.StartShake(duration, strength, vibrato);
shakeByPerlinNoise.StartShake(duration, strength, vibrato);
shakeByDoTween.StartShake(duration, strength, (int) vibrato, 90.0f, true);
}
/// <summary>
/// 長い揺れテスト
/// </summary>
public void PushButton2()
{
var duration = 3.0f;
var strength = 30.0f;
var vibrato = 30.0f;
shakeByRandom.StartShake(duration, strength, vibrato);
shakeByPerlinNoise.StartShake(duration, strength, vibrato);
shakeByDoTween.StartShake(duration, strength, (int) vibrato, 90.0f, true);
}
/// <summary>
/// 早い振動テスト
/// </summary>
public void PushButton3()
{
var duration = 3.0f;
var strength = 30.0f;
var vibrato = 100.0f;
shakeByRandom.StartShake(duration, strength, vibrato);
shakeByPerlinNoise.StartShake(duration, strength, vibrato);
shakeByDoTween.StartShake(duration, strength, (int) vibrato, 90.0f, true);
}
/// <summary>
/// 強い揺れテスト
/// </summary>
public void PushButton4()
{
var duration = 3.0f;
var strength = 100.0f;
var vibrato = 30.0f;
shakeByRandom.StartShake(duration, strength, vibrato);
shakeByPerlinNoise.StartShake(duration, strength, vibrato);
shakeByDoTween.StartShake(duration, strength, (int) vibrato, 90.0f, true);
}
}
↑テスト4パターン
エレキベア
楽しみクマ〜〜〜

マイケル
ちなみにDOTween使用については、
「ランダム度合い」は90、「フェードアウトするか」はtrueに設定してあります。
「ランダム度合い」は90、「フェードアウトするか」はtrueに設定してあります。
短めの揺れ

マイケル
まずは短めの揺れ(1秒)を実行!


エレキベア
動きは違えどさほど大きな違いはないように見えるクマね
長めの揺れ

マイケル
次に長めの揺れ(3秒)!


エレキベア
違いが大きくなってきたクマね
ランダム使用のはかなりカクカクしてるクマ
ランダム使用のはかなりカクカクしてるクマ
大きめの振動

マイケル
時間はそのままに振動を大きくしてみます。


エレキベア
ランダムのはガックガククマ
そしてDOTweenのは動きが落ち着いてるように見えるクマね
そしてDOTweenのは動きが落ち着いてるように見えるクマね
強めの揺れ

マイケル
最後に揺れを強くしてみます!


エレキベア
並べてみるとDOTweenのはやっぱりどこか落ち着いている感じはあるクマね
パーリンノイズの方が衝撃感はありそうクマ
パーリンノイズの方が衝撃感はありそうクマ
まとめ

マイケル
というわけで、それぞれの使用シーンをまとめると
下記のようになると思います!
下記のようになると思います!
[揺れ使用シーンまとめ]
・乱数を使用
→ガクガクな揺れにしたい時
・パーリンノイズを使用
→衝撃感のある揺れにしたい時
・DOTweenを使用
→落ち着いた感じの揺れにしたい時

マイケル
とはいえ、細かい実装や値によっても変わってくると思うので
この限りではありません・・・!
みなさん自分に合った揺れを見つけていきましょう!
この限りではありません・・・!
みなさん自分に合った揺れを見つけていきましょう!

エレキベア
楽しかったクマ〜〜〜〜
おわりに

マイケル
じゃーーん!!
早速ゲームに取り入れてみたぞ!!
早速ゲームに取り入れてみたぞ!!


マイケル
今回はUI部分にパーリンノイズを採用しました!
これだけでもだいぶ迫力が出たんじゃないかな!
これだけでもだいぶ迫力が出たんじゃないかな!

エレキベア
クオリティが高くなった気がするクマ〜〜〜

マイケル
今後パーリンノイズもDOTweenも、
それぞれ取り上げてみるのも面白そうだね!
また触ってみよう!!
それぞれ取り上げてみるのも面白そうだね!
また触ってみよう!!

マイケル
というわけで今日はこの辺で!
アデューー!!
アデューー!!

エレキベア
クマ〜〜〜〜
【Unity】揺らす方法3選!それぞれの揺れ方を比較してみた 〜完〜
[参考]
Mathf.PerlinNoise – Unityスクリプトリファレンス
DOTween – Documentation
【Unity】パーリンノイズで様々な物を揺らす | ねこじゃらシティ
DOTween でオブジェクトまたは画面全体をゆらす | ねぎたまらぼ
コメント