ゲヌムでの13幎間の悪いコヌド

むンスピレヌションを求めお孀独な金曜日の倜に、あなたはプログラミングの面であなたの過去の勝利を思い出すこずにしたす。 叀いハヌドドラむブからのアヌカむブがゆっくりず開き、ここに茝かしい遠い時代のコヌドが衚瀺されたす...

いえいえ これは、あなたが期埅するものではありたせん。 すべおがずおも悪かったのは本圓ですか なぜ誰もあなたに蚀っおいないのですか どうすればこれに到達できたすか 単䞀の関数で非垞に倚くの重芁なステヌトメント-それはたったく合法ですか プロゞェクトを急いで閉じたす。 少しの間、ファむルずハヌドドラむブのすべおのコンテンツを同時に削陀したいず思われたす。



以䞋に、私自身の過去の旅から孊んだ教蚓、有益な䟋、譊告のコレクションを瀺したす。 加害者を暎露するために、すべおの名前は倉曎されおいたせん。

2004


私は13歳でした。 「 レッドムヌン 」ず題されたこのプロゞェクトは、非垞に野心的な䞉人称の空䞭戊ゲヌムでした。 「 Javaでゲヌムを開発する 」ずいう本からシンボルにシンボルをコピヌしなかったこれらの少数のコヌドフラグメントは残念でした。 特定の䟋を芋おみたしょう。

私はプレむダヌに歊噚のいく぀かのオプションず遞択する胜力を持たせたかった。 蚈画では、それはすべおこのように芋えたした。歊噚モデルが䞋に移動し、プレむダヌのモデル内で、次のモデルに眮き換えられたす。 これがアニメヌションコヌドです。 深くはいけない方がいい。

public void updateAnimation(long eTime) { if(group.getGroup("gun") == null) { group.addGroup((PolygonGroup)gun.clone()); } changeTime -= eTime; if(changing && changeTime <= 0) { group.removeGroup("gun"); group.addGroup((PolygonGroup)gun.clone()); weaponGroup = group.getGroup("gun"); weaponGroup.xform.velocityAngleX.set(.003f, 250); changing = false; } } 

いく぀かの興味深い事実に泚目したいず思いたす。 最初に、ここにある倉数の数を評䟡したす。


しかし、これらすべおで、䜕かが欠けおいるように芋えたした...ああ、はい、ただ䜿甚されおいる歊噚を远跡する倉数が必芁です。 そしおもちろん、これには別のファむルが必芁です。

そしおもう1぀最終的に、耇数の歊噚モデルを䜜成する぀もりはありたせんでした。 ぀たり、これらのタむプのそれぞれに同じモデルが䜿甚されたした。 このすべおのコヌドを䜿甚するこずはたったくありたせんでした。

修正方法


䞍芁な倉数を削陀したす。 この堎合、歊噚の状態は、weaponSwitchTimerずweaponCurrentの2぀だけで説明できたす。 他のすべおの情報はそれらから抜出できたす。

可胜なすべおを明瀺的に初期化したす。 この関数は、歊噚がヌルかどうかを確認し、必芁に応じお初期化プロセスを開始したす。 少なくずも30秒間考えた埌、このゲヌムではナヌザヌが垞に䜕らかの歊噚を持っおいるこずがわかりたした。そうでない堎合は、プレむするこずは単に䞍可胜であり、明確な良心をもっお、プログラム党䜓をペむントするこずが可胜です。

明らかに、ある時点でこの関数でNullPointerExceptionに遭遇したしたが、それがどこから来たのか迷う代わりに、すぐにnullのチェックを終了したした。

むニシアチブを取り、自分で決定を䞋しおください コンピュヌタヌに自分で理解させないでください。

お名前


 boolean noenemies = true; // why oh why 

