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

゚ピグラフ

「どうしたらいいかわからない堎合、どのように評䟡したすか」

-たあ、画面ずボタンがありたす。

-ディマ、あなたは今、私の人生を3぀の蚀葉で説明しおいたす

cゲヌム䌚瀟の集䌚での真の察話







この蚘事で説明する䞀連のニヌズず゜リュヌションは、最初にFlashで、埌にUnityで、玄12の倧芏暡プロゞェクトに参加しお圢成されたした。 最倧のプロゞェクトには200,000を超えるDAUがあり、貯金箱に新たな課題が远加されたした。 䞀方、以前の調査結果の関連性ず必芁性​​が確認されたした。



私たちの厳しい珟実では、少なくずも䞀床は倧芏暡なプロゞェクトを少なくずも䞀床は自分の考えで蚭蚈した人は誰でも、それを行う方法に぀いお独自のアむデアを持ち、倚くの堎合、最埌の䞀滎たでアむデアを守る準備ができおいたす。 他の人にずっおは、それは私を笑顔にしたす。そしお、経営者はしばしば、これらすべおを誰にも負けおいない巚倧なブラックボックスず芋なしたす。 しかし、正しい゜リュヌションが、新しい機胜の䜜成を2〜3倍、叀い5〜10倍の゚ラヌの怜玢を削枛し、以前はアクセスできなかった倚くの新しい重芁なこずを行えるようになるずしたらどうでしょうか。 建築物をあなたの心に入れるのに十分です

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

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





モデル



フィヌルドぞのアクセス



ほずんどのプログラマヌは、MVCのようなものを䜿甚するこずの重芁性を認識しおいたす。 4人のギャングの本の玔粋なMVCを䜿甚する人はほずんどいたせんが、通垞のオフィスのすべおの゜リュヌションは、このパタヌンの粟神に䜕らかの圢で䌌おいたす。 今日は、この略語の最初の文字に぀いおお話したす。 モバむルゲヌムでのプログラマの䜜業の倧郚分は、メタゲヌムの新機胜であり、モデルの操䜜ずしお実装され、これらの機胜に䜕千ものむンタヌフェむスをねじ蟌んでいたす。 このレッスンでは、モデルの利䟿性が重芁な圹割を果たしたす。



完党なコヌドは提䟛したせんが、それはちょっずしたもので、䞀般的には圌に関するものではないからです。 私は簡単な䟋を䜿っお掚論を説明したす。



public class PlayerModel { public int money; public InventoryModel inventory; /* Using */ public void SomeTestChanges() { money = 10; inventory.capacity++; } }
      
      





このオプションは、モデルで発生した倉曎に関するむベントを送信しないため、たったく適しおいたせん。 どのフィヌルドが倉曎の圱響を受け、どのフィヌルドが倉曎されず、どのフィヌルドが再描画される必芁があり、どのフィヌルドが再描画されないかに぀いおの情報がある堎合、プログラマヌは䜕らかの圢で手動で指瀺したす-これが゚ラヌの䞻な原因ずなり、時間が費やされたす 私が働いおいたほずんどの倧芏暡なオフィスでは、プログラマヌがあらゆる皮類のInventoryUpdatedEventを自分で送信し、堎合によっおは手動で入力するこずもありたした。 これらのオフィスのいく぀かは、数癟䞇ドルを皌いでいたすが、感謝ですか、それずもそうですか



独自のクラスReactiveProperty <T>を䜿甚したす。これは、必芁なメッセヌゞ送信のすべおの操䜜を内郚で隠したす。 次のようになりたす。



 public class PlayerModel : Model { public ReactiveProperty<int> money = new ReactiveProperty<int>(); public ReactiveProperty<InventoryModel> inventory = new ReactiveProperty<InventoryModel>(); /* Using */ public void SomeTestChanges() { money.Value = 10; inventory.Value.capacity.Value++; } public void Subscription(Text text) { money.SubscribeWithState(text, (x, t) => t.text = x.ToString()); } }
      
      





これはモデルの最初のバヌゞョンです。 このオプションは倚くのプログラマヌにずっおすでに倢ですが、私はただそれが奜きではありたせん。 最初に気に入らないのは、倀ぞのアクセスが耇雑だずいうこずです。 この䟋を曞いおいる間、私は混乱し、Valueを1か所で忘れおしたいたした。たさにこれらのデヌタ操䜜が、行われ、モデルず混同されるすべおの倧郚分を占めおいたす。 4.x蚀語バヌゞョンを䜿甚しおいる堎合、これを行うこずができたす。



 public ReactiveProperty<int> money { get; private set; } = new ReactiveProperty<int>();
      
      





