Celowin - パート 4: ユーザー定義イベント

前置き


この一連のレッスンの目的は、全くの初心者をプログラミングの世界に誘い、モジュールを作成するためにNWNのスクリプトをどのように使うかを教えることです。初めの 部分のレッスンはとても基本的なものになると思われるので、何かのプログラムを書いたことがある人は飛ばしても構わないでしょう。これらのレッスンの最終目的は、プログラムと聞いただけで身震いするような人々でも学べる場を提供することです。


これらのレッスンをフォーラムに掲示したり、印刷したり、修正を加えることは大いに構いません。しかし、その際には私が作成したということをどこかで触れて下さい。良い悪いを含めて、これらのレッスンについて何かコメントがあれば私に送って下さい。Celowin.


これらのレッスンは、オーロラツールセットをある程度触っている人を想定して書かれています。これらのレッスンにおいて解りにくい部分があるという意見が多数寄せられれば、必要な部分をさらに詳しく説明することも考えています。


レッスン3の整理


今までこの一連のレッスンを読んできた人の中で鋭い人は、レッスン3で作ったガードのスクリプトは「まあまあ」ではあるけれども、我々が望むような行動を正確にはしていないことに気づいているでしょう。 ガードは、友好的な時はいつでも2回挨拶をします。注意深い人だけが気づくと思いますが、ここで問題を解決しておいた方がいいでしょう。加えて、別の概念について説明する機会としても利用したいと思います。


まず、ガードが何故そのそうな行動をしたのかを正確に把握し、それをOnPerceptionスロットのスクリプトに要約する必要があります。OnPerceptionスロットのスクリプトが実行される要因としては、以下の4つがあります。

したがって、NPCはPCを見て、かつPCの音を聞いたのでスクリプトが2回実行されたのです。重大なことではありませんが、どっちにしろ我々が望んだ行動ではありません。


この問題を次の方法で解決してみましょう。何かを見るときだけ、ガードに反応させます。


if文を入れ子にすることで、このことを実現できますが、既にスクリプトが少し複雑になっています。代わりに、スクリプトに2つのことを同時にチェックさせることにします。つまり、気づかれたオブジェクトがPCで、かつ視認されたということを確認させます。両方の条件が満たされる時だけ、後の処理を行います。


条件に演算子「&&」(シンプルに 'and'と呼びます)を追加することで可能です。条件に用いられる場合、「&&」は、2つの条件を繋ぎ合わせます。両方の条件がtrueの時だけ、全体の条件がtrueとなります。スクリプトを以下のように修正するだけです。

if (GetIsPC(oSeen))

if (GetIsPC(oSeen) && GetLastPerceptionSeen())

に修正します。そうすると、スクリプトは、PCがガードに気づかれた時、しかも、他の方法ではなく視認によって気づかれた時だけ処理を実行します。


GetLastPerceptionSeen関数は、視認によって気づかれた場合は、TRUE、そうでない場合はFALSEを返す関数です。


今回は例を示すつもりはありませんが、条件を結び繋げる演算子として「&&」の他に「||」があります。「||」は「or」と読み、いずれかの条件がtrueであれば、trueであるということを意味します。これは、ガードがPCを攻撃する理由が複数ある場合に利用できるかもしれません。(例えば、PCがリングを持っていない場合、PCが村長の首を持っている場合、いずれかの条件が満たされればガードがPCを攻撃するとか)



告白


ここで、私はこれまでのレッスンで嘘をついていたと認めなければなりません。私は何回も、実際のモジュール作成を想定したスクリプトの書き方をしますと言ってきました。しかし、これまで書いてきたスクリプトは、私が最終的に書く形ではないということを白状しなければなりません。


問題は、我々が作ったNPCが大抵の刺激に無反応であるということです。我々がこれまでに作成したガードは、スタートしたらその場に立っているだけで、現実的な行動をしているとは到底言えません。興味があれば次のことを試してみて下さい。ガードのHPとレベルを増強して、モジュールをスタートします。リングを拾って近づくと、ガードは友好的に接するでしょう。それから、ガードを攻撃します。ガードは攻撃されるがままに、その場に立っているだけでです。これは、大抵のNPCに望む行動とは決して言えません。


このようなNPCとなってしまった理由は、BioWareによって丹念に作られたデフォルトのスクリプトを削除したためです。デフォルトのスクリプトには、ほとんどのケースにおいて我々が求めると思われる便利な「標準」の行動が定義されています。 (実際、このレッスンで作成するNPCの中に、デフォルトのスクリプトを削除するものもありますが、他についてはデフォルトのスクリプトを使用します。)


では、どのようにしてデフォルトのスクリプトを削除せずに我々独自の行動を定義すればいいのでしょうか? それには、「OnUserDefined」スロットのスクリプトを使用します。上手く使えば、OnSpawnのスクリプトを少し修正し、大部分のスクリプトをOnUserDefinedスロットのスクリプトに書くことで、他の全てのスロットのスクリプトを利用することができます。


