using System.Collections.Generic; using jeanf.scenemanagement; using Unity.Collections; using Unity.Entities; using Unity.Mathematics; using Unity.Transforms; using UnityEngine; namespace AutomaticDoorSystem.Utilities { /// /// Makes the doors answer SceneManagement's "how do I get the player out of this zone?". /// Drop it next to the other door bridges (the Door Management object) - one per scene is /// enough, it serves every door in the loaded SubScenes. /// /// A door needs nothing but its exit anchor: its is the /// number of the room it closes, so the room is resolved through /// . Doors whose id matches no zone (corridor /// doors, pass-throughs) never answer. /// /// /// Doors are authored in SubScenes, so DoorAuthoring itself no longer exists at runtime: the /// anchor is baked into a , and this bridge reads it back. The /// dependency runs door -> SceneManagement (never the reverse), which is why the provider /// pushes itself into instead of being looked up. /// /// [DefaultExecutionOrder(-100)] [AddComponentMenu("Automatic Door System/Door Zone Evacuation Provider")] public class DoorZoneEvacuationProvider : MonoBehaviour, IZoneEvacuationProvider { [Tooltip("Log every exit query and what it resolved to.")] [SerializeField] private bool isDebug = false; private EntityManager _entityManager; private EntityQuery _exitQuery; private bool _hasWorld; private void OnEnable() { var world = World.DefaultGameObjectInjectionWorld; if (world == null || !world.IsCreated) { Debug.LogWarning("[DoorZoneEvacuationProvider] no ECS world yet - door exits will not be available " + "this session. Make sure this component lives in a scene loaded after the ECS world " + "is created.", this); return; } _entityManager = world.EntityManager; _exitQuery = _entityManager.CreateEntityQuery( ComponentType.ReadOnly(), ComponentType.ReadOnly(), ComponentType.ReadOnly()); _hasWorld = true; ZoneEvacuationRegistry.Register(this); } private void OnDisable() { ZoneEvacuationRegistry.Unregister(this); // The world can already be gone on quit; disposing a query of a dead world throws. if (_hasWorld && _entityManager.World is { IsCreated: true }) _exitQuery.Dispose(); _hasWorld = false; } public bool TryGetExit(string zoneId, Vector3 from, IReadOnlyCollection blockedZoneIds, out ZoneExit exit) { exit = default; if (!_hasWorld || string.IsNullOrEmpty(zoneId)) return false; if (_entityManager.World is not { IsCreated: true }) return false; var exits = _exitQuery.ToComponentDataArray(Allocator.Temp); var doors = _exitQuery.ToComponentDataArray(Allocator.Temp); var transforms = _exitQuery.ToComponentDataArray(Allocator.Temp); var found = false; var bestDistanceSq = float.MaxValue; var bestDoorId = 0; try { for (var i = 0; i < exits.Length; i++) { // The door id IS the room number: door 2005 closes the zone numbered 2005. var room = WorldManager.GetZoneByNumber(doors[i].DoorId); if (room == null || room.id != zoneId) continue; var worldPosition = math.transform(transforms[i].Value, exits[i].ExitLocalPosition); var distanceSq = math.distancesq(worldPosition, (float3)from); if (found && distanceSq >= bestDistanceSq) continue; var worldForward = math.rotate(transforms[i].Value, exits[i].ExitLocalForward); worldForward.y = 0f; var rotation = math.lengthsq(worldForward) > 1e-6f ? Quaternion.LookRotation(math.normalize(worldForward), Vector3.up) : Quaternion.identity; bestDistanceSq = distanceSq; bestDoorId = doors[i].DoorId; // The anchor's own zone is not authored, so nothing is claimed about where it // lands: the blocked-zone filter only applies to providers that do know. exit = new ZoneExit(worldPosition, rotation, string.Empty, $"door {bestDoorId}"); found = true; } } finally { exits.Dispose(); doors.Dispose(); transforms.Dispose(); } if (isDebug) Debug.Log(found ? $"[DoorZoneEvacuationProvider] exit out of '{zoneId}' at {exit.Position}, through door {bestDoorId}." : $"[DoorZoneEvacuationProvider] no door with an exit anchor carries the number of zone '{zoneId}'.", this); return found; } } }