見出し画像

ティラノスクリプト製ゲーム「マダム・ポプスキンの憂鬱」の技術的な話【配列(js)】

ティラノスクリプトはhtml・css・jsで作られたゲーム制作ツールです。
つまり、html・css・jsがわかればいくらでも機能追加ができるわけです。
「マダム・ポプスキンの憂鬱」ではどんな機能追加をしたのか数回にわけて紹介していきます。

今作に必要だった機能

シナリオ画面上に表示される「通知」「行動指針」「調査手帳」
行動内容にあわせて3段階評価がされる「本日の成果」

「マダム・ポプスキンの憂鬱」はミステリーもので、とにかく情報量が多いゲームです。
一定区間ごとに情報をまとめ、何をすれば良いかプレイヤーに提示しないと、何をすればいいのかわからなくなってしまう内容です。
ですので、どんな行動をすれば良いかまとめた「行動指針」と情報をまとめた「調査手帳」。そして雑記以外の情報が追加されるたびに「通知」を表示し、1日の終わりには「本日の成果」を表示するようにしました。

今回の解説はjsの「配列」

「行動指針」や「調査手帳の雑記」は、特定の文章を通過すると情報テキストが順番に追加されます。「本日の成果」も行動結果にあわせた文章が表示されます。これらを作るのに複数のデータを順番に並べた構造の「配列」を使いました。

この記事を読むために

変数について

ティラノスクリプト公式サイトの「実践テクニック>変数とは」を理解されている前提で話を進めます。
システム変数は「sf.変数名」、ゲーム変数は「f.変数名」、一時変数は「tf.変数名」がわかればOKです。

[macro]について

ティラノスクリプト公式サイトの「サブルーチンとマクロとは」を理解されている前提で話を進めます。
「mp」については「ティラノスクリプトを完全に理解する」で確認できます。

jsの記述方法

このnoteは「"singleQuote": true,"semi": false」で記載しています。
記述方法で「;」とか「'」とか「"」をどうするかという話。
わからないなら読み飛ばしてOK。動けばいいんだYO!

jsとjQueryが混在している可能性

私はjQueryチョットワカル勢であってjsに理解のある人間ではありません。
白状するとjQueryの記述メインで突然jsの記述が顔を出すような書き方をしていました。
jQueryもjsの一種ではあるので動くのですが、ライブラリを必要とするため、ライブラリのメジャーバージョンが変わると動作しなくなることもあります。
ので、noteに掲載するものはなるべくjsに揃えていきますが、混在していたらごめんなさい。

配列を覚えよう

実践1:変数に配列を格納して表示

ゲーム変数にデータを配列として格納し、リスト表示させるコードを書いてみましょう。
次の内容をコピーし、scene1.ksなりのシナリオファイルに貼り付けてみてください。

;[note_set]というmacroを作成。「f.note_array」に配列として情報を足していく挙動
;macroはfirst.ksなど、最初に読み込まれるファイルへの記載を推奨
[macro name="note_set"]
  [iscript]
    f.note_array.push({ num: mp.note_num, text: mp.note_text })
  [endscript]
[endmacro]

;変数「f.note_array」は配列であると宣言
[eval exp="f.note_array = []"]

;雑記を追加したいタイミングに次を記載
;2つめのデータを2回いれているのはわざと、実践2で紹介することの前振り
;1→32になっているのは、実践3で紹介することの前振り
[note_set note_num="1" note_text="1つめのデータ"]
[note_set note_num="3" note_text="3つめのデータ"]
[note_set note_num="2" note_text="2つめのデータ前者"]
[note_set note_num="2" note_text="2つめのデータ後者"]

;console.logに「f.note_array」の内容を表示、リリース前に消すこと
[eval exp="console.log(f.note_array)"]

;htmlで配列を表示する領域を用意
;ティラノスクリプトのhtmlタグは時間がかかるので、ゲーム開始時にhtmlを追加するjsを記述しておくのもよい
[html]
  <div id="array_area" style="position:relative;background:#fff;color:#000;font-size:16px;padding:20px;width:500px;"></div>
