ゲームエンジンでハードコーディングを回避する方法


22

私の質問はコーディングの質問ではありません。一般的にすべてのゲームエンジンの設計に適用されます。

ハードコーディングをどのように回避しますか?

この質問は思ったよりもずっと深いです。操作に必要なファイルをロードするゲームを実行したい場合load specificfile.wad、エンジンのコードのようなものを言わないようにするにはどうすればよいでしょうか?また、ファイルがロードされたときに、どうやって言うのを避けるのload aspecificmap in specificfile.wadですか?

この質問は、ほとんどすべてのエンジン設計に当てはまり、可能な限り少ないエンジンをハードコーディングする必要があります。それを達成する最良の方法は何ですか?

回答:


42

データ駆動型コーディング

あなたが言及するものはすべて、データで指定できるものです。なぜロードしていaspecificmapますか?なぜなら、プレーヤーが新しいゲームを開始するとき、ゲーム構成が最初のレベルであると言っているため、またはそれがロードしたばかりのプレーヤーのセーブファイルの現在のセーブポイントの名前だからです。

どうやって見つけますaspecificmapか?マップIDとそれらのディスク上のリソースをリストするデータファイルにあるためです。

必要なのは、ハードコーディングを回避するのが正当に困難または不可能な「コア」リソースの特に小さなセットのみです。少し手間をかけると、これはのような単一のハードコードされたデフォルトのアセット名に制限されますmain.wad。このファイルは、コマンドライン引数をゲームに渡すことにより、実行時に変更される可能性がありますgame.exe -wad mymain.wad

データ駆動型コードの記述は、他のいくつかの原則に依存しています。たとえば、システムまたはモジュールが特定のリソースを要求するのを避け、代わりにそれらの依存関係を逆にすることができます。つまり、初期化コードをDebugDrawerロードしないでくださいdebug.font。代わりに、DebugDrawer初期化コードでリソースハンドルを取得します。そのハンドルは、メインのゲーム構成ファイルからロードされる場合があります。

コードベースの具体的な例として、リソースデータベースからロードされる「グローバルデータ」オブジェクトがあります(これ自体はデフォルトでは./resourcesフォルダーですが、コマンドライン引数でオーバーロードできます)。このグローバルデータのリソースデータベースIDは、コードベースで唯一必要なハードコード化されたリソース名です(プログラマーが怠け者になることもあるため、他にもありますが、通常は最終的にそれらを修正/削除します)。このグローバルデータオブジェクトには、構成データを提供することを唯一の目的とするコンポーネントがいっぱいです。コンポーネントの1つは、他の多くの構成アイテムの中ですべての主要なUIリソース(フォント、Flashファイル、アイコン、ローカリゼーションデータなど)へのリソースハンドルを含むUIグローバルデータコンポーネントです。UI開発者がメインUIアセットの名前を/ui/mainmenu.swfから/ui/lobby.swfグローバルデータ参照を更新するだけです。エンジンコードを変更する必要はまったくありません。

このグローバルデータをすべてに使用します。すべてのプレイ可能なキャラクター、すべてのレベル、UI、オーディオ、コア資産、ネットワーク構成、すべて。(まあ、すべてではありませんが、それらの他のものは修正すべきバグです。)

このアプローチには他にも多くの利点があります。1つは、リソースのパッキングとバンドリングをプロセス全体に統合することです。また、エンジンのハードコーディングパスは、ゲームアセットをパッケージ化するスクリプトまたはツールに同じパスをハードコーディングする必要があることを意味する傾向があり、これらのパスは同期しなくなる可能性があります。代わりに、単一のコアアセットとそこからの参照チェーンに依存して、単一のコマンドでアセットバンドルを構築し、bundle.exe -root config.data -out main.wad必要なすべてのアセットを含めることができます。さらに、バンドラーはリソース参照をたどるだけなので、必要なアセットのみが含まれ、プロジェクトの存続期間中に必然的に蓄積するすべての残りのフラフをスキップすることがわかります(さらに、そのリストを自動的に生成できます)剪定用の綿毛)。

この全体のトリッキーなコーナーケースは、スクリプトにあります。エンジンをデータ駆動にするのは概念的には簡単ですが、スクリプトがデータと見なされ、リソースパスを無差別に使用することを「許可」する非常に多くのプロジェクト(AAAの趣味)を見てきました。それをしないでください。Luaファイルがリソースを必要とし、関数を呼び出すだけのtextures.lua("/path/to/texture.png")場合、アセットパイプラインは、スクリプトが/path/to/texture.png正しく動作するために必要であり、テクスチャが未使用で不要であると考える可能性があることを知るのに多くの問題を抱えています。スクリプトは他のコードと同様に扱う必要があります。リソースやテーブルなど、必要なデータは、エンジンとリソースパイプラインが依存関係を検査できる構成エントリで指定する必要があります。foo.lua代わりに「スクリプトを読み込む」というデータは、「foo.luaパラメーターに必要なリソースが含まれている場合は、これらのパラメーターを指定します。たとえば、スクリプトがランダムに敵を生成する場合、可能性のある敵のリストをその構成ファイルからスクリプトに渡します。可能性のあるスポーンの完全なリストを知っているため)、リソースパイプラインはすべての敵をゲームにバンドルすることを知っています(構成データによって明確に参照されているため)。スクリプトがパス名の文字列を生成し、load関数を呼び出す場合エンジンもリソースパイプラインも、スクリプトがロードしようとするアセットを具体的に知る方法を持っています。


