安永ノリカズのゲーム制作&Javaサンプル集

#06 2号の設計図

会話の方向性として「選択肢の動的生成」について検討しています。

2001年02月22日更新

ゲッターロボ?

テストプログラム第2弾「魔法の森・プロトタイプ2号」は、会話シミュレーターでいこうと思います。 NPCとのコミュニケーション、それも、多様な会話のやりとりは、 なんといっても、このゲームの肝となる部分ですからね。 出来るだけ早く手をつけておくべきでしょう。
今回のコラムでは、新しいプログラムの発表はありませんが、 会話実装におけるコンセプトは明確にしておきたいと思います。 たぶん次回コラムで、なんらかの形になったものをお見せできるんじゃないでしょうか。

今後の制作方針として、機能ごとにテストプログラムを作っていくことにしました。 1号はもう一段改良して、人工生命っぽい行動シミュレーターとして完成させる予定で、 3号では、仕事と生産、物流や貨幣にスポットをあてた経済社会シミュレーターを作ろうと思ってます。
そして、昔のロボットアニメみたいに、「1号、2号、3号、合体!」てな具合で、 正式バージョンへ展開するという段取りを考えてます。 ひょっとしたら、4号、5号の登場もあるかもしれないけど……
ま、そのへんはおいといて、今回の会話シミュレーターの説明と参りましょう。

 ページトップへ戻る

会話データの定石

会話シミュレーターを作るとなると、いきなり「こんなセリフも言わせたい」と、 最終的なアウトプットに目が行きがちですが、ここはちょっと気持ちを落ち着けて、 根本的なデータ構造から考えてみようと思います。やっぱ、基本が大事ですね。
実は、今回「どうせテストプログラムだし」と思って、中途半端にプログラムの設計をしたら、 案の定、途中で行き詰まってしまいました。 その行き詰まりというのは「会話の多様性への見通しが立たない」というものです。

会話データは、一般的なコマンド選択型のアドベンチャーなどで使われる、 [選択肢テーブル][メッセージテーブル]の構成で充分だと、最初は思ってました。
探偵もののアドベンチャーゲームを例にすれば、次のような形のデータ構成です。

[選択肢テーブル]
No.選択1選択2選択3
選択肢01会話01  
選択肢02会話02会話03会話04
選択肢03会話05  
選択肢04会話07  
選択肢05会話09会話10会話11

[メッセージテーブル]
No.メッセージジャンプ先
会話01はじめまして、私は探偵の寺野修豪と申します会話12
会話02あなたの名前を教えてください会話13
会話03職業は?会話14
会話04昨夜の9時頃、どこにいましたか?会話15
会話05そのとき何か物音が聞こえませんでした?会話17
会話07たびたびすみません、探偵の寺野です会話18
会話08実は、ある女性とあなたの関係を調べてまして……会話19
会話09片岡真樹をご存じですよね?会話20
会話10この写真に見覚えは?会話21
会話11以前、チェックボックスという会社に勤めてましたよね?会話22
会話12は、はじめまして……終了
会話13岡部広子といいます終了
会話14ファッションデザイナーをしています終了
会話15事務所で仕事をしていました終了
会話17すみません、もう帰ってください!終了
会話18私に何の用ですか?会話08
会話19女性?終了
会話20いいえ、はじめて聞く名前です終了
会話21全くありません終了
会話22はい、2年ほど前まで終了

ゲームの中で、メッセージが表示される流れを説明すると……

1.フラグやパラメータを参照し、選択肢をテーブルから検索。
「容疑者とはじめて会うなら[選択肢01]」「容疑者に1回目の聞き込み中なら[選択肢02]」 など、どの状況でどの選択肢を表示するかを、スクリプトなどで記述しておきます。
例えば、容疑者に1回目の聞き込み中なら[選択肢02]なので、

[会話02]『あなたの名前を教えてください』
[会話03]『職業は?』
[会話04]『昨夜の9時頃、どこにいましたか?』

と、コマンドウィンドウに表示され、プレイヤーの選択を待つことになります。

2.ユーザーが選択したメッセージを表示。
この場合、そのメッセージのジャンプ先に[終了]が来るまで続けて表示します。
例えば、[会話07]が選ばれた場合はこうなります。

