「ゲームとUI間でデータをやり取りする」の編集履歴(バックアップ)一覧に戻る
ゲームとUI間でデータをやり取りする」を以下のとおり復元します。
#contents

* ゲームとUI間でデータをやり取りする
このチュートリアルではSkyrimでのプレイヤー名をUI上で表示し、その名前を別のものに変更して承認ボタンを押したら、ゲーム内のプレイヤー名が変わるものを作ります。

ここでは以下の内容ができるようになります。
・CLIKコンポーネントを利用する
・ゲームからUIにデータを送る
・UIからゲームにデータを送る

#region(close,Flash関連ファイル)
本チュートリアルのFlashに関連するファイルを置いておきます。Adobe Flashを所持している方はこれ抜きでチュートリアルを進めても問題ありません。
ソフトを持ってなかったりするけど動作は見てみたい、というような方はご利用ください。
[[ゲームとUI間でデータをやり取りする]]
|ファイル名|説明|
|02test.fla|Adobe Flashで開く編集ファイルです。|
|02test.swf|コンパイルして作成したデータやり取りのインターフェースです。CKのスクリプトで読み込むことで開くことができます。|
|MainPanel.as|ゲームとUIのやり取りのためのASソースです。|
#endregion

**必要なオブジェクトを作る

Flashを用いて名前の入力域、名前変更を承認する「Submit」ボタン、名前の変更をキャンセルする「Cancel」ボタンを作ります。

#image(01screen.jpg,https://img.atwikiimg.com/www50.atwiki.jp/skyrim_mod/attach/81/938/01screen.jpg)

画面では、黒い領域に「My name is ...」と表示し、下の「name」の所にプレイヤー名を表示します。
この「name」の領域はこちらの入力を受け付けるようにし、ここに名前を入力して「Submit」ボタンを押すことでプレイヤー名が変更できるという構造です。
ファイル・フォルダ構造について、制作者の環境ではプロジェクトのファイル名は02test.flaとし、「UIを開く・閉じる」のプロジェクトファイルと同じ場所に新しく02testフォルダを作成し、そこに保存しております。

***各オブジェクトの作成
UIを開く・閉じるで行ったように、名前を表示する領域、承認ボタン、キャンセルボタンの3つのボタンを作成します。
必要なオブジェクトとテキストを配置します。
各設定パラメータは以下のようにしています。

・領域・ボタン
||名前領域|承認ボタン|キャンセルボタン|
|X|440|340|680|
|Y|200|400|400|
|W|400|260|260|
|H|160|120|120|
|塗りつぶし色|#000000|#CCCCCC|#CCCCCC|

・テキスト
||New name is...|name|Submit|Cancel|
|フォントファミリー|>|>|>|MS UI Gothic|
|サイズ|48.0|64.0|32.0|32.0|

テキストエリアについて、選択可能かどうかは以下の画像の部分で設定します。グレーの状態が選択できる状態です。
名前を表示する場所(「name」としているテキストボックス)はユーザーが変更できるようにするため、選択可能な状態にしておきます。

#image(02selectabletext.jpg,https://img.atwikiimg.com/www50.atwiki.jp/skyrim_mod/attach/81/939/02selectabletext.jpg)

名前を表示する場所は、<インスタンス名>の部分を「textField」にします。
絶対にこの名前である必要があります(理由は後述)。大文字小文字にも注意しましょう。

#image(03instancename.jpg,https://img.atwikiimg.com/www50.atwiki.jp/skyrim_mod/attach/81/940/03instancename.jpg)

[埋め込み...]というボタンからフォントの埋め込みを行います。
Flash上で変更の可能性があるテキストボックスはswf自体にフォントを埋め込む必要があるようです。
文字の範囲について、「大文字」~「日本語 漢字」あたりまでチェックが入ればいいと思います。

#image(04infusefont.jpg,https://img.atwikiimg.com/www50.atwiki.jp/skyrim_mod/attach/81/941/04infusefont.jpg)

①名前領域と②承認ボタンと③キャンセルボタンをそれぞれ別々のムービークリップとして変換します。
ここでは名前、識別子、クラスを以下のように設定します。

||名前領域|承認ボタン|キャンセルボタン|
|名前|NameArea|SubmitButton|CancelButton|
|識別子|~|~|~|
|クラス|gfx.controls.TextInput|>|gfx.controls.Button|

クラスの所がそれぞれ謎のテキストになっていますが、これはskyuiリソースに入っているCLIKフォルダ以下のパスを示しています。
名前領域はCLIK\gfx\controls\TextInput.as、ボタン二つはCLIK\gfx\controls\Button.asが適用されます。

