using System; using System.Collections.Generic; using Sirenix.OdinInspector; using UnityEditor.Animations; using UnityEngine; using UnityEngine.Serialization; // ReSharper disable once CheckNamespace namespace BlueWaterProject { [Serializable] public class UnitMatrix { public int units; // 부대 안의 병사 수 public int rows; // 배치될 행의 갯수 public int columns; // 배치될 열의 갯수 //public int centerNum; // 부대의 중심 번호 public UnitMatrix(int units, int rows, int columns) { this.units = units; this.rows = rows; this.columns = columns; } } public class UnitManager : Singleton { #region Property and variable [Tooltip("유닛 프리팹")] [field: SerializeField] public GameObject UnitPrefab { get; private set; } [Tooltip("캐릭터 기초 프리팹")] [field: SerializeField] public GameObject BaseCharacterPrefab { get; private set; } [Tooltip("화살 프리팹")] [field: SerializeField] public GameObject ArrowPrefab { get; private set; } [Tooltip("바닥 레이어")] [field: SerializeField] public LayerMask GroundLayer { get; private set; } [Tooltip("바닥과의 최대 허용 거리")] [field: SerializeField] public float MaxGroundDistance { get; private set; } = 2.5f; [Tooltip("병력 간의 간격")] [field: SerializeField] public float UnitSpacing { get; private set; } = 2.5f; [Tooltip("부대 배치 행렬")] [field: SerializeField] public List UnitMatrices { get; private set; } = new(MATRICES_CAPACITY); [FormerlySerializedAs("soldierPrefabList")] [Tooltip("병력들의 프리팹 리스트(순서 중요)")] [SerializeField] private List characterPrefabList = new(CHARACTER_PREFAB_CAPACITY); [field: Tooltip("병력들의 애니메이터 컨트롤러 리스트")] [field: SerializeField] public List AIAnimatorControllerList { get; private set; } = new(GlobalValue.AI_ANIMATOR_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 CHARACTER_PREFAB_CAPACITY = 10; private const int ANIMATOR_CONTROLLER_PREFAB_CAPACITY = 6; 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 = 2.5f; UnitSpacing = 2.5f; characterPrefabList = new List(CHARACTER_PREFAB_CAPACITY); InitUnitMatrices(); InitCharacterPrefabList(); 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("@BaseCharacterPrefab == null || UnitPrefab == null || ArrowPrefab == null || AIAnimatorControllerList.Count != ANIMATOR_CONTROLLER_PREFAB_CAPACITY")] [Button("프리팹 초기화")] private void InitCharacterPrefabList() { UnitPrefab = Utils.LoadPrefabFromFolder("Assets/05.Prefabs/Character", "Unit"); BaseCharacterPrefab = Utils.LoadPrefabFromFolder("Assets/05.Prefabs/Character", "BaseCharacter"); ArrowPrefab = Utils.LoadPrefabFromFolder("Assets/05.Prefabs", "Arrow_01"); AIAnimatorControllerList = new List(ANIMATOR_CONTROLLER_PREFAB_CAPACITY) { Utils.LoadAnimatorControllerFromFolder("Assets/07.Animation", "Archer"), Utils.LoadAnimatorControllerFromFolder("Assets/07.Animation", "Axeman"), Utils.LoadAnimatorControllerFromFolder("Assets/07.Animation", "SpearKnight"), Utils.LoadAnimatorControllerFromFolder("Assets/07.Animation", "Spearman"), Utils.LoadAnimatorControllerFromFolder("Assets/07.Animation", "SwordKnight"), Utils.LoadAnimatorControllerFromFolder("Assets/07.Animation", "Swordman") }; characterPrefabList = new List(CHARACTER_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() { SetPlayerUnits(); playerUnitList = new List(PLAYER_UNIT_CAPACITY); foreach (Transform item in playerUnits) { if (!item.gameObject.activeSelf) continue; playerUnitList.Add(item.GetComponent()); } } private void SetPlayerUnits() { 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; } } private void SetUnitName(UnitController unitController, string baseName) { 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; } } public void CreateAndAssign(string cardIdx, Vector3 assignPos) { var newUnitController = CreateUnit(cardIdx, AttackerType.OFFENSE); AssignUnit(newUnitController, assignPos); } /// /// 동적 생성용 부대 생성 함수 /// public UnitController CreateUnit(string cardIdx, AttackerType attackerType) { var card = DataManager.Inst.GetCardDictionaryFromKey(cardIdx); var unit = DataManager.Inst.GetUnitDictionaryKey(card.UnitIdx); var captainStat = DataManager.Inst.GetAiStatDictionaryKey(unit.CaptainStatIdx); var sailorStat = DataManager.Inst.GetAiStatDictionaryKey(unit.SailorStatIdx); SetPlayerUnits(); var newUnitController = Instantiate(UnitPrefab, Vector3.zero, Quaternion.identity, playerUnits).GetComponent(); newUnitController.unit = new Unit(unit); DestroyDeployedUnits(newUnitController); var baseName = newUnitController.unit.UnitType.ToString(); SetUnitName(newUnitController, baseName); newUnitController.unit.UnitList = new List(newUnitController.unit.SailorCount + 1); var unitControllerTransform = newUnitController.transform; var unitControllerRotation = unitControllerTransform.rotation; unitControllerTransform.rotation = Quaternion.identity; var gridSize = 0; switch (newUnitController.unit.SailorCount) { case 0: gridSize = 1; break; case <= 3: gridSize = 2; break; case <= 8: gridSize = 3; break; case <= 15: gridSize = 4; break; default: print("유닛의 병사 숫자 설정 에러"); break; } var heroPosition = (gridSize * gridSize) / 2; for (var i = 0; i < gridSize; i++) { for (var j = 0; j < gridSize; j++) { var currentPos = i * gridSize + j; if (currentPos > newUnitController.unit.SailorCount) break; var zOffset = (i - (gridSize - 1) / 2.0f) * UnitSpacing; var xOffset = (j - (gridSize - 1) / 2.0f) * UnitSpacing; var spawnPosition = unitControllerTransform.position + new Vector3(xOffset, 0, zOffset); var baseObj = Instantiate(BaseCharacterPrefab, spawnPosition, Quaternion.identity, newUnitController.transform); var newSoldierName = $"{baseName}_{currentPos + 1:00}"; baseObj.name = newSoldierName; baseObj.gameObject.SetActive(false); var aiController = GetAiController(baseObj, newUnitController.unit.UnitType, currentPos == heroPosition ? captainStat : sailorStat); aiController.SetAttackerType(attackerType); newUnitController.unit.UnitList.Add(aiController); } } newUnitController.transform.rotation *= unitControllerRotation; return newUnitController; } /// /// 에디터용 부대 생성 함수 /// public void CreateUnitInEditor(UnitController unitController) { var unit = DataManager.Inst.GetUnitSoKey(unitController.unit.Idx) ?? DataManager.Inst.GetEnemyUnitSoKey(unitController.unit.Idx); SetPlayerUnits(); unitController.unit = new Unit(unit); var captainStat = DataManager.Inst.GetAiStatSoKey(unitController.unit.CaptainStatIdx); var sailorStat = DataManager.Inst.GetAiStatSoKey(unitController.unit.SailorStatIdx); DestroyDeployedUnits(unitController); var baseName = unitController.unit.UnitType.ToString(); SetUnitName(unitController, baseName); unitController.unit.UnitList = new List(unitController.unit.SailorCount + 1); var unitControllerTransform = unitController.transform; var unitControllerRotation = unitControllerTransform.rotation; unitControllerTransform.rotation = Quaternion.identity; var gridSize = 0; switch (unitController.unit.SailorCount) { case 0: gridSize = 1; break; case <= 3: gridSize = 2; break; case <= 8: gridSize = 3; break; case <= 15: gridSize = 4; break; default: print("유닛의 병사 숫자 설정 에러"); break; } var heroPosition = (gridSize * gridSize) / 2; for (var i = 0; i < gridSize; i++) { for (var j = 0; j < gridSize; j++) { var currentPos = i * gridSize + j; if (currentPos > unitController.unit.SailorCount) break; var zOffset = (i - (gridSize - 1) / 2.0f) * UnitSpacing; var xOffset = (j - (gridSize - 1) / 2.0f) * UnitSpacing; var spawnPosition = unitControllerTransform.position + new Vector3(xOffset, 0, zOffset); var baseObj = Instantiate(BaseCharacterPrefab, spawnPosition, Quaternion.identity, unitController.transform); var newSoldierName = $"{baseName}_{currentPos + 1:00}"; baseObj.name = newSoldierName; baseObj.gameObject.SetActive(false); var aiController = GetAiController(baseObj, unitController.unit.UnitType, currentPos == heroPosition ? captainStat : sailorStat); aiController.SetAttackerType(unitController.unit.AttackerType); aiController.InitStartInEditor(); unitController.unit.UnitList.Add(aiController); } } unitController.transform.rotation *= unitControllerRotation; } private AiController GetAiController(GameObject baseObj, GlobalValue.UnitType unitType, AiStat aiStat) { AiController temp = null; switch (unitType) { case GlobalValue.UnitType.NONE: break; case GlobalValue.UnitType.ARCHER_E: temp = baseObj.AddComponent(); break; case GlobalValue.UnitType.SPEAR_KNIGHT_E: temp = baseObj.AddComponent(); break; case GlobalValue.UnitType.SPEARMAN_E: temp = baseObj.AddComponent(); break; case GlobalValue.UnitType.SWORD_KNIGHT_E: temp = baseObj.AddComponent(); break; case GlobalValue.UnitType.SWORDMAN_E: temp = baseObj.AddComponent(); break; case GlobalValue.UnitType.ARCHER_P: temp = baseObj.AddComponent(); break; case GlobalValue.UnitType.AXEMAN_P: temp = baseObj.AddComponent(); break; case GlobalValue.UnitType.SPEARMAN_P: temp = baseObj.AddComponent(); break; case GlobalValue.UnitType.SWORD_KNIGHT_P: temp = baseObj.AddComponent(); break; case GlobalValue.UnitType.SWORDMAN_P: temp = baseObj.AddComponent(); break; default: throw new ArgumentOutOfRangeException(nameof(unitType), unitType, null); } if (temp == null) return null; temp.AiStat = new AiStat(aiStat); return temp; } /// /// 유닛 배치 함수 /// public bool CanAssignUnit(UnitController unitController, Vector3 assignPos) { if (unitController.unit.UnitList.Count <= 0) return false; unitController.transform.position = assignPos; for (var i = 0; i < unitController.unit.SailorCount; i++) { var soldierPos = unitController.unit.UnitList[i].transform.position; var ray = new Ray(soldierPos + Vector3.up, 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.UnitList) { var myPos = item.transform.position; var ray = new Ray(myPos + Vector3.up, Vector3.down); if (Physics.Raycast(ray, out var hit, MaxGroundDistance, GroundLayer)) { item.transform.position = new Vector3(myPos.x, hit.point.y, myPos.z); var hitIslandInfo = hit.transform.root.GetComponent(); item.SetIslandInfo(hitIslandInfo); } item.SetAttackerType(unitController.unit.AttackerType); item.gameObject.SetActive(true); } } /// /// 기존에 생성된 부대 병력들이 있으면 확인해서 삭제해주는 함수 /// public void DestroyDeployedUnits(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 } }