94
91

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

[Unity初心者Tips]どれが良いかわかる!ものを動かす方法はこうして決める

Last updated at Posted at 2019-01-30

Unityでサンプル通りにやってみてから、「さて、自分でいっちょ作ってみるか!」と思い立つも、壁になるのが物体を動かす方法です。どう「動かしたいのか」で、実装する方法は変わります。以下を参考に、何をしたら良いか、考えてみてください。

方法:one::瞬間移動で物理を無視するTranslate

これが一番、単純な方法ですが、場所が瞬間で移動するワープです、途中の動きはありません。

実装方法

以下はどれも同じになります。

transform.positionに代入する

例)現在位置から(1,2,3)の位置へ場所を移す

SubstituteTransformPosition
transform.position=transform.position +new Vector3(1,2,3);

スクリプトリファレンス:Transform.position

transform.translateを用いる

上記と全く同じ意味を持ちます。
例)現在位置から(1,2,3)の位置へ場所を移す

Transform.Translate
transform.Translate(1,2,3);

スクリプトリファレンス:Transform.Translate

Animationで動かす

標準のAnimatorAnimationを使ったものは、こちらとほぼ同様になります。

positionへの代入を使ってみたサンプル

これを使うと、一瞬でその位置に移動します。そのため、物体の当たり判定などを考慮すると、適切ではない状態になりやすいです。
例として、これを使って往復運動するものを用意しました。

Turn.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Turn : MonoBehaviour
{

    [Header("かかる時間"), Range(0.0625f, 30)]
    public float duration = 1.0f;
    [Header("移動する距離"), Range(0, 30)]
    [SerializeField] private float _distance;

    public float distance
    {
        set
        {
            _distance = value;
            targetPosition = GetTargetPosition(value);
        }
        get
        {
            return _distance;
        }

    }
 
    float pastTime = 0;
    Vector3 homePosition;
    Vector3 targetPosition;

    private Vector3 GetTargetPosition(float distance)
    {
        return transform.localPosition + transform.forward * distance;
    }
    // Use this for initialization
    private void Awake()
    {
        homePosition = transform.localPosition;
        targetPosition = GetTargetPosition(distance);
    }

    // Update is called once per frame
    void Update()
    {
        pastTime += Time.deltaTime;
        Vector3 newPosition = Vector3.Lerp(homePosition, targetPosition, Mathf.PingPong(pastTime, duration) / duration);
        transform.localPosition = newPosition;
    }

}

このScriptは、変数durationに格納された時間(秒数)で、distanceに格納された距離を移動し、往復を繰り返します。
利用しているClassなどはそれぞれ、リファレンスで参照してください。
これを用いたSceneのサンプルがこちらになります。
これを移動して見える大きなcubeにアタッチしています。さらに、周囲のおおきなCubeと小さなCubeは共通で以下がアタッチされています。

  • BoxCollider
  • Rigidbody:Collision Detection=Continuous Dynamicにしてできるだけ当たり判定をする設定

Image from Gyazo

見ると判る通り、ポリゴンが喰いこんで物理的にあり得ない状況が生まれています。Colliderが瞬間で移動するため、当たった瞬間が検知されず、内包を許しています。こうした状況を生むため、Colliderを持った物体には注意が必要です。

使い途は、Colliderが関係ないリスポーン処理やGUIに

考えられる使い途は、Colliderを持たないもの、持っていてもLayer Collision Matrixで無効になっていたり、周囲に何もないのが明らかな空間へ移動する場合に適しています。例えば、以下のようなとき。

  • Canvas等で表示するだけのGUIで、Colliderを持たないTextなどを空間に表示する。
  • 壁も通り抜けちゃうお化けのGameObject
  • リスポーンなどで一旦、画面から消えてリスポーン地点に戻す(周囲に何もない)
  • 再利用するためにカメラから一旦、見えないところに移動して待機しているNPC(処理が重い時によく使うテクニックです)

など、色々あります。基本的に物理を無視するので、Rigidbodyが必要なものに使う場合は、Colliderが干渉しないか調べて問題ないなら使う、ということになります。逆に、リスポーンでリスポーン地点に戻るときにこれじゃない方法を用いると、途中の障害物にぶつかって戻れない、なんていうことになりますからね!

方法:two::isKinematicとMovePositionでゴリ押し

上記の方法と同じで指定位置へ確実に移動しますが、物理挙動を周囲に与えることができます。

実装方法