[endhtml]

;「f.note_array」の配列を表示するためのjs、jsファイルで外部化してもよい
;js外部ファイル化する場合は「f.note_array」を「TYRANO.kag.stat.f.note_array」と書く
[iscript]
  //htmlいう名前の変数を作る(ティラノスクリプトでいうtf.変数名みたいな一時的なもの)
  var html = ''
  //配列に格納されている区切りごとに同じ処理を繰り返す
  $.each(f.note_array, function (index, val) {
    //変数「html」の中身を追加、val.numにはnote_num、val.textにはnote_text、が代入される
    html +=
      '<p class="note n_' + val.num + '">' + val.text + '</p>'
  })
  //id="array_area"と書かれている箇所に変数「html」を追加する
  document.getElementById('array_area').innerHTML = html
[endscript]

<div id="array_area"></div>内に次の記述が出現します。

<p class="note n_1">1つめのデータ</p>
<p class="note n_3">3つめのデータ</p>
<p class="note n_2">2つめのデータ前者</p>
<p class="note n_2">2つめのデータ後者</p>

Tyrano Studio デバッグツール=Google Chrome デベロッパー ツールのConsole、message内にも次のように表示されるはずです。

Tyrano Studioでデバッグツールを表示した画面
console.logで出力したものがmessageに表示される、探すのがちょいと大変かも

これは「f.note_array = []」へ[note_set]を通過するたびにデータが追加され「f.note_array = [{num:"1",text:"1つめのデータ"},{num:"2",text:"2つめのデータ"},{num:"2",text:"2つめのデータ"},{num:"3",text:"3つめのデータ"}]」になったということです。

データは配列の末尾に追加されていくので、取得した情報を順番に表示したいといったことに使いやすいです。

これだけなら「note_num」を指定する必要もないし、配列を使わずに変数に文字列を書き足していく処理でも問題ありません。
しかし「{num:"2",text:"2つめのデータ"},{num:"2",text:"2つめのデータ"}」と同じデータが格納されるの気持ち悪いですよね。
これを避けるための処理を実践2で紹介します。

実践2:配列から重複データを排除

ゲームではAルートで「2つめのデータ」を取得、その後Bルートでも「2つめのデータ」を取得といったことが発生しがちです。
その場合、配列に何度も同じデータが格納されるのはよろしくないので、重複するデータが存在する場合は排除する処理を[macro name="note_set"]内に書きましょう。

;[note_set]というmacroを作成。「f.note_array」に配列として情報を足していく挙動
;macroはfirst.ksなど、最初に読み込まれるファイルへの記載を推奨
[macro name="note_set"]
  [iscript]
    f.note_array.push({ num: mp.note_num, text: mp.note_text })
    //配列の重複を削除処理
    var result_array = f.note_array.filter((val, index, array) => {
      //num だけをリスト化する
      var numList = array.map((val) => val['num'])
      //重複を削除する
      if (numList.indexOf(val.num) === index) {
        return val
      }
    })
    //重複排除した値をf.note_arrayに再格納
    f.note_array = result_array
  [endscript]
[endmacro]

;変数「f.note_array」は配列であると宣言
[eval exp="f.note_array = []"]

;雑記を追加したいタイミングに次を記載
;2つめのデータを2回いれているのはわざと
;1→32になっているのは、実践3で紹介することの前振り
[note_set note_num="1" note_text="1つめのデータ"]
[note_set note_num="3" note_text="3つめのデータ"]
[note_set note_num="2" note_text="2つめのデータ前者"]
[note_set note_num="2" note_text="2つめのデータ後者"]

;console.logに「f.note_array」の内容を表示、リリース前に消すこと
[eval exp="console.log(f.note_array)"]

;htmlで配列を表示する領域を用意
;ティラノスクリプトのhtmlタグは時間がかかるので、ゲーム開始時にhtmlを追加するjsを記述しておくのもよい
[html]
  <div id="array_area" style="position:relative;background:#fff;color:#000;font-size:16px;padding:20px;width:500px;"></div>
[endhtml]

