World/Zone.cs

A component representing a named gameplay zone defined by BoxCollider triggers. It collects trigger colliders on the same GameObject, tags them as "zone", exposes an OnZoneEnter event, provides a static GetAt(position) to find zones at a point via scene trace, and draws gizmo boxes in the editor.

File Access
🐞 UpdateColliders returns from inside the foreach when it hits a non-trigger BoxCollider, so later colliders are never processed and the component is left partially set up.

using System.Diagnostics;

/// <summary>
/// A region of the map with some specific gameplay purpose.
/// The extents of the zone are defined by <see cref="BoxCollider"/>s attached to this object.
/// </summary>
public class Zone : Component
{
	[Property] public Color Color { get; set; } = Color.White;

	private readonly HashSet<BoxCollider> _colliders = new();

	public event Action<Collider> OnZoneEnter;
	
	protected override void OnValidate()
	{
		UpdateColliders();
	}

	protected override void OnEnabled()
	{
		UpdateColliders();
	}

	private void ZoneEnter(Collider Collider)
	{
		if (Collider == null)
		{
			return;
		}

		OnZoneEnter.Invoke(Collider);
	}

	private void UpdateColliders()
	{
		_colliders.Clear();

		foreach (var collider in Components.GetAll<BoxCollider>())
		{
			if (!collider.IsTrigger)
			{
				return;
			}

			_colliders.Add(collider);

			collider.GameObject.Tags.Add("zone");
			collider.OnTriggerEnter += ZoneEnter;
		}
	}

	/// <summary>
	/// Returns all zones that contain the given position.
	/// </summary>
	public static IEnumerable<Zone> GetAt(Vector3 pos)
	{
		var result = Game.ActiveScene.Trace
			.Sphere(0.001f, pos, pos) // Doesn't work with Ray?
			.HitTriggersOnly()
			.WithTag("zone")
			.RunAll() ?? Array.Empty<SceneTraceResult>();

		return result
			.Select(x => x.GameObject.Components.GetInAncestorsOrSelf<Zone>())
			.Where(x => x != null)
			.Distinct();
	}

	protected override void DrawGizmos()
	{
		Gizmo.Draw.Color = Color.WithAlpha(Gizmo.IsSelected ? 0.5f : 0.25f);

		foreach (var collider in Components.GetAll<BoxCollider>().Where(x => x.IsTrigger))
		{
			Gizmo.Transform = collider.Transform.World;
			Gizmo.Draw.SolidBox(BBox.FromPositionAndSize(collider.Center, collider.Scale));
		}
	}
}