プログラミング系のネタをまとめていきます。

Build Settings


多人数でゲームを開発するとき、1ステージを複数のシーンに分割する手法が一般的な解決方法です。
ステージ数と、1ステージ内の分割数が増えてくると、
パッケージビルド時の Build Settings の設定を手動で行うのが面倒になってきます。

Unityでは、スクリプトから Build Settings を操作するクラスが用意されているので、
それを利用して簡単なサンプルを作ってみました。

まずは、シーンファイルの構成はこのような感じ。


1ステージを3つ(character, gimmick, map)に分割しています。

作成したサンプルツールはこんな感じ。

stage1が選択された状態です。

使い方
SelectAll全ステージにチェックを入れる
ClearAll全ステージのチェックを外す
stage X各ステージのチェックボックス
登録したいステージだけチェックします
ApplyBuild Settings に設定する

最後に、全ステージを選択した状態です。

ソースコード


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

public class BuildSettingsHelper : EditorWindow
{
	private static int STAGE_NUM = 2;	// ステージ数はとりあえず2

	// シーン選択チェック
	private static bool[] m_stageSw = 
	{
		false,
		false
	};

	// シーンファイルのルートパス
	private static string SCENEFILE_ROOT_PATH = "Assets/Scenes/BuildSettingsTest/";

	// 1ステージ内のシーンカテゴリ名
	private static string[] SCENEFILE_CATEGORY = 
	{
		"character",
		"gimmick",
		"map",
	};

	// このツールウィンドウを開くためのカスタムメニュー
	[MenuItem("Custom/Build Settings/Open Helper")]
	public static void OpenWindow()
	{
		BuildSettingsHelper window = EditorWindow.GetWindow<BuildSettingsHelper>();
		if (window != null) window.Show();
	}

	void OnGUI()
	{
		EditorGUILayout.BeginHorizontal();
		if (GUILayout.Button("SelectAll"))	// 全ステージ選択
		{
			for (int i = 0; i < STAGE_NUM; i++) m_stageSw[i] = true;
		}
		if (GUILayout.Button("ClearAll"))	// 全ステージクリア
		{
			for (int i = 0; i < STAGE_NUM; i++) m_stageSw[i] = false;
		}
		EditorGUILayout.EndHorizontal();

		// 個別にステージ選択
		for (int i = 0; i < STAGE_NUM; i++)
		{
			m_stageSw[i] = EditorGUILayout.Toggle("stage " + (i+1), m_stageSw[i]);
		}

		if (GUILayout.Button("Apply"))	// Build Settingsに設定
		{
			List<EditorBuildSettingsScene> buildScenesList = new List<EditorBuildSettingsScene>();
			int catNum = SCENEFILE_CATEGORY.Length;
			for(int i = 0; i < STAGE_NUM; i++)
			{
				if(m_stageSw[i])	// 選択されたステージ
				{
					for (int j = 0; j < catNum; j++)	// 全カテゴリのシーンを追加
					{
						string sceneName = string.Format("{0}stage{1:00}/stage{1:00}_{2}.unity", 
															SCENEFILE_ROOT_PATH, 
															i+1, 
															SCENEFILE_CATEGORY[j]);
						buildScenesList.Add(new EditorBuildSettingsScene(sceneName, true));	// 第2引数はBuildSettingsのシーン有効チェック
					}
				}
			}
			// 配列にして Build Settings に設定する
			// 配列内の順番が維持されるので、一番最初に起動するシーンが先頭に来るように。このサンプルでは適当に並べてるだけです…
			EditorBuildSettings.scenes = buildScenesList.ToArray();
		}
	}
}

参考リンク


Unity Communityから
http://forum.unity3d.com/threads/116210-How-to-add...

公式リファレンス …は、参考にならない…
http://unity3d.com/support/documentation/ScriptRef...


AnimationWindowをスクリプトから操作する


SceneViewはEditorWindowから派生されていて、スクリプトからアクセスできるようになっています。
リファレンスマニュアルには書かれていませんが、 SceneView というクラスがあります。

こんな感じ。


他のウィンドウもクラスが存在するようですが、スクリプト側には隠蔽されているようで操作できません。

でも、.NETのりフレクション機能を使って色々とアクセスすることが出来ます。
まずはウィンドウを探す方法。
ウィンドウのインスタンスは Resources の管理下にあるようです。
using System.Reflection;