[会話07]『たびたびすみません、探偵の寺野です』→[会話18]
[会話18]『私に何の用ですか?』→[会話08]
[会話08]『実は、ある女性とあなたの関係を調べてまして……』→[会話19]
[会話19]『女性?』→[終了]

そして、また1に戻ります。
実際には、フラグの操作などが加わるのですが、 メッセージの持ち方としては基本的に、こういう形になってます。

ワーネバでは、この方式の双方向型を採用しました。 ジャンプ先に会話番号ではなくて、選択肢番号を持っている形です。 これによって、PCとNPCが相互に、会話を選択しあって話題を進める形を実現しました。

この方式の利点は、まず管理しやすいこと。 構造がシンプルなので、データの作成/管理も、コーディングも比較的容易です。 そして、検索も速いです。 メッセージの総数が何百何千となっても、ツリー状の条件分岐にしておけば、 少ないステップ数で目的の選択肢に到達できます。 当初、この構造を採用しようと思ったのは、これらの利点を考えてのことでした。

 ページトップへ戻る

ダイナミックな会話だねぇ

とまあ、シンプルに管理できる[選択肢テーブル型]ですが、単純さゆえに、 会話がパターン化してしまうという問題点もあります。 相手に話しかけた場合、かけられた場合、いずれにせよ、絞り込まれた選択肢からしか選ぶことはできません。 NPCの意外性のある反応などは生まれなくなってしまいます。

もちろん、「大量の選択肢の組み合わせテーブルを用意する」という対処方法も考えられますが、 選択肢の追加には、選択肢検索ルーチンの修正を伴うことから、 作業量、メンテナンス性の面で、現実的ではなさそうです。 将来の、臨機応変なユーザーニーズへの対応や、サーブレット化でのリアルタイムゲームデータ更新まで考えると、 データの独立性の低さは致命的な問題です。

そこで、「動的に選択肢を生成する」方向性を模索することにしました。

コンピュータの世界ではプログラム実行時に行うことを、「動的(dynamic)」と呼びます。 それに対応するのが、「静的(static)」で、プログラム実行前に、あらかじめ用意されているものを指します。 さっきの会話の例で言えば、[選択肢テーブル]は、前もって作っておくわけですから、静的ですね。 動的に選択肢を生成するというのは、 会話データにどの状況で発生するかの情報を付加しておいて、 その状況にマッチする会話の全て、もしくは上位何個かをそのつど選択肢としてリストアップする方式です。

メッセージデータは次のような構成になります。

[発生条件つきメッセージテーブル]
番号発生条件メッセージ
会話01[雨の日あいさつ]雨ってヤだよね
会話02[晴の日あいさつ]いやあ、いい天気だね
会話03[元気がある]ラン、ラララン〜
会話04[元気がない]ふー……
会話05[相手がほうきに]どこかにお出かけかい?
会話06[相手が歩いている]お散歩中ですか?
会話07[くろねこ広場にいる]新しい魔法、覚えた?
会話08[どっきり酒場にいる]今日は、一緒に飲もうぜ!
会話09[雨の日の答え]ホント、気持ちが滅入るよな
会話10[晴の日の答え]気分がスカッとするよね
会話11[相手が元気よさそう]機嫌がよさそうだね
会話12[相手が元気悪そう]どうしたの、元気ないじゃない
会話13[虹色の川に向かう]どうだい、虹色の川まで遊びにいかないか?

そして、例えば、以下のような条件で会話が発生したとします。

 天候:晴
 場所:くろねこ広場
 自分:歩いている、元気がある
 相手:ほうきに乗っている、元気ない、虹色の川に行く途中

このとき、自分の選択肢は、発生条件に合致するものがリストアップされ、次のようになります。

【自分】
[会話02][晴の日あいさつ]『いやあ、いい天気だね』
[会話03][元気がある]『ラン、ラララン〜』
[会話05][相手がほうきに]『どこかにお出かけかい?』
[会話07][くろねこ広場にいる]『新しい魔法、覚えた?』

