WWA Script応用:コピペ可、サンプル配布あり!色々やってみよう

この記事は WWA Advent Calendar の19日目です。

はじめに

WWAクリエイターのアルクスです。

今回は前回の記事の続きです。

取り扱う内容は前回の記事より難しいですが、使いこなせるようになるとWWA制作の幅が恐ろしく広がります。

また応用編とはいえ、大前提として初心者向けを意識してはおりますので、分かりにくいという意見をいただいた箇所があれば随時アップデートしていきます。(更新時は後述のDiscordサーバーでもお知らせいたしますので、宜しくお願い致します)


不安定版のダウンロード

WWA Scriptは、安定版だと使用できません。
下記から、不安定版を入手した上でWWA作成を行うようにしてください。

unstable

今回は『ファンタジーアイランド』をベースに改造していきますが、中には不安定版テスト用ページで手軽に確認できるものもあります。

関数(function)を活用する

関数(function)とは

ザックリ言うと、『特定の処理をひとまとまりにしたもの』です。
嘘は付いていませんが、恐らくこんな説明だけではサッパリ。

ではここで、ファンタジーアイランドのモンスターを改造する前提で考えてみましょう。

スライム(動かない個体、動く個体)

今回は、スライムを倒すと『スライム撃破数』をカウントし、生命力も10回復するということにしてみましょう。
ついでにマップには全11体のスライムがいるので、10体倒したら防御力+1と専用のメッセージも出るようにしてみます。

すると、こうなります。

<script>
v[51] += 1; //v[51]:スライム撃破数
HP += 10;
if(v[51] == 10){
  MSG("スライムを10体倒した!");
  DF += 1;
}

感じ方は人それぞれですが、人によってはダルいと思われることでしょう。
なぜならば上記の仕様で確定させるならまだいいですが、

  • コウモリやゾンビも含めてカウントしよう!

    • ついでに20体撃破でもボーナスを発生させよう!

  • 一定数撃破時のメッセージを変更・廃止しよう!

  • 撃破ごとの回復はなくそう!

  • DF+1があまり強くないからDF+5くらいにしちゃおう!

みたいな感じで変更が発生した場合、複数のパーツを対象に直す必要があるからです。もちろん、これを一部しか直せていないと仕様が一貫していないバグにも繋がります。

では、どうするのがいいか?
こうしてみましょう。

共通で同じ処理を記述。

『afterBattle』を各種モンスターに記述しました。
このafterBattleというのは今回の説明で用いる関数(function)の名前です。

afterBattle サンプル(※どこに書く?というのは後述)

function afterBattle(){
    if(ID == 41 || ID == 106){ //スライム
        v[51] += 1; //v[51]:スライム撃破数
        HP += 10;
        if(v[51] == 10){
            MSG("スライムを10体倒した!");
            DF += 1;
        }    
    }
    else if(ID == 42 || ID == 107){ //コウモリ
        //今のとこ何もしないが、撃破数だけ数えておく
        v[52] += 1; //v[52]:コウモリ森撃破数
    }
    else if(ID == 43 || ID == 108){ //ゾンビ
        //今のとこ何もしないが、撃破数だけ数えておく
        v[53] += 1; //v[53]:ゾンビ撃破数
    }
}

ID
そのスクリプトを呼び出したパーツ番号を取得します。
例えば、動くスライム(物体パーツ106)を倒してafterBattleを呼んだ場合はID=106です。

こうしておくことで、変更が発生する場合は『afterBattle』の一箇所を修正するだけで良くなります

関数(function)の名前は英字構成にしておけば基本的に自由に命名できますが、同じ処理をさせたいところは同じ名前の関数(function)を呼ぶように統一させてください。

関数はどこに書く?

単なるスクリプトであれば<script>○○で記述できましたが、
呼び出される関数についてはDATファイル内には書き込めません。

jsファイルを用意して、そこに書いていく形になります。

こればかりはNotionをご確認いただいたほうがよいのですが、
忙しい方はこちらのzipをダウンロードして解凍してみてください。

script_file_list.jsonファイルに紐づいてさえいれば、どのような名前のjsファイルにどのような関数(function)を記述してもよいのですが、サンプルに倣うのであれば

  • カスタムイベント関数(後述)の処理はdefined.jsに記述する。

  • 他の処理はindex.jsに記述する。

    • 記述量が多くなり必要な情報を探しにくいなど、ファイル分割の必要性を感じる場合はお好みで○○.jsを作成して記述を分ける。

