新機能・WWAscriptの紹介と今後について

この記事は[WWA Advent Calendar](https://adventar.org/calendars/9442) の8日目の記事になります。

https://adventar.org/calendars/9442

なお、この記事は [まつゆき](https://matsuyuki.dev/) 氏と @aokashi 氏監修の元執筆しております。

## WWA とは？
Qiitaから直接来た人向けに簡単に説明しますと、WWA（World Wide Adventure）はブラウザ上で遊べJavaScriptゲーム並びにゲーム開発環境です。
WWAアドベントカレンダーと合わせて1日目の記事で詳しく説明していますので、気になる方はこちらを参照してください

https://aokashi.hatenablog.jp/entry/start-wwa-advent-calendar-2023

## WWA Scriptとは？
WWA ScriptはWWAのマクロ文を発展させて、JavaScriptライクの言語にてWWAを動作を直接制御できる機構になります。
前身となったマクロ文では `$item=10` のように書くことで簡単な処理を実行できたのですが、WWA Scriptはそれを発展したものとなります。
RPGツクールMVで言うところのスクリプト機能に近いものとなります。

こう言ってしまうとこの記事の意義が無くなってしまうのですが、詳しい使い方や使用できる関数一覧なんかはこちらを参照してください。

https://www.notion.so/wwawing/WWA-Script-c0b8474fda96446f8852d36d2f411b46

注意事項として、こちらのWWA Scriptは現段階（2023/12/08時点）では不安定版のみに組み込まれた機能となります。安定版ではご利用頂けませんのでご注意ください。
また、不安定版の仕様は予告なく変更されることがありますのでご注意ください。

## WWA Scriptの動かし方

WWA Scriptの動かし方は大きく2つありまして、一つはメッセージウインドウに `<script>` タグを入れ、その下にWWA Scriptを記述する方法と、マップデータの外側に外部スクリプトを設置して定義した関数を `<script>` で呼ぶ方法があります。

![物体609番の編集 2023_12_02 20_54_53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/179706/57e7389a-0b3c-cadb-8a8e-c218e95e2270.png)

```javascript
<script>
if(v[33] > 25) {
  MSG("私は真面目な警察官。\nそこの市民よ、何か事件があれば遠慮なく報告してくれたまえ");
}
else {
  MSG("君！なんだその悪臭は！銭湯にでも行って綺麗にしてこい！");
}
```

こちらのサンプルは自作ゲームである「カマック・ライフ」から引用したのですが、通常のJavaScriptと同様に `if` 文で条件によって分岐させて、条件に応じて表示するメッセージを変えたりできます。

WWA Scriptを体験するには、開発ツールを起動してWWAを作る必要はなく、単純に動作を確認するだけでしたらこちらのテストプレイサイトから動作を確認することが出来ます。

https://wwawing.com/unstable/wwamap.html

例えばコンソール欄に `MSG("こんにちは！")` と入れて実行ボタン（Xキー）を押すと、入力した内容が実行されます。

![FireShot Pro Screen Capture #164 - 'World Wide Adventure Wing' - wwawing.com.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/179706/488a35b5-e867-56b6-9db6-564c34051512.png)

スタンダードマップだけでなくてこちらの配布ページより不安定版をDLしてLocal環境でサーバーを立ち上げて頂くと、ご自身で作成したWWAについてもテスト実行することができます。

https://wwawing.com/downloads/

## WWA Scriptで出来ることの例

* 周辺5マスの物体パーツを削除する（エフェクトなし）
```javascript
for(i=-5; i<=5; i++) {
	for(j=-5; j<=5; j++) {
		if(i!=0 || j!=0) {
			o[PX+i][PY+j] = 0;
		}
	}
}
```

こちらの例では周囲のマスを削除することが出来ます。
`o[(X座標)][(Y座標)] = (パーツ番号)` と指定することで該当位置に指定した物体パーツを出現させられるのですが、ここには変数を入れることも出来ます。
従来では指定位置にパーツを出現を使ったり、 `$map` マクロを使う方法はありましたが、Scriptの導入によって繰り返し処理を使って少しのコードで同様の処理を実現できるようになっています。

* 周辺5マスにランダムな物体パーツと背景パーツを配置する
```javascript
for(i=-5; i<=5; i++) {
	for(j=-5; j<=5; j++) {
		if(i!=0 || j!=0) {
			o[PX+i][PY+j] = RAND(100);
			m[PX+i][PY+j] = RAND(100);
		}
	}
}
```

![FireShot Pro Screen Capture #165 - 'World Wide Adventure Wing' - wwawing.com.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/179706/c1406a8c-35b4-a3d5-7524-33f6f1aae45c.png)

`RAND()` のような関数をScript中に組み込むことも出来まして、上の例ではランダムな物体パーツ・背景パーツを出現させて画面全体を混沌とさせていますが、従来あったランダム選択をこちらで置き換えて従来より柔軟な確率設定でゲームを進ませるようなことも出来ます。

![FireShot Pro Screen Capture #166 - '付録. 開発中機能 (WWA Script)' - www.notion.so.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/179706/9c80278a-1f16-e0ef-022e-0db228929c88.png)

最近では `GET_UNIXTIME()` やら日時の取得が出来る関数も実装しましたので、例えば2024年の1月1日を迎えないと進まないイベントだったり、現実の時間帯に合わせてゲーム中でも昼や夜に切り替えたりすることも出来ます。

昔キャラバンサークルであったネットワークゲーム「キャラバン」ではプレイヤーが1日に行動可能な回数に制限があり、制限を超えると次の日までは行動できなくなってしまうのですが、そのようなソシャゲ的な現実世界にて一定時間経過しないと次のアクションが出来ないようなシステムを構築することも出来ます。

## 外部スクリプトを使った例
外部スクリプトを使うともう少し凝ったスクリプトを組むことが出来ます。
`./script/script_file_list.json` に読み込みたい外部スクリプト一覧JSONを定義することで、任意のスクリプトを実行することが出来ます。

カスタムイベント関数と呼ばれる、予め定義された文字列の関数をWWA実行中に特定のイベントが起きた際に自動実行することが出来ます。

例えば `CALL_WWA_START` 関数はゲーム開始時に呼ばれるため、以下のように書いておくとゲーム開始時に任意の処理を実行させられます。

```javascript
/** WWAを開始した際に呼ばれる関数 */
function CALL_WWA_START() {
  MSG("ゲームを開始します");
}
```

例としてカマック・ライフにおいては以下のようにして1秒おきに時間を進ませることでリアルタイムで時が流れて満腹度や眠気が上下するようなシステムを作っています。

```javascript
/** 1フレームごとに呼ばれる関数 */
function CALL_FRAME() {
  call_one_second();
}

/**
 * 1秒おきに処理を実行できる関数
 */
function call_one_second() {
  // 時が停止していなければ動き出す
  if(v[5] == 0) {
    if(TIME > v[0]) {
      // 次回実行タイミング
      v[0] = TIME + 1000;
      process_date();
    }
  }
}
```

他の外部スクリプトの使い方についてはこちらも参照してみてください。

https://www.notion.so/wwawing/0c451248fae1453487612a438e82c582

## WWA Scriptの今後の展開について
### 開発中の Picture Edition (PE) との連携

![FireShot Pro Screen Capture #167 - 'World Wide Adventure Wing' - feature-picture-feature-toml-format--wwawing-dev.netlify.app.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/179706/52dcf689-6ae4-3898-3b9d-8d7c10e9485d.png)

現在 @aokashi 氏によってPicture Editionの開発が進められていますが、こちらが安定版に取り込まれましたらWWA Scriptでもピクチャ表示が出来るようにする予定です。
現状ですとステータス画面などをメッセージウィンドウを使わずに表示する時には、マップチップに予め文字を埋め込む必要があるのですが、PEとWWA Scriptを融合できたら画像に頼らずに文字列や変数を画面上に表示できるようになるかと思います。

他にも現在ではマップチップは一つだけなのですが、画像ファイルが大きくなると管理も大変になりますので、複数の画像ファイルを読み込んで直接画面上に出来るようにもしたいです。

最終的には HTML5の [canvas機能](https://developer.mozilla.org/ja/docs/Web/API/Canvas_API/Tutorial) みたいなことが出来るようになるのが理想となります。

PE機能について気になる方は、こちらの解説ページも見てみてください。

https://www.notion.so/d4e527e1b22040d29cd571f6d32c649c

### 関数に引数・returnを持たせる
外部スクリプトで関数を定義することはできますが、現状では引数としてユーザ変数（ `v[0]` など ）に一旦値をいれてから関数内で呼び出す必要があります。
例えば、カマック・ライフにて食事をした時に呼ばれる `eat_food()` 関数はコメントでどのユーザ変数を使うかで管理しています。

```javascript
/**
 * 食事をしたときの処理
 * v[100] 満腹量
 * v[101] 食事に要する時間(min)
 */
function eat_food() {
  v[1] = v[1] + v[100];
  if(v[1] > 100) {
    v[1] = 100;
    MSG("お腹いっぱいで少し残してしまった")
  }
  v[11] = v[11] + v[101];
}
```

この方法では関数の中で別の関数を呼び出す時に、一時変数として使っている値が分かりにくくなってしまうことはあり、実際カマック・ライフ実装中にも思った通りの動作をしない不具合を調査した時に、関数Aで別の関数Bを呼び、関数A・Bで同じユーザー変数を使っていたために意図しない挙動になっていたことがありました。

将来的には以下のように関数に引数と戻り値を渡せるようにして、以下のように書けるようにしていくのを目標にしています。

```javascript
/**
 * 食事をしたときの処理
 * @param value 満腹量
 * @param min 食事に要する時間(min)
 * @return お残しが発生したか？
 */
function eat_food(value, min) {
  v["hungry"] += value;
  v["tmp_lost"] = false;
  if(v["hungry"] > 100) {
    v["hungry"] = 100;
    v["tmp_lost"] = true;
  }
  v["current_min"] += min;
  return v["tmp_lost"];
}

function main() {
    // カレーを食べる
    v["lost"] = eat_food(30, 20);
    if(v["lost"]) {
        MSG("お腹いっぱいで少し残してしまった")
    }
}
```

## WWA Scriptを支える技術（少しだけ）
ここからは WWA Script自体の開発に関する話となります。
WWA Scriptでは外部ファイルや `<script>` 以下で書かれたスクリプト文を直接JavaScriptのコードとして実行するのではなく、Acornと呼ばれるオープンソースのライブラリを用いて構文解析と抽象構文木(AST)への変換を経て、その結果をもとにWWA Wing本体の機能を呼び出します。

https://www.npmjs.com/package/acorn

例えば `MSG("こんにちは！")` のような `MSG` 関数の場合には、実際には以下のような処理が呼ばれており、 `this.evalWwaNode()` にて引数を解決した値を取得し、WWA本体の内部関数である `reserveMessageDisplayWhenShouldOpen()` を使ってメッセージウィンドウを表示しています。 

```javascript
  evalMessage(node: Wwa.Msg) {
    const value = this.evalWwaNode(node.value);
    const showString = isNaN(value)? value: value.toString();
    this.generator.wwa.reserveMessageDisplayWhenShouldOpen(showString);
    return 0;
  }
```

WWA Script は JavaScript (ECMAScript) の構文をベースとしていますが、その実行結果は JavaScript の結果と一致するものではないことに注意してください。例えば、WWA Scriptでの数値の割り算の結果は 2023年12月現在では整数になるよう調整されます。

もう少し詳しく知りたい人は、実際に構文解析の結果を実行している `wwa_expression2/eval.ts` ファイルを見てみてください。

https://github.com/WWAWing/WWAWing/blob/v3.13.0-unstable.based-on.3.12.11.p.2/packages/engine/src/wwa_expression2/eval.ts

## おわりに
今回は簡単ではありますがWWA Scriptの使い方や今後について紹介してみました。
明日はかわせみ様の「WWA素材　クリスマス」となります！

それでは明日のWWAアドベントカレンダーをお楽しみに～