;「f.note_array」の配列を表示するためのjs、jsファイルで外部化してもよい
;js外部ファイル化する場合は「f.note_array」を「TYRANO.kag.stat.f.note_array」と書く
[iscript]
  //htmlいう名前の変数を作る(ティラノスクリプトでいうtf.変数名みたいな一時的なもの)
  var html = ''
  //配列に格納されている区切りごとに同じ処理を繰り返す
  $.each(f.note_array, function (index, val) {
    //変数「html」の中身を追加、val.numにはnote_num、val.textにはnote_text、が代入される
    html +=
      '<p class="note n_' + val.num + '">' + val.text + '</p>'
  })
  //id="array_area"と書かれている箇所に変数「html」を追加する
  document.getElementById('array_area').innerHTML = html
[endscript]

<div id="array_area"></div>内に次の記述が出現します。

<p class="note n_1">1つめのデータ</p>
<p class="note n_3">3つめのデータ</p>
<p class="note n_2">2つめのデータ前者</p>

実践1では重複していた「note_num="2"」データのうち「2つめのデータ後者」が消えました、スッキリ!
文字列で処理をかけることもできますが、情報を番号管理し、その番号が重複しないようにするといった処理のほうが管理がしやすいです。

実践3:配列内の検索

ゲームでは取得すべきデータが5件あり、現在は3件のみ取得していると明示したい。さらに今の配列の格納順番「1つめのデータ」「3つめのデータ」「2つめのデータ」になっているのを「1→2→3」と並べ替えたい、ということがあります。
そういった場合は「note_num」で指定した番号が存在するかチェックして順番に表示してあげましょう。

;[note_set]というmacroを作成。「f.note_array」に配列として情報を足していく挙動
;macroはfirst.ksなど、最初に読み込まれるファイルへの記載を推奨
[macro name="note_set"]
  [iscript]
    f.note_array.push({ num: mp.note_num, text: mp.note_text })
    //配列の重複を削除処理
    var result_array = f.note_array.filter((val, index, array) => {
      //num だけをリスト化する
      var numList = array.map((val) => val['num'])
      //重複を削除する
      if (numList.indexOf(val.num) === index) {
        return val
      }
    })
    //重複排除した値をf.note_arrayに再格納
    f.note_array = result_array
  [endscript]
[endmacro]

;変数「f.note_array」は配列であると宣言
[eval exp="f.note_array = []"]

;雑記を追加したいタイミングに次を記載
;順番が132になっていたり、2つめのデータを2回いれているのはわざと
[note_set note_num="1" note_text="1つめのデータ"]
[note_set note_num="3" note_text="3つめのデータ"]
[note_set note_num="2" note_text="2つめのデータ"]
[note_set note_num="2" note_text="2つめのデータ"]

;console.logに「f.note_array」の内容を表示、リリース前に消すこと
[eval exp="console.log(f.note_array)"]

;htmlで配列を表示する領域を用意
;ティラノスクリプトのhtmlタグは時間がかかるので、ゲーム開始時にhtmlを追加するjsを記述しておくのもよい
[html]
  <div id="array_area" style="position:relative;background:#fff;color:#000;font-size:16px;padding:20px;width:500px;"></div>
[endhtml]

;「f.note_array」の配列を表示するためのjs、jsファイルで外部化してもよい
;js外部ファイル化する場合は「f.note_array」を「TYRANO.kag.stat.f.note_array」と書く
[iscript]
  //htmlいう名前の変数を作る(ティラノスクリプトでいうtf.変数名みたいな一時的なもの)
  var html = ''
  //ループ処理を5件までおこなう。0からカウントされるので0,1,2,3,4で5件。だからi<5までループ処理。
  for (var i = 0; i < 5; i++) {
    //fornumの番号は0,1,2,3,4ではなく、1,2,3,4,5としたいのでi+1
    var fornum = i + 1
    //fornumを数値から文字列に変換したものを用意
    var fornums = String(fornum)
    //note_numに入っている数字(配列上ではnum)が、現在の処理番号1,2,3,4,5に合致するか検索し判定
    var notecheck = f.note_array.find((val) => val.num === fornums)
    //変数「html」の中身を追加。データが存在すれば情報を表示、そうでないなら取得できていないと表示
    if (notecheck) {
      html +=
        '<p class="note n_' + notecheck.num + '">' + notecheck.text + '</p>'
    } else {
      html +=
        '<p class="note n_none">' +  fornums + 'つめのデータは取得できていません</p>'
    }
  }
  //id="array_area"と書かれている箇所に変数「html」を追加する
  document.getElementById('array_area').innerHTML = html
