【Unity】ノベルゲーム用アセットFungusをVRで使えるようにしてみた
Unityでのノベルゲーム製作で有用なアセットに、Fungus
があります。
私はOculus用のVRゲームを開発しており、その中で会話ウィンドウを出したくなりました。
しかし残念ながら、Fungus
はVRに対応していません。そこで、Fungus
をVRでも使えるように機能追加してみました。
作ったもの
こんな感じで会話ウィンドウが出るようになります。
やらなければいけないこと
会話ウィンドウをVRで使えるようにします。具体的には以下の機能を実装します。
- 会話ウィンドウを3D空間上に発生させる。
- Fungusのデフォルトの会話ウィンドウは、画面前面に表示されるようになっています。
- 発言者の口から会話ウィンドウを発生させ、プレイヤーの目の前に移動する。
- 発言者から発せられたように見せることができます。
- プレイヤーの頭の動きに追随させる。
- 好みによりますが、空間上に会話ウィンドウが固定されているより見やすいと感じました。
環境
- Unityのバージョン: 2019.2.18f1(64-bit)
- Fungusのバージョン: v3.12.0
- Oculus Integrationのバージョン: 12.0
作り方
以下の流れで作成します。
- VR用の会話ウィンドウを作成する
- VR用のSayコマンドを作成する
- 会話を次の状態に進めるスクリプトを作成する
- SayDialogの設定を行う
VR用の会話ウィンドウを作成する
VR用の会話ウィンドウ、SayDialogForVR
コンポーネントを作成します。
SayDialog
コンポーネントとの違いは、会話ウィンドウが3D空間上に配置されること、発言者の位置から会話ウィンドウが発生し、定位置に移動することです。
using UnityEngine;
using System;
namespace Fungus
{
public class SayDialogForVR : SayDialog
{
Vector3 prevPosition = Vector3.zero;
Quaternion prevRotation = Quaternion.identity;
Vector3 prevScale = Vector3.zero;
Vector3 toScale;
public float speed = 0.05f;
public Vector3 localPosition = new Vector3(0.0f, -0.222f, 2.3732f);
public Transform mainCamera;
Vector3 GetDialogPosition()
{
return mainCamera.position
+ mainCamera.right * localPosition.x
+ mainCamera.up * localPosition.y
+ mainCamera.forward * localPosition.z;
}
Quaternion GetDialogRotation()
{
return Quaternion.LookRotation(GetDialogPosition() - mainCamera.position);
}
public void SetDialogTransform(Transform from)
{
prevPosition = from ? from.position: GetDialogPosition();
prevRotation = GetDialogRotation();
prevScale = from ? Vector3.zero : toScale;
}
protected override void Start()
{
toScale = transform.localScale;
mainCamera = mainCamera ?? Camera.main.transform;
base.Start();
}
protected override void UpdateAlpha()
{
transform.position = Vector3.Lerp(prevPosition, GetDialogPosition(), speed);
transform.rotation = Quaternion.Lerp(prevRotation, GetDialogRotation(), speed);
transform.localScale = Vector3.Lerp(prevScale, toScale, speed);
prevPosition = transform.position;
prevRotation = transform.rotation;
prevScale = transform.localScale;
base.UpdateAlpha();
}
}
}
VR用のSayコマンドを作成する
Fungusでは自作コマンドを作成できます。
VR用のSayコマンド、SayForVR
コマンドを作成しましょう。先ほど作成した、SayDialogForVR
を呼び出すコマンドです。
using UnityEngine;
namespace Fungus
{
[CommandInfo("Narrative",
"SayForVR",
"Writes text in a dialog box.")]
[AddComponentMenu("")]
public class SayForVR : Say
{
// 子オブジェクトから、指定のタグが付いたTransformを探す。
protected Transform FindTagInChildren(Transform t, string tagName)
{
if (t == null)
{
return null;
}
if (t.tag == tagName)
{
return t;
}
foreach (Transform child in t)
{
Transform s = FindTagInChildren(child, tagName);
if (s)
{
return s;
}
}
return null;
}
public Transform talker;
public override void OnEnter()
{
Transform from = FindTagInChildren(talker, "Head") ?? talker;
((SayDialogForVR)setSayDialog).SetDialogTransform(from);
base.OnEnter();
}
}
}
会話を次の状態に進めるスクリプトを作成する
会話を次に進めるコンポーネントは、SayDialogについているDialogInput
コンポーネントなのですが、このコンポーネントは、UIへの入力検知にStandaloneInputModule
を使用していることを前提としています。
通常、Hierarcy上にCanvas
等のUIオブジェクトを配置したとき、同時にEventSystem
というGameObjectが配置されます。
このEventSystemにはStandAloneInputModule
というコンポーネントがくっついています。これによりUIオブジェクトがキー入力、マウス入力を検知できるようになるわけですが、Oculusでは、入力にOculus Touch
コントローラを使うため、このStandAloneInputModule
をOVRInputModule
に置き換えて使うはずです。
そこで、OVRInputModule
を使用するように書き換えた、DialogInputForVR
を作成します。
コード
以下のコードでは、Oculus Touchコントローラの、ABXYのいずれかのボタンを押したときに、会話ウィンドウが次に進むように設定しています。デバッグ用に、キーボードのEnter、Space、Zも有効化しています。
using UnityEngine;
using UnityEngine.EventSystems;
namespace Fungus
{
public class DialogInputForVR : DialogInput
{
protected override void Update()
{
if (EventSystem.current == null)
{
return;
}
if (writer != null && writer.IsWriting)
{
// ここを好きなキーに設定する
if (OVRInput.GetDown(OVRInput.Button.One)
|| OVRInput.GetDown(OVRInput.Button.Two)
|| OVRInput.GetDown(OVRInput.Button.Three)
|| OVRInput.GetDown(OVRInput.Button.Four)
|| Input.GetKeyDown(KeyCode.Return)
|| Input.GetKeyDown(KeyCode.Z)
|| Input.GetKeyDown(KeyCode.Space))
{
SetNextLineFlag();
}
}
if (nextLineInputFlag)
{
var inputListeners = gameObject.GetComponentsInChildren<Fungus.IDialogInputListener>();
for (int i = 0; i < inputListeners.Length; i++)
{
var inputListener = inputListeners[i];
inputListener.OnNextLineEvent();
}
nextLineInputFlag = false;
}
}
}
}
参照の追加
上記のスクリプトはAssets/Fungus/Script/Componentsに作りましたが、その場合、Fungus.csprojにOculus.VRのアセンブリ参照を追加する必要があります。そうしないとOVRInput
がないといわれてコンパイルエラーになります。
- Unity Editor上でAssets/Fungus/Fungus.asmdefを選択してください。
- InspectorでAssembly Definition ReferencesにOculus.VRを追加してください。
Fungus.csprojではなく、Oculus.VRが参照されている別のプロジェクト(Assembly-CSharp.csproj)でスクリプトを作成してもよいです。
SayDialogの設定を行う
Funugsでの会話ウィンドウであるSayDialog
を、VRで使えるように設定変更します。
-
SayDialogを配置します。
-
SayDialogの設定を変更します。
-
Say Dialogコンポーネントを外します。
-
Say Dialog For VRコンポーネントをつけます。
-
Dialog Inputコンポーネントを外します。
-
Dialog Input For VRコンポーネントをつけます。
使い方
SayForVRコマンド
Flowchart上で、SayForVRコマンドを作成します。冒頭のgifだとこんな感じです。
通常のSayコマンドと同じように使えますが、Talker
というパラメータが一つ増えているはずです。
ここに、発言者のTransformを入れましょう。
すると、発言者の口(頭?)から会話ウィンドウが出てきたように見えるはずです。
Talker
を設定しなかった場合は、会話ウィンドウがその場に発生しますので、地の分の時に使えます。
3Dモデルの頭の位置のTransformを探し出すのがめんどくさい
3Dモデルの頭の位置は、モデルによってはHierarchyの階層の深いところにあると思います。例えば今使用している3Dモデルはこんな深いところに頭のTransformがあります。
毎回これを探し出して、SayForVRコマンドで指定してやらないといけないのはめんどくさいです。そんな時は、頭のTransformを持つGameObjectに、Head
タグをつけましょう。
SayForVRコマンドの中で、デフォルトでHead
というタグのついたTransformを、子階層のTransformを探してくる仕様になっています。
これにより、3Dモデルの一番親階層のTransformをTalkerに指定してやればよくなるので、あまりめんどくさくないですね。
まとめ
Fungusの主要な機能である、SayDialog
をVRでも使用できるようにしました。
Menuも需要があれば対応してみます。