EditorWindow[] windows = Resources.FindObjectsOfTypeAll(typeof(EditorWindow)) as EditorWindow[];
foreach (EditorWindow w in windows)
{
	Debug.Log(w.title);
}

コンソールの出力結果。


ウィンドウのタイトルが UnityEditor.AnimationWindow となっているのがアニメーションウィンドウです。
例えば上記ループ内で、
if (string.Compare(w.title, "UnityEditor.AnimationWindow") == 0)
{
	w.Close();
}
とすると、アニメーションウィンドウが閉じられます。

メソッドの列挙


EditorWindowのインスタンスとして扱うと、AnimationWindowの機能に触ることができません。
とりあえず、AnimationWindowのメソッドを調べてみます。

// publicメッソドを列挙
MethodInfo[] methods = w.GetType().GetMethods();	// w は AnimationWindow
foreach (MethodInfo m in methods)
{
	string str = "";
	if(m.IsPublic) str += "public ";
	if(m.IsStatic) str += "static ";
	str += m.ReturnType.ToString() + " ";
	str += m.Name + "\n";

	// 引数
	ParameterInfo[] param = m.GetParameters();
	for (int i = 0; i < param.Length; i++)
	{
		str += param[i].ParameterType.ToString() + " " + param[i].Name + (i < param.Length - 1 ? ", " : "");
	}
	Debug.Log(str);
}

// privateメッソドを列挙
methods = w.GetType().GetMethods(BindingFlags.NonPublic | BindingFlags.InvokeMethod | BindingFlags.Instance);
foreach (MethodInfo m in methods)
{
	string str = "";
	if (m.IsPrivate) str += "private ";
	str += m.ReturnType.ToString() + " ";
	str += m.Name + "\n";

	// 引数
	ParameterInfo[] param = m.GetParameters();
	for (int i = 0; i < param.Length; i++)
	{
		str += param[i].ParameterType.ToString() + " " + param[i].Name + (i < param.Length - 1 ? ", " : "");
	}
	Debug.Log(str);
}

publicメソッドはこんな感じ。
AnimationWindowっぽい関数が列挙されています。


privateなメソッドも確認できます。


引数はわかりづらいのもありますが、これだけ情報が揃えば幾つかのメソッド呼び出しが出来ます。
機能は名前から想像して…

メソッド呼び出し


以下のコードは、AnimationClipをセットしたGameObjectをHierarchy上で選択した状態であることを前提に動作させています。

System.Type t = w.GetType();	// w は AnimationWindow

// アニメーション編集モード(?)にする
t.InvokeMember("SetAutoRecordMode", BindingFlags.InvokeMethod, null, w, new object[] { true });
// タイムラインを 1:00 に合わせる
t.InvokeMember("UpdateTime", BindingFlags.InvokeMethod, null, w, new object[] { 1.0f });

実行結果
タイムライン 1:00 のところにカーソルが合わされています。


動作をきちんと調べていけば、タイムラインの指定の位置にキーフレームを追加、削除も出来そうです。

その他

リフレクションを使うと privateなメソッドを呼び出すこともできるし、
各種メンバにアクセスすることも出来ます。

Unityがわざわざ隠しているものを無理やりアクセスするのはあまり良くないかもしれませんが、
これを利用するとシーン編集機能の拡張の幅が広がります。

もちろん、Unityのバージョンアップにより関数名、引数など変更される可能性は高いと思うので、注意が必要です。

コンポーネントの並び替えツール


EditorUtility.CloneComponent()を使って、GameObjectに追加されたコンポーネントを並び替えることが出来ます。

実行画面


並び替えたいGameObjectを登録したところ。


並び替えたところ。


インスペクタ:ソート前。


インスペクタ:ソート後。

ソースコード


