Celowin - パート 5: 自己学習、非NPCを扱ったスクリプト

前置き


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


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


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


他の情報源


私は最近、スクリプトに関する数多くの質問を受けています。もし私に十分な時間があれば、喜んで質問者一人一人に対してアドバイスしますが、いつも全ての質問に対して回答できるわけではありません。したがって、あなたが必要としている情報が得られる場所について、少し説明したいと思います。正直なところ、90%以上の質問は以前にもされており、後はその答えが何処にあるかを探すことだけが課題といえます。


答えを探すべき場所は、主に以下の4つがあります。順番は特にありません。

それぞれについて、どのように利用するのか少し説明しましょう。[もちろんNWN Lexiconも含まれますが、既に知ってますよね。]


スクリプトFAQ


「よくある質問(Frequently Asked Questions)」ということでそう呼ばれています。ここで編集されているドキュメントには、スクリプトにおける最も困難な問題や、一般的な人々が知りたいような簡単な問題に対する回答が数多くあります。実際、何か問題があれば、まずここを見た方がいいです。


ドキュメントが、あなたの抱えている問題にすぐ適用できないようであっても、読むのをためらわないで下さい。ここにあるドキュメントをいずれ必要とする日が来るでしょう。NWNスクリプトは、学べば学ぶほど分かるようになってきます。しばしば、私が抱えている問題と全く関係ないドキュメントを読んでいるときに、参考になることを見つけることもあります。それは、今まで知らなかった関数であったり、私を興奮させるほどの賢い変数の使い方であったりします。


FAQのドキュメントは様々な作者の手によって作られているので、あなたの理解度を超えているものもあるでしょう。そんな場合は、さらに学習して、再び読んでみて下さい。現在のあなたのレベルであっても、多くの情報が得られることに驚くかもしれません。試してみて下さい。


NWNスクリプトフォーラム


コンスタントに質問が寄せられ、熱心な人々がそれに答えている場所です。私は、あなたが抱いている質問に対する回答は既にあると思うと言いました。


では、どのようにして答えを見つければいいのでしょうか?それには、サーチツールから始めるのが良いでしょう。完璧に信頼はできませんが、試してみる価値はあります。


サーチツールで答えを見つけられない場合、私はフォーラムの最初の2、3ページに答えがないか探します。同じ質問がひっきりなしにされているので、最初の3ページ以内を探すことは、あなたが求めている答えを探す良いチャンスだと言えます。


これらの方法でも駄目だった場合のみ、質問を投稿します。注意:あなたが問題を解決するために努力していることを伝えれば伝えるほど、みんなはあなたを助けようと努力してくれます。例を挙げましょう。

みんながそうするとは限りませんが、私は2人目の人を手助けしたいと思います。1人目の人は、問題解決のために15時間費やしたかもしれませんが、文章からはそれを知ることはできません。この文章では、「全然学ぶ気はないので、あなたが私のためにやって下さい。」と考えていると思われても仕方ありません。一方、2番目の人は、努力していることがすぐにわかります。問題が何であるかを簡単に知ることができますし、詳しく書かれているので、スクリプトのコードを書いてあげることもできるかもしれません。


最後にフォーラムから情報を得たときの注意について述べます。必ずしも必要ではありませんが、以下のようにあなたのスクリプトを解決する手助けとなった人について、コメントの中で触れるのが礼儀だと言えます。


// OnUserDefinedスロットに設定するスクリプト: tm_guard_ud
// 武器を振り回しているPCを見たときに、ガードが唸って警告します。
//
// 最終更新日: 7/11/02
// 作成者 The Great Gatsby
// このスクリプトの修正において、NWNスクリプトフォーラムのCelowin氏の情報が手助けになりました。

モジュールをプレイして遊んでいるだけの人々は、このコメントを決して見ることはないでしょう。しかし、スクリプトを覗いている人々は見る可能性があります。その人の本名が分からない場合は、あなたが知っているユーザネームでもいいでしょう。上で行っているように、どこからその情報を得たのかについて触れるのも良いです。


