お前らの変数管理はダサい【ティラノスクリプト】
ティラノスクリプトでは「sf」「f」「tf」の変数があらかじめ用意されています。
変数はゲーム制作においては必須とも言えるものですが、この使い方を間違ってしまうといらない苦労をするハメになったりします。
というわけで、今回はより効率的・効果的な変数の使い方を話していきたいと思います。
具体的には、配列とオブジェクトについての話です。
配列 is 何
つまりどういうことかというと、配列とは「ひとつの名前を持つ変数内に複数の値を格納したもの」というわけです。
配列の構造を見てみるとわかりやすいと思います。
tf.test = ["a", "b", "c", "d"]
「[]」で囲うことで配列として扱われます。
「[]」の中に、複数の値がカンマ区切りで格納されているのがわかるでしょうか。
配列とは、ひとつの変数の中に複数の部屋を持つものです。
配列の参照
各部屋の値を参照するには、以下のように記述します。
tf.test = ["a", "b", 123, false]
tf.test[0] //"a"
tf.test[2] //123
tf.test[3] //false
tf.test[10] //undefined
上の例にある通り、一つの配列には、部屋さえ分ければ文字列だろうが数値だろうが真偽値だろうが入れることができます。
これはJavaScriptの特徴で、他の、変数の型などが厳密に定められた言語ではできないことが多いです。
配列のそれぞれの値を参照するには、「変数名[部屋番号]」のように記述します。「[]」の中に、部屋の番号を入れてやるイメージです。
そして部屋の番号は「0」から始まることに注意しておきましょう。
上の例では、左から「0,1,2,3」と部屋番号が決まっています。
それでは、上記コードの最後の例のように、「存在しない部屋番号」を指定するとどうなるのでしょうか。
答えは例にもある通り、「undefined」、未定義ですよという値が返ってきます。
配列の定義
さて、順番が前後しましたが、「配列に値を格納する」方法について説明していきます。
tf.test1 = ["a", "b", "c", "d"]
tf.test2 = []
tf.test2[0] = 123
tf.test2[1] = 456
tf.test2[10] = 789
tf.test2 // [123, 456, , , , , , , , , 789]
これまでのコード例で挙げたように、あらかじめ「変数名 = [配列内の値]」のように定義する他に、tf.test2のような書き方もあります。
最初に「変数名 = []」として空の配列を定義しておき、あとから「変数名[部屋番号] = 値」のように、部屋番号を指定して配列の中に値を格納してやる書き方です。
この場合、最後の例のように部屋番号を飛ばして値を格納することもできます。
が、何か意図がある場合を除いてやらない方がいいと思います。
配列の使い所
そんな配列ですが、どういう場面で使うと良いのでしょうか。
一例を挙げると、複数の値を持つ同一の項目に使うと便利です。
とはいっても、具体的にどんな場合だよ、と思われる方もいると思いますので、実例を挙げてみたいと思います。
わかりやすいところでいうと、ティラノデフォルトの「config.ks」でしょうか。
// ボタンを表示する座標(tf.config_y_ch[0]とtf.config_y_auto[0]は未使用)
tf.config_x = [1040, 400, 454, 508, 562, 616, 670, 724, 778, 832, 886]; // X座標(共通)
[button name="bgmvol,bgmvol_10" fix="true" target="*vol_bgm_change" graphic="&tf.btn_path_off" width="&tf.btn_w" height="&tf.btn_h" x="&tf.config_x[1]" y="&tf.config_y_bgm" exp="tf.current_bgm_vol = 10; tf.config_num_bgm = 1"]
[button name="bgmvol,bgmvol_20" fix="true" target="*vol_bgm_change" graphic="&tf.btn_path_off" width="&tf.btn_w" height="&tf.btn_h" x="&tf.config_x[2]" y="&tf.config_y_bgm" exp="tf.current_bgm_vol = 20; tf.config_num_bgm = 2"]
[button name="bgmvol,bgmvol_30" fix="true" target="*vol_bgm_change" graphic="&tf.btn_path_off" width="&tf.btn_w" height="&tf.btn_h" x="&tf.config_x[3]" y="&tf.config_y_bgm" exp="tf.current_bgm_vol = 30; tf.config_num_bgm = 3"]
[button name="bgmvol,bgmvol_40" fix="true" target="*vol_bgm_change" graphic="&tf.btn_path_off" width="&tf.btn_w" height="&tf.btn_h" x="&tf.config_x[4]" y="&tf.config_y_bgm" exp="tf.current_bgm_vol = 40; tf.config_num_bgm = 4"]
[button name="bgmvol,bgmvol_50" fix="true" target="*vol_bgm_change" graphic="&tf.btn_path_off" width="&tf.btn_w" height="&tf.btn_h" x="&tf.config_x[5]" y="&tf.config_y_bgm" exp="tf.current_bgm_vol = 50; tf.config_num_bgm = 5"]
[button name="bgmvol,bgmvol_60" fix="true" target="*vol_bgm_change" graphic="&tf.btn_path_off" width="&tf.btn_w" height="&tf.btn_h" x="&tf.config_x[6]" y="&tf.config_y_bgm" exp="tf.current_bgm_vol = 60; tf.config_num_bgm = 6"]
[button name="bgmvol,bgmvol_70" fix="true" target="*vol_bgm_change" graphic="&tf.btn_path_off" width="&tf.btn_w" height="&tf.btn_h" x="&tf.config_x[7]" y="&tf.config_y_bgm" exp="tf.current_bgm_vol = 70; tf.config_num_bgm = 7"]
[button name="bgmvol,bgmvol_80" fix="true" target="*vol_bgm_change" graphic="&tf.btn_path_off" width="&tf.btn_w" height="&tf.btn_h" x="&tf.config_x[8]" y="&tf.config_y_bgm" exp="tf.current_bgm_vol = 80; tf.config_num_bgm = 8"]
[button name="bgmvol,bgmvol_90" fix="true" target="*vol_bgm_change" graphic="&tf.btn_path_off" width="&tf.btn_w" height="&tf.btn_h" x="&tf.config_x[9]" y="&tf.config_y_bgm" exp="tf.current_bgm_vol = 90; tf.config_num_bgm = 9"]
[button name="bgmvol,bgmvol_100" fix="true" target="*vol_bgm_change" graphic="&tf.btn_path_off" width="&tf.btn_w" height="&tf.btn_h" x="&tf.config_x[10]" y="&tf.config_y_bgm" exp="tf.current_bgm_vol = 100; tf.config_num_bgm = 10"]
; BGMミュート
[button name="bgmvol,bgmvol_0" fix="true" target="*vol_bgm_change" graphic="&tf.btn_path_off" width="&tf.btn_w" height="&tf.btn_h" x="&tf.config_x[0]" y="&tf.config_y_bgm" exp="tf.current_bgm_vol = 0; tf.config_num_bgm = 0"]
横長なスクリプトですが、[button]タグの右側で配列の値が指定されています。
「x="&tf.config_x[1]"」の部分ですね。
「tf.config_x」というのが、いちばん最初にコメントがあるとおり、ボタンのx座標の値を格納しているものです。
[0]の値は使われていないため、[1]から始まっていますが、やっていることは同じです。
さて、それではこういう書き方をすると、何が便利なのでしょうか。
その説明の前にまずは、「特定の値を変数に格納する」ことの便利さを説明しましょう。
今回のコンフィグ画面のように、特定の値をタグに直接記述せず、一度変数に入れておいて、その変数名をタグに指定する、ということをする場面があります。
これは、特に「特定の値」が変更される可能性がある場合に力を発揮します。
特定の値を変数に格納せず直接記述した場合、値の変更の都度、その値を使った場所を書き換えなければなりませんが、変数に格納しておけば、その「変数に特定の値を格納する」部分を書き換えるだけで済みます。
「でもそれって、一括置換使えば同じですよね?」という方もいるかも知れません。
しかし、その「特定の値」が別の「特定の値」とたまたま、偶然にも、運命の悪戯のように、同じ値になってしまったとき、どうなるでしょうか。
そこに広がっているのは果てしなき阿鼻叫喚、一面の地獄絵図です。
一括置換で余計な場所まで書き換えが発生するリスク、ひとつひとつ丹精込めた書き換えで抜け漏れが発生するリスク、それらを避けるためにも、変数による値の指定はできるようにしておきましょう。
そしてこの期に及んでも、「それなら、変更が発生しない部分には直接値を指定していいんですよね!」などとのたまう輩には、この言葉を贈っておきます。
前置きが長くなりました。
配列で値の指定をする利点は、前置きでも説明したとおり、余計な書き換え、つまりは書き間違いが生じるリスクを少しでも減らすためです。
コンピュータサイエンス的にはメモリとかそういう面での説明もあるでしょうが、今回は「コードを書く上での利点」の話なので割愛します。
たとえば今回の、コンフィグ画面のような使い方だと、このような変数の使い方でも同じようなことはできます。
tf.config_x_1 = 400
tf.config_x_2 = 454
tf.config_x_3 = 508
tf.config_x_4 = 562
//以下略
しかし上の例のように、ひとつひとつ、別の変数を使うとどうなるか。
ひとつひとつ、別の変数を使っているので、定義の時点で変数名を書き間違えるリスクが発生します。
定義の時点で間違っているので、一括置換による書き換えも使えません。後述するfor文による繰り返し処理もできません。
それなら素直に配列を使おうという話です。
プログラミングの世界でバッドノウハウやバッドプラクティスとして挙げられるものは、数多の先人たちの血によって記述された、何にも代えがたい最良の教科書です。
やめろと言われていることには、必ず理由があるのです。
オブジェクト is 何
続いてオブジェクトの説明です。個人的には、JavaScriptにおいてはオブジェクトの使い方を知ることでよりわかりやすいコードを記述することができると思っています。
配列が「部屋番号を持った部屋」の集合であったのに対して、オブジェクトは「部屋名を任意に決められる部屋」の集合であるといえます。
オブジェクトの参照
tf.test = {
id: 1,
name: "aaa",
wepon: "sword",
}
「{}」で囲むことで、オブジェクトが定義できます。
そして上の例のように、「部屋名: 値」のように、「部屋の名前」と「値」を組み合わせて記述していきます。
この「部屋の名前」をプロパティ名、部屋名と値との組み合わせをプロパティと言ったりします。
部屋の名前はなんでもいいですが、特にこだわりがないなら「半角英数字」にしておくのが無難です。記号を使いたい場合は「_」(半角アンダースコア)にしておきましょう。ハイフンを使うと余計なエラーが起きる場合があります。
そして配列と同じように、各部屋の値には文字列でも数字でも真偽値でもundefinedでも、なんでも格納することができます。さらに、配列やオブジェクトまで格納できてしまいます。
つまり、
tf.test = {
id: 1,
name: "aaa",
wepon: [
"sword",
"sheald",
"knife"
]
}
というような書き方もできるというわけです。
そして、各プロパティを参照するには以下のように記述します。
tf.test = {
id: 1,
name: "aaa",
wepon: [
"sword",
"shield",
"knife"
]
}
tf.test.id //1
tf.test.name //"aaa"
tf.test.wepon[0] //"sword"
「変数名.プロパティ名」と記述することで各プロパティを参照、プロパティの値が配列の場合は「変数名.プロパティ名[部屋番号]」と書けばOKです。
さてこの書き方、なにかに似ていますよね。
そう、ティラノスクリプトの変数、sf、f、tfです。
これらの変数は、あらかじめ定義されたオブジェクトなのです。
ちなみにオブジェクトの値を参照するには、このような書き方もできます。
tf.test = {
id: 1,
name: "aaa",
wepon: [
"sword",
"shield",
"knife"
]
}
tf.wepon_num = 1
tf.item = "wepon"
tf.test["id"] //1
tf["test"].name //"aaa"
tf.test[tf.item][tf.wepon_num] //"shield"
「変数名[プロパティ名]」とすることでも参照できます。
そしてこの書き方の場合、最後の例のようにプロパティ名に「変数」を指定することができます。
そしてこの書き方、「tf["test"].name」の例で示しているとおり、「sf、f、tf」変数の変数名に対しても使うことができます。
オブジェクトの定義
それではオブジェクトの定義方法について見ていきましょう。
tf.test1 = {
id: 1,
name: "aaa",
wepon: [
"sword",
"shield",
"knife"
]
}
tf.item = "accessory"
tf.test2 = {}
tf.test2.id = 1
tf.test2.name ="bbb"
tf["test2"][tf.item] = "ring"
既に例で挙げているとおり、各プロパティを「{}」で囲んで定義する書き方の他に、空のオブジェクトを定義しておいて、あとからプロパティを追加する書き方もできます。
このときにも、「変数名[プロパティ名]」とすることでプロパティ名に変数を指定することができます。
また、こういったこともできます。
tf.test1 = {
id: 1,
name: "aaa",
wepon: [
"sword",
"shield",
"knife"
]
}
tf.test1.item = "herb"
先にオブジェクトとして値を定義していた「tf.test1」変数に、あとから「item」プロパティを追加しました。
tf変数自体がオブジェクトで、そこに好きなように変数を追加できることを考えれば当然のことですが、意識しないと意外と気づけない盲点だったりもします。
オブジェクトの使い所
オブジェクトは、「明示的な目的を持った複数の値」をまとめるのに適しているといえると思います。
前項の例で挙げたように、キャラクターのID、名前、装備などを、「キャラクター」というひとまとまりにして格納しておくと、管理がしやすいですよね。
これをそれぞれ別の変数、例えば「f.chara_id」「f.chara_name」「f.chara_wepon」などに分けるのは、変数の管理はもとよりコードも煩雑になります。
管理がしやすいとはバグが発生しにくいこと、バグが発生しにくいとはバグ修正に手間取る時間をゲームそのものの面白さを高めるために使えるということです。
ゲーム制作者が本当にするべきはバグと戦うことではなく、面白い、自分が作りたいゲームを作ることなのです。
配列とオブジェクトを組み合わせる
さて、これまで説明してきた配列とオブジェクトですが、これらは組み合わせることによって真価を発揮します。
例えば、「オブジェクトを値として持つ配列」を作ることによって、データベース的に情報を管理することができます。
f.item = [
{
"id": "item_1",
"type": "item",
"kind": "dice",
"name": "乗合馬車の乗車券",
"price": "1000",
"rarity": "1",
"discription": "どの国の乗合馬車にも乗れる券。1ターンの間、振れるサイコロの数が2個になる。",
"turn": "1",
"count": "2"
},
{
"id": "item_2",
"type": "item",
"kind": "dice",
"name": "辻馬車の乗車券",
"price": "5000",
"rarity": "2",
"discription": "どの国の辻馬車にも乗れる券。3ターンの間、振れるサイコロの数が2個になる。",
"turn": "3",
"count": "2"
},
//中略
]
上記のコードは、実際に私が自作ゲームの中で使っているものです。
f.itemという変数名の配列の中に、オブジェクトが入っています。
そしてそれぞれのオブジェクトには、id、type、nameなどのプロパティがあります。
この変数には、ゲーム中で使用できるアイテムのマスターデータが格納されています。
ツクールで言うところの、アイテムデータベースのようなものでしょうか。
これらの値をそれぞれ個別の変数にしてしまうと、必要になる変数の数は際限なく増えていきますし、そんなにたくさんの変数があるコードは見にくいことこの上ありません。
より効率的なコーディング
このように配列とオブジェクトを組み合わせて管理することで、複雑な処理でも簡便に記述することができるようになります。
例えば、先程のアイテムを「購入する」処理を見てみましょう。
f.item = [
{
"id": "item_1",
"type": "item",
"kind": "dice",
"name": "乗合馬車の乗車券",
"price": "1000",
"rarity": "1",
"discription": "どの国の乗合馬車にも乗れる券。1ターンの間、振れるサイコロの数が2個になる。",
"turn": "1",
"count": "2"
},
{
"id": "item_2",
"type": "item",
"kind": "dice",
"name": "辻馬車の乗車券",
"price": "5000",
"rarity": "2",
"discription": "どの国の辻馬車にも乗れる券。3ターンの間、振れるサイコロの数が2個になる。",
"turn": "3",
"count": "2"
},
//中略
]
sf.am.buy = [
{
id: "item_1",
count: 0
},
{
id: "item_2",
count: 0
},
//中略
]
まず、アイテムのマスターデータベースに加えて、購入したアイテムを管理する「sf.am.buy」配列があります。
配列内にはオブジェクトが格納されており、オブジェクトの構造は上記のコードのとおりです。
//複数回購入可能アイテム(消費アイテム)
let item = sf.am.buy.item.filter(function(item){
return item.id == tf.id
})[0]
item.count++
//所持金減らす
let item = f.am[tf.type].filter(function(item){
return item.id == tf.id
})[0]
sf.am.money -= item.price
その上で、アイテムを購入する際はこのように処理されます。
let item = sf.am.buy.item.filter(function(item){
return item.id == tf.id
})[0]
item.count++
まず、購入するアイテムを割り出します。
.filter()は、JavaScriptの配列では共通して使える関数(メソッド)です。
その名の通り、特定の値で配列全体をフィルターして、特定の値のみが格納された部屋だけの配列を新しく作ります。
関数の引数として関数が入っているのでわかりにくいですが、上記のコードでは「配列内のオブジェクトのidプロパティがtf.idの値と同じもの」をフィルターするようになっています。
例えばtf.idに「item_1」が格納されていれば、「sf.am.buy」配列の中から「id」プロパティが同値のもの、つまり1番目のオブジェクトを取り出して、新たに配列を作ります。
つまり、上記のfilter関数を実行して返される値は↓のようになります。
[
{
id: "item_1",
count: 0
}
]
オブジェクトがひとつだけ入った配列になっていますね。
let item = sf.am.buy.item.filter(function(item){
return item.id == tf.id
})[0] //←ここ!
このコードの最後の「[0]」とは、この「ひとつだけ入ったオブジェクト」を配列から取り出したものを「item」変数に代入しているのです。
したがって、実際にitem変数に代入される値は以下になります。
{
id: "item_1",
count: 0
}
そしてこのオブジェクトを、以下のように処理します。
item.count++
これにより、「item」オブジェクト内の「count」プロパティ、つまり「購入したアイテムの個数」が1加算されるのです。
値渡しと参照渡し
さて、ここで疑問を覚えた方もいるのではないでしょうか。
そうなのです。
「購入したアイテムを管理」しているのは「sf.am.buy」変数です。
「sf.am.buy」からfilter関数によって「新しく作り出されたitem変数」の値を変えたところで、「sf.am.buy」変数の値は変わらない…はずですよね?
ここがJavaScriptの罠のひとつです。
変数の代入には、「値渡し」と「参照渡し」という2種類があります。
この内、「値渡し」はいわゆる通常の値の代入です。
たとえば以下のようなコードがあったとします。
tf.test1 = "aaa"
tf.test2 = tf.test1
tf.test2 = "bbb"
tf.test1 //"aaa"
tf.test1にある値を代入して、tf.test1変数をさらにtf.test2変数に代入したとします。
そこからさらにtf.test2変数の値を変えても、tf.test1変数の値は変わりません。これは特に違和感なく理解できると思います。
しかし「参照渡し」は違います。
tf.test1 = {
id: 1,
count: 0
}
tf.test2 = tf.test1
tf.test2.count++
tf.test1
// {
// id: 1,
// count: 1
// }
おわかりいただけただろうか…
先ほどのコードと同じように、tf.test2変数にtf.test1変数を代入、その後「tf.test2」変数内のプロパティを変更しています。
にも関わらず、「tf.test1」変数内のプロパティまで変更されているではありませんか!
これが「参照渡し」です。
変数Aを変数Bに代入したとき、変数のAの「値」が渡されるのではなく、変数Aへの「参照」が渡され、変数Bの変更は参照先である変数Aにも反映される、というものです。
JavaScriptにおいて、オブジェクト変数は「参照渡し」されるのです。
そして配列もまた、JavaScriptにおいてはオブジェクトの一種と捉えられるため、「参照渡し」されます。
したがってこの疑問には、「item変数はsf.am.buy変数を参照しているため、item変数への変更はsf.am.buy変数にも反映される」というのが答えになります。
なんだかややこしいな…と思われた方も多いでしょう。
しかしこの「参照渡し」、使い所さえわかっていれば大変便利なものなのです。
実例を挙げてみましょう。
たとえば、ティラノスクリプトで用意されている「sf、f、tf」変数もまた、別の変数の参照なのです。
それぞれの変数を正しく記述するとこのようになります。
TYRANO.kag.variable.sf
TYRANO.kag.stat.f
TYRANO.kag.variable.tf
実際はこのように長々した名前なのですが、ティラノスクリプト内では使いやすいように「sf、f、tf」という別の変数名をつけているのです。
そしてこれらは参照渡しされているので、sf変数への変更が即座に「TYRANO.kag.variable.sf」変数に反映されますし、逆もまた然りなのです。
とはいえ、ときには「オブジェクトの構造を保ったまま値渡ししたい…」というときもあるしょう。
そんなときは以下のように記述します。
tf.test2 = Object.assign({}, tf.test1) //素のJavaScript
tf.test2 = $.extend(true, {}, tf.test1) //jQuery
どちらの書き方でも結果は同じです。
上の書き方がJavaScriptそのものの書き方、下はjQuery(JavaScriptを便利にするライブラリ)の書き方です。
それぞれの引数の意味はググってもらうとして、どちらも「tf.test1をコピーした別のオブジェクト変数をtf.test2に代入」しています。
この場合、tf.test1とtf.test2に入っているオブジェクトは「構造は同じ別物」なので、tf.test2への変更がtf.test1に反映されることはなく、その逆もありません。
配列、オブジェクトの便利な関数
既にいくつか実例を挙げていますが、配列とオブジェクトには、JavaScript側で用意された便利な関数が大量にあります。
よく使いそうなものを挙げてみましょう。
説明を書くと文字数が大変なことになるのでMDNへのリンクにしときます。今の時点で13000字あるのでご容赦ください。
なお、「Array.prototype.~」の「prototype」というのは、「Arrayに初期実装されているもの」くらいの意味です。実際にコードを書くときには省略できますし省略してください。
関数を組み合わせる
上記の関数を組み合わせることで、以下のような処理が可能になります。
sf.am.story = {
country_1: {
ep_1: {
id: "main_1_1",
name: "中央王国 エピソード1",
read: false
},
ep_2: {
id: "main_1_2",
name: "中央王国 エピソード2",
read: false
},
ep_3: {
id: "main_1_3",
name: "中央王国 エピソード3",
read: false
}
}
}
上記のような配列に対して、以下のような処理を行います。
let ev = Object.keys(sf.am.story.country_1).filter(function(item){
return sf.am.story.country_1[item].id === "main_1_2"
})
まず、「Object.keys」関数で「sf.am.story.country_1」オブジェクトの「プロパティ名を列挙した配列」を作ります。今回の場合だと、「ep_1、ep_2、ep_3」となります。
その後、この配列に対してfilter関数を実行しています。
引数として関数が渡されており、この関数の引数である「item」に入る値は、「filter関数が実行されている配列の値」、つまり「ep_1、ep_2、ep_3」です。
filter関数内の関数では、「sf.am.story.country_1[item].id」が「main_1_2」であるか、つまり「sf.am.story.country_1」変数内の「ep_1、ep_2、ep_3」プロパティに対して、プロパティ内の「id」プロパティの値が「main_1_2」であるものを返す、となります。
したがって、「ev」変数に入るのは↓となります。
ep_2: {
id: "main_1_2",
name: "中央王国 エピソード2",
read: false
}
この「Object.keys(xxx).forEach()」は、使用頻度が高いです。使い方を覚えておくと処理の簡略化に繋がります。
変数設計の話
さて、ここまで変数の使い方について話してきましたが、そもそもの変数設計が間違っていると便利な使い方ができず、いらないバグを産む温床ともなります。
設計というと、なんだか難しく聞こえるかもしれませんが、原則としてはそうややこしいことでもありません。
わかりやすい、管理しやすい、使いやすい変数を用意すればいいのです。
配列、オブジェクトを使ってみる
シンプルなノベルゲーム、例えばキャラクターの好感度によってエンド分岐するようなゲームであれば、それほど複雑な変数を用意する必要はありません。
この場合であれば、キャラクター別の好感度を示す変数を用意するだけですみます。
それでは、どういった場面でオブジェクトや配列が必要になってくるでしょうか。
それは、「フラグ」の他に「データ」が必要なゲームです。
わかりやすいところで言うと、探索ゲームなどでしょうか。
例として、架空の探索ゲームで必要になりそうなデータを考えてみましょう。
あんまり項目を多くしてもわかりにくいので、こんなところにしときます。
上記の項目は、大きく分けて「探索箇所」「アイテム」に分けられますね。
まずはこの大きなくくりで変数を分けましょう。
「名前」「説明文」「状態」というくくりでも分けられますが、このくくりでは直感的にわかりにくいですし、管理もしにくそうです。
探索箇所、アイテムでわけてみました。
それではこれをオブジェクトにしてみます。
f.place = [
{
name: "居間",
sub: "家族団欒の場",
state: true
},
{
name: "台所",
sub: "ごはんを作るところ",
state: false
},
]
f.item = [
{
name: "金属バット",
sub: "曲がった金属バット",
state: false
}
{
name: "バールのようなもの",
sub: "細長い金属の棒",
state: true
}
]
こんな感じでしょうか。
f.place、f.itemという配列の中に、「名前、説明、状態」のプロパティを持ったオブジェクトが格納されています。
配列、オブジェクトを設計してみる
それでは、実際にこの変数を使うときのことを想定して、ここにもうひと工夫してみましょう。
例えば、「金属バット」を持っているときにのみ発生するイベントがあったとします。
その場合、スクリプトでは↓のように記述することになります。
[iscript]
tf.state = false
for(let i = 0; i < f.item.length - 1; i++){
if(f.item[i].name == "金属バット"){
tf.state = f.item[i].state
}
}
[endscript]
[if exp="tf.state"]
;持っているときの処理
[else]
;持っていないときの処理
[endif]
ちょっとややこしいですね。
やっていることとしては、f.item配列の各オブジェクトを巡回し、名前が「金属バット」のオブジェクトの「state」プロパティの値をtf.stateに代入、tf.stateの値を参照して処理を出し分けしています。
ここで、f.item変数のの構造をこのように変更してみます。
f.item = {
"金属バット": {
sub: "曲がった金属バット",
state: false
}
"バールのようなもの": {
sub: "細長い金属の棒",
state: true
}
}
f.item変数を配列からオブジェクトに変更し、各アイテムの「名前」をプロパティ名とするオブジェクトを入れ子にしています。
このような構造にすることで、先ほどのコードはこのように書き換えることができます。
[if exp="f.item['金属バット'].state"]
;持っているときの処理
[else]
;持っていないときの処理
[endif]
だいぶスッキリしましたね。
このように、「名前」をプロパティ名に持つオブジェクトを入れ子にすることで、いちいち配列の値をすべて判定しなくてもよくなります。
とはいえ、配列には配列の利点があります。
たとえば、アイテムをすべて集めた場合に特別なイベントを発生させる場合です。
tf.not_complete = f.item.includes(function(item){
return item.state === false
})
[if exp="tf.not_complete"]
//全部所持していないときの処理
[else]
//全部所持しているときの処理
[endif]
↑のコードは、「f.item配列内に、stateプロパティがfalseのオブジェクトが存在する」場合に「tf.not_complete」変数にtrueを代入するものです。
要するに、所持していないアイテムがある場合にtrueになります。
しかし上記のコードで使っている.includes()メソッドは、配列でしか使えないものです。
f.item変数をオブジェクトにする場合だと、この手が使えない…と思いましたか?
このように記述することで、オブジェクトの場合でも配列と同じように操作することができます。
tf.not_complete = Object.keys(f.item).includes(function(key){
return item.state === false
})
//以下同
関数を組み合わせるの項でも説明したとおり、「Object.keys()」を使うことで、オブジェクト変数を擬似的な配列として操作可能になります。
それではこれで変数設計は完了…とはなりません。
順調にゲーム制作を進めていたあなたは、ふと新しいアイデアを思いつきます。
f.item = {
"金属バット": {
sub: "曲がった金属バット",
state: false
}
"バールのようなもの": {
sub: "細長い金属の棒",
state: true
}
}
……どうしますか?
配列の使い所の項でも挙げた阿鼻叫喚が生まれようとしていますね。
「アイテムの名前」を、f.itemオブジェクトのプロパティ名として持っているため、アイテム名の変更の都度、すべてのシナリオファイル内の「f.item["金属バット"]」を書き換えなければなりません。
これではいけないというのは、先に説明したとおりです。
それではどうするか。
プレイヤーに対して表示する「名前」とは別に、そのアイテムを示す固有の値、「ID」を用意してやればいいのです。
f.item = {
item_01: {
name: "ぼろぼろの金属バット",
sub: "曲がった金属バット",
state: false
},
item_02: {
name: "バールのようなもの",
sub: "細長い金属の棒",
state: true
},
item_03: {
name: "バールのようなもの",
sub: "先が曲がった金属の棒",
state: true
}
}
「名前」をプロパティ名からオブジェクト内のプロパティに移動、プロパティ名は「item_01」といった「一意の値」となっています。
「一意の値」というのが重要です。ここの値は絶対に同一階層の別のプロパティ名と同じになってはいけませんし、またあとからこの値を変えることは極力避けなければいけません。
つまるところ、あとからオブジェクト変数、配列変数の構造を変えたり、IDの値を変えたりといったことがないよう、慎重に変数を設計しなければならないのです。
そしてそれは、「その変数はなんのために使うものなのか」「自分がどのようにその変数を利用したいのか」、これをよく考えることで実現できることといえます。
データベース的な使い方
さて、こうして配列とオブジェクトを組み合わせて作成した変数は、ずいぶん複雑な構造になっていることでしょう。
これは、もはや「データベース」と呼ぶべきものです。
データベースについての説明は上記のとおりですが、もっと簡単な説明として、「表計算ソフトに入力されたデータ」に近いものといえると思います。
つまり、こういうやつです。
縦の列に項目、横の行にそれぞれのデータが格納されています。
ExcelやGoogleスプレッドシートを使ったことがある方なら見慣れたものだと思います。
これら、表計算ソフトで作成された(疑似)データベースは、CSV形式などで出力することで、ティラノスクリプト(というかJavaScript)でも扱いやすいものになります。
CSVとは、こういうやつです。
CSV、comma-separated values、つまりカンマ区切りで表現されたデータ、です。
その名の通り、カンマで各項目の値が区切られているのがわかると思います。
このCSVをティラノスクリプトで簡単に読み込めるようになるプラグインがあるので紹介しておきます。
単純な配列形式だけでなく、オブジェクトとして読み込むこともできるので、自分が使いやすい形で変数に格納できます。おすすめ。
まとめ
ということで、変数管理の話でした。
配列やオブジェクトを使わなくてもゲームは作れますが、使ったほうが管理しやすくわかりやすくなる場面も多いです。
使用する変数の数が多くなるほど、配列やオブジェクトを使う恩恵も増してくるでしょう。
変数管理に「これが正解」というものはないかもしれませんが、その変数を「どう使うか」を考えながら設計することで、バグやミスの少ないスクリプトを書くことができます。
そして複雑な変数を管理できれば、より複雑なゲームを作ることもできるでしょう。
この記事があなたの選択肢を広げる助けになっていれば幸いです。
サポートをしていただけると私がたいへんよろこびます。 ちなみに欲しい物リストはこちら→https://www.amazon.jp/hz/wishlist/ls/2DBRPE55L3SQC?ref_=wl_share