0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Unity】DOTweenを使ってオブジェクトをいい感じに上下(単振動)させる

Last updated at Posted at 2025-12-03

この記事は、K3 Advent Calendar 2025 4日目の記事になります。
Qiitaで記事を書くのは初めてなので、うまくできてるといいな~!

この記事で説明すること

この記事は、オブジェクトを以下の動画のように上下させたい人に向けたものです。

また、この記事は ある程度UnityやDOTweenを触った人向け になります。

宣伝!

今回紹介したスクリプトはゲーム「アリアドネの対価」の動くリフトに採用しております。

私はリフトを含む一部プログラムとギミックデザインを担当しました。
そのうちWebGL版が上がると思うので、ぜひ遊んでみてください!
追記(2025/12/04)
上がりました!こちらから遊べますよ~

あわよくば「応援する!!」を押してほしいな!

基本編(とりあえず動かしたい人向け)

ここから本編になります~

スクリプトをアタッチする前にやること

  • スクリプトをアタッチするオブジェクトには、 RigidBodyコンポーネント をアタッチする
  • アタッチするオブジェクトの 重力をオフ にする
  • アタッチするオブジェクトの 「キネマティックにする」をオン にする

スクリプト

SimpleHarmonicMotion.cs
using UnityEngine;
using DG.Tweening;

/// <summary>
/// 単振動のプログラム(初期位相なし)
/// </summary>

public class SimpleHarmonicMotion : MonoBehaviour
{
    // アニメーション開始時の座標
    // この座標をアニメーションの位置の中心とする
    private Vector3 posStart;
    // アニメーション中の現在の座標
    private Vector3 posCurrent;
    // RigidBody2Dコンポーネント
    private Rigidbody rigidBody;
    // アニメーション
    private Tween myAnimation;
    [Header("アニメーションの1周期にかかる時間(秒)")]
    // アニメーションの1周期にかかる時間(単位は秒)
    [SerializeField] private float duration;
    [Header("アニメーションで動く幅")]
    // アニメーションで動く幅(振幅)
    [SerializeField] private float delta;

    void Awake()
    {
        // Rigidbodyコンポーネントを取得
        rigidBody = GetComponent<Rigidbody>();
        // Rigidbodyコンポーネントがアタッチされているかチェック
        if (rigidBody == null)
        {
            Debug.LogError("RigidBody cannot found.");
        }
        // アタッチされている場合
        else
        {
            // スタート時の座標を取得
            posStart = rigidBody.transform.position;
        }

    }

    void Start()
    {
        // アニメーションを設定------------------------------------------------
        myAnimation = DOVirtual.Float(0, 2 * Mathf.PI, duration, value =>
        {
            // リフトを上下に動かすアニメーション
            // Rigidbodyを使って動かすことで、当たり判定を維持しながら動かせる
            posCurrent = posStart;
            posCurrent.y = posCurrent.y + delta * Mathf.Sin(value);
            rigidBody.MovePosition(posCurrent);
        })
        .SetLoops(-1, LoopType.Restart) // 無限ループを設定
        .SetEase(Ease.Linear);  // イージングはリニア(線形)にする
        // -------------------------------------------------------------------
        // アニメーションを再生
        myAnimation.Play();
    }

}

解説は後ほど~

スクリプトをアタッチした後にやること

  • スクリプトを先ほど準備したオブジェクトにアタッチ
  • インスペクタ上で 下の画像にあるパラメータ2つを0でない正の数 に設定
                                              
image.png
幅(Delta)は振幅なので、オブジェクトはDeltaから-Deltaまで動きます

ここまで設定できていれば、オブジェクトがいい感じに上下してくれます。

RigidBodyを使って動かしているので、当たり判定を持ったまま動かすことも可能です。

スクリプトの解説

気になる人向け

Awake()内

ここはUnityをある程度触っている人なら分かるはずなのでサラッと説明します。

    void Awake()
    {
        // Rigidbodyコンポーネントを取得
        rigidBody = GetComponent<Rigidbody>();
        // Rigidbodyコンポーネントがアタッチされているかチェック
        if (rigidBody == null)
        {
            Debug.LogError("RigidBody cannot found.");
        }
        // アタッチされている場合
        else
        {
            // スタート時の座標を取得
            posStart = rigidBody.transform.position;
        }

    }

