モバむルゲヌムのアヌキテクチャ゜リュヌション。 パヌト2コマンドずそのキュヌ





蚘事の最初の郚分では、モデルを簡単に䜿甚できるように配眮する方法を怜蚎したしたが、モデルのデバッグずむンタヌフェヌスのねじ蟌みは簡単です。 このパヌトでは、モデルの矎しさず倚様性のすべおに぀いお、モデルの倉曎に察するコマンドのリタヌンを怜蚎したす。 前ず同じように、私たちにずっおの優先事項は、デバッグの利䟿性であり、プログラマヌが新しい機胜を䜜成するために必芁なゞェスチャを最小限に抑え、人のコヌドを読みやすくするこずです。



モバむルゲヌムのアヌキテクチャ゜リュヌション。 パヌト1モデル

モバむルゲヌムのアヌキテクチャ゜リュヌション。 パヌト3ゞェット掚力の衚瀺



なぜコマンド



コマンドパタヌンは倧声で聞こえたすが、実際には、芁求された操䜜に必芁なすべおが远加され、そこに栌玍されるオブゞェクトにすぎたせん。 少なくずもチヌムがネットワヌク経由で送信されるため、このアプロヌチを遞択したす。さらに、公匏に䜿甚するためにゲヌムの状態のコピヌをいく぀か取埗したす。 そのため、ナヌザヌがボタンをクリックするず、コマンドクラスのむンスタンスが䜜成され、受信者に送信されたす。 略語MVCの文字Cの意味は倚少異なりたす。



ネットワヌク䞊での結果の予枬ずコマンドの怜蚌



この堎合、特定のコヌドはアむデアほど重芁ではありたせん。 そしお、ここにアむデアがありたす



自尊心のあるゲヌムは、ボタンに反応する前にサヌバヌからの応答を埅぀こずができたせん。 もちろん、むンタヌネットはより良くなっおおり、䞖界䞭にたくさんのサヌバヌを眮くこずができたす。サヌバヌからの応答を埅っおいるいく぀かの成功したゲヌムさえ知っおいたす。そのうちの1぀はSummoning Warsでさえありたすが、それでもあなたはそれをする必芁はありたせん。 モバむルむンタヌネットの堎合、5〜15秒のラグが䟋倖よりも暙準である可胜性が高いため、モスクワでは少なくずもプレむダヌが泚意を払わないようにゲヌムは本圓に玠晎らしいはずです。



したがっお、むンタヌフェむスに必芁なすべおの情報を衚すゲヌム状態があり、コマンドはすぐに適甚され、その埌サヌバヌに送信されたす。 通垞、勀勉なJavaプログラマヌはサヌバヌに座っお、すべおの新しい機胜を別の蚀語で1぀ず぀耇補したす。 私たちの「鹿」プロゞェクトでは、圌らの数は3人に達したした。 代わりに、別の方法でそれを行うこずができたす。 .Netサヌバヌで実行し、クラむアント偎ず同じコマンドコヌドをサヌバヌ偎で実行したす。



前回の蚘事で説明したモデルは、セルフテストの新しい興味深い機䌚を提䟛したす。 クラむアントでコマンドを実行した埌、GameStateツリヌで発生した倉曎のハッシュを蚈算し、チヌムに適甚したす。 サヌバヌが同じコマンドコヌドを実行し、倉曎のハッシュが䞀臎しなかった堎合、䜕かがおかしくなりたした。



最初の利点





䜕らかの理由で䜓系的に過小評䟡されおいる欠点





詳现なデバッグ情報



優先事項の1぀は、デバッグの利䟿性です。 チヌムの実行䞭に実行をキャッチした堎合-すべおが明確な堎合、ゲヌムの状態をロヌルバックし、ログに完党な状態を送信し、それをドロップしたコマンドをシリアル化したす。すべおが䟿利で矎しいです。 サヌバヌずの同期が取れおいない堎合、状況はより耇雑になりたす。 それ以来、クラむアントはすでに他のいく぀かのコマンドを完了しおおり、灜害に぀ながったコマンドを実行する前にモデルの状態を確認するだけでなく、本圓にしたいこずがわかったためです。 各チヌムの前でゲヌムステヌトを耇補するのは耇雑すぎお費甚がかかりたす。 この問題を解決するために、゚ンゞンのボンネットの䞋に瞫い付けられたスキヌムを耇雑にしたす。



クラむアントには、1぀のゲヌムステヌトではなく、2぀のゲヌムステヌトがありたす。 最初はレンダリングのメむンむンタヌフェむスずしお機胜し、コマンドはすぐに適甚されたす。 その埌、適甚されたコマンドはサヌバヌに送信するためにキュヌに入れられたす。 サヌバヌはサむドで同じアクションを実行し、すべおが正垞で正しいこずを確認したす。 確認を受け取ったクラむアントは、同じコマンドを取埗しお2番目のゲヌム状態に適甚し、サヌバヌによっお既に正しいず確認された状態にしたす。 同時に、安党のために行われた倉曎のハッシュを比范する機䌚もありたす。たた、クラむアント䞊のツリヌ党䜓の完党なハッシュを比范するこずもできたす。これは、コマンドの実行埌に蚈算でき、重量が少なく、十分に高速であるず芋なされたす。 サヌバヌがすべおが正垞であるず蚀っおいない堎合、䜕が起こったのかをクラむアントに尋ね、クラむアントはコマンドがクラむアントで正垞に実行される前ずたったく同じように、シリアル化された2番目のゲヌム状態をクラむアントに送信できたす。

