バージョン管理により、同様のゲームのゲームコードからゲームエンジンを分離する


15

他のバージョンでは辞退したいゲームが完成しました。これらは、ほぼ同じ種類のデザインの類似したゲームになりますが、常にではなく、基本的に物事が変化する場合があります。

コアコードをゲームとは別にバージョン管理して、ゲームAで見つかったバグを修正した場合、その修正がゲームBに存在するようにします。

私はそれを管理する最良の方法を見つけようとしています。私の最初のアイデアはこれです:

  • engine一般化できるすべてを含み、ゲームの他の部分から完全に独立したモジュール/フォルダー/何でも作成します。これにはいくつかのコードが含まれますが、ゲーム間で共有される汎用アセットも含まれます。
  • このエンジンを独自のgitリポジトリに配置します。これはゲームに含まれますgit submodule

私が苦労しているのは、残りのコードの管理方法です。メニューシーンがあるとしましょう。このコードはゲーム固有ですが、ほとんどのコードは汎用的な傾向があり、他のゲームで再利用できます。に入れることはできませんがengine、ゲームごとに再コーディングするのは非効率です。

ある種のgitブランチのバリエーションを使用すると、それを管理するのに効果的かもしれませんが、これが最善の方法だとは思いません。

誰にもアイデアや共有する経験、またはそれについて何かありますか?


あなたのエンジンは何語ですか?一部の言語には、gitサブモジュールを使用するよりも意味のある専用のパッケージマネージャーがあります。たとえば、NodeJSにはnpmがあります(ソースとしてGitリポジトリをターゲットにできます)。
ダンパントリー

ジェネリックコードの構成管理の最適な方法、「セミジェネリック」コードの構成管理の方法、コードの設計方法、コードの設計方法、または何についての質問ですか。
ダンク

これは各プログラミング言語環境によって異なる場合がありますが、コントロールバージョンソフトウェアだけでなく、ゲームエンジンをゲームコード(パッケージ、フォルダー、APIなど)から分割する方法から始めることもできます。 、制御バージョンを適用します。
umlcat

1つのブランチの1つのフォルダーの履歴をクリーンにする方法:エンジンをリファクタリングして、別の(将来の)リポジトリが別のフォルダーにあるようにします。これが最後のコミットです。次に、新しいブランチを作成し、そのフォルダー以外のすべてを削除して、コミットします。次に、リポジトリの最初のコミットに移動し、それを新しいブランチにマージします。これで、そのフォルダのみを持つブランチが作成されました。他のプロジェクトにプルしたり、既存のプロジェクトにマージしたり、あるいはその両方を行ったりできます。コードが既に分離されている場合、これはブランチ内のエンジンを分離するのに非常に役立ちました。gitモジュールは必要ありません。
バリーステイズ

回答:


13

一般化できるすべてのものを含み、ゲームの他の部分から完全に独立したエンジンモジュール/フォルダー/何でも作成します。これにはいくつかのコードが含まれますが、ゲーム間で共有される汎用アセットも含まれます。

このエンジンを独自のgitリポジトリに配置します。これは、gitサブモジュールとしてゲームに含まれます。

それはまさに私がやっていることであり、非常にうまく機能します。アプリケーションフレームワークとレンダリングライブラリがあり、これらはそれぞれプロジェクトのサブモジュールとして扱われます。私を見つけるSourceTreeは、あなたがプロジェクトAにエンジンのサブモジュールを更新した場合、それがサブモジュールに来るとき、それはそれらをよく管理し、あなたが何かを忘れさせないように、便利で、例えば、それはプロジェクトBの変更をプルするために通知されます

経験を積むと、エンジン内のコードとプロジェクトごとのコードの知識が得られます。少しでも確信が持てない場合は、今のところ各プロジェクトに保管することをお勧めします。時間が経つにつれて、さまざまなプロジェクトの中で何が同じかを確認し、それを徐々にエンジンコードに組み込むことができます。言い換えると、プロジェクトごとに個別に変更されていないことが100%確実になるまでコードを複製し、それを一般化します。

ソース管理とバイナリに関する注意

