Unityでゲーム作るぞ

Unityでゲーム制作できるようになるまでのリアルドキュメント

Unity上でSpineオブジェクトをアニメさせつつ連番PNGを作成

果たして需用あるのか謎だが、spineのデータ(json)をunityに読み込み、各パーツのアニメフレームを再生しつつ、連番PNGを出力させる事に成功した。c#
(別にspineでも同じ事はできるが、パーツが多すぎるとON,OFFの切り替えが面倒そうだったので…)

using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;

public class Screenshot : MonoBehaviour {
	private Animator model_animator;
	void Awake(){
			model_animator = GameObject.Find("skeleton.json").GetComponent<Animator>();
			model_animator.speed = 0; // アニメ停止

			StartCoroutine(Test());
  }

	private IEnumerator Test()
	{
		// 全オブジェクトリストアップ。引っ掛けたくないものは非アクティブにする
		List<GameObject> ary = new List<GameObject>();
		foreach (GameObject obj in UnityEngine.Object.FindObjectsOfType(typeof(GameObject))){
			if(obj.transform.FindChild(obj.name))ary.Add(obj);
		}
		
		// 画像を変えていく
		System.IO.Directory.CreateDirectory(@"フォルダを作る場所");
		Texture2D tex = new Texture2D(Screen.width, Screen.height, TextureFormat.ARGB32, false);
		for (int i = 0 ; i < ary.Count ; i++){
			yield return new WaitForEndOfFrame(); // 描画系には描画を待つだけのウェイトを
			// yield return new WaitForSeconds(1.5f);
			for (int j = 0 ; j < ary.Count ; j++){
				GameObject child = ary[j].transform.FindChild(ary[j].name).gameObject;
				var s_compo = child.GetComponent<SkinnedMeshRenderer>();
				s_compo.enabled = (i == j);
			}

			System.IO.Directory.CreateDirectory(@"フォルダを作る場所" + ary[i].name);				
			var anime_flame = 0.0f;
			var count = 0;
			while(model_animator.GetCurrentAnimatorStateInfo(0).normalizedTime < 1.0f){
				// アルファチャンネル・保存のために
				yield return new WaitForEndOfFrame();
				tex.ReadPixels(new Rect(0, 0, Screen.width, Screen.height), 0, 0);
				tex.Apply();	
				byte[] bytes = tex.EncodeToPNG();
				File.WriteAllBytes("フォルダを作る場所" + ary[i].name + "\\" + ary[i].name + count.ToString() + ".png", bytes);
				
        count++;
				
				anime_flame += (1.0f / 60.0f); 
				model_animator.ForceStateNormalizedTime(anime_flame); // アニメフレームコマ送り
			}
			model_animator.ForceStateNormalizedTime(0.0f);	
		}
		Destroy(tex);
		// エディタの再生、終了
		UnityEditor.EditorApplication.isPlaying = false;
	}
}

Unityに取り込んだspineのオブジェクト名は”skeleton.json”だと仮定している。
フォルダの生成箇所も手動で書き換えよう

要はアニメフレームを1つずつめくりながら画面のスクリーンショットを撮る、という荒業。
自分のPCでは20fpsくらいになってしまうという負荷だった…

画面スクショは再生ウィンドーに大きさが影響されるので、再生(GAME)ウィンドーの大きさを固定する為のクラスも作成。
この場合は横1000、縦800としている。
http://answers.unity3d.com/questions/267097/editor-programming-experts-regarding-layouts.html
このフォーラムを参考にしてコピペ。

これをEditorと名前を付けたフォルダに入れると、メニュー欄に"Caputer">"start"という項目が加わるので、それを押せば勝手に始まって勝手に終わる。

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

public class EditorGamePosition : EditorWindow
{
    [MenuItem ("Caputer/start")]
		static void Init () {
        WindowInfos windows = new WindowInfos();
        windows.game.position = new Rect(0, 0, 1000, 800 + 17); // titleバー分が17px?
        UnityEditor.EditorApplication.isPlaying = true;
    }