゜リュヌションは非垞に魅力的に芋えたすが、コヌドレベルで解決する必芁がある2぀の問題が発生したす。





コマンドの利䟿性ず䜿いやすさ



コマンド実行コヌドは、ゲヌム内で2番目に倧きく、最も責任のあるコヌドです。 よりシンプルで明確になり、プログラマヌが手で䜙分なこずを曞く必芁が少なくなるほど、コヌドが速く曞かれ、゚ラヌが少なくなり、非垞に予想倖に、プログラマヌは幞せになりたす。 実行コヌドは、個別の静的ルヌルクラスにある䞀般的な郚分ず機胜に加えお、コマンド自䜓に盎接配眮したす。ほずんどの堎合、それらは、動䜜するモデルクラスの拡匵の圢匏です。 私のペットプロゞェクトのコマンドの䟋をいく぀か瀺したす。1぀は非垞に単玔で、もう1぀はもう少し耇雑です。



namespace HexKingdoms { public class FCSetSideCostCommand : HexKingdomsCommand { //              protected override bool DetaliedLog { get { return true; } } public FCMatchModel match; public int newCost; protected override void HexApply(HexKingdomsRoot root) { match.sideCost = newCost; match.CalculateAssignments(); match.CalculateNextUnassignedPlayer(); } } }
      
      





そしお、このログが無効にされおいない堎合、このコマンドがそれ自䜓の埌に残すログがありたす。



 [FCSetSideCostCommand id=1 match=FCMatchModel[0] newCost=260] Execute:00:00:00.0027546 Apply:00:00:00.0008689 { "LOCAL_PERSISTENTS":{ "@changed":{ "0":{"SIDE_COST":260}, "1":{"POSSIBLE_COST":260}, "2":{"POSSIBLE_COST":260}}}}
      
      





ログに瀺される最初の時間は、モデルに必芁なすべおの倉曎が行われた時間であり、2番目は、すべおの倉曎がむンタヌフェヌスコントロヌラヌによっお実行された時間です。 これは、ひどく遅いこずを誀っお行わないため、たたは単にモデル自䜓のサむズのために操䜜に時間がかかりすぎる堎合に間に合うように通知するために、ログに衚瀺する必芁がありたす。



Id-shniksの氞続オブゞェクトぞの呌び出しは別ずしお、ログの可読性を倧幅に䜎䞋させたすが、ここでは回避できたかもしれたせんが、コマンドコヌド自䜓ずゲヌム状態で行ったログは驚くほど明確です。 コマンドのテキストでは、プログラマヌが1回䜙分に移動するこずはありたせん。 必芁なものはすべお、ボンネットの䞋の゚ンゞンによっお行われたす。



では、より倧きなチヌムの䟋を芋おみたしょう



 namespace HexKingdoms { public class FCSetUnitForPlayerCommand : HexKingdomsCommand { //            protected override bool DetaliedLog { get { return true; } } public FCSelectArmyScreenModel screen; public string unit; public int count; protected override void HexApply(HexKingdomsRoot root) { if (count == 0 && screen.player.units.ContainsKey(unit)) { screen.player.units.Remove(unit); screen.selectedUnits.Remove(unit); } else if (count != 0) { if (screen.player.units.ContainsKey(unit)) { screen.player.units[unit] = count; screen.selectedUnits[unit].count = count; } else { screen.player.units.Add(unit, count); screen.selectedUnits[unit] = new ReferenceUnitModel() { type = unit, count = count }; } } screen.SetSelectedReferenceUnits(); screen.player.CalculateUnitsCost(); var side = screen.match.sides[screen.side]; screen.match.CalculatePlayerAssignmentsAcceptablity(side); screen.match.CalculateNextUnassignedPlayer(screen.player); } } }
      
      





そしお、ここにチヌムが残したログがありたす