[endscript]

<div id="array_area"></div>内に次の記述が出現します。

<p class="note n_1">1つめのデータ</p>
<p class="note n_2">2つめのデータ</p>
<p class="note n_3">3つめのデータ</p>
<p class="note n_none">4つめのデータは取得できていません</p>
<p class="note n_none">5つめのデータは取得できていません</p>

実践4:配列内の検索と分岐作成

配列内を検索した結果を変数で格納し、シナリオファイル内の分岐に使えるようにしてみましょう。「本日の成果」で使った手法です。

;[note_set]というmacroを作成。「f.note_array」に配列として情報を足していく挙動
;macroはfirst.ksなど、最初に読み込まれるファイルへの記載を推奨
[macro name="note_set"]
  [iscript]
    f.note_array.push({ num: mp.note_num, text: mp.note_text })
    //配列の重複を削除処理
    var result_array = f.note_array.filter((val, index, array) => {
      //num だけをリスト化する
      var numList = array.map((val) => val['num'])
      //重複を削除する
      if (numList.indexOf(val.num) === index) {
        return val
      }
    })
    //重複排除した値をf.note_arrayに再格納
    f.note_array = result_array
  [endscript]
[endmacro]

;変数「f.note_array」は配列であると宣言
[eval exp="f.note_array = []"]

;雑記を追加したいタイミングに次を記載
[note_set note_num="1" note_text="1つめの休息データ"]
[note_set note_num="2" note_text="2つめの休息データ"]
[note_set note_num="3" note_text="3つめの進展データ"]

;console.logに「f.note_array」の内容を表示、リリース前に消すこと
[eval exp="console.log(f.note_array)"]

;htmlで配列を表示する領域を用意
;ティラノスクリプトのhtmlタグは時間がかかるので、ゲーム開始時にhtmlを追加するjsを記述しておくのもよい
[html]
  <div id="array_area" style="position:relative;background:#fff;color:#000;font-size:16px;padding:20px;width:500px;"></div>
[endhtml]

;「f.note_array」の配列を表示するためのjs、jsファイルで外部化してもよい
;js外部ファイル化する場合は「f.note_array」を「TYRANO.kag.stat.f.note_array」と書く
[iscript]
  //htmlいう名前の変数を作る(ティラノスクリプトでいうtf.変数名みたいな一時的なもの)
  var html = ''
  //配列に格納されている区切りごとに同じ処理を繰り返す
  $.each(f.note_array, function (index, val) {
    //変数「html」の中身を追加、val.numにはnote_num、val.textにはnote_text、が代入される
    html +=
      '<p class="note n_' + val.num + '">' + val.text + '</p>'
  })
  //ここから文字列で結果判定をするための情報を取得する記述
  //1.配列の「num([note_set note_num="数字"]の数字)」だけを取り出す
  var result_array = f.note_array.map((val) => val['text'])
  //2-1.「休息」の文字列の件数を取り出す
  var result_rest = result_array.filter(value => value.match(/休息/g)).length
  //2-2.ゲーム内の記述に利用したい場合はティラノスクリプトのゲーム変数に2-1の結果を格納
  f.result_rest = result_rest
  //3-1.「進展」の文字列の件数を取り出す
  var result_progress = result_array.filter(value => value.match(/進展/g)).length
  //3-2.ゲーム内の記述に利用したい場合はティラノスクリプトのゲーム変数に3-1の結果を格納
  f.result_progress = result_progress

  //変数「html」の中身を追加
  html += '<p class="result">休息:' + result_rest + '件</p>'
  html += '<p class="result">進展:' + result_progress + '件</p>'

  //id="array_area"と書かれている箇所に変数「html」を追加する
  document.getElementById('array_area').innerHTML = html
