【Unity】GameObject.SetActive() は重い処理のようだ

Unityで表示・非表示を実装する時にまず思い浮かべるのが「GameObject.SetActive()」です。 UI周りでも比較的頻度が高い処理だと思います。 私も実際のゲームで何の疑問もなく使っていたのですが、これがメモリーを圧迫する(処理落ち)の原因になるという話を最近聞いたので、少し調べてみました。

GameObject.SetActive()

とりあえず、Textを100個用意して「GameObject.SetActive()」で点灯をさせてみます。 [c] private bool stop = true;

private IEnumerator UpdateCoroutine(GameObject text) { var delayTime = new WaitForSeconds(1.0f); while (stop) { text.gameObject.SetActive(false); yield return delayTime; text.gameObject.SetActive(true); yield return delayTime; } }

[/c]

これをUnityの「Profiler」を使って測定します。

なお、私はこれまで「Profiler」を含めてのパフォーマンス・チューニングをしてきていなかったので、いろいろなサイトを見て挑戦をしてみました。 CPUプロファイラでパフォーマンスを改善する - 前編 CPUプロファイラでパフォーマンスを改善する - 後編

CPU Usage

まずは「CPU Usage」です。 特徴として「GameObject.SetActive()」をtureにした時に「CG Alloc」が増大して負荷がましていました。

2019 1 30 2

2019 1 30 1

GC Alloc

ガベージコレクション(GC)を使ってメモリを確保した時に発生します。 具体的にはヒープ領域内にメモリを確保するために、GCが空き領域を検索するコストになります。

ヒープ領域に関してはこちらのサイトが図も含めてとてもわかりやすいです。 メモリ管理 - コンピューターの基礎知識

GC Alloc」が増加しているということから、GameObject.SetActive()を使った時に、再度メモリの割当作業が発生しているということです。 UI の最適化に関するその他のヒントとテクニック

Canvasのリビルドとリパッチ処理

Profilerを見ると、TimeLine上に少し色の違った処理が行われています。 2019 1 30 3

  • UGUI.Rendering.UpdateBatches
  • Canvas.SendWEillRenderCanvased

Canvas上での変更点を修正するために、レイアウトとメッシュの再計算が行われる。これをリビルド処理といいます。 「バッチの処理」とは、CanvasがそのUI要素を表すメッシュを組み合わせ、Unityのグラフィックス・パイプラインに送るための適切なレンダリングコマンドを生成する処理のことです。 上記のスクリプトはこの「リビルド処理」「バッチ処理」になります。 Unity - UIの基礎 設計からパフォーマンスを意識した開発(UI編)

回避方法

通常、実装する場合には「GameObject.SetActive()」を使っても問題ないと思いますが、大量にGameObjectの表示・非表示周りを触る場合には別の方法を模索したほうがよいかもしれません。

  • 移動させる
  • アルファで調整(UGUI)
  • CanvasRenderで調整

移動させる

単純にカメラの外にPositionを移動させて表示させなくする処理です。 見た目を一切触らずに実装ができます。

アルファを調整

UGUIのTextやImageなどColorを調整して表示・非表示を実装します。 [c] var delayTime = new WaitForSeconds(1.0f); var alphaZero = new Color32(0xff, 0xff, 0xff, 0x00); var alphaOne = new Color32(0xff, 0xff, 0xff, 0xff); Text text = gameObject.GetComponent<Text>(); if (text != null) { while (stop) { text.color = alphaZero; yield return delayTime; text.color = alphaOne; yield return delayTime; } } [/c]

こちらの方法だと「GC Alloc」が発生しません。 ただ、「リビルド」「バッチ処理」が発生しています。

CanvasRenderで調整

CanvasRenderを使う場合も「GC Alloc」「リビルド」「バッチ処理」が発生しません。 [c] private IEnumerator UpdateRendererCoroutine(Text gameObject) { var delayTime = new WaitForSeconds(1.0f); var cr = gameObject.GetComponent<CanvasRenderer>(); while (stop) { cr.SetAlpha(0.0f); yield return delayTime; cr.SetAlpha(1.0f); yield return delayTime; } } [/c]

CanvasRenderで調整するのが一番良い方法に思われます。

最後に