といった形で整理するといいでしょう。

for文を活用する

そもそもforってなんなん?

for文は、繰り返し処理を行わせるために出す命令文です。

for(i=1; i<=3; i=i+1) {
    MSG(i + "回目の表示です。");
}

上記のコードをunstableにコピペして実行してみましょう。
三段構えでメッセージが表示されるはずです。

これだけの説明で『はい、分かりました!』とならない人向けの記事です。
一個一個解説していきます。

for(i=1; i<=3; i=i+1) {
}

  • i という変数を使って、ループ(繰り返し)を3回行います。

    • i=1 からスタートします。

    • 条件 i<=3 が満たされている間、ループを繰り返します。

    • 1回繰り返すたびに i=i+1 で i が1増えます。

MSG(i + "回目の表示です。");

  • MSG(○○);を用いてメッセージを表示しております。

    • ポイントは( i + ○○)という風に『変数 i』を付けた部分。
      これにより、ループのたびにメッセージの内容が変わります。

  • 前述のとおり今回のループ文だと3回繰り返されるので、
    下記のようにメッセージが表示されます。

    • 『1回目の表示です。』

    • 『2回目の表示です。』

    • 『3回目の表示です。』

繰り返しの流れ
下記のような感じです。

  • i=1 → 条件 1<=3 を満たす → メッセージ表示 → i が1増える(i=2)。

  • i=2 → 条件 2<=3 を満たす → メッセージ表示 → i が1増える(i=3)。

  • i=3 → 条件 3<=3 を満たす → メッセージ表示 → i が1増える(i=4)。

  • i=4 → 条件 4<=3 が false(満たされない) ので、ループが終了。

ポイント

  • i は 回数をカウントするための変数 です。v[○○]は使えません。

  • i=○○の初期値を変えたり、条件部分(i<=3)を変えたりすることで回数を変えることができます。
    (例:for(i=1; i<=5; i=i+1) → 5回繰り返す[i = 1,2,3,4,5])
    (例:(i=0; i<3; i=i+1) → 3回繰り返す[0,1,2])

  • i=i+1としている部分については、必ずループが終わりに近づくようにしましょう。そうしないと無限ループするおそれがあります。

    • 上記サンプルの場合、iが1ずつ増えていくので最終的に『3以下』を満たさなくなります)

    • 『i = i+1』は『i++』と表記することも可能です。

という感じでfor文についての解説でしたが、細かい部分は実際に使って少しずつ理解する感じで大丈夫かと思われます。

さて、人によってはこう思われたのではないでしょうか。
『繰り返せるから何なんだ?』と。

for文は色々なことに応用できるのですが、有効的に活用するためには発想力が重要です。というわけで、使い方をいくつか紹介してみましょう。

文字列を繋げる

forで特定の文字列を複数回つなげ、(お好みで)forに関係しない固定文言とくっつけてメッセージに表示したりすることもできます。

v["sample"] = "";
for(i=0; i<2; i++){
  v["sample"] += "ホントに";
}
v["sample"] += "ライオンだ";
MSG(v["sample"]);
v["sample"] = "";
for(i=0; i<DF; i++){
  v["sample"] += "ホントに";
}
v["sample"] += "ライオンだ";
MSG(v["sample"]);
記事内のサンプルに差し替えが入った場合はお察しください

アイテム自動買い取り

下記は、『ファンタジーアイランド2』で使用可能な、アイテム全買い取りのスクリプトです。アイテムボックスのどこに何が何個あったとしても、漏らさずに買い取ることができます。

//アイテムボックスから全アイテムを買い取る
function allItemBuy(){
    for(i=1; i<=12; i++){
        if(ITEM[i] == 20){ //鍵
            GD += 6;
        }
        else if(ITEM[i] == 21){ //短剣
            GD += 15;
        }
        else if(ITEM[i] == 22){ //斧
            GD += 9;
        }
        else if(ITEM[i] == 23){ //長剣
            GD += 45;
        }
        else if(ITEM[i] == 25){ //釣り竿
            GD += 18;
        }
        else{
            //他のアイテムは非売品なのであげない!
            continue;
        }
        ITEM[i] = 0;
    }
}

