[UE4]Unreal.jsでUE4のエディタ拡張を作る

この記事は[裏 Unreal Engine 4 (UE4) Advent Calendar 2016](http://qiita.com/advent-calendar/2016/ue4_inside)の5日目の記事です。

# はじめに

UnrealEngine4(UE4)はUnity3D(Unity)に比べるとエディタ拡張が作りにくい。Unityの場合、通常のゲームのコードを書くときと同じC# or UnityScriptで特別な環境がなくても書ける。一方、UE4の場合はVisualStudioかXcodeでUnreal C++で書かないといけない。

UE4でエディタ拡張をやりたい場合、「エディタに新機能を付ける」というよりも「 **効率の良い処理を便利なUIでやりたい** 」という需要が多いのではないか [^1] 。そしてその需要があるのはプログラマはもちろん、アーティストやゲームデザイナなどのノンプログラマも同じである。

ノンプログラマにVisualStudioやXcodeの環境はふつうはまずない。C++というのも敷居が高い。BlutilityはBPで書けるって？いやー、あれUI自由に作れないし、そもそもしばらくメンテされてないよね～。

そんなあなたに福音となるかもしれないのが、**[Unreal.js](https://github.com/ncsoft/Unreal.js)でエディタ拡張を書く** というアプローチだ。

参考：[Unreal.js 入門](http://qiita.com/ConquestArrow/items/bb81ad5e63cf6bc4e549)

# エディタ拡張テンプレ

```js:エディタ拡張のテンプレcode
module.exports = function main(){
    const UMG = require("UMG");
    const I = require('instantiator');
    const EMaker = require("editor-maker");
    
    //menu group settings
    if(!global.editorGroup){
        global.editorGroup = JavascriptWorkspaceItem.AddGroup(JavascriptWorkspaceItem.GetGroup("Root"), "Samples");
    }

    //Editor window with tab simple template
    EMaker.tabSpawner(
        {
            DisplayName:"Simple Tab Win",
            TabId: "SimpleTabWin@",
            Role: EJavascriptTabRole.MajorTab,
            Group: global.editorGroup
        },
        () => I(UMG.text({},"test"))
    )

    //deconstructor
    return () => {
        
    };
}
```

メニュー > Window > Samples > Simple Tab Win に追加される。

![Template_Tabspawner_SampleTabWin.png](https://qiita-image-store.s3.amazonaws.com/0/54764/67e5fac3-7e3c-6f70-009d-5808f47c2d83.png)

生成されるのはタブが一つあるウィンドウ。

![SampleTabWin.png](https://qiita-image-store.s3.amazonaws.com/0/54764/55c516f5-f79b-61b1-a340-1f3f451c1172.png)

 * [sample code](https://github.com/ConquestArrow/UnrealJSEditorExtSamples/blob/master/Content/Scripts/extension-SimpleTabWin.js)

これをひな型に作るのが容易。

※エディタ拡張制作中は`bootstrap.js`を持ってきて、live-reload対応の処理を入れておくとなお便利。

```js:コードの最後に追加してmain関数をここでexportするように書き換えておく
// bootstrap to initiate live-reloading dev env.
try {
    module.exports = () => {
        let cleanup = null

        // wait for map to be loaded.
        process.nextTick(() => cleanup = main());

        // live-reloadable function should return its cleanup function
        return () => cleanup()
    }
}
catch (e) {
    require('bootstrap')('extension-Button')
}
```

# 基本的な作り方

Unreal.jsにはエディタ拡張作成に便利なライブラリが同梱されているのでそれを使う[^2]。

 1. ファイル名を `extension-*.js` で作成
 2. GUI部品は同梱ライブラリの `UMG.js` を使って設定
 3. UMG.jsで作ったものは `instantiator.js` で有効化
 4. ウィンドウやタブ、メニュー登録などは `editor-maker.js`で設定
 5. `JavascriptEditorLibrary`や`JavascriptEditorEngineLibrary`などのAPIでエディタの機能にアクセス
 6. 処理自体はJSで書く

ドキュメントは不足しているので、[Unreal.jsの型定義ファイルを再生成してAPIを調べやすくする](http://qiita.com/ConquestArrow/items/fa7560d8fa7aa03553e5)で紹介した方法で`ue.d.ts`を更新し、コード補完が効くようにしておくのがおすすめ。


# UI編

UE4のエディタUIはSlateでできているが、UMGはSlateを継承しているので、UMG.jsでSlateもUMGも扱える。

公式Wiki: [UMG widgets](https://github.com/ncsoft/Unreal.js/wiki/UMG-widgets)

## レイアウト

### `UMG.div()`

`VerticalBox`を作成する。HTMLのdiv要素のように[^3]、子要素を縦方向に持つことができる。

```js:使い方
UMG.div({【オプションのObject】}, ...children)
```

オプションに指定できるのは`VerticalBox`およびそれの先祖クラスのプロパティが指定できる。

### `UMG.span()`

`HorizontalBox`を作成する。HTMLのspan要素のように[^3]、子要素を横方向に持つことができる。

```js:使い方
UMG.span({【オプションのObject】}, ...children)
```
オプションに指定できるのは`HorizontalBox`およびそれの先祖クラスのプロパティが指定できる。

### スロットのサイズルール

UMG/Slateには「[スロット](https://docs.unrealengine.com/latest/JPN/Engine/UMG/UserGuide/Slots/index.html)」があり、Unreal.jsでUIを設定する際にもUMG.jsのオプションやプロパティ経由で設定できる。

レイアウトにかかわるスロットのサイズルールは`ESlateSizeRule`というEnumがあるのでそれで指定可能。

```js
UMG.div(
  {
    //オプションでの設定
    Slot:{Size:{Value:10, SizeRule:ESlateSizeRule.Fill}},
    //入れ子が面倒な時は以下の方法でも設定可能
    "slot.size.size-rule":ESlateSizeRule.Fill,
    //...
  },
  //...
)
```

### タブ・パネルのレイアウト

ある程度複雑な複数のタブやパネルをもつエディタ拡張を作る場合には、上記までの簡易な方法ではなく`JavascriptEditorTabManager`に対してJSONフォーマットをパラメータに与える形になる。詳細は[後述](#タブレイアウト)。

## テキスト

[sample code](https://github.com/ConquestArrow/UnrealJSEditorExtSamples/blob/master/Content/Scripts/extension-Text.js)

### `UMG.text({}, "text")`

`TextBlock`を生成する、UMG.jsのメソッド。ショートハンドメソッドになっており、`UMG(TextBlock,{Text:"text"})`のシュガーシンタックスである。

![UMG_text.png](https://qiita-image-store.s3.amazonaws.com/0/54764/033a8f40-4caa-1a60-0c71-a5f8d823c696.png)

```js
UMG.div(
    {},
    UMG.text({},"text"),
    UMG(TextBlock,{Text:"text"})
)
```

なお、エディタ拡張用にはデフォルトのフォントが大きすぎるので小さいフォントを設定しておくとよい。

```js
//エディタ組み込みのフォントにアクセスするために取得
const GEngine = Root.GetEngine();

//フォント：SmallFont、サイズ10が大体エディタ拡張の通常のフォントサイズ
UMG.text({Font:{FontObject:GEngine.SmallFont,Size:10}},"text");
```

![UMG_fonts.png](https://qiita-image-store.s3.amazonaws.com/0/54764/9855d80b-543d-7eab-f04f-ca71ef699577.png)

 * [sample code](https://github.com/ConquestArrow/UnrealJSEditorExtSamples/blob/master/Content/Scripts/extension-Text.js)

なお、文字入力系は`EditableTextBox`などを使用する。


## 画像

![Image_sample.png](https://qiita-image-store.s3.amazonaws.com/0/54764/b6493393-fa75-d8df-5c84-6003b9f12042.png)


```js
UMG.span(
    {},
    UMG(UImage,
    {
        Brush:{
            ImageSize:{X:248,Y:138},
            ResourceObject:UObject.Load("/Game/Image")
        }
    })
)
```

`VerticalBox`直下に置くと、エディタUI上のデフォルトだと引き延ばされるので注意。

なお、`UMG.img()`というショートハンドメソッドもある。

```js
UMG.img({Brush:{ResourceObject:UObject.Load("/Game/Image")}})
```

 * [sample code](https://github.com/ConquestArrow/UnrealJSEditorExtSamples/blob/master/Content/Scripts/extension-Image.js)

## ボタン

いわゆる普通のボタン。

![Button_default.png](https://qiita-image-store.s3.amazonaws.com/0/54764/cdfd98ea-43d0-17d3-1b48-0c6e42b64d8b.png)

```js
UMG(Button,{},"default button")
```

何も設定しないとフォントがでかく、パネルいっぱいに広がるので実際には調整することになるだろう。

![Button_decotext.png](https://qiita-image-store.s3.amazonaws.com/0/54764/946e2c88-f50f-64c8-6d8c-9d82b0df6fd8.png)

```js
const GEngine = Root.GetEngine();
const btnFColor = new LinearColor()
    btnFColor.R = btnFColor.G = btnFColor.B = 0;
    btnFColor.A = 100;

//simple button with font decoration
UMG(Button,{ColorAndOpacity:btnFColor},UMG.text({Font:{FontObject:GEngine.SmallFont,Size:10}},"decorated text button"))
```

 * [sample code](https://github.com/ConquestArrow/UnrealJSEditorExtSamples/blob/master/Content/Scripts/extension-Button.js)

## リスト

`JavascriptListView`

![ListView_Sample.png](https://qiita-image-store.s3.amazonaws.com/0/54764/b72010ba-eda5-f41d-e39d-e5e02650de53.png)

`OnGenerateRowEvent`プロパティの中でリスト項目を設定する。リスト項目は`UObject`である必要があるのでuclass.jsで`Struct`を作って渡す。

```js:リストの表示データ
    class A/* Struct */{
        ctor(){
            this.desc = "A desc"
            this.name = "A"
        }
        properties(){
            this.desc /* String */
            this.name /* String */
        }
    }
    class B/* Struct */{
        ctor(){
            this.desc = "B desc"
            this.name = "B"
        }
        properties(){
            this.desc /* String */
            this.name /* String */
        }
    }

    let data = [
        new (UClass()(global, A)),
        new (UClass()(global, B)),
    ]
```

公式のサンプルにはJSONからUObjectのデータをまとめて作成する [json2u.js](https://github.com/ncsoft/Unreal.js/blob/3f3cd4bfa65769089855b403289c9893aa27b4e9/Examples/Content/Scripts/json2u.js) というライブラリもあるので数がある場合はこれを使うのもよいだろう。

```js:表示設定箇所
OnGenerateRowEvent: (item, column) => {
    let s = ""
    switch(column){
        case "Name":
            s = item ? item.name : column;
            break;
        case "Desc":
            s = item ? item.desc : column;
            break;
        default:
            s = "default"
    }

    let design = 
        UMG(JavascriptTextBlock,
            {
                Font : {
                    FontObject : GEngine.SmallFont,
                    Size : 10
                },
                Text : s
            }
        )
    return I(design)
},
```

なお、3項演算子で設定している個所があるのは、最初はリストのヘッダー部分で`item`が`undefined`で返ってくるからである。

 * [sample code](https://github.com/ConquestArrow/UnrealJSEditorExtSamples/blob/master/Content/Scripts/extension-List.js)

## PropertyEditor

Detailsパネルの中身。

![PropertyEditorSample.png](https://qiita-image-store.s3.amazonaws.com/0/54764/dd26c7b0-a71c-5650-099b-62727c93cddb.png)


表示内容はプロパティを見たいものを直接渡すか、見せたいUPROPERTYを定義したUClassを作って渡す必要がある。
→参考： [[UE4]Unreal.jsでJSのコードに型とフラグ情報を付与する](http://qiita.com/ConquestArrow/items/6c1eac9226aeb5ce2794)

```js:見せたいプロパティを定義したクラス
//PropertyEditor showing props
class ShowProps{
    ctor(){
        //default value set 

        this.myInt = 123;
        this.myFloat = 987.6;
    }
    properties(){
        this.myBoolean /* EditAnywhere+bool */;
        this.myInt /* EditAnywhere+Int */;
        this.myFloat /* EditAnywhere+float */;
        this.myIntArray /* EditAnywhere+Int[] */;
        this.myString /* EditAnywhere+String */;
        this.myVector2d /* EditAnywhere+Vector2D */;
        this.myVector /* EditAnywhere+Vector */;
        this.myActor /* EditAnywhere+Actor */;
        this.myColor /* EditAnywhere+Color */;
        this.someProp /* EditAnywhere+Category:MyCategory+DisplayName:My Prop Name+int */;
        this.advancedProp /* EditAnywhere+Category:MyCategory+AdvancedDisplay+Color */
    }
}

let UShowProps = UClass()(global, ShowProps)
let ushowProp = new UShowProps();
```

```js:PropertyEditorのコア処理
UMG(PropertyEditor,
{
    OnChange:(propertyName) => {
        //...
    },
    $link:(elem)=>{
        elem.SetObject(ushowProp)
        elem.updateData = _ => {
            elem.SetObject(ushowProp)
        }
    },
    $unlink:(elem) =>{
        //...
    }
})
```

 * [sample code](https://github.com/ConquestArrow/UnrealJSEditorExtSamples/blob/master/Content/Scripts/extension-PropertyEditor.js)

## タブ

![Tabs_sample.png](https://qiita-image-store.s3.amazonaws.com/0/54764/e6dde054-6387-8038-4fbc-93b064239246.png)

タブ自体は`JavascriptEditorTab`のインスタンス。editor-maker.jsに簡単に生成できる`tab()`メソッドがあるのでそれを使うと便利。

```js:最低限のタブのサンプル
const EMaker = require("editor-maker")
let tab = EMaker.tab(
            {
                TabId:"Tab",
                Role:EJavascriptTabRole.NamadTab,
                DisplayName:"Tab"
            },
            ()=>{
                return I(UMG.text({},"text"))
            }
);
```

 * [sample code](https://github.com/ConquestArrow/UnrealJSEditorExtSamples/blob/master/Content/Scripts/extension-Tabs.js)

### タブの種類

`EJavascriptTabRole`には4種類あるが、見た目上は2種類しかない。

- `MajorTab` : 基本的なタブで、パネルとしてドッキングはできない。
- `NomadTab` : 他のNomadTabの四隅にドッキングしたり、タブを隠してパネル化できる。
- `DocumentTab`, `PanelTab` : 見た目・機能的には`NomadTab`と同じ？意味的に使い分けるのかもしれない。

### 複数のタブ

複数タブを表示させるにはそのままではなく`JavascriptEditorTabManager`を使い、レイアウトやタブの設定等を行う。

```js
    let tabManager = new JavascriptEditorTabManager(JavascriptLibrary.CreatePackage(null,'/Script/Javascript'))
    tabManager.Tabs = [tab, /* ... */]
    tabManager.Layout = JSON.stringify(tabLayout)

    //...

    $link:elem => {
         elem.AddChild(tabManager).Size.SizeRule  = "Fill"
   }
```

### タブレイアウト

`tabManager.Layout`にはJSONを設定する。このJSONはエディタのレイアウトを保存したときにiniファイルの中に保存されるものと同じフォーマットで、`/Saved/Config/Windows/Editor.ini`などを見るとわかる。

パラメータの詳細は以下の公式ドキュメントを見ると参考になる。

 - [FLayout](https://docs.unrealengine.com/latest/INT/API/Runtime/Slate/Framework/Docking/FTabManager/FLayout/index.html)
 - [FArea](https://docs.unrealengine.com/latest/INT/API/Runtime/Slate/Framework/Docking/FTabManager/FArea/index.html)
 - [FLayoutNode](https://docs.unrealengine.com/latest/INT/API/Runtime/Slate/Framework/Docking/FTabManager/FLayoutNode/index.html)
 - [FStack](https://docs.unrealengine.com/latest/INT/API/Runtime/Slate/Framework/Docking/FTabManager/FStack/index.html)
 - [FSplitter](https://docs.unrealengine.com/latest/INT/API/Runtime/Slate/Framework/Docking/FTabManager/FSplitter/index.html)
 - [ETabState::Type](https://docs.unrealengine.com/latest/INT/API/Runtime/Slate/Framework/Docking/ETabState__Type/index.html)

## メニュー

デフォルトのエディタの左上のメニューバーや画面上部のツールバーを拡張することができる。

[公式サンプル](https://github.com/ncsoft/Unreal.js/blob/master/Examples/Content/Scripts/extension-extender.js)

なお、[現時点ではUnreal.jsはデフォルトのウィンドウ（`Level Editor`）のメニューの拡張にしか対応していない](https://github.com/ncsoft/Unreal.js-core/blob/master/Source/JavascriptEditor/JavascriptEditorLibrary.cpp#L589)。

## ユーザーウィジェットをUIとして使う

WidgetBPをDesinerでWysiwygに作ったものをエディタ拡張のUIとして使うこともできる。

![UserWidgetDesiner.png](https://qiita-image-store.s3.amazonaws.com/0/54764/bf0983b5-1d1e-4ca1-bc97-5850ce6b2d8a.png)
![UserWidget_sample.png](https://qiita-image-store.s3.amazonaws.com/0/54764/f71ff934-514c-4f0f-d16f-a615b671667b.png)

JSで設定するのすら面倒！な場合はこの方法が使えるかも？
ただし、イベント周りの処理は工夫が必要になるだろう（未検証）。

 * [sample code](https://github.com/ConquestArrow/UnrealJSEditorExtSamples/blob/master/Content/Scripts/extension-MyWidgetUMG.js)

## その他

JS側にAPIが出されているUI(`ue.d.ts`で`Visual`を継承する子クラス)であれば基本使用可能。

```js:呼び方
UMG(【UI】,【UIのオプション】);
```

[GithubのC++ソース](https://github.com/ncsoft/Unreal.js-core/tree/master/Source/JavascriptUMG)を眺めるだけでも全体像はつかめる。`JavascriptGraphEditor`など気になるものも用意されているようだ。

ここにないものは追加でプラグインに組み込むことになる。

# 機能編

## UIのトリガーイベント

### `$link`, `$unlink`

オプションの`Object`プロパティに`$link`,`$unlink`を設定する。

```js
UMG(Button,
  {
     $link:elem => {},
     $unlink:elem => {}
  }
)
```
`$link`はUIの構築時、`$unlink`はUIの破棄時に呼ばれる？ようだ。

なお、`$link`,`$unlink`の引数`elem`はイベントの設定されたUI自身となる。

```ts:TypeScriptの型定義で書くと以下のような感じ

UMG<T extends Visual>(
  T,
  {
    $link?:(elem:T)=>void,
    $unlink?:(elem:T)=>void
  },
//...
```



### UI毎のトリガーイベント

UIの部品ごとに設定できるイベントがあるので、

```ts:_part_1_ue.d.ts
declare class Button extends ContentWidget { 
        //略
	OnClicked: UnrealEngineMulticastDelegate<() => void>;
	OnPressed: UnrealEngineMulticastDelegate<() => void>;
	OnReleased: UnrealEngineMulticastDelegate<() => void>;
	OnHovered: UnrealEngineMulticastDelegate<() => void>;
	OnUnhovered: UnrealEngineMulticastDelegate<() => void>;
//以後省略
```

`$link`などと同様にオプションの`Object`内で設定する。

```js
UMG(Button,
  {
     OnClicked: _ => {
        //do something
     }
  }
)
```

## 選択中のアクターインスタンスを取得する

```js
//エディタ（World Outlinerなど）上で選択したActorインスタンスの配列を取得
let usel = JavascriptEditorEngineLibrary.GetSelectedSet(Root.GetEngine(), Actor)
let sel = usel.GetSelectedObjects()

//一つ目のActorインスタンスを取得
let p = sel.Out[0];
```


# ライブラリ編

## UE4 API

### JavascriptEditorLibrary

エディタの機能にアクセスするAPI群が定義されたライブラリクラス。

### JavascriptEditorEngineLibrary

上記に似ているが、こちらは`EditorEngine`に関してのAPI群のライブラリクラス。

### Javascript*クラス

`Javascript*`という名前のクラスは頭の`Javascript`を消すと、Unreal C++の元の該当するAPIにたどり着けることが多い。

## Unreal.js同梱

同梱ライブラリでエディタ拡張に使えそうなものを紹介。

### [fs.js](https://github.com/ncsoft/Unreal.js-core/blob/master/Content/Scripts/fs.js)

node.js互換のファイルIOライブラリ。

コード中でファイルの読み書きに使えるほか、npm経由で入れたjsライブラリで`fs`モジュールを呼んでいる場合に役に立つ。

なお、ファイルIO自体はUE4自体のAPIを使うこともできる。

### [UMG.js](https://github.com/ncsoft/Unreal.js-core/blob/master/Content/Scripts/UMG.js)

```js
UMG(【UI】,【UIのオプション】,【子要素UI】);
```

すでに紹介済みだが、UMG/Slateを便利に扱うためのクラス。いくつかのショートハンドメソッドも用意されている。

ショートハンドメソッドには実は`UMG.list()`や`UMG.TabManager()`など上記で元のクラスAPIだけ紹介したものもあり、代表的なものは一通り用意されているようだ（今回は未検証）。時間があれば今後検証していきたい。


### instantiator.js

UMG.jsで作成したUIを各APIに渡せる形にする。

### [editor-maker.js](https://github.com/ncsoft/Unreal.js-core/blob/master/Content/Scripts/editor-maker.js)

エディタのUI（タブ・メニュー・コマンドなど）を便利に作れるライブラリ。

# まとめ

* [基本的な作り方](#基本的な作り方) をもとにJSでエディタ拡張を作れる
* UE4のAPI群とUnreal.js同梱のライブラリを活用できる
* [今回のサンプルをgithubにあげてある](https://github.com/ConquestArrow/UnrealJSEditorExtSamples)ので参考に

# Next Day

明日（6日目）は @alwei さんによる　「[UE4 VR空間で手を飛ばす方法について](http://unrealengine.hatenablog.com/entry/2016/12/06/000000)」です。

[^1]: UE4の場合、機能追加はエンジンのソースに手を入れればよいので。
[^2]: もちろん、APIを一つ一つたたくことで作成することもできるが
[^3]: 正確にはもちろんHTMLのdiv/spanは縦横の並びは固定ではない。