#image(05CLIKComponent.jpg,https://img.atwikiimg.com/www50.atwiki.jp/skyrim_mod/attach/81/942/05CLIKComponent.jpg)

#region(close,CLIKについて)
SkyUIの中に入っているCLIKフォルダには、Scaleformと呼ばれる「ゲーム内でFlashを用いたUIを運用するためのコンポーネント」に関するソースが入っています。

元々Flashはアニメーションを作るためのものであり、ゲーム用のUIとして活用するには色々と問題がありました。
そのためFlashのムービークリップを拡張してゲーム内でボタンやテキスト、スクロールバーのような構造を簡単に作ることができるようにしたのがScaleformのCLIKコンポーネントと呼ばれるものです。
SkyUIチームはこれらを拡張してSkyrim専用にしたソースを作成しており、それらがCommonフォルダ以下に入っています。
#endregion

ライブラリの項目に3つのムービークリップシンボルができていたらOKです。

#image(06library.jpg,https://img.atwikiimg.com/www50.atwiki.jp/skyrim_mod/attach/81/943/06library.jpg)

***すべてをまとめて1つのムービークリップにする
ステージ上の3つのムービークリップについて、インスタンス名を以下のように設定します。

|名前領域|nameArea|
|承認ボタン|submitButton|
|キャンセルボタン|cancelButton|

この3つのムービークリップをすべて選択し、シンボルへ変換から1つのムービークリップにします。
シンボルに変換ウィンドウの名前、識別子、クラスはすべてMainPanelにします。
また、これによって作成したムービークリップのインスタンス名はmainPanelにします。

#image(06mainpanel.jpg,https://img.atwikiimg.com/www50.atwiki.jp/skyrim_mod/attach/81/944/06mainpanel.jpg)

こうする理由は、以降のスクリプトファイルの編集時に、MainPanel.asだけを用いて名前領域、商人ボタン、キャンセルボタンの3つを操作するためです。

**スクリプトファイルの作成
スクリプトファイル「MainPanel.as」を作成し、以下の内容を記述します。

 import gfx.controls.TextInput;
 import gfx.controls.Button;
 import Shared.GlobalFunc;
 
 class MainPanel extends MovieClip 
 {
 	/* PRIVATE Variables */
 	private var MENU_NAME :String;
 
 	/* SHORTEND Variables */
 	public var _optionText: TextField;
 	
 	/* STAGE ELEMENTS */
 	public var nameArea: TextInput;
 	public var submitButton: Button;	
 	public var cancelButton: Button;
 	
 	public function MainPanel()
 	{
 		super();
 		
 		MENU_NAME = "CustomMenu";
 		_optionText = nameArea.textField;
 	}
 	
 	// @override MovieClip
 	private function onLoad()
 	{
 		super.onLoad();
 		
 		nameArea.addEventListener("focusIn", this, "startInput");
 		nameArea.addEventListener("focusOut", this, "endInput");
 
 		submitButton.addEventListener("click", this, "onSubmitPress");
 		cancelButton.addEventListener("click", this, "onCancelPress");
 	}
 	
 	private function startInput()
 	{
 		_optionText.type = "input";
 		Selection.setFocus(_optionText);
 		Selection.setSelection(0,0);
 		skse.AllowTextInput(true);
 	}
 	
 	private function endInput()
 	{
 		_optionText.type = "dynamic";
 		skse.AllowTextInput(false);
 	}
 	
 	private function onCancelPress()
 	{
 		skse.CloseMenu(MENU_NAME);
 	}
 	
 	private function onSubmitPress()
 	{
 		if (_optionText.text != ""){
 			skse.SendModEvent("TEST_setName", _optionText.text);
 			skse.CloseMenu(MENU_NAME);
 		}
 	}
 	
 	public function startMenu(name: String)
 	{
 		GlobalFunc.MaintainTextFormat()
 		_optionText.SetText(name); 
 	}
 }

***nameAreaとsubmitButtonとcancelButton
初見では混乱するかもしれませんが、コンストラクタやonLoadにおいて、nameAreaやsubmitButton、cancelButtonのオブジェクトを何の初期化もなしに扱っています。
これはFlashでステージ上に設置したオブジェクトに対して設定した<インスタンス名>が関わっており、あそこで設定した名称と同じ名前の変数はそのオブジェクトのインスタンス化が予め行われている扱いになります。

MainPanelはステージ上に配置されたNameAreaとSubmitButtonとCancelButtonの3つのムービークリップで構成されます。先ほどの設定にてこれらの3つのムービークリップはそれぞれnameArea、submitButton、cancelButtonというインスタンス名を付けました。