ここまで、ガードの作成に多くの時間を費やしてきました。先に進んで、あるべき姿に修正してみましょう。

モジュールをスタートすると、今度はガードがよりリアルな行動をするでしょう。ガードは、我々が書いたスクリプトに従って反応しますが、今回は他の刺激に対しても反応します。ガードを攻撃すれば、反撃してきます。他にも数多くの行動がスクリプトに書かれていますが、それらの多くは内密に行われており、通常は決して気づくことはないでしょう。


では、実際に何を行ったのでしょうか? OnSpawnスクリプトの「//」を削除するということは、何かの「コメントアウトを外す」ということです。「//」の後のスクリプトは全て無視されると言うことを思い出して下さい。つまり、BioWareはOnSpawnにちょっとした「オプション」を設定しており、その前に「//」をつけることで処理されないようにしたのです。


しかし、今回は処理させたいので、「//」を削除することで「その行を処理して欲しい」と伝えています。


では、その「復活した」行は実際に何を行っているのでしょうか? それは、「OnPerceptionスロットのスクリプトを実行するときに、OnUserDefinedスロットのスクリプトも実行する」ということです。


より鋭い人は、「NPCに複数の新しい行動をさせたい場合は?つまり、OnPerceptionOnHeartbeatにおいて、特別な行動をさせたい場合はどうすればいいのでしょうか?」と思うでしょう。


それは可能ですが、少し追加の作業が必要です。スクリプトを単純にするために、NPCの行動も単純にしましょう。6秒毎に「退屈だ」と言い、PCを見たときにお辞儀をするNPCを作成してみます。



Q&A


GetUserDefinedEventNumberとは何ですか?

この数字には注目する必要があります。BioWareはとてもかしこい事をしました。それぞれの異なったスロットからユーザ定義のスクリプトを呼べるようにしただけでなく、呼ばれた時にそれぞれ別々の数字を情報として渡すようにしました。 よって、最初の行で行っているのは、スクリプトがHeartbeat (1001)、または、Perception (1002)のどのスロットから呼ばれたのかを調べています。

switch命令とは何ですか?

ここでは全て詳しく説明するつもりはありません。 基本的に、switch命令は与えられた整数に対して、その数字が含まれている「case」の行に「ジャンプ」します。よって、nCalledByが1001であれば、スクリプトは「case 1001」の行にジャンプします。そして、break文までの間にある処理を実行します。

書式上の注意: switch命令は、{  }で囲まれた部分の処理だけを実行します。また、if文のようにswitchの行の後ろにはセミコロンは必要ありません。

私は、OnUserDefinedスロットのスクリプトを書くとき、例え1つのスロットからしか呼ばれなくても、いつもswitch文を使うようにしています。なぜなら、後で気が変わって複数のスロットから呼ぶようにするかもしれないからです。前もって計画を立てることを怠ったがために、スクリプトをいじくらなければならない羽目になるよりも、今後の可能性を考え準備しておいた方が良いと言えます。

おい、if文に{  } が使われていないじゃないか!

if文の処理が1行しかない場合は、{ }は必要ありません。 私にとっては、{ }があった方が良いと思うときもあれば、ない方が良いと思うときもあります。どちらにしろ、スクリプトをすっきりさせるためです。今回は、{ }があると邪魔だと思ってつけませんでした。

追加例


この時点で、あなたはスクリプトを大分扱えるようになっていると思います。 もうこのレッスンを完全な初心者のレッスンとは呼ぶべきではありません。なぜなら、あなたはもう完全な初心者ではないからです。 まだ、学ぶ必要がある関数や、テクニックは多くあります。しかし、今まで学んできたことを上手く組み合わせれば、クールなことがたくさんできます。


その例をこれから示したいと思います。スクリプトはちょっと複雑で、多くの設定、いくつかの新しい関数を使います。しかし、最後までやる価値はあると思います。

さて、これは本当に複雑なスクリプトです。 全てについて説明するつもりはありませんが、いくつかポイント的に説明します。


ローカル変数 DARTSTATEを使って、準備が出来てない時にはダーツを投げないようにしたり、ダーツを拾いに行く行動を邪魔させないようにしています。


ActionDoCommand関数は、驚くほど便利です。この関数は、アクションキューに配置されないコマンドを、キューに配置するように変更します。通常、スクリプトはSetLocalInt関数を見つけると、すぐにローカル変数を設定します。ここでのActionDoCommand関数は、キューに配置されたNPCの行動が全て終わるまで、スクリプトにSetLocalInt関数の処理を待つように伝えます。


CreateItemOnObject関数は、NPCのために新しいダーツを作成するために使われています。「nw_wthdt001」は、ダーツのブループリントで、最後の3はスタック数です。


他の部分は、スクリプトをじっくり追跡出来るときに確認して下さい。新しい関数がいくつかありますが、そのほとんどは説明的な名前です。いずれにしろ、分からないことがあればいつでも質問して下さい。





 author: Celowin, editor: Charles Feduke, additional contributor(s): Jenn Jimerson, Sergio Paschoal, JP team: katsu794
 Send comments on this topic.