[endscript]

<div id="array_area"></div>内に次の記述が出現します。

<p class="note n_1">1つめの休息データ</p>
<p class="note n_2">2つめの休息データ</p>
<p class="note n_3">3つめの進展データ</p>
<p class="result">休息:2件</p>
<p class="result">進展:1件</p>

if文と取得できた件数を使って、よくできた、できなかったの判定を書けば「本日の成果」は出来上がりです。

実践5:配列のデータを書き換える

「行動指針」のように完了/未完了を設定し、既読未読をつけましょう。
処理の差分をわかりやすくするため、メッセージを挟んだ長めのコードをサンプルとして用意しました。

#
メッセージ枠が表示されている前提。[r]
ティラノの「html」タグは描写に時間がかかりますので、描画が完了するまでお待ちください。

;[note_set]というmacroを作成。「f.note_array」に配列として情報を足していく
;macroはfirst.ksなど、最初に読み込まれるファイルへの記載を推奨
[macro name="note_set"]
  [iscript]
    f.note_array.push({ num: mp.note_num, text: mp.note_text, read: false, done: false })
    //配列の重複を削除処理
    var result_array = f.note_array.filter((val, index, array) => {
      //num だけをリスト化する
      var numList = array.map((val) => val['num'])
      //重複を削除する
      if (numList.indexOf(val.num) === index) {
        return val
      }
    })
    //重複排除した値をf.note_arrayに再格納
    f.note_array = result_array
  [endscript]
[endmacro]

;[note_done]というmacroを作成。「f.note_array」の配列内の情報を確認して書き換え
[macro name="note_done"]
  [iscript]
    //1.配列の「num([note_set note_num="数字"]の数字)」だけを取り出す
    var result_array = f.note_array.map((val) => val['num'])
    //1のなかのどこに今回書き換えたい数字([note_done note_num="数字"]の数字)があるか探す
    var result_num = result_array.indexOf(mp.note_num)
    //探した番号のところの完了フラグとして利用している「done」をtrueに書き換える
    f.note_array[result_num].done = true
  [endscript]
[endmacro]

;変数「f.note_array」は配列であると宣言
[eval exp="f.note_array = []"]

;雑記を追加したいタイミングに次を記載
;2つめのデータを2回いれているのはわざと
[note_set note_num="1" note_text="1つめのデータ"]
[note_set note_num="2" note_text="2つめのデータ前者"]
[note_set note_num="2" note_text="2つめのデータ後者"]
[note_set note_num="3" note_text="3つめのデータ"]

;console.logに「f.note_array」の内容を表示、リリース前に消すこと
[eval exp="console.log(f.note_array)"]

;htmlで配列を表示する領域を用意
;ティラノスクリプトのhtmlタグは時間がかかるので、ゲーム開始時にhtmlを追加するjsを記述しておくのもよい
[html]
  <div id="array_area" style="position:relative;background:#fff;color:#000;font-size:16px;padding:20px;width:500px;"></div>
[endhtml]

;「f.note_array」の配列を表示するためのjs、jsファイルで外部化してもよい
;js外部ファイル化する場合は「f.note_array」を「TYRANO.kag.stat.f.note_array」と書く
[iscript]
  //htmlいう名前の変数を作る(ティラノスクリプトでいうtf.変数名みたいな一時的なもの)
  var html = ''
  //配列に格納されている区切りごとに同じ処理を繰り返す
  $.each(f.note_array, function (index, val) {
    //変数「html」の中身を追加、val.numにはnote_num、val.textにはnote_text、が代入される
    html +=
      '<p class="note note_' + val.num + '">' +val.text + '<br>既読:' + val.read + '<br>完了:' + val.done + '</p>'
  })
  //id="array_area"と書かれている箇所に変数「html」を追加する
  document.getElementById('array_area').innerHTML = html

  //描画後に既読(read)をすべてtrueに書き換えて閲覧済みにする
  f.note_array.forEach((val) => {
    val.read = true
  })