タイトルで「GameObject.SetActive()」は重い処理と書きました理由は「GC Alloc」「リビルド」「バッチ処理」が発生するためです。 ただ「重い」と書きましたが、あくまで一度に大量の処理を行った場合になりますので、もし処理落ちなどが起こった場合に別の方法を考えるのがよいと思います。

【Unity】構造体(struct)について

c#の構造体(struct)は結構前からどういった存在なのか気になっていました。 クラスとの違いはどの部分なのか、どのようなメリットがあるのか、など知っておけば、パフォーマンスを加味したコードを書けるのではないか。 これまで個人的に全く使って来ていなかったのですが、少し時間をとって調べてみました。

構造体

とりあえず適当に構造体を作ってみます。 [c] public struct StructTest { private int id; private string name;

public int Id
{
    get { return _id; }
}

public string Name
{
    get { return _name; }
}

public StructTest(int id, string name)
{
    _id = id;
    _name = name;
}

public void SetId(int id)
{
    _id = id;
}

} [/c]

構造体を作成する時に struct を名前の前につけます。 [c] public struct Hoge {

} [/c]

これを任意の場所で利用をします。 [c] var test = new StructTest(1, "たろう"); Debug.Log(test.Id); // 1 Debug.Log(test.Name); // たろう [/c]

値型 参照型

構造体は「値型」と呼ばれています。

  • メモリ管理がスタックなので、宣言した時点でメモリが確保される
  • 値渡し

メモリ管理 スタック

メモリ管理には「ヒープ型」「スタック型」があります。 構造体は「スタック」でクラスは「ヒープ」で管理されています。

ヒープ型の場合、GCが走る時にUnityでは結構は負荷がかかります。

メモリ管理についてはこちらのスライドがわかりやすかったです。 C# ゲームプログラミングはホントにメモリのことに無頓着でいいの?

値渡し

「値渡し」の場合次のような挙動になります。 [c] var hoge = new StructTest(); var hoge2 = hoge; Debug.Log(hoge.Id); // 0 Debug.Log(hoge2.Id); // 0 hoge.SetId(19); Debug.Log(hoge.Id); // 19 Debug.Log(hoge2.Id);// 0 [/c]

構造体の場合、それぞれの変数が独立(値を渡し)しています。 hogeの値を変更してもhoge2には反映されません。

クラスの場合「参照型」になるので、値の変更が反映されます。

構造体でできないこと

クラスでできることが構造体でできない場合があります。

  • 継承ができない
  • staticが定義できない

[c] // 継承できない public struct StructChild : StructTest {

}

// staticな構造体は作れない public static struct StructTest {

} [/c]

最後に

