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 pirateUnitList = new(PIRATE_UNIT_CAPACITY); public IReadOnlyList PirateUnitList => pirateUnitList; private Transform pirateUnits; private const int MATRICES_CAPACITY = 9; private const int CHARACTER_PREFAB_CAPACITY = 10; private const int ANIMATOR_CONTROLLER_PREFAB_CAPACITY = 6; private const int PIRATE_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(); pirateUnitList = new List(PIRATE_UNIT_CAPACITY); foreach (Transform item in pirateUnits) { if (!item.gameObject.activeSelf) continue; pirateUnitList.Add(item.GetComponent()); } } private void SetPlayerUnits() { var pirateUnitsObj = GameObject.Find("PirateUnits"); if (pirateUnitsObj) { pirateUnits = pirateUnitsObj.transform; } else { pirateUnitsObj = new GameObject("PirateUnits"); pirateUnitsObj.transform.SetPositionAndRotation(Vector3.zero, Quaternion.identity); pirateUnits = pirateUnitsObj.transform; } } private void SetUnitName(BaseUnit baseUnit, string unitName, string baseName) { if (string.IsNullOrEmpty(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 != baseUnit.gameObject) { namingNum++; } else { baseUnit.gameObject.name = newUnitName; break; } } } else { baseUnit.gameObject.name = unitName; } } public void PirateUnitCreateAndAssign(string cardIdx, Vector3 assignPos) { var newUnitController = CreatePirateUnit(cardIdx, EAttackerType.OFFENSE); AssignPirateUnit(newUnitController, assignPos, true); } /// /// 동적 생성용 부대 생성 함수 /// public PirateUnit CreatePirateUnit(string cardIdx, EAttackerType attackerType) { var card = DataManager.Inst.GetCardDictionaryFromKey(cardIdx); var unit = DataManager.Inst.GetPirateUnitStatDictionaryFromKey(card.UnitIdx); var captainStat = DataManager.Inst.GetEnemyStatDictionaryFromKey(unit.CaptainStatIdx); var sailorStat = DataManager.Inst.GetEnemyStatDictionaryFromKey(unit.SailorStatIdx); SetPlayerUnits(); var newUnitController = Instantiate(UnitPrefab, Vector3.zero, Quaternion.identity, pirateUnits).GetComponent(); newUnitController.pirateUnitStat = new PirateUnitStat(unit); DestroyDeployedUnits(newUnitController); var pirateStat = DataManager.Inst.GetPirateStatDictionaryFromKey(newUnitController.pirateUnitStat.SailorStatIdx); var baseName = pirateStat.UnitType.ToString(); SetUnitName(newUnitController, newUnitController.pirateUnitStat.UnitName, baseName); newUnitController.pirateUnitStat.UnitList = new List(newUnitController.pirateUnitStat.SailorCount + 1); var unitControllerTransform = newUnitController.transform; var unitControllerRotation = unitControllerTransform.rotation; unitControllerTransform.rotation = Quaternion.identity; var gridSize = 0; switch (newUnitController.pirateUnitStat.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.pirateUnitStat.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, pirateStat.UnitType, currentPos == heroPosition ? captainStat : sailorStat); aiController.SetAttackerType(attackerType); aiController.SetOffenseType(newUnitController.pirateUnitStat.OffenseType); aiController.SetDefenseType(newUnitController.pirateUnitStat.DefenseType); newUnitController.pirateUnitStat.UnitList.Add(aiController); } } newUnitController.transform.rotation *= unitControllerRotation; return newUnitController; } /// /// 에디터용 부대 생성 함수 /// public void CreateEnemyUnitInEditor(EnemyUnit enemyUnit) { var unit = DataManager.Inst.GetEnemyUnitStatSoFromKey(enemyUnit.enemyUnitStat.Idx); SetPlayerUnits(); enemyUnit.enemyUnitStat = new EnemyUnitStat(unit); var captainStat = DataManager.Inst.GetEnemyStatSoFromKey(enemyUnit.enemyUnitStat.CaptainStatIdx); var sailorStat = DataManager.Inst.GetEnemyStatSoFromKey(enemyUnit.enemyUnitStat.SailorStatIdx); DestroyDeployedUnits(enemyUnit); var enemyStat = DataManager.Inst.GetEnemyStatDictionaryFromKey(enemyUnit.enemyUnitStat.SailorStatIdx); var baseName = enemyStat.UnitType.ToString(); SetUnitName(enemyUnit, enemyUnit.enemyUnitStat.UnitName, baseName); enemyUnit.enemyUnitStat.UnitList = new List(enemyUnit.enemyUnitStat.SailorCount + 1); var unitControllerTransform = enemyUnit.transform; var unitControllerRotation = unitControllerTransform.rotation; unitControllerTransform.rotation = Quaternion.identity; var gridSize = 0; switch (enemyUnit.enemyUnitStat.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 > enemyUnit.enemyUnitStat.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, enemyUnit.transform); var newSoldierName = $"{baseName}_{currentPos + 1:00}"; baseObj.name = newSoldierName; baseObj.gameObject.SetActive(false); var aiController = GetAiController(baseObj, enemyStat.UnitType, currentPos == heroPosition ? captainStat : sailorStat); aiController.SetAttackerType(enemyUnit.enemyUnitStat.AttackerType); aiController.SetOffenseType(enemyUnit.enemyUnitStat.OffenseType); aiController.SetDefenseType(enemyUnit.enemyUnitStat.DefenseType); aiController.InitStartInEditor(); enemyUnit.enemyUnitStat.UnitList.Add(aiController); } } enemyUnit.transform.rotation *= unitControllerRotation; } private AiController GetAiController(GameObject baseObj, GlobalValue.UnitType unitType, EnemyStat 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(EnemyUnit enemyUnit, Vector3 assignPos) { if (enemyUnit.enemyUnitStat.UnitList.Count <= 0) return false; enemyUnit.transform.position = assignPos; for (var i = 0; i < enemyUnit.enemyUnitStat.SailorCount; i++) { var unitPos = enemyUnit.enemyUnitStat.UnitList[i].transform.position; var ray = new Ray(unitPos + Vector3.up, Vector3.down); if (Physics.Raycast(ray, out var hit, MaxGroundDistance, GroundLayer)) { unitPos.y = hit.point.y; } else { return false; } } return true; } public void AssignEnemyUnit(EnemyUnit enemyUnit, Vector3 assignPos) { enemyUnit.transform.position = assignPos; foreach (var unit in enemyUnit.enemyUnitStat.UnitList) { var myPos = unit.transform.position; var ray = new Ray(myPos + Vector3.up, Vector3.down); if (Physics.Raycast(ray, out var hit, MaxGroundDistance, GroundLayer)) { unit.transform.position = new Vector3(myPos.x, hit.point.y, myPos.z); } } foreach (var unit in enemyUnit.enemyUnitStat.UnitList) { unit.gameObject.SetActive(true); } } public void AssignPirateUnit(PirateUnit pirateUnit, Vector3 assignPos, bool isOffense) { pirateUnit.transform.position = assignPos; IslandInfo hitIslandInfo = null; foreach (var unit in pirateUnit.pirateUnitStat.UnitList) { var myPos = unit.transform.position; var ray = new Ray(myPos + Vector3.up, Vector3.down); if (Physics.Raycast(ray, out var hit, MaxGroundDistance, GroundLayer)) { unit.transform.position = new Vector3(myPos.x, hit.point.y, myPos.z); if (isOffense && hitIslandInfo == null) { hitIslandInfo = hit.transform.root.GetComponent(); } } } foreach (var unit in pirateUnit.pirateUnitStat.UnitList) { if (isOffense) { unit.SetIslandInfo(hitIslandInfo); } unit.gameObject.SetActive(true); } } /// /// 기존에 생성된 부대 병력들이 있으면 확인해서 삭제해주는 함수 /// public void DestroyDeployedUnits(BaseUnit baseUnit) { if (baseUnit.transform.childCount <= 0) return; for (var i = baseUnit.transform.childCount - 1; i >= 0; i--) { if (Application.isPlaying) { Destroy(baseUnit.transform.GetChild(i).gameObject); } else { DestroyImmediate(baseUnit.transform.GetChild(i).gameObject); } } } /// /// pirateUnitList 내의 속성 /// public void RemovePlayerUnitListElement(PirateUnit pirateUnit) { if (pirateUnitList.Contains(pirateUnit)) { pirateUnitList.Remove(pirateUnit); } else { Debug.Log("제거하려는 속성이 리스트 내에 존재하지 않습니다."); } } #endregion } }