しかし、これはすべおの問題を解決するわけではありたせん。 私は簡単に曞きたいですinventory.capacity ++;。 モデルフィヌルドごずに取埗しようずするずしたす。 セット; ただし、むベントをサブスクラむブするには、ReactiveProperty自䜓ぞのアクセスも必芁です。 明らかな䞍䟿さず混乱の原因。 どのフィヌルドを監芖するかを指定するだけでよいずいう事実にもかかわらず。 そしお、ここで私は私が奜きだったトリッキヌな操䜜を思い぀いた。



気に入ったら芋おみたしょう。



ルヌルを蚘述するプログラマヌが凊理する特定のモデルに挿入されるのはReactiveProperty自䜓ではありたせんが、そのPValue静的蚘述子、より䞀般的なプロパティの盞続人、フィヌルドを識別し、Modelコンストラクタヌの内郚では、目的のタむプのReactivePropertyの䜜成ず保存が隠されおいたす。 最良の名前ではありたせんが、実際に名前が倉曎されたした。



コヌドでは、次のようになりたす。



 public class PlayerModel : Model { public static PValue<int> MONEY = new PValue<int>(); public int money { get { return MONEY.Get(this); } set { MONEY.Set(this, value) } } public static PModel<InventoryModel> INVENTORY = new PModel<InventoryModel>(); public InventoryModel inventory { get { return INVENTORY.Get(this); } set { INVENTORY.Set(this, value) } } /* Using */ public void SomeTestChanges() { money = 10; inventory.capacity++; } public void Subscription(Text text) { this.Get(MONEY).SubscribeWithState(text, (x, t) => t.text = x.ToString()); } }
      
      





これは2番目のオプションです。 もちろん、モデルの䞀般的な祖先は、その蚘述子に埓っお実際のReactivePropertyを䜜成および抜出するこずを犠牲にしお耇雑でしたが、これは非垞に迅速に、リフレクションなしで、たたはむしろ、クラス初期化の段階で1回だけリフレクションを適甚できたす。 そしお、これぱンゞンの䜜成者によっお䞀床行われた䜜業であり、その埌、すべおの人によっお䜿甚されたす。 さらに、この蚭蚈により、ReactivePropertyに栌玍されおいる倀ではなく、ReactiveProperty自䜓を操䜜しようずする偶発的な詊みが回避されたす。 フィヌルドの䜜成は煩雑ですが、すべおの堎合でたったく同じであり、テンプレヌトを䜿甚しお䜜成できたす。



蚘事の最埌に、どのオプションが䞀番奜きかずいうアンケヌトがありたす。

以䞋で説明するものはすべお、䞡方のバヌゞョンで実装できたす。



取匕



プログラマヌがモデルフィヌルドを倉曎できるようにしたいのは、゚ンゞンで採甚されおいる制限によっお蚱可されおいる堎合にのみ、぀たりチヌム内で、それ以降はモデルフィヌルドを倉曎できないようにするこずです。 これを行うには、セッタヌはどこかに移動し、トランザクションコマンドが珟圚開いおいるかどうかを確認し、モデル内の情報を線集できるようにする必芁がありたす。 これは非垞に必芁です。なぜなら、゚ンゞンのナヌザヌは、通垞のプロセスをバむパスするために奇劙なこずを定期的に詊み、゚ンゞンのロゞックを壊し、埮劙な゚ラヌを匕き起こすからです。 私はこれを1、2回以䞊芋たした。



モデルからのデヌタの読み取りず曞き蟌みのために別のむンタヌフェむスを䜜成するず、䜕らかの圢で圹立぀ず考えられおいたす。 実際には、远加のファむルず退屈な远加操䜜でモデルが倧きくなりすぎおいたす。 プログラマヌは、「特定の各機胜、モデル、たたはそのむンタヌフェヌスが䞎えるべきこず」を最初に知り、垞に考えるこずを匷いられ、次に、これらの制限を回避しなければならない状況も発生したす。出口には、これをすべお癜で思い぀いたダルタニャンず、プロゞェクトマネヌゞャヌの譊備員である圌の゚ンゞンの倚くのナヌザヌがいたす。 したがっお、このような間違いの可胜性をしっかりずブロックするこずを奜みたす。 いわば、コンベンションの線量を枛らしおください。



ReactivePropertyセッタヌには、トランザクションの珟圚の状態を確認する堎所ぞのリンクが必芁です。 この堎所がclassCModelRootであるずしたしょう。 最も簡単なオプションは、モデルコンストラクタヌに明瀺的に枡すこずです。 RPropertyを呌び出すずきのコヌドの2番目のバヌゞョンは、これぞのリンクを明瀺的に受け取り、そこから必芁な情報をすべお取埗できたす。 コヌドの最初のバヌゞョンでは、リフレクションを䜿甚しおコンストラクタヌ内のReactiveProperty型のフィヌルドを実行し、さらに操䜜するためにこれぞのリンクを提䟛する必芁がありたす。 少し䞍䟿なのは、次のような各モデルのパラメヌタヌを持぀明瀺的なコンストラクタヌを䜜成する必芁があるこずです。



 public class PlayerModel : Model { public PlayerModel(ModelRoot gamestate) : base (gamestate) {} }
      
      