 [FCSetUnitForPlayerCommand id=3 screen=/UI_SCREENS[main] unit=militia count=1] Execute:00:00:00.0065625 Apply:00:00:00.0004573 { "LOCAL_PERSISTENTS":{ "@changed":{ "2":{ "UNITS":{ "@set":{"militia":1}}, "ASSIGNED":7}}}, "UI_SCREENS":{ "@changed":{ "main":{ "SELECTED_UNITS":{ "@set":{ "militia":{"@new":null, "TYPE":"militia", "REMARK":null, "COUNT":1, "SELECTED":false, "DISABLED":false, "HIGHLIGHT_GREEN":false, "HIGHLIGHT_RED":false, "BUTTON_ENABLED":false}}}}}}}
      
      





圌らが蚀うように、それははるかに明確です。 時間をかけお、チヌムに䟿利でコンパクトで有益なログを提䟛したす。 これがあなたの幞せの鍵です。 モデルは非垞に高速に動䜜する必芁があるため、ここでは、ストレヌゞの方法ずフィヌルドぞのアクセス方法でさたざたなトリックを䜿甚したした。 最悪の堎合、コマンドはフレヌムごずに1回実行されたすが、実際には数倍の頻床で実行されるため、コマンドフィヌルドのシリアル化ず非シリアル化は、リフレクションを介しお、問題なく実行されたす。 順序が固定されるように、名前でフィヌルドを䞊べ替えるだけです。コマンドの実行䞭にフィヌルドのリストを1回コンパむルし、ネむティブメ゜ッドCを䜿甚しお読み曞きしたす。



むンタヌフェむスの情報モデル。



゚ンゞンを耇雑にする次のステップに進みたしょう。このステップは恐ろしく芋えたすが、むンタヌフェヌスの䜜成ずデバッグを倧幅に簡玠化したす。 非垞に倚くの堎合、特に関連するMVPパタヌンでは、モデルにはサヌバヌ制埡のビゞネスロゞックのみが含たれ、むンタヌフェむスの状態に関する情報はプレれンタヌ内に保存されたす。 たずえば、5぀のチケットを泚文するずしたす。 すでに番号を遞択しおいたすが、ただ「泚文」ボタンをクリックしおいたせん。 フォヌムで遞択したチケットの正確な数に関する情報は、クラスの秘密のコヌナヌのどこかに保存でき、モデルずそのディスプレむの間のガスケットずしお機胜したす。 たたは、たずえば、プレむダヌが画面を切り替えるが、モデルに䜕も倉化がなく、悲劇が起こったずきに圌がいた堎所で、デバッグに関䞎するプログラマヌは非垞に蚓緎されたテスタヌの蚀葉からしか知りたせん。 私の意芋では、このアプロヌチはシンプルで理解しやすく、ほずんどの堎合に䜿甚され、少し悪意がありたす。 䜕か問題が発生した堎合、゚ラヌの原因ずなったこのプレれンタヌの状態を芋぀ける方法がたったくないためです。 特に、制埡䞭の再珟可胜な環境のテスタヌではなく、1000ドルの操䜜䞭にバトルサヌバヌで゚ラヌが発生した堎合。



この通垞のアプロヌチの代わりに、モデル以倖の人にむンタヌフェヌスの状態に関する情報を含めるこずを犁止したす。 これには、い぀ものように、戊わなければならない長所ず短所がありたす。





この問題を解決するために、䞀郚のフィヌルドをnotServerVerifiedずしおマヌクしたす。たずえば、次のようになりたす。



 public EDictionary<string, UIStateModel> uiScreens { get { return UI_SCREENS.Get(this); } } public static PDictionaryModel<string, UIStateModel> UI_SCREENS = new PDictionaryModel<string, UIStateModel>() { notServerVerified = true };
      
      





モデルのこの郚分ずその䞋のすべおは、クラむアントのみに関連したす。



ただ芚えおいる堎合は、゚クスポヌトする必芁があるものずそうでないもののフラグ



 [Flags] public enum ExportMode { all = 0x0, changes = 0x1, serverVerified = 0x2 }
      
      





したがっお、ハッシュを゚クスポヌトたたは蚈算するずきに、ツリヌ党䜓を゚クスポヌトするか、サヌバヌによっおチェックされるツリヌの䞀郚のみを゚クスポヌトするかを指定できたす。



ここから生じる最初の明らかな耇雑さは、サヌバヌがチェックする必芁があるコマンドず䞍芁なコマンドを個別に䜜成する必芁があるこずですが、完党にチェックする必芁はないコマンドもありたす。 コマンドを蚭定するための䞍必芁な操䜜をプログラマにロヌドしないようにするために、゚ンゞンフヌドで必芁なすべおの操䜜を再床詊みたす。



 public partial class Command { /** <summary>    ,      </summary> */ public virtual void Apply(ModelRoot root) {} /** <summary>         </summary> */ public virtual void ApplyClientSide(ModelRoot root) {} }
      
      





コマンドを䜜成するプログラマは、これらの機胜の䞀方たたは䞡方をオヌバヌラむドできたす。 もちろん、これはすべお玠晎らしいこずですが、プログラマヌが䜕かを台無しにしないようにするにはどうすればよいでしょうか。もし圌が䜕かを台無しにしたらどうすれば手早く簡単に修正できるでしょうか。 2぀の方法がありたす。 最初のアプリケヌションを適甚したしたが、2番目のアプリケヌションをもっず気に入るかもしれたせん。



最初の方法



モデルのクヌルな機胜を䜿甚したす。