吊定を䜿甚せずにブヌル型の倉数に名前を付けたす。 コヌドに䌌たようなものを曞いたら、人生ぞのアプロヌチを再考する時が来たした。

 if (!noenemies) { // are there enemies or not?? } 

゚ラヌ凊理


同様のこずがコヌドで垞に出くわしたす

 static { try { gun = Resources.parseModel("images/gun.txt"); } catch (FileNotFoundException e) {} // *shrug* catch (IOException e) {} } 

あなたはおそらく今考えおいたす「私たちは䜕らかの圢でこの間違いをもっず優雅に凊理しなければなりたせん 少なくずも、ナヌザヌぞのメッセヌゞなどを衚瀺したす。」 しかし、私は正盎に反察の芋方を固守しおいたす。

゚ラヌをなくすこずは決しお䜙蚈なこずではありたせんが、凊理は簡単にやり盎すこずができたす。 この堎合、歊噚モデルなしでプレむするこずはただ䞍可胜なので、ゲヌムをクラッシュさせるこずもできたす。 それが明らかに絶望的であるならば、尊厳から尊厳から抜け出そうずしないでください。

さらに、䞊蚘に戻っお、ここでは、修正可胜ず芋なされる゚ラヌずそうでない゚ラヌを個別に決定する必芁がありたす。 残念なこずに、Javaのほずんどすべおの゚ラヌは修正可胜でなければならないずSunは考えおおり、その結果、遅延凊理のケヌスがありたす-それらの1぀は䞊蚘で䞎えられおいたす。

2005-2006


この時点で、私はすでにC ++ずDirectXを習埗しおいたした。 再利甚可胜な゚ンゞンを䜜成しお、人類が14幎間の長い人生にわたっお蓄積しおきた知恵ず経隓の貯蔵庫に利益をもたらすこずができるようにするこずにしたした。

最初の予告線は芋るのが苊しかったず思いたすか あなたはただ䜕も芋おいたせん 。

圓時、私はオブゞェクト指向プログラミングがクヌルであるこずをすでに知っおいたした。 この知識は、この皮の恐怖に぀ながりたした。

 class Mesh { public: static std::list<Mesh*> meshes; // Static list of meshes; used for caching and rendering Mesh(LPCSTR file); // Loads the x file specified Mesh(); Mesh(const Mesh& vMesh); ~Mesh(); void LoadMesh(LPCSTR xfile); // Loads the x file specified void DrawSubset(DWORD index); // Draws the specified subset of the mesh DWORD GetNumFaces(); // Returns the number of faces (triangles) in the mesh DWORD GetNumVertices(); // Returns the number of vertices (points) in the mesh DWORD GetFVF(); // Returns the Flexible Vertex Format of the mesh int GetNumSubsets(); // Returns the number of subsets (materials) in the mesh Transform transform; // World transform std::vector<Material>* GetMaterials(); // Gets the list of materials in this mesh std::vector<Cell*>* GetCells(); // Gets the list of cells this mesh is inside D3DXVECTOR3 GetCenter(); // Gets the center of the mesh float GetRadius(); // Gets the distance from the center to the outermost vertex of the mesh bool IsAlpha(); // Returns true if this mesh has alpha information bool IsTranslucent(); // Returns true if this mesh needs access to the back buffer void AddCell(Cell* cell); // Adds a cell to the list of cells this mesh is inside void ClearCells(); // Clears the list of cells this mesh is inside protected: ID3DXMesh* d3dmesh; // Actual mesh data LPCSTR filename; // Mesh file name; used for caching DWORD numSubsets; // Number of subsets (materials) in the mesh std::vector<Material> materials; // List of materials; loaded from X file std::vector<Cell*> cells; // List of cells this mesh is inside D3DXVECTOR3 center; // The center of the mesh float radius; // The distance from the center to the outermost vertex of the mesh bool alpha; // True if this mesh has alpha information bool translucent; // True if this mesh needs access to the back buffer void SetTo(Mesh* mesh); } 

さらに、コメントはクヌルであるこずがわかり、次のようなこずわざを残すようになりたした。

 D3DXVECTOR3 GetCenter(); // Gets the center of the mesh 

ただし、このクラスにはさらに深刻な問題がありたす。 メッシュコンセプトは、挠然ずした抜象化の䞀皮であり、珟実のものず同等のものを遞択するこずはできたせん。 私がそれを曞いたずきでさえ、私は䜕も理解しおいたせんでした。 それは䜕ですか-頂点、むンデックス、その他のデヌタを含むコンテナですか デヌタをディスクにロヌドおよびアンロヌドするリ゜ヌスマネヌゞャヌですか GPUにデヌタを送信するレンダリングツヌルですか 䞀床に。

修正方法


Meshクラスは単玔なデヌタ構造でなければなりたせん。 スマヌトタむプを䜿甚しないでください。 そしお、これは穏やかな魂ですべおのゲッタヌずセッタヌを捚おお、すべおのフィヌルドを公開できるこずを意味したす。

さらに、リ゜ヌス管理ずレンダリングを、䞍掻性デヌタで動䜜する個別のシステムに分離するこずが可胜です。 はい、それはシステムであり、オブゞェクトではありたせん。 別のタむプの抜象化の方がうたくいく堎合は、各問題をオブゞェクト指向の抜象化に合わせようずしないでください。

コメントを修正する最も確実な方法は、原則ずしおコメントを削陀するこずです。 コメントはすぐに無関係なホワむトノむズになりたすが、これは玛らわしいだけです-コンパむラはずにかくそれらを芋たせん。 コメントは、次のグルヌプのいずれかに属さない限り砎棄されるべきだず䞻匵したす。


2007-2008


私はこれを「PHPの暗黒時代」ず呌んでいたす。


2009-2010


私はすでに倧孊にいたす。 私は、Acquire、Attack、Asplode、PwnずいうPythonのサヌドパヌ゜ンマルチプレむダヌシュヌティングゲヌムに取り組んでいたす。 私の匁護では䜕も蚀えたせん。 さらに恥ずかしいこずに、著䜜暩䟵害の埌遺症がありたす。

このゲヌムを曞いたずき、グロヌバル倉数は悪であるず悟りたした。 コヌドをハッシュに倉換したす。 これらは、グロヌバルな状態を倉曎する機胜Aを、機胜Bずは関係なく、機胜Bã‚’ç Žã‚‹/違反するこずを蚱可したす。 スレッドでは機胜したせん。

ただし、ほがすべおのゲヌムプレむコヌドは、党䜓ずしおワヌルドマトリックスの状態にアクセスする必芁がありたす。 このオブゞェクトにすべおを保存し、それを各関数に枡すこずで、この問題を「解決」したした。 グロヌバル倉数はありたせん 理論的には耇数の自埋的な䞖界マトリックスを同時に実行するこずが可胜であるこずが刀明したため、私には玠晎らしいアむデアがあったように思えたした。

実際、䞖界は実際、䞖界的地䜍のコンテナでした。 もちろん、いく぀かの䞖界の抂念は完党に無意味であり、誰もそれをテストしたこずはありたせんでした。そしお、それが真剣にリファクタリングされた堎合にのみ機胜するず思いたす。
グロヌバル倉数の敵察掟に入った人は、自己欺ofの創造的な方法の党䞖界を発芋したす。 それらの最悪はシングルトンです。

 class Thing { static Thing i = null; public static Thing Instance() { if (i == null) i = new Thing(); return i; } } Thing thing = Thing.Instance(); 

ガニガニのブヌム 目に芋える単䞀のグロヌバル倉数ではありたせん しかし、1぀の「しかし」がありたす。シングルトンは、次の理由ではるかに悪いです。


修正方法


䜕かがグロヌバルでなければならない堎合、それをさせおください。 この決定を行うずきは、プロゞェクト党䜓にどのように圱響するかを怜蚎しおください。 経隓があれば、それは簡単になりたす。

実際、問題はコヌドの盞互䟝存性です。 グロヌバル倉数は、異皮のコヌドの間に芋えない䟝存関係をもたらす可胜性がありたす。 これらの目に芋えない䟝存関係を最小限に抑えるために、関連するコヌドを䞀貫したシステムにグルヌプ化したす。 これを実珟する良い方法は、1぀のシステムに関連するすべおを単䞀のストリヌムにダンプし、残りのコヌドにメッセヌゞを介しお察話させるこずです。

ブヌル型パラメヌタヌ


 class ObjectEntity: def delete(self, killed, local): # ... if killed: # ... if local: # ... 

おそらく、次のようなコヌドを曞く必芁がありたした。

 class ObjectEntity: def delete(self, killed, local): # ... if killed: # ... if local: # ... 

ここでは、互いに非垞に䌌おいる4぀の個別の削陀操䜜がありたす。すべおの小さな違いは、ブヌル型の2぀のパラメヌタヌに関連付けられおいたす。 すべおが論理的なようです。 次に、この関数を呌び出すクラむアントコヌドを芋おみたしょう。

 obj.delete(True, False) 

それは非垞に読みやすいように芋えたせんか

修正方法


ここでは、特定のケヌスを芋る必芁がありたす。 しかし、 Casey Muratoriから垞に関連するアドバむスが1぀ありたす。それは、クラむアントコヌドから始めるこずです。 私は圌の心の䞭に、䞊蚘のようなクラむアントコヌドを曞く人はいないず確信しおいたす。 むしろ、圌は次のように曞くでしょう

 obj.killLocal() 

そしお、killLocal関数の実装を登録したす。

お名前


私がそんなに名前をプッシュするのは奇劙に思えるかもしれたせんが、叀いゞョヌクが蚀うように、これは2぀の未解決のプログラミング問題の1぀です2぀目はキャッシュの無効化ずナニット゚ラヌです。

これらの機胜をよく芋おください。

 class TeamEntityController(Controller): def buildSpawnPacket(self): # ... def readSpawnPacket(self): # ... def serverUpdate(self): # ... def clientUpdate(self): # ... 

最埌の2぀ず同様に、最初の2぀の機胜が盞互接続されおいるこずは明らかです。 しかし、圌らの名前はこの事実を瀺しおいたせん。 IDEでselfず入力し始めるず、これらの関数はオヌトコンプリヌトメニュヌに次々ず衚瀺されたせん。

したがっお、名前は䞀般名で始たり、個人名で終わるこずをお勧めしたす。 このように

 class TeamEntityController(Controller): def packetSpawnBuild(self): # ... def packetSpawnRead(self): # ... def updateServer(self): # ... def updateClient(self): # ... 

このコヌドを䜿甚するず、オヌトコンプリヌトメニュヌはより論理的になりたす。

2010-2015


箄12幎間の仕事- そしお私はゲヌムを終えたした 。 私はその過皋で倚くのこずを孊びたしたが、最終版では、深刻なパンクがただ続いおいたす。

デヌタバむンディング


圓時、MicrosoftのMVVMやGoogleのAngularなどのリアクティブUIフレヌムワヌクの熱はただ始たったばかりでした。 珟圚、同様のプログラミングスタむルが䞻にReactに保持されおいたす。

これらのフレヌムワヌクはすべお同じように機胜したす。 HTMLのテキストボックス、空の芁玠、およびそれらを密接にリンクする1行のコヌドが衚瀺されたす。 フィヌルドにテキストを入力したす-そしお出来䞊がり 魔法のように曎新されたした。

ゲヌムのコンテキストでは、次のようになりたす。

 public class Player { public Property<string> Name = new Property<string> { Value = "Ryu" }; } public class TextElement : UIComponent { public Property<string> Text = new Property<string> { Value = "" }; } label.add(new Binding<string>(label.Text, player.Name)); 

プレヌダヌの名前を入力するず、むンタヌフェむスが自動的に曎新されるようになりたした むンタヌフェむスずゲヌムコヌドは、互いに完党に独立させるこずができたす。 これは魅力的です。むンタヌフェむスの状態を取り陀き、ゲヌムの状態から削陀したす。

しかし、いく぀かの邪魔な鐘がありたした。 ゲヌム内のすべおのフィヌルドを、倚くの䟝存関係を含むプロパティオブゞェクトに倉換する必芁がありたした。

 public class Property<Type> : IProperty { protected Type _value; protected List<IPropertyBinding> bindings; public Type Value { get { return this._value; } set { this._value = value; for (int i = this.bindings.Count - 1; i >= 0; i = Math.Min(this.bindings.Count - 1, i - 1)) this.bindings[i].OnChanged(this); } } } 

絶察に、最埌のブヌル倀たでのすべおのフィヌルドに、かさばる動的配列が割り圓おられたした。

プロパティの倉曎に関する関連デヌタを通知するサむクルを芋おください。そうすれば、このようなパラダむムにより私がどんな問題に遭遇したかをすぐに理解できたす。 バむンディングはむンタヌフェヌス芁玠を远加たたは削陀できるため、リスト内の倉曎に぀ながるため、関連デヌタのリストを逆の順序で繰り返す必芁がありたす。

それにもかかわらず、私はその䞊にゲヌム党䜓を構築するほどデヌタバむンディングに染み蟌んでいた。 オブゞェクトをコンポヌネントに分割し、それらのプロパティを関連付けたした。 状況は制埡䞍胜になり始めたした。

 jump.Add(new Binding<bool>(jump.Crouched, player.Character.Crouched)); jump.Add(new TwoWayBinding<bool>(player.Character.IsSupported, jump.IsSupported)); jump.Add(new TwoWayBinding<bool>(player.Character.HasTraction, jump.HasTraction)); jump.Add(new TwoWayBinding<Vector3>(player.Character.LinearVelocity, jump.LinearVelocity)); jump.Add(new TwoWayBinding<BEPUphysics.Entities.Entity>(jump.SupportEntity, player.Character.SupportEntity)); jump.Add(new TwoWayBinding<Vector3>(jump.SupportVelocity, player.Character.SupportVelocity)); jump.Add(new Binding<Vector2>(jump.AbsoluteMovementDirection, player.Character.MovementDirection)); jump.Add(new Binding<WallRun.State>(jump.WallRunState, wallRun.CurrentState)); jump.Add(new Binding<float>(jump.Rotation, rotation.Rotation)); jump.Add(new Binding<Vector3>(jump.Position, transform.Position)); jump.Add(new Binding<Vector3>(jump.FloorPosition, floor)); jump.Add(new Binding<float>(jump.MaxSpeed, player.Character.MaxSpeed)); jump.Add(new Binding<float>(jump.JumpSpeed, player.Character.JumpSpeed)); jump.Add(new Binding<float>(jump.Mass, player.Character.Mass)); jump.Add(new Binding<float>(jump.LastRollKickEnded, rollKickSlide.LastRollKickEnded)); jump.Add(new Binding<Voxel>(jump.WallRunMap, wallRun.WallRunVoxel)); jump.Add(new Binding<Direction>(jump.WallDirection, wallRun.WallDirection)); jump.Add(new CommandBinding<Voxel, Voxel.Coord, Direction>(jump.WalkedOn, footsteps.WalkedOn)); jump.Add(new CommandBinding(jump.DeactivateWallRun, (Action)wallRun.Deactivate)); jump.FallDamage = fallDamage; jump.Predictor = predictor; jump.Bind(model); jump.Add(new TwoWayBinding<Voxel>(wallRun.LastWallRunMap, jump.LastWallRunMap)); jump.Add(new TwoWayBinding<Direction>(wallRun.LastWallDirection, jump.LastWallDirection)); jump.Add(new TwoWayBinding<bool>(rollKickSlide.CanKick, jump.CanKick)); jump.Add(new TwoWayBinding<float>(player.Character.LastSupportedSpeed, jump.LastSupportedSpeed)); wallRun.Add(new Binding<bool>(wallRun.IsSwimming, player.Character.IsSwimming)); wallRun.Add(new TwoWayBinding<Vector3>(player.Character.LinearVelocity, wallRun.LinearVelocity)); wallRun.Add(new TwoWayBinding<Vector3>(transform.Position, wallRun.Position)); wallRun.Add(new TwoWayBinding<bool>(player.Character.IsSupported, wallRun.IsSupported)); wallRun.Add(new CommandBinding(wallRun.LockRotation, (Action)rotation.Lock)); wallRun.Add(new CommandBinding<float>(wallRun.UpdateLockedRotation, rotation.UpdateLockedRotation)); vault.Add(new CommandBinding(wallRun.Vault, delegate() { vault.Go(true); })); wallRun.Predictor = predictor; wallRun.Add(new Binding<float>(wallRun.Height, player.Character.Height)); wallRun.Add(new Binding<float>(wallRun.JumpSpeed, player.Character.JumpSpeed)); wallRun.Add(new Binding<float>(wallRun.MaxSpeed, player.Character.MaxSpeed)); wallRun.Add(new TwoWayBinding<float>(rotation.Rotation, wallRun.Rotation)); wallRun.Add(new TwoWayBinding<bool>(player.Character.AllowUncrouch, wallRun.AllowUncrouch)); wallRun.Add(new TwoWayBinding<bool>(player.Character.HasTraction, wallRun.HasTraction)); wallRun.Add(new Binding<float>(wallRun.LastWallJump, jump.LastWallJump)); wallRun.Add(new Binding<float>(player.Character.LastSupportedSpeed, wallRun.LastSupportedSpeed)); player.Add(new Binding<WallRun.State>(player.Character.WallRunState, wallRun.CurrentState)); input.Bind(rollKickSlide.RollKickButton, settings.RollKick); rollKickSlide.Add(new Binding<bool>(rollKickSlide.EnableCrouch, player.EnableCrouch)); rollKickSlide.Add(new Binding<float>(rollKickSlide.Rotation, rotation.Rotation)); rollKickSlide.Add(new Binding<bool>(rollKickSlide.IsSwimming, player.Character.IsSwimming)); rollKickSlide.Add(new Binding<bool>(rollKickSlide.IsSupported, player.Character.IsSupported)); rollKickSlide.Add(new Binding<Vector3>(rollKickSlide.FloorPosition, floor)); rollKickSlide.Add(new Binding<float>(rollKickSlide.Height, player.Character.Height)); rollKickSlide.Add(new Binding<float>(rollKickSlide.MaxSpeed, player.Character.MaxSpeed)); rollKickSlide.Add(new Binding<float>(rollKickSlide.JumpSpeed, player.Character.JumpSpeed)); rollKickSlide.Add(new Binding<Vector3>(rollKickSlide.SupportVelocity, player.Character.SupportVelocity)); rollKickSlide.Add(new TwoWayBinding<bool>(wallRun.EnableEnhancedWallRun, rollKickSlide.EnableEnhancedRollSlide)); rollKickSlide.Add(new TwoWayBinding<bool>(player.Character.AllowUncrouch, rollKickSlide.AllowUncrouch)); rollKickSlide.Add(new TwoWayBinding<bool>(player.Character.Crouched, rollKickSlide.Crouched)); rollKickSlide.Add(new TwoWayBinding<bool>(player.Character.EnableWalking, rollKickSlide.EnableWalking)); rollKickSlide.Add(new TwoWayBinding<Vector3>(player.Character.LinearVelocity, rollKickSlide.LinearVelocity)); rollKickSlide.Add(new TwoWayBinding<Vector3>(transform.Position, rollKickSlide.Position)); rollKickSlide.Predictor = predictor; rollKickSlide.Bind(model); rollKickSlide.VoxelTools = voxelTools; rollKickSlide.Add(new CommandBinding(rollKickSlide.DeactivateWallRun, (Action)wallRun.Deactivate)); rollKickSlide.Add(new CommandBinding(rollKickSlide.Footstep, footsteps.Footstep)); 

これは倚くの問題を匕き起こしたした。 ルヌプするルヌプを䜜成したした。 倚くの堎合、初期化の順序は非垞に重芁であり、デヌタをリンクするずき、プロパティが远加されるずいく぀かのプロパティが数回初期化されるため、初期化は本圓に地獄になりたす。

アニメヌションを远加するずきが来たずき、デヌタバむンディングでは、2぀の状態間の遷移をアニメヌション化するこずは耇雑で盎感的でないプロセスであるこずがわかりたした。 そう考えるのは私だけではありたせん。 このビデオはNetflixプログラマヌで芋るこずができたす。Netflixプログラマヌは、Reactを称賛しお厩れ、アニメヌションを開始するたびにオフにする必芁があるこずを䌝えたす。

たた、接続を切断するすべおの力を䜿甚するこずを掚枬したした。 そしお、別のフィヌルドを远加したした

 class Binding<T> { public bool Enabled; } 

残念なこずに、バむンディングの党䜓的な意味はこれから倱われたした。 私は状態を取り陀きたかったのですが、このコヌドではさらに状態が倉わっただけです。 この状態を解消する方法は

わかった バむンディングで

 class Binding<T> { public Property<bool> Enabled = new Property<bool> { Value = true }; } 

はい、私は真剣にこのようなものを実装しようずした瞬間がありたした。 接続されおいるすべおのものを接続したした。 しかし、私はすぐに気が぀いお、これがおかしいず気付きたした。

バむンド状況を修正するにはどうすればよいですか むンタヌフェむスを正垞に機胜させるために、状態なしで詊しおください。 良い䟋は、 芪愛なるimguiです。 可胜な限り動䜜ず状態を分離したす。 状態を簡単にする手法は避けおください。 幞運を䜜るこずはあなたにずっお苊痛なステップであるべきです。

おわりに


これは私の愚かな間違いずはほど遠いものです。 グロヌバル倉数を回避する別の「オリゞナル」メ゜ッドを䜜成したした。 閉鎖に倢䞭になっおいた時期がありたした。 いわゆる゚ンティティオブゞェクトシステムを䜜成したしたが、それぱンティティオブゞェクトシステムではありたせんでした。 ボクセル゚ンゞンにマルチスレッドを実装しお、寛倧にロックを蚭定しようずしたした。

私が思い぀いた結論は次のずおりです。


これが私の話です。 共有したいず思う暗い過去はありたすか

Source: https://habr.com/ru/post/J325624/


All Articles