top / 1 / 2 / 3 / 4 / ex1 / 5 / 6 / ex2 / 7 / 8 / 9 / 10 / 11 / ex3 / 12 / 13 / 14 /..../ 関数 / 覚え書き / 倉庫
第8回 【 スクリプト型MODを作る 】

「ゲーム開始時点で任意のアイテムを所持させる」

とりあえずここまで説明してきたMODは、言ってみれば「性的」「静的」なMODばかりで、データそのものを弄るものでした。
ここからは、スクリプトと呼ばれるゲームを駆動させている命令群を弄る事で、ある程度ではありますが「動的」なMODを作ってみたいと思います。

説明を始める前にお断りしなければならないのですが、ここから紹介するスクリプトの使い方などは全て海外のMODなどを解析してノリと雰囲気とプログラム開発経験で辛うじて理解したものです。
と言うのも、シングルプレイ用のSDK(開発環境)は存在はするものの、まだリリースされていないのです。
どの命令がどんな機能を持っているのか、一般公開されていないので正確な部分は全く判りません。
今のところ、「ここをこうしたら、こうなった」と言う結果論からしか説明する事ができないのです。
その中でも更に、「まあ恐らく間違いないだろう」と思われる部分に絞って説明させてもらうので、あまり多くの機能は期待しないでください。

とりあえず、今回は「ゲーム開始時点で任意のアイテムを所持させる」方法を書いていこうと思います。
今回からは急激にプログラム的な説明が連発します。開発経験が全くない方にも判るように書いたつもりですが、今までのように「数字を当てはめるだけ」で完成するものではありません。難易度は明らかに上がっています。
とは言え基本は同じなので、そう構える事はありません。
いつものノリでジャンジャン行きましょう。失敗してもやり直せば良いだけです。
最悪、ここに書いてある事が理解できなくても最終的に出来上がったスクリプトをコピペすれば動くと思います。
それでも駄目ならば、オイラの環境で動作確認したものをお土産にしてあるのでお持ち帰りください。(一番最後に置いてます)

判らなくてもとりあえずやってみて、後でスクリプトを眺めてみるだけでも何となく雰囲気が伝わるものです。

とにかく、一つ作ってみましょう。
前回の痛銃と同じで、説明が長くて難しい印象ですが、やってる処理はほんのちょっぴりです。大丈夫です。
プログラム経験がある方には多分物凄い勢いでシャカセッポー(釈迦に説法)する事になると思うので、予め御了承ください。

若干内容を修正しました。
修正点はコメント文で、STALKERfilesに投稿してるものと一致させました。



仕組みから説明するとマジでキリがないと言うかオイラ自身ロクに理解してないので、結果から説明させてもらいますね。

まず、構成です。
こんなんなります。

□start_item ┣□gamedata ┃┗□scripts ┃ ┣■bind_stalker.script ┃ ┗■ngc_mod.script ┗■readme.txt

さて、この構成を作ろうとして「あれ?」と気付いた点があると思います。
そう。ファイル「ngc_mod.script」はアンパックしたgamedataフォルダの中には存在しません
これは、自分の手でゼロから作る必要があるのです。
ngcと言うのはオイラのハンドルの略なので、このファイル名は貴方の好きな名前で構いません。
半角英数小文字が好ましいですが、それ以外にも、「使われないような名前」にしてください。
例えばテストで作ったスクリプトだからと言って、「test.script」とか安直な名前を付けると、他の人や別のスクリプトで既に使われていたりして、下手するとどちらかがどちらかを上書きしてメチャクチャになる可能性もあります。
実際、「test.script」はフォルダ「gamedata\scripts\」内に存在します。

よって、出来る限り使われる可能性の少ない名前を付ける必要があるのです。
ハンドルネームなどの固有名詞(ここでは“ngc”)を付けるのが無難だと思います。
新しく作るスクリプトの中身は空っぽで構いません。中身にはテキストデータが入る事になるので、テキストエディタで開くものだと思っておいてください。
作り方が判らないなら、空っぽのテキストファイル「無題.txt」を用意し、「拡張子ごと」改名すればOKです。

では、中身の説明をしますね。
まず、フォルダ「gamedata\scripts\」に元々入っていたファイル「bind_stalker.script」から行きます。
中身を開いてみると・・・。開発経験の無い人はここでいきなり挫けそうになりますね。大丈夫です。
例によってテキストエディタの自動改行の設定によって、何行目かは人によって異なると思いますが、29行目らへんからこんな一文があります。

