最終更新:
bokkuri_orz 2014年03月09日(日) 00:31:30履歴
多人数でゲームを開発するとき、1ステージを複数のシーンに分割する手法が一般的な解決方法です。
ステージ数と、1ステージ内の分割数が増えてくると、
パッケージビルド時の Build Settings の設定を手動で行うのが面倒になってきます。
Unityでは、スクリプトから Build Settings を操作するクラスが用意されているので、
それを利用して簡単なサンプルを作ってみました。
まずは、シーンファイルの構成はこのような感じ。
1ステージを3つ(character, gimmick, map)に分割しています。
作成したサンプルツールはこんな感じ。
stage1が選択された状態です。
使い方
| SelectAll | 全ステージにチェックを入れる |
| ClearAll | 全ステージのチェックを外す |
| stage X | 各ステージのチェックボックス 登録したいステージだけチェックします |
| Apply | Build 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...
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のバージョンアップにより関数名、引数など変更される可能性は高いと思うので、注意が必要です。
各種メンバにアクセスすることも出来ます。
Unityがわざわざ隠しているものを無理やりアクセスするのはあまり良くないかもしれませんが、
これを利用するとシーン編集機能の拡張の幅が広がります。
もちろん、Unityのバージョンアップにより関数名、引数など変更される可能性は高いと思うので、注意が必要です。
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();
}
まずはこんなクラスを用意します。
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
Android/iOS開発の際、Editor上からはスクリーンのタッチ操作を出来ない。
(Input.touches から情報を取得できない。)
この問題を解決するために、Unity Remoteというアプリがある。
インストールした端末をPCにUSB接続し、アプリを起動すると UnityEditorの実行画面が端末に表示される。
Android端末の画面をタッチすると、UnityEditor上でタッチ動作が反映される。
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(本体) | /data/app/<アプリのID>.apk |
| Android(SDカード) | /mnt/asec/<アプリのID>/pkg.apk |
| iOS | /var/mobile/Applications/XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX/myappname.app/Data |
永続的なデータを保存するパス。
| Android(本体) | /data/data/<アプリのID>/files/ |
| Android(SDカード) | /mnt/sdcard/Android/data/<アプリのID>/files/ |
| iOS | /var/mobile/Applications/XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX/Documents |
一時的なデータを保存するパス
| Android(本体) | /data/data/<アプリのID>/cache/ |
| Android(SDカード) | /mnt/sdcard/Android/data/<アプリのID>/cache/ |
| iOS | /var/mobile/Applications/XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX/Library/Caches |
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の処理を呼び出せそうです。
タグ


最新コメント