やってることはこの2つです。

  • RigidBodyコンポーネントを取得
  • オブジェクトの位置を取得(この値を基準に動かします)

Start()内

ここが 本題

    void Start()
    {
        // アニメーションを設定------------------------------------------------
        myAnimation = DOVirtual.Float(0, 2 * Mathf.PI, duration, value =>
        {
            // リフトを上下に動かすアニメーション
            // Rigidbodyを使って動かすことで、当たり判定を維持しながら動かせる
            posCurrent = posStart;
            posCurrent.y = posCurrent.y + delta * Mathf.Sin(value);
            rigidBody.MovePosition(posCurrent);
        })
        .SetLoops(-1, LoopType.Restart) // 無限ループを設定
        .SetEase(Ease.Linear);  // イージングはリニア(線形)にする
        // -------------------------------------------------------------------
        // アニメーションを再生
        myAnimation.Play();
    }

}

DOVirtual.Float()というメソッドを使っています。
このメソッドは以下のようなパラメータを持ちます。

DOVirtual.Float(
    float from, 
    float to, 
    float duration, 
    TweenCallback<float> onVirtualUpdate)

ざっくり説明すると、 duration秒かけてfromからtoへ変化する値をonVirtualUpdate内で扱うのがこのメソッドです 。
上記の説明を踏まえた上で、もう一度スクリプトを見てみましょう。
from = 0、to = 2πになっているのが分かります。
つまり、今回のスクリプトでは、 duration秒かけて0から2πまで変化する値 を扱っています。

では、どのように使っているのかというと、

            posCurrent = posStart;
            posCurrent.y = posCurrent.y + delta * Mathf.Sin(value);
            rigidBody.MovePosition(posCurrent);

この部分で、先ほど説明した「fromからtoまで変化する値」を使っています。
ここで出てくるvalueという変数が、「fromからtoまで変化する値」です。
やっていることは以下の2つです。

  1. Y座標の値をsin(value)に沿って変更
  2. 変更した座標の値をRigidBody経由で反映

以上の手順によって、オブジェクトをsin関数に従って上下させる=単振動させることができました!

応用編(スタート位置を変えられるようにする)

