using System; using System.Collections.Generic; using System.Linq; using Sirenix.OdinInspector; using UnityEngine; // ReSharper disable once CheckNamespace namespace BlueWaterProject { [Serializable] public class UnitMatrix { public int soldiers; // 부대 안의 병사 수 public int rows; // 배치될 행의 갯수 public int columns; // 배치될 열의 갯수 //public int centerNum; // 부대의 중심 번호 public UnitMatrix(int soldiers, int rows, int columns) { this.soldiers = soldiers; this.rows = rows; this.columns = columns; } } public class UnitManager : Singleton { #region Property and variable [Tooltip("바닥 레이어")] [field: SerializeField] public LayerMask GroundLayer { get; private set; } [Tooltip("바닥과의 최대 허용 거리")] [field: SerializeField] public float MaxGroundDistance { get; private set; } = 0.5f; [Tooltip("병력 간의 간격")] [field: SerializeField] public float SoldierSpacing { get; private set; } = 0.5f; [Tooltip("부대 배치 행렬")] [field: SerializeField] public List UnitMatrices { get; private set; } = new(MATRICES_CAPACITY); [Tooltip("병력들의 프리팹 리스트(순서 중요)")] [SerializeField] private List soldierPrefabList = new(SOLDIER_PREFAB_CAPACITY); [Tooltip("플레이어가 가지고 있는 부대리스트")] [SerializeField] private List playerUnitList = new(PLAYER_UNIT_CAPACITY); public IReadOnlyList PlayerUnitList => playerUnitList; private Transform playerUnits; private const int MATRICES_CAPACITY = 9; private const int SOLDIER_PREFAB_CAPACITY = 10; private const int PLAYER_UNIT_CAPACITY = 50; #endregion #region Unity built-in function protected override void OnAwake() { GroundLayer = LayerMask.GetMask("Ground"); InitUnitMatrices(); InitPlayerUnitList(); } private void Reset() { GroundLayer = LayerMask.GetMask("Ground"); MaxGroundDistance = 0.5f; SoldierSpacing = 0.5f; soldierPrefabList = new List(SOLDIER_PREFAB_CAPACITY); // TODO : 프리팹 자동 리셋화 필요 InitUnitMatrices(); InitSoldierPrefabList(); InitPlayerUnitList(); } #endregion #region Custom function /// /// 부대 배치 행렬 초기화 함수 /// [GUIColor(0, 1, 0)] [ShowIf("@UnitMatrices.Count != MATRICES_CAPACITY")] [Button("행렬 초기화")] private void InitUnitMatrices() { UnitMatrices = new List(MATRICES_CAPACITY) { new(1, 1, 1), new(2, 1, 2), new(3, 1, 3), new(4, 2, 2), new(6, 2, 3), new(8, 2, 4), new(9, 3, 3), new(12, 3, 4), new(16, 4, 4), }; } #if UNITY_EDITOR /// /// 프리팹 초기화 함수 /// [GUIColor(0, 1, 0)] [ShowIf("@soldierPrefabList.Count != SOLDIER_PREFAB_CAPACITY")] [Button("프리팹 초기화")] private void InitSoldierPrefabList() { soldierPrefabList = new List(SOLDIER_PREFAB_CAPACITY) { Utils.LoadPrefabFromFolder("Assets/05.Prefabs/Character/Enemy", "Archer_E"), Utils.LoadPrefabFromFolder("Assets/05.Prefabs/Character/Enemy", "SpearKnight_E"), Utils.LoadPrefabFromFolder("Assets/05.Prefabs/Character/Enemy", "Spearman_E"), Utils.LoadPrefabFromFolder("Assets/05.Prefabs/Character/Enemy", "SwordKnight_E"), Utils.LoadPrefabFromFolder("Assets/05.Prefabs/Character/Enemy", "Swordman_E"), Utils.LoadPrefabFromFolder("Assets/05.Prefabs/Character/Pirate", "Archer_P"), Utils.LoadPrefabFromFolder("Assets/05.Prefabs/Character/Pirate", "Axeman_P"), Utils.LoadPrefabFromFolder("Assets/05.Prefabs/Character/Pirate", "Spearman_P"), Utils.LoadPrefabFromFolder("Assets/05.Prefabs/Character/Pirate", "SwordKnight_P"), Utils.LoadPrefabFromFolder("Assets/05.Prefabs/Character/Pirate", "Swordman_P") }; } #endif /// /// 플레이어가 가진 유닛 리스트 초기화 /// [GUIColor(0, 1, 0)] [Button("플레이어 유닛 가져오기")] private void InitPlayerUnitList() { var playerUnitsObj = GameObject.Find("PlayerUnits"); if (playerUnitsObj) { playerUnits = playerUnitsObj.transform; } else { playerUnitsObj = new GameObject("PlayerUnits"); playerUnitsObj.transform.SetPositionAndRotation(Vector3.zero, Quaternion.identity); playerUnits = playerUnitsObj.transform; } playerUnitList = new List(PLAYER_UNIT_CAPACITY); foreach (Transform item in playerUnits) { if (!item.gameObject.activeSelf) continue; playerUnitList.Add(item.GetComponent()); } } /// /// 부대 생성 함수 /// public void CreateUnit(UnitController unitController) { DestroyDeployedSoldiers(unitController); var baseName = soldierPrefabList[(int)unitController.unit.unitType - 1].name; if (string.IsNullOrEmpty(unitController.unit.unitName)) { const int maxIterations = 100; var namingNum = 0; while (namingNum < maxIterations) { var newUnitName = $"{baseName}_Unit_{namingNum + 1:00}"; var checkGameObject = GameObject.Find(newUnitName); if (checkGameObject && checkGameObject != unitController.gameObject) { namingNum++; } else { unitController.gameObject.name = newUnitName; break; } } } else { unitController.gameObject.name = unitController.unit.unitName; } unitController.gameObject.layer = LayerMask.NameToLayer("Unit"); unitController.unit.soldierList = new List(unitController.unit.soliderCount); var matrix = UnitMatrices.Find(um => um.soldiers == unitController.unit.soliderCount); if (matrix == null) { Debug.LogError("사용할 수 없는 병력의 숫자입니다. UnitManager의 UnitMatrices를 확인해주세요."); return; } var unitControllerTransform = unitController.transform; var unitControllerRotation = unitControllerTransform.rotation; unitControllerTransform.rotation = Quaternion.identity; for (var i = 0; i < unitController.unit.soliderCount; i++) { var row = i / matrix.columns; var column = i % matrix.columns; var xOffset = (column - (matrix.columns - 1) / 2.0f) * SoldierSpacing; var zOffset = (row - (matrix.rows - 1) / 2.0f) * SoldierSpacing; var spawnPosition = unitControllerTransform.position + new Vector3(xOffset, 0, zOffset); var soldierObject = Instantiate(soldierPrefabList[(int)unitController.unit.unitType - 1], spawnPosition, Quaternion.identity, unitController.transform).GetComponent(); var newSoldierName = $"{baseName}_{i + 1:00}"; soldierObject.name = newSoldierName; unitController.unit.soldierList.Add(soldierObject); soldierObject.gameObject.SetActive(false); } unitController.transform.rotation *= unitControllerRotation; if (unitController.unit.unitType.ToString().Contains("_E")) { unitController.SetAttackerType(AttackerType.DEFENSE); foreach (var soldier in unitController.unit.soldierList) { soldier.SetAttackerType(AttackerType.DEFENSE); } } else if (unitController.unit.unitType.ToString().Contains("_P")) { unitController.SetAttackerType(AttackerType.OFFENSE); foreach (var soldier in unitController.unit.soldierList) { soldier.SetAttackerType(AttackerType.DEFENSE); } } } /// /// 유닛 배치 함수 /// public bool CanAssignUnit(UnitController unitController, Vector3 assignPos) { if (unitController.unit.soldierList.Count <= 0) return false; unitController.transform.position = assignPos; for (var i = 0; i < unitController.unit.soliderCount; i++) { var soldierPos = unitController.unit.soldierList[i].transform.position; var ray = new Ray(soldierPos, Vector3.down); if (Physics.Raycast(ray, out var hit, MaxGroundDistance, GroundLayer)) { soldierPos.y = hit.point.y; } else { return false; } } return true; } public void AssignUnit(UnitController unitController, Vector3 assignPos) { unitController.transform.position = assignPos; foreach (var item in unitController.unit.soldierList) { item.gameObject.SetActive(true); } } /// /// 기존에 생성된 부대 병력들이 있으면 확인해서 삭제해주는 함수 /// private void DestroyDeployedSoldiers(UnitController unitController) { if (unitController.transform.childCount <= 0) return; for (var i = unitController.transform.childCount - 1; i >= 0; i--) { if (Application.isPlaying) { Destroy(unitController.transform.GetChild(i).gameObject); } else { DestroyImmediate(unitController.transform.GetChild(i).gameObject); } } } /// /// playerUnitList 내의 속성 /// public void RemovePlayerUnitListElement(UnitController unitController) { if (playerUnitList.Contains(unitController)) { playerUnitList.Remove(unitController); } else { Debug.Log("제거하려는 속성이 리스트 내에 존재하지 않습니다."); } } #endregion } }