    // 簡単な書き方で各エディターにアクセスしやすいようにしてくれてるクラス
    class WindowInfos
    {
        // note: some of this data might need to change a little between different versions of Unity
        public WindowInfo scene = new WindowInfo("UnityEditor.SceneView", "Scene", "Window/Scene");
        public WindowInfo game = new WindowInfo("UnityEditor.GameView", "Game", "Window/Game");
        public WindowInfo inspector = new WindowInfo("UnityEditor.InspectorWindow", "Inspector", "Window/Inspector");
        public WindowInfo hierarchy = new WindowInfo("UnityEditor.HierarchyWindow", "Hierarchy", "Window/Hierarchy");
        public WindowInfo project = new WindowInfo("UnityEditor.ProjectWindow", "Project", "Window/Project");
        public WindowInfo animation = new WindowInfo("UnityEditor.AnimationWindow", "Animation", "Window/Animation");
        public WindowInfo profiler = new WindowInfo("UnityEditor.ProfilerWindow", "Profiler", "Window/Profiler");
        public WindowInfo console = new WindowInfo("UnityEditor.ConsoleWindow", "Console", "Window/Console");
        public WindowInfo navigation = new WindowInfo("UnityEditor.NavMeshEditorWindow", "Navigation", "Window/Navigation");
        public WindowInfo occlusion = new WindowInfo("UnityEditor.OcclusionCullingWindow", "Occlusion", "Window/Occlusion Culling");
        public WindowInfo lightmapping = new WindowInfo("UnityEditor.LightmappingWindow", "Lightmapping", "Window/Lightmapping");
        public WindowInfo assetServer = new WindowInfo("UnityEditor.ASMainWindow", "Server", "Window/Asset Server");
        public WindowInfo assetStore = new WindowInfo("UnityEditor.AssetStoreWindow", "Asset Store", "Window/Asset Store");
        public WindowInfo particle = new WindowInfo("UnityEditor.ParticleSystemWindow", "Particle Effect", "Window/Particle Effect");
        public MainWindow main = new MainWindow();
    }

    class WindowInfo
    {
        string defaultTitle;
        string menuPath;
        Type type;

        public WindowInfo(string typeName, string defaultTitle = null, string menuPath = null, System.Reflection.Assembly assembly = null)
        {
            this.defaultTitle = defaultTitle;
            this.menuPath = menuPath;

            if (assembly == null)
                assembly = typeof(UnityEditor.EditorWindow).Assembly;
            type = assembly.GetType(typeName);
            if (type == null)
                Debug.LogWarning("Unable to find type \"" + typeName + "\" in assembly \"" + assembly.GetName().Name + "\".\nYou might want to update the data in WindowInfos.");
        }

        public EditorWindow[] FindAll()
        {
            if (type == null)
                return new EditorWindow[0];
            return (EditorWindow[])(Resources.FindObjectsOfTypeAll(type));
        }

        public EditorWindow FindFirst()
        {
            foreach (EditorWindow window in FindAll())
                return window;
            return null;
        }

        public EditorWindow FindFirstOrCreate()
        {
            EditorWindow window = FindFirst();
            if (window != null)
                return window;
            if (type == null)
                return null;
            if (menuPath != null && menuPath.Length != 0)
                EditorApplication.ExecuteMenuItem(menuPath);
            window = EditorWindow.GetWindow(type, false, defaultTitle);
            return window;
        }

        // shortcut for setting/getting the position and size of the first window of this type.
        // when setting the position, if the window doesn't exist it will also be created.
        public Rect position
        {
            get
            {
                EditorWindow window = FindFirst();
                if (window == null)
                    return new Rect(0, 0, 0, 0);
                return window.position;
            }
            set
            {
                EditorWindow window = FindFirstOrCreate();
                if (window != null)
                    window.position = value;
            }
        }

        // shortcut for deciding if any windows of this type are open,
        // or for opening/closing windows
        public bool isOpen
        {
            get
            {
                return FindAll().Length != 0;
            }
            set
            {
                if (value)
                    FindFirstOrCreate();
                else
                    foreach (EditorWindow window in FindAll())
                        window.Close();
            }
        }
    }