今回構造体について色々調べてみたのですが、実際に使い所がいまいちよくわかりませんでした。 使い方については公式ではこのような記載がありました。 構造体の仕様 (C# プログラミング ガイド) クラスまたは構造体の選択

上記を読むとメモリ解放周りでメリットがありそうに感じますが、実際にそこまでシビアな状況がないので、とりあえずはクラスで定義してメモリの仕様状況によって構造体を考えていこうかと思いました。

【Unity】C#のIEquatableとは

他の人のコードを読むときに思わない出会いがよくあります。 今回調べてみようと思った「IEquatable」もその中の一つです。 c#が用意しているインターフェースということはわかるのですが、どのような時に使ったら良いのかを簡単ですが調べてみることにしました。

IEquatable

IEquatableにはEqualsというメソッドが定義されています。 IEquatable Interface

[c] namespace System { public interface IEquatable<T> { bool Equals(T other); } } [/c]

実装例

[c] public class Hoge : IEquatable<Hoge> { public int id;

public Hoge(int value)
{
    id = value;
}

public Hoge(){}

public bool Equals(Hoge other)
{
    if (other == null)
    {
        return false;
    }

    return this.id == other.id;
}

} [/c]

Equals()を使ってオブジェクト同士を比較します。 [c] var test1 = new Hoge(1); Hoge test2 = new Hoge(10);; var test3 = test1; var text3 = new Hoge(1);

Debug.Log(test1.Equals(test2)); // false Debug.Log(test1.Equals(test3)); // true Debug.Log(test1.Equals(text3)); // true [/c]

継承して利用する

IEquatableを使うメリットとして、継承したクラスとの比較ができることです。

[c] public class HogeChild : Hoge,IEquatable<HogeChild> { public string name;

public HogeChild(int value, string text)
{
    id = value;
    name = text;
}

public bool Equals(HogeChild other)
{
    if (other == null)
    {
        return false;
    }

    return base.Equals(other) &amp;&amp; (this.name == other.name);
}

}

[/c]

HogeオブジェクトとHogeChildオブジェクトを比較してみます。 [c] var test1 = new Hoge(1); var test4 = new HogeChild(1, "たろう"); var test5 = new HogeChild(1, "たろう"); var test6 = new HogeChild(1, "花子");

Debug.LogError(test4.Equals(test5)); // true Debug.LogError(test4.Equals(test6)); // false Debug.LogError(test4.Equals(test1)); // true [/c]

このようにオブジェクトを比較できます。

最後に

ObjectクラスにはそもそもEquals(object obj)が定義されています。 IEquatableを使うメリットは型のキャストがないので、パフォーマンスが良いみたいです。 IEquatableを完全に理解する

ただ、こちらの記事を見る限り使い所がとても難しそうに思える、かつ実際の使い所がピンとこないので個人的には使う機会がない気がしています。

【Unity】MonoBehaviourについて調べてみた

Unityでスクリプトを作成する際にデフォルト「MonoBehaviour」を継承したクラスになります。 普段余り考えて使っていなかったのですが、このMonoBehaviourが一体どういったクラスなのかに興味をもち少し調べてみました。 簡単ですがブログに記事にまとめていきます。

MonoBehaviour

UnityのドキュメントにはMonoBehaviourはこのように説明がされています。 MonoBehaviour - リファレンス

このページを見れば、だいたいどのようなことができるのかがわかってきます。

MonoBehaviourの継承ツリー

MonoBehaviourは、次のようにクラスを継承して作られています。 それぞれで使える機能が違っています。

  • Object
  • Component
  • Behaviour

Object

MonoBehaviourの一番最下層の基底クラスが「Object」です。 Unityを少しでも使っている方ですと、GameObjectを生成するときにもしかしたらこの型を見たことがあるかもしれません。 [c] // Instantiate()はObjectにメソッドが定義されています。 var oj = Instantiate(_prefab, transform);

// DestoryもObjectに定義されています。 Destroy(_object);

[/c]

そのため、Instantiate()で生成するためには、Objectを継承したクラスでないといけません。 Destory()も同じです。 逆にいえば、Destory()だけを使いたい場合はMonoBehaviourを継承するのではなく、Objectを継承したほうがパフォーマンスがよくなります。

GameObject

Objectによく似たクラスに「GameObject」があります。 Unityを使っていると、この「GameObject」の方が馴染みがあると思います。

「GameObject」はObjectを継承したクラスで、UnityではおなじみのGetComponent()やSetActive()、transformなど使うことができます。 [c] obj.SetActive(false);

obj.transform.localPosition = new Vector3(1f, 1f, 0f); [/c]

GameObjectはsealedで定義されているため、継承元としては使うことができません。

Component

「Object」を継承しているクラスが「Component」になります。 GetComponent()やtransform、SendMessageなどが利用できます。先程の「GameObject」とよく似た機能が使えるようです。 こちらは継承可能なクラスです。

[c] var go = GetComponent<GameObject>(); [/c]

Behaviour

「Component」を継承しているクラスが、Behaviourです。 ここでは2つのメソッドが定義されています。個人的にenabledはよく使っています。 [c] // コンポーネントを有効・向こうを制御する _behaviour.enabled = true;

// アクティブかつコンポネントが有効化どうか var hoge = _behaviour.isActiveAndEnabled; [/c]

有効・無効

コンポーネントを有効・無効にするとは、次の画像だと「MonoText」のスクリプトにチェックを操作します。 チェックを外すとスクリプトは動きません。 2019_01_19_memo

MonoBehaviour

最後にMonoBehaviourではどのようなことができるのかを調べていきたいと思います。 代表的なのがStartCoroutine()です。 [c] StartCoroutine(HogeCorutine());

private IEnumerator HogeCoroutine() { yield return null; } [/c]

非同期処理をUnityで実装するにはコルーチンを使うのが最も手っ取り早い方法です。 MonoBehaviourを継承したクラスではこのコルーチンを使うことができます。

Invoke()

Invoke()は特定の場所でActionを呼び出すことができます。 [c] // Hoge()を0.0fで呼び出しています。 monoObject.Invoke("Hoge", 0.0f); [/c]

print

これはDebug.Log()を呼び出しているだけですので、直接呼び出しても良い気がしました。 [c] monoObject.print("メッセージ"); [/c]

最後に

Unityを使っているとMonoBehaviourを継承したクラスを作成しがちだと思います(デフォルトで設定されているので) 今回色々調べてみて、必ずしもMonoBehaviourを使わないくても良いとわかったので、必要に応じでクラスを作成していこうと思います。

【Unity】UniRxの止める

UniRxを使っていてタイマーみたいなものを実装したときに必要なくなった場合にどう止めるのかを調べたことをメモとしてブログに書いていきたいと思います。 すでに先人が丁寧にWebで公開していましたので、それをそのまま参考にしました。

UniRxを止める

今回実装が必要になったのが、Observable.Interval()を止める方法です。 任意の場所で止める際には次のような実装を行いました。 [c] int count = 0; Var disposable = new SerialDisposable ();

disposable.Disposable = Observable.Interval (System.TimeSpan.FromSeconds (1)).Subscribe (_ => { count += 1; Debug.LogError (count); }).AddTo (this); [/c]

UniRxの挙動を止めたいタイミングで、IDisposableのDisposeメソッドを使います。 [c] disposable.Dispose(); [/c]

AddTo()

AddTo()を使うことで、引数にしたGameObjectが破棄されたときにUniRxも止まります。 通常はこのAddTo()を忘れずに使用していれば問題がないかと思います。 [c] private ReactiveProperty<string> name = new ReactiveProperty<string>(); name.Subscribe(SetName).AddTo(this); [/c]

Dispose()

Observable.Intervalは戻り値として IDisposableのオブジェクトを返します。 これを使ってDisposeすればUniRxが止まります。 c#のRxでは、解除、取り消しといった操作をIDisposableを使います。 いまさら聞けないReactive Extensions.1

さきほどの記述ではローカル変数にこのIDisposableを格納できるSerialDisposableを使っています。

  • SerialDisposable
  • SingleAssignmentDisposable
  • CompositeDisposable

IsDisposed

Rxが停止したかどうかはIsDisposedを使えば確認ができます。 [c] if (!disposable.IsDisposed) { disposable.Dispose(); } [/c]

SerialDisposable

IDisposableのオブジェクトを保持できるクラス。 上書き可能、上書きをした場合に前のRxは停止する。

SingleAssignmentDisposable

SerialDisposableとほぼ同じだが、上書きすると例外が発生する。

CompositeDisposable

Listのように複数のIDisposableを格納でき、同時に複数のRxを止めることができます。 [c] Var compDisposable = new CompositeDisposable (); compDisposable.Add (disposable); compDisposable.Add (disposable2); compDisposable.Add (disposable3);

// 削除もできます compDisposable.Remove (disposable2);

compDisposable.Dispose (); [/c]

最後に

UniRxは便利な機能ですが、停止処理を上手く書かないと大変なことになりそうですので、利用用途を限定して使うのが安全です。 できれば、Dispose()を使って止める手段は取らずに、AddTo()だけで挙動を制御したいです。

【参考】 UniRxのシンプルなサンプル その9(TimerとInterval 一定時間後に実行

[UniRx]購読を停止する

UniRxはこちらから入手できます。

【Unity】SRDebuggerを使ってデバックを行う

Unityでデバックをするときに「SRDebugger」というAssetを使うと色々なことが捗ります。 エディタ拡張をして独自にデバック機能を作る方法もあるのですが、この「SRDebugger」を拡張したほうが手間も工数も短縮して利用ができます。 私も当然の如く「SRDebugger」を使って、エラーの確認などを行っていたのですが、実際にどうやってプロジェクトへ導入されたか気になったので調べてみました。

SRDebugger

SRDebuggerは外部Assetです。 有料のAssetなのですが、その値段以上に便利に利用ができます。 まずこちらをダウンロードし、Unityのプロジェクトへインストールを行います。

導入

SRDebuggerを使うためにはどこかのタイミングで初期化を行う必要があります。 その際に次のコードを呼び出します。 [c] SRDebug.Init(); [/c]

設定

SRDebuggerをインストールすると、Winodw => SRDebuggerというメニューが増えています。 2019_01_13_01

SettingWindowを開きます。 2019_01_13_02

こちらでSRDebuggerの各種設定ができます。 特に触るところはないのですが、「Layout」の「Entry Trigger」を好きな場所に設定することができます。

Entry Trigger

画面のどこをタッチ(長押し)するとデバックメニューを表示することができるかを設定できます。 「Bottom」」なら画面の真ん中下を長押しすると、デバッグメニューへ飛びます。

【デバックメニュー】 2019_01_13_03

見方

SRDebuggerの簡単な見方を説明します。 左側にタブがあります。

  • SYSTEM
  • CONSOLE
  • OPTION
  • PROFILER

CONSOLE

CONSOLEにはUnityのエディタ上で吐き出されるDebug.Logを見ることができます。 実機でも見れるので、どこで何が起こっているかがわかりやすくなります。

画面右上のピンマークを押すと、SRDebuggerを閉じてもログだけ見ることができるのでとても便利です。

OPTION

OPTIONは初期段階では何もありません。 ここでは自身が拡張をして色々な数字の設定やメソッドの呼び出しをすることができます。 SRDebuggerの便利な点はこの拡張性の高さにあると個人的に思っています。

OPTIONの拡張

OPTIONの拡張に関しては、ドキュメントを見れば何ができるかがひと目でわかります。 SRDebugger ドキュメント

partial を使ってSROptionsクラス内に定義をします。 [c] public partial class SROptions { } [/c]

プロパティ

他のクラスのようにプロパティを定義すると、SRDebugger上では次のように表示されます。 [c] private float scale = 1f; public float Scale { get { return scale; } set { _scale = value; } } [/c]

2019_01_13_04

ただこれでは変更した値がどこにも反映されませんのでPlayerPrefsを使って保存をして使います。 [c] public float Scale { get { return PlayerPrefs.GetFloat("hoge", 0f); } set { PlayerPrefs.SetFloat("hoge", value); } } [/c]

値を制限する場合にはNumberRangeを使います。 [c] [NumberRange(0,10)] public float Scale {

} [/c]

メソッド

メソッドも他のクラスと同じように定義できます。 [c] [Category("Method")] [DisplayName("外部メソッド")] public void HogeMethod() { Debug.LogError("これはほげです"); } [/c]

CategoryとDisplayNameを付けると画面が見やすくなります。

2019_01_13_06

最後に

簡単ですがSRDebuggerの使い方について書きました。 有料のAssetではありますが、とても便利に使えると思いますので、興味を持っていただけたら幸いです。

【Unity】UniTaskによるasync/await を使った非同期処理

少し前に身内内での勉強会で取り上げられていたのが「UniTask」です。 現状コルーチンを行っている非同期の処理を c#のasync/awaitを使って良い感じでUnityで使えるとのことで、今後新規でゲームを作成するときに利用する機会が多くなるかもしれません。 簡単ですが「UniRx」でどういったことができるのかを調べてみました。

UniTask

「UniTask」はUniRxをプロジェクトへ追加をすれば利用することができます。

導入

現状(2019/1)の最新のUnityバージョンである2018.3.0.f2を使っています。 まずはAssetStoreから「UniRx」をダウンロードし、プロジェクトへインストールします。 https://assetstore.unity.com/packages/tools/integration/unirx-reactive-extensions-for-unity-17276

namespace

UniTaskを使うときにはnamespaceを指定します。 [c] using UniRx.Async; [/c]

async/await

今まではコルーチンを使っていた非同期処理がasync/awaitでも実現することができます。

非同期メソッド

頭にasyncをつけたメソッドのこと。 [c] async void Start() {

} [/c]

このメソッド内にawaitを実装することができる。 例えば1000ミリ秒待つときには「UniTask.Delay()」を使う。 [c] async void Start() { await UniTask.Delay(1000); Debug.Log("1000㍉秒待つ"); } [/c]

こちらはコルーチンだと次のような記述で書くこともできました。 [c] private IEnumerator Hoge() { yield return new WaitForSeconds(1.0f); } [/c]

UniTaskにはdelayを含めてたくさんの便利なメソッドが良いされています。

並列実行

UniTask.WhenAll()を使えば、引数で指定した処理を並列に実行してくれます。 すべての処理が終わればその結果を受け取ることができます。 [c] async UniTask DoAsync() { var task1 = GetString("太郎",1); var task2 = GetString("次郎",100); var task3 = GetString("三郎",10); var (name1, name2, name3) = await UniTask.WhenAll(task1, task2, task3); Debug.Log(name1 + name2 + name3);

} [/c]

タプル

ここで見慣れないのはタプル記法で書かれた箇所です。 タプル記法について [c] var (name1, name2, name3) = await UniTask.WhenAll(task1, task2, task3); [/c]

名前のない型がタプルと呼ばれています。 UniTask.WhenAll()を使えば結果をこのタプルで取得ができます。

ラムダ式

UniTask.Run()を使うと、ラムダ式内の処理を実装できます。 戻り値も設定ができます。

[c] var hoge = await UniTask.Run(() => { return " hoge "; }); Debug.Log(hoge); //hogeと出力 [/c]

遅延

UniTask.Delayを使えば、引数に指定したミリ秒処理を待機させることができます。 [c] await UniTask.Delay(1000); [/c]

その他、UniTask.DelayFrameを使えば、引数に指定したフレーム分待機をさせることができます。 [c] Await UniTask.DelayFrame(1f); [/c]

PlayerLoopと同期させる

PlayerLoopに合わせて、特定の処理を実行できます。 [c] await UniTask.Yield(PlayerLoopTiming.FixedUpdate); [/c]

GetAsyncEventHandler

イベントの発火を待機することができます。 例えば、あるボタンが押されるまで待機するなどの処理ができます。 [c] var buttonEvent = _button.onClick.GetAsyncEventHandler(CancellationToken.None); await buttonEvent.OnInvokeAsync(); Debug.Log("ボタン押した"); [/c]

コルーチンを待つ

UniTaskを使えばコルーチンと併用をする機会は少ないかもしれませんが、特定のコルーチンを待つこともできます。 [c] await HogeCoroutine;

private IEnumerator HogeCoroutine() { yield return new WaitForSeconds(1.0f); } [/c]

キャンセル処理

UniTaskの欠点はキャンセル処理が面倒くさいことです。 MonoBehaviourに依存していないので、GameObjectを削除しても消えません。

  • CancellationTokenを発行する
  • CancellationTokenSource.Cancel()を実行する
  • 非同期処理にキャンセル時の挙動を記載する

CancellationTokenを発行する

ローカル変数なりグローバル変数なりでCancellationTokenSourceを定義し、必要な非同期処理にtokenを渡して上げます。 [c] // CancellationTokenを発行する private CancellationTokenSource _tokenSource;

private void Start() { _tokenSource = new CancellationTokenSource();

// 引数にCancellationTokenを渡す CancellTest(_tokenSource.Token).Forget(); } [/c]

CancellationTokenSource.Cancel()を実行する

必要なときにCancel()を呼びます。 [c] void OnDestroy() { _tokenSource?.Cancel(); } [/c]

非同期処理にキャンセル時の挙動を記載する

CancellationTokenSource.Cancel()を呼び出したときに「例外」が発生するようにします。 [c] await UniTask.Delay(TimeSpan.FromSeconds(5), cancellationToken: cancellationToken);

try { Debug.Log("正常"); } catch (OperationCanceledException) { // キャンセル時に実行したい処理があればtry~catchで捕捉 Debug.Log("Canceled"); throw; } [/c]

キャンセル時の挙動は例外を飛ばす方法以外にも、if文でも判定ができたりしますが、複雑な処理には不向きな気がします。

最後に

とりあえず、UniTaskを少し触ってみました。 コルーチンの処理に慣れていると、UniTaskによる非同期処理は複雑な感じがしました(特にキャンセル時) 色々と新しいいことができそうな予感はしているので、早く使えるようになりたい。

UniRxはこちらから入手できます。