なのでMainPanel.as上では、nameAreaという変数は最初からNameAreaムービークリップのインスタンス化が済んでおり、これを用いることでステージ上に配置されたNameAreaを操作することができるのです。
同様にSubmitButtonとCancelButtonもすでにインスタンス化が済んだ状態でsubmitButton、cancelButtonという変数名で扱えます。

#image(07instance.jpg,https://img.atwikiimg.com/www50.atwiki.jp/skyrim_mod/attach/81/945/07instance.jpg)

NameAreaのオブジェクト内で名前を表示する場所をtextFiledというインスタンス名にしたのもこれが関係しており、CLIK\gfx\TextInput.asファイルではテキストを記述する領域を「textFiled」という変数名で取り扱っているため、名前を揃える必要がありました。

MainPanel.as内では、今後はnameArea.textFiledという形でそこに書いているテキストを参照したり代入したりすることができるようになります。
ただ、いちいち長い文章を書くのは面倒なので、コンストラクタで_optionTextという短い名前で扱うようにしています。

***イベントの設定
nameArea、submitButton、cancelButtonはそれぞれaddEventListenerを実行してしていますが、これらは「第一引数の名称のイベントが発生した場合、第三引数の関数を実行する」という仕組みを登録するためのCLIKコンポーネントが使えるメソッドです。
focusIn/focusOutは「テキストが入力状態になった/入力状態が解除された」、clickは「クリックされた」というイベントに対応しています。
どのようなイベントがあるかは[[こちら>https://help.autodesk.com/view/SCLFRM/ENU/?guid=__scaleform_help_clik_clik_user_guide_prebuilt_components_html]]の「Topics in this section」から進んだところにある「Events」の項目で確認できます。

CLIKコンポーネントを利用する場合、onPressなどのAction Scriptのデフォルトのイベントハンドラは使わず、すべてaddEventListenerを使用します。

***各関数の説明
startInputとendInputは名前入力の開始、終わりに起動します。
テキストフィールドのtypeプロパティは「入力可能(input)」か「入力不可能(dynamic)」かを示します。
Selectionは「今選んているもの」に対してなんやかんやできるオブジェクトです。

setFocusはそのオブジェクトにフォーカスを当てます。テキストフィールドなら入力状態になります。
setSelectionは選択範囲のはじめと終わりを設定するものです。テキストフィールドなら「第一引数の文字目から第二引数の文字目までを選択する」という感じです。

skse.AllowTextInputはキーボードの状態をテキスト入力に対応させるかどうかです。これがtrueのままUIを終わらせた場合、ゲーム中でも「テキスト入力中」という扱いになり、キーボードによる行動が制限されます。
なのでフォーカスが外れた(入力が終了した)際にはAllowTextInputをfalseにする必要があります。

onSubmitPressとstartMenuはゲームとのデータのやり取りを行うための関数になっています。
Papyrusの構造も含めて説明が必要なため、これらの関数の役割は後述します。

**コンパイル
Flashからコンパイルを行い、.swfファイルを作成します。

**Mod環境の構築
CKを用いてModを作成します。

***CKからUIを起動するMod環境を作る
UIを開く・閉じると同じように、新規mod環境を作成し、適当なQuestを作り、スクリプトとして以下の内容を追加します。

 ;CONSTANTS
 string property MENU_NAME = "CustomMenu" AutoReadonly
 int property MENU_KEY = 210 AutoReadonly
 string property MENU_ROOT = "_root.mainPanel" AutoReadonly
 
 Event OnInit()
 	RegisterKey()
 EndEvent
 
 Function OnGameReload()
 	RegisterForModEvent("TEST_setName", "OnSetName")
 EndFunction
 
 Function RegisterKey()
 	RegisterForKey(MENU_KEY)
 EndFunction
 
 Event OnKeyDown(int KeyCode)
 	if (KeyCode == MENU_KEY && !UI.IsMenuOpen(MENU_NAME))
 		UI.OpenCustomMenu("02test", 0)
 		UI.InvokeString(MENU_NAME, MENU_ROOT + ".startMenu", Game.GetPlayer().GetBaseObject().GetName())
 	endif
 EndEvent
 
 Event OnSetName(string eventName, string strArg, float numArg, Form sender)
 	Game.GetPlayer().GetBaseObject().SetName(strArg)
 EndEvent
 
また、Aliasをプレイヤーで設定し、Aliasのスクリプトとして以下の内容を追加します。

 aaaMyQuest Property QuestScript Auto ;「aaaMyQuest」はQuestのScriptとして追加したスクリプト名を使います。
 
 Event OnInit()
 	QuestScript.OnGameReload()
 EndEvent
 
 Event OnPlayerLoadGame()
 	QuestScript.OnGameReload()
 EndEvent
 
ここで注目するのはOnGameReloadで実行している「RegisterForModEvent」と、OnKeyDown内で実行している「UI.InvokeString」、そして「OnSetName」です。
ソースコードを見れば大体わかると思いますが、以下のような構造でゲームとUI上でデータのやりとりをしています。

#image(08systemflow.jpg,https://img.atwikiimg.com/www50.atwiki.jp/skyrim_mod/attach/81/946/08systemflow.jpg)

#region(close,ソースコードの詳しい解説)
・UI.InvokeString
UI.InvokeStringは、開いているUIに定義されている関数に対して、文字列を引数として呼び出すことができます。
第一引数としてメニュー名、第二引数としてどの関数か、第三引数として送信する文字列を指定します。
第二引数で指定する関数はフルパスで指定する必要があります。
Flashにおけるオブジェクトは「_root」を基本とした階層構造になっており、Flashのステージ上に配置されたオブジェクトのインスタンス名を辿ることで目的の関数を指定します。

#image(09_root.jpg,https://img.atwikiimg.com/www50.atwiki.jp/skyrim_mod/attach/81/947/09_root.jpg)

OpenCustomMenuにてUIの表示が完了した後(UIのインスタンスが作成し終えた後)、この関数を実行してStartMenuを起動、プレイヤーの名前を渡しているという構図です。

InvokeStringの他にどのようなものがあるかは、[[ここ>https://www.creationkit.com/index.php?title=UI_Script]]で確認できます。
Invokeの後に型名があるものはその型を送信します。
末尾にAとついているものはその型の配列を送信します。

.as上の関数で送信された物を受け取る場合、括弧内に適当に変数名を書けばその名前で受け取ります。
今回のケースではstartMenuの変数名を「name」としていますが、別名でも問題ありません。
ただ、型を指定する場合はInvoke〇〇関数で送信する型と受け取る型は一致させる必要があります
Invoke関数に対応する型は以下の通りです。

|使う関数|対応するActionScriptの型|
|InvokeBool|Boolean|
|InvokeInt|Number|
|InvokeFloat|Number|
|InvokeNumber|Number|
|InvokeString|String|
|Invoke〇〇A|Array|
|InvokeForm|不明|

また、何も書かないということもできます。この場合、「arguments」という配列型に値が入り、以下のような感じで使用できます。

 public function startMenu()
 {
 	GlobalFunc.MaintainTextFormat()
 	_optionText.SetText(arguments[0]); //Invokeで投げられた値はargumentsという配列型で使用
 }

・RegisterForModEventとOnSetName
RegisterForModEventはイベントによるコールバックを自分で登録することができる関数です。
第一引数としてイベント名を設定し、その名前のイベントが発生した場合に、第二引数の関数を実行します(関数も文字列として登録している点に注意しましょう)。
第二引数にて登録する関数の引数は、必ず「string、string、float、form」の引数を持っておく必要があります。
ここで実際に登録しているOnSetNameはこの4つの引数で動いていますね。

そして、「イベントの発生」をどう行うかというと、SendModEventという関数を使ってイベント実行を行います。
SendModEventは以下のように、イベント名とコールバック関数(登録した関数)に投げる引数を指定します。

SendModEvent("TEST_setName", strArg, numArg)

さてこのSendModEventですが、これをまさにFlash側のMainPanel.asのonSubmitPressで使っている構図となっています。
Flash側にて承認ボタンが押された場合、onSubmitPress内のSendModEventが実行され、「TEST_setName」イベントを発生させつつコールバック関数に名前欄の記述内容を送信します。
そうしたらPapyrus側にてOnSetNameイベントが実行され、プレイヤーの名前に受け取った引数がセットされる、という形です。

ちなみにSendModEventで送信できる引数は文字列と数字だけです(配列も不可)。これはそれぞれコールバック用関数(今回はOnSetName)の第二引数と第三引数に格納されます。
文字列だけ送信したい場合やfloatだけ送信したい場合など、以下のような記述方法で対応できます。

 SendModEvent("eventName", strArg) ;文字列のみ送信。numArgは省略可能
 SendModEvent("eventName", NULL, numArg) ;数字のみ送信。文字列の送信はないのでNULLを指定

#endregion

**動作確認
作成したModを導入し、swfファイルも配置して動作を確認します。
UI表示時にプレイヤーの名前が出ており、そこを編集してSubmitボタンを押したらプレイヤー名が変更されていれば成功です。

復元してよろしいですか?

目安箱バナー