※『ITEM[i] == ○○』の○○部分を各種アイテムパーツの番号に変えれば、
 他のWWAでも使えます。
※サンプルは売値の6割です(短剣買い取りオヤジと同レート)
 GD+=××の部分はお好みの金額を設定してください。

しれっと『continue』というものを使いましたが、こいつについては後ほどご説明いたします。

量産少女:RE』より。
最後のスコア処理も、この技法を用いればすぐに終わったりします。

アイテムボックス内のアイテム置換

マクロの$partsやScriptのPARTS関数でマップ上のパーツを置き換えることができますが、アレのアイテムボックス版みたいなこともforを使えばやれます。

//アイテムボックスの空き枠を鍵で埋め尽くす
function fillKey(){
    for(i=1; i<=12; i++){
        if(ITEM[i] == 0){
            ITEM[i] = 20;
        }
    }
}


自作品『いつか見た虹』より、耽美の仮面。
前述した『自動買い取り+アイテム置換』を併用しています。

マップの構造をガラっと変える

――みんな大好き、地形破壊!

というわけで、プレイヤー自身の座標+周囲8マスの背景パーツを書き換えるスクリプトを書いてみましょう。

一応、『入門編』で触れた範囲のスクリプトでもこういうことはできるのですが、書き方があまり美しくありません。それに、『やっぱり範囲を5×5にしよう!』みたいなことをすると修正が大変です。

ゴリ押しの例

ではforを使ってこんな風にしてみましょうか。

v[0] = 1; //プレイヤーの上下左右何マスをいじるか。3×3にするなら1、5×5なら2、7×7なら3……
for(i=v[0]*-1; i<v[0]+1; i++){
  m[PX+i][PY+i] = 14;
}

今回の場合、i=-1で始まり、i<2になるまでやる。
つまり-1,0,1の3回ずつ処理を実行するということですね。

-1も+1も触れているのでマップ破壊準備、ヨシ!……おや?

失敗例

これの問題点は単純です。最初のゴリ押しだと9回の処理を行っていましたが、今回はm[PX-1][PY-1]、m[PX][PY]、m[PX+1][PY+1]の3処理しか行えておりません。処理を9種類に増やさなければ……

3×3にするだけなら↓のようなやり方でもいいのですが、結局5×5にしよう!とかした場合には追加の対応が必要です。適切なやり方ではありません。

for(i=v[0]*-1; i<v[0]+1; i++){
  m[PX+i][PY-1] = 14;
  m[PX+i][PY] = 14;
  m[PX+i][PY+1] = 14;
}

では、どうするか。
forの中にforを入れてしまうのです。

v[0] = 1; //プレイヤーの上下左右何マスをいじるか。3×3にするなら1、5×5なら2、7×7なら3……
for(i=v[0]*-1; i<v[0]+1; i++){
  for(j=v[0]*-1; j<v[0]+1; j++){
    m[PX+i][PY+j] = 14;
  }
}

今回、変数jを新登場させました。使い方自体はiと同じです。
X座標を変数iに、Y座標を変数jに握らせ縦横それぞれを制御します。

こうすることで、ループ中に9回処理を行います。

  1. i=-1, j=-1

  2. i=-1, j=0

  3. i=-1, j=1

  4. i=0, j=-1

  5. i=0, j=0

  6. i=0, j=1

  7. i=1, j=-1

  8. i=1, j=0

  9. i=1, j=1

最初のv[0] = ○○の数値を変えただけで、範囲もガラッと広げることができます。

注意点
これを用いてX座標やY座標がマイナスになったり、逆にマップサイズよりも大きな値を指定したりするとScriptのエラーが発生します。
プレイヤーがエラーになる境界線を超えたらマップ破壊できないようにif文を入れるなど、工夫を凝らしてみましょう。

forの制御文

for文の中で使える特殊な制御文についての解説です。

continue
これを実行すると、その回は終了して次のループに行きます。
例えば、下記のスクリプトを実行すると『3回目の表示です。』は表示されませんし、v[0]の値も4になります。

v[0] = 0;
for(i=1; i<=5; i=i+1) {
  if(i == 3){
    continue;
  }
  //3回目のループだとcontinueが入り、下2行は処理されない
  v[0] += 1;
  MSG(i + "回目の表示です。");
}
MSG("メッセージは" + v[0] + "回表示されました");