公式キャンペーン


私は何度も「公式キャンペーンの何章でやっているような、何々のことをするにはどうすればいいのですか?」というような質問を見ます。このようなタイプの質問は、怠慢というよりもむしろ公式キャンペーンのモジュールの中身を見る知識に欠けているのではないかと思っています。


従って、以下に公式キャンペーンのモジュールをツールセットで開く方法について述べます。

  1. Neverwinter Nightsフォルダの中に、nwnというフォルダがあります。このフォルダに公式キャンペーンのモジュールが入っています。
  2. 中身を見たいファイルをmodulesフォルダにコピーします。
  3. ファイルの拡張子をnwmからmodに変更します。 (Windowsの設定によっては、「mod」の拡張子が消えるかもしれません。)
  4. 名前を変えたファイル上で右クリックをして、「プロパティ」を選択します。「読み取り専用」のチェックを外して、OKをクリックします。
  5. このようにして、公式キャンペーンのどの章でもツールセットで開くことができます。

BioWareが作成した様々な設定からとても多くの情報が得られます。どのようにしてNPCに会話を始めさせるか知りたいですか?序章のパーベルのスクリプトを見て下さい。どのようにしてPCを攻撃するクリーチャーを召還させるか知りたいですか?序章のアリベスの会話を見て下さい。どのようにしてNPCが特定のNPCを発見するまでPCについてきて、その後消えるようにするか知りたいですか?第1章の戦士かメイドのスクリプトを見て下さい。このように、公式キャンペーンには全ての種類の仕掛けが設定されています。そして、ツールセットで開くことによってそれらを熟読できます。


私は個人的には、何か分からないことがあったらまず、序章を見ています。それは、ただツールセットで読み込む時間が短いというだけの理由ですが。確かに、公式キャンペーンの他の章には、序章では使われていない数多くの仕掛けがありますが、序章だけでもかなり多くの有用な情報が含まれています。


特に複雑なことを行っている場合に該当しますが、中には追跡するには難しいスクリプトもあります。しかし、スクリプトをどのように修正すればいいのか分からなくても心配しないで下さい。それは、現在のあなたのスクリプト作成レベルを超えているからです。他のスクリプトを勉強して、再び取り組んでみて下さい。


ここで心に止めておいて欲しいことを少し述べます。大抵、モジュールを作り始めた人は、すぐに「格好いい部分」を作りたがります。しかし、不運にも、そのためにはいつも複雑なスクリプトが必要になります。走り出す前に、まず歩かなければなりません。例え退屈なほど単純なスクリプトであっても、そこから始めて、少しずつ新しく難しいことに取り組むべきです。私は以前に、最初のスクリプトにも係わらず、6本のレバー、12個の宝石、複数の目的地を持ったポータル、そして、数多くの視覚エフェクトを実現したいと書いていた人を覚えています。彼がやりたいことは可能でしょうし、自分が何をやっているか分かっているのならばそんなに難しくもないでしょう。しかし、初めてのスクリプトにしては、悪夢の様に複雑なものになるでしょう。


ツールセット

ツールセットがスクリプトを学習する手段になるとは少し変に思うかもしれません。しかし、正直なところ、私が最もスクリプトの学習に活用しているのはツールセットです。基本的なスクリプト作成を理解していれば、ツールセットのドキュメントから多くの情報を得ることができます。


まず、スクリプトを編集している時、ウィンドウの右部分に関数の膨大なリストがあるのに気づくと思います。それらの関数をクリックすると、ウィンドウの下にその概要が表示されます。ほとんどの関数は説明的な名前をしていますので、当てはまりそうな関数をリストから捜して、その関数がどのような機能を持っているのか見ることができます。


このレッスンの後半で紹介するスクリプトから、その例を示してみましょう。PCを異なったロケーションにテレポートさせたいとします。しかし、どんな関数を使えばいいのか頭に浮かばないので、関数リストの中を捜し始めます。


