using System; using System.Collections.Generic; using BehaviorDesigner.Runtime; using Sirenix.OdinInspector; using UnityEditor.Animations; using UnityEngine; // ReSharper disable once CheckNamespace namespace BlueWaterProject { public class UnitManager : Singleton { #region Property and variable [Tooltip("Pirate 프리팹")] [field: SerializeField] public GameObject PiratePrefab { get; private set; } [Tooltip("캐릭터 기초 프리팹")] [field: SerializeField] public GameObject BaseHumanPrefab { get; private set; } [Tooltip("화살 프리팹")] [field: SerializeField] public GameObject ArrowPrefab { get; private set; } [Tooltip("Enemy 비헤이비어 트리")] [field: SerializeField] public ExternalBehaviorTree EnemyBehaviorTree { get; private set; } [Tooltip("Pirate 비헤이비어 트리")] [field: SerializeField] public ExternalBehaviorTree PirateBehaviorTree { get; private set; } [Tooltip("바닥 레이어")] [field: SerializeField] public LayerMask GroundLayer { get; private set; } [Tooltip("바닥과의 최대 허용 거리")] [field: SerializeField] public float MaxGroundDistance { get; private set; } = 4f; [Tooltip("병력 간의 간격")] [field: SerializeField] public float UnitSpacing { get; private set; } = 2.5f; [field: Tooltip("병력들의 애니메이터 컨트롤러 리스트")] [field: SerializeField] public List AIAnimatorControllerList { get; private set; } = new(GlobalValue.AI_ANIMATOR_CAPACITY); private Transform pirateUnits; 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"); } private void Reset() { GroundLayer = LayerMask.GetMask("Ground"); MaxGroundDistance = 4f; UnitSpacing = 2.5f; InitCharacterPrefabList(); } #endregion #region Custom function #if UNITY_EDITOR /// /// 프리팹 초기화 함수 /// [GUIColor(0, 1, 0)] [ShowIf("@PiratePrefab == null || BaseHumanPrefab == null || ArrowPrefab == null || EnemyBehaviorTree == null || PirateBehaviorTree == null ||" + "AIAnimatorControllerList == null || AIAnimatorControllerList.Count != ANIMATOR_CONTROLLER_PREFAB_CAPACITY")] [Button("프리팹 초기화")] private void InitCharacterPrefabList() { GroundLayer = LayerMask.GetMask("Ground"); PiratePrefab = Utils.LoadFromFolder("Assets/05.Prefabs/Character/Unit", "PirateUnit", ".prefab"); BaseHumanPrefab = Utils.LoadFromFolder("Assets/05.Prefabs/Character", "BaseHuman", ".prefab"); ArrowPrefab = Utils.LoadFromFolder("Assets/05.Prefabs", "Arrow_01", ".prefab"); EnemyBehaviorTree = Utils.LoadFromFolder("Assets/09.BehaviorTree/Enemy", "BaseEnemyAi", ".asset"); PirateBehaviorTree = Utils.LoadFromFolder("Assets/09.BehaviorTree/Pirate", "BasePirateAi", ".asset"); AIAnimatorControllerList = new List(ANIMATOR_CONTROLLER_PREFAB_CAPACITY) { Utils.LoadFromFolder("Assets/07.Animation", "Archer", ".controller"), Utils.LoadFromFolder("Assets/07.Animation", "Axeman", ".controller"), Utils.LoadFromFolder("Assets/07.Animation", "SpearKnight", ".controller"), Utils.LoadFromFolder("Assets/07.Animation", "Spearman", ".controller"), Utils.LoadFromFolder("Assets/07.Animation", "SwordKnight", ".controller"), Utils.LoadFromFolder("Assets/07.Animation", "Swordman", ".controller") }; } #endif 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.GetPirateStatDictionaryFromKey(unit.CaptainStatIdx); var sailorStat = DataManager.Inst.GetPirateStatDictionaryFromKey(unit.SailorStatIdx); SetPlayerUnits(); var newUnitController = Instantiate(PiratePrefab, 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.PirateAiList = 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(BaseHumanPrefab, spawnPosition, Quaternion.identity, newUnitController.transform); var newSoldierName = $"{baseName}_{currentPos + 1:00}"; baseObj.name = newSoldierName; baseObj.gameObject.SetActive(false); var pirateAi = GetPirateAi(baseObj, pirateStat.UnitType, currentPos == heroPosition ? captainStat : sailorStat); pirateAi.SetAttackerType(attackerType); pirateAi.SetOffenseType(newUnitController.pirateUnitStat.OffenseType); pirateAi.SetDefenseType(newUnitController.pirateUnitStat.DefenseType); newUnitController.pirateUnitStat.PirateAiList.Add(pirateAi); } } 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.GetEnemyStatSoFromKey(enemyUnit.enemyUnitStat.SailorStatIdx); var baseName = enemyStat.UnitType.ToString(); SetUnitName(enemyUnit, enemyUnit.enemyUnitStat.UnitName, baseName); enemyUnit.enemyUnitStat.EnemyAiList = 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(BaseHumanPrefab, spawnPosition, Quaternion.identity, enemyUnit.transform); var newSoldierName = $"{baseName}_{currentPos + 1:00}"; baseObj.name = newSoldierName; baseObj.gameObject.SetActive(false); var currentStat = currentPos == heroPosition ? captainStat : sailorStat; var enemyAi = GetEnemyAi(baseObj, enemyStat.UnitType, currentStat); enemyAi.SetAttackerType(enemyUnit.enemyUnitStat.AttackerType); enemyAi.SetOffenseType(enemyUnit.enemyUnitStat.OffenseType); enemyAi.SetDefenseType(enemyUnit.enemyUnitStat.DefenseType); enemyAi.InitStartInEditor(); enemyUnit.enemyUnitStat.EnemyAiList.Add(enemyAi); } } enemyUnit.transform.rotation *= unitControllerRotation; } private EnemyAi GetEnemyAi(GameObject baseObj, GlobalValue.UnitType unitType, EnemyStat enemyStat) { EnemyAi enemyAi = unitType switch { GlobalValue.UnitType.ARCHER_E => baseObj.AddComponent(), GlobalValue.UnitType.SPEAR_KNIGHT_E => baseObj.AddComponent(), GlobalValue.UnitType.SPEARMAN_E => baseObj.AddComponent(), GlobalValue.UnitType.SWORD_KNIGHT_E => baseObj.AddComponent(), GlobalValue.UnitType.SWORDMAN_E => baseObj.AddComponent(), _ => throw new ArgumentOutOfRangeException(nameof(unitType), unitType, null) }; if (enemyAi == null) return null; enemyAi.EnemyStat = new EnemyStat(enemyStat); return enemyAi; } private PirateAi GetPirateAi(GameObject baseObj, GlobalValue.UnitType unitType, PirateStat pirateStat) { PirateAi pirateAi = unitType switch { GlobalValue.UnitType.ARCHER_P => baseObj.AddComponent(), GlobalValue.UnitType.AXEMAN_P => baseObj.AddComponent(), GlobalValue.UnitType.SPEARMAN_P => baseObj.AddComponent(), GlobalValue.UnitType.SWORD_KNIGHT_P => baseObj.AddComponent(), GlobalValue.UnitType.SWORDMAN_P => baseObj.AddComponent(), _ => throw new ArgumentOutOfRangeException(nameof(unitType), unitType, null) }; if (pirateAi == null) return null; pirateAi.PirateStat = new PirateStat(pirateStat); return pirateAi; } /// /// 유닛 배치 함수 /// public bool CanAssignUnit(EnemyUnit enemyUnit, Vector3 assignPos) { if (enemyUnit.enemyUnitStat.EnemyAiList.Count <= 0) return false; enemyUnit.transform.position = assignPos; for (var i = 0; i < enemyUnit.enemyUnitStat.SailorCount; i++) { var unitPos = enemyUnit.enemyUnitStat.EnemyAiList[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.EnemyAiList) { 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.EnemyAiList) { 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.PirateAiList) { 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.PirateAiList) { if (isOffense) { unit.SetAttackingIslandInfo(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); } } } #endregion } }