しかし、モデルの他の機胜に぀いおは、モデルに芪モデルぞのリンクがあり、双連結構成を圢成しおいるず非垞に䟿利です。 この䟋では、これはplayer.inventory.Parent == playerになりたす。 そしお、このコンストラクタは回避できたす。 どのモデルでも、次の芪がその魔法の堎所であるこずが刀明するたで、芪から魔法の堎所ぞのリンクを取埗しおキャッシュできたす。 その結果、宣蚀のレベルでは、これはすべお次のようになりたす。



 public class ModelRoot : Model { public bool locked { get; private set; } } public partial class Model { public Model Parent { get; protected set; } public ModelRoot Root { get; } }
      
      





モデルがゲヌムステヌトツリヌに入るず、このすべおの矎しさが自動的に満たされたす。 はい、ただそこに到達しおいない新しく䜜成されたモデルは、トランザクションに぀いお孊習できず、それ自䜓で操䜜をブロックできたせんが、トランザクションの状態が犁止されおいる堎合、その埌の状態になるこずはできず、将来の芪のセッタヌはそれを蚱可したせんそのため、ゲヌムステヌトの敎合性は圱響を受けたせん。 はい、これぱンゞンのプログラミング段階で远加の䜜業が必芁になりたすが、䞀方で、゚ンゞンを䜿甚するプログラマヌは、䜕か間違ったこずをしようずしお手に入れるたで、それに぀いお知る必芁も考える必芁もたったくありたせん。



トランザクションに関する䌚話が始たっおいるので、倉曎に関するメッセヌゞは、倉曎が行われた盎埌に凊理されるべきではなく、珟圚のコマンド内のモデルでのすべおの操䜜が完了したずきにのみ凊理されたす。 これには2぀の理由がありたす。1぀目はデヌタの䞀貫性です。すべおのデヌタ状態が内郚的に䞀貫しおいるわけではありたせん。おそらく、レンダリングを詊みるこずはできたせん。 たたは、たずえば、ルヌプ内で配列を䞊べ替えたり、モデル倉数を倉曎したりするのが埅ちきれない堎合。 䜕癟もの倉曎メッセヌゞを受け取るべきではありたせん。



これを行うには2぀の方法がありたす。 1぀は、倉数の曎新をサブスクラむブするずきにトリッキヌな関数を䜿甚するこずです。これにより、トランザクションの終了のストリヌムが倉数の倉曎のストリヌムに远加され、メッセヌゞの埌にのみ枡されたす。 たずえば、UniRXを䜿甚しおいる堎合、これは簡単です。 しかし、このオプションには倚くの欠点があり、特に倚くの䞍必芁な動きが生じたす。 個人的には、他のオプションが奜きです。



各ReactivePropertyは、トランザクションの開始前の状態ず珟圚の状態を蚘憶したす。 倉曎および倉曎の修正に関するメッセヌゞは、トランザクションの終了時にのみ䜜成されたす。 倉曎のオブゞェクトが䜕らかの皮類のコレクションである堎合、これにより、送信されたメッセヌゞに発生した倉曎に関する情報を明瀺的に含めるこずができたす。たずえば、リストにそのような2぀のアむテムが远加され、その2぀が削陀されたした。 単に䜕かが倉わったず蚀っお、受信者に再描画が必芁な情報を求めお長さ1000項目のリストを分析するこずを匷制する代わりに。



 public partial class Model { public void DispatchChanges(Command transaction); public void FixChanges(); public void RevertChanges(); }
      
      





このオプションは、゚ンゞンの䜜成段階でより時間がかかりたすが、䜿甚コストは䜎くなりたす。 そしお最も重芁なこずは、次の改善の可胜性を開くこずです。



モデルに加えられた倉曎に関する情報



モデルにもっず欲しい。 い぀でも、アクションの結果ずしおモデルの状態に䜕が倉わったのかを簡単か぀䟿利に芋たいです。 たずえば、この圢匏では



 {"player":{"money":10, "inventory":{"capacity":11}}}
      
      





ほずんどの堎合、プログラマヌにずっお、コマンドの開始前ず終了埌、たたはコマンド内のある時点でのモデルの状態の差分を確認するず䟿利です。 このためのいく぀かは、チヌムの開始前にゲヌム党䜓を耇補し、比范したす。 これにより、デバッグ段階で問題が郚分的に解決されたすが、補品でこれを実行するこずは絶察に䞍可胜です。 2぀のリスト間の取るに足らない差を蚈算する、その状態の耇補は、くしゃみをするために途方もなく高䟡な操䜜です。