PCをロケーションに移動させたいので、ActionMoveToLocation関数がそれに合っていそうです。しかし、我々が以前にMove関数を使った時には、テレポートというよりはむしろ歩いて移動という感じでした。そこで、リストのすぐ側にActionJumpToLocation関数というのがあるのですが、まだゲーム内で使ったことがありません。そこで、関数を確認してみましょう。関数名をクリックすると、ウィンドウの下に以下の内容が表示されます。

// 対象はlLocationにジャンプします。(エリア間の移動も可能)
// lLocationが無効なオブジェクトの場合、何も起きません。
void ActionJumpToLocation(location lLocation);

最初の2行は関数に関する概要のコメントです。最初の行は我々がしたいことに少し似ています。2行目は、関数の実行に関しての特別な情報です。それでは3行目は何でしょうか?


3行目は、スクリプトの中で関数をどのようにして使うかについての重要な情報が書かれています。レッスン1を思い出して下さい。voidは、その関数は何の「出力」も返さない、つまり、ある処理はするけれども、どんな種類の答えも返さないということを示していると説明しました。その後の部分の関数名は既に知っています。しかし、今回はその関数にどんな入力を与えればいいのかを示しています。この関数では、ロケーションを入力情報として与える必要があります。


また、関数のリストからはもっと手助けになりそうなことが学べます。まず、これは「Action」関数ですので、テレポートするというアクションをキューに配置すると思われます。もし、すぐにテレポートさせたければ、代わりの関数が必要になります。JumpToLocation関数ではどうでしょうか?


次の問題は、関数に与えるロケーション情報をどのようにして得るかです。「Get」から始まる関数のほとんどは、ある答えを返します。よって、その関数群を見てみましょう。GetLocation関数というのがありますね。関数名をクリックすることで、その使用方法に関する情報が表示されます。


ふぅむ。JumpToLocation関数には移動させたいオブジェクトの入力情報がありませんね。この関数は、少し分かりにくいです。使い方を見てみて下さい。このレッスンの後半のスクリプトでも説明するつもりです。


上に書いたように、ツールセットを使えば恐ろしいほど多くの関数の使い方を学ぶことができます。また、退屈な時には時々、関数のリストの中から興味を持った物をランダムにクリックしてみることをお奨めします。今まで知らなかった役に立つ関数を発見できるかもしれません。


NWN Lexicon


この記事を読んでいること自体がチャンスですよね。他に何か言う必要がありますか?


別の話題


スクリプトを学べる場所については十分に説明したので、次に進んでスクリプトを作ってみましょう。今までのレッスンでは、NPCを取り扱ったスクリプトを作成してきました。そこで、今までとは違ったものを作りましょう。ここでは、配置用オブジェクトを扱ったスクリプトと、トリガーを扱ったスクリプトを作成してみましょう。


一般的には、取り扱うオブジェクトが何であっても方法は同じです。スクリプトを呼び出す「トリガー」は様々なものがあるので、OBJECT_SELFが何を参照しているのかに気をつける必要がありますが、基本的な構文や関数は全く同じです。


これまでのテストモジュールでは、もしあなたがこれまでのレッスンに沿って作っていれば、繋がれていない2つのエリアが存在しています。必要であれば、いずれのエリアにもスタート地点を設定できて便利ですが、それではちょっとつまらないです。エリアトランジッションを使えば、2つのエリアを繋ぐこともできますが、それもつまらないです。そこで、もっと楽しいことをしましょう。


