サークル獏の佐藤敏 Unityとか備忘録

サークル獏の佐藤敏がUnityとかで知ったTipsを書いておく備忘録です。

DynamicBoneを(複数の)GameObjectに追加する拡張

若干仕様を追い切れているか自信がないのですが、とりあえず載せておきます。
DynamicBoneを(複数の)GameObjectに追加します。

ソースコード

using UnityEngine;
using UnityEditor;
using System.Collections.Generic;

public class DynamicBoneUtil {
	[MenuItem("GameObject/AddDynamicBone", false, 10)]
	public static void AddDynamicBone(MenuCommand command) {

		var list = Selection.gameObjects;

		foreach (var obj in list) {
			if (obj != command.context)
				continue;

			var col = obj.AddComponent<DynamicBone>();
			col.m_Root = obj.transform;

			var parent = obj.transform.parent;
			if (parent != null) {
				var headCol = parent.GetComponent<DynamicBoneCollider>();
				if (headCol != null) {
					if (col.m_Colliders == null)
						col.m_Colliders = new List<DynamicBoneColliderBase>();
					col.m_Colliders.Add(headCol);
				}
			}
		}
	}
}

これをDynamicBoneUtil.csとして、Editorフォルダ作ってその中に保存すると

f:id:VinSatoo:20190708201612p:plain
動作画面
こんな風に右クリックメニューにAddDynamicBoneが追加されます。
 

動作

・DynamicBoneを追加
・もしそのオブジェクトの親がDynamicBoneColliderを持っていたら、DynamicBoneのCollidersにソレを追加
(headにColliderをつけて、その子の髪にDynamicBoneをつけるパターンをイメージ)

col.m_Stiffness = 0.5f; とかを追加すれば DynamicBoneの初期パラメータをスキにいじることもできますね。
 

詰まったところ&注意点

フツーにやると「3つのGameObjectを選択していた場合、3つのオブジェクト全てに対して3つDynamicBoneがつく」みたいな動作に。command.contextをチェックすることで回避できるみたいです。
 
ただこれが、右クリックでなくメニューバーから呼び出した場合nullになっていることがあるなんて話もあるのでそこはご注意を。普通は右クリックから呼び出すと思いますが……
How to execute MenuItem for multiple objects once. - Unity Answers

Unity2018.3系からBlendShapeが100越えとかマイナスとかとれるようになっていたらしい……

いまのUnityはBlendShapeが100越えとかマイナスとかとれる!
(※スライダーから変更するのではダメで、直接値を入力するか、スクリプトから変更する)
めちゃくちゃ前から欲しかった機能なのに知らなかった。。。

使えると何がうれしいのか

f:id:VinSatoo:20190622172254p:plain
こんな風に200にしたり
f:id:VinSatoo:20190622172355p:plain
Close系のBlendShapeをマイナスにして目を見開いたり
 
2018.3系からはNested Prefabとか色々変わっているところがあるとはいえ、人によってはこの機能だけで2018.3系に変える意味があるでしょう。
 

設定変更が必要な場合も

なお、2018.2以前で作ったプロジェクトを持ってきてコンバートした場合はここの設定を変えないといけない模様。

f:id:VinSatoo:20190622172029p:plain
2018.2系から持ってきたプロジェクトの場合? こんな風にメッセージ

f:id:VinSatoo:20190622172115p:plain
Edit→ProjectSettings→PlayerのOtherSettingsからここのチェックを外す(2018.3でNewProjectしたら最初から外れてる?)
 

やれやれ

これでもうBlender上でくぱぁモーフの値をマイナスにして閉じモーフを別途作ったりする必要がなくなりますね。。。
ただいつものことですが、2018.2系から2018.3系にプロジェクトを持ってくる場合はバックアップをとってからにしましょう。
壊れたら泣くに泣けない。

複数のfbxファイルからアニメーション(anim)を抽出&余計なAnimationEventを削除

tsubakit1.hateblo.jp
こちらのテラシュールブログさんの記事にfbxからanimを抽出する方法は掲載されているんですが、複数のfbxからアニメーション(anim)を抽出するパターンを。
あと、最初からAnimationEventがついていると再生時にエラーが出て鬱陶しいのでそれを削除するコードも入っています。

f:id:VinSatoo:20190502043534p:plain
AnimationEventが必要ない場合、放置するとこんなエラーが出るので。

やり方

Unity プロジェクトの「Editor」フォルダに追加
→Project画面のfbxファイルを(複数)選択し右クリック
→「AnimExtractor」を実行すると同じフォルダにanimが抽出されます。