で、「いやあ、いい天気だね」を選んだときの相手の反応は、先の条件に、 [晴の日のあいさつを受けた]という情報を加えてリストアップされた

【相手】
[会話10][晴の日の答え]『気分がスカッとするよね』
[会話04][元気がない]『ふー……』
[会話06][相手が歩いている]『お散歩中ですか?』
[会話07][くろねこ広場にいる]『新しい魔法、覚えた?』
[会話13][虹色の川に向かう]『どうだい、虹色の川まで遊びにいかないか?』

の中から選択されることになります。 つまり、こちらのあいさつに答えてくれるだけでなく、 溜め息をついたり、遊びに誘ったり、という反応が返ってくる場合もあるわけです。

また、状況が変わって、

 天候:雨
 場所:どっきり酒場
 自分:座っている、元気ない
 相手:座っている、元気ある

の条件で会話が発生したら、

【自分】
[会話01][雨の日あいさつ]『雨ってヤだよね』
[会話04][元気がない]『ふー……』
[会話08][どっきり酒場にいる]『今日は、一緒に飲もうぜ!』

「ふー……」を選択すると

【相手】
[会話01][雨の日あいさつ]『雨ってヤだよね』
[会話03][元気がある]『ラン、ラララン〜』
[会話08][どっきり酒場にいる]『今日は、一緒に飲もうぜ!』
[会話12][相手が元気悪そう]『どうしたの、元気ないじゃない』

という具合になります。 たった10数個の会話でも、状況の変化で、何パターンもの話題の展開が考えらます。 これと同じことを、[選択肢テーブル型]で行おうとすると、 場合分けを考えるだけでも、気が遠くなりそうです。 選択肢の動的生成のほうが、柔軟性の面において、ずいぶん優れているといえます。

もちろんこの形式にも問題点は予想されます。

◆不自然な会話の発生
その状況に合致するセリフが適正にリストアップされないと、 話題の展開が不自然になる可能性があります。 多少のズレなら面白さと受け止めることもできるでしょうが、 あまりひどいのになると、バグとしか呼べませんからね。 主にデータ作成面でフォローしなければならないでしょう。

◆会話の無限ループ
お互いに、[終了]の情報を持った会話を選択しなかった場合、 延々と会話が続くことになります。 これもまた、「長話をした」と見なすことも出来るかもしれませんが、 ホントに終わらなかったら、ただのハマリです。 システム側で回数の制限を設けるなどの対処が必要でしょう。

◆検索の遅さ
これが一番致命的な問題かもしれません。 なにせ、会話の主導権が移るごとに、全メッセージの発生条件を解析して、 選択肢を生成するんですから。データ量と正比例して検索に時間がかかってしまう恐れがあります。 Java自体、決して速い代物ではないのにです。 選択肢の動的生成をする状況を限定するなど、何らかの高速化の手段が必要となるでしょう。
もしかすると、この会話検索が、街の最大人口を決定する要因になるかもしれません。

他にも未決定の事項として、 発生条件データのフォーマットの問題もあります。 指定パラメータとの相関度を定めた情報にするか、 簡単なスクリプトをバイトコード化して持たせて、真偽を返すようにするか、 正直、どうしたもんかと迷っている最中です。

とにもかくにも、会話シミュレーションのコンセプトは固まりました。 さあ、データをいっぱい用意してテストするぞ!! と意気込んではいるんですが、 世界設定を漠然としか決めてなかったんで、データが用意しにくくて……。 もう、難問だらけ(責任は自分にあるんだけど)です。
ただ、会話エンジンのパフォーマンスは、今後の制作に大きく影響しそうなだけに、 未決定事項をビシバシ片付けて、押し進めていくしかないっすね。

さあ、果たしてこの方向性で、2号はちゃんと動きだすんでしょうか?
そもそも、合体するのはいつなんだ? それまで、貯金は持つのか?
今回の内容は、専門的過ぎて分かりにくかったんじゃないか?
しかも、回を追うごとにコラムの文章が長くなってるのは、気のせいか?
確定申告ってどうやったらいいんだ?
反町隆史が松島菜々子と結婚……うらやましいぞ!

様々な問題をはらみつつ、開発は進みます。

 ページトップへ戻る