以下をそれぞれ、対象の物体にアタッチします。

Rigidbodyをアタッチし、isKinematicにする

Componentをアッタッチし、InspectorでisKinematicを☑します。
Image from Gyazo

こうすることで、移動先を指定し、さらに物理挙動も与えられます。
リファレンス:Rigidbody

Rigidbody.MovePositionを使うScriptコンポーネント

移動したい先の座標を指定します。この座標は絶対座標です。
例:相対位置(1,2,3)の場所に強制移動

GetComponent<Rigidbody>().MovePosition(transform.position+new Vecttor3(1,2,3));

詳しくは以下を見てください。
スクリプトリファレンス:Rigidbody.MovePosition

MovePositionを使ってみたサンプル

確実に移動し、その物理挙動を周囲に影響を与えるサンプルとして、以下を作りました。
前記と同様の処理を、MovePositionで実装しています。

TurnKinematic.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Assertions;

[RequireComponent(typeof(Rigidbody))]

public class TurnKinematic : MonoBehaviour
{

    [Header("かかる時間"), Range(0.0625f, 30)]
    public float duration = 1.0f;
    [Header("移動する距離"), Range(0, 30)]
    [SerializeField] private float _distance;

    public float distance
    {
        set
        {
            _distance = value;
            targetPosition = GetTargetPosition(value);
        }
        get
        {
            return _distance;
        }

    }
    private Vector3 GetTargetPosition(float distance)
    {
        if (targetPosition == Vector3.zero)
        {
            return transform.position + transform.forward * distance;
        }
        else
        {
            return (transform.position + targetPosition).normalized * distance;

        }
    }


    float pastTime = 0;
    Vector3 homePosition;
    private Vector3 targetPosition = Vector3.zero;
    Rigidbody rigidBody;

    // Use this for initialization
    private void Awake()
    {
        homePosition = transform.position;
        targetPosition = GetTargetPosition(_distance);
        rigidBody = GetComponent<Rigidbody>();
        if (rigidBody == null)
        {
            Debug.LogError("Unable to GetComponent rigidbody at Awake");
        }
        else if (!rigidBody.isKinematic && this.enabled)
        {
            Debug.LogWarning("Rigidbody is not Kinematic");
        }

    }

    private void FixedUpdate()
    {
        pastTime += Time.fixedDeltaTime;
        rigidBody.MovePosition(
            Vector3.Lerp(homePosition, targetPosition, Mathf.PingPong(pastTime, duration) / duration));
    }
}

周囲の物体は上記と同じになっています。
Image from Gyazo

このように、ポリゴンが食い込むこともなく、きちんと指定通りの場所に移動します。速度を上げても適切に動作します。
Image from Gyazo

MovePositionの使い途は抗うことができない物体に

周りにあるRigidbodyを持つものに影響を与えますが、自身は何にぶつかっても押し通すことができますから、使い方には注意が要ります。

利用する対象には以下のようなものが考えられます。

  • ダンジョンのエレベーターやエスカレーター、自動ドア
  • 宇宙空間に浮かんでいて、何かにぶつかられてもびくともしない巨大な星や宇宙船
  • 地震で動く地面
  • 乗客で速度が落ちたりしない、回り続ける観覧車

⚠️MovePositionは強力なので注意も必要!

動く範囲に同じようなMovePositionで動くものが在って衝突すると重なり合います、適切でないと厄介なときもあるでしょう。
Image from Gyazo

また、isKinematicの☑を外した場合、一応、動きはしますが、衝突した物体の影響を受けます。
Image from Gyazo

これだと、後述するAddForceによる動きに近くなりますが、自身が受けた状態は相手に従い、ぶつかった相手側は食い込むことを許すのでそのあとの挙動がColliderが重なり合い、強く反発するところが違います。こうした特殊な状態を作り出したいゲームの仕様はあまり、無いように思われますし、こういうのがたまたまゲームに残っていて使い道があると、グリッチ扱いですね。

方法:three::ぶつかってぶつかられて動くAddForce

力学の計算をして相互作用したい場合に用います。

実装方法

以下のコンポーネントをそれぞれアタッチします。

RigidBodyをアタッチし、isKinematic☐にする

Componentをアッタッチし、InspectorでisKinematicをの☑は外し☐にします。
Image from Gyazo

こうすることで、物理挙動を受け付けさせます。☑isKinematicの場合は、AddForceを受け付けません。

リファレンス:Rigidbody