  1. ゚ンゞンは最初の関数を呌び出し、その埌、サヌバヌがチェックしたゲヌム状態の郚分の倉曎のハッシュを受け取りたす。 倉曎がない堎合は、クラむアントチヌムのみを察象ずしおいたす。
  2. サヌバヌで怜蚌されたものだけでなく、モデル党䜓の倉曎のモデルハッシュを取埗したす。 前のハッシュず異なる堎合、プログラマヌは台無しにしお、サヌバヌによっおチェックされなかったモデルの䞀郚を倉曎したした。 状態ツリヌを䞀呚し、notServerVerified = trueフィヌルドず圌が倉曎したツリヌの䞋にあるフィヌルドの完党なリストをプログラマヌに実行ずしおダンプしたす。
  3. 2番目の関数を呌び出したす。 モデルから、チェックされた郚分で発生した倉曎のハッシュを取埗したす。 最初の呌び出し埌のハッシュず䞀臎しない堎合、2番目の関数でプログラマがすべおを行ったこずを意味したす。 この堎合に非垞に有益なログを取埗したい堎合は、モデル党䜓を元の状態にロヌルバックし、ファむルにシリアル化したす。プログラマはデバッグに䟿利になり、党䜓を耇​​補したす2行-シリアル化-非シリアル化、最初に適甚したす関数、モデルが倉曎されおいないように倉曎をコミットし、その埌、2番目の関数を適甚したす。 そしお、サヌバヌチェックされた郚分のすべおの倉曎をJSONの圢匏で゚クスポヌトし、䞍正な実行に含めたす。これにより、恥ずかしいプログラマヌは、䜕をどこで倉曎し、䜕を倉曎すべきではないかをすぐに確認できたす。


もちろん、恐ろしく芋えたすが、実際には7行です。これを行う関数はすべお2番目の段萜からツリヌをトラバヌスするこずを陀く既に準備ができおいるからです。 そしお、これはレセプションであるため、最適ではない行動をずるこずができたす。



第二の方法



もう少し残忍です。ModelRootには1぀のロックフィヌルドがありたすが、2぀に分割できたす。1぀はチェックされたフィヌルドのみをサヌバヌにロックし、もう1぀はチェックされたフィヌルドのみをロックしたす。 この堎合、䜕か間違ったこずをしたプログラマヌは、それを行った堎所に結び付けおすぐにそれに぀いお説明を埗るでしょう。 このアプロヌチの唯䞀の欠点は、ツリヌで1぀のモデルプロパティが未チェックずしおマヌクされおいる堎合、各フィヌルドがマヌクされおいなくおも、ハッシュの蚈算ず倉曎の制埡に関するその䞋のツリヌのすべおが怜査されないこずです。 そしおもちろん、ロックは階局を調べたせん。぀たり、ツリヌのチェックされおいない郚分のすべおのフィヌルドをマヌクする必芁があり、UIの同じクラスずツリヌの通垞の郚分を䜿甚する堎所によっおは機胜したせん。 オプションずしお、そのような構成が可胜です簡略化しお蚘述したす。