バイナリアセットが頻繁に変更されることが予想される場合は、これらをgitのようなテキストベースのソース管理に入れたくない場合があることに注意してください。ただ言って...バイナリのためのより良いソリューションがあります。「エンジンソース」リポジトリをクリーンでパフォーマンスの高い状態に保つために今のところできる最も簡単なことは、バイナリのみを保持する別個の「エンジンバイナリ」リポジトリを用意することです。このようにして、「エンジンソース」リポジトリに加えられるパフォーマンスの損傷を軽減します。これは常に変化しているため、コミット、プッシュ、プルなどの高速反復が必要です。gitなどのソース管理システムはテキストデルタで動作します、バイナリを導入するとすぐに、テキストの観点から大量のデルタを導入します。これは最終的に開発時間を浪費します。GitLab Annex。Googleはあなたの友達です。


あまり頻繁に変わることはありませんが、私はそれに興味があります。バイナリバージョンについては何も知りません。どのようなソリューションがありますか?
Malharhak

@Malharhak編集してコメントに答えます。
エンジニア

@Malharak このトピックに関する良い情報があります。
エンジニア

1
可能な限りプロジェクトプロジェクト内に維持するための+1 。共通のコードはより高い複雑さを与えます。絶対に必要になるまでは避けてください。
ガスドール

1
@Malharhakいいえ、特にあなたの目標は、コードが不変であり、一般的なものとしてファクタリングできることに気付くまで「コピー」を保持することだけですから。Gusdorはこれを繰り返しました-警告-あまりにも早く要素を取り除くことで簡単に膨大な時間を浪費し、そのコードを一般的に維持する方法を見つけようとしますが、さまざまなプロジェクトに適合するように適応します...たくさんのパラメーターとスイッチがあり、それはい混乱に変わりますが、それでも新しいプロジェクトごとに変更するため、必要なものではありませ早めにファクタリングしないでください。我慢してください。
エンジニア

6

ある時点で、エンジンはゲームに関する情報を特化し、知る必要があります。ここで接線から出発します。

RTSでリソースを取得します。一つのゲームが持っていることCreditsCrystal、他MetalPotatoes

オブジェクト指向の概念を適切に使用し、最大限に活用する必要があります。コードの再利用。の概念Resourceがここに存在することは明らかです。