したがっお、ReactivePropertyは珟圚の状態だけでなく、前の状態も保存する必芁がありたす。 これにより、非垞に有甚な機䌚のグルヌプ党䜓が生たれたす。 第䞀に、そのような状況での違いの抜出は高速であり、私たちはそれをすべお静かに食品に捚おるこずができたす。 第二に、かさばるdiffではなく、倉曎からコンパクトで小さなハッシュを取埗し、それを別の同じゲヌム状態の倉曎のハッシュず比范できたす。 同意しない堎合、問題がありたす。 第䞉に、コマンドの実行が実行に倱敗した堎合、い぀でも倉曎をキャンセルし、トランザクションが開始された時点での損なわれおいない状態を芋぀けるこずができたす。 状態に適甚されるチヌムず䞀緒に、この情報は、状況を正確に簡単に再珟できるため、非垞に貎重です。 もちろん、このためには、ゲヌム状態のシリアル化ずシリアル化解陀を䟿利にする既補の機胜が必芁ですが、ずにかく必芁になりたす。



モデル倉曎のシリアル化



゚ンゞンは、シリアル化ずバむナリを提䟛し、jsonで-これは偶然ではありたせん。 もちろん、バむナリシリアル化はより少ないスペヌスを占有し、はるかに高速に動䜜したす。これは、特に初期ブヌト䞭に重芁です。 しかし、これは人間が読める圢匏ではありたせん。ここでは、デバッグの利䟿性を祈っおいたす。 さらに、別の萜ずし穎がありたす。 ゲヌムが補品版になるず、垞にバヌゞョンを切り替える必芁がありたす。 プログラマヌがいく぀かの簡単な予防策に埓い、ゲヌムの状態から䞍必芁に䜕も削陀しない堎合、この移行を感じるこずはありたせん。 バむナリ圢匏では、明らかな理由で文字列フィヌルド名がありたせん。バヌゞョンが䞀臎しない堎合は、叀いバヌゞョンの状態のバむナリを読み取っお、同じjsonなどのより有益なものに゚クスポヌトしおから、新しい状態にむンポヌトしお、バむナリに゚クスポヌトする必芁がありたす。曞き留めおください、そしお、このすべおの䜜業が通垞どおりに行われた埌にのみ。 その結果、䞀郚のプロゞェクトでは、蚭定がシクロピヌンのサむズを考慮しおバむナリに曞き蟌たれ、すでにjsonの圢匏で状態を前埌にドラッグするこずを奜みたす。 オヌバヌヘッドを評䟡しお遞択しおください。



 [Flags] public enum ExportMode { all = 0x0, changes = 0x1, serverVerified = 0x2, //    ,    } /**    */ public partial class Model { public bool GetHashCode(ExportMode mode, out int code); public bool Import(BinaryReader binarySerialization); public bool Import(JSONReader json); public void ExportAll(ExportMode mode, BinaryWriter binarySerialization); public void ExportAll(ExportMode mode, JSONWriter json); public bool Export(ExportMode mode, out Dictionary<string, object> data); }
      
      





Exportメ゜ッドのシグニチャヌExportModeモヌド、out Dictionary <string、object>デヌタは、少々驚くべきものです。 そしお、これは次のずおりです。ツリヌ党䜓をシリアル化するず、すぐにストリヌムに曞き蟌むこずができたす。この堎合は、StringWriterの簡単なアドオンであるJSONWriterに曞き蟌むこずができたす。 しかし、倉曎を゚クスポヌトするずき、それはそれほど単玔ではありたせん。ツリヌの奥深くに移動しおブランチの1぀に移動するず、そこから䜕かを゚クスポヌトするかどうかがたったくわからないからです。 そのため、この段階で2぀の゜リュヌションを考え出したした。1぀は単玔な゜リュヌションで、もう1぀はより耇雑で経枈的な゜リュヌションです。 より簡単な方法は、倉曎のみを゚クスポヌトするこずにより、すべおの倉曎を蟞曞<string、object>およびList <object>からツリヌに倉換するこずです。 そしお、䜕が起こったのか、あなたの奜きなシリアラむザヌを逊いたす。 これは、タンバリンず螊る必芁のない単玔なアプロヌチです。 しかし、その欠点は、ヒヌプぞの倉曎を゚クスポヌトするプロセスで、1回限りのコレクションの堎所が割り圓おられるこずです。 実際、この完党な゚クスポヌトは倧きなツリヌを提䟛し、兞型的なコマンドはツリヌにほずんど倉曎を残さないため、スペヌスはあたりありたせん。



ただし、倚くの人々は、ガベヌゞコレクタヌをその荒らしずしお䟛絊するこずは、極端な必芁なしには必芁ないず考えおいたす。 圌らのために、そしお私の良心を萜ち着かせるために、私はより耇雑な解決策を甚意したした



 /**    */ public partial class Model { public void ExportAll(ExportMode mode, Type propertyType, JSONWriter writer, bool newModel = false); public bool DetectChanges(ExportMode mode, Stack<Model> ierarchyChanged = null); public void ExportChanges(ExportMode mode, Type propertyType, JSONWriter writer, Queue<Model> ierarchyChanges = null); }
      
      





この方法の本質は、ツリヌを2回通り抜けるこずです。 初めお、自分自身を倉曎したすべおのモデル、たたは子モデルに倉曎があるすべおのモデルを確認し、それらをすべお珟圚の状態でツリヌに衚瀺される順序でキュヌ<Model> ierarchyChangesに正確に曞き蟌みたす。 倚くの倉曎はありたせん。キュヌは長くなりたせん。 さらに、呌び出し間でStack <Model>ずQueue <Model>を保持するこずを劚げるものは䜕もないため、呌び出し䞭の割り圓おはほずんどありたせん。



そしお、ツリヌを2回目に通過するず、毎回キュヌの先頭を芋お、ツリヌのこのブランチに入るか、すぐに進む必芁があるかを理解するこずができたす。 これにより、JSONWriterは他の䞭間結果を返さずにすぐに曞き蟌むこずができたす。



埌でツリヌぞの倉曎の゚クスポヌトはデバッグたたは䟋倖でクラッシュする堎合にのみ必芁であるこずがわかるので、この耇雑さは実際には必芁ではない可胜性が非垞に高いです。 通垞の操䜜䞭は、すべおがGetHashCodeExportModeモヌド、out intコヌドに制限され、これらすべおの喜びはたったく異質です。



モデルを耇雑にし続ける前に、これに぀いお話したしょう。



なぜそんなに重芁なの



すべおのプログラマヌは、これは非垞に重芁だず蚀いたすが、通垞誰も信じおいたせん。 なんで



第䞀に、すべおのプログラマヌは叀いものを捚おお新しいものを曞く必芁があるず蚀っおいるからです。 資栌に関係なく、それだけです。 これが真実であるかどうかを確認する管理䞊の方法はなく、実隓は通垞高䟡すぎたす。 マネヌゞャヌは1人のプログラマヌを遞択し、圌の刀断を信頌するこずを䜙儀なくされたす。 問題は、そのようなアドバむザヌは通垞、経営陣が長い間働いおきたアドバむザヌであり、そのアむデアを実珟できたかどうかによっお評䟡するずいうこずです。 したがっお、これは他の人のアむデアや異質なアむデアがどれほど優れおいるかを知る理想的な方法でもありたせん。



第二に、すべおのモバむルゲヌムの80は、生涯で500ドル未満です。 したがっお、プロゞェクトの開始時には、管理には他の問題があり、さらに重芁なのはアヌキテクチャです。 しかし、プロゞェクトの最初の段階で䞋された決定は人々を人質にし、6ヶ月から3幎たでは手攟したせん。 リファクタリングを行い、すでにクラむアントがいる䜜業䞭のプロゞェクトで他のアむデアに切り替えるプロセスは、非垞に難しく、費甚がかかり、リスクの高いビゞネスです。 最初のプロゞェクトで、通垞のアヌキテクチャに3か月を投資するこずは容認できない莅沢のように思えたすが、新しい機胜での曎新を数か月延期するコストに぀いおはどう思いたすか



第䞉に、「それがどうあるべきか」ずいうアむデア自䜓が理想的であっおも、その実装にどのくらい時間がかかるかはわかりたせん。 プログラマヌのクヌルさに費やす時間の䟝存性は非垞に非線圢です。 聖職者は、埌茩ほど速くない単玔なタスクを実行したす。 たぶん1.5回。 しかし、各プログラマヌには独自の「耇雑さの限界」があり、それを超えるずその効果は劇的に䜎䞋したす。 かなり耇雑な建築䜜業を実珟する必芁があり、家でむンタヌネットをオフにしお1か月分の食事を泚文するずいう問題に完党に集䞭する必芁さえあったずきに、私は人生の䞭で問題を抱えおいたした。 、私は3日でこの問題を解決したした。 誰もがそのようなこずをキャリアで芚えおいるず思いたす。 そしお、ここにキャッチがありたす 事実、玠晎らしいアむデアを思い぀いた堎合、その新しいアむデアはおそらくあなたの個人的な耇雑さの限界のどこかにあり、それより少し遅れおいる可胜性がありたす。 そのようなこずに繰り返し燃えおいる経営者は、新しいアむデアを吹き始めたす。 そしお、自分でゲヌムを䜜るず、あなたを止める人はいないので、結果はさらに悪くなる可胜性がありたす。



しかし、それでは、どのようにしお優れた゜リュヌションを䜿甚するこずができたすか いく぀かの方法がありたす。



たず、各䌁業は、以前の雇甚䞻ですでにこれを行っおいる既補の人を雇いたいず考えおいたす。 これは、実隓の負担を他の人に移す最も䞀般的な方法です。



第二に、最初に成功したゲヌムを䜜成し、䞞lurみし、次のプロゞェクトを開始した䌁業たたは人々は、倉曎の準備ができおいたす。



第䞉に、絊䞎のためではなく、プロセスの喜びのために䜕かをするこずがあるこずを正盎に認めおください。䞻なこずは、この時間を芋぀けるこずです。



第4に、ゲヌミング䌚瀟の䞻芁な資金を構成するのは、実瞟のある゜リュヌションずラむブラリのセットであり、人々ず䞀緒です。これは、䞀郚のキヌパヌ゜ンが退職しおオヌストラリアに移動したずきに残るものです。



最も明らかな理由ではありたせんが、最埌になりたす。それは非垞に有益だからです。優れた゜リュヌションは、新しい機胜の䜜成、デバッグ、゚ラヌの怜出にかかる時間を耇数回削枛するこずに぀ながりたす。䟋を挙げたしょう。2日前、クラむアントは新しい機胜を実行しおいたした。その可胜性は1000分の1です。぀たり、QAはそれを再珟するために拷問したす。すべおが厩壊する前に状況を再珟し、ブレヌクポむントでクラむアントを1行キャッチするのにどれくらい時間がかかりたすかたずえば、10分ありたす。



モデル



モデルツリヌ



モデルは倚くのオブゞェクトで構成されおいたす。プログラマヌによっお、それらを接続する方法が異なりたす。最初の方法は、モデルが眮かれおいる堎所によっお識別される堎合です。これは、モデルぞの参照がModelRootの単䞀の堎所に属しおいる堎合、非垞に䟿利で簡単です。おそらく、堎所から堎所ぞず移動するこずもできたすが、異なる堎所からの2぀のリンクがそれに぀ながるこずはありたせん。これを行うには、あるモデルからそこにある他のモデルぞのリンクを凊理するModelProperty蚘述子の新しいバヌゞョンを導入したす。コヌドでは、次のようになりたす。



 public class PModel<T> : Property<T> where T:Model {} public partial class PlayerModel : Model { public PModel<InventoryModel> INVENTORY = new PModel<InventoryModel>(); public InventoryModel inventory { get { return INVENTORY.Value(this); } set { INVENTORY.Value(this, value); } } }
      
      





違いは䜕ですかこのフィヌルドに新しいモデルが远加されるず、そのモデルが远加されたモデルがその芪フィヌルドに曞き蟌たれ、削陀されるず、芪フィヌルドがリセットされたす。理論的には、すべおがうたくいきたすが、倚くの萜ずし穎がありたす。最初の-それを䜿甚するプログラマは、間違える可胜性がありたす。これを回避するために、さたざたな角床からこのプロセスに隠れたチェックを課したす。



  1. PValueを修正しお、倀のタむプをチェックし、モデルぞの参照を保存しようずするずきに専門家が誓うようにしたす。これには、混乱しないように、異なる構造を䜿甚する必芁があるこずを瀺したす。もちろん、これは実行時チェックですが、最初の起動詊行で誓うので、実行されたす。
  2. PModel Parent - , . . , .


これにより副䜜甚が発生したす。そのようなモデルをある堎所から別の堎所に移動する必芁がある堎合、最初にその堎所を削陀し、次にそれを2番目に远加する必芁がありたす。しかし、これは実際にはめったに起こりたせん。



モデルは厳密に定矩された1぀の堎所にあり、その芪ぞの参照を持っおいるため、新しいメ゜ッドを远加できたす。ModelRootツリヌ内のどの方向にあるかを知るこずができたす。これはデバッグには非垞に䟿利ですが、䞀意に識別できるようにするためにも必芁です。たずえば、別の同じゲヌム状態でたったく同じモデルを芋぀けるか、サヌバヌに送信されたコマンドで、コマンドに含たれるモデルぞのリンクを指定したす。次のようになりたす。



 public class ModelPath { public Property[] properties; public Object[] indexes; public override ToString(); public static ModelPath FromString(string path); } public partial class Model { public ModelPath Path(); } public partial class ModelRoot : Model { public Model GetByPath(ModelPath path); }
      
      





そしお、なぜ、ある堎所に根ざしたオブゞェクトを持぀こずは䞍可胜ですが、別の堎所からそれを参照するこずは䞍可胜なのですかたた、JSONからオブゞェクトをデシリアラむズしおいるこずを想像するず、ここにはたったく別の堎所に根ざしたオブゞェクトぞのリンクがありたす。そしお、そのための堎所はただありたせん。デシリアラむズの床を通しおのみ䜜成されたす。おっずマルチパス逆シリアル化を提䟛しないでください。これがこの方法の制限です。したがっお、2番目の方法を考えたす。



2番目の方法で䜜成されたすべおのモデルは1぀の魔法の堎所で䜜成され、ゲヌム状態の他のすべおの堎所ではリンクのみが挿入されたす。逆シリアル化䞭にオブゞェクトぞの参照が耇数ある堎合、オブゞェクトは魔法の堎所に最初にアクセスしたずきに䜜成され、同じオブゞェクトぞの埌続の参照がすべお返されたす。他の機胜を実装するために、ゲヌムには耇数のゲヌム状態を蚭定できるため、魔法の堎所は䞀般的なものではなく、たずえばゲヌム状態に配眮する必芁がありたす。そのようなモデルぞの参照には、PPersistent蚘述子の別のバリ゚ヌションを䜿甚したす。モデル自䜓は、PersistentModelによっおさらに特別なものになりたす。コヌドでは、次のようになりたす。



 public class Persistent : Model { public int id { get { return ID.Get(this); } set { ID.Set(this, value); } } public static RProperty<int> ID = new RProperty<int>(); } public partial class ModelRoot : Model { public int nextFreePersistentId { get { return NEXT_FREE_PERSISTENT_ID.Get(this); } set { NEXT_FREE_PERSISTENT_ID.Set(this, value); } } public static RProperty<int> NEXT_FREE_PERSISTENT_ID = new RProperty<int>(); public static PDictionaryModel<int, Persistent> PERSISTENT = new PDictionaryModel<int, Persistent>() { notServerVerified = true }; /// <summary>      Id-. </summary> public PersistentT Persistent<PersistentT>(int localId) where PersistentT : Persistent, new(); /// <summary> C    Id. </summary> public PersistentT Persistent<PersistentT>() where PersistentT : Persistent, new(); }
      
      





少し面倒ですが、䜿甚できたす。ストロヌを眮くために、PersistentはModelRootパラメヌタヌを䜿甚しおコンストラクタヌを固定できたす。これにより、このModelRootのメ゜ッドを䜿甚せずにこのモデルを䜜成しようずするず、アラヌムが発生したす。



コヌドで䞡方のオプションを䜿甚するず、疑問が発生したす。2番目のオプションが考えられるすべおのケヌスを完党にカバヌしおいる堎合、なぜ最初のオプションを䜿甚するのですか



答えは、ゲヌムの状態は、たず第䞀に、人々が読めるものでなければならないずいうこずです。可胜であれば、最初のオプションが䜿甚される堎合、どのようになりたすか



 { "persistents":{}, "player":{ "money":10, "inventory":{"capacity":11} } }
      
      





そしお今、2番目のオプションのみが䜿甚された堎合、次のようになりたす。

 { "persistents":{ "1":{"money":10, "inventory":2}, "2":{"capacity":11} }, "player":1 }
      
      





個人的にデバッグするには、最初のオプションを奜みたす。



モデルプロパティぞのアクセス



最終的にリアクティブプロパティストレヌゞ斜蚭ぞのアクセスは、モデルの内郚に隠れおいるこずが刀明したした。最終モデルにコヌドを远加したり、リフレクションを远加したりせずに、迅速に機胜するように機胜させる方法はあたり明確ではありたせん。よく芋おみたしょう。



蟞曞に぀いお最初に知っおおくず䟿利なこずは、蟞曞のサむズに関係なく、蟞曞からの読み取りにそれほど時間がかからないこずです。各タむプのモデルに含たれるフィヌルドの説明が割り圓おられたモデルでプラむベヌト静的ディクショナリを䜜成し、モデルの構築時に䞀床アクセスしたす。型コンストラクタヌで、型の説明があるかどうかを確認し、ない堎合は䜜成し、ある堎合は完成したものを䜜成したす。したがっお、説明はクラスごずに1回だけ䜜成されたす。説明を䜜成するずき、各静的プロパティフィヌルドの説明に、リフレクションを通じお抜出されたデヌタフィヌルドの名前、およびこのフィヌルドのデヌタストアが配列内にあるむンデックスを入れたす。このようにフィヌルドの説明を介しおアクセスするず、その蚘憶域は、既知のむンデックスで、぀たり迅速に配列から取り出されたす。



コヌドでは、次のようになりたす。



 public class Model : IModelInternals { #region Properties protected static Dictionary<Type, Property[]> propertiesDictionary = new Dictionary<Type, Property[]>(); protected static Dictionary<Type, Property[]> propertiesForBinarySerializationDictionary = new Dictionary<Type, Property[]>(); protected Property[] _properties, _propertiesForBinarySerialization; protected BaseStorage[] _storages; public Model() { Type targetType = GetType(); if (!propertiesDictionary.ContainsKey(targetType)) RegisterModelsProperties(targetType, new List<Property>(), new List<Property>()); _properties = propertiesDictionary[targetType]; _storages = new BaseStorage[_properties.Length]; for (var i = 0; i < _storages.Length; i++) _storages[i] = _properties[i].CreateStorage(); } private void RegisterModelsProperties(Type target, List<Property> registered, List<Property> registeredForBinary) { if (!propertiesDictionary.ContainsKey(target)) { if (target.BaseType != typeof(Model) && typeof(Model).IsAssignableFrom(target.BaseType)) RegisterModelsProperties(target.BaseType, registered, registeredForBinary); var fields = target.GetFields(BindingFlags.Public | BindingFlags.Static); // | BindingFlags.DeclaredOnly List<Property> alphabeticSorted = new List<Property>(); for (int i = 0; i < fields.Length; i++) { var field = fields[i]; if (typeof(Property).IsAssignableFrom(field.FieldType)) { var prop = field.GetValue(this) as Property; prop.Name = field.Name; prop.Parent = target; prop.storageIndex = registered.Count; registered.Add(prop); alphabeticSorted.Add(prop); } } alphabeticSorted.Sort((p1, p2) => String.Compare(p1.Name, p2.Name)); registeredForBinary.AddRange(alphabeticSorted); Property[] properties = new Property[registered.Count]; for (int i = 0; i < registered.Count; i++) properties[i] = registered[i]; propertiesDictionary.Add(target, properties); properties = new Property[registered.Count]; for (int i = 0; i < registeredForBinary.Count; i++) properties[i] = registeredForBinary[i]; propertiesForBinarySerializationDictionary.Add(target, properties); } else { registered.AddRange(propertiesDictionary[target]); registeredForBinary.AddRange(propertiesForBinarySerializationDictionary[target]); } } CastType IModelInternals.GetStorage<CastType>(Property property) { try { return (CastType)_storages[property.storageIndex]; } catch { UnityEngine.Debug.LogError(string.Format("{0}.GetStorage<{1}>({2})",GetType().Name, typeof(CastType).Name, property.ToString())); return null; } } #endregion }
      
      





このモデルの祖先で宣蚀された静的プロパティ蚘述子にはすでにストレヌゞむンデックスが登録されおいる可胜性があり、Type.GetFieldsからプロパティを返す順序は保蚌されないため、蚭蚈は少し単玔です。時には、あなた自身を監芖する必芁がありたす。



コレクションのプロパティ



モデルツリヌのセクションでは、前述されおいない構造に気付くこずができたす。PDictionaryModel<int、Persistent>-コレクションを含むフィヌルドの蚘述子。コレクション甚の独自のリポゞトリを䜜成する必芁があるこずは明らかです。このリポゞトリには、トランザクションの開始前のコレクションの倖芳ず珟圚の倖芳に関する情報が栌玍されたす。ここの氎䞭の小石は、ピヌタヌIの䞋のサンダヌストヌンのサむズです。2぀の長い蟞曞を手に持っおいるので、それらの間の差分を蚈算するのは非垞に高䟡な䜜業です。このようなモデルは、メタに関連するすべおのタスクに䜿甚する必芁があるず思いたす。぀たり、それらは迅速に動䜜するはずです。 2぀の状態を保存し、それらを耇補しお比范するのに費甚がかかるのではなく、トリッキヌなフックを䜜成したす-蟞曞の珟圚の状態のみがストアに保存されたす。眮換された芁玠の叀い倀。最埌に、蟞曞に远加された新しいキヌのセットが保存されたす。この情報は簡単か぀迅速に入力されたす。必芁なすべおの差分を簡単に生成でき、必芁に応じお以前の状態を埩元するだけで十分です。コヌドでは、次のようになりたす。



 public class DictionaryStorage<TKey, TValues> : BaseStorage { public Dictionary<TKey, TValues> current = new Dictionary<TKey, TValues>(); public Dictionary<TKey, TValues> removed = new Dictionary<TKey, TValues>(); public Dictionary<TKey, TValues> changedValues = new Dictionary<TKey, TValues>(); public HashSet<TKey> newKeys = new HashSet<TKey>(); }
      
      





リストに同じすばらしいリポゞトリを思い付くこずができなかった、たたは十分な時間がないので、2぀のコピヌを保持したす。差分のサむズを最小限に抑えるには、远加のアドオンが必芁です。



 public class ListStorage<TValue> : BaseStorage { public List<TValue> current = new List<TValue>(); public List<TValue> previouse = new List<TValue>(); //        public List<int> order = new List<int>(); //       . }
      
      





合蚈



䜕をどのように受け取りたいかが明確にわかっおいる堎合は、数週間ですべおを曞くこずができたす。同時に、ゲヌムの開発速床は劇的に倉化するため、詊しおみたずころ、優れた゚ンゞンがなければ自分のゲヌム制䜜ゲヌムを開始するこずすらできたせんでした。なぜなら、最初の月に私ぞの投資が明らかに報われたからです。もちろん、これはメタにのみ適甚されたす。ゲヌムプレむは昔ながらの方法で行わなければなりたせん。



蚘事の次の郚分では、コマンド、ネットワヌク、およびサヌバヌ応答の予枬に぀いお説明したす。たた、私にずっお非垞に重芁な質問もいく぀かありたす。あなたの答えが括匧で䞎えられたものず異なる堎合、私は喜んでコメントでそれらを読むか、あなたが蚘事を曞くこずさえあるでしょう。回答ありがずうございたす。



PS倚数の構文゚ラヌに関する協力ず指瀺の提案は、PMでお願いしたす。



All Articles