次のようにしてはどうでしょうか 。一方のエリアに「オフ」状態の2つのレバーを作ります。両方のレバーが「オン」の状態になった時に、PCをもう一方のエリアに転送するポータルを出現させます。ステップ・バイ・ステップで作ってみましょう。


  1. ツールセットを起動して、モジュールを開きます。そして、ポータルを置きたいエリアを開きます。(恐らく大きい方のエリアになるでしょう)
  2. 「配置用オブジェクト」の「入れ物・スイッチ」から「床のレバー」を選択し、レバーを2つ配置します。
  3. 一方のレバーのタグをLEVER1とし、もう一方のタグをLEVER2にします。
  4. ポータルを出現させたい場所にウェイポイントを配置し、タグをTM_INWPにします。.
  5. ウェイポイントの回りにトリガーを作成します。(クリックして頂点を配置していき、閉じたい所でダブルクリックするとポリゴンが閉じられます) トリガーのタグをPORTTRIGにします。
  6. 以下のスクリプトをそれぞれのレバー(両方のレバーとも同じスクリプト)のOnUsedスロットに設定します。スクリプトをtm_lever_ouという名前で保存します。
    // OnUsedスロットに設定するスクリプト: tm_lever_ou
    //
    // このスクリプトは、LEVER1、LEVER2という名前のレバーを設定します。
    // 始めは両方のレバーともオフの状態です。両方ともレバーがオンになると、タグTM_INWPのウェイポイント上に
    // ポータルが出現します。そして、タグPORTTRIGのトリガーがオンになります。
    // 
    //
    // 作成者 Celowin
    // 最終更新日: 7/12/02
    //
    void main()
    {
      int nUsed1 = GetLocalInt(OBJECT_SELF, "LEVER_STATE");
      int nUsed2;
      //
      // nUsed1とnUsed2は、2つのレバーの状態を一時的に保存するために使われます。
      // 0がオフで、1がオンです。
      // LEVER_STATEは、レバーの状態を保管するための変数です。
      // それぞれのレバーが自分自身のLEVER_STATEを持っています。
      //
      if(nUsed1 == 0)
      {
        ActionPlayAnimation(ANIMATION_PLACEABLE_ACTIVATE);
        SetLocalInt(OBJECT_SELF, "LEVER_STATE", 1);
        nUsed1 = 1;
      }
      else
      {
        PlayAnimation(ANIMATION_PLACEABLE_DEACTIVATE);
        SetLocalInt(OBJECT_SELF, "LEVER_STATE", 0);
        nUsed1 = 0;
      }
      nUsed1 = GetLocalInt(GetObjectByTag("LEVER1"), "LEVER_STATE");
      nUsed2 = GetLocalInt(GetObjectByTag("LEVER2"), "LEVER_STATE");
      if ((nUsed1 == 1) && (nUsed2 == 1))  // 両方のレバーがオンの状態か?
      {  // もしそうであれば、ポータルを作成しトリガーにテレポートさせる準備ができていることを伝えます。
        object oPortalSpot = GetWaypointByTag("TM_INWP");
        CreateObject(OBJECT_TYPE_PLACEABLE,"plc_portal",GetLocation(oPortalSpot), TRUE);
        SetLocalInt(GetObjectByTag("PORTTRIG"), "READY", 1);
      }
    }

    このスクリプトについての説明が必要でしょうが、大部分はコメントで分かると期待しています。全体の説明は、設定が全て終わってからにしたいと思います。

  7. 次は作成したトリガーの設定に移ります。
  8. トリガーのプロパティを開いて、スクリプトタブを選択します。
  9. 以下のスクリプトを「OnEnter」スロットに設定し、tm_portal_en という名前で保存します。
    // OnEnterスロットに設定するスクリプト: tm_portal_en
    //
    // ポータルがオンの状態でPCが入ると、PCをウェイポイントTM_OUTWPにワープさせます。
    // 
    //
    // 作成者 Celowin
    // 最終更新日: 7/12/02
    //
    void main()
    {
      // 必要な一時変数を設定します。
      object oPC = GetEnteringObject();
      object oDest = GetWaypointByTag("TM_OUTWP");
      int nReady = GetLocalInt(OBJECT_SELF, "READY");
    
      // チェック: オブジェクトがPCで、かつ、ポータルがオンの状態か?
      // もしそうであれば、PCを出口にジャンプさせます。
      if ((nReady == 1) && (GetIsPC(oPC)))
        AssignCommand(oPC, JumpToLocation(GetLocation(oDest)));
    }
  10. これでほとんど設定は終わりです。「OK」を押してトリガーのプロパティを閉じます。
  11. 次に、テレポート先としたいもう一方のエリアを開きます。
  12. ウェイポイントを作成して、タグをTM_OUTWPにします。
  13. 全てを保存して、ツールセットを終了します。
  14. モジュールを開いて、テストしてみて下さい。

