ブラウザベースのZPGで行うクエストジェネレーターについてお話したいと思います。
RPGでの自動ジョブ生成の問題は非常に古いという事実にもかかわらず、非常に原始的なオプションを除いて、そのようなジェネレーターの公に利用可能な動作バージョンはほとんどありません(ほとんどありません)。 このトピックに関してはあまり作業がありませんが、積極的にグーグルで調べれば、何かを掘り下げることができます。 したがって、このテキスト(およびジェネレーター自体、リポジトリへのリンクが記事の最後にある)が役立つことを願っています。
急いで: 受信したタスクの1つを視覚化します。
なぜ発電機が必要なのですか
ZPG-ゼロプレイヤーゲーム-プレイヤーが参加しないゲームで、最も近い人気のアナログはゴッドビルです。
ゲーム内のプレイヤーのキャラクター(ヒーロー)は完全に独立して行動し、もちろん彼の主な職業はNPCタスクを完了することです。
重要な点は、タスクが非線形であり、プレイヤーはヒーローがどのNPCを助けるか、どのNPCを傷つけるかを選択する必要があるということです。 「世界の運命」はこれに直接依存します(たとえば、多くの人がそれを害した場合、NPCはゲームを離れることができます)。
さらに、ヒーローには割り当て中の行動に影響を与える「キャラクター」があります(たとえば、特定のNPCを助けようとすることを示すことができます)。
主人公は、タスクを完了するためにのみ経験値を受け取ります。
これに基づいて、私たちは、常識に矛盾せず、プレイヤーが選択を行う前に考えることを要求する、興味深く複雑なタスクを作成するメカニズムを必要としていました。
ゲーム内のプレイヤーのキャラクター(ヒーロー)は完全に独立して行動し、もちろん彼の主な職業はNPCタスクを完了することです。
重要な点は、タスクが非線形であり、プレイヤーはヒーローがどのNPCを助けるか、どのNPCを傷つけるかを選択する必要があるということです。 「世界の運命」はこれに直接依存します(たとえば、多くの人がそれを害した場合、NPCはゲームを離れることができます)。
さらに、ヒーローには割り当て中の行動に影響を与える「キャラクター」があります(たとえば、特定のNPCを助けようとすることを示すことができます)。
主人公は、タスクを完了するためにのみ経験値を受け取ります。
これに基づいて、私たちは、常識に矛盾せず、プレイヤーが選択を行う前に考えることを要求する、興味深く複雑なタスクを作成するメカニズムを必要としていました。
さらに、「クエスト」の代わりに、説明に便利な「履歴」という用語を使用します(各クエストは、いくつかの規則によって制限されるストーリーであるため、ストーリージェネレーターについて話す方が合理的です)。
問題の声明
ストーリーに提出した要件は、次のように定式化できます。
- 非線形性(任意の数のフォークと終了オプション);
- ネスト(1つのストーリーは、ネストされたサブストーリーまたは連続したサブストーリーをいくつでも持つことができます);
- 整合性(ヒーローがどの方向に進んでも、歴史には常に終末があるはずです);
- 実現可能性(主人公は限られた時間内にあらゆる物語を通過することが保証されなければなりません);
- 一貫性(歴史は世界の状態やそれ自体と矛盾しないはずです);
- スケーラビリティ(任意の数のプレイヤーキャラクターまたはNPCがストーリーの参加者になることができます);
- 変動性(一般的には同じものであっても、ストーリーは互いに異なる必要があります)。
ストーリー自体の要件に加えて、ジェネレーターに直接いくつかの要件があります。
- 欠陥のあるストーリーが存在しない(ストーリーが生成される場合、すべての要件を満たす必要があります);
- 結果を視覚化する機能(視覚化しないと、開発は地獄に変わります)。
要件を決定したら、実際にそのようなクエストまたはストーリーが何であるかを決定する必要がありました。 その結果、次の定義に到達しました。
履歴は、有向非巡回接続グラフです。 ノードは、参加するオブジェクトの状態(状態要件)を履歴の特定の段階で説明し、エッジはこれらの段階間の可能な遷移を決定します。
この定義から、ゲームの制御下で与えられるステートマシンの形でストーリーを実現するというアイデアがスムーズに続きます。
そのため、ジェネレーターは、上記のプロパティを持つ世界の現在の状態に関する情報に基づいて履歴グラフを作成する必要があります。
次に、結果のグラフをゲームのロジックで解釈する必要があります。 ストーリーの現在の状態と予想される将来の状態に関する情報に基づいて、ヒーローまたは環境の行動に必要な変更を開始し、ストーリーを次の段階に移行するために必要なすべての要件を満たします。
インタプリタ自体は非常に簡単に実装されます(記事の最後に実装例へのリンクがあります)。
ストーリー構造
したがって、履歴はノードとエッジで構成されるグラフです。 各ノードには、履歴がノードに対応する状態になるように実行する必要がある要件のリスト(または必要に応じてチェック)があります。 要件は、特定の場所でヒーローを見つけること、または適切な金額を持つことです。
各ノードの「世界」の状態の要件に加えて、ストーリーがこのノードに表示されるときに実行する必要があるアクションのリストが追加されました。 もちろん、要件のある個別のノードとして配置することもできますが、これによりグラフ自体が大幅に増加し、開発者による分析が複雑になります。 この場合のアクションは、プレイヤーにメッセージを送信し、モンスターとの戦いを開始するか、ヒーローに報酬を与えることです。 同じアクションリストが、エッジに沿った動きの開始と終了に割り当てられます。
簡単な物語はこのようなものになります。
写真内のグラフの規則
- 灰色の結び目-物語の始まりと終わり。
- 紫色のノードは選択ポイントです。
- 緑のノードは通常のプロットポイントです。
- 赤いノード-条件付き遷移。
- ターコイズの輪郭-サブヒストリー;
- ノード内の暗い背景は、プロットのこのポイントに移動するために満たす必要がある状況の要件を示しています。
- 背景が明るいほど、プロットポイントへの移行直後に実行する必要があるアクションが強調表示されます。
ノード間の移動は、サイクルとして表すことができます。
- 動きが進むグラフのエッジの選択。
- rib骨に沿った動きの開始に割り当てられたアクションの実行。
- 次のノードのすべての要件が満たされるまで待機します。
- rib骨に沿った動きを終了するために割り当てられたアクションのパフォーマンス。
- 次のノードへの移行。
- ノードでのアクションの実行
- パラグラフ1に戻ります。
履歴のノードは、その役割を決定するタイプに便利に分割されます:
- 最初は、ストーリー(またはサブストーリー)への唯一のエントリーポイントです。 この状態の要件は、常識の観点からすべてが正しく発生し続けることを保証する必要があります。
- end-ストーリー(またはサブストーリー)を完了するためのマーカー。
- 選択のポイント-ヒーロー(またはプレイヤー)がイベントの開発のさらなるパスを選択する必要があるノード。
- 条件付き遷移ポイント-いくつかの動的パラメーター(たとえば、ヒーローが持っている金額)によってさらなるパスが決定されるノード。
- 通常のノード-追加のプロパティを持たないノード(線形シーケンスの次のイベントを単純に決定します)。
そして、これはもっと複雑な話です。
ストーリー生成
ストーリーは、いくつかの「アトミック」タスクで構成できます。 たとえば、病気のNPCは、薬のヒーローを魔女に送ることができます。魔女は魔女にサービスを必要とします(別の「ネストされた」タスクを完了します)。
したがって、「アトミック」パターンのセットから構築されます。 テンプレートは同じストーリーグラフですが、すべてのシナリオ(ストーリーを矛盾させる可能性のあるシナリオも含む)を備えています。
ここで重要な点は、パターンの接続です(最終的には接続グラフが必要です)。
- 親履歴の少なくとも1つのノードは、子の開始ノードにエッジを移動する必要があります。
- 子ストーリーの少なくとも1つの終了ノードは、親ノードの端に到達する必要があります。
よく考えた結果、すべてのストーリーは一般に、一連の重要な「オブジェクト」(場所、キャラクター、オブジェクト)によって特徴付けられることが決定されました。 そのため、各テンプレートに対して、固定形式のデータを受け入れる一連のコンストラクターが作成されます。 コンストラクターの例を次に示します。
- 特定の場所から-物語が特定の場所で始まることが重要であり、他のすべてが何でもよい場合。
- 2つのNPCの間-1つのNPC(イニシエーター)が2番目のNPC(受信者)に関連するジョブを提供する必要がある場合
子ストーリーを生成するとき、親は、そのストーリーに必要なコンストラクターの存在によってすべてのテンプレートをフィルター処理します。このコンストラクターから、ランダムなものが既に選択されています。
親ストーリーを子ストーリーの開始にリンクするのは簡単です-親は、目的のノードから子ストーリーの唯一の開始ノードにエッジを作成します。
複数の最終ノードが存在する可能性があり、ストーリーに対して異なる意味の意味を持つことができます。 たとえば、魔女に関するストーリーでは、ヒーローは自分のタスクを遂行できるだけでなく、それぞれ失敗することもあります。異なるエンドノードからのアークは、意味でそれらに対応する親ストーリーのノードに厳密に行われなければなりません。
これを行うために、参加者の各オブジェクトのタスク結果のリストが各エンドノードに表示されます。 現在、3つの可能な結果があります。
- positive-タスクがオブジェクトにプラスの影響を与えた;
- ニュートラル-タスクはオブジェクトに影響しませんでした。
- 負-タスクはオブジェクトに損害を与えました。
このため、各エンドノードについて、その一般的な意味を判断し、それを親ストーリーに正しく関連付けることができます。
後処理と検証
上記の手順を実行すると、視覚的に正しい履歴グラフが手元に表示されますが、一貫性は保証されません。 この列には、世界の現在の状態に反して、イベントを開発するためのすべてのオプションが含まれています。
たとえば、ブランチの1つで、ヒーローは友人を傷つけることができます。 または、敵によってマークされた2つのNPCが一緒に行動できます。
したがって、ストーリーをさらに処理する必要があります。 これには、次のアクションが含まれます。
- ランダムイベントがアクティブになります-一部のノードは、代替の履歴開発オプションのグループに結合されます。 各グループから1つのノードが選択され、残りは削除されます。
- オブジェクトの禁止ジョブ結果を含むすべてのエンドノードが削除されます。
- 結果のグラフは、「ハング」ノードから削除されます。 次のタイプのノードは「ハング」していると見なされます。
- 最も外部の履歴の最終ノードではなく、エッジが出てこないノード。
- 最も外部の履歴の開始ノードではなく、着信エッジのないノード。
結果のグラフは、少なくとも一貫性があります。 しかし、それは実行不可能または無関係になる可能性があります(ストーリーは非統合的になります)。 したがって、次のステップは、結果のストーリーに必要なすべてのプロパティを確認することです。
テストが成功した場合、新しいストーリーがあります。
そうでない場合は、作成を再開します。
完全なロールバックアプローチは、ゲームワールドが非常に小さく、多くの接続がある場合、非常に長い世代につながる可能性があります。 しかし、実際には、通常、世界には多くのオブジェクトがあり、それらの間の接続はほとんどないため、問題はありません。
頻繁にエラーが発生する場合、ジェネレーターを使用するゲームは、世界の設定プロパティの数を減らすことができます(たとえば、友情の関係を考慮するのをやめる)。 ジェネレータ自体は、世界の状態に関する追加の仮定を試みません。
参照資料
ジェネレーターはPythonで記述されており、BSDライセンスの下でgithubに投稿されています。
- リポジトリ: questgen
- インタープリターの例: example.py
- 終了したタスクの視覚化: large_quest.svg
- 作成されたゲーム: http : //the-tale.org