ゲームのアーキテクチャ/デザインパターンに関するアドバイス


16

私はしばらくの間2D RPGに取り組んでいますが、デザインに関するいくつかの悪い決定を下したことに気付きました。特に問題を引き起こしているものがいくつかあるので、他の人がそれらを克服するためにどのようなデザインを使用したのか、または使用するのだろうかと思っていました。

少し背景を説明するために、私は去年の夏休みに仕事を始めました。最初はC#でゲームを作成していましたが、約3か月前にC ++に切り替えることにしました。C ++を頻繁に使用してからしばらく経っていたので、C ++の適切なハンドルを取得したかったので、このような興味深いプロジェクトが良い動機付けになると考えました。ブーストライブラリを広く使用しており、グラフィックスにはSFMLを、オーディオにはFMODを使用しています。

かなりの量のコードが書かれていますが、それを廃棄して最初からやり直すことを検討しています。

ここに私が持っている主要な関心分野があり、他の人がそれらを解決したり、解決したりする適切な方法について意見を聞きたいと思いました。

1.循環的な依存関係 C#でゲームを実行していたとき、C#での問題ではないので、これについて実際に心配する必要はありませんでした。C ++に移行すると、これはかなり大きな問題になり、間違って設計したのではないかと思うようになりました。クラスを分離する方法を想像することはできませんが、それでもクラスに自分のしたいことをさせることができます。依存関係チェーンの例をいくつか示します。

ステータス効果クラスがあります。このクラスには、キャラクターに効果を適用するためのいくつかのメソッド(Apply / Unapply、Tickなど)があります。例えば、

virtual void TickCharacter(Character::BaseCharacter* character, Battles::BattleField *field, int ticks = 1);

この関数は、ステータス効果を与えられたキャラクターがターンするたびに呼び出されます。Regen、Poisonなどのエフェクトを実装するために使用されます。ただし、BaseCharacterクラスとBattleFieldクラスへの依存関係も導入します。当然、BaseCharacterクラスは、現在どのステータスエフェクトがアクティブになっているかを追跡する必要があるため、循環依存関係になります。バトルフィールドは戦闘パーティーを追跡する必要があり、パーティークラスには別の循環依存関係を導入するBaseCharactersのリストがあります。

2-イベント

C#では、キャラクター、バトルフィールドなどのイベントにフックするためにデリゲートを広範囲に使用しました(たとえば、キャラクターのヘルスが変化したとき、ステータスが変更されたとき、ステータスエフェクトが追加/削除されたときなどにデリゲートがありました。)そして、戦場/グラフィックコンポーネントがそれらのデリゲートにフックして、その効果を強化します。C ++では、似たようなことをしました。明らかに、C#デリゲートに直接相当するものはないため、代わりに次のようなものを作成しました。

typedef boost::function<void(BaseCharacter*, int oldvalue, int newvalue)> StatChangeFunction;

そして私のキャラクタークラスで

std::map<std::string, StatChangeFunction> StatChangeEventHandlers;

キャラクターのステータスが変更されるたびに、マップ上のすべてのStatChangeFunctionを繰り返し呼び出します。動作しますが、これは物事を行うための悪いアプローチであることが心配です。

3-グラフィックス

これは大きなことです。これは私が使用しているグラフィックスライブラリとは関係ありませんが、概念的なものです。C#では、グラフィックスを、ひどいアイデアだとわかっている多くのクラスと結合しました。今回はそれを分離したいので、別のアプローチを試みました。

グラフィックを実装するために、ゲームに関連するすべてのグラフィックを一連の画面として想像していました。つまり、タイトル画面、キャラクターステータス画面、マップ画面、インベントリ画面、バトル画面、バトルGUI画面があり、基本的にこれらの画面を必要に応じて積み重ねてゲームグラフィックを作成することができます。アクティブな画面が何であれ、ゲーム入力を所有します。

ユーザー入力に基づいて画面をプッシュおよびポップする画面マネージャーを設計しました。

たとえば、マップ画面(タイルマップの入力ハンドラー/ビジュアライザー)で起動ボタンを押した場合、スクリーンマネージャーへの呼び出しを発行して、メイン画面をマップ画面上に押してマップをマークします。描画/更新されない画面。プレーヤーはメニューをナビゲートし、必要に応じてより多くのコマンドをスクリーンマネージャーに発行して、新しいスクリーンをスクリーンスタックにプッシュし、ユーザーがスクリーンを変更/キャンセルするとそれらをポップします。最後に、プレーヤーがメインメニューを終了したら、ポップメニューからポップしてマップ画面に戻り、描画/更新するようにコメントして、そこから移動します。