 public class GameState : Model { public RootModelData data; public RootModelLocal local; } public class RootModel { public bool locked { get; } }
      
      





次に、各サブツリヌに独自のロックがあるこずがわかりたす。 GameStateはモデルを継承したす。同じ機胜をすべお個別に実装するよりも簡単だからです。



必芁な改善



もちろん、コマンドの凊理を担圓するマネヌゞャヌも新しい機胜を远加する必芁がありたす。 倉曎の本質は、すべおのコマンドがサヌバヌに送信されるのではなく、チェックされた倉曎を䜜成するコマンドのみがサヌバヌに送信されるこずです。 その偎のサヌバヌは、ゲヌム状態ツリヌ党䜓を䞊げるのではなく、チェック察象の郚分のみを䞊げるため、ハッシュはチェック察象の郚分に぀いおのみ䞀臎したす。 サヌバヌでコマンドが実行されるず、コマンドの2぀の関数のうち最初の関数のみが起動されたす。ゲヌム状態のモデルぞの参照を解決する堎合、パスがツリヌの怜蚌䞍可胜な郚分に぀ながる堎合、モデルの代わりにコマンド倉数にnullが配眮されたす。 すべおの非送信チヌムは、通垞のチヌムず正盎に䞊んでいたすが、同時に確認枈みずみなされたす。 それらがラむンに到達し、それらの前に未確認のものがなくなるずすぐに、それらはすぐに2番目の状態に適甚されたす。



実装には基本的に耇雑なものはありたせん。 モデルの各フィヌルドのプロパティには、もう1぀の条件であるツリヌトラバヌサルがありたす。



別の必芁な改良-ツリヌのチェック枈みおよび未チェックの郚分でParsistentModelの個別のファクトリが必芁になり、それらのNextFreeIdは異なりたす。



サヌバヌによっお開始されたコマンド



サヌバヌがクラむアントにコマンドをプッシュしたい堎合、サヌバヌに察するクラむアントの状態が数ステップ先にゞャンプする可胜性があるため、いく぀かの問題がありたす。 䞻な考え方は、サヌバヌがコマンドを送信する必芁がある堎合、次の応答でサヌバヌ通知をクラむアントに送信し、このクラむアントに送信される通知のフィヌルドにそれを曞き蟌みたす。 クラむアントは通知を受信し、それに基づいおコマンドを䜜成し、クラむアントで完了したがただサヌバヌに到達しおいないものの埌に、それをキュヌの最埌に配眮したす。 しばらくするず、モデルを操䜜する通垞のプロセスの䞀郚ずしお、コマンドがサヌバヌに送信されたす。 凊理のためにこのコマンドを受信するず、サヌバヌは発信キュヌから通知をスロヌしたす。 クラむアントが次のパッケヌゞで蚭定時間内に通知に応答しなかった堎合、再起動コマンドが送信されたす。 通知を受け取ったクラむアントが萜ちたり、埌で接続したり、䜕らかの理由でゲヌムをロヌドしたりした堎合、サヌバヌはすべおの通知をコマンドに倉換しおから状態に枡し、その偎でそれらを実行したす。その埌、参加クラむアントに新しい状態を䞎えたす。 サヌバヌがプレむダヌからお金を奪った瞬間にプレむダヌがお金を䜿うこずができた堎合、プレむダヌは負のリ゜ヌスず競合状態になる可胜性があるこずに泚意しおください。 偶然の䞀臎はありそうにありたせんが、DAUが倧きい堎合、ほずんど避けられたせん。 したがっお、このような状況でむンタヌフェヌスずゲヌムのルヌルが死ぬこずはありたせん。



サヌバヌの応答を知る必芁がある実行するコマンド



兞型的な間違いは、乱数はサヌバヌからしか取埗できないず考えるこずです。 共通のsidから開始しお、クラむアントずサヌバヌで同期しお実行される同じ擬䌌乱数ゞェネレヌタヌを䜿甚するこずを劚げるものは䜕もありたせん。 さらに、珟圚のシヌドはゲヌムステヌトに盎接保存できたす。 このゞェネレヌタヌの応答を同期するのが難しいず感じる人もいたす。 実際、このためには、同じ蚘事にもう1぀数字があれば十分です。぀たり、この時点たでにゞェネレヌタヌから受け取った数字の数です。 䜕らかの理由でゞェネレヌタヌが収束しない堎合、どこかに゚ラヌがあり、コヌドは確定的に機胜したせん。 そしお、この事実はカヌペットの䞋に隠されるべきではなく、敎理されお゚ラヌを探すべきです。 䞍思議な箱も含めお、ほずんどの堎合、このアプロヌチで十分です。



ただし、このオプションが適切でない堎合がありたす。 たずえば、非垞に高䟡な賞品をプレむしおいお、cな仲間にゲヌムを逆コンパむルさせたくない堎合、ボットを曞いお、今すぐダむアモンドボックスを開けるず、その前に別の堎所でドラムを回すずどうなるかを事前に䌝えたす。 ランダム倉数ごずにシヌドを個別に保存できたす。これにより、正面からのハッキングを防止できたすが、必芁な補品が珟圚いく぀あるかを通知するボットからの助けにはなりたせん。 さお、最も明癜なケヌス-たれなむベントの可胜性に関する情報をクラむアント蚭定に反映したくない堎合がありたす。 芁するに、サヌバヌの応答を埅぀必芁がある堎合がありたす。

このような状況は、゚ンゞンの远加機胜ではなく、チヌムを2぀に分割するこずで解決する必芁がありたす。最初の状況では、状況を準備し、通知を埅機状態にしたす。 クラむアントでそれらの間のむンタヌフェむスをしっかりずブロックしおも、別のコマンドがすり抜けるこずがありたす。たずえば、゚ネルギヌの単䜍が時間内に埩元されたす。



このような状況はルヌルではなく、䟋倖であるこずを理解するこずが重芁です。 実際、ほずんどのゲヌムに必芁なのは、GetInitialGameStateずいう1぀のチヌムだけです。 そのようなコマンドのもう1぀のパックは、メタゲヌムでのプレむダヌ間の察話、たずえばGetLeaderboardです。 他の200個すべおは決定論的です。



サヌバヌデヌタストレヌゞずサヌバヌ最適化の曖昧なトピック



私はクラむアントであるずすぐに認めたすが、時々、よく知られおいるサヌバヌサヌノァントから、頭に忍び寄らないようなアむデアやアルゎリズムを聞いたこずがありたす。 同僚ずのコミュニケヌションから、理想的な堎合にサヌバヌサむドで自分のアヌキテクチャがどのように機胜するかに぀いおの図を䜜成したした。 ただし、犁忌がありたす。専門のサヌバヌに盞談する必芁がありたす。



たず、デヌタストレヌゞに぀いお。 远加の制限があるのはサヌバヌ偎です。 たずえば、静的フィヌルドの䜿甚が犁止されおいる堎合がありたす。 さらに、コマンドずモデルのコヌドは自動移怍可胜ですが、クラむアントずサヌバヌのプロパティコヌドは䞀臎する必芁はありたせん。 たずえば、memcacheからのフィヌルド倀の遅延初期化たで、あらゆるものを非衚瀺にするこずができたす。 プロパティフィヌルドは、サヌバヌで䜿甚される远加のパラメヌタヌを受け取るこずもできたすが、クラむアントの操䜜には圱響したせん。



サヌバヌの最初の基本的な違いフィヌルドはシリアル化および非シリアル化されたす。 合理的な解決策は、状態ツリヌのほずんどが1぀の巚倧なバむナリたたはjsonフィヌルドにシリアル化されるこずです。 同時に、いく぀かのフィヌルドはテヌブルから取埗されたす。 プレむダヌ間のむンタラクションサヌビスが機胜するためには、䞀郚のフィヌルドの倀が垞に必芁になるため、これが必芁です。 たずえば、アむコンずレベルはさたざたな人々によっお絶え間なく動いおいたす。 それらは通垞のデヌタベヌスに保存するのが最適です。 誰かが自分の領域を調べるこずを決めた堎合、その人の完党なたたは郚分的な、しかし詳现な状態が必芁になるこずはほずんどありたせん。



さらに、ベヌスからフィヌルドを䞀床に1぀ず぀匕き出すのは䞍䟿であり、長時間すべおをドラッグするこずができたす。 私たちのアヌキテクチャでのみ利甚可胜な非垞に非暙準的な゜リュヌションは、クラむアントがコマンドを実行するずきに、ゲッタヌがタッチするテヌブルに個別に保存されたすべおのフィヌルドに関する情報を収集し、サヌバヌがこのフィヌルドのグルヌプを䞊げるこずができるようにコマンドにこの情報を远加するこずで構成されたすデヌタベヌスぞの1぀のリク゚スト。 もちろん、すべおを䞍泚意に觊れた利き腕のあるプログラマヌに起因するDDOSを請うこずのないように、合理的な制限がありたす。



このような別個のストレヌゞでは、あるプレヌダヌが別のプレヌダヌのデヌタにクロヌルするずき、たずえば圌からお金を盗むずき、トランザクション性のメカニズムを考慮する必芁がありたす。 しかし、䞀般的なケヌスでは、通知によっおこれを行いたす。 ぀たり、泥棒はすぐにお金を受け取り、奪われた人は、そのこずになるずお金を取り消すよう指瀺する通知を受け取りたす。



チヌムをサヌバヌ間で分割する方法



サヌバヌにずっお2番目の重芁な瞬間です。 2぀のアプロヌチがありたす。 最初に、芁求たたは芁求のパケットを凊理するために、状態党䜓がデヌタベヌスたたはキャッシュからメモリに䞊げられ、凊理された埌、デヌタベヌスに返されたす。 操䜜は、倚数の異なる実行サヌバヌでアトミックに実行され、共通のベヌスのみを持ち、垞にそうではありたせん。 クラむアントずしお、各チヌムに状態党䜓を䞊げるこずは衝撃的ですが、どのように機胜し、非垞に信頌性が高くスケヌラブルに機胜するかを芋たした。 2番目のオプションは、状態がメモリ内でいったん䞊昇し、クラむアントが珟圚の状態をデヌタベヌスに時々远加するだけで萜ちるたでそこに存圚するこずです。私は、この方法たたはその方法の長所ず短所をあなたに䌝えるこずができたせん。コメントの誰かが私に最初の人が䞀般に生呜に察する暩利を持っおいる理由を説明しおくれるず良いでしょう。 2番目のオプションでは、偶然別のサヌバヌでレむズされたこずが刀明したプレヌダヌ間の察話方法に぀いおの質問が発生したす。これは、たずえば、耇数のクランメンバヌが共同攻撃を開始する準備をしおいる堎合に重芁になるこずがありたす。 10セヌブの遅延で、圌のパヌティヌメンバヌの状態を他の人に瀺すこずはできたせん。残念ながら、ここで開くこずはできたせん。䞊蚘の通知を介したやり取り、あるサヌバヌから別のサヌバヌぞのコマンド-珟時点では、そこで発生したプレヌダヌの珟圚の状態を保存するこずはできたせん。サヌバヌが異なる堎所から同じレベルの可甚性を備えおいる堎合、そしお、バランサヌを管理できるので、あるサヌバヌから別のサヌバヌにプレむダヌを静かに静かに転送するこずができたす。解決策をよく知っおいる堎合は、コメントに必ず蚘茉しおください。



時間をかけお螊る



質問から始めたしょう。私はむンタビュヌで人々を匕き付けるのが本圓に奜きです。ここには、クラむアントずサヌバヌがあり、それぞれにかなり正確な時蚈がありたす。それらがどの皋床異なるかを調べる方法。ナプキンのコヌヒヌショップでこの問題を解決しようずするず、プログラマヌの最高の品質ず最悪の品質が明らかになりたす。事実は、問題には正匏な数孊的に正しい解決策がないこずです。しかし、むンタビュヌを受けた人は、これを知っおいるので、原則ずしお、䞻芁な質問の埌でのみ、5番目に少し時間がかかりたす。そしお、圌がどのようにこのむンスピレヌションに出䌚い、圌が次に䜕をするか-圌のキャラクタヌで最も重芁なこず-この人があなたのプロゞェクトで実際の問題が始たったずきに䜕をするかに぀いお倚くを語っおいたす。



私が知っおいる最良の解決策では、正確な違いを芋぀けるこずはできたせんが、クラむアントからサヌバヌに実行された最適なパケットの時間たでの倚くの芁求-応答ず、サヌバヌからクラむアントに実行された最良のパケットの時間たでの範囲を明確にするこずはできたせん。合蚈するず、これにより数十ミリ秒の粟床が埗られたす。これは、モバむルゲヌムのメタゲヌムに必芁なものよりも䜕倍も優れおいたす。ここではVRマルチプレむダヌやCSはありたせんが、クロック同期の難しさの芏暡ず性質を衚珟するこずはプログラマにずっおはただ良いこずです。最も可胜性が高いのは、30を超える偏差のカットオフを䜿甚しお、長い間、pingずしお実行される平均遅延を知るこずで十分です。



遭遇する可胜性のある2番目のクヌルな状況は、スリップゲヌムのステヌゞングず、携垯電話でのクロックの転送です。どちらの堎合も、アプリケヌションの時間は劇的に急激に倉化するため、これを正しく解決する必芁がありたす。少なくずもゲヌムは再起動したすが、もちろん、各スリップの埌に再起動しない方が良いので、アプリケヌションの起動以降に経過した時間を䜿甚するこずはできたせん。



第䞉に、状況は䜕らかの理由で、䞀郚のプログラマヌにずっお理解しがたい問題ですが、それに察する正しい解決策がありたす操䜜はサヌバヌ時間では絶察に実行できたせん。たずえば、生産の芁求がサヌバヌに到着したずきに商品の生産を開始したす。さもなければ、決定論に別れを告げ、賞をクリックするこずが既に可胜かどうかに぀いおクラむアントずサヌバヌの異なる意芋によっお匕き起こされる1日あたり35,000の非同期をキャッチしたす。正しい決定は、チヌムが実行された時間に関する情報を蚘録するこずです。サヌバヌは、珟圚のサヌバヌ時間ずコマンド内の時間ずの時差が蚱可された間隔内に収たっおいるかどうかを確認し、蚱容範囲内にある堎合は、クラむアントが宣蚀した時間を䜿甚しお䞀郚でコマンドを実行したす。

むンタビュヌの別のタスククラむアントが再起動を詊みるタむムアりト-30秒。蚱容されるサヌバヌ時間差の制限は䜕ですかヒント1間隔は察称ではありたせん。ヒント2このセクションの最初の段萜をもう䞀床読み盎し、゚ッゞ効果で1日あたり3000゚ラヌをキャッチしないように間隔を延長する方法を指定したす。



これが矎しく正しく動䜜するためには、コマンド呌び出しパラメヌタヌに远加のパラメヌタヌ、぀たり呌び出し時間を远加するこずをお勧めしたす。このようなもの



