Unityでサンプル通りにやってみてから、「さて、自分でいっちょ作ってみるか!」と思い立つも、壁になるのが物体を動かす方法です。どう「動かしたいのか」で、実装する方法は変わります。以下を参考に、何をしたら良いか、考えてみてください。
方法:瞬間移動で物理を無視するTranslate
これが一番、単純な方法ですが、場所が瞬間で移動するワープです、途中の動きはありません。
実装方法
以下はどれも同じになります。
transform.position
に代入する
例)現在位置から(1,2,3)の位置へ場所を移す
transform.position=transform.position +new Vector3(1,2,3);
スクリプトリファレンス:Transform.position
transform.translate
を用いる
上記と全く同じ意味を持ちます。
例)現在位置から(1,2,3)の位置へ場所を移す
transform.Translate(1,2,3);
スクリプトリファレンス:Transform.Translate
Animationで動かす
標準のAnimator
とAnimation
を使ったものは、こちらとほぼ同様になります。
position
への代入を使ってみたサンプル
これを使うと、一瞬でその位置に移動します。そのため、物体の当たり判定などを考慮すると、適切ではない状態になりやすいです。
例として、これを使って往復運動するものを用意しました。
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
にしてできるだけ当たり判定をする設定
見ると判る通り、ポリゴンが喰いこんで物理的にあり得ない状況が生まれています。Colliderが瞬間で移動するため、当たった瞬間が検知されず、内包を許しています。こうした状況を生むため、Colliderを持った物体には注意が必要です。
使い途は、Colliderが関係ないリスポーン処理やGUIに
考えられる使い途は、Colliderを持たないもの、持っていてもLayer Collision Matrix
で無効になっていたり、周囲に何もないのが明らかな空間へ移動する場合に適しています。例えば、以下のようなとき。
- Canvas等で表示するだけのGUIで、
Collider
を持たないText
などを空間に表示する。 - 壁も通り抜けちゃうお化けの
GameObject
- リスポーンなどで一旦、画面から消えてリスポーン地点に戻す(周囲に何もない)
- 再利用するためにカメラから一旦、見えないところに移動して待機しているNPC(処理が重い時によく使うテクニックです)
など、色々あります。基本的に物理を無視するので、Rigidbody
が必要なものに使う場合は、Collider
が干渉しないか調べて問題ないなら使う、ということになります。逆に、リスポーンでリスポーン地点に戻るときにこれじゃない方法を用いると、途中の障害物にぶつかって戻れない、なんていうことになりますからね!
方法:isKinematicとMovePositionでゴリ押し
上記の方法と同じで指定位置へ確実に移動しますが、物理挙動を周囲に与えることができます。
実装方法
以下をそれぞれ、対象の物体にアタッチします。
Rigidbody
をアタッチし、isKinematic
にする
Componentをアッタッチし、InspectorでisKinematic
を☑します。
こうすることで、移動先を指定し、さらに物理挙動も与えられます。
リファレンス:Rigidbody
Rigidbody.MovePositionを使うScriptコンポーネント
移動したい先の座標を指定します。この座標は絶対座標です。
例:相対位置(1,2,3)の場所に強制移動
GetComponent<Rigidbody>().MovePosition(transform.position+new Vecttor3(1,2,3));
詳しくは以下を見てください。
スクリプトリファレンス:Rigidbody.MovePosition
MovePosition
を使ってみたサンプル
確実に移動し、その物理挙動を周囲に影響を与えるサンプルとして、以下を作りました。
前記と同様の処理を、MovePositionで実装しています。
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));
}
}
このように、ポリゴンが食い込むこともなく、きちんと指定通りの場所に移動します。速度を上げても適切に動作します。
MovePosition
の使い途は抗うことができない物体に
周りにあるRigidbody
を持つものに影響を与えますが、自身は何にぶつかっても押し通すことができますから、使い方には注意が要ります。
利用する対象には以下のようなものが考えられます。
- ダンジョンのエレベーターやエスカレーター、自動ドア
- 宇宙空間に浮かんでいて、何かにぶつかられてもびくともしない巨大な星や宇宙船
- 地震で動く地面
- 乗客で速度が落ちたりしない、回り続ける観覧車
⚠️MovePosition
は強力なので注意も必要!
動く範囲に同じようなMovePositionで動くものが在って衝突すると重なり合います、適切でないと厄介なときもあるでしょう。
また、isKinematic
の☑を外した場合、一応、動きはしますが、衝突した物体の影響を受けます。
これだと、後述するAddForceによる動きに近くなりますが、自身が受けた状態は相手に従い、ぶつかった相手側は食い込むことを許すのでそのあとの挙動がColliderが重なり合い、強く反発するところが違います。こうした特殊な状態を作り出したいゲームの仕様はあまり、無いように思われますし、こういうのがたまたまゲームに残っていて使い道があると、グリッチ扱いですね。
方法:ぶつかってぶつかられて動くAddForce
力学の計算をして相互作用したい場合に用います。
実装方法
以下のコンポーネントをそれぞれアタッチします。
RigidBody
をアタッチし、isKinematic
☐にする
Componentをアッタッチし、InspectorでisKinematic
をの☑は外し☐にします。
こうすることで、物理挙動を受け付けさせます。☑isKinematicの場合は、AddForceを受け付けません。
Rigidbody.AddForceを使うScriptコンポーネント
移動したい先の座標を指定します。この座標は絶対座標です。
例:相対位置(1,2,3)のベクトルの方向に力を加える
GetComponent<Rigidbody>().AddForce(new Vector3(1,2,3),ForceMode.VelocityChange);
この場合では質量を無視するので、速度を代入する記述でも、同じ効果が得られます。
GetComponent<Rigidbody>().velocity+=new Vector3(1,2,3);
ここで指定するForceModeで色々、挙動が変わります。試してみると面白いでしょう。
AddForce
を使った例
これまでの例と同じScene
で、動かすScriptを以下のものと差し替えました。
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);
}
}
このように、自ら動きつつ、周囲の物体に接触すると影響を受けているのが分かります。
AddForce
は動きまわる物体に
isKinematicが☐の状態のRigidbody
を持つ者同士は、相互に作用・反作用します。空間で絶対的に固定しなくないものに用います。
- 飛び回るボール
- 銃弾や矢
- ぶつかりあうキャラクター
#まとめ:動かしたいもので3パターン
以上をまとめると以下になります。
実装方法 | 動き | Colliderの食い込む可能性 | 用途 |
---|---|---|---|
TranslateやPosition代入 | 瞬間移動する | あり | リスポーンの戻りやUIアニメーション |
isKinematic☑でMovePosition | 自分の動きはごり押し、他は影響を受ける | isKinematic☑同士 | エレベーターや動く壁 |
isKinematic☐でAddForce
|
周囲と影響しあう | 無し(CollisionDetection による) |
動くボール |
どう動かしたいかを考えて、選びましょう。また、それぞれが独立で利用するものではなく、組み合わせもあります。例えば3Dゲームのキャラクターなら、
- 通常の動きは
AddForce
でWASDの操作で動かす - パワーアップアイテムでダッシュするときは
MovePosition
でごり押し - 敵にやられてリスポーンポイントまで戻るときは
Translate
といった具合です。
補足
尚、上記は直線運動でしたが、これが回転でも同じ3種類になります、参考にしてみてください。
直線 | 回転 |
---|---|
Translate やPosition 代入 |
Rotate やRotation 代入 |
isKinematic ☑でMovePosition |
isKinematic ☑でMoveRotation
|
isKinematic ☐でAddForce
|
isKinematic ☐でAddTorque
|
ここで紹介した動画になっているプロジェクトデータは以下から入手可能です。
https://github.com/JunShimura/Roll-a-Ball-Custom-1stDev-2018
それではよいUnityLifeを!