良い答え、非常に実用的で、これを実装するときに人々が犯す落とし穴とエラーについても説明しています!+1
whn

+1。改造を有効にする場合は、構成データを含むリソースを指すパターンに従うことも非常に役立ちます。オリジナルのデータファイルを作成してそれらを指すのではなく、元のデータファイルを変更する必要があるゲームを変更するのは、はるかに困難で危険です。定義された優先順位で複数のファイルを指すことができればさらに良いです。
ジュートナルグ

12

一般的な関数でハードコーディングを回避するのと同じ方法。

パラメーターを渡し、構成ファイルに情報を保持します。

そのような状況では、エンジンの作成とクラスの作成にソフトウェアエンジニアリングの違いはまったくありません。

MgrAssets
public:
  errorCode loadAssetFromDisk( filePath )
  errorCode getMap( mapName, map& )

private:
  maps[name, map]

次に、クライアントコードは、アセットファイルの場所とそれらに含まれるマップを示す情報を含む「マスター」構成ファイル(これはハードコードされているか、コマンドライン引数として渡されます)を読み取ります。

そこから、すべてが「マスター」構成ファイルによって駆動されます。


1
ええ、それに加えて、カスタムロジックを取り込むための何らかのメカニズムがあります。ユーザー定義の機能によってエンジンのコア機能を拡張するために、C#、pythonなどの言語を埋め込むことによって可能性があります
-qCring

3

私は他の答えが好きなので、少し反対になります。;)

データに関する知識をエンジンにコーディングすることは避けられません。情報がどこから来ても、エンジンはそれを探すために知っていなければなりません。ただし、実際の情報自体をエンジンにエンコードすることは避けられます。

「純粋な」データ駆動型アプローチでは、初期構成をロードするために必要なコマンドラインパラメーターで実行可能ファイルを起動しますが、その情報を解釈する方法を知るためにエンジンをコーディングする必要があります。お使いのコンフィギュレーション・ファイルはJSONであれば例えば、あなたはエンジンを探すために知っておく必要があります例えば、ハードコードにあなたが探して変数を必要"intro_movies""level_list"し、等々 。

ただし、「よく構成された」エンジンは、構成データとそれが参照するデータを交換するだけで、さまざまなゲームで機能します。

したがって、マントラはハードコーディングを避けることではなく、可能な限り少ない労力で変更を行えるようにすることです。

データファイルアプローチ(私は心からサポートしています)とは対照的に、データをエンジンにコンパイルしても大丈夫かもしれません。そうすることの「コスト」がより低ければ、実際の害はありません。あなたがそれに取り組んでいるのがあなただけなら、あなたは後日ファイル処理を延期することができ、必ずしも自分自身を台無しにする必要はありません。私の最初のいくつかのゲームプロジェクトには、ゲーム自体にハードコードされたデータの大きなテーブルがありました。たとえば、武器とその各種データのリストです。

struct Weapon
{
    enum IconID icon;
    enum ModelID model;
    int damage;
    int rateOfFire;
    // etc...
};

const struct Weapon g_weapons[] =
{
    { ICON_PISTOL, MODEL_PISTOL, 5, 6 },
    { ICON_RIFLE, MODEL_RIFLE, 10, 20 },
    // etc...
};

したがって、このデータは参照しやすい場所に配置し、必要に応じて簡単に編集できます。このようなものを何らかの構成ファイルに入れるのが理想ですが、構文解析と翻訳を行う必要があります。そして、すべてのジャズに加えて、構造間参照を接続することは、本当にしたくない追加の痛みになるかもしれません扱う。


jsonを解析するのはそれほど難しくありません。関連する唯一の「コスト」は学習です。 (具体的には、適切なモジュールまたはライブラリーの使用方法を学習します。たとえば、Goには優れたjsonサポートがあります。)
ワイルドカード

それほど難しいことではありませんが、ただ学ぶこと以上のことをする必要があります。たとえば、JSONを技術的に解析する方法は知っていますが、他の多くのファイル形式のパーサーを作成しましたが、サードパーティソリューションを見つけてインストールする必要があります(依存関係とその構築方法を把握する必要があります)。しないよりも時間がかかります。
ダッシュトムバング

4
すべては、それをしないよりも時間がかかります。 ただし、必要なツールはすでに作成されています。ゲームを書くためにコンパイラ設計したり、マシンコードをいじったりする必要はないが、作業しているプラ​​ットフォームの言語学ぶ必要があるのと同じです。したがって、jsonパーサーの使用も学びます。
ワイルドカード

あなたの議論が何なのか分かりません。この回答では、YAGNIを提唱しています。あなたが役に立たない何かをする時間を費やす/無駄にする必要がないなら、そうしないでください。あなたがそのことに時間を費やしたいなら、それは素晴らしいことです。後で時間を費やす必要があるかもしれませんが、多分そうではないかもしれませんが、前もってそれを行うことは、実際にゲームを作成するタスクからあなたをそらすだけです。ゲーム開発は簡単です。ゲームを作成するすべてのタスクは簡単です。ほとんどのゲームには数百万の単純なタスクがあり、責任のある開発者がその目標を最も早く達成するタスクを選択するだけです。
ダッシュトムバン

2
実際、私はあなたの答えを支持しました。そのような本当の議論はありません。JSONの解析難しくないことに注意したいだけです。もう一度読んで、私は主にスニペットに応答していたと思います。 しかし、個人的なプロジェクトゲームなどについては、YAGNIに同意します。:)
ワイルドカード
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.