using System.Collections.Generic; using Unity.Entities; using Unity.Mathematics; using Unity.Physics; using Unity.Transforms; using UnityEngine; using EE2Clone.Components; namespace EE2Clone.Hybrid { /// /// Handles click-select, shift-add, and box-drag selection of entities. /// Client-side only. Adds/removes SelectedTag on ECS entities. /// public class SelectionManager : MonoBehaviour { public static SelectionManager Instance { get; private set; } [Header("Settings")] [SerializeField] private LayerMask selectableLayers = ~0; [SerializeField] private float dragThreshold = 5f; public List SelectedEntities { get; } = new(); // Control groups: index 0-9 private readonly List[] _controlGroups = new List[10]; // Box selection state private bool _isDragging; private Vector2 _dragStart; // Exposed for SelectionBoxUI public bool IsDragging => _isDragging; public Vector2 DragStart => _dragStart; public Vector2 DragEnd { get; private set; } private Camera _mainCamera; private void Awake() { if (Instance != null && Instance != this) { Destroy(gameObject); return; } Instance = this; for (int i = 0; i < 10; i++) _controlGroups[i] = new List(); } private void Start() { _mainCamera = Camera.main; } private void Update() { var input = RTSInputActions.Instance; if (input == null || _mainCamera == null) return; HandleSelection(input); HandleControlGroups(input); } private void HandleSelection(RTSInputActions input) { if (input.LeftClickPressed) { _dragStart = input.MousePosition; _isDragging = false; } if (input.LeftClickHeld) { DragEnd = input.MousePosition; if (Vector2.Distance(_dragStart, DragEnd) > dragThreshold) _isDragging = true; } if (input.LeftClickReleased) { DragEnd = input.MousePosition; if (_isDragging) BoxSelect(input.ShiftHeld); else ClickSelect(input.MousePosition, input.ShiftHeld); _isDragging = false; } } private void ClickSelect(Vector2 screenPos, bool additive) { if (!additive) ClearSelection(); var ray = _mainCamera.ScreenPointToRay(new Vector3(screenPos.x, screenPos.y, 0)); // Raycast using Unity Physics (ECS) var world = World.DefaultGameObjectInjectionWorld; if (world == null || !world.IsCreated) return; var physicsWorld = world.EntityManager .CreateEntityQuery(typeof(PhysicsWorldSingleton)) .GetSingleton(); var rayInput = new RaycastInput { Start = ray.origin, End = ray.origin + ray.direction * 500f, Filter = new CollisionFilter { BelongsTo = ~0u, CollidesWith = ~0u, GroupIndex = 0 } }; if (physicsWorld.CastRay(rayInput, out var hit)) { var em = world.EntityManager; var entity = physicsWorld.Bodies[hit.RigidBodyIndex].Entity; if (em.HasComponent(entity) || em.HasComponent(entity)) { if (additive && SelectedEntities.Contains(entity)) { SelectedEntities.Remove(entity); em.RemoveComponent(entity); } else if (!SelectedEntities.Contains(entity)) { SelectedEntities.Add(entity); em.AddComponent(entity); } } } } private void BoxSelect(bool additive) { if (!additive) ClearSelection(); var world = World.DefaultGameObjectInjectionWorld; if (world == null || !world.IsCreated) return; var em = world.EntityManager; var min = Vector2.Min(_dragStart, DragEnd); var max = Vector2.Max(_dragStart, DragEnd); // Query all unit entities and check if their screen position is within the box var query = em.CreateEntityQuery(typeof(UnitTag), typeof(LocalTransform)); var entities = query.ToEntityArray(Unity.Collections.Allocator.Temp); var transforms = query.ToComponentDataArray(Unity.Collections.Allocator.Temp); for (int i = 0; i < entities.Length; i++) { var screenPos = _mainCamera.WorldToScreenPoint(transforms[i].Position); if (screenPos.z > 0 && screenPos.x >= min.x && screenPos.x <= max.x && screenPos.y >= min.y && screenPos.y <= max.y) { var entity = entities[i]; if (!SelectedEntities.Contains(entity)) { SelectedEntities.Add(entity); if (!em.HasComponent(entity)) em.AddComponent(entity); } } } entities.Dispose(); transforms.Dispose(); } public void ClearSelection() { var world = World.DefaultGameObjectInjectionWorld; if (world != null && world.IsCreated) { var em = world.EntityManager; foreach (var entity in SelectedEntities) { if (em.Exists(entity) && em.HasComponent(entity)) em.RemoveComponent(entity); } } SelectedEntities.Clear(); } private void HandleControlGroups(RTSInputActions input) { int group = input.ControlGroupPressed; if (group < 0) return; if (input.CtrlHeld) { // Assign current selection to control group _controlGroups[group].Clear(); _controlGroups[group].AddRange(SelectedEntities); } else { // Recall control group ClearSelection(); var world = World.DefaultGameObjectInjectionWorld; if (world == null || !world.IsCreated) return; var em = world.EntityManager; foreach (var entity in _controlGroups[group]) { if (em.Exists(entity)) { SelectedEntities.Add(entity); if (!em.HasComponent(entity)) em.AddComponent(entity); } } } } public Entity[] GetSelectedEntitiesOfType() where T : unmanaged, IComponentData { var world = World.DefaultGameObjectInjectionWorld; if (world == null || !world.IsCreated) return System.Array.Empty(); var em = world.EntityManager; var result = new List(); foreach (var entity in SelectedEntities) { if (em.Exists(entity) && em.HasComponent(entity)) result.Add(entity); } return result.ToArray(); } private void OnDestroy() { if (Instance == this) Instance = null; } } }