ポータルに設定したスクリプトはとても単純です。1つだけ説明した方が良いと思うのは「AssignCommand」関数です。この関数は基本的に、何かにアクションをさせるために使われます。今回の場合、PCにJumpToLocation関数のアクションをしてもらいたいので、PCにコマンドを割り当てています。この関数は、あらゆる場面で利用できる便利な関数です。


レバーに設定したスクリプトは、本当に複雑です。キーとなる部分は、レバーの「オン」「オフ」を保管するローカル変数の設定だけです。しかし、いくつかの点について説明する必要があると思います。スクリプトの中でnUsed1を2回使っているのは、格好悪いといえるかもしれません。一つ目は、スクリプトを呼び出したレバーの状態を得るために使っており、どちらのレバーからも使用されます。2つ目は、LEVER1 の状態を得るために特別に使われています。プログラマーの中には、私を大きい杖でぴしゃりと打って「別々の変数を使うべきだ」と言う人もいるでしょう。何故そうしたかですって?私はそういうプログラマーに対して舌を出してべーっと言えます。真剣な話、2つの変数は限りなく同じ物に近く、また、スクリプトも単純なので、別の変数を使う必要を感じませんでした。


他に触れておいた方がいいことは、CreateObject関数です。このためには、「タグ」と「ブループリントResRef」の違いを理解する必要があります。ゲーム内のオブジェクトはタグを持っています。既に作られているオブジェクトに対しては、タグを通してそのオブジェクトを特定すことができます。しかし、もしオブジェクトがまだ作られていなければ、そのオブジェクトはタグを持つことができません。実際の名札を想像して見て下さい。まだ存在しない物には名札を付けることはできません。


代わりに、「ブループリント」を使います。それは物を作成するための設定図であり、識別するために特有のラベルを持つ必要があります。それが「plc_portal」です。それは、ポータルオブジェクトに与えられたブループリント「resource reference(リソース参照)(ResRef)」です。注意: 私はポータルのResRefを見つけるために、エリアに一時的にポータルを作成しました。そして、ポータルのプロパティにからResRefを見つけて、一時的に作成したポータルを削除しました。パレットのオブジェクト一覧から右クリックして、オブジェクトをコピーしないで下さい。そうすると、オリジナルのブループリントの新しいResRefが作成されてしまいます。見分け方としては、標準のオブジェクトのResRefは、「001」で終わることはないということに注意して下さい。.


これである程度は分かったと思います。少し拡大して説明したつもりですので、あなたはきっと分かっているはずです。


追加演習


ここら辺で、先生は退場することとして、自己学習の時間にしましょう!必ずやってもらいたいとは言いませんが、これまでに作ったものを拡張することで、さらに学習を重ねる価値はあると思います。


では、次のことに挑戦してみて下さい。: 現段階では、両方のレバーをオンにすることでポータルが出現します。確かにフェアです。1つのレバーがオフの状態で、もう1つのレバーをオンにするとポータルは出現しません。これも納得できます。しかし、両方のレバーをオンにしてポータルを出現させ、1つのレバーをオフにするとポータルはまだ現れたままです。これでは余り意味がありません。可能であれば、スクリプトを修正して、いずれかのレバーをオフにしたときにポータルが消えるようにしてみて下さい。もちろん、再びレバーをオンにするとポータルが再出現する必要があります。


別の拡張: 現段階では、ポータルは「一方通行」です。これを両方通行にしてみて下さい。


最初の課題は、これまでのレッスンをやってきた人にとってはちょうど良いくらいの難易度で、2番目は簡単でしょう。


いずれにしろ上手くできることを祈ってます!





 author: Celowin, editor: Charles Feduke, additional contributor(s): Iskander Merriman, Mads Eibe Sørensen, JP team: katsu794
 Send comments on this topic.