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/
ここを参照した。
腰布が透明になるのは良いんだけど、本体を消す時に一瞬黒くなって目立つんだよなあ…何故に…