    // experimental support for getting at the main Unity window
    class MainWindow
    {
        Type type;
        UnityEngine.Object window;

        public MainWindow()
        {
            type = typeof(UnityEditor.EditorWindow).Assembly.GetType("UnityEditor.ContainerWindow");
            if (type != null)
            {
                foreach (UnityEngine.Object w in Resources.FindObjectsOfTypeAll(type))
                {
                    object parent = type.InvokeMember("get_mainView", System.Reflection.BindingFlags.InvokeMethod | System.Reflection.BindingFlags.Instance, null, w, null);
                    if (parent != null && parent.GetType().Name.Contains("MainWindow"))
                    {
                        window = w;
                        break;
                    }
                }
            }
            if (window == null)
                Debug.LogWarning("Unable to find main window.\nMaybe you'll need to update the MainWindow constructor for your version of Unity.");
        }

        public Rect position
        {
            get
            {
                if (window == null)
                    return new Rect(0, 0, 0, 0);
                return (Rect)type.InvokeMember("get_position", System.Reflection.BindingFlags.InvokeMethod | System.Reflection.BindingFlags.Instance, null, window, null);
            }
            set
            {
                if (window != null)
                    type.InvokeMember("set_position", System.Reflection.BindingFlags.InvokeMethod | System.Reflection.BindingFlags.Instance, null, window, new object[] { value });
            }
        }

        public bool isOpen
        {
            get
            {
                return window != null;
            }
            set
            {
                if (!value)
                {
                    if (EditorApplication.SaveCurrentSceneIfUserWantsTo())
                        EditorApplication.Exit(0);
                }
            }
        }
    }
}

spineをUnityにインポートする時のメモ

基本は
http://qiita.com/kamone/items/85de6bd779cd7a2f403b
http://ch.nicovideo.jp/vava/blomaga/ar778596
を参考にすればできたが、もっと簡単な方法を見つけたので。

Githubのモロモロを入れた後、
Spineから作成したデータを
skeleton.atlas.txt
skeleton.json.txt
とリネーム。
それをUnity側にインポートすれば自動認識、マテリアルとかを勝手に作ってくれる。

あとはSpineSkeletonDataをCreateして(これだけ作られてない)それにSpineAtlasとjsonの登録をするだけで完成できる。

Unity 5.1.0f3
spine 2.1.27 Professional
で確認。

三十七日目ーゴブリンの同時死亡、腰布消しに対応


腰布が残っていたのは、肌部分と布部分で使っているテクスチャーが違うため。
materialとしていた所を、material”s”にして配列で取得、全て透明度を上げてやる。すなわちforとmaterials.lengthでアクセス。

http://docs-jp.unity3d.com/Documentation/ScriptReference/SkinnedMeshRenderer.html
こういった仕様は公式ドキュメントに明るいので読もう。日本語訳も大体されている

複数を同時に倒した時にうまくいかないのはコルーチンを疑ったが全然関係なかった。
GameObject.Find("U_Char_0")
としていたのだが、
これは"存在する全てのオブジェクトの中から、U_Chara_0という名前のものを探す"である。
ゴブリンは何体も出るし、それぞれ、その中にU_Chara_0を持っている。結果、幾つも存在している。

これにより例えば3番めに登場した敵を倒しても、取得されるのは1番めという事になり、バグ。(だから一体の時は問題なかった)

結論的には
transform.Find("U_Char_0");
で、それぞれのゴブリンの中にある、U_Charを探す事になる。
http://blog.be-style.jpn.com/article/57118009.html
を参照した。

だがここでもう一つ問題、GameObject(クラス)の時は絶対座標だが、transform(オブジェクト)のFindは相対的となり…、要は一階層下までしか潜らないらしく。
という訳で
transform.Find("U_Char/U_Char_0");
と、しっかり階層構造を示してアクセスする必要があった。

http://www.cho-design-lab.com/2013/08/21/unity-find-object-by-name/
ここを参照した。


腰布が透明になるのは良いんだけど、本体を消す時に一瞬黒くなって目立つんだよなあ…何故に…