 public interface Command { void Apply(ModelRoot root, long time); }
      
      





ちなみに、私のアドバむスは、モデルの時間にネむティブのUnityタむプを䜿甚しないでください。 UnixTimeをサヌバヌ時間に保存し、垞に䟿利な倉換メ゜ッドを甚意する必芁がある堎合、PValue <long>ずは異なる特別なPTimeフィヌルドにモデルに保存するこずをお勧めしたす。JSONに゚クスポヌトする堎合は、 import人間が読める圢匏の時間。あなたは私に埓うこずができたせん。私はあなたに譊告したした。



第4の状況ゲヌムの状態では、プレヌダヌの参加なしにチヌムを開始しなければならない状況がありたす。たずえば、゚ネルギヌの回埩などです。実際、非垞に䞀般的な状況。私はフィヌルドを持ちたい、それは䟿利に緎習しおいたす。たずえば、PTimeOutでは、コマンドを䜜成しお実行する必芁がある時点を蚘録できたす。コヌドでは、次のようになりたす。



 public class MyModel : Model { public static PTimeOut RESTORE_ENERGY = new PTimeOut() {command = (model, property) => new RestoreEnergyCommand() { model = model}} public long restoreEnergy { get { return RESTORE_ENERGY.Get(this); } set { RESTORE_ENERGY.Set(this, value); }} }
      
      





もちろん、プレヌダヌの初期ロヌド䞭に、サヌバヌは最初にこれらすべおのコマンドの䜜成ず実行を匕き起こし、それからプレヌダヌに状態を䞎える必芁がありたす。ここでの萜ずし穎は、このすべおがこの間にプレヌダヌが受け取る可胜性のある通知を劚害するこずで有名です。したがっお、コマンドの束を同時にプルする必芁がある堎合は、最初の通知の時刻の前にたず時間を緩め、次に通知自䜓からコマンドを䜜成し、次の通知たで時間を緩めお、それを実行する必芁がありたす。この党䜓の䌑日がサヌバヌのタむムアりトに収たらない堎合、およびプレヌダヌが通知でいっぱいになった堎合にこれが可胜であれば、珟圚の状態をメモリからデヌタベヌスに曞き蟌み、代わりにクラむアントに再接続するコマンドで応答したす。



これらのコマンドはすべお、䜜成しお実行に必芁なものに぀いお䜕らかの方法で孊習する必芁がありたす。私の少し束葉杖ですが、䟿利な解決策は、モデルにもう1぀の課題があるこずです。これは、モデルの階局党䜓を移動し、各コマンドが実行された埌、タむマヌでも䜜動したす。もちろん、これはほずんど曎新でツリヌを歩き回る䜙分なオヌバヌヘッドです。代わりに、このフィヌルドを倉曎するたびにゲヌム状態から突き出たcurrentTimeむベントをサブスクラむブたたはサブスクラむブ解陀できたす。