[endscript]

[glink target="next1" text="次へ" size="20" width="110" x="330" y="200"]
[s]

*next1

#
画面上に表示された配列を削除しました。「1つめのデータを完了」し「4つめのデータを追加」して再度画面上に配列を表示します。

;4つめのデータを追加
[note_set note_num="4" note_text="4つめのデータ"]

;1つめのデータを完了
[note_done note_num="1"]

;htmlで配列を表示する領域を用意
[html]
  <div id="array_area" style="position:relative;background:#fff;color:#000;font-size:16px;padding:20px;width:500px;"></div>
[endhtml]

;「f.note_array」の配列を表示するためのjs、jsファイルで外部化してもよい
;js外部ファイル化する場合は「f.note_array」を「TYRANO.kag.stat.f.note_array」と書く
[iscript]
  //htmlいう名前の変数を作る(ティラノスクリプトでいうtf.変数名みたいな一時的なもの)
  var html = ''
  //配列に格納されている区切りごとに同じ処理を繰り返す
  $.each(f.note_array, function (index, val) {
    //変数「html」の中身を追加、val.numにはnote_num、val.textにはnote_text、が代入される
    html +=
      '<p class="note note_' + val.num + '">' +val.text + '<br>既読:' + val.read + '<br>完了:' + val.done + '</p>'
  })
  //id="array_area"と書かれている箇所に変数「html」を追加する
  document.getElementById('array_area').innerHTML = html

  //描画後に既読(read)をすべてtrueに書き換えて閲覧済みにする
  f.note_array.forEach((val) => {
    val.read = true
  })
[endscript]

<div id="array_area"></div>に最初に表示されるのはこちら。

<p class="note note_1">1つめのデータ<br>既読:false<br>完了:false</p>
<p class="note note_2">2つめのデータ前者<br>既読:false<br>完了:false</p>
<p class="note note_3">3つめのデータ<br>既読:false<br>完了:false</p>

配列の既読状態が表示後に変更され、[note_set note_num="4" note_text="4つめのデータ"]と[note_done note_num="1"]を通過した結果、次に<div id="array_area"></div>に表示されたのはこちら。

<p class="note note_1">1つめのデータ<br>既読:true<br>完了:true</p>
<p class="note note_2">2つめのデータ前者<br>既読:true<br>完了:false</p>
<p class="note note_3">3つめのデータ<br>既読:true<br>完了:false</p>
<p class="note note_4">4つめのデータ<br>既読:false<br>完了:false</p>

1〜3つめまでのデータ既読false→true、1つめのデータの完了false→true、4つめのデータが新規追加されました。
この既読や完了で出力される文字列をclass名に利用して装飾すると「行動指針」のような表示になります。

まとめ

今回は「行動指針」「調査手帳の雑記」「本日の成果」を作成するのに必要だった「配列」について解説しました。……ゲーム上で書いていたのがあまりにも美しくないコードだったのでだいぶん色々修正しています。
私の中ではこうかけばよかったね…という内容になり、うわぁい。

jsそのものについて知りたい場合はMDN、技術記事はQiitaあたりで探すと良いです。
英語が読めるならstackoverflowなど海外コミュニティで探すのもおすすめ。

実際ゲーム画面はどんな挙動かを確認したい場合は、ゲーム「マダム・ポプスキンの憂鬱」をプレイしてみてください。

ノベルゲームコレクションの配信(ブラウザ・Windowsダウンロード版)
https://novelgame.jp/games/show/6563

ふりーむの配信(Windowsダウンロード版)
https://www.freem.ne.jp/win/game/28248

特設サイト
https://totetike.rgr.jp/madam/

次回は「調査手帳の雑記以外」で利用している技術でjsとcssの合わせ技について解説予定です。「通知」の解説になりました!

※この記事は2022年5月28日時点の内容をまとめたものです。

この記事が気に入ったらサポートをしてみませんか?