OnGUI()だけ載せます。
	void OnGUI()
	{
		// ターゲットGameObject
		GameObject target = EditorGUILayout.ObjectField("target", m_targetObj, typeof(GameObject), true) as GameObject;
		if (target != m_targetObj)
		{
			m_targetObj = target;
			m_compList = new List<Component>(target.GetComponents<Component>());
			m_compList.Remove(target.GetComponent<Transform>());	// Transformだけ削除
			m_sortedIdxList = new List<int>();
			m_selected = new bool[m_compList.Count];
		}

		if (m_compList == null || m_targetObj == null || m_sortedIdxList == null || m_selected == null) return;

		EditorGUILayout.BeginHorizontal();

		// ソート前のコンポーネントをボタンで列挙
		{
			EditorGUILayout.BeginVertical();
			int i = 0;
			for(i = 0; i < m_compList.Count; i++)
			{
				EditorGUI.BeginDisabledGroup(m_selected[i]);
				if (GUILayout.Button(m_compList[i].GetType().ToString()))
				{
					// 押したボタンのインデックスをソート済みに登録
					m_sortedIdxList.Add(i);
					m_selected[i] = true;
				}
				EditorGUI.EndDisabledGroup();
			}
			EditorGUILayout.EndVertical();
		}
		
		// ソート後のコンポーネントをラベルで列挙
		{
			EditorGUILayout.BeginVertical();
			foreach(int idx in m_sortedIdxList)
			{
				EditorGUILayout.LabelField(m_compList[idx].GetType().ToString());
			}
			EditorGUILayout.EndVertical();
		}
		
		EditorGUILayout.EndHorizontal();

		// 確定ボタン
		EditorGUI.BeginDisabledGroup(m_sortedIdxList.Count != m_compList.Count);
		{
			if (GUILayout.Button("Apply"))
			{
				// シーンUndoに登録
				Undo.RegisterSceneUndo("Sort Component");

				// ソートの順番でコンポーネントを複製
				// GameObjectの最後に登録される
				foreach (int idx in m_sortedIdxList)
				{
					EditorUtility.CloneComponent(m_compList[idx]);
				}
				// ソート前のコンポーネントを削除
				foreach (Component comp in m_compList)
				{
					DestroyImmediate(comp);
				}

				// クリア
				m_sortedIdxList = null;
				m_targetObj = null;
				m_compList = null;
				m_selected = null;
			}
		}
		EditorGUI.EndDisabledGroup();
	}

SerializedPropertyの動作確認


まずはこんなクラスを用意します。
publicメンバにシリアライズ可能なクラスの配列を用意します。

public class test_comp0 : MonoBehaviour
{
	public enum EnumDef
	{
		AAA,
		BBB,
		CCC
	}
	
	[System.SerializableAttribute]
	public class TestData
	{
		public int _iVal;
		public float[] _fVal;
		public EnumDef[] _enumVal;
		
		public TestData()
		{
		}
		
		public TestData(int iv, float[] fv)
		{
			_iVal = iv;
			_fVal = fv;
		}
	}

	
	public TestData[] m_data;
	
	// 以下略
}

インスペクタではこんなかんじで設定。


コンポーネントを渡して、全SerializedPropertyを出力する処理はこんな感じ。
void OutputSerializedProperties(Object obj)
{
	UnityEditor.SerializedObject sobj = new UnityEditor.SerializedObject(obj);
	
	UnityEditor.SerializedProperty sprop = sobj.GetIterator();
	
	System.IO.StreamWriter sw = new System.IO.StreamWriter("test.txt");
	
	while(true)
	{
		if(sprop.editable)
		{
			string text = string.Format("{0} : {1}({2}:{3}) hasChildren:{4} isArray:{5}", 
				sprop.propertyPath, 			// プロパティのパス
				sprop.name, 					// プロパティの名前(メンバ名)
				sprop.propertyType, 			// プロパティの型
				sprop.type,						// 一般的な(?)型(.NETでの型?)
				sprop.hasChildren ? "o":"x", 	// 子のプロパティを持つかどうか
				sprop.isArray ? "o":"x");		// 配列かどうか
			
			sw.WriteLine(text);
		}
		
		if(!sprop.Next(true)) break;	// 最後のプロパティでループ終了
	}
	
	sw.Close();
}

出力結果


結果を見ると分かる通り、SerializedProperty.Next(true) を呼ぶことで、シリアライズされた全てのパラメータをたどれます。
また、配列のroot、配列サイズ、シリアライズクラスのroot、それぞれ一つの SerializedProperty として扱われています。

今回は出力していませんが、値も取れるようになっています。
propertyTypeで型を調べて、SerializedProperty.floatValue, SerializedProperty.enumValueIndex, といった感じで、型にあった値を取り出します。

 : Base(Generic:MonoBehaviour) hasChildren:o isArray:x