 public partial class Model { public void SetCurrentTime(long time); } vs public partial class RootModel { public event Action<long> setCurrentTime; }
      
      





これは良いこずですが、問題は、モデルツリヌから氞久に削陀され、そのようなフィヌルドを含むモデルはこのむベントにサブスクラむブされたたたであり、正しく動䜜する必芁があるこずです。コマンドを送信する前に、それらがただツリヌ内にあり、このむベントたたは制埡の反転に匱いリンクを持っおいるかどうかを確認したす。これにより、GCにアクセスできなくなるこずはありたせん。



付録1、実生掻から取られた兞型的なケヌス



コメントから最初の郚分に移りたす。おもちゃでは、非垞に倚くの堎合、モデルぞのコマンドの盎埌ではなく、䜕らかのアニメヌションの最埌にいく぀かのアクションが実行されたす。私たちの実践では、ミステリヌボックスが開くようなケヌスがありたした。もちろん、アニメヌションが最埌たで再生されたずきにのみお金が倉わるはずです。私たちの開発者の1人は、圌の人生を簡玠化するこずを決定し、コマンドで倀を倉曎せず、サヌバヌに圌が倉曎したこずを䌝え、アニメヌションの最埌にコヌルバックを実行し、モデルの倀を目的の倀に修正したす。䞀蚀で蚀えば、よくできたした。圌は2週間これらの䞍思議な箱を䜜り、その埌、圌の掻動の結果ずしお衚面化した3぀のキャッチしにくいミスを芋぀けたした。そしお、「通垞の曞き換え」の時間はもちろんですが、誰も匷調できたせんでした。そこから鮮やかに続く結論は、最初からすべおを通垞の反応性で行う方が良いずいうこずだず思いたす。



だから、私の決定はこのようなものです。もちろん、お金は別のフィヌルドにあるのではなく、むンベントリ蟞曞のオブゞェクトの1぀ですが、これは今のずころそれほど重芁ではありたせん。モデルには、サヌバヌによっおチェックされ、それに基づいおビゞネスロゞックが機胜する郚分ず、クラむアント䞊にのみ存圚する郚分がありたす。メむンモデルのお金は、決定が行われるずすぐに発生し、「遅延衚瀺」リストの2番目の郚分では、その倖芳の事実によっおアニメヌションを開始するのず同じ量の芁玠が䜜成され、アニメヌションが終了するず、この芁玠を削陀するコマンドが起動されたす。このような玔粋なクラむアントのメモは、「ただこの金額を衚瀺したせん」たた、実際のフィヌルドでは、フィヌルドの倀だけでなく、フィヌルドの倀からすべおのクラむアント延期を差し匕いた倀が衚瀺されたす。ちょうどそのような2぀のチヌムぞの分割が行われたのは、クラむアントが最初のチヌムの埌でリブヌトし、2番目のチヌムの前にプレヌダヌが受け取ったすべおのお金がマヌクや䟋倖なしで圌のアカりントに入ればどうなるでしょうコヌドでは、次のようになりたす。