function actor_binder:net_spawn(data) printf("actor net spawn") level.show_indicators() self.bCheckStart = true self.weapon_hide = false -- ・瓔M 齏・D・A跖 ・・珸腋AA・ weapon_hide = false -- 瓏粱鞣瑯・肭@琿・ 蒟・A 璢. if object_binder.net_spawn(self,data) == false then return false end db.add_actor(self.object) if self.st.disable_input_time == nil then level.enable_input() end self.weather_manager:reset() -- game_stats.initialize () if(actor_stats.add_to_ranking~=nil)then actor_stats.add_to_ranking(self.object:id()) end --' ヌ璢赳褌 鳰・蓿@・ death_manager.init_drop_settings() return true end

先頭行の function と言うのは「機能」の事で、「ここから機能が記述されてます。機能の名前は“actor_binder:net_spawn”です」と言う内容だと思ってください。
最終行に end とありますが、これは「ここまでで“actor_binder:net_spawn”の記述は終わりです」と言う意味です。
ゲーム内で駆動している動的な仕掛けはこのように「ファンクション」と言う形で記述されています。

では、いま紹介したこのファンクションはどのような機能を持っているのでしょうか?
最初からずっこけるようで申し判りませんが、正直、良く判りません。
しかし、確実に判っていることがあります。
ファンクション「actor_binder:net_spawn」は、主人公がゲーム内の世界に出現した瞬間に実行されていると言う事です。
つまり、ゲーム開始時ゲームロード時レベル間を移動した時、このようにゲームが何らかの要因で中断状態から稼動状態に移行した瞬間にのみ、このファンクションは動いているのです。

このような何かしらの状態やタイミングをトリガーとして動くファンクションは多数あります。
今開いているファイル「bind_stalker.script」は、そんなトリガー型ファンクションの中でも主人公に関わるものが記述されているようです。

では、早速ここに改造を施してみましょう。
このファンクションの中で何をやっているかは、少なくともここでは重要ではありません。
重要なのは、「主人公がロードされた瞬間にのみ、このファンクションが動く」と言う事実なのです。
つまり、今やろうとしている修正、「ゲーム開始時点で任意のアイテムを所持させる」は、このファンクションに機能を一つ追加してやる事で実現できるのです。

このファンクションの最後から2行目を見ると、こんな記述がありますね。
return true
この直前、1行上に空白の行があります。
丁度良いので、この行にこの一行を突っ込んでください。
ngc_mod.ngclordcheck() -- NGC_MOD ADDITIONAL
「ngc_mod」の部分は先ほど貴方が名づけたスクリプトファイルの名前にしてください。拡張子(.script)は必要ありません。
例えば、貴方が名付けたスクリプトファイルの名前が、「xyz123.script」であった場合、突っ込む文章は以下のようになります。
xyz123.ngclordcheck() -- MOD名 ADDITIONAL
「--」はスクリプトにおけるコメント文です。使い方はltx形式における「;(セミコロン)」と全く同じです。
出来た文を全部書いてるとキリがないので中略しますが、こんな感じになりましたね?
function actor_binder:net_spawn(data) printf("actor net spawn") 〜〜中略〜〜 --' ヌ璢赳褌 鳰・蓿@・ death_manager.init_drop_settings() ngc_mod.ngclordcheck() -- NGC_MOD ADDITIONAL ←この行を突っ込んだ。 return true end
「ngclordcheck」と言うのは、貴方がこれから作るファンクションの名前です。
自分で好きな名前をつけても構いませんが、その際にはとりあえず以下の事に気をつけてください。

・半角英数小文字のみ使用可。
・他の人が使いそうに無い名前を使う事。(ファイル名と同じ理由です)
・機能の内容が想像できる名前にする事。


「ngclordcheck」「ニャギ茶ロードチェック」と言う意味です。
ファイル「bind_stalker.script」に対する修正はこれで終わりです。
今後も主人公にあわせて何かをするタイプのMODを作るときには、このファイルはちょくちょく改造する事になると思います。