m_ObjectHideFlags : m_ObjectHideFlags(Integer:UInt32) hasChildren:x isArray:x
m_PrefabParentObject : m_PrefabParentObject(ObjectReference:PPtr<EditorExtension>) hasChildren:o isArray:x
m_PrefabParentObject.m_FileID : m_FileID(Integer:SInt32) hasChildren:x isArray:x
m_PrefabParentObject.m_PathID : m_PathID(Integer:SInt32) hasChildren:x isArray:x
m_PrefabInternal : m_PrefabInternal(ObjectReference:PPtr<Prefab>) hasChildren:o isArray:x
m_PrefabInternal.m_FileID : m_FileID(Integer:SInt32) hasChildren:x isArray:x
m_PrefabInternal.m_PathID : m_PathID(Integer:SInt32) hasChildren:x isArray:x
m_GameObject : m_GameObject(ObjectReference:PPtr<GameObject>) hasChildren:o isArray:x
m_GameObject.m_FileID : m_FileID(Integer:SInt32) hasChildren:x isArray:x
m_GameObject.m_PathID : m_PathID(Integer:SInt32) hasChildren:x isArray:x
m_Enabled : m_Enabled(Boolean:UInt8) hasChildren:x isArray:x
m_EditorHideFlags : m_EditorHideFlags(Integer:UInt32) hasChildren:x isArray:x
m_Script : m_Script(ObjectReference:PPtr<MonoScript>) hasChildren:o isArray:x
m_Script.m_FileID : m_FileID(Integer:SInt32) hasChildren:x isArray:x
m_Script.m_PathID : m_PathID(Integer:SInt32) hasChildren:x isArray:x
m_Name : m_Name(String:string) hasChildren:o isArray:o
m_Name.Array : Array(Generic:Array) hasChildren:o isArray:o
m_Name.Array.size : size(ArraySize:SInt32) hasChildren:x isArray:x
m_data : m_data(Generic:TestData) hasChildren:o isArray:o                   ←プロパティのroot
m_data.Array : Array(Generic:Array) hasChildren:o isArray:o                 ←配列開始
m_data.Array.size : size(ArraySize:SInt32) hasChildren:x isArray:x          ←配列サイズ
m_data.Array.data[0] : data(Generic:Generic Mono) hasChildren:o isArray:x   ←シリアライズされたクラスのroot
m_data.Array.data[0]._iVal : _iVal(Integer:int) hasChildren:x isArray:x
m_data.Array.data[0]._fVal : _fVal(Generic:vector) hasChildren:o isArray:o
m_data.Array.data[0]._fVal.Array : Array(Generic:Array) hasChildren:o isArray:o
m_data.Array.data[0]._fVal.Array.size : size(ArraySize:SInt32) hasChildren:x isArray:x
m_data.Array.data[0]._fVal.Array.data[0] : data(Float:float) hasChildren:x isArray:x
m_data.Array.data[0]._fVal.Array.data[1] : data(Float:float) hasChildren:x isArray:x
m_data.Array.data[0]._enumVal : _enumVal(Generic:vector) hasChildren:o isArray:o
m_data.Array.data[0]._enumVal.Array : Array(Generic:Array) hasChildren:o isArray:o
m_data.Array.data[0]._enumVal.Array.size : size(ArraySize:SInt32) hasChildren:x isArray:x
m_data.Array.data[0]._enumVal.Array.data[0] : data(Enum:int) hasChildren:x isArray:x
m_data.Array.data[0]._enumVal.Array.data[1] : data(Enum:int) hasChildren:x isArray:x
m_data.Array.data[1] : data(Generic:Generic Mono) hasChildren:o isArray:x
m_data.Array.data[1]._iVal : _iVal(Integer:int) hasChildren:x isArray:x
m_data.Array.data[1]._fVal : _fVal(Generic:vector) hasChildren:o isArray:o
m_data.Array.data[1]._fVal.Array : Array(Generic:Array) hasChildren:o isArray:o
m_data.Array.data[1]._fVal.Array.size : size(ArraySize:SInt32) hasChildren:x isArray:x
m_data.Array.data[1]._fVal.Array.data[0] : data(Float:float) hasChildren:x isArray:x
m_data.Array.data[1]._fVal.Array.data[1] : data(Float:float) hasChildren:x isArray:x
m_data.Array.data[1]._fVal.Array.data[2] : data(Float:float) hasChildren:x isArray:x
m_data.Array.data[1]._enumVal : _enumVal(Generic:vector) hasChildren:o isArray:o
m_data.Array.data[1]._enumVal.Array : Array(Generic:Array) hasChildren:o isArray:o
m_data.Array.data[1]._enumVal.Array.size : size(ArraySize:SInt32) hasChildren:x isArray:x
m_data.Array.data[1]._enumVal.Array.data[0] : data(Enum:int) hasChildren:x isArray:x

