こんにちは、Unityエンジニアのオオバです。
Transformはゲーム開発において超絶必須
はじめに結論を簡単にまとめます。
①Transformは移動・回転・拡大縮小といった重要機能を持つ
②GameObjectに必ず付与、削除不能な特別コンポーネント
③ゲーム開発では必ず使うレギュラーコンポーネント
TransformはUnityでゲーム開発する上で超絶必須機能 です。「移動」「回転」「拡大縮小」「グループ化」といったゲーム中に何度も登場する重要な機能を担っています。ゲーム開発でキャラクターを動かしたり、パズルを動かすときには必ずTransformを操作します。
Unity初心者から熟練者まで誰もが常時関わる機能です。ぜひTransformを使えるようになりましょう。
👉DOTweenの教科書を読んでUnityアニメーションをプログラミングしてみよう!
- 特別なコンポーネントTransformとは?
- Transformの基本的な使い方4選
- 使い方①Transformの位置変更「position・localPosition」
- 使い方②Transformの回転「eulerAngles・rotation」
- 使い方③Transformで拡大縮小「localScale」
- 使い方④Transformの階層化「SetParent」
- よく使うTransformテクニック9選
- テク①Transformを指定した方向を向かせる「LookAt」
- テク②Transformのアニメーション
- テク③指定の軸と座標を中心に回転
- テク④子オブジェクトの取得
- テク⑤指定のTransformが親オブジェクトかチェック
- テク⑥Hierarchy内のTransform数を取得
- テク⑦親子関係を解除するDetachChildren
- テク⑧スクリーン座標を絶対座標に変換するテクニック
- テク⑨絶対座標におけるスケール値を取得する「lossyScale」
- パフォーマンスを意識したTransformの使い方
- Transformはキャッシュして使う
- 位置と回転同時に更新するときはSetPositionAndRotationを使用
- hierarchyCapacityでパフォーマンスアップ
- positionはlocalPositionより負荷が高くなる
- Transformの注意点
- InspectorウィンドウのPositionはlocalPosition
- Unityは左手座標系
- Transform.Findはおすすめしない理由
- Updateでものを動かす場合はTime.deltaTimeを使う理由
- TransformアクセスにGetComponentは使うな
- FixedUpdateで動かしてはいけない
- Unity Transformのまとめ
特別なコンポーネントTransformとは?
Transform(トランスフォーム)とは コンポーネントの1種 です。コンポーネントとはUnityにとっては機能のことを指します。Transformはコンポーネントの1つなので、カメラやライトと同じような機能の1つと考えて大丈夫です。
Transformにどのような機能があるかというと、 「移動」」「回転」「拡大縮小」「グループ化(階層化)」 です。ゲーム開発ではよく使われる機能を持っていると言えます。
またTransformコンポーネント(以下:Transform)は以下の3つの点においてUnityの中では 特別な存在 です。
①GameObjectに必ずセットされる
②Transformは削除不可能
③Transformの取得にGetComponentは不要
そもそも 「コンポーネント」って何? という方のためにお伝えすると、 コンポーネントの理解はUnityのゲーム開発では必須 です。「ちょっと理解があいまいだな」と思っている方はぜひ次の記事がおすすめです。Unity歴11年のオオバがコンポーネントについて徹底解説しています。本記事とあわせて読むことでより学べると思いますので、ぜひ読んでみてください。
特別その①GameObjectに必ずセットされる
Transformは1つのGameObjectに1つ ”必ず” セットされます。つまり、 TransfromをもたないGameObjectは存在しない ということです。
すべてのGameObjectには移動や回転といった基本的な機能がTransformによって実装されているのです。
そもそもGameObjectとはUnityの基礎となるオブジェクトです。こちらの記事でGameObjectを徹底解説していますので、ぜひあわせて読んでみてください。
特別その②Transformは削除不可能
Transformは削除できません。特別その①GameObjectに必ずセットされるで解説したとおり、GameObjectには 必ずTransformが存在します。 つまり、このTransformを削除する方法はないということです。
以下の図の通りTransformのInspectorウィンドウには「Remove Component」がありません。
ちなみに通常のコンポーネントの削除はRemove Componentを使います。
スクリプトから削除しようとすると以下のようなエラーが出力されます。
Can't destroy Transform component of 'Destroyを実行したメソッド名'.
If you want to destroy the game object, please call 'Destroy' on the game object instead. Destroying the transform component is not allowed.
つまり、TransformはDestroy(オブジェクトの削除)を許可していない のです。
Transformは2つ以上追加不可
Transformは2つ以上追加できません。 ためしに Add Component からTransformを追加してみます。
上図の通り、そもそも「Transform」がリストに表示されないのです。GameObjectに最初から付与されていることから、追加することのないコンポーネントなので リストから除外 されているのだと思われます。
特別その③Transformの取得にGetComponentは不要
通常コンポーネントを取得するために GetComponent を使います。しかし、Transformはコンポーネントの中では特別で、 transform を使ってもTransformを取得できます。
// GetComponentでTransformを取得
Transform tr = gameObject.GetComponent<Transform>();
↓
Transform tr = gameObject.transform;
上のソースコードの通りGetComponentを使わず、 transform
でTransformを取得できるのです。
Transformと似た名前で「RectTransform」コンポーネントを見かけますよね。
RectTransformは ”UI用のTransform” です。
UI上の「位置」「回転」「拡大縮小」といった機能を担っています。
また、RectTransform自体はTransformのサブクラスです。
つまりRectTransformはTransformに機能を追加した存在。
UI開発でお世話になるRectTransformは、Transformを学ぶことでより深く理解できると思います。
Transformの基本的な使い方4選
TransformはUnityにとって「特別なコンポーネント」だとわかりました。ここからはTransformの基本的な使い方について解説していきます。
①Transformの位置変更「position・localPosition」
②Transformの回転「eulerAngles・rotation」
③Transformで拡大縮小「localScale」
④Transformの階層化「SetParent」
使い方①Transformの位置変更「position・localPosition」
ゲーム開発でキャラクターの位置を動かしたくなりますよね。位置の変更はTransformの役割です。Inspectorウィンドウ上では「Position」が 位置のパラメーター です。
X、Y、Zそれぞれの座標を変更できます。
スクリプトでTransformの位置を操作する場合は position または localPosition を使います。実際のサンプルを見ていきましょう。1秒後にTransformのX座標を0から2へ移動させるサンプルです。
💻ソースコード : 位置を変更するサンプル
using System.Collections;
using UnityEngine;
public class PositionUpdate : MonoBehaviour
{
IEnumerator Start()
{
transform.localPosition = new Vector3(0, 0, 0);
// コルーチンで1秒待機
yield return new WaitForSeconds(1f);
// 1秒後に座標を(2, 0, 0)へ移動
transform.localPosition = new Vector3(2f, 0, 0);
}
}
このスクリプトを実際に動かしてみましょう。今回Unityちゃんに先のサンプルコードをアタッチして動かしてみました。
1秒後にX座標が移動したことがわかります。
このようにTransformの position または localPosition に値をセットすることで、Transformの座標を更新、変更することが出来るのです。
👉 【無料】SD版Unityちゃんのダウンロードから使い方を解説!
ところで先の例では「コルーチン」を使って1秒の 待機処理 を実装しました。コルーチンは非同期処理と呼ばれ、 ゲーム開発では必須の知識 。コルーチンを学びたい、復習したい方はあわせてこちらの記事がおすすめです。
positionとlocalPositionの違い
Transformの座標プロパティには「position」と「localPosition」の2つが存在します。似ているようで全く違います。両者の違いは”正確”に理解しておく必要があります。自分の思ったとおりにオブジェクトを配置するために 必須の知識 です。
結論から解説すると positionは「絶対座標」 、 localPositionは「相対座標」 です。
position「絶対座標」とは?
言わずもがな Unityの世界は3D です。X軸、Y軸、Z軸そして「原点」が存在します。原点とはXYZそれぞれ0の座標です。(0, 0, 0)のこと。
絶対座標とは原点基準の座標 です。下の図はUnityちゃんを原点からX座標2離れた位置に配置しました。
もう1例としてUnityちゃんを原点から(1, 0, 2)の座標に配置。
このように 絶対座標は感覚的でわかりやすい です。絶対座標は「グローバル座標」とも呼ばれます。
localPosition「相対座標」とは?
次にlocalPosition(相対座標)についてです。結論、 localPosition(相対座標)とは「親階層基準の座標」 です。使い方④Transformの階層化で詳しく解説しますが、Transformは階層を作れます。
上図のような階層です。
下記のシンプルな親子関係を例にlocalPositionを解説します。
親階層を「Parent」、子階層を「Child」と名付けました。
まずは親階層のParentの位置を(0, 1, 0)にしてみます。
では子階層ChildはX軸を2だけ動かした(2, 0, 0)の位置にしてみます。
するとposition「絶対座標」とは?で解説したときのUnityちゃんと位置が変わります。Parentの位置が基準のため、Unityちゃんは原点よりY座標が1上なのです。
同じ座標でもlocalPosition(相対座標)とposition(絶対座標)では配置される場所がかることがわかります。
Visual Scriptingを使うとソースコードを書かずにTransformを操作できるようになります。これによりプログラマーとクリエーターの協業作業がしやすくなります。興味ある方はこちらの記事も読んでみてください。
positionとlocalPositionの使い分け
絶対座標のpositionと相対座標のlocalPositionの違いを解説してきましたが、どう使い分けたら良いかわからないですよね。結論から書きますと、 普段の使用はlocalPosition で大丈夫です。Transformの階層が深く、どうしても子階層を絶対座標に移動させたい場合に position を使いましょう。
重要なことですが、 無意味にlocalPositionとpositionを混ぜるのは混乱の元なので絶対に禁止 です。自分自身もそうですが、チームメンバーも訳がわからなくなります。ほとんどの配置処理はlocalPositionの使用をおすすめします。
繰り返しになりますが、普段の使用は「localPosition」、どうしてもな場合に限り「position」という考えで大丈夫です。
ところでUnityでアニメーションを作成するときの候補に「AnimationClip」は必ず挙がります。 AnimationClipは初心者でも簡単にアニメーションを作れるUnityのツールです。 ゲームにはアニメーションは必須スキル。次の記事ではAnimationClipの使い方を初心者向けに徹底解説しました。本記事とあわせてぜひを読んでみてください。
Unityの単位はメートル
Unity内の座標の話が登場したため、Unityの距離と単位について解説します。結論、 Unityの単位はメートル です。
例えばPositionに(3, 0, 0)とセットすれば、原点からX軸 3メートル離れた位置 に配置したことになります。
単位は思っている以上に重要です。Unityに3Dソフトで作成した素材をインポートすることがあります。 3Dソフトの単位はメートルとは限りません。
Unityにインポートして、意図した大きさにならない場合は単位の確認が重要なのです。 「Unityはメートル」 であることをぜひ覚えておきましょう。
Vector3とは?
今回登場した Vector3
とは3つの数字を保持するデータです。x、y、zの3つのプロパティでアクセスできます。次のサンプルを見てみましょう。
💻ソースコード : Vector3の各要素をログ出力するサンプル抜粋
// Vector3を生成
Vector3 vector3 = new Vector3(10, 5, 20);
// vector3のx、y、zの各要素をログ出力
Debug.Log($"{vector3.x} / {vector3.y} / {vector3.z}");
実行すると以下のようにログがConsoleウィンドウに出力されます。
Vector3は数字を保持する他に、四則演算、2点間の距離といった便利な機能が多く実装されています。1点気をつけるべき点は、Vector3の保持する数字は小数です。小数とは、「1.4」「0.5」「-0.07」このような ”小数点” を持つ数字。
逆に整数を扱う Vector3Int
という型も存在します。ここでは紹介だけで終っておきますね。
Translateメソッドで位置移動
Transformのposition、localPositionの値の更新で位置を変更してきました。その他に、 Translate メソッドもTransformの位置を変更できます。Translateメソッドの引数に移動させる距離を指定。Transoformの現在距離に加算します。
transform.Translate(new Vector3(1, 0, 0));
上のTranslateのコードは下記のコードと処理内容は同じです。
transform.localPosition += new Vector3(1, 0, 0);
相対座標に対して距離を加算します。
一方Translateの第2引数に Space.World
を代入すると、絶対座標の方向指定になります。
transform.Translate(new Vector3(1, 0, 0), Space.World);
上のTranslateのコードは下記のコードと同じです。
transform.position += new Vector3(1, 0, 0);
絶対座標に対して距離を加算します。
無理してTranslateを使わずともposition、localPositionを使っても同じ処理を書けます。初心者はTranslateは覚えなくても大丈夫。まずは基本となる「position」と「localPosition」の使い方を覚えておきましょう。
使い方②Transformの回転「eulerAngles・rotation」
Transformの回転機能について解説します。
Inspectorウィンドウでは Rotation がTransformの角度パラメーターです。
X、Y,Zそれぞれの軸の回転が可能です。
スクリプトから回転を制御する方法はいくつかあります。
localEulerAngles・eulerAnglesで角度を変更
localEulerAngles、または eulerAnglesを使って角度を変更する処理の紹介です。Eulerの読み方は 「オイラー」 です。使い方①Transformの位置変更「position・localPosition」と同様、回転にも原点基準の回転と親基準の回転が存在します。
次のサンプルコードを見てみましょう。1秒後にTransformのY軸を90度回転させます。
using System.Collections;
using UnityEngine;
public class EulerAnglesUpdate : MonoBehaviour
{
IEnumerator Start()
{
transform.localEulerAngles = new Vector3(0, 0, 0);
// コルーチンで1秒待機
yield return new WaitForSeconds(1f);
// 1秒後に座標を(0, 90, 0)へ移動
transform.localEulerAngles = new Vector3(0, 90, 0);
}
}
このコードを実行してみます。
すると、1秒経過するとUnityちゃんが90度横を向来ました。localEulerAngles、EulerAnglesはVector3の値を代入することでTransformを回転させます。
localRotation / rotationで角度を変更
eulerAnglesのほかに、 rotation を使って角度を変更できます。rotationにはVector3ではなく、 Quaternion(読み方 : クォータニオン) を使います。
聞き慣れないキーワードかもしれません。しかし回転処理をさせる上でとても便利です。また原点基準のrotation、親階層基準のlocalRotationを利用できます。localEulerAngles・eulerAnglesで角度を変更で紹介した90度回転するUnityちゃんサンプルを rotation で実装すると以下のコードになります。
using System.Collections;
using UnityEngine;
public class RotationUpdate : MonoBehaviour
{
IEnumerator Start()
{
transform.rotation = Quaternion.identity;
// コルーチンで1秒待機
yield return new WaitForSeconds(1f);
// 1秒後に正面方向が(1, 0, 0)を向く
transform.rotation = Quaternion.LookRotation(
new Vector3(1, 0, 0), new Vector3(0, 1, 0)
);
}
}
ポイントは以下のコードです。パッと見よくわからないと思うので解説します。
transform.rotation = Quaternion.LookRotation(
new Vector3(1, 0, 0), new Vector3(0, 1, 0)
);
Quaternion.LookRotation
は、第1引数に「向かせたい方向」、第2引数に「回転物の上方向」を指定します。第1引数に「向かせたい方向」は、回転物の正面が向く方向になります。 正面とは「正のZ方向」 です。つまり(0, 0, 1)。
下図のUnityちゃんの例では、Z方向はUnityちゃんの背中の方向です。
つまり、このUnityちゃんの正面とは背中方向だということです。
上図のとおり第2引数の上方向は(1, 0, 0)です。向かせたい方向は(1, 0, 0)。
回転を実行すると、Unityちゃんの背中が正のX方向(1, 0, 0)を向くのです。
Quaternion.LookRotationはなぜ上方向を指定するのか?
Quaternion.LookRotation はなぜ第2引数で上方向を指定するのでしょうか。理由は3D上の回転なので、さまざまな方向から回転できてしまうからです。以下の例は第2引数の上方向を(0, 1, 0)ではない値を指定した例です。最終的な結果は同じですが、途中経過が意図しません。
上方向を指定することで、意図した回転になるのです。今回立っているUnityちゃんを動かしたいので、上方向とはY軸方向。つまり(0, 1, 0)と指定しました。Yの値を1にしていますが、10にしても100にしても大丈夫です。
このコードを実行するとEulerAnglesのサンプルと同じ挙動になります。
Quaternionは難しい
本記事ではQuaternionを深堀りしません。なぜなら 難しいから です。
Quaternionを理解するためには前提として数学的な知識が必要。初心者は内部処理は知らなくてよいと考えています。とりあえず使い方を知っておくだけで大丈夫。それよりゲーム開発に時間を使うほうが有意義だと思っています。
それでもQuaternionについて理解したい方はこちらの動画がおすすめです。元Unityの安原さんの動画です。とても分かりやすく順序立てて解説しているためぜひ見てみてください。
使い方③Transformで拡大縮小「localScale」
Transformの拡大縮小を解説します。
Inspectorウィンドウでは Scale が拡大縮小のパラメーター。
X、Y,Zそれぞれの軸の拡大縮小が可能です。スクリプトでは localScale を使います。
using System.Collections;
using UnityEngine;
public class ScaleUpdate : MonoBehaviour
{
IEnumerator Start()
{
transform.localScale = new Vector3(1, 1, 1);
// コルーチンで1秒待機
yield return new WaitForSeconds(1f);
// 1秒後に2倍にスケール
transform.localScale = new Vector3(2, 2, 2);
}
}
このコードを実行すると、1秒後にUnityちゃんは2倍の大きさになります。
Transformの拡大縮小は移動や回転と違い、「localScale」1種類のみです。
使い方④Transformの階層化「SetParent」
移動、回転、拡大縮小に並んでTransformの重要な機能は 階層化 です。
今までで何度か登場しましたが、上図のような階層を作れます。
Inspectorウィンドウ上ではGameObjecctをドラッグ・アンド・ドロップすることで階層化出来ます。
SetParentを使ってTransformを階層化
プログラムで実装する場合は SetParent メソッドを使います。
using UnityEngine;
public class SetParentSample : MonoBehaviour
{
[SerializeField] Transform _parent;
void Awake()
{
// SetParentで自分自身を「_parent」の子階層にする
transform.SetParent(_parent, false);
}
}
SetParent の引数にTransformインスタンスを指定します。指定されたTransformが親階層に設定されるのです。
そもそも Awake とはなにか?という人もいますよね。AwakeとはUnityではイベント関数と呼ばれています。 MonoBehaviour クラスを継承するとUnityから自動で呼ばれる関数です。
Awakeはイベント関数の1つで、初回に1度呼ばれます。詳しくは次の記事を読んでみてください。
SetParentの第2引数の違いは重要
SetParentメソッドの第2引数の理解は重要です。大きく挙動が変わります。
SetParentの第2引数は、子オブジェクトの座標、回転、拡大縮小パラメーターを引き継ぐかどうかです。
- true・・・引き継がない
- false・・・引き継ぐ
仕様だけ説明しても理解しづらいため具体例を挙げて紹介します。
次のような「Parent」と「Child」の2つのオブジェクトを用意します。
2つのオブジェクトは最初並列で、親子関係ではありません。
SetParentで親子関係にしたとき、第2引数の切り替えでどのような変化があるのか確認していきます。
trueを指定すると見た目は何も起きません。
falseを指定すると「Child」が動きましたね。
繰り返しになりますが、SetParentの第2引数は 子オブジェクトの座標、回転、拡大縮小パラメーターを引き継ぐかどうか です。
SetParentの第2引数をfalseにすると、Childは親子になる前の(0, 0, 0)座標を引き継ぎます。親のParentの座標は(4, 0, 0)でした。子階層は親階層を基準にした座標でしたね。
つまり、Childは(4, 0, 0)を基準に(0, 0, 0)に配置されたということです。パッと見Childは(4, 0, 0)に移動したように見えますね。SetParent第2引数は、たいていfalseを採用します。trueはめったに使いません。使わざるを得ない状況で使うと良いでしょう。
1つ上の親を参照するparent
Transformの親子を作ったら、親子供それぞれアクセスしたくなります。指定のTransformの親アクセスは「parent」プロパティです。次のような親子関係の「Child」から親にアクセスしてみます。
次のコードを見てみましょう。
using UnityEngine;
public class ParentSample : MonoBehaviour
{
void Awake()
{
var parent = transform.parent;
Debug.Log(parent);
}
}
このコードをChildにセットして実行します。
するとこのように親階層である「Parent」を取得したログが出力されます。
ルート階層でparentアクセスはnull
parentアクセスの注意点はルート階層で使った場合です。結論、ルート階層でparentアクセスするとnullが返ってきます。具体的には以下の状態です。
Hierarchyウィンドウの第一階層に親階層は存在しません。parentにアクセスしてもnullなのです。先のコードを改善すると以下です。
using UnityEngine;
public class ParentSample : MonoBehaviour
{
void Awake()
{
var parent = transform.parent;
if (parent != null) {
// nullチェックしてアクセスすると安全
}
}
}
transform.parent
はnullの可能性があるため、nullチェックすると安全です。
ルートの参照方法root
深い階層からルートのTransformにアクセスしたくなる時があります。そんなときは root プロパティを使います。
上図のようなイメージです。深い階層「Child」から「Root」を取得します。次のコードをChildにアタッチして実行してみましょう。
using UnityEngine;
public class RootSample : MonoBehaviour
{
void Awake()
{
var root = transform.root;
Debug.Log(root);
}
}
するとConsoleウィンドウにRootが取得できた旨のログが出力されました。そこまでよく使う機能ではないですが、基本知識として 深い子階層からでもroot参照できる ことを覚えておきましょう。
ここまで何度も登場している「MonoBehaviour」。MonoBehaviourはコンポーネントの一種で ゲーム開発の基盤 です。MonoBehaviourを正しく理解し使いこなすことで Unityでのゲーム開発効率を飛躍的にアップ させます。「まだMonoBehaviourについて理解できていないな」 と思われる方はこちらの記事がオススメ。
MonoBehaviourを徹底解説しています。ぜひ読んでみてください。
よく使うTransformテクニック9選
Transformの基本をおさえたところで、次はよく使うTransformテクニックを紹介します。
①Transformを指定した方向を向かせる「LookAt」
②Transformのアニメーション
③指定の軸と座標を中心に回転
④すべての子オブジェクトを取得する
⑤指定のTransformが親オブジェクトかチェック
⑥Hierarchy内のTransform数を取得
⑦親子関係を解除するDetachChildren
⑧スクリーン座標をワールド座標に変換するテクニック
⑨絶対座標におけるスケール値を取得する
テク①Transformを指定した方向を向かせる「LookAt」
ゲーム開発で指定の方向を向かせる場面はよく発生します。例えば敵が自分に対して弾を撃ってくるシーン。敵は自分を向いてから弾を撃ちますよね。一見、難しそうに感じますが簡単です。
LookAt メソッドを使うと指定したオブジェクトに向く処理を簡単に実装できます。向かせたいTransformの Z方向がターゲットを向く のです。
サンプルコードはこちら。Unityちゃんにアタッチしています。
_target
変数に球を指定。
using UnityEngine;
public class LookAtTargetSample : MonoBehaviour
{
// 視線を向かせるTransformをセット
[SerializeField] Transform _target;
void Update()
{
transform.LookAt(_target);
}
}
実行するとこのようにUnityちゃんは球の方を向いてくれます。
LookAtは引数に 座標 を指定することもできます。
💻ソースコード : 座標指定でLookAtの使用例
// Vector3型の座標を指定することも可能
transform.LookAt(new Vector3(5, 0, -5));
座標指定のLookAtは上のような使い方です。
テク②Transformのアニメーション
Transformのアニメーションはゲーム開発では高頻度で登場します。どういったアニメーション方法があるのか解説していきます。
Updateでアニメーション
とてもシンプルなアニメーション方法です。等速直線運動させてみます。「等速直線運動」とは速度一定の動きです。サンプルとして一定区間を行ったり来たりするアニメーションを作ってみました。
using UnityEngine;
public class TransformAnimationSample : MonoBehaviour
{
float _dir = 1f;
void Update()
{
var rangeX = 1.5f;
var speed = 2f;
var pos = transform.localPosition;
if (pos.x < -rangeX)
{
// X座標が区間を越えたら方向を変える
_dir *= -1f;
pos.x = -rangeX;
}
else if (pos.x > rangeX)
{
// X座標が区間を越えたら方向を変える
_dir *= -1f;
pos.x = rangeX;
}
// Unityちゃんの座標を更新
pos.x += speed * Time.deltaTime * _dir;
// 座標をUnityちゃんに反映
transform.localPosition = pos;
}
}
このソースコードをUnityで実行してみます。
するとUnityちゃんが一定区間を行ったり来たりするアニメーションの完成です。
ソースコードの中で Time.deltaTime
って何?と思った人もいると思います。アニメーションさせるときにはかなり重要な要素です。Updateでものを動かす場合はTime.deltaTimeを使う理由で解説しているため、ぜひこのまま読み進めていってください。
AnimationClipでアニメーション
最後にAnimationClipを使ったアニメーションの紹介。AnimationClipとはタイムライン風のAnimationウィンドウを使います。キーフレームを打ちながらアニメーションを作成します。
感覚的にアニメーションが作れますし、プログラミング不要です。
AnimationClip、Animationウィンドウの使い方は、次の記事の「Animationウィンドウ」の章で解説していますので読んでみてください。
DOTweenでアニメーション
最後に「DOTween」を使ったアニメーションを紹介します。Unity外部の機能(無料)になるのですが、さまざまなゲーム開発で採用されています。DOTweenを使うと数行のコードで簡単にアニメーションを実装できます。
using DG.Tweening;
using UnityEngine;
public class TweenAnimationSample : MonoBehaviour
{
void Awake()
{
var rangeX = 1.5f;
var speed = 2f;
// DOTweenで実装
transform.DOLocalMoveX(rangeX, speed).SetEase(Ease.Linear)
.SetLoops(-1, LoopType.Yoyo).SetSpeedBased(true);
}
}
Updateでアニメーションで紹介したサンプルプログラムは28行ありました。DOTweenを使うと13行まで減らせました。
また、Updateメソッドを使わないためとてもシンプルです。
氷山の一角ですがこのようなアニメーションが簡単に作れてしまうのがDOTweenの魅力です。詳しくはオオバが執筆した DOTweenの教科書がおすすめです。
そのまま使えるソースコードとアニメーション動画付き。全体文字数11万文字のボリューム満点の教科書です。 無料範囲だけでもDOTweenの基礎はガッチリ固められます 。ぜひ一度読んでみてください。いいねボタンを押してもらえると嬉しいです。
テク③指定の軸と座標を中心に回転
任意の座標を中心にものを回転させたくなる時があります。以下のような感じです。
そんなときは RotateAround を使用すると便利。
回転の中心からUnityちゃんを離しておきます。今回の回転軸はY座標です。サンプルコードはこちら。
using UnityEngine;
public class RotateAroundSample : MonoBehaviour
{
void Update()
{
// 回転の中心
var center = new Vector3(0, 0, 0);
// 回転軸(Y座標)
var axis = new Vector3(0, 1, 0);
// 毎フレーム回転する角度
var angle = 1;
transform.RotateAround(center, axis, angle);
}
}
RotateAround に「回転の中心座標」「回転軸」「回転スピード」をセットします。この処理をUpdateで毎フレーム実行することでアニメーションするのです。
さらにテク①Transformを指定した方向を向かせる「LookAt」と組み合わせると指定方向を向きながら回転処理をさせることもできます。面白いですよね。
テク④子オブジェクトの取得
使い方④Transformの階層化「SetParent」で解説したとおり、Transformは階層化できます。階層化した子オブジェクトを取得する機会はよくあります。本章では2つの子オブジェクトを取得する方法を紹介します。
1階層下の子オブジェクトの取得
1階層下の子オブジェクト、つまり実の子供の取得方法です。
transformをforeach文で回すことで、1階層下の子オブジェクトすべてを取得できます。
using UnityEngine;
public class GetChildrenSample : MonoBehaviour
{
private void Awake()
{
// 1階層下の子部ジェクトをすべて取得
foreach(Transform tr in transform)
{
Debug.Log(tr.name);
}
}
}
上のコードのとおりTransformにforeachを使って子オブジェクトすべてを取得しています。よく使うテクニックなのでぜひ覚えておきましょう。
パッと見、配列やリストに見えない「Transform」をforeachで回すことに違和感があるかもしれません。
Transformクラスは「IEnumerableインターフェース」を継承しているためforeachで回せるのです。
本記事ではこれ以上深く解説しませんが、IEnumerableを継承したクラスはforeachが使えるということを覚えておきましょう。
もう1つの方法「GetChild」
GetChild メソッドを使っても1つ下の階層オブジェクトを取得できます。引数に整数を代入して子オブジェクトを取得します。
その数字とは、Hierarchyウィンドウ上の順番です。具体的には以下の図です。
Parentから見て上の方が数字が小さくなります。
1番目の子オブジェクトはプログラムでは「 0 」を指定することに注意です。
💻ソースコード : GetChildのサンプル
void Awake()
{
// 2番目の子オブジェクトを取得する
Transform child = transform.GetChild(1)
}
もう1つ注意点。下図では3つの子オブジェクトが存在します。そこで GetChild(3)
と存在しない子オブジェクトを参照しようとするとどうなるか。
結論エラーです。
UnityException: Transform child out of bounds
このようなエラーが出力されてプログラムが止まります。
子オブジェクトの数を取得「childCount」で紹介する childCount を使って子オブジェクトの数を取得しながらGetChildを使うとよいです。
子オブジェクトすべて取得
GetComponentsInChildren を使うことで自分を含めて子階層すべてを取得できます。
自分を含め子階層すべてのTransformを取得するサンプルコードはこちら。
using UnityEngine;
public class GetChildrenSample : MonoBehaviour
{
private void Awake()
{
var children = GetComponentsInChildren<Transform>();
foreach(Transform tr in children)
{
Debug.Log(tr.name);
}
}
}
もし自分自身を除外した子階層すべてを取得したい場合は、以下のように自分自身を除外する処理を書いてください。
using UnityEngine;
public class GetChildrenSample : MonoBehaviour
{
private void Awake()
{
var children = GetComponentsInChildren<Transform>();
foreach(Transform tr in children)
{
// 自分自身を除外する
if (tr != transform) {
Debug.Log(tr.name);
}
}
}
}
テク⑤指定のTransformが親オブジェクトかチェック
指定のTransformが自分自身の親なのかチェックしたい時があります。 IsChildOf を使うと簡単です。
下の図は実行者(Executor)がIsChildOfを使った際の判定範囲です。
ピンクの枠内のオブジェクトはtrueを返します。 自分自身IsChildOfで比較してもtrue が返ってくることに注意です。
using UnityEngine;
public class IsChildOfSample : MonoBehaviour
{
[SerializeField] Transform _checkTransform;
void Awake()
{
transform.IsChildOf(_checkTransform);
}
}
↑サンプルコードはこちら。
テク⑥Hierarchy内のTransform数を取得
今どのくらいのTransformを使っているのか調べたくなることがあります。本章ではHierarchy内の Transform数の取得方法 を紹介します。
Transforom数の全取得「hierarchyCount」
hierarchyCount を使用すると所属するTransformの数を取得できます。
上図の例ではTransformは8個存在します。
💻ソースコード : hierarchyCountのサンプル
using UnityEngine;
public class HierarchyCountSample : MonoBehaviour
{
void Awake()
{
Debug.Log(transform.hierarchyCount);
}
}
上のコードを実行すると「8」が出力されます。
子オブジェクトの数を取得「childCount」
childCountを使うと1階層下の子オブジェクトの数が取得できます。以下のようなイメージです。
子オブジェクトは3つです。
💻ソースコード : childCountのサンプル
using UnityEngine;
public class ChildCountSample : MonoBehaviour
{
void Awake()
{
Debug.Log(transform.childCount);
}
}
このプログラムを実行すると「3」が出力されます。
テク⑦親子関係を解除するDetachChildren
親子関係を操作するときに使えるテクニック DetachChilren を紹介します。DetachChildrenを使うとオブジェクトを解除できます。
動画のとおり子オブジェクト全てを子供から外すことができます。
💻ソースコード : DetachChildrenのサンプル
using UnityEngine;
public class GetChild : MonoBehaviour
{
void Awake()
{
transform.DetachChildren();
}
}
あまり頻出するテクニックではありませんが、こんな機能もあるんだなくらいの温度感で覚えておくとよいかと思います。
テク⑧スクリーン座標を絶対座標に変換するテクニック
座標にはいくつか種類があります。スクリーン座標とは画面の座標。分かりやすく言えば「マウス座標」です。例えば以下のようなペイントアプリを作ってみます。
マウス座標に追随して絶対座標上にメッシュを生成しているのです。
ここで重要なのは 「座標変換」 。TransformとCameraを使うことでスクリーン座標を絶対座標に変換できます。詳しくはこちらの記事で解説していますので、読んでみてください。
テク⑨絶対座標におけるスケール値を取得する「lossyScale」
深い階層にあるオブジェクトの絶対座標におけるスケールを取得したくなるときがあります。具体的には次のような場合です。各階層でスケール値を変更しています(分かりやすくGameObject名にスケール値を記載)。
深い階層にあるUnityChan_Bが実際にどう見えるでしょうか?
UnityChan_A
は 1倍 のまま。 UnityChan_B
は各階層のスケールを乗算した結果 1.8倍 になります。このスケール1.8は、 lossyScale を使うと簡単に取得できるのです。 💻ソースコード : lossyScaleのサンプル
using UnityEngine;
public class LossyScaleSample : MonoBehaviour
{
void Awake()
{
// 絶対座標上でのスケール
Debug.Log(transform.lossyScale);
}
}
このコンポーネントをUnityChanBにアタッチします。すると「1.8」が出力されます。各階層でスケールを調整する必要があり、絶対座標上でのスケール値を取得したいときに _lossyScale が活躍します。ぜひ覚えておきましょう。
パフォーマンスを意識したTransformの使い方
Unity開発しているとTransformの使い方1つで負荷が大きく変わることがあります。本章ではパフォーマンスを意識したTransformの使い方について解説します。 ちょっとした意識の積み重ねが完成時のゲームパフォーマンスを大きく変える のです。
Transformはキャッシュして使う
Transformにアクセスする方法は gameObject.transform
または transsform
でした。しかし、この方法は毎度 GetComponent<Transform>
をしているのと同じ。
つまり毎回 GetComponent の負荷を積み上げているのです。アクセス頻度が低いのであれば問題ありません。しかし、Transformは Update で毎フレームアクセスすこともありますよね。そんなときは キャッシュ して使うと良いです。
💻ソースコード : Transformをキャッシュしていない例
using UnityEngine;
public class LookAtPosSample : MonoBehaviour
{
void Update()
{
transform.Translate(new Vector3(0, 0, 1f));
}
}
💻ソースコード : Transformをキャッシュした例
using UnityEngine;
public class LookAtPosSample : MonoBehaviour
{
private Transform _cachedTransform;
void Awake()
{
// Transformインスタンスをキャッシュする
_cachedTransform = transform;
}
void Update()
{
_cachedTransform.Translate(new Vector3(0, 0, 1f));
}
}
一度 _cachedTransform
変数にAwakeのタイミングでキャッシュ。Update内ではキャッシュしたTransformにアクセスするのです。すると負荷自体は0になります。
もちろん、通常の使用では普通に transform
アクセスで問題ありません。大量のTransformが存在し、毎フレームアクセスしているといった状況で初めて効果のあるテクニックであることを覚えておいてください。
実は「transform」、「gameobject.transform」は内部でGetComponent<Tranform>を使っていません。Unity内部では最適化された ”Transform専用の取得関数” が用意されています。もちろんGetComponent<Transform>でもTransformを取得できます。しかし「transform」、「gameobject.transform」を使った方がGetComponent<Transform>よりパフォーマンスはよいのです。
ただし、いくら最適化したとしてもTransformのキャッシュ利用にはかないません。Transformにアクセスするときの最適な方法はTransformをキャッシュして使うことなのです。
位置と回転同時に更新するときはSetPositionAndRotationを使用
移動と回転を同時に実行する場合は、 SetPositionAndRotation を使いましょう。transform.localPosition
、 transform.localRotation
とそれぞれ処理を書いてしまいがちです。しかしそれは良くない方法です。移動と回転を同時に実行する場合は、以下の通り SetPositionAndRotationメソッド を使いましょう。
transform.SetPositionAndRotation(
new Vector3(1, 0, 0),
Quaternion.AngleAxis(30, new Vector3(0, 1, 0))
);
メリットは負荷軽減です。移動、回転それぞれ処理を書くよりパフォーマンスは良くなります。
hierarchyCapacityでパフォーマンスアップ
ゲーム中のTransformの階層が決まっている場合、 hierarchyCapacity
を指定することでパフォーマンス向上が期待できます。
void Awake()
{
transform.hierarchyCapacity = 10;
}
上記はTransformの階層を10までと指定したサンプルコードです。
なんのパフォーマンスがあがるのか、というとメモリ消費量です。公式リファレンスによるとSetParent、Destroyのパフォーマンスも向上するようです。
📚 参考サイト : Transform-hierarchyCapacity - Unity スクリプトリファレンス
Unityエディタ上で20階層ほどのTransformで検証しました。しかし、SetParentのCPU負荷に変化はありませんでした。階層の浅いTransformではあまり効果はないのかもしれません。
positionはlocalPositionより負荷が高くなる
とても細かい話ですが知っておかないと損するお話。 TransformのpositionはlocalPositionより負荷が高い です。下図はposition、localPositionへのアクセス負荷を計測したものです。positionはlocalPositionに比べて 負荷が約2倍 です。
枠で囲った数字は、1フレーム処理するためにかかったミリ秒数。positionは 15.33ミリ秒 。localPositionは 7.14ミリ秒 かかっています。2倍も多くかかる理由は Transformの階層数 に関係しています。
浅い階層では、以下の通りpositionとlocalPositionの負荷の差はほぼありません。
Transformの階層が深くなると顕著にpositionの負荷は高くなる のです。
もちろん、今回のサンプルは10万回position、localPositionにアクセスした負荷なので、通常使用においては気にしなくて大丈夫です。もし 大量のTransform のpositionにアクセスする必要がある場合は、意識してみてください。サンプルコードはこちらです。
using UnityEngine;
using UnityEngine.Profiling;
public class Perfomance : MonoBehaviour
{
CustomSampler localPosSampler;
CustomSampler posSampler;
public int n = 100000;
private void Awake()
{
localPosSampler = CustomSampler.Create("### LocalPosition ###");
posSampler = CustomSampler.Create("### Position ###");
}
void Update()
{
localPosSampler.Begin(this);
for(int i = 0; i < n; i++)
{
var localpos = transform.localPosition;
}
localPosSampler.End();
posSampler.Begin(this);
for (int i = 0; i < n; i++)
{
var pos = transform.position;
}
posSampler.End();
}
}
Transformの注意点
Transformの基本的な使い方からよく使うテクニック、パフォーマンスを意識した話と網羅的に解説してきました。最後にTransformを使う上での注意点を紹介します。
InspectorウィンドウのPositionはlocalPosition
勘の良い人はすでに気づいていると思いますが、InspectorウィンドウのPositionは相対座標、つまりlocalPositionです。
また、Rotationも同様、親基準の角度です。そして、Rotationと記述していますが、Inspectorウィンドウ上では「EulerAnglesの値」です。Quternionではありません。
Unityは左手座標系
ところで Unityは左手座標系 です。左手座標系とは左手の「親指をX軸」「人差し指をY軸」「中指をZ軸」にあわせたとき、
指先に向かうほど値が大きくなる座標系です。
つまり、X軸は右に行くほど、Y軸は上に行くほど、Z軸は奥に行くほど値は大きくなります。MayaやBlenderといった3DソフトとUnityを連携したときに、座標系が違う場合があるため注意が必要です。
Transform.Findはおすすめしない理由
子階層Transformを取得する方法はもう1つあります。文字列を指定した方法です。
transform.Find("a/b/c");
このような感じでGameObject名を指定します。 「/(スラッシュ)」 は階層です。
オオバ的にTransform.Findの使用はおすすめしません。次の記事で詳しく解説していますのでぜひ読んでみてください。
Updateでものを動かす場合はTime.deltaTimeを使う理由
Updateとは毎フレーム固定の秒数使っているわけではありません。ゲームをプレイしている「カクつき」を経験したことがあると思います。これは、Updateが毎フレーム固定ではない証拠です。フレームレート30のゲームだったら、30より下になるとカクつきを感じます。
カクつく原因は負荷です。重い処理を実行することでUpdateの実行タイミングは遅れるのです。遅れること自体は仕方なく、そのために今回紹介する「Time.deltaTime」が活躍します。
Time.deltaTimeとは、Updateにかかった時間です。つまり1フレームにかかった時間という理解で大丈夫。
サンプルコードを見てみましょう。Transformを毎フレーム1メートル処理です。このままでも動きはしますが正しくはありません。
void Update()
{
transform.localPosition += new Vector3(1, 0, 0);
}
↓正しくはこちら。
void Update()
{
transform.localPosition += new Vector3(1, 0, 0) * Time.deltaTime * 30;
}
* Time.deltaTime * 30
が追加されています。この「30」というのはフレームレートです。Application.targetFrameRate = 30;
で指定可能です。Time.deltaTime
は先ほどの説明のとおり、フレームにかかった時間です。この計算式によって、カクつきなどでフレーム時間が遅くなっても意図した位置にTransformを移動させることができるのです。
1フレームで移動させたい距離 * Time.deltaTime * フレームレート;
毎フレーム移動させたい場合の移動公式です。重要なのはTime.deltaTime。ぜひ覚えておきましょう。
TransformアクセスにGetComponentは使うな
コンポーネントにアクセスする際、 GetComponent を使います。しかし、Transformに関しては特別で、直接transformとアクセスするのがおすすめです。
// ×
gameObject.GetCpomponent<Transform>()
↓
// ◯
gameObject.trasform
理由はパフォーマンスです。次の図は大量のTransformにアクセスした負荷を計測したものです。
GetComponentよりtransformの方が 3倍早い のです。これはUnityがTransformに最適化をしているため。
実際にTransformは内部的に専用の取得メソッドが呼ばれています。Transformにアクセスする際は、GetComponentではなく「transform」を使ってみてください。
FixedUpdateで動かしてはいけない
最初に結論、FixedUpdateでTransformを動かしてはいけません。「ドキ!!」とびっくりした人もいるかも知れません。 FixedUpdateは物理演算処理で使うために用意されたイベント関数 です。Unityのイベント関数内で更新型のメソッドは3つあります。
- Update
- LateUpdate
- FixedUpdate
この中で、FixedUpdateは 0.02秒 ごとに一定の間隔で実行されます。
画面を更新するスピードをフレームレートやFPSと言います。例えばフレームレート30の場合、1秒に30回画面を更新することになるのです。仮にフレームレート30のゲームの場合、1フレーム0.03秒。イベント関数Updateは0.03秒に1回実行されるのです。2フレームで0.06秒ですね。
一方FixedUpdateは0.02秒に1回実行するため、Updateが2フレーム実行したら3回実行してしまうのです。(0.06秒 ÷ 0.02秒 ≒ 3回)。
FixedUpdateはフレームレートとは別軸でタイムを刻んでいる ということです。つまり、FixedUpdateでTransformを動かしてしまうと意図しないアニメーションになります。本来1フレーム分動かしたい処理が2回走ってしまうこともあり、動きがずれてしまうのです。
結論、 FixedUpdateは物理演算系の処理に使いましょう。
Unity Transformのまとめ
Transformについて徹底的に解説してきました。この記事を簡単にまとめます。
①Transformとはゲーム開発に必須コンポーネント
②TransformはすべてのGameObjectにセットされている
③Transformの重要機能4種「移動」「回転」「拡大縮小」「階層化」
④よく使うTransformテクニック9つを紹介
⑤使い方しだいでTransformのパフォーマンスは変わる
こんな感じです。
普段何気なく使っているUnityの移動や回転は、Transformの機能を使っているということがわかったと思います。 ゲーム開発にTransformは必須 です。つまりTransformの正しい使い方を理解することはゲーム開発にとって超重要だということです。
1回読んだだけでは理解できなかった部分もあると思います。ぜひこの記事をブックマークして、何度も読み返してみてください。同時にUnityを動かしてみましょう。効率の良い学び方はインプットしながらアウトプットすること です。ぜひ、この記事を片手にUnityを動かしてTransformの理解を深めてみてください。
この記事があなたの人生を豊かにする手助けになるとうれしいです。
この記事が気に入ったらフォローしよう
「Unity初心者大学」というUnity初心者向けのYouTube始めました!!
ぜひチャンネル登録をお願いします!
最後まで読んでいただきありがとうございました!
すばらしいUnityライフをお過ごしください。
- Unity2022.3.18f1