スタート位置 (分かる人向けに言うなら 初期位相を変更できるオプション を付けます。
先ほどと同じオブジェクトにスクリプトをアタッチする想定で話を進めます。

スクリプト

先ほどのスクリプトから一部を変更したものです。

InitialPhase.cs
using UnityEngine;
using DG.Tweening;

/// <summary>
/// 単振動のプログラム(初期位相あり)
/// </summary>

public class InitialPhase : MonoBehaviour
{
    // アニメーション開始時の座標
    // この座標をアニメーションの位置の中心とする
    private Vector3 posStart;
    // アニメーション中の現在の座標
    private Vector3 posCurrent;
    // RigidBody2Dコンポーネント
    private Rigidbody rigidBody;
    // アニメーション
    private Tween myAnimation;
    [Header("アニメーションの1周期にかかる時間(秒)")]
    // アニメーションの1周期にかかる時間(単位は秒)
    [SerializeField] private float duration;
    [Header("アニメーションで動く幅")]
    // アニメーションで動く幅(振幅)
    [SerializeField] private float delta;
    [Header("初期位相(単位は度)")]
    // 初期位相(単位は度数)
    [SerializeField] private float initialPhaseDeg;

    void Awake()
    {
        // Rigidbodyコンポーネントを取得
        rigidBody = GetComponent<Rigidbody>();
        // Rigidbodyコンポーネントがアタッチされているかチェック
        if (rigidBody == null)
        {
            Debug.LogError("RigidBody cannot found.");
        }
        // アタッチされている場合
        else
        {
            // スタート時の座標を取得
            posStart = rigidBody.transform.position;
            // 初期位相に合わせてオブジェクトの位置を動かす
            Vector3 posTemp = rigidBody.transform.position;
            posTemp.y += delta * Mathf.Sin(initialPhaseDeg / 180 * Mathf.PI);
            rigidBody.transform.position = posTemp;
        }

    }

    void Start()
    {
        // アニメーションを設定------------------------------------------------
        myAnimation = DOVirtual.Float(0, 2 * Mathf.PI, duration, value =>
        {
            // リフトを上下に動かすアニメーション
            // Rigidbody2Dを使って動かすことで、当たり判定を維持しながら動かせる
            posCurrent = posStart;
            posCurrent.y = posCurrent.y + delta * Mathf.Sin(value + initialPhaseDeg / 180 * Mathf.PI);
            rigidBody.MovePosition(posCurrent);
        })
        .SetLoops(-1, LoopType.Restart) // 無限ループを設定
        .SetEase(Ease.Linear);  // イージングはリニア(線形)にする
        // -------------------------------------------------------------------
        // アニメーションを再生
        myAnimation.Play();
    }
}

追加されたパラメータについて

スクリプトをアタッチすると、先ほどと比較してパラメータが1つ増えているのが分かると思います。

                                              
image.png
初期位相(スタート位置)を表すInitial Phase Degというパラメータが増えています。

このパラメータを操作することでスタート位置を変えることができます。
試しに、180に設定して動かしてみましょう。
うまくいけば、動き始める時に上へ動いてから下へ向かっていたのが、下へ動いてから上に向かうように変わるはずです。
比較用動画はこちら↓

Initial Phase Degの単位は度になっています。
例えば90に設定すればてっぺんから動き始めますし、270に設定すれば1番下から動き始めます。
あなたのよろしいように、変えてみてください。

スクリプトの解説

気になる人向け

Awake()内

スクリプトが少し追加されています。

    void Awake()
    {
        // Rigidbodyコンポーネントを取得
        rigidBody = GetComponent<Rigidbody>();
        // Rigidbodyコンポーネントがアタッチされているかチェック
        if (rigidBody == null)
        {
            Debug.LogError("RigidBody cannot found.");
        }
        // アタッチされている場合
        else
        {
            // スタート時の座標を取得
            posStart = rigidBody.transform.position;
            // 初期位相に合わせてオブジェクトの位置を動かす
            Vector3 posTemp = rigidBody.transform.position;
            posTemp.y += delta * Mathf.Sin(initialPhaseDeg / 180 * Mathf.PI);
            rigidBody.transform.position = posTemp;
        }

    }

追加したのはこの部分です。

            // 初期位相に合わせてオブジェクトの位置を動かす
            Vector3 posTemp = rigidBody.transform.position;
            posTemp.y += delta * Mathf.Sin(initialPhaseDeg / 180 * Mathf.PI);
            rigidBody.transform.position = posTemp;

スタート位置をズラすために、オブジェクトの位置をinitialPhaseDegの分だけズラしています。

start()内

スクリプトを一部変更しています。

    void Start()
    {
        // アニメーションを設定------------------------------------------------
        myAnimation = DOVirtual.Float(0, 2 * Mathf.PI, duration, value =>
        {
            // リフトを上下に動かすアニメーション
            // Rigidbody2Dを使って動かすことで、当たり判定を維持しながら動かせる
            posCurrent = posStart;
            posCurrent.y = posCurrent.y + delta * Mathf.Sin(value + initialPhaseDeg / 180 * Mathf.PI);
            rigidBody.MovePosition(posCurrent);
        })
        .SetLoops(-1, LoopType.Restart) // 無限ループを設定
        .SetEase(Ease.Linear);  // イージングはリニア(線形)にする
        // -------------------------------------------------------------------
        // アニメーションを再生
        myAnimation.Play();
    }
}

変更したのはこの部分です。

posCurrent.y = posCurrent.y + delta * Mathf.Sin(value + initialPhaseDeg / 180 * Mathf.PI);

sin関数の中身が初期位相を考慮したものに変更されています。

まとめ

自分で調べた時に記事が出てこなかったので、今回アドカレに合わせて書いてみました。
今回は上下に動かすためにY座標を変化させましたが、他の座標軸でも同じように動かせるのでぜひ活用してみてください。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?