 public class OpenMisterBox : Command { public BoxItemModel item; public int slot; //        ,  . public override void Apply(GameState state) { state.inventory[item.revardKey] += item.revardCount; } //       . public override void Apply(GameState state) { var cause = state.NewPersistent<WaitForCommand>(); cause.key = item.key; cause.value = item.value; state.ui.delayedInventoryVisualization.Add(cause); state.ui.mysteryBoxScreen.animations.Add(new Animation() {cause = item, slot = slot})); } } public class MysteryBoxView : View { /* ... */ public override void ConnectModel(MysteryBoxScreenModel model, List<Control> c) { model.Get(c, MysteryBoxScreenModel.ANIMATIONS) .Control(c, onAdd = item => animationFactory(item, OnComleteOrAbort => { AsincQueue(new RemoveAnimation() {cause = item.cause, animation = item}) }), onRemove = item => {} ) } } public class InventoryView : View<InventoryItem> { public Text text; public override void ConnectModel(InventoryItem model, List<Control> c) { model.GameState.ui.Get(c, UIModel.DELAYED_INVENTORY_VISUALIZATION). .Where(c, item => item.key == model.key) .Expression(c, onChange = (IList<InventoryItem> list) => { int sum = 0; for (int i = 0; i < list.Count; i++) sum += list[i].value; return sum; }, onAdd = null, onRemove = null ) //      .Join (c, model.GameState.Get(GameState.INVENTORY).ItemByKey(model.key)) .Expression(c, (delay, count) => count - delay) .SetText(c, text); //     ,      ,   ,  ,   ,       ,     : model.inventory.CreateVisibleInventoryItemCount(c, model.key).SetText(c, text); } } public class RemoveDelayedInventoryVisualization : Command { public DelayCauseModel cause; public override void Apply(GameState state) { state.ui.delayedInventoryVisualization.Remove(cause); } } public class RemoveAnimation : RemoveDelayedInventoryVisualization { public Animation animation public override void Apply(GameState state) { base.Apply(state); state.ui.mysteryBoxScreen.animations.Remove(animation); } }
      
      





最埌に䜕がありたすか 2぀のビュヌがあり、そのうちの1぀でいく぀かのアニメヌションが再生され、その終わりはお金が完党に異なるビュヌで衚瀺されるのを埅っおいたす。すべおが反応的です。い぀でもGameStateの完党な状態をゲヌムに読み蟌むこずができ、アニメヌションの開始を含め、䞭断したずころから正確にプレむを開始したす。アニメヌションステヌゞは消去しないため、真実は最初から始たりたすが、本圓に必芁な堎合は消去するこずもできたす。



合蚈



モデル、コマンド、静的ファむルをルヌルでゲヌムのビゞネスロゞックを䜜成し、矎しく詳现で自動生成されたログでそれらをラップし、新しい機胜を芋たプログラマヌが犯した倚くの兞型的なミスの有益な実行を囲みたす。癜い光。それは、新しい機胜を数倍速く登録できるからです。新しい機胜のダりンロヌドずデバッグが簡単な堎合、ゲヌムデザむナヌは同じプログラマヌを䜿甚しおゲヌムデザむンの実隓を数回行うこずができるため、これは非垞に重芁です。私たちの仕事に敬意を払っお、ゲヌムが倱敗するかどうかは私たち次第ですが、それが射撃するかどうかはgamedismsに䟝存し、実隓のためにスペヌスを䞎える必芁がありたす。

そしお今、私にずっお非垞に重芁な質問に答えるようお願いしたす。あなたが私が䞋手にしたこずをする方法に぀いおのアむデアを持っおいるか、私の答えにコメントしたい堎合、私はコメントであなたを埅っおいたす。倚数の構文゚ラヌに関する協力ず指瀺の提案は、PMでお願いしたす。



All Articles