大量のファイルを処理すると結構重いんで、最初は10個以下くらいから様子見してください。20~30個とかanimが含まれるfbxなら個別にやったほうがいいかもしれません。

コード

using UnityEngine;
using UnityEditor;
using System.IO;

public class AnimExtractor
{

	static string tempExportedClip = "Assets/tempClip.anim";
	static AnimationEvent[] emptyAnimationEventArray = new AnimationEvent[0];

	[MenuItem("Assets/AnimExtractor")]
	static void AssetCopy() {


		Object[] selectedAsset = Selection.GetFiltered(typeof(Object), SelectionMode.Assets);
		foreach (var go in selectedAsset) {
			ExtractFunc(go);
		}

		AssetDatabase.Refresh();
	}

	private static void ExtractFunc(Object obj) {
		string path = AssetDatabase.GetAssetPath(obj);
		string currentFolder = Path.GetDirectoryName(path);

		var animations = AssetDatabase.LoadAllAssetsAtPath(path);
		var originalClips = System.Array.FindAll<Object>(animations, item =>
			  item is AnimationClip
		);

		foreach(var clip in originalClips) {
			copyClip(clip, currentFolder);
		}
	}

	private static void copyClip(Object clip, string folder) {
		if (clip.name.StartsWith("__preview__"))
			return;

		var instance = Object.Instantiate(clip);
		AnimationClip copiedAnim = instance as AnimationClip;
		AnimationUtility.SetAnimationEvents(copiedAnim, emptyAnimationEventArray);  //AnimationEventを削除しなくていいならこの行を削除

		AssetDatabase.CreateAsset(copiedAnim, tempExportedClip);
		string exportPath = folder + "/" + clip.name + ".anim";

		if(File.Exists(exportPath))
			exportPath = AssetDatabase.GenerateUniqueAssetPath(exportPath);

		File.Copy(tempExportedClip, exportPath, true);
		File.Delete(tempExportedClip);
	}
}

【教えてください】頂点シェーダで非表示を決定→フラグメントシェーダーでdiscardする適切な方法

Unityの頂点シェーダで非表示を決定→フラグメントシェーダーでdiscardするときの、適切&エレガントな方法を教えてくださいm(__)m

なんでそんなことをしたくなったか:前半

f:id:VinSatoo:20181012232526p:plain
こちらは我らがImagineGirlsのR1N4 v2さんにUTS 2.0.5Test版を適用したものである。設定は割と単純で、眉毛貫通とかもしていない。

github.com

f:id:VinSatoo:20181012232158p:plain
f:id:VinSatoo:20181012232823p:plain
これに、R1N4さんの顔のUVを参考にしつつ作ったOutline_Samplerを適用するとこんな感じになる。
口元にあった変な線が消えた。
ついでに顔の輪郭線も強弱ができた。
(カラダは別マテリアルなのでそのまま)

なんでそんなことをしたくなったか:後半

f:id:VinSatoo:20181012232936p:plain
ところがここに、自作のアレなBlendShapeを適用するとまた変な線がでてきてしまう。
(口の下のところ)
恐らく原因はBlenderでBlendShapeを作ったとき、ポリゴンの配置がヘンになってしまったからだ。
しかしモデリング技術がアレなのはどうにもならないし、トゥーン系の表現ならこの線さえ消えてくれればどうにか妥協できないこともない。

f:id:VinSatoo:20181012233143p:plain
ちなみにこの線、Outline由来なのは間違いない(Outline_Colorの色を変えるとそれに従って変わる)。
だが、Outline_Widthを0にしてもこのように残ってしまう。
画像を見てもわかる通り、顔の他の輪郭線は消えたのに口の周りだけ線が残る。

ムリヤリどうにかしてみる

そこで、「UCTS_Outline.cginc」をのぞいてみる(以前のバージョンでは「MyUCTS_Outline.cginc」だった模様)。
散々試行錯誤した結果、以下のようにしたらうまくいった。
(うまくいった結果を先に貼っておく)
f:id:VinSatoo:20181012233512p:plain

(以下の改造は自己責任でお願いします。また、この改造の内容についてUTS公式さんなどに問い合わせないでくださいm(__)m )

VertexOutputに、1つ値を追加する。

struct VertexOutput {
                float4 pos : SV_POSITION;
                float2 uv0 : TEXCOORD0;
                float3 normalDir : TEXCOORD1;
                float3 tangentDir : TEXCOORD2;
                float3 bitangentDir : TEXCOORD3;
				fixed4 disableFlag : Color; //これを加える
            };