break
これを実行すると、即座にループを終了します。
たとえば、下記のスクリプトは『for(i=0; i<999; i++)』と本来なら999回実行されるところを途中で打ち切っています。

v["sample"] = "いぬ26";
for(i=0; i<999; i++){
  v[0] = i;
  if("いぬ" + i == v["sample"]){
    break;
  }
}
MSG("ワンワン!\n" + v[0] + "回目で見つけたワン!");
break;を消すと998回目で見つけたとなります。

これらの制御文は不要な場面も多いので無理して使おうとすると本末転倒ですが、うまく活用することでfor文をより器用に扱うことができます。

カスタムイベント関数

DATファイルの外側にWWA Scriptを導入する最大の利点とも言えます。

先ほど紹介した通常の関数については、functionの中身を<script>で記述することで(多少の難はあれど)実装が可能です。

しかしながら<script>で実装できる処理はあくまでもパーツに紐づく処理に限られます。つまり……

  • 条件を満たすと自動的に処理を発生させる

  • セーブやロードなど、セーブデータの操作に対して処理を発生させる

  • アイテムの『入手』そのものに対して処理を発生させる

  • キーボードの特定のキーを押下すると処理を発生させる

  • WWA戦闘のダメージ計算方式を変える

など、特定のパーツに関係のない部分で発生させる処理は作れません。
これらを作れるようにするのが『カスタムイベント関数』です。

サンプルとして、いくつかご紹介していきましょう。

CALL_GET_ITEM()

アイテムを入手したタイミングで実行されるカスタムイベント関数です。
今回はファンタジーアイランドで『鍵』『短剣』『斧』を入手した時の初回メッセージを設定しています。

function CALL_GET_ITEM(){
  if(ITEM_ID==20 && !v["isShownedKey"]){ //鍵入手時メッセージ
    MSG("鍵はあちこちの扉を開けられます。\n必要な数は限られるので\n買いすぎに注意しましょう。");
    v["isShownedKey"] = true;
  }
  if(ITEM_ID==21 && !v["isShownedKnife"]){ //短剣入手時メッセージ
    MSG("短剣は攻撃力が10上がりますが、\n他の武器を取ると消えてしまいます。\n\n他の武器を入手する際には、\n事前にどこかの人に短剣を\n買い取ってもらいましょう。");
    v["isShownedKnife"] = true;
  }
  if(ITEM_ID==22 && !v["isShownedAxe"]){ //斧入手時メッセージ
    MSG("斧は小さな木を切り倒すことができます。\nしかし、使うと無くなってしまうので\nセーブしてから使うといいでしょう。");
    v["isShownedAxe"] = true;
  }
}


購入時はもちろん……
マップで拾った場合にも!

TIPS
『ITEM_ID』はカスタムイベント関数を発生させたアイテムのパーツ番号を取得します。CALL_GET_ITEM()やCALL_USE_ITEM()、CALL_GET_ITEM_FULL()のようにアイテムが関連する3種のカスタムイベント関数でしか扱えない点にご注意ください。

CALL_PUSH_■()

特定のキーを押下したタイミングで実行されるカスタムイベント関数です。

『ハンバーガー』『DCE』といった作品はこれありきで作られています。

今回は、Hキーを押下した時にヒントが表示されるスクリプトを作ります。

/** Hボタンを押した際に呼ばれる関数 */
function CALL_PUSH_H() {
  if(o[64][5] == 0){ //カニがもういない
    MSG("見事成し遂げましたね!\n他のWWAも是非遊んでみましょう!");
  }
  else if(HAS_ITEM(59)){ //魚を持っている
    MSG("美味しそうな魚を手に入れましたね。\n島のどこかに、お腹がすいた方が\nいらっしゃるみたいですよ。");
  }
  else if(HAS_ITEM(63)){ //槍を持っている
    MSG("ゲームクリアまであと少し!\n倒せなかった敵も倒せるかも?");
  }
  else if(AT_TOTAL < 20){ //武器なし
    MSG("武器を持っていないようですね。\nこのままでは危ないのでお店で武器を買ったり、\nマップの中で拾ったりしましょう。");
  }
  else if(HAS_ITEM(23)) { //長剣あり
    MSG("かっこいい長剣ですね。\n島の反対側に行くと\n新しい発見があるかもしれません。");
  }
  else{
    MSG("島の中で手に入るお金は有限です。\n無駄遣いに気をつけましょう!");
  }
}