では、ファイル「bind_stalker.script」は閉じて結構です。
次に先ほど作った中身空っぽのファイル「ngc_mod.script」を開いてください。もちろん自分でファイル名を決めた場合は(ry
ここにオリジナルのファンクションを記述していく事になります。

まずは、先ほどのファンクションを造りましょう。
ファンクション名は先ほど決めた通り、「ngclordcheck」ですね。もちろん自分でファンクション名を決めた場合には(ry もう書きません。以降、脳内変換してください)
まずは先ほどの例に習い、先頭行と最終行を書きます。
function ngclordcheck() end
よろしいですか?
では、中身。
主人公がロードされたら、このファンクションは何をするのか?そう、アイテムを追加するのです。
アイテムを追加する命令はこれです。
alife():create("アイテムID", db.actor:position(), db.actor:level_vertex_id(), db.actor:game_vertex_id(), db.actor:id())
2行に分かれていますが、実際には一つの命令です。 単語とかをぶった切らない限り、文法的にカンマ区切りなどでの改行は許されているので、見やすくするために改行しているのです。
これを先ほど作ったファンクション内に記述しましょう。
使い方は"アイテムID"の部分に目的のアイテムIDを入れるだけです。
では取りあえず、ロケットランチャーでも持たせて見ましょうか。バイオハザードの2周目みたいですね。
ロケットランチャーのアイテムIDは「wpn_rpg7」なので、
function ngclordcheck() alife():create("wpn_rpg7", db.actor:position(), db.actor:level_vertex_id(), db.actor:game_vertex_id(), db.actor:id()) end
こうなりますね。
何文字分かインデントを入れているのは見やすくするためです。
しかしあまり見やすくなってないですね。命令文が長すぎて複数行に渡ってしまっているからです。
まあこんなに長い命令文はそう多くは無いので、見やすくないのは今回は我慢してください。
さて、これではロケットランチャーだけで弾がありません。
寂しいので弾丸を5発程追加しましょう。
function ngclordcheck() alife():create("wpn_rpg7", db.actor:position(), db.actor:level_vertex_id(), db.actor:game_vertex_id(), db.actor:id()) alife():create("ammo_og-7b", db.actor:position(), db.actor:level_vertex_id(), db.actor:game_vertex_id(), db.actor:id()) alife():create("ammo_og-7b", db.actor:position(), db.actor:level_vertex_id(), db.actor:game_vertex_id(), db.actor:id()) alife():create("ammo_og-7b", db.actor:position(), db.actor:level_vertex_id(), db.actor:game_vertex_id(), db.actor:id()) alife():create("ammo_og-7b", db.actor:position(), db.actor:level_vertex_id(), db.actor:game_vertex_id(), db.actor:id()) alife():create("ammo_og-7b", db.actor:position(), db.actor:level_vertex_id(), db.actor:game_vertex_id(), db.actor:id()) end
こうですね。
これで主人公がロードされた瞬間、イベントリの中にロケットランチャーとその弾が出現するようになりました。
基本的な機能はこれでできた事になりますね。

さて、ここで一つ問題があります。
薄々感づいてる方もおられるかも知れませんが、このファンクションを呼び出すのはファイル「bind_stalker.script」内のファンクション「actor_binder:net_spawn」ですね。
そして、そのファンクションは、ロード時に必ず実行されるファンクションです。
先ほども書きましたが、ロードとはゲーム開始は勿論、セーブしたところからやり直すのもそうですし、レベルを移動した時にも該当します。
つまり、このままでは、セーブ&ロードする度、隣のレベルに移動するたびにロケットランチャー&弾5発セットが次々に追加されて無限増殖してしまうのです。

そこで、スクリプトに条件分岐をさせなければなりません。
特定の条件を与え、その条件によって処理を分岐させるのです。
まず、我々が考えなければならないのは“ゲーム開始”“それ以外”のロードを切り分ける方法です。
これは、以下の命令で取得できます。
xr_logic.pstor_retrieve(db.actor, "x_first_run", true)
「xr_logic」とは、このゲームに最初から備わっている機能群で、中身はファイル「xr_logic.script」で見る事が出来ます。が、ややこしいのでパス。例によってオイラにも良く判ってません。
知るべき事は、このファイル「xr_logic」に記述されているファンクション「pstor_retrieve」を使用する事で、「今、ゲームを開始したのか、それともそれ以外のロードなのか」を知る事ができると言う事です。

ここで取得できる情報は「true(真)」または「false(偽)」と呼ばれる二択の値で、世間一般では「フラグ」と呼ばれています。
恋愛要素のあるギャグ漫画などでたまに特定の女性との仲が進展したり、そのきっかけを得たりすると「(その女性の)フラグが立った」などと言う事がありますが、あれはこのプログラム用語が語源です。
「ハルヒフラグ」「こなたフラグ」も全て、プログラムが「ハルヒの好感度が一定以上か?」「必要なイベントはこなしたか?」などと言う状態要素に対し「true(YES)」または「false(NO)」と言う二択の形で状態を認識している事に由来しており、その様子があたかもフラグ(旗)を立てたり下ろしたりするのに似ている事により「フラグ」と呼ばれているのです。

つまり、このゲームの内部でも「今、ゲーム開始したところ?」と言う状態要素に対し、常に「true(YES)」または「false(NO)」のどちらかの値を保持しており、スクリプト内の命令でそのフラグを見せてもらう事が出来ると言うわけです。
先ほど紹介したのが、そのための命令です。
では実際に使ってみましょう。
こんなんなります。
function ngclordcheck() if (xr_logic.pstor_retrieve(db.actor, "x_first_run", true) == true) then alife():create("wpn_rpg7", db.actor:position(), db.actor:level_vertex_id(), db.actor:game_vertex_id(), db.actor:id()) alife():create("ammo_og-7b", db.actor:position(), db.actor:level_vertex_id(), db.actor:game_vertex_id(), db.actor:id()) alife():create("ammo_og-7b", db.actor:position(), db.actor:level_vertex_id(), db.actor:game_vertex_id(), db.actor:id()) alife():create("ammo_og-7b", db.actor:position(), db.actor:level_vertex_id(), db.actor:game_vertex_id(), db.actor:id()) alife():create("ammo_og-7b", db.actor:position(), db.actor:level_vertex_id(), db.actor:game_vertex_id(), db.actor:id()) alife():create("ammo_og-7b", db.actor:position(), db.actor:level_vertex_id(), db.actor:game_vertex_id(), db.actor:id()) else end end
相変わらず見づらいですね。
命令文が長いとよくあることなんですよ。
取りあえず追加されたのがこの文。
if (xr_logic.pstor_retrieve(db.actor, "x_first_run", true) == true) then 〜〜中略〜〜 else end
何だかプログラミング教室と化してきましたね。
これはif条件式と呼ばれる条件分岐の一つで、まんま英語のように解釈して構いません。
「もしも(if)」条件が成立した「場合(then)」、次の処理を実行してくださいと言う意味です。
「else」は条件が成立しなかった場合の処理ですが、今回は何もしないので空白になってます。
条件の中に「==」と言う記述がありますが、これは「右と左がイコールであれば」と言う意味です。
今回の場合、「xr_logic.pstor_retrieve命令の結果、見せてもらったフラグ == true であれば」と言う条件を設定しているわけですね。

余談ながら「~=」と書くと「右と左がイコールでなければ」と言う条件になります。
つまりフラグのように、値が「true」と「false」の2種類しかない場合に限っては、

if (フラグ == ture) then

と書くのも、そして、

if (フラグ ~= false) then

こう書くのも全く同じ意味になります。
if条件式の基本は次の形式です。
function 機能名 if (条件) then 条件が成立した場合に行う処理@ 条件が成立した場合に行う処理A else 条件が成立しなかった場合に行う処理@ 条件が成立しなかった場合に行う処理A end end
で、今回の場合はこう言う意味になります。
function ニャギ茶ロードチェック if (ゲーム開始フラグが立ってる?) then イベントリにロケットランチャを追加 イベントリにロケット弾を追加x5 else 特に何もしません end end
ちなみに「else」の部分は何もしないのであれば省略可能です。
こうですね。
function ニャギ茶ロードチェック if (ゲーム開始フラグが立ってる?) then イベントリにロケットランチャを追加 イベントリにロケット弾を追加x5 end end
このほうがスッキリしてるので、可能な場合は省略するのが一般的です。
さて、これで条件分岐は出来ました。
最後に、もう一つだけやらねばならない事があります。
実は、先ほどの「ゲーム開始であるか?」のフラグですが、あれは下ろす時だけは我々が手動で操作してやらねばならないのです。
フラグを下ろす命令は次の一文です。
xr_logic.pstor_store(db.actor, "x_first_run", false)
この一行を書き込む事で、「ゲーム開始であるか?」のフラグは「false(NO)」になり、以降、ゲームを最初から始めるまでずっとfalse(NO)のままになります。
もちろんどこかのスクリプト内で強引に「true(YES)」を設定したりしたら話は別ですが・・・。
これをさっきの形式に追加すると、
function ニャギ茶ロードチェック if (ゲーム開始フラグが立ってる?) then イベントリにロケットランチャを追加 イベントリにロケット弾を追加x5 ゲーム開始フラグを下ろす end end
これで、2回目からのロードではこの処理は行われなくなりますね。
では、実際にスクリプトを書いてみましょう。
function ngclordcheck() if (xr_logic.pstor_retrieve(db.actor, "x_first_run", true) == true) then alife():create("wpn_rpg7", db.actor:position(), db.actor:level_vertex_id(), db.actor:game_vertex_id(), db.actor:id()) alife():create("ammo_og-7b", db.actor:position(), db.actor:level_vertex_id(), db.actor:game_vertex_id(), db.actor:id()) alife():create("ammo_og-7b", db.actor:position(), db.actor:level_vertex_id(), db.actor:game_vertex_id(), db.actor:id()) alife():create("ammo_og-7b", db.actor:position(), db.actor:level_vertex_id(), db.actor:game_vertex_id(), db.actor:id()) alife():create("ammo_og-7b", db.actor:position(), db.actor:level_vertex_id(), db.actor:game_vertex_id(), db.actor:id()) alife():create("ammo_og-7b", db.actor:position(), db.actor:level_vertex_id(), db.actor:game_vertex_id(), db.actor:id()) xr_logic.pstor_store(db.actor, "x_first_run", false) end end
さあ、これでスクリプト完成です。
早速動かしてみるのも良いのですが、その前にコメントを入れておくと、後で自分が見たときに見やすくなります。
先ほども言いましたが、スクリプト文法におけるコメント開始の記号は「--」です。
最初はとにかく細かく書いておく事をお勧めします。

それではまとめますね。


構造:
□start_item ┣□gamedata ┃┗□scripts ┃ ┣■bind_stalker.script ┃ ┗■ngc_mod.script(自分で作る) ┗■readme.txt


既存スクリプト修正:
ファイル:bind_stalker.script ファンクション:actor_binder:net_spawn 最後から3行目、「return true」の直前行に以下の一文を追加。 ngc_mod.ngclordcheck() -- NGC_MOD ADDITIONAL


新規スクリプト作成:
ファイル:ngc_mod.script 以下の内容で新規作成。 --@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ --@@ NGC_MOD --@@ --@@ Function Now: --@@ 1. Item appears at start game. --@@ --@@ Last Update:2008/02/05 --@@ Maked by Nyagicha --@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ------------------------------------------------------------------------ --- Name : ngclordcheck --- IN : none --- OUT : none --- Trigger : bind_stalker.script/actor_binder:net_spawn/before return. --- Description : Trigger is loading. If first load, item appears. ------------------------------------------------------------------------ function ngclordcheck() -- If flag of "game start" is TRUE? if (xr_logic.pstor_retrieve(db.actor, "x_first_run", true) == true) then -- ADD Items alife():create("wpn_rpg7", db.actor:position(), db.actor:level_vertex_id(), db.actor:game_vertex_id(), db.actor:id()) alife():create("ammo_og-7b", db.actor:position(), db.actor:level_vertex_id(), db.actor:game_vertex_id(), db.actor:id()) alife():create("ammo_og-7b", db.actor:position(), db.actor:level_vertex_id(), db.actor:game_vertex_id(), db.actor:id()) alife():create("ammo_og-7b", db.actor:position(), db.actor:level_vertex_id(), db.actor:game_vertex_id(), db.actor:id()) alife():create("ammo_og-7b", db.actor:position(), db.actor:level_vertex_id(), db.actor:game_vertex_id(), db.actor:id()) alife():create("ammo_og-7b", db.actor:position(), db.actor:level_vertex_id(), db.actor:game_vertex_id(), db.actor:id()) -- Lowers flag of "game start" xr_logic.pstor_store(db.actor, "x_first_run",false) end end

これでゲーム開始時に好きなアイテムを持たせる事ができますね。
もちろん、前に作った自作武器もオッケーです。
しかし、こう言ったプログラム的な事やってたらお約束なんだけど、どこかに何らかの記述ミスがあって、動かない場合もありえます。
そんな時にも、いきなり諦めて消したりしないでください。
一生懸命作ったのに動かなかったと言うような方も居る事でしょうし、次回はデバッグ(ミスの発見、修正)の仕方を書こうと思います。


今回作ったもの:start_item.zip

覚え書き(別窓で開きます)
この覚え書きの内容は次々に更新していくつもりです。
どんどん新しくなると思うので、この説明の時点と覚え書きの内容が必ずしも(時系列的に)一致するとは限りません。
常に覚え書きは最新版に書き換わっているものとお考えください。



1 / 2 / 3 / 4 / ex1 / 5 / 6 / ex2 / 7 / 8 / 9 / 10 / 11 / ex3 / 12 / 13 / 14 /..../ top / 関数 / 覚え書き / 倉庫
ニャギ茶★SPOT --工作部屋--