vert関数の後半をこのように変える。

#ifdef _OUTLINE_NML
                //v.2.0.4.3 baked Normal Texture for Outline
				if (Set_Outline_Width <= 0) { //ここから
					o.disableFlag.r = 1;
					return o;
				}
				o.disableFlag.r = 0; //ここまでが追加された内容
				o.pos = UnityObjectToClipPos(lerp(float4(v.vertex.xyz + v.normal*Set_Outline_Width,1), float4(v.vertex.xyz + _BakedNormalDir*Set_Outline_Width,1),_Is_BakedNormal));
#elif _OUTLINE_POS

frag関数の冒頭のこのように変える。

			float4 frag(VertexOutput i) : SV_Target{
				if (i.disableFlag.r == 1) {//ここから
					discard;
				} //ここまで

これで、Set_Outline_Widthが0以下のときは頂点シェーダでdisableFlag.rに1が入る。
そしてフラグメントシェーダの冒頭でdisableFlag.rが1ならdiscardで表示が破棄される。
(Set_Outline_Widthはアレコレ計算した結果の主線の太さらしい)
f:id:VinSatoo:20181012232936p:plainf:id:VinSatoo:20181012233512p:plain
(before→afterで再掲)
その結果、↑のようにヘンな線が消えてくれる。

もうちょっと綺麗な適切な書き方はないのか

しかし色とナンの関係もないのにColorセマンティクスをムリヤリ加え、それをフラグとして使って値を放り込むというのは気持ちが悪い。
また、if文はシェーダーを遅くする原因というのも聞いたことがある。
frag関数のほうはともかく、vert関数までifを使っていいものかという疑問が残る。
そもそも頂点シェーダで「このケースは描画破棄」とできれば一番ラクなのだが……

というわけで、シェーダに詳しい方、こういうケースでもっとエレガントな書き方があれば教えてくださいm(__)m

プロジェクトタブからAnimationClipをAnimatorに加えるEditor拡張

アニメーション編集でVeryAnimationなどを使っているとき、操作できるAnimationClipはAnimator(AnimationController)に加えられているものだけだ。
プロジェクトウィンドウにあるClipをAnimatorタブにドラッグアンドドロップすればいいのだが、地味に面倒くさい。

そこで、少し汚い方法だがEditor拡張を書いてしまう。
AnimationClipを追加したいキャラの名前を決め打ちで書いているのがアレだが、実用上はそこまで困らない。
 

スクリプトの内容

using UnityEditor;
using UnityEditor.Animations;
using UnityEngine;

public class AnimConMenu
{
    [MenuItem("Assets/Add to anim-controller")]
    static void AddToController() {
        if (Selection.activeObject is AnimationClip) {

            var key = "R1N4_V2_mine01 (1)"; //ここに対象にしたいキャラの名前を入れる

            AnimationClip clip = (AnimationClip)Selection.activeObject;

            var animator = GameObject.Find(key).GetComponent<Animator>();
            if (animator == null)
                return;

            var controller = animator.runtimeAnimatorController as AnimatorController; ;
            if (controller == null)
                return;

            Motion motion = (Motion)clip as Motion;
            controller.AddMotion(motion);
        }
    }
}

var key = のところを、自分が使っているキャラの名前に変更するのを忘れないこと!
このようなスクリプトを「AnimConMenu.cs」といった名前でEditorフォルダに保存する。
 

実行手順

f:id:VinSatoo:20181012170759p:plain
こんなAnimatorに、

f:id:VinSatoo:20181012170904p:plain
この「mikarup_st006.anim」を加えたいとする。
(繰り返しになるが、一回きりならドラッグアンドドロップすればいいだけである。ただAnimatorタブを開きに行くなどの手順が面倒くさいので……)

f:id:VinSatoo:20181012171015p:plain
さきほど書いたスクリプトにより、Add to anim-controllerというメニュー項目ができている。

f:id:VinSatoo:20181012171136p:plain
実行すると、たしかに「mikarup_st006」が加わった。

VeryAnimationでも……

f:id:VinSatoo:20181012171206p:plain
VeryAnimationでもこの通り、選択できるクリップのリストに「mikarup_st006」が加わっている。

f:id:VinSatoo:20181012171248p:plain
選択すれば、先程のハゲ頭がやっていたポーズと同じポーズになっている。
あとはVeryAnimationの「Tools - Create New Clip」の「Duplicate」でも使えば、元のクリップを残したまま新しいAnimationClipを編集することも可能だ。
(逆に最初の段階でProjectタブからCTRL+Dで複製し、それをAdd to anim-controllerするのでももちろん良い)
 

メリットは

プロジェクトタブで「よし、このClipを元に新しいClipを作ろう」と思ったときに

  • Animatorタブを選択して
  • 元々選んでいたClipを改めて選択して長距離移動のドラッグアンドドロップをし
  • Animatorタブをまたウラに戻す(常時開いていてもだいたい邪魔なだけでメリットがない。普通はSceneやGameタブを大きく開いているはず)

とかやるのは案外面倒である。
ちょっとした時間の短縮だが、VeryAnimationを使っている場合はけっこう作業しやすさが増すと思う。

【教えてください】UnityでMonoBehaviorでないSingletonはどうやる?

Singletonでいこう

世間ではUniRxやらZenjectでSingleton祭りを脱却しよう、みたいな記事がたくさん出ている。
だが、小規模アマチュア僕たちはSingleton祭りでいいんじゃないかなという気がしてしまう。
どうせゲームのTestは書きづらいということもあるし。
 

MonoBehaviorのSingletonは既に色々説明記事がある

qiita.com
tsubakit1.hateblo.jp

このようにMonoBehaviorのSingletonは既に色々説明記事がある。
が、「別にMonoBehaviorの機能いらないよ」という場合はコレで実装するのがどうも気が引ける。
MonoBehaviorに生えているプロパティやらメソッドの数は結構なものであるし、インテリセンスで余計なものがサジェストに大量に出てくるのも微妙だ。
 

MonoBehaviorでないSingletonを書く

MonoBehaviorでないSingleton実装方法としては、普通はこんな感じだろうか。
(非常にSingletonにされやすい音関係をイメージしてクラス名はSoundManagerとした)

public class SoundManager
{
    private static SoundManager mInstance = null;
    public static SoundManager Instance
    {
        get {
            if (mInstance == null) {
                mInstance = new SoundManager();
            }
            return mInstance;
        }
    }

    //privateなので var s = new SoundManager(); とかはできない
    private SoundManager() {

    }

    public void Play(string key) {
         ...
    }

これで、

SoundManager.Instance.Play("bang");

とか

var SoundManager soundManager = SoundManager.Instance;
soundManager.Play("bong");

とかすればフツーに使える。

f:id:VinSatoo:20180926171249p:plain
newもできないのでたしかにSingletonとして機能している。
 

うまくいかない、SingletonBaseを継承するパターン

が、毎度毎度mInstanceのどうこうを書くのは非常にめんどい。
SingletonBaseとかを継承すればOKという風にしたい。

public class SoundManager : SingletonBase<SoundManager>
{
    private SoundManager() {

    }

    public void Play(string key) {
         ...
    }

とかできれば非常にラクで見やすい。
ところが、

public abstract class SingletonBase<T>
    where T : SingletonBase<T>, new()
{
    private static T mInstance = new T();
    public static T Instance
    {
        get {
            return mInstance;
        }
    }
}

のように書いて継承するとエラーが起きてしまう。
f:id:VinSatoo:20180926171552p:plain
(パブリックでないとダメだそうだ……)
 
 
f:id:VinSatoo:20180926171738p:plain
SoundManagerコンストラクタをpublicにすればエラーはでないが……
その場合、
f:id:VinSatoo:20180926171811p:plain
普通にnewできてしまう。
これではSingletonではない。

教えてください

ぶっちゃけ1人で書いている場合は「newしないように気をつける」「全部staticプロパティやメソッドにするよりは見やすくて書きやすいからメリットあり」ってことでコンストラクタをpublicにしたまま上記のSingletonBaseを使っていってもいいのだが、やはり気持ち悪い。

どうすればこのケースでコンストラクタをpublicにせずSingletonBaseクラスを書けるか、C#に詳しい人教えてくださいm(__)m

中心がズレた小道具をAdjust Pivotで直す

f:id:VinSatoo:20180917074718p:plain
こちらはImagineGirlsのVienneV2のfbxをBlenderに読み込み、メガネ以外を全部削除して再度fbxとして書きだしたもの。
メガネだけをよそで転用したい、みたいな感じである。
それをUnityに読み込んだ。

中心がズレた小道具の問題

見ての通り、中心点(Pivot)とメガネの中心位置が激しく違う。
このまま回転させると……
f:id:VinSatoo:20180917074904p:plain
この通りである。これでは位置合わせがやりづらくてしょうがない。

Adust Pivot(無料)の登場

そこでAdust Pivot(無料)を活用する。
assetstore.unity.com
プロジェクトにインポートしたら、[Window]-[Adjust Pivot]ができるので早速起動。
f:id:VinSatoo:20180917075252p:plain
Adjust Pivotのウィンドウが出てきた。
f:id:VinSatoo:20180917075322p:plain

位置調整オブジェクトの追加

今回中心を調整したいglassesの子に、位置調整用の空オブジェクトを作る。
f:id:VinSatoo:20180917075447p:plain
名前はGame Objectのままで特に問題ナシ。
f:id:VinSatoo:20180917075516p:plain

位置調整オブジェクトの位置合わせ

普通にUnityの移動ツールでGame Objectをメガネの中心になるように動かしていく。
カメラの角度によって中心だと思っていた位置が全然違っていたりするので、カメラを回転させたりしながら確認していこう。
f:id:VinSatoo:20180917080314p:plain
f:id:VinSatoo:20180917075611p:plain
そしてGame Objectをメガネの中心に持ってきた後で、Adust Pivotの「Move ~~ pivot here」を押すと……
f:id:VinSatoo:20180917075730p:plain

移動の中心がメガネの中心になった

このように、「移動の中心=メガネの中心」になった。
最初と違って、回転させてもとんでもない動き方をしない。
これなら位置調整もしやすい。
f:id:VinSatoo:20180917080037p:plain
f:id:VinSatoo:20180917075949p:plain

ちなみに、一度こうなったら位置調整用のGame Objectは削除してしまって構わない。
あとはPrefabにでもして保存、Unity Packageとして書き出したりすれば使い回しもしやすいだろう。
 
 

位置調整を目分量でやらない

ところで、上のやり方はGameObjectを手動で動かしていることになる。
カメラの角度によって見え方がまるで違ったりするし、結構大変だ。

で、メッシュの中心はGetComponent().bounds.center;で得られるらしい。
thethird.hatenablog.com

なので、自動化することを考えた。
(Adjust Pivotにはこの機能は入っていないように見える……タブン。)

using UnityEngine;
using UnityEditor;

public class MeshCenter 
{

    // 自前のメニューと項目を作成
    [MenuItem("Window/MeshCenter", false, 1000)]
    static void MeshCenterProc() {
        if (Selection.activeGameObject == null)
            return;

        GameObject centerTarget = new GameObject("CenterTarget");

        var vec3 = Selection.activeGameObject.GetComponent<Renderer>().bounds.center;
        Selection.activeGameObject.transform.position = -vec3;
        centerTarget.transform.SetParent(Selection.activeGameObject.transform);

        Selection.activeGameObject = centerTarget;
    }
}

このようなC#スクリプトを書き、MeshCenter.csなどの名前でプロジェクト内の「Editor」フォルダに保存する。

MeshCenterの実行

Glassesを選択。
MeshCenterを実行する。
f:id:VinSatoo:20180917081353p:plain
自動で作られたCenterTargetというオブジェクト。
勝手に選択状態になる。
f:id:VinSatoo:20180917083005p:plain
 
位置調整も自動で終わっているので、そのまま「Adjust Pivot」を実行。
f:id:VinSatoo:20180917081427p:plain
さっき手動でやったのと同じように、回転の位置がちょうどよくなった。
もし微妙に中心をズラしたい場合は、「Adjust Pivot」実行前に手動で調整すればよい。
f:id:VinSatoo:20180917081435p:plain
また、小道具によっては自動計算された中心と見た目の中心がまるで違うこともある。
その場合は手動でガンバルしかないだろう。
 
 

Blenderからの書きだしのときの多少の注意点?

f:id:VinSatoo:20180917080854p:plain
メガネだけになったVienne。
記憶があいまいだが、メガネは最初非表示だったかもしれない。
その場合、Unityに持ってきたときMeshRendererがオフになっているので手動でメガネのMeshRendererをInspectorからオンにすること。

f:id:VinSatoo:20180917080617p:plain
書き出しの際はオプションの「エンプティ」「カメラ」「ランプ」「アーマチュア」「メッシュ」「その他」と並んでいるところから「メッシュ」だけ選んだ。日本語化していないと表記が違うかもしれない

ImagineGirlsのライセンス表記

f:id:VinSatoo:20180917081654p:plain
なお、今回使ったVienneV2は無料素材ではなく有料素材なのでそこは注意。
と言っても使っているのはメガネだけだが……
www.dlsite.com

(初代Vienneなどは無料だし、V2にもフリー版はあるもよう)
imaginegirls.com