最初に
この記事では、Tencentが公開しているOSSのXLuaを使わせていただき、量産できるノベルゲーシステムの紹介です。
ターゲット
- アドベンチャーゲームを作りたい方
- 宴が高くて手が出せない方
- テキスト(スクリプトベース)でアドベンチャーゲームを作りたい方
XLuaとは
Unityのランタイムで動作するLua言語が使えるようになるOSSです。
今回はこちらのXLuaを用いて量産できるノベルゲーシステムを作りました。
拡張すると出来上がるスクリプト
label("主人公") --名前のところに表示する文字
show_unit("主人公", "boy") -- 登録する名前
text("初めまして、主人公です")
text("アドベンチャーゲームとかで使えるシステムです。")
move_unit("主人公", 100) -- 名前, X, Y
text("右に移動することもできます")
move_unit("主人公", -100, 1)
text("左に移動することもできます")
move_unit("主人公", 0, 1)
text("他にも実装すればさまざまなことができます")
これらのスクリプトは、事前に読み込んでコマンドを積み実行していく方式ではなくランタイムで動作します。
実行するために必要なこと
- XLuaをダウンロード
Unity 2018LTS or higher
XLua 2.1.15 or hight
mac os BigSur 11.2.1
https://github.com/Tencent/xLua/releases/download/v2.1.15f1/xlua_v2.1.15.zip
URLから最新のXLuaをダウンロードします。
2.Unityプロジェクトに配置
Unityプロジェクトを作成し、Assetsと同階層にダウンロードしてきたXLua/Toolsを配置します。
projectname/Tools/FileSignature.exe
projectname/Tools/FileSignature.ext.meta
projectname/Tools/XXX...
このようになるはずです。
次にAssets/XLuaディレクトリを作成します。
ダウンロードしてきたxlua_v2.1.15/Assets/XLuaの中身を全てAssets/XLua/に配置します。
Assets/Pluginsディレクトリを作成します。(既にある場合は大丈夫です)
ダウンロードしてきたxlua.v2.1.15/Assets/Plugins/の中身を全てAssets/Pluginsに配置します。
ここまでで大体の配置(インポート)は完了です。
参考:https://baba-s.hatenablog.com/entry/2017/09/26/090000
AssemblyDefinisionの定義
必要な方は設定してください。
残念ながらXLuaは標準でAssembluDefinisionが定義されていませんので自分で作成する必要があります。
Assets/XLua/Tencent.XLua.asmdef
Assets/XLua/Src/Editor/Tencent.XLua.Editor.asmdef
以上の二つを定義すれば動作するかと思います。
もちろんEditorはEditorにチェックを入れてXLuaのみを参照します。
XLuaは何も参照しません。
(name spaceをXlua.asmdefにすると命名が被ってエラーになるのでご注意を)
プログラム
XLuaが正しくインポートされているか確認します。
適当なゲームオブジェクトを作成し、以下のプログラムを実行してください
using UnityEngine;
using XLua;
public class Sample : MonoBehaviour
{
void Start()
{
var luaEnv = new LuaEnv();
luaEnv.DoString("print(\"luaスクリプトの実行\")");
}
}
Consoleにて、luaスクリプトの実行と表示されたら成功です🎉
DLLNotFoundとか他のエラーが出た時
以下の対応をしてみてください。
-
PlayerSetting.Configuration.ScriptingBackEndをMonoからIL2CPPに変更
-
公式のIssueに上がっているもの
https://github.com/Tencent/xLua/issues/481
https://github.com/Tencent/xLua/issues/766
XLuaのインポート、動作確認が終わりました。
ここから実際にノベルゲーシステムの構成をしていきます。
これは任意ですが、非同期処理やクリック処理などの処理簡潔化のために以下のライブラリを導入しています。
(コードにライブラリのコードが散見されるかもしれませんが、ご容赦ください)
- UniTask
実装
Resources/Scenario/sample.lua.txtを作成します。
ディレクトリがない場合は作成してください。
sample.lua.txtが動くように実装していきます。
text("表示するテキスト")
一旦このようなスクリプトを記述しておきます。
次に、このtextコマンドの実装をします。
Resources/Scenario/lib.lua.txtを作成します。
ディレクトリがない場合は作成してください。
command = CS.Main.MainPresenter;
util = require 'xlua.util'
function text(text)
command.ShowText(text)
end
(CS.Main.MainPresenterについて呼び出したいpublicでstaticな関数が定義されているC#側のネームスペースを指定すれば大丈夫です)
Main.MainPresenterの作成
lua側から呼び出す関数をC#側に記述します。
Main.MainPresenterにしていますが、別の名前でも問題ないです。(lib.lua.txtのCS.Main.MainPresenterはCS.変更後の名前にする必要があります)
using UnityEngine;
using XLua;
namespace Main
{
private static MainView _view;
public class MainPresenter
{
public void Initialize(MainView mainView)
{
_view = mainView;
var luaEnv = new LuaEnv();
var libText = GetScenario("ScenarioLib/lib.lua"); //libの実行
luaEnv.DoString(libText);
var scenario = GetScenario("Scenario/sample.lua"); // メインシナリオの実行
luaEnv.DoString(scenario);
}
private string GetScenario(string path)
{
return Resources.Load<TextAsset>(path).text;
}
public static void ShowText(string text)
{
Debug.Log(text);
}
}
}
この状態で起動してもInitialzieが呼ばれないため、MainViewを作成します。
using UnityEngine;
namespace Main
{
public class MainView : MonoBehaviour
{
private void Start()
{
var mainPresenter = new MainPresenter();
mainPresenter.Initialize(this);
}
}
}
MainViewを適当なゲームオブジェクトにアタッチし起動します。
コンソールに表示するテキストと表示されれば成功です🎉
一旦この状態でUI有りの画面に表示してみます。
UI適用
以下のような構成画面配置にしています。
ここはお好きなデザインにしてみてください。
MainView、MainPresenterにSetText系のメソッドを増やします。
using UnityEngine;
using UnityEngine.UI;
namespace Main
{
public class MainView : MonoBehaviour
{
[SerializeField]
private Text _text;
private void Start()
{
var mainPresenter = new MainPresenter();
mainPresenter.Initialize(this);
}
public void SetText(string text)
{
_text.text = text;
}
}
}
using UnityEngine;
using XLua;
namespace Main
{
public class MainPresenter
{
private static MainView _view;
public void Initialize(MainView mainView)
{
_view = mainView;
var luaEnv = new LuaEnv();
var libText = GetScenario("ScenarioLib/lib.lua");
luaEnv.DoString(libText);
var scenario = GetScenario("Scenario/sample.lua");
luaEnv.DoString(scenario);
}
private string GetScenario(string path)
{
return Resources.Load<TextAsset>(path).text;
}
public static void ShowText(string text)
{
_view.SetText(text);
}
}
}
Unity Scene上のMainViewコンポーネントにTextをアタッチし、起動すると以下の画面のようになります。
これでUIの適用が完了です🎉
ラベルや背景の命令も同様に増やしていくことが可能です。
クリック待ちを行う
クリック待ちを実装していきます。
現状だと普通のノベルゲーのようにクリックで物語を進めることができません。
テキストを表示するたびにクリック待ちのコマンドを入れることで実装していきます。
事前準備
Main直下に以下のC#を追加してください。
lua側からUnityの型を扱いたいときに事前に登録しておく必要があります。
Dotween等も使いたい場合はこちらに追加記述します。
using System.Collections.Generic;
using System;
using UnityEngine;
using XLua;
public static class XLuaGenConfig
{
//lua中要使用到C#库的配置,比如C#标准库,或者Unity API,第三方库等。
[LuaCallCSharp]
public static List<Type> LuaCallCSharp = new List<Type>()
{
typeof(System.Object),
typeof(UnityEngine.Object),
typeof(Vector2),
typeof(Vector3),
typeof(Vector4),
typeof(Quaternion),
typeof(Color),
typeof(Ray),
typeof(Bounds),
typeof(Ray2D),
typeof(Time),
typeof(GameObject),
typeof(Component),
typeof(Behaviour),
typeof(Transform),
typeof(Resources),
typeof(TextAsset),
typeof(Keyframe),
typeof(AnimationCurve),
typeof(AnimationClip),
typeof(MonoBehaviour),
typeof(ParticleSystem),
typeof(SkinnedMeshRenderer),
typeof(Renderer),
typeof(WWW),
typeof(Mathf),
typeof(System.Collections.Generic.List<int>),
typeof(Action<string>),
typeof(UnityEngine.Debug),
typeof(WaitForSeconds),
typeof(System.Collections.IEnumerator),
};
//C#静态调用Lua的配置(包括事件的原型),仅可以配delegate,interface
[CSharpCallLua]
public static List<Type> CSharpCallLua = new List<Type>()
{
typeof(Action),
typeof(Func<double, double, double>),
typeof(Action<string>),
typeof(Action<double>),
typeof(UnityEngine.Events.UnityAction),
typeof(System.Collections.IEnumerator)
};
//黑名单
[BlackList]
public static List<List<string>> BlackList = new List<List<string>>()
{
new List<string>() {"System.Xml.XmlNodeList", "ItemOf"},
new List<string>() {"UnityEngine.WWW", "movie"},
#if UNITY_WEBGL
new List<string>(){"UnityEngine.WWW", "threadPriority"},
#endif
new List<string>() {"UnityEngine.Texture2D", "alphaIsTransparency"},
new List<string>() {"UnityEngine.Security", "GetChainOfTrustValue"},
new List<string>() {"UnityEngine.CanvasRenderer", "onRequestRebuild"},
new List<string>() {"UnityEngine.Light", "areaSize"},
new List<string>() {"UnityEngine.Light", "lightmapBakeType"},
new List<string>() {"UnityEngine.WWW", "MovieTexture"},
new List<string>() {"UnityEngine.WWW", "GetMovieTexture"},
new List<string>() {"UnityEngine.AnimatorOverrideController", "PerformOverrideClipListCleanup"},
#if !UNITY_WEBPLAYER
new List<string>() {"UnityEngine.Application", "ExternalEval"},
#endif
new List<string>() {"UnityEngine.GameObject", "networkView"}, //4.6.2 not support
new List<string>() {"UnityEngine.Component", "networkView"}, //4.6.2 not support
new List<string>() {"System.IO.FileInfo", "GetAccessControl", "System.Security.AccessControl.AccessControlSections"},
new List<string>() {"System.IO.FileInfo", "SetAccessControl", "System.Security.AccessControl.FileSecurity"},
new List<string>() {"System.IO.DirectoryInfo", "GetAccessControl", "System.Security.AccessControl.AccessControlSections"},
new List<string>() {"System.IO.DirectoryInfo", "SetAccessControl", "System.Security.AccessControl.DirectorySecurity"},
new List<string>() {"System.IO.DirectoryInfo", "CreateSubdirectory", "System.String", "System.Security.AccessControl.DirectorySecurity"},
new List<string>() {"System.IO.DirectoryInfo", "Create", "System.Security.AccessControl.DirectorySecurity"},
new List<string>() {"UnityEngine.MonoBehaviour", "runInEditMode"},
};
}
[SerializeField]
private Button _button;
public IEnumerator WaitClicked()
{
yield return _button.OnClickAsync().ToCoroutine(); // OnClickAsync()はUniTaskの機能です
}
_buttonがないと思うので、以下の画像のようなヒエラルキー構造にしbuttonを追加してください。
大きさは画面全体のストレッチでColorのaを0に設定し透明な状態にします。
lua側でWaitClickedを扱えるようにlib.lua.txtに定義します
function wait_clicked()
command.WaitClicked()
end
実際に再生したいスクリプトで使用してみます
text("表示するテキスト1")
wait_clicked()
text("表示するテキスト2")
wait_clicked()
text("表示するテキスト3")
wait_clicked()
入力待ちができていればOKです🙆♂️
まとめ
ここまでで基本的なコマンドの実装を行ってきました。
この流れで一番最初にgif画像で上げたような画面を作るには、他のコマンドの実装していくことになります。
ぜひ頑張ってみてください!