シータ＋Oculus+UNITYでスライドショーアプリ作ってみた。

動作
===
![game.jpg](https://qiita-image-store.s3.amazonaws.com/0/32364/17d4b62a-de2d-fc69-daca-8d843f053ce6.jpeg)
Thetaで撮影し、全天球の画像が有るのに一部だけしか閲覧できないディスプレイで見るのはもったいない!
ということで作ってみました。

上記みたいな感じでRicoh Thetaで撮影された写真を背景にMMDのモデルが踊り、
１シーンに付き、エフェクト（画面上ではメテオっぽいの）を１個付けられます。
背景は次々に変わっていき、現在は１０秒ずつ８個設定されています。
この画像専用として作っているわけではないので、素材さえ有れば簡単に別の写真で再生することが出来ます。

Oculus対応、と言うか専用になっています。
Oculusでお楽しみください。

以下コードのこととか。
今回は本題ではないので適当に流してください。

バイナリのアドレス

http://bowlroll.net/up/dl28590
http://bowlroll.net/up/dl28592

エディット画面
===
![slide.jpg](https://qiita-image-store.s3.amazonaws.com/0/32364/36d842b0-3768-69da-69d6-35106dc74b26.jpeg)
Stateより下で写真の設定を行います。

必要なパラメータは上から
EndTime:その写真が表示終了するタイミング
Radius:写真を貼り付ける天球をスケーリングします。100mを基準としその倍率で指定
FadeIn:写真へフェードインする時間(sec)を指定します。
FadeOut:写真がフェードアウトする時間(sec)を指定します。
Effect:Effectを指定します。Effectは現在6種類有ります。(ただし、3種類は雪なんで実質3種類のみ)
Rotation(上):写真を貼り付ける天球を回転させます。
Rotation(下):Mikuさんが出てくる位置を数字にしてます。Rotationなのに座標です。

ボタン
add:addを押した写真の下に1枚新しい写真を追加します。
remove:指定した写真を削除します。
Tset:指定したパラメータをエディター上で設定します。プレビュー用使います。
mikuPosGet:エディター上で配置したミクさんの位置を取得します。ミクさんの位置を指定する場合は必ずこれで行ってください。

保存ボタンとか(画像の下に下記３つのボタンが有ります。)
test add:写真を一個にします。
Save:現在のパラメータリストを保存します。
Load:パラメータをロードします。

他は内部的な表示になってます。
Undo実装は真面目に実装してないため、基本的に機能しません。
FileName:パラメータを保存するファイル名を指定します。
ファイルは必ずルートに配置されたResourceフォルダ下に入れてください。
写真も全てルートに配置されたResourceフォルダ下に入れる必要があります。

XML保存フォーマット
===

下記のような形で保存されます。
XMLによる保存で後方互換も維持し易いし、パーサも簡単に書けるよう工夫されています。

```XML:pictDef.xml
<?xml version="1.0" encoding="UTF-8"?>
<root>
  <PictView>
    <RotX>-7</RotX>
    <RotY>-12.46</RotY>
    <RotZ>5</RotZ>
    <Texture>pict/R0010019</Texture>
    <Radius>1</Radius>
    <EndGTime>10</EndGTime>
    <FadeIn>1</FadeIn>
    <FadeOut>1</FadeOut>
    <Effect>5</Effect>
    <MikuPX>-0.4455535</MikuPX>
    <MikuPY>0.7812473</MikuPY>
    <MikuPZ>5.480559</MikuPZ>
    <MikuRX>0</MikuRX>
    <MikuRY>0.9972526</MikuRY>
    <MikuRZ>0</MikuRZ>
    <MikuRW>0.07407662</MikuRW>
  </PictView>
</root>
```

保存用のコード
===

Editorで動かしてる時しか呼び出すことが出来ません。
（AssetDatabase.GetAssetPath(tempView.m_PictDef[i].m_Tex.GetInstanceID());）がEditorじゃないと使用できない。

```csharp:save

	static void Save(ViewPict tempView)
	{
		XmlDocument xmlDoc = new XmlDocument();

		XmlDeclaration declaration = xmlDoc.CreateXmlDeclaration("1.0", "UTF-8", null);  // XML宣言
		XmlElement elmRoot = xmlDoc.CreateElement("root");  // ルート要素

		xmlDoc.AppendChild(declaration);
		xmlDoc.AppendChild(elmRoot);

		for (int i = 0; i < tempView.m_PictDef.Count; i++)
		{
			XmlElement pictViewElem = xmlDoc.CreateElement("PictView");
			XmlElement rotation_X = xmlDoc.CreateElement("RotX");
			XmlElement rotation_Y = xmlDoc.CreateElement("RotY");
			XmlElement rotation_Z = xmlDoc.CreateElement("RotZ");
			XmlElement texture = xmlDoc.CreateElement("Texture");
			XmlElement radius = xmlDoc.CreateElement("Radius");
			XmlElement endGTime = xmlDoc.CreateElement("EndGTime");

			XmlElement fadeIn = xmlDoc.CreateElement("FadeIn");
			XmlElement fadeOut = xmlDoc.CreateElement("FadeOut");

			XmlElement mikuP_X = xmlDoc.CreateElement("MikuPX");
			XmlElement mikuP_Y = xmlDoc.CreateElement("MikuPY");
			XmlElement mikuP_Z = xmlDoc.CreateElement("MikuPZ");

			XmlElement mikuR_X = xmlDoc.CreateElement("MikuRX");
			XmlElement mikuR_Y = xmlDoc.CreateElement("MikuRY");
			XmlElement mikuR_Z = xmlDoc.CreateElement("MikuRZ");
			XmlElement mikuR_W = xmlDoc.CreateElement("MikuRW");


			XmlElement effect = xmlDoc.CreateElement("Effect");

			rotation_X.InnerText = tempView.m_PictDef[i].m_Rotate.x.ToString();
			rotation_Y.InnerText = tempView.m_PictDef[i].m_Rotate.y.ToString();
			rotation_Z.InnerText = tempView.m_PictDef[i].m_Rotate.z.ToString();

			if (tempView.m_PictDef[i].m_Tex != null)
			{
				string tempStr = AssetDatabase.GetAssetPath(tempView.m_PictDef[i].m_Tex.GetInstanceID());
				tempStr = tempStr.Replace( "Assets/Resources/" ,"");
				tempStr = tempStr.Remove(tempStr.LastIndexOfAny(".".ToCharArray()));
				texture.InnerText = tempStr;

			}
			else
			{ 
				texture.InnerText = "null";
			}

			radius.InnerText = tempView.m_PictDef[i].m_Radius.ToString();
			endGTime.InnerText = tempView.m_PictDef[i].m_EndGTime.ToString();

			fadeIn.InnerText = tempView.m_PictDef[i].m_FadeIn.ToString();
			fadeOut.InnerText = tempView.m_PictDef[i].m_FadeOut.ToString();
			effect.InnerText = tempView.m_PictDef[i].m_Effect.ToString();

			mikuP_X.InnerText = tempView.m_PictDef[i].m_MikuPos.x.ToString();
			mikuP_Y.InnerText = tempView.m_PictDef[i].m_MikuPos.y.ToString();
			mikuP_Z.InnerText = tempView.m_PictDef[i].m_MikuPos.z.ToString();

			mikuR_X.InnerText = tempView.m_PictDef[i].m_MikuRot.x.ToString();
			mikuR_Y.InnerText = tempView.m_PictDef[i].m_MikuRot.y.ToString();
			mikuR_Z.InnerText = tempView.m_PictDef[i].m_MikuRot.z.ToString();
			mikuR_W.InnerText = tempView.m_PictDef[i].m_MikuRot.w.ToString();

			pictViewElem.AppendChild(rotation_X);
			pictViewElem.AppendChild(rotation_Y);
			pictViewElem.AppendChild(rotation_Z);
			pictViewElem.AppendChild(texture);
			pictViewElem.AppendChild(radius);
			pictViewElem.AppendChild(endGTime);
			pictViewElem.AppendChild(fadeIn);
			pictViewElem.AppendChild(fadeOut);
			pictViewElem.AppendChild(effect);

			pictViewElem.AppendChild(mikuP_X);
			pictViewElem.AppendChild(mikuP_Y);
			pictViewElem.AppendChild(mikuP_Z);

			pictViewElem.AppendChild(mikuR_X);
			pictViewElem.AppendChild(mikuR_Y);
			pictViewElem.AppendChild(mikuR_Z);
			pictViewElem.AppendChild(mikuR_W);


			elmRoot.AppendChild(pictViewElem);
		}


		xmlDoc.Save("Assets/Resources/" + tempView.m_FileName+".xml");

	}
```

読み出し用コード
===

Editor側でなくとも動きます。
これをAwake()のタイミングで呼び出して保存された情報を吸い出します。

```csharp:load
	public static void LoadXML(ViewPict vPict, bool angleOut)
	{
		ReleaseAll( vPict );
		vPict.m_PictDef = new List<PictDef>();
		TextAsset xmlFile = (TextAsset)Resources.Load(vPict.m_FileName, typeof(TextAsset));
		MemoryStream assetStream = new MemoryStream(xmlFile.bytes);
		XmlDocument xmlDoc = new XmlDocument();
		xmlDoc.Load(assetStream);

		XmlNode root = xmlDoc.DocumentElement;

		for (int i = 0; i < root.ChildNodes.Count; i++)
		{
			if (root.ChildNodes[i].Name == "PictView")
			{
				AnalyzePictNode(vPict, root.ChildNodes[i], angleOut);
			}
		}

		xmlDoc = null;
	}

	static void AnalyzePictNode( ViewPict vPict , XmlNode node , bool angleOut)
	{

		PictDef tempDef = new PictDef();
		for (int i = 0; i < node.ChildNodes.Count; i++)
		{
			if (node.ChildNodes[i].Name == "RotX")
			{
				tempDef.m_Rotate.x = float.Parse(node.ChildNodes[i].InnerText);
			}
			else if (node.ChildNodes[i].Name == "RotY")
			{
				tempDef.m_Rotate.y = float.Parse(node.ChildNodes[i].InnerText);
			}
			else if (node.ChildNodes[i].Name == "RotZ")
			{
				tempDef.m_Rotate.z = float.Parse(node.ChildNodes[i].InnerText);
			}
			else if (node.ChildNodes[i].Name == "Texture")
			{

				if (node.ChildNodes[i].InnerText == "null")
				{
					tempDef.m_Tex = null;
				}
				else
				{
					tempDef.m_Tex = (Texture)Resources.Load(node.ChildNodes[i].InnerText ,typeof(Texture));
					if (angleOut)
					{
						SerchAngle(node.ChildNodes[i].InnerText);
					}
					
				}

			}
			else if (node.ChildNodes[i].Name == "Radius")
			{
				tempDef.m_Radius = float.Parse(node.ChildNodes[i].InnerText);
			}
			else if (node.ChildNodes[i].Name == "EndGTime")
			{
				tempDef.m_EndGTime = float.Parse(node.ChildNodes[i].InnerText);
			}
			else if (node.ChildNodes[i].Name == "FadeIn")
			{
				tempDef.m_FadeIn = float.Parse(node.ChildNodes[i].InnerText);
			}
			else if (node.ChildNodes[i].Name == "FadeOut")
			{
				tempDef.m_FadeOut = float.Parse(node.ChildNodes[i].InnerText);
			}
			else if (node.ChildNodes[i].Name == "Effect")
			{
				tempDef.m_Effect = int.Parse(node.ChildNodes[i].InnerText);
			}
			else if (node.ChildNodes[i].Name == "MikuPX")
			{
				tempDef.m_MikuPos.x = float.Parse(node.ChildNodes[i].InnerText);
			}
			else if (node.ChildNodes[i].Name == "MikuPY")
			{
				tempDef.m_MikuPos.y = float.Parse(node.ChildNodes[i].InnerText);
			}
			else if (node.ChildNodes[i].Name == "MikuPZ")
			{
				tempDef.m_MikuPos.z = float.Parse(node.ChildNodes[i].InnerText);
			}
			else if (node.ChildNodes[i].Name == "MikuRX")
			{
				tempDef.m_MikuRot.x = float.Parse(node.ChildNodes[i].InnerText);
			}
			else if (node.ChildNodes[i].Name == "MikuRY")
			{
				tempDef.m_MikuRot.y = float.Parse(node.ChildNodes[i].InnerText);
			}
			else if (node.ChildNodes[i].Name == "MikuRZ")
			{
				tempDef.m_MikuRot.z = float.Parse(node.ChildNodes[i].InnerText);
			}
			else if (node.ChildNodes[i].Name == "MikuRW")
			{
				tempDef.m_MikuRot.w = float.Parse(node.ChildNodes[i].InnerText);
			}

		}
		vPict.m_PictDef.Add(tempDef);
	}
```

エディター拡張
===
エディター部分を作ってるコードになります。
特徴としては、
「レイアウトを作るためのコードの部分」と「ボタンを押した時、変更があった時等に動く部分」が
分離していることです。
こうすることでレイアウトを作る所はレイアウトだけを考えられ
データ修正もレイアウトに影響されることなく書くことが出来ます。

一見Undo処理も書いてるように見えますが、実際には機能しません。
サンプルでは入ってたんですが途中で面倒くさくなってちゃんと書きませんでした。
XMLをバイナリ化してデータ入れれば動くような気がしますがやってません。
※下記の行の辺りが名残です。
・Undo.RegisterUndo(target, "Step size change");
・EditorUtility.SetDirty(target);


```csharp:save
	public override void OnInspectorGUI()
	{
		base.OnInspectorGUI();

		ViewPict tempPoint = target as ViewPict;


		EditorGUILayout.BeginVertical();
		for (int i = 0; i < tempPoint.m_PictDef.Count  ;i++ )
		{
			EditorGUILayout.BeginHorizontal();

				EditorGUILayout.BeginVertical();
					Texture tempTex =(Texture) EditorGUILayout.ObjectField(	"",tempPoint.m_PictDef[i].m_Tex ,typeof(Texture) ,true);
					EditorGUILayout.BeginHorizontal();
						bool addButton = GUILayout.Button("add");
						bool removeButton = GUILayout.Button("remove");
						bool setAngle = GUILayout.Button("TSet");
					EditorGUILayout.EndHorizontal();
					EditorGUILayout.BeginHorizontal();
						bool mikuPos = GUILayout.Button("mikuPosGet");
					EditorGUILayout.EndHorizontal();
				EditorGUILayout.EndVertical();


				EditorGUILayout.BeginVertical();

					float tempEnd = EditorGUILayout.FloatField("EndTime", tempPoint.m_PictDef[i].m_EndGTime);
					float radius = EditorGUILayout.FloatField("Radius" , tempPoint.m_PictDef[i].m_Radius );

					float fadeIn = EditorGUILayout.FloatField("FadeIn", tempPoint.m_PictDef[i].m_FadeIn);
					float fadeOut = EditorGUILayout.FloatField("FadeOut", tempPoint.m_PictDef[i].m_FadeOut );
					int effect = EditorGUILayout.IntField("Effect", tempPoint.m_PictDef[i].m_Effect );

					
				EditorGUILayout.EndVertical();
			EditorGUILayout.EndHorizontal();
			Vector3 tempVec3 = EditorGUILayout.Vector3Field("Rotation", tempPoint.m_PictDef[i].m_Rotate);
			Vector3 tempMikuPos = EditorGUILayout.Vector3Field("Rotation", tempPoint.m_PictDef[i].m_MikuPos);

			if (tempTex)
			{
				Undo.RegisterUndo(target, "Step size change");
				tempPoint.m_PictDef[i].m_Tex = tempTex;
				EditorUtility.SetDirty(target); 
			}

			if (tempEnd != tempPoint.m_PictDef[i].m_EndGTime ||
				radius != tempPoint.m_PictDef[i].m_Radius ||
				tempVec3 != tempPoint.m_PictDef[i].m_Rotate ||

				fadeIn != tempPoint.m_PictDef[i].m_FadeIn ||
				fadeOut != tempPoint.m_PictDef[i].m_FadeOut ||
				effect != tempPoint.m_PictDef[i].m_Effect 
				)
			{
				Undo.RegisterUndo(target, "Step size change");
				tempPoint.m_PictDef[i].m_EndGTime = tempEnd;
				tempPoint.m_PictDef[i].m_Radius  = radius;
				tempPoint.m_PictDef[i].m_Rotate = tempVec3;

				tempPoint.m_PictDef[i].m_FadeIn  = fadeIn;
				tempPoint.m_PictDef[i].m_FadeOut = fadeOut;
				tempPoint.m_PictDef[i].m_Effect = effect;
				tempPoint.m_PictSphere.transform.rotation = Quaternion.Euler(tempPoint.m_PictDef[i].m_Rotate);

				EditorUtility.SetDirty(target);
				
			}

			if (addButton)
			{
				Undo.RegisterUndo(target, "Step size change");

				tempPoint.m_PictDef.Insert(i + 1, new ViewPict.PictDef());
				EditorUtility.SetDirty(target);
				i++;
			}

			if (removeButton)
			{
				Undo.RegisterUndo(target, "Step size change");

				tempPoint.m_PictDef.RemoveAt(i);
				EditorUtility.SetDirty(target);
				i--;
			}

			if (setAngle == true)
			{
				tempPoint.m_PictSphere.renderer.sharedMaterial.SetTexture("_MainTex", tempPoint.m_PictDef[i].m_Tex);
				tempPoint.m_PictSphere.transform.rotation = Quaternion.Euler(tempPoint.m_PictDef[i].m_Rotate);

				tempPoint.m_Miku.transform.position = tempPoint.m_PictDef[i].m_MikuPos;
				tempPoint.m_Miku.transform.rotation = tempPoint.m_PictDef[i].m_MikuRot;
			}

			if (mikuPos == true)
			{
				tempPoint.m_PictDef[i].m_MikuPos = tempPoint.m_Miku.transform.position;
				tempPoint.m_PictDef[i].m_MikuRot = tempPoint.m_Miku.transform.rotation;
			}
		}
		EditorGUILayout.EndVertical();


		EditorGUILayout.BeginHorizontal();
			if (GUILayout.Button("test add"))
			{
				tempPoint.m_PictDef.Add(new ViewPict.PictDef());
			}

			if (GUILayout.Button("Save"))
			{
				Save(tempPoint);
			}
			
			if( GUILayout.Button("Load") )
			{
				ViewPict.LoadXML(tempPoint , true);
			}
		EditorGUILayout.EndHorizontal();

	}
```