Metal Gear Mechanics
A recreation of Metal Gear Solid's stealth loop in Unity. Guards patrol set routes, notice the player through a vision cone backed by a line-of-sight check, and go through scan and search states before returning to patrol. It also has CQC, the cardboard box, and wall cover.
My role
Solo project. I wrote all of the gameplay code: the AI, items and inventory, the managers, scene loading, UI, and helpers like the object pooler. The environment art, particle effects and the PS1-style post-processing are asset packs.
The problem
Every guard has to check what's near it every frame. Unity's basic Physics.OverlapSphere returns a brand new array every time you call it, so with a lot of guards you're making garbage every frame, and eventually the garbage collector pauses the game to clean it up. I wanted detection that makes zero garbage no matter how many guards are running.
How it works
- All guards share one static Collider[16] array instead of each one allocating its own.
- Physics.OverlapSphereNonAlloc fills that array and returns a count, so nothing new gets allocated per check.
- A layer mask limits the check to one layer, so physics skips everything else.
- A vision cone check with Vector3.Angle throws out anything outside the guard's field of view before any raycast happens.
- Physics.Raycast then confirms line of sight, but only for what passed the cone check, since raycasts cost more.
- The guard's behavior is split into five state classes (patrol, caution, search, combat and grabbed), each in its own file, and NavMesh handles walking between waypoints.
Trade-offs
- The shared array holds 16 colliders. Anything past 16 in range gets ignored. My levels never put that many things near one guard, but it's a hard limit.
- Because every guard uses the same array, detection can't be split across threads without changing that. It doesn't matter here, since Unity runs Update on the main thread anyway.
- Every guard checks every frame instead of taking turns across frames, so the total cost grows with the number of guards.
What I measured
Measured in the Unity 2022.3 editor on an RTX 4080 SUPER and Ryzen 7 9800X3D, reading AIController.Update in the Unity Profiler. It works out to about 0.015 ms per guard, and allocation stayed at 0 B at every guard count. At 584 guards the AI was 21% of a 41 ms frame. The biggest cost at that point was rendering the animated guard models, not the AI.