GetComponentの負荷検証
気になる内容の記事があったので検証してみました
qiita.com
気になる点
- Dictionaryのインデクサを使用している←TryGetValueを使用しないと負荷が高い(ソースは忘れた…)
- transform.Rotationの実装次第ではあまり検証になっていない…気がする
ちょっと修正版作成
コードそのままお借りしてます+無理やり適当な名前で追加してすみません
極力transform取得の処理負荷が出るようにlocalPositionにしています
public class PerformanceTest : MonoBehaviour { private readonly Dictionary<Type, object> _componentCache = new Dictionary<Type, object>(); public new T GetComponent<T>() where T : Component { var type = typeof(T); if (_componentCache.ContainsKey(type) == false) { var component = base.GetComponent<T>(); if (component == null) component = gameObject.AddComponent<T>(); _componentCache.Add(type, component); } return (T) _componentCache[type]; } private readonly Dictionary<Type, Component> _componentCacheFast = new Dictionary<Type, Component>(); public T GetComponentFast<T>() where T : Component { var type = typeof(T); Component component = null; if (_componentCacheFast.TryGetValue(type, out component) == false) { component = base.GetComponent<T>(); if (component == null) component = gameObject.AddComponent<T>(); _componentCacheFast.Add(type, component); } return (T) component; } private readonly int max = 10000; private CustomSampler samplerNormal; private CustomSampler samplerDefaultGetComponent; private CustomSampler samplerOverrideGetComponent; private CustomSampler samplerGetComponentFast; private CustomSampler samplerCache; private Transform cacheTransform; private void Awake() { samplerNormal = CustomSampler.Create("transform"); samplerDefaultGetComponent = CustomSampler.Create("base.GetComponent"); samplerOverrideGetComponent = CustomSampler.Create("GetComponent"); samplerGetComponentFast = CustomSampler.Create ("GetComponentFast"); samplerCache = CustomSampler.Create("cacheTransform"); cacheTransform = base.GetComponent<Transform>(); } private void Update() { Vector3 pos; //普通の使い方 samplerNormal.Begin(); for (var i = 0; i < max; ++i) pos = transform.localPosition; samplerNormal.End(); //GetComponentおじさん samplerDefaultGetComponent.Begin(); for (var i = 0; i < max; ++i) pos = base.GetComponent<Transform> ().localPosition; samplerDefaultGetComponent.End(); //貼るだけで速くなるコード(?) samplerOverrideGetComponent.Begin(); for (var i = 0; i < max; ++i) pos = GetComponent<Transform> ().localPosition; samplerOverrideGetComponent.End(); //貼るだけで速くなるコードTryGetValue版 samplerGetComponentFast.Begin(); for (var i = 0; i < max; ++i) pos = GetComponentFast<Transform> ().localPosition; samplerGetComponentFast.End(); //メンバ変数でキャッシュ samplerCache.Begin(); for (var i = 0; i < max; ++i) pos = cacheTransform.localPosition; samplerCache.End(); } }
結果
丁度2018が正式リリースされていたので2018.1.0f2で検証
transform | 0.58ms |
GetComponent | 0.846ms |
DictionaryIndexer | 3.17ms |
DictionaryTryGetValue | 2.06ms |
transformCache | 0.399ms |
TryGetValueにするだけで約65%軽減(どっちにしろ重いけど、、)
キャッシュとの違いは10倍近く
結論
何が言いたいかというとキャッシュはしたほうがいいよ
Dictionaryのインデクサは使わないほうがいいよ
という話でした
Unityを使って水っぽい表現作ってみた
今GravityDAZE2をやっているのですが、水表現が面白いことになっていたので自分でUnityを使って実装してみました
水https://t.co/nvvfGaXU1g pic.twitter.com/fxkgM5oWpV
— 渡邉太郎 (@taro_b19) 2017年1月29日
背景が殆ど無いので分かりにくいと思いますが…
シェーダ
モデルの裏側をキャプチャしたものを使用して法線で歪ませて描画する方法で実装しました
GrabPass { "_WaterGrab" }
GrabPassを記述すると描画直前のバッファを_GrabTextureにコピーしてくれますが、上のようにTexture名を指定することでこのGrabPassが初めて呼ばれたときのみコピーされるみたいです。
例えばこのシェーダで4個メッシュを描画すると1個めが描画されたときのみGrabPassが走り、2個め以降は1個めで使用したGrabTextureを再利用する
これで負荷を最小限にできます!
あとは一般的なスクリーン投影計算とリム計算ぐらいです。
half kScale = 1.7777; half2 enc; enc = i.viewNormal.xy / (i.viewNormal.z+1); enc /= kScale; i.viewNormal.xy = enc * 0.5;
フラグメントシェーダ内のこの処理は法線方向をスクリーンでいい感じに見えるように変換する計算式です
UnityCG.cgincのEncodeViewNormalStereo関数を持ってきました
Unityの法線テクスチャ描画時に呼ばれている関数なのですが、使ってみたら結構いい感じになったのでこれにしました。
Cloth
水の揺れ表現にはClothを使ってみました
パラメータはちょこちょこっと変えていい感じにして外部から少しだけ力を加えて動くようになっています。
頂点設定は一部の頂点を動きにくい設定にして凸凹感が出やすいようにしました。
多分ここをきれいに設定するともっと柔らかい歪みになると思います。
0.1だと動かなすぎるし大きい値だと動きすぎて本来の一から離れすぎてしまうので0.15~0.2ぐらいがちょうどよかったです
Cloth工夫次第でいろいろできそうな予感がします
これ使って何か作りたい
UnityEditor再生開始時のイベント取得
UnityEditor再生時のイベント
実行、非実行時でモデルプレビュー出来るツールを作成しているのですが、再生時にモデルを一旦リセットして再生成するスクリプトを作成したい
ということでこんなスクリプトを書くと
ここのボタンを押す度にOnPlayModeStateChanged
メソッドが呼ばれるようになります
void OnEnable () { EditorApplication.playmodeStateChanged += OnPlayModeStateChanged; } void OnDisable () { EditorApplication.playmodeStateChanged -= OnPlayModeStateChanged; } void OnPlayModeStateChanged () { Debug.Log("Changed"); }
ここで問題なのが思っているよりも多くこのイベントが発行されるということです しかし、このメソッド内で
EditorApplication.isPlaying
EditorApplication.isPlayingOrWillChangePlaymode
の値を見るとより詳細なStateが確認できます
- 再生ボタン押下
- OnChanged : isPlaying=false, isPlayingOrWillChangePlaymode=true ※このタイミングで生成すると実行開始時にCleanupエラーが出る
- 開始するまで少し待つあれ 実行ログ出力開始
- OnChanged : isPlaying=true, isPlayingOrWillChangePlaymode=true ※実行開始、生成すると通常通り動作する
- 停止ボタン押下
- OnChanged : isPlaying=true, isPlayingOrWillChangePlaymode=false ※停止開始、生成するとあれ
- 完全停止待つ
- OnChanged : isPlaying=false, isPlayingOrWillChangePlaymode=false ※完全停止Editorの挙動開始
つまりisPlaying==isPlayingOrWillChangePlaymodeを見ることで実行、Editorに完全移行したかどうかが取れ、逆の場合は遷移する直前の処理が書けます
このままだとPauseボタンでも呼ばれてしまうので上記の条件が一致したらフラグを立てておくことでPauseを無視することができます でもOnDisableとOnEnableだけで上手く頑張れば出来るっぽい雰囲気 とりあえずこのプロパティと組み合わせれば厳密にStateを取れたという感じ
Unity5.3でExecuteInEditModeとDontDestroyOnLoadを同時に使用した際の挙動
Unity5.3にアップデートしてかなり発見しにくいバグ?を見つけたのでメモ
問題
[ExecuteInEditMode] public class Test : MonoBehaviour { void Awake() { DontDestroyOnLoad (gameObject); } }
こんなクラスがあった時にEditor上でHierarchyにスクリプトを追加しようとすると表示されず、GameObject毎どこかに行ってしまいます。
FindObjectOfType
を使用するとどこかに存在していることは分かるのですが、困ったことにどこにも見つかりません。
原因
原因はDontDestroyOnLoadの挙動が変更されているからのようです。 このメソッドが呼び出された直後、普段は隠蔽されている”DontDestroyOnLoad”シーンにGameObjectが移動される挙動になっていました。
gameObject.scene.name
でログを出力するとDontDestroyOnLoadシーンに飛ばされていることが分かります。
このシーンは実行直前にHierarchy上で確認することが出来ます。
(実行が開始すると消えるので動画キャプチャから抜き出しました)
解決策
解決策としては実行している時のみ指定をすれば一応動作します
if (Application.isPlaying) { DontDestroyOnLoad (gameObject); }
滅茶苦茶ハマりました…
ScriptableObjectのメンバリネーム
ScriptableObjectを使用している時に変数定義を変えたくなることがあると思います。
例えばpublicで定義していたけどやはりprivateのSerializeFieldにしてスクリプトからは読み込み専用にしたくなった等
その時は
[System.Serializable] public class Data { [SerializeField] [UnityEngine.Serialization.FormerlySerializedAs ("name")] private string _name; }
これで変数名の移行が行われます。
この状態でプロジェクトの保存を行うと_nameに値が移行されていることを確認することが出来ます。
全ての関連ファイルの変更が行われたことを確認できたらFormerlySerializedAs属性は消しても大丈夫です。
※これに限らないですがPrefabと同じようにSaveProjectを実行しないと見た目は差し替わっていてもファイルに書き込まれていないため、書き込まれていることを確認してから属性削除は行う必要があります。
ターゲットの周りをくるくる回るサンプル
なんとなく作ったので記事にします
gist.github.com
ターゲットの周りを常に一定間隔で回るサンプルです。カメラのコンポーネントに付けてみてください
クォータニオン先輩使えば回転量定義できて座標変換が簡単に実装できます。