バトル画面はより複雑になります。背景として機能する画面、戦闘の各パーティを視覚化する画面、および戦闘のUIを視覚化する画面があります。UIはキャラクターイベントにフックし、それらを使用してUIコンポーネントをいつ更新/再描画するかを決定します。最後に、使用可能なアニメーションスクリプトを使用するすべての攻撃は、画面スタックからポップする前に、追加のレイヤーを呼び出して自身をアニメーション化します。この場合、すべてのレイヤーが一貫して描画可能で更新可能としてマークされ、バトルグラフィックスを処理する画面のスタックを取得します。

私はまだスクリーンマネージャを完全に動作させることができていませんが、しばらくはできると思います。それについての私の質問は、これは価値のあるアプローチですか?悪いデザインの場合、必要なすべての画面を作成するのに多くの時間を費やす前に、今すぐ知りたいと思います。ゲームのグラフィックをどのように構築しますか?

回答:


15

全体として、リストしたものがシステムを廃棄して最初からやり直す原因になるとは言いません。これは、すべてのプログラマーが作業中のプロジェクト全体の約50〜75%を実行したいものですが、開発の終わりのないサイクルにつながり、何も完了しません。したがって、そのために、各セクションにフィードバックします。

  1. これは問題になる可能性がありますが、通常は他の何よりも厄介です。#pragma onceまたは#ifndef MY_HEADER_FILE_H #define MY_HEADER_FILE_H ... #endifを上部で使用するか、.hファイルをそれぞれ使用しますか?このように、.hファイルは各スコープ内に1つだけ存在しますか?もしそうなら、私の推奨事項は、すべての#includeステートメントを削除してコンパイルし、必要に応じてステートメントを追加してゲームを再度コンパイルするようになります。

  2. 私はこれらのタイプのシステムのファンであり、何の問題もありません。C#でのイベントとは、通常、イベントシステムまたはメッセージングシステムに置き換えられます(詳細については、ここで質問を検索してください。ここで重要なのは、物事が発生する必要があるときにこれらを最小限に抑えることです。すでにあなたがそうしているように聞こえますが、ここでは最小限の心配はありません。

  3. これは私にとっても正しい軌道に乗っているようで、個人的にも専門的にも自分のエンジンのためにやっていることです。これにより、メニューシステムは、設定方法に応じて、ルートメニュー(ゲーム開始前)またはプレーヤーHUDが「ルート」画面として表示される状態システムになります。

要するに、私はあなたが実行しているものにふさわしい再起動は何もないと思います。今後、より正式なイベントシステムの交換が必要になる場合がありますが、それは間に合います。Cyclic includeは、すべてのC / C ++プログラマーが常に飛び越えなければならないハードルであり、グラフィックスを分離する作業はすべて論理的な「次のステップ」のように見えます。

お役に立てれば!


#ifdefは、循環インクルードの問題を解決しません。
共産主義のダック

循環インクルードを追跡する前にそこにいることを期待して、私の基礎を覆いました。複数のシンボル定義がある場合、それ自体を含むファイルを含める必要があるファイルとは対照的に、他の魚のケトルになります。(インクルードが.Hファイルではなく.CPPファイルにある場合、彼が説明したことから、2つの基本オブジェクトがお互いを知っていれば問題ありません)
ジェームズ

アドバイスをありがとう:)私が正しい軌道に乗っていることを知ってうれしい
-user127817

4

ヘッダーファイルで可能なクラスを宣言し、実際にそれらを.cpp(またはその他の)ファイルに含める限り、循環依存関係は問題になりません。

イベントシステムについては、2つの提案があります。

1)現在使用しているパターンを保持する場合は、std :: mapではなくboost :: unordered_mapに切り替えることを検討してください。キーとして文字列を使用したマッピングは遅くなります。特に.NETは内部で処理を高速化するためにいくつかの素晴らしいことを行うためです。unordered_mapを使用すると文字列がハッシュされるため、一般に比較が高速になります。

2)boost :: signalsのようなより強力なものに切り替えることを検討してください。それを行うと、boost :: signals :: trackableから派生してゲームオブジェクトを追跡可能にするなど、イベントシステムから手動で登録を解除する代わりに、デストラクタがすべてのクリーンアップを処理できるようにすることができます。また、各スロットを指す複数の信号を使用することもできます(またはその逆、正確な命名法を覚えていません)ので、C#+=でa を実行するのと非常に似ていdelegateます。boost :: signalsの最大の問題は、コンパイルする必要があることです。ヘッダーだけではないため、プラットフォームによっては起動して実行するのが面倒な場合があります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.