Unity Remote


Android/iOS開発の際、Editor上からはスクリーンのタッチ操作を出来ない。
(Input.touches から情報を取得できない。)

この問題を解決するために、Unity Remoteというアプリがある。
インストールした端末をPCにUSB接続し、アプリを起動すると UnityEditorの実行画面が端末に表示される。
Android端末の画面をタッチすると、UnityEditor上でタッチ動作が反映される。

参考サイト

開発者から見た、Unityの技術的な魅力
http://www.buildinsider.net/consumer/charmofunity/...

Unity Remote demo



デバイスID取得方法


参考サイト
http://kinsentansa.blogspot.jp/2013/02/androidadmo...

DDMS LogCatのソート欄でタグ指定で探せます。
tag:ads

テクスチャのパッキング


Unityが用意しているメソッドを使うと簡単にパッキングできます。

Texture2D[] textures;	// パッキングしたいテクスチャ配列
int padding = 1;		// 各テクスチャ間のパディング

// パッキング先のテクスチャを作成
Texture2D tex2D = new Texture2D(1024, 1024);

// テクスチャをパッキング
// rectにアトラス情報が入る
Rect[] rect = tex2D.PackTextures(textures, padding, 1024);

// 今回作成されたテクスチャを、元のテクスチャファイルに上書き
{
	// バイト配列を作成して
	byte[] texBytes = tex2D.EncodeToPNG();

	// 上書き先のテクスチャパス
	string path = "";
	
	// バイト配列をファイルに上書き
	File.WriteAllBytes(path, texBytes);

	// アセットを更新
	AssetDatabase.Refresh();
}

ファイル保存パス


Androidの外部ストレージは端末によってパスが変わります。

Application.dataPath


アプリケーションが保存されているパス。
Android(本体)/data/app/<アプリのID>.apk
Android(SDカード)/mnt/asec/<アプリのID>/pkg.apk
iOS/var/mobile/Applications/XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX/myappname.app/Data

Application.persistentDataPath


永続的なデータを保存するパス。
Android(本体)/data/data/<アプリのID>/files/
Android(SDカード)/mnt/sdcard/Android/data/<アプリのID>/files/
iOS/var/mobile/Applications/XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX/Documents

Application.temporaryCachePath


一時的なデータを保存するパス
Android(本体)/data/data/<アプリのID>/cache/
Android(SDカード)/mnt/sdcard/Android/data/<アプリのID>/cache/
iOS/var/mobile/Applications/XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX/Library/Caches

UnityからIntent発行


private AndroidJavaObject m_unityPlayer = null;
private AndroidJavaObject m_currentActivity = null;
private string m_tweetText = "tweet";

private void Start()
{
	// UnityPlayer, currentActivityを取得
	m_unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
	m_currentActivity = m_unityPlayer.GetStatic<AndroidJavaObject>("currentActivity");
}

private void SendText(string text)
{
	// テキストを送信するIntent生成
	AndroidJavaObject intent = new AndroidJavaObject("android.content.Intent", "android.intent.action.SEND");
	intent.Call<AndroidJavaObject>("putExtra", new object[] { "android.intent.extra.TEXT", m_tweetText });
	intent.Call<AndroidJavaObject>("setType", "text/plain");

	// Intent送信
	m_currentActivity.Call("startActivity", intent);

	intent.Dispose();
}

void OnGUI()
{
	m_tweetText = GUILayout.TextArea(m_tweetText);	// テキスト入力欄
	if (GUILayout.Button("Send Text"))
	{
		// ボタンを押してテキスト送信
		SendText(m_tweetText);
	}
}

void OnDestroy()
{
	if (m_currentActivity != null)
	{
		m_currentActivity.Dispose();
		m_currentActivity = null;
	}
	if (m_unityPlayer != null)
	{
		m_unityPlayer.Dispose();
		m_unityPlayer = null;
	}
}

この要領で、少ない処理であればプラグインを作成しなくても、手軽にUnityからAndroidの処理を呼び出せそうです。
タグ

Menu

メインコンテンツ

プログラミング

機器

Macツール

各種情報

Wiki内検索

おまかせリンク

Androidアプリ

AdSense

技術書


管理人/副管理人のみ編集できます