注意点その1
スマホやタブレット、ゲームパッドなどで遊ぶ場合には今のところ実行手段がございません。最初からPCプレイ専用と割り切るわけでない場合、NPCに話しかけたり特定のアイテムをクリックしたりしても代用できるようにしておくことが望ましいです。

注意点その2
ほとんどのキーでこのカスタムイベント関数は使えますが、QWEASDZXCといったアイテムボックスと連動するキーで設定するとアイテムクリック時の処理と処理が重なってしまいます。
それらのキーでカスタムイベント関数を動かしたい場合、アイテムボックスの該当位置にクリック可能なアイテムを持たせられないようにするとよいでしょう。

CALL_GAMEOVER()

ゲームオーバー時に呼び出されるカスタムイベント関数です。
今回は、初回時のみコンティニューできる処理を入れてみましょう。

/** ゲームオーバー時に呼ばれる関数 */
function CALL_GAMEOVER() {
  if(!v["isContinued"]){ //未コンティニュー
    MSG("おっとっと、やられてしまいましたか……。\nしかし、今回は特別に復活できます!");
    HP = 120;
    GD += 30;
    JUMPGATE(35,65);
    v["isContinued"] = true;
  }
  else{ //コンティニュー済み
    MSG("おっとっと、やられてしまいましたか……。\n次はうまくいくといいですね!");
  }
}

注意点
$no_gameover=1を適用している場合、このカスタムイベント関数は処理を記述しても動きません。

CALL_FRAME()

1フレームごとに動作する関数です。
超ザックリ説明すると、WWAを遊んでいる間ほぼずっと動く処理です。

『逃げ金!』『カマック・ライフ』のような、リアルタイムでゲーム上に影響を与えるような作品にとっては生命線とも言える関数です。

また、Re!ハビリダンジョン2や、『Eden ~骸の園~』のように、生命力が0になったらゲームオーバー以外のことが発生するような作品作りでもこの関数は欠かせません。

要するに、使い方次第であんなことやこんなことができる。
それくらい重要な関数ですが、使いこなすには発想力が超重要です。

今回はサンプルとして、一定の時間経過でモンスターがランダム出現するようにしてみましょう。

/** 1フレームごとに呼ばれる関数 */
function CALL_FRAME() {
  //変数ない場合は初期化
  if(!v["frame"]){
    v["frame"] = 0;
  }
  v["frame"] += 1;

  //一定間隔でモンスターをランダム沸きさせる
  if(v["frame"] == 88){
    o[47][66] = 41+RAND(3); //物体41:スライム 42:コウモリ 43:ゾンビ からランダム
    v["frame"] = 0;
  }
}

補足
WWAをにおける物体パーツは2画像アニメーションが可能ですが、1画像につき22フレーム使われております。
なのでサンプルの場合、1→2→1→2→と動いたタイミングでモンスター出現の処理が発生しております。

注意事項
前述のとおり1フレームごとに処理が発生します。


他にも、色々とカスタムイベント関数はございます。
作りたい作品に応じて色々と使ってみましょう。

おわりに

前回に比べて文字数が膨れ上がってしまいましたが、
Script応用サンプルの記事をお送りいたしました。

しかし、WWA Scriptのすべてを紹介できたわけではありません。
今回ご紹介できた内容は、WWA Contest 2023の範囲までです。

ピクチャ機能やオブジェクト変数を用いることで、WWA作品制作の幅は更に広がっていくのです。

というわけで、2025年1月を目処に『発展編』の記事を執筆予定です。

また『発展編』の記事がなくとも、下記のような記事も合わせて読んでいくことで、より色々なことができるようになることでしょう。
腕に自信のある方は是非覗いてみてください。

色々と難しさを感じ敬遠してしまう方もいらっしゃるかもしれませんが、WWA WingのDiscordコミュニティでは有識者から様々なサポートを行うことが可能です。

是非、意見交換したり、分からないところを相談したりしてみてください。
WWA制作どころかWWA自体に疎いよという方のご見学もOKです。

明日はAokashiさんの
『セーブデータ破損を防ぐWWAの作り方』です。


いいなと思ったら応援しよう!