はじめに
-
Unreal.jsとは
- UnrealEngine上でJavaScriptを動かすことのできるプラグイン
- Unreal.js 入門
-
TypeScriptとは
- JavaScriptの新規格先行取り込み+静的型付けをしたAltJS
- http://qiita.com/tags/TypeScript
- 👍 Unreal.jsをTypeScriptで書くメリット
この記事の想定対象者
- UE4は知っているが、JavaScriptはあまり詳しくない人
- Unreal.jsを書き始めたが、やっぱりUE4の世界は型がほしいという人
- Unreal.jsの吐き出す
ue.d.ts
を見てTypeScriptが気になった人
比較
- UnityのUnityScriptと同じように型があるが、最適化には用いられない
- C#と言語設計者が同じなので似ている
- Microsoftが一推し中の言語で環境が整備されている
- ES2015のスーパーセットなので謎仕様・歴史的経緯はそのまま
準備
既にUnreal.js導入済みの場合はNode.js環境の準備までスキップ。
UEのプロジェクトの準備
- Unreal.jsプラグインのインストール
- Unreal.jsの有効化
- 既存のプロジェクトでも新規でもどちらでもOK
詳細は Unreal.js 入門#導入 を参照のこと。
テキストエディタの準備
- 推奨: VSCodeのインストール
TypeScriptの補完に対応したテキストエディタであればVSCode以外でもOK。以降はVSCode前提とする。
Node.js環境の準備
Node.jsのインストール
環境に合わせてNode.jsを導入しておく。
Unreal.jsはNode.js自体が使われるわけではないが、以下の目的で環境整備に必要になる。
- TypeScriptコンパイラの導入と実行
- 型定義ファイルの導入
- 外部ライブラリの導入
Node.jsをインストールするとパッケージマネージャのnpmもインストールされる。
package.jsonの作成
- プロジェクトの
Content/Scripts/
以下に移動してコンソールから以下のコマンドを打つnpm init
- VSCodeの場合、統合コンソールから打つこともできる
- いろいろと質問されるので適当に答えておく
- 後から書き換えればよいので適当でOK
TypeScriptの設定
tscのインストール
TypeScriptのコンパイラであるtsc
をインストールする。
コンソールに次のコマンドを入れる。
npm i typescript -g -D
- パッケージ(プロジェクト)毎にインストールする場合(TSのバージョンを分ける時など)
npm i typescript -D
tsconfig.jsonの設定
TypeScriptの設定ファイルを作成する。コンパイラ以外にも、VSCodeがこのファイルを自動で読み込んでエラーなどを表示するので必須。
- まず
Content/Scripts/
でコンソールに以下のコマンドを入れるtsc --init
- 質問に答えてゆくと
tsconfig.json
ファイルが生成される -
tsconfig.json
ファイルを開き以下のように設定する
{
"compilerOptions": {
//デフォルトの設定(略)
"target": "es2016",
"lib": [
"es2016"
],
"skipLibCheck": true,
"allowJs": true,
"moduleResolution": "node",
//etc...
},
"exclude": [
"aliases.js"
]
}
必須系型定義ファイルの導入
上記までの設定では console.log()
などのEcmaScript1のコアAPI以外のAPI呼び出しがコンパイルエラーになる。
そこでNode.js用の型定義ファイルを代用で導入する。
npm i @types/node -D
Unreal.jsはNode.jsの互換APIを持つのでこれでコンパイルエラーにならず補完が効くようになる。ただし、全くイコールではないので注意。
(オプション)エディタの補完時にAPIの説明コメントを追加する
お好みで以下の設定をしておく。
Unreal.jsの型定義ファイルを再生成してAPIを調べやすくする
コーディング
型定義ファイルのインストール
Content/Scripts/typings
以下にはUnreal.jsが生成したUnrealEngineの型定義ファイルが既に存在するが、Unreal.js同梱の内部ライブラリや他のJSの外部ライブラリの型定義ファイルはない。そこで外部から型定義ファイルを取得してくることになる(なくても書けないことはない)。
npmで型定義ファイルを導入する
TypeScriptの型定義ファイルは(存在すれば)npmで導入することができる。
外部ライブラリの例として、Unreal.jsの内部に同梱されているlodashの型定義ファイルをインストールしてみる。
npm i @types/lodash -D
これでlodashを使うときに補完や型チェックが効くようになった。
使いたいライブラリの型定義ファイルがあるかは 以下のサイトで検索可能。
https://microsoft.github.io/TypeSearch/
内部ライブラリ用の型定義ファイルを導入
一部だけだが存在するのでDLするなりして導入。
Unreal.jsの型定義出力で出力されない一部の定義も追加してある。
型定義ファイルがない場合
tsconfig.jsonにallowJs
オプションを追加して有効にする。こうすることで素のJSファイルもざっくりとした補完が効くようになるが型情報は多くの場合補完されない。
また、allowJs
を設定するだけではjsファイルがtscのコンパイル対象になることがあるため(同階層にファイルがあるなど)、exclude
オプションも設定しておく。
Unreal.jsむけTypeScriptコードの書き方
require
-> import
Unreal.jsで外部ライブラリなどをCommonJS形式で読み込む箇所はTypeSciptではimport
文で置き換えることができる。
//CommonJS
const UMG = require("UMG")
↓
//import
import * as UMG from "UMG"
import
文で記述し、型定義ファイルを導入すると型チェックと補完が効くようになる。
CommonJSはimport
以前の歴史的な古い"工夫"なのでUnreal.jsむけコードでもimport
文で置き換えられる箇所は積極的に置き換えていきたい。
なお、ver. 2.4以降では ES.next のDynamic import式にも対応しているため、require()
が必要な場面が少なくなっている。
【追記】※最新版では問題あり
最新のTypeScriptではimport
文をCommonJS式でトランスパイルすると、次の一文が出力される。
```js:tscがimport
文をCommonJS式でトランスパイルすると出力する一文
Object.defineProperty(exports,"__esModule", {value: true});
Unreal.jsの世界には`exports`オブジェクトが存在しないのでこの一文がエラーになる。古いバージョンのtsc(2.1等)を使うか、`require()`のままでいくか(型情報が失われる)、出力後のJSファイルからGulp等で自動でその一文を削除するか、工夫が必要。
### uclass.js
```js:jsでの書き方
const UClass = require("uclass")
class ShowProps{
ctor(){
}
properties(){
this.myInt /* EditAnywhere+Int */;
this.myIntArray /* EditAnywhere+Int[] */
this.blueprints /* Category:Select Blueprints+EditAnywhere+Blueprint[] */;
}
}
let UShowProps = UClass()(global, ShowProps)
let ushowProp = new UShowProps();
上記のushowProp
はue.d.ts
のClass
クラスの各種プロパティとShowProps.properties()
メソッド内で定義されたプロパティを持つ。さすがにそこまではTypeScriptのコンパイラは型推論できないため、通常(内部的にtscを利用する)エディタの補完は期待できない。
TypeScriptで記述する場合はuclass.d.ts
の型定義と ジェネリクス を使うことで、上記プロパティの型チェックや補完が効くようになる3。
import UClass from "uclass"
class ShowProps{
myInt:number;
myIntArray:number[];
blueprints:Blueprint[]
properties(){
this.myInt /* EditAnywhere+Int */;
this.myIntArray /* EditAnywhere+Int[] */
this.blueprints /* Category:Select Blueprints+EditAnywhere+Blueprint[] */;
}
}
let UShowProps = UClass<Class, ShowProps>()(global, ShowProps)
let ushowProp = new UShowProps();
globalに生やしたオブジェクト
//menu group settings
if(!global.editorGroup){
global.editorGroup = JavascriptWorkspaceItem.AddGroup(JavascriptWorkspaceItem.GetGroup("Root"), "BP Tools");
}
Unreal.jsのサンプルコードではglobalに設定等を記録しているコードがよくあるが、TSではそのままでは生やしたオブジェクトの定義がないのでエラーになる。以下のように事前に定義しておけばよい。
//extend global
declare var global: {
editorGroup:JavascriptWorkspaceItem;
//...
}
UObject.GetOuter()
,UObject.GetOutermost()
UE4のAPIではouterオブジェクトを引数に指定するものが多く、UObject.GetOuter()
,UObject.GetOutermost()
あたりはUnreal.jsでも使用頻度が高い。ただし、標準のue.d.ts
で定義されているように、戻り値の型はUObject
になってしまう。
そこで、ジェネリクスを使って戻り値の型を明確化する。
まずはUObject
の定義を拡張する。TypeScriptでは定義済みのクラスも、同名のインターフェイスで定義の拡張ができるのでこれを使う。
declare interface UObject{
}
元のUObject.GetOuter()
,UObject.GetOutermost()
はそのままに、関数オーバーロードの形で、ジェネリクス版を定義する。
declare interface UObject {
/**
* Get a outer object with type parameter
*/
GetOuter<T extends UObject = UObject>():T;
/**
* Get a outer most object with type parameter
*/
GetOutermost<T extends UObject = UObject>():T;
}
戻り値の型が明らかな場合はこちらを使用することで、型情報を失うことなくouter
オブジェクトを呼べる。
function renameAllFunctions(g:JavascriptGraphEdGraph){
let bp = g.GetOuter<Blueprint>() //`bp`の型が`Blueprint`として保持される
}
コンパイル
TypeScriptは修正するたびにコンパイルコマンドを打つようなことはせず、tsc -w
でwatch buildさせる(あるいは, gulpなどでも良い)。TSのソースコードが変更されるたびに自動でJSにコンパイルされる。
cd [tsconfig.jsonの置いてあるディレクトリ]
tsc -w
また、Unreal.jsはUE4の Hot reloadに対応しているので、bootstrap.jsと組み合わせて通常のJSと同様の環境を作ることもできる。 TSファイルを修正→JSファイルに自動ビルド→Hot reloadで自動読み直し 、というパイプラインができるのでおすすめ。
bootstrap.ts
TypeScript版のbootstrap.jsは以下。こちらを使用することできちんと補完できるようになる。
export default function (filename:string) {
Context.RunFile('aliases.js')
Context.RunFile('polyfill/unrealengine.js')
Context.RunFile('polyfill/timers.js')
require('devrequire')(filename)
}
import bootstrap from "./bootstrap"
// bootstrap to initiate live-reloading dev env.
try {
module.exports = () => {
let cleanup:Function|null = null
// wait for map to be loaded.
process.nextTick(() => cleanup = main()); //この場合、main()が再読み込みされる
// live-reloadable function should return its cleanup function
return () => (<Function>cleanup)()
}
}
catch (e) {
// 追加するファイル名を記入
bootstrap('【ファイル名】')
}
サンプルコード
テンプレートプロジェクト
-
UnrealTSTemplate (github)
- ※サンプルコードで使用している型定義ファイルの一部は含まれないのでコピーして使うとよい
ソース管理
-
*ue.d.ts
とaliases.js
は自動生成なのでバージョン管理下に置かない- TypeScriptだけでなく共通
-
*.ts
ファイルもバージョン管理下に置く - 各自の環境でjsファイルをビルドする前提なら、jsファイルはバージョン管理から除外できる
-
*.ts
ファイルは/Content/Script/
以下にある必要は必ずしもない- tsconfig.jsonのコマンドラインオプションで工夫することで分けることもできる
まとめ
- Unreal.jsをTypeScriptで書くと、型チェックとコード補完の恩恵にあずかれるよ!
- 出やすいエラーはいくつかの工夫で回避できるよ!
- サンプルプロジェクトやテンプレを参考にしてね!