したがって、リソースには次のものがあると判断します。

  1. 自分自身をインクリメント/デクリメントするメインループのフック
  2. 現在の金額を取得する方法(を返しますint
  3. 任意に減算/加算する方法(プレイヤーがリソースを移動したり、購入したり...)

このaの概念はResource、ゲームでのキルまたはポイントを表す可能性があることに注意してください!あまり強力ではありません。

次に、ゲームについて考えてみましょう。ペニーを処理し、出力に小数点を追加することにより、通貨をソートできます。私たちができないことは、「瞬間的な」リソースです。「電力グリッドの生成」と言うように

InstantResource同様のメソッドを持つクラスを追加するとしましょう。これで、エンジンをリソースで汚染し始めています。


問題

RTSの例をもう一度見てみましょう。プレイヤーが何かCrystalを他のプレイヤーに寄付するとします。あなたは次のようなことをしたい:

if(transfer.target == engine.getPlayerId()) {
    engine.hud.addIncoming("You got "+transfer.quantity+" of "+
        engine.resourceDictionary.getNameOf(transfer.resourceId)+
        " from "+engine.getPlayer(transfer.source).name);
}
engine.getPlayer(transfer.target).getResourceById(transfer.resourceId).add(transfer.quantity)
engine.getPlayer(transfer.source).getResourceById(transfer.resourceId).add(-transfer.quantity)

しかし、これは非常に面倒です。それは一般的な目的ですが、面倒です。すでにそれはa resourceDictionaryを課していますが、これはあなたのリソースが名前を持たなければならないことを意味します!そして、それはプレイヤーごとですので、チームのリソースを持つことができなくなります。

これは「多すぎる」抽象化です(私が認める素晴らしい例ではありません)代わりに、ゲームにプレイヤーとクリスタルが含まれていることを受け入れて、(たとえば)

engine.getPlayer(transfer.target).crystal().receiveDonation(transfer)
engine.getPlayer(transfer.source).crystal().sendDonation(transfer)

クラスとPlayerそのクラスのオブジェクトが自動的に寄付金の送信/転送用HUD上のものを表示します。CurrentPlayerCurrentPlayercrystal

これにより、クリスタル、クリスタルの寄付、現在のプレイヤー向けのHUD上のメッセージ、その他すべてでエンジンが汚染されます。読み取り/書き込み/保守がより高速で簡単です(大幅に高速ではないため、より重要です)


最後の挨拶

リソースのケースは素晴らしいものではありません。それでもあなたがポイントを見ることができることを望みます。特定のゲームに必要なものとして、またリソースのすべての概念に適用できるものとして、「リソースはエンジンに属していません」ということを実証しました。通常、3(または4)個の「レイヤー」があります

  1. 「コア」-これはエンジンの教科書定義であり、イベントフックを備えたシーングラフであり、シェーダーとネットワークパケット、およびプレーヤーの抽象的な概念を扱います。
  2. 「GameCore」-これは、すべてのゲームではなく、ゲームの種類にかなり一般的です-たとえば、RTSのリソースやFPSの弾薬。ここからゲームロジックが浸透し始めます。これは、以前のリソースの概念が存在する場所です。ほとんどのRTSリソースに意味のあるこれらのものを追加しました。
  3. 作成中の実際のゲームに非常に固有の「GameLogic」。あなたのような名前を持つ変数見つけるcreatureか、shipまたはをsquad。使い方継承あなたはすべての3つの層にまたがるクラス取得します(たとえばことCrystal Resourceこれです GameLoopEventListenerと言います)
  4. 「資産」は他のゲームには役に立たない。たとえば、半減期2のAIスクリプトの組み合わせを考えてみましょう。これらは、同じエンジンを搭載したRTSで使用されることはありません。

古いエンジンから新しいゲームを作成する

これは非常に一般的です。フェーズ1では、レイヤー3と4(ゲームがまったく異なるタイプの場合は2)をリッピングします。古いRTSからRTSを作成しているとします。クリスタルやリソースではなく、リソースがまだあります。したがって、レイヤー2および1の基本クラスは依然として意味があり、3および4で参照されているクリスタルはすべて破棄できます。だからそうする。しかし、私たちがやりたいことの参考としてそれをチェックするかもしれません。


レイヤー1の汚染

これは起こる可能性があります。抽象化とパフォーマンスは敵です。たとえば、UE4は最適化された合成のケースを多数提供します(したがって、XとYが必要な場合、誰かがXとYを非常に高速に一緒に実行するコードを作成した場合-両方を実行していることがわかります)、結果として非常に大きなものになります。これは悪くありませんが、時間がかかります。レイヤー1は、「シェーダーにデータを渡す方法」や「アニメーション化する方法」などを決定します。プロジェクトに最適な方法でそれを行うことは常に良いことです。未来に向けて計画してみてください。コードの再利用はあなたの友人であり、意味のある場所を継承します。


レイヤーの分類

最後に(私は約束します)レイヤーをあまり恐れません。エンジンは、固定機能パイプラインの昔からの古語であり、エンジンはほぼ同じようにグラフィカルに機能しました(その結果、多くの共通点がありました)。開発者が達成したかったあらゆる効果。AIは(無数のアプローチのため)エンジンの際立った機能でしたが、現在はAIとグラフィックスです。

コードをこれらのレイヤーにファイルしないでください。有名なUnrealエンジンでさえ、それぞれが異なるゲームに固有の多くの異なるバージョンを持っています。変更されていないファイル(データ構造のようなものを除く)はほとんどありません。これは結構です!別のゲームから新しいゲームを作成する場合、30分以上かかります。重要なのは、どのビットをコピーして貼り付け、何を残すかを計画し、知ることです。


1

汎用と特定の両方が混在するコンテンツを処理する方法についての私の個人的な提案は、それを動的にすることです。例としてメニュー画面を取り上げます。あなたが求めていたものを誤解した場合は、あなたが知りたいことを教えてください、私は私の答えを適応させます。

メニューシーンには(ほとんど)常に3つの要素があります:背景、ゲームのロゴ、メニュー自体です。これらは通常、ゲームによって異なります。このコンテンツに対してできることは、エンジンでMenuScreenGeneratorを作成することです。これには、BackGround、Logo、Menuの3つのオブジェクトパラメーターが必要です。これらの3つの部分の基本構造もエンジンの一部ですが、エンジンは実際にはこれらの部分がどのように生成されるか、単にどのパラメーターを与えるべきかを述べていません。

次に、実際のゲームコードで、BackGround、Logo、およびMenuのオブジェクトを作成し、これをMenuScreenGeneratorに渡します。繰り返しますが、ゲーム自体はメニューの生成方法を処理しません。これはエンジン用です。ゲームは、エンジンにどのように見えるか、どこにあるべきかを伝えるだけです。

基本的に、エンジンはゲームが表示するものを指示するAPIである必要があります。適切に行われた場合、エンジンはハードワークを実行し、ゲーム自体は、使用するアセット、実行するアクション、および世界の外観をエンジンに通知するだけです。

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