OldBlueWater/BlueWater/Assets/02.Scripts/Ai/Human/Unit/UnitManager.cs
2023-09-12 23:46:57 +09:00

549 lines
22 KiB
C#

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<UnitManager>
{
#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<UnitMatrix> UnitMatrices { get; private set; } = new(MATRICES_CAPACITY);
[FormerlySerializedAs("soldierPrefabList")]
[Tooltip("병력들의 프리팹 리스트(순서 중요)")]
[SerializeField] private List<GameObject> characterPrefabList = new(CHARACTER_PREFAB_CAPACITY);
[field: Tooltip("병력들의 애니메이터 컨트롤러 리스트")]
[field: SerializeField] public List<AnimatorController> AIAnimatorControllerList { get; private set; } = new(GlobalValue.AI_ANIMATOR_CAPACITY);
[Tooltip("플레이어가 가지고 있는 부대리스트")]
[SerializeField] private List<PirateUnit> pirateUnitList = new(PIRATE_UNIT_CAPACITY);
public IReadOnlyList<PirateUnit> 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<GameObject>(CHARACTER_PREFAB_CAPACITY);
InitUnitMatrices();
InitCharacterPrefabList();
InitPlayerUnitList();
}
#endregion
#region Custom function
/// <summary>
/// 부대 배치 행렬 초기화 함수
/// </summary>
[GUIColor(0, 1, 0)]
[ShowIf("@UnitMatrices.Count != MATRICES_CAPACITY")]
[Button("행렬 초기화")]
private void InitUnitMatrices()
{
UnitMatrices = new List<UnitMatrix>(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
/// <summary>
/// 프리팹 초기화 함수
/// </summary>
[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<AnimatorController>(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<GameObject>(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
/// <summary>
/// 플레이어가 가진 유닛 리스트 초기화
/// </summary>
[GUIColor(0, 1, 0)]
[Button("플레이어 유닛 가져오기")]
private void InitPlayerUnitList()
{
SetPlayerUnits();
pirateUnitList = new List<PirateUnit>(PIRATE_UNIT_CAPACITY);
foreach (Transform item in pirateUnits)
{
if (!item.gameObject.activeSelf) continue;
pirateUnitList.Add(item.GetComponent<PirateUnit>());
}
}
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);
}
/// <summary>
/// 동적 생성용 부대 생성 함수
/// </summary>
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<PirateUnit>();
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<AiController>(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;
}
/// <summary>
/// 에디터용 부대 생성 함수
/// </summary>
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<AiController>(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<Archer>();
break;
case GlobalValue.UnitType.SPEAR_KNIGHT_E:
temp = baseObj.AddComponent<SpearKnight>();
break;
case GlobalValue.UnitType.SPEARMAN_E:
temp = baseObj.AddComponent<Spearman>();
break;
case GlobalValue.UnitType.SWORD_KNIGHT_E:
temp = baseObj.AddComponent<SwordKnight>();
break;
case GlobalValue.UnitType.SWORDMAN_E:
temp = baseObj.AddComponent<Swordman>();
break;
case GlobalValue.UnitType.ARCHER_P:
temp = baseObj.AddComponent<Archer>();
break;
case GlobalValue.UnitType.AXEMAN_P:
temp = baseObj.AddComponent<Axeman>();
break;
case GlobalValue.UnitType.SPEARMAN_P:
temp = baseObj.AddComponent<Spearman>();
break;
case GlobalValue.UnitType.SWORD_KNIGHT_P:
temp = baseObj.AddComponent<SwordKnight>();
break;
case GlobalValue.UnitType.SWORDMAN_P:
temp = baseObj.AddComponent<Swordman>();
break;
default:
throw new ArgumentOutOfRangeException(nameof(unitType), unitType, null);
}
if (temp == null) return null;
//temp.AiStat = new AiStat(aiStat);
return temp;
}
/// <summary>
/// 유닛 배치 함수
/// </summary>
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<IslandInfo>();
}
}
}
foreach (var unit in pirateUnit.pirateUnitStat.UnitList)
{
if (isOffense)
{
unit.SetIslandInfo(hitIslandInfo);
}
unit.gameObject.SetActive(true);
}
}
/// <summary>
/// 기존에 생성된 부대 병력들이 있으면 확인해서 삭제해주는 함수
/// </summary>
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);
}
}
}
/// <summary>
/// pirateUnitList 내의 속성
/// </summary>
public void RemovePlayerUnitListElement(PirateUnit pirateUnit)
{
if (pirateUnitList.Contains(pirateUnit))
{
pirateUnitList.Remove(pirateUnit);
}
else
{
Debug.Log("제거하려는 속성이 리스트 내에 존재하지 않습니다.");
}
}
#endregion
}
}