太郎Work

Unityとかで困ったこと等を残しておきます

Unity2019時代のパッケージ管理

今までのUnityでは .unitypackage を使用したり Assets 以下にGitのSubmoduleで管理するといった方法がありましたが、やっとPackageManager周りが実用的になったので個人的に良いなと思ったフローを紹介します

今回の記事は2019.1.3で作成しています。今後仕様が変わる可能性があるので注意してください
ここで作成したサンプルはこちらに上げているので気になったらどうぞ
github.com

続きを読む

UnityEditorのGameViewが更新されない時の対処方法

非実行時にEditorスクリプトからHierarchey内のオブジェクトに変更を加えても
UnityEditor全体に対しての更新通知が行かないため、SceneView,GameViewの描画更新が行われない

Viewサイズを変える、Inspectorから変更をする等をすれば更新されるがすぐに確認したい場合

Unity - Scripting API: EditorApplication.QueuePlayerLoopUpdate

遡ると2017.2から追加された模様

これを呼び出せばUnityEditor全体にRepaint通知が行ってすぐに見た目が確認できるようになった

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で検証
f:id:tarowork:20180503033428p:plain

transform 0.58ms
GetComponent 0.846ms
DictionaryIndexer 3.17ms
DictionaryTryGetValue 2.06ms
transformCache 0.399ms

TryGetValueにするだけで約65%軽減(どっちにしろ重いけど、、)
キャッシュとの違いは10倍近く

結論

何が言いたいかというとキャッシュはしたほうがいいよ
Dictionaryのインデクサは使わないほうがいいよ
という話でした

Unityを使って水っぽい表現作ってみた

今GravityDAZE2をやっているのですが、水表現が面白いことになっていたので自分でUnityを使って実装してみました


背景が殆ど無いので分かりにくいと思いますが…

シェーダ

モデルの裏側をキャプチャしたものを使用して法線で歪ませて描画する方法で実装しました

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を使ってみました
f:id:tarowork:20170130022406p:plain

パラメータはちょこちょこっと変えていい感じにして外部から少しだけ力を加えて動くようになっています。
頂点設定は一部の頂点を動きにくい設定にして凸凹感が出やすいようにしました。
多分ここをきれいに設定するともっと柔らかい歪みになると思います。
0.1だと動かなすぎるし大きい値だと動きすぎて本来の一から離れすぎてしまうので0.15~0.2ぐらいがちょうどよかったです

Cloth工夫次第でいろいろできそうな予感がします


これ使って何か作りたい

UnityEditor再生開始時のイベント取得

UnityEditor再生時のイベント

実行、非実行時でモデルプレビュー出来るツールを作成しているのですが、再生時にモデルを一旦リセットして再生成するスクリプトを作成したい

ということでこんなスクリプトを書くと

f:id:tarowork:20160301174309p:plain

ここのボタンを押す度にOnPlayModeStateChangedメソッドが呼ばれるようになります

void OnEnable ()
{
    EditorApplication.playmodeStateChanged += OnPlayModeStateChanged;
}
void OnDisable ()
{
    EditorApplication.playmodeStateChanged -= OnPlayModeStateChanged;
}
void OnPlayModeStateChanged ()
{
    Debug.Log("Changed");
}

ここで問題なのが思っているよりも多くこのイベントが発行されるということです しかし、このメソッド内で

EditorApplication.isPlaying

EditorApplication.isPlayingOrWillChangePlaymode

の値を見るとより詳細なStateが確認できます

  1. 再生ボタン押下
  2. OnChanged : isPlaying=false, isPlayingOrWillChangePlaymode=true ※このタイミングで生成すると実行開始時にCleanupエラーが出る
  3. 開始するまで少し待つあれ 実行ログ出力開始
  4. OnChanged : isPlaying=true, isPlayingOrWillChangePlaymode=true ※実行開始、生成すると通常通り動作する
  5. 停止ボタン押下
  6. OnChanged : isPlaying=true, isPlayingOrWillChangePlaymode=false ※停止開始、生成するとあれ
  7. 完全停止待つ
  8. OnChanged : isPlaying=false, isPlayingOrWillChangePlaymode=false ※完全停止Editorの挙動開始

つまりisPlaying==isPlayingOrWillChangePlaymodeを見ることで実行、Editorに完全移行したかどうかが取れ、逆の場合は遷移する直前の処理が書けます

このままだとPauseボタンでも呼ばれてしまうので上記の条件が一致したらフラグを立てておくことでPauseを無視することができます でもOnDisableとOnEnableだけで上手く頑張れば出来るっぽい雰囲気 とりあえずこのプロパティと組み合わせれば厳密にStateを取れたという感じ

gist.github.com

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上で確認することが出来ます。 f:id:tarowork:20151215221022p:plain

(実行が開始すると消えるので動画キャプチャから抜き出しました)

解決策

解決策としては実行している時のみ指定をすれば一応動作します

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を実行しないと見た目は差し替わっていてもファイルに書き込まれていないため、書き込まれていることを確認してから属性削除は行う必要があります。