Rigidbody.AddForceを使うScriptコンポーネント

移動したい先の座標を指定します。この座標は絶対座標です。
例:相対位置(1,2,3)のベクトルの方向に力を加える

GetComponent<Rigidbody>().AddForce(new Vector3(1,2,3),ForceMode.VelocityChange);

この場合では質量を無視するので、速度を代入する記述でも、同じ効果が得られます。

GetComponent<Rigidbody>().velocity+=new Vector3(1,2,3);

リファレンス:Rigidbody.AddForce

ここで指定するForceModeで色々、挙動が変わります。試してみると面白いでしょう。

リファレンス:ForceMode

AddForceを使った例

これまでの例と同じSceneで、動かすScriptを以下のものと差し替えました。

TurnPhysical.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Assertions;

[RequireComponent(typeof(Rigidbody))]

public class TurnPhysical : MonoBehaviour
{

    [Header("かかる時間"), Range(0.0625f, 30)]
    public float duration = 1.0f;
    [Header("移動する距離"), Range(0, 30)]
    [SerializeField] private float _distance;

    public float distance
    {
        set
        {
            _distance = value;
            targetPosition = GetTargetPosition(value);
        }
        get
        {
            return _distance;
        }

    }
    private Vector3 GetTargetPosition(float distance)
    {
        if (targetPosition == Vector3.zero)
        {
            return transform.position + transform.forward * distance;
        }
        else
        {
            return (transform.position + targetPosition).normalized * distance;

        }
    }

    float pastTime = 0;
    private Vector3 homePosition;
    private Vector3 targetPosition = Vector3.zero;
    private Vector3 nextPosition;
    Rigidbody rigidBody;

    // Use this for initialization
    private void Awake()
    {
        homePosition = transform.position;
        targetPosition = GetTargetPosition(_distance);
        rigidBody = GetComponent<Rigidbody>();
        if (rigidBody == null)
        {
            Debug.LogError("Unable to GetComponent rigidbody at Awake");
        }
        else if (rigidBody.isKinematic && this.enabled)
        {
            Debug.LogWarning("Rigidbody is Kinematic");
        }

    }
  
    private void FixedUpdate()
    {
        pastTime += Time.fixedDeltaTime;
        nextPosition = Vector3.Lerp(homePosition, targetPosition, Mathf.PingPong(pastTime, duration) / duration);
        rigidBody.AddForce(nextPosition - transform.position - rigidBody.velocity, ForceMode.VelocityChange);
    }
}

これを、以下の設定で実行してみます。
Image from Gyazo

実行した結果がこちらです。
Image from Gyazo

このように、自ら動きつつ、周囲の物体に接触すると影響を受けているのが分かります。

AddForceは動きまわる物体に

isKinematicが☐の状態のRigidbodyを持つ者同士は、相互に作用・反作用します。空間で絶対的に固定しなくないものに用います。

  • 飛び回るボール:baseball:
  • 銃弾や矢:bow_and_arrow:
  • ぶつかりあうキャラクター:runner_tone2:

#まとめ:動かしたいもので3パターン

以上をまとめると以下になります。

実装方法 動き Colliderの食い込む可能性 用途
TranslateやPosition代入 瞬間移動する あり リスポーンの戻りやUIアニメーション
isKinematic☑でMovePosition 自分の動きはごり押し、他は影響を受ける isKinematic☑同士 エレベーターや動く壁
isKinematic☐でAddForce 周囲と影響しあう 無し(CollisionDetectionによる) 動くボール

どう動かしたいかを考えて、選びましょう。また、それぞれが独立で利用するものではなく、組み合わせもあります。例えば3Dゲームのキャラクターなら、

  • 通常の動きはAddForceでWASDの操作で動かす
  • パワーアップアイテムでダッシュするときはMovePositionでごり押し
  • 敵にやられてリスポーンポイントまで戻るときはTranslate

といった具合です。

補足

尚、上記は直線運動でしたが、これが回転でも同じ3種類になります、参考にしてみてください。

直線 回転
:one:TranslatePosition代入 RotateRotation代入
:two:isKinematic☑でMovePosition isKinematic☑でMoveRotation
:three:isKinematic☐でAddForce isKinematic☐でAddTorque

ここで紹介した動画になっているプロジェクトデータは以下から入手可能です。
https://github.com/JunShimura/Roll-a-Ball-Custom-1stDev-2018

それではよいUnityLifeを!

94
91
2

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
94
91

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?