はじめに
こんにちは。azarashin です。 Unity では様々なオブジェクトを扱います。このオブジェクトはすべて座標に関連する情報を保持しており、Transform という概念を用いて座標に関連する情報を操作することができます。 本記事ではこのTransform の使い方について説明していきます。
Transform とは
例えば、Hierarchy 欄にてポップアップメニューから3D Object → Cube と選択すると、立方体を設置することができます。
新しく追加した立方体(Cube)を選択すると、Scene ビューに赤・緑・青の矢印が表示されます。それぞれの矢印はGameObject 内のx軸・y軸・z軸をそれぞれ表しています。 また、Inspector 欄にはTransform という欄が表示されています。
Transform 欄にはPosition, Rotation, Scale があり、それぞれGameObject の位置・回転姿勢・大きさを表しています。 これらの値を操作することでScene ビューの立方体が変化します。
Transform の属性をエディタ上で操作する
Transform 欄のPosition, Rotation, Scale ではそれぞれX, Y, Z 成分を入力できるようになっており、 これらの欄の値を変更するとScene ビューの立方体の位置・回転姿勢・サイズが変更されることがわかると思います。 この編集方法は値を厳密に指定する時に便利です。
数値を直接入力しなくてもPosition, Rotation, Scale 欄のX, Y, Z ラベルを左右にドラッグすることで値を少しずつ変化させることも可能です。 この方法は値を確認しつつ連続的に値を変化させながら画面を確認するのに便利です。
また、Scene ビューの矢印を操作してPosition, Rotation, Scale を編集することも可能です。 この方法であれば直感的に編集できるのがメリットです。
Transform をプログラムで制御する
前述したやり方の場合、Unity エディタ上でキャラクターの姿勢を変更することはできても実際のゲームを実行しながら姿勢を変更することはできません。 実際のゲームを実行しながらキャラクターの姿勢を変更するにはプログラムを記述する必要があります。
事前準備
まず、下図を参照しながらCube オブジェクトの配置、プログラム(スクリプトファイル)の作成及びInspector への割り当てを行い、Cube オブジェクトにスクリプトファイルを割り当ててください。
この時点で、SampleObject クラスは下記のように定義されているものとします。
using System.Collections; using System.Collections.Generic; using UnityEngine; public class SampleObject : MonoBehaviour { // Start is called before the first frame update void Start() { } // Update is called once per frame void Update() { } }
以降の説明では上記のソースコードをベースに改良を加えていきたいと思います。
単純な軸を中心に回転させ続ける
Updateメソッドを使う
Update メソッドを下記のように変更してください。
// Update is called once per frame void Update() { transform.Rotate(0, 0, 0.1f); }
その後、下図のように再生ボタンをクリックすると、Game 欄にて直方体が回転していることと、Inspector 欄にてRotation のz成分が変化していることとがそれぞれ確認できると思います。
MonoBehaviour を継承した独自クラス(SampleObject)のメソッド内でtransform と記述すると、対象となるGameObject のTransform を操作し、GameObject の位置・回転姿勢・大きさをそれぞれ変化させることができます。 実はMonoBehaviour クラスも他の上位クラスを継承しており、このtransform は他の上位クラスで定義されている属性(メンバ変数)です。普段はこのようなクラス構成は特に意識せず、「transform と記述すれば、対象となるGameObject の位置・回転姿勢・大きさをそれぞれ変化させることができる」と覚えておけば十分です。
これらの操作のうち、今回は回転姿勢の制御をしますので、transform のRotation メソッドを使用します。Rotation メソッドを使用すると直前の状態から対象のオブジェクトをx, y, z軸を中心に指定された角度だけ回転させることができます(回転角度の単位は「度(degree)」)。上記のソースコードの例であれば、描画するごとに0.1度ずつz軸を中心に回転させることになります。
さて、実はこのやり方には問題点があります。それは実行する環境によって回転速度が変化するということです。 Update メソッドというのは画面を描画するごとに呼び出されるのですが、この呼び出し頻度は一定していません。 描画性能の高いパソコンであれば高頻度で呼び出される一方、低スペックパソコンであれば低頻度でしか呼び出されません。 これはつまり、「描画性能が高い程1秒間により大きな角度回転する」ということになり、オブジェクトの回転速度に影響を及ぼします。
FixedUpdateメソッドを使う
実行環境の違いによる回転速度の違いを軽減させる方法として、FixedUpdate を用いる方法があります。
Update メソッドを下記のように変更してください。
// Update is called once per frame void FixedUpdate() { transform.Rotate(0, 0, 0.1f); }
この方法を用いる場合、いくつか注意点・問題点があります。まず回転がカクつくことです(早速大問題ですが…)。 FixedUpdate が呼び出される間隔が画面の更新間隔に比べてかなり長いことです。 この間隔はUnity のProject Settings にてFixed Timestep を設定することで変更できますが、この値の初期値は0.02 (20ミリ秒)となっています。 60fps のゲームの場合、更新間隔は16.6ミリ秒程度ですし、そもそも描画にかかる時間は様々な要因により一定ではありません。 そのため、FixedUpdate が呼び出される間隔と画面の更新間隔が整合せず、回転の動きがカクつく原因となってしまいます。
Time.deltaTime とUpdateメソッドを使う
実行環境の違いによらず回転速度を一定にすること、そして回転の動きを滑らかにすること、これら2つを達成するにはさらに他の方法を検討する必要があります。 今度はUpdate メソッドを下記のように変更してみましょう。
// Update is called once per frame void Update() { float speed = 360.0f; // 1秒間に360度回転させる transform.Rotate(0, 0, speed * Time.deltaTime); }
上記で記述したTime.deltaTime という変数には、前回Update メソッドが呼び出されてから経過した時間(秒)が格納されています。 そのため、回転角度を speed * Time.deltaTime とすると、描画に要した時間に応じて回転角度が変動します。 具体的には、描画に要した時間が長い程次の描画時に回転させる量が増えます。 このような制御をおこなうことで、 実行環境の違いによらず回転速度を一定にすること、そして回転の動きを滑らかにすることとを両立させることができます。
Time.time とUpdateメソッドを使う
前述したTime.deltaTime を用いれば多くの場合問題が生じることは少ないのですが、 実はTime.deltaTime を用いた計算結果を何度も足し合わせていくと、徐々に誤差が蓄積していきます。 多くのゲームの場合はあまり影響が目立たないのですが、音ゲーのようなゲームの場合はこういった蓄積による誤差が 許容できなくなってきます。 音ゲーでこの問題が発生すると、音楽とリズムマークの動きが徐々にずれてきて、 音楽の流れにおいては正しいタイミングでボタンを押したのに、実際のタイミング判定では低評価と誤判定される問題が生じます。 この問題を解消するため、今度はUpdate メソッドを下記のように変更してみます。
// Update is called once per frame void Update() { float speed = 360.0f; // 1秒間に360度回転させる transform.rotation = Quaternion.Euler(0, 0, speed * Time.time); }
Quaternion クラスは回転姿勢を制御するための機能を集めたクラスです。このQuaternion クラスのEuler メソッドを呼び出し、 transform.rotation (回転姿勢を保持するための属性(メンバ変数))に代入することで、 オブジェクトを少しずつ回転させるのではなく、ゲームが起動してから経過した時間に合わせて絶対的な回転角度を決定し、 オブジェクトの回転角度をより安定させることが可能になります。
なお、音ゲーの場合は音楽の再生経過時間を扱うための手段がTime.time とは別にありますので、 そちらを使用するようにしてください(今回は説明を省略します)。
サイズを変更させる
// Update is called once per frame void Update() { transform.localScale = new Vector3(2.0f + Mathf.Sin(Mathf.PI * Time.time * 2.0f), 1.0f, 1.0f); }
上記のようにtransform.localScale を使うとオブジェクトのサイズ(拡大・縮小率)を設定することができます。 Vector3 は3次元ベクトルを扱うためのクラスで、座標関連のデータを扱うときにお世話になります。 この例では経過時間に応じたMathf.Sin を呼び出して2.0f を足しておりますので、Vector3 のx 成分が 0~2の範囲で変動します。 その結果、直方体が伸びたり縮んだりするようになります。 (Mathf.Sin は高校時代に数学で習ったsin 関数を求めるためのメソッドです。)
移動させる
// Update is called once per frame void Update() { float amp = 2.0f; // 振幅 transform.position = new Vector3(Mathf.Sin(Mathf.PI * Time.time * 2.0f) * amp, 0.0f, 0.0f); }
上記のようにtransform.position を使うとオブジェクトの位置を設定することができます。 この例では経過時間に応じたMathf.Sin を呼び出しておりますので、Vector3 のx 成分が -1~1の範囲で変動します。 その結果、直方体が左右に振動するようになります。
まとめ
以上を踏まえ、Update メソッドを下記のように変更してみます。
// Update is called once per frame void Update() { float speed = 360.0f; // 1秒間に360度回転させる transform.rotation = Quaternion.Euler(0, 0, speed * Time.time); float amp = 2.0f; // 振幅 transform.position = new Vector3(Mathf.Sin(Mathf.PI * Time.time * 2.0f) * amp, 0.0f, 0.0f); transform.localScale = new Vector3(2.0f + Mathf.Sin(Mathf.PI * Time.time * 2.0f), 1.0f, 1.0f); }
直方体が下記のように動くことが確認できると思います。
おわりに
GameObject の位置・姿勢を変化させることはゲーム上のキャラやオブジェクトを動かすための必須機能と言えます。 今回紹介したような振動させるようなアニメーションだけでなく、特定の位置に徐々に近づけたり、 プレイヤーの入力操作に合わせてオブジェクトの位置・姿勢を変化させたりと、 transform の使い方にも様々なバリエーションが存在します。 自分の作成したプログラムでオブジェクトが動き始めると徐々にプログラミングも楽しくなってきますので、 是非色々なアニメーションに挑戦してみてはいかがでしょうか?