Zone detection
Almost every ambient mod or mission trigger needs to know: "Has the player arrived here?" Detecting proximity is simple math, but doing it sixty times per second across dozens of locations will needlessly choke your game's frame budget.
Proximity calculation
To check if the player is within a radius of a center point:
Vector3 zoneCenter = new Vector3(190f, -850f, 30f);
float triggerRadius = 15.0f;
float distance = Vector3.Distance(player.Position, zoneCenter);
bool isInside = distance <= triggerRadius;
The performance trap: scan cadence throttling
A common beginner mistake is computing distances on dozens of world coordinates or entities every single frame in OnTick. At 60 FPS, checking 50 locations means 3,000 square root calculations per second, every second, even while the player is peacefully driving on the highway.
Human reaction time is roughly 200 milliseconds. Checking proximity every 400 milliseconds (2.5 times per second) is imperceptible to the player, but reduces CPU consumption by over 95%:
private int _nextScanTime;
private const int ScanIntervalMs = 400; // Real-world performance sweet spot
private void OnTick(object sender, EventArgs e)
{
// Throttle: skip this frame if the interval has not elapsed
if (Game.GameTime < _nextScanTime)
{
return;
}
_nextScanTime = Game.GameTime + ScanIntervalMs;
RunProximityScans();
}
State change detection: Enter vs Inside vs Exit
Do not trigger your logic repeatedly just because the player is inside the zone. You usually want to know the exact moment the player crosses the threshold:
private bool _wasInsideZone;
private void RunProximityScans()
{
Ped player = Game.Player.Character;
if (player == null || !player.Exists())
{
return;
}
bool isInside = Vector3.Distance(player.Position, _zoneCenter) <= _radius;
// Entered the zone this pass
if (isInside && !_wasInsideZone)
{
OnZoneEntered();
}
// Exited the zone this pass
else if (!isInside && _wasInsideZone)
{
OnZoneExited();
}
_wasInsideZone = isInside;
}
private void OnZoneEntered()
{
GTA.UI.Notification.PostTicker("Entered restricted gang territory.", false);
}
private void OnZoneExited()
{
GTA.UI.Notification.PostTicker("Leaving restricted territory.", false);
}
Tracking _wasInsideZone gives you clean, one-shot events for both arrival and departure without spamming notifications.
Next, we look at state machines: organizing multiple dynamic behaviors into distinct, manageable states.