/* * @Author: 白哉 * @Description: AI 逻辑总模块 * @Date: 2025年04月01日 星期二 14:04:01 * @Modify: */ using System.Collections.Generic; using System.Linq; using Logic.Action; using Logic.CrashSight; using NodeCanvas.BehaviourTrees; using NodeCanvas.Framework; using UnityEngine; using RuntimeData; using TH1_Core.Managers; using TH1_Logic.AITrain; using Unity.VisualScripting; namespace Logic.AI { public enum AILogicState { Prepare, Playing, PrePlay, Pausing, Finished, } public enum AIActionType { Grid, City, Unit, Tech, Max, } public class AILogic { public AILogicState AILogicState; public PlayerData PlayerData => _playerData; private float _targetTime; private bool _isWaitFrame; private AIActionScoreCalculator _scoreCalculator; private AIActionGenerator _generator; private List RecordActions; private AIActionBase MaxScoreAction; private MapData _mapData; private PlayerData _playerData; private AIConfigAsset _cfg; private GameObject _logicObject; private BehaviourTreeOwner _btOwner; private AICalculatorData _data; private int _actionCount; private List _actionBitCodec; private int _sameCount; public static uint CurrentAIPlayerId; public static Dictionary> AIRecordsDict; public AILogic() { AIRecordsDict = new Dictionary>(); AILogicState = AILogicState.Prepare; RecordActions = new List(); _scoreCalculator = new AIActionScoreCalculator(); _cfg = Resources.Load("Export/AIConfig"); _generator = new AIActionGenerator(); _logicObject = GameObject.Find("AIBT"); _btOwner = _logicObject.GetComponent(); var data = _btOwner.blackboard.GetVariable("Data"); if (data.value == null) { _data = new AICalculatorData(); data.value = _data; } else _data = data.value; } // 开始 AI 逻辑 public void StartAILogic(MapData mapData, PlayerData playerData) { AILogicState = AILogicState.Playing; _actionCount = 0; _mapData = mapData; _playerData = playerData; var aiDiff = _cfg.GetAIDiffInfo(_mapData.MapConfig.AIDiff); _data.AiDiffInfo = aiDiff; _generator.Init(_mapData, _playerData); _data.Refresh(mapData, playerData); _btOwner.StopBehaviour(); _btOwner.StartBehaviour(); MainEditor.Instance.Data = _data; #if UNITY_EDITOR CurrentAIPlayerId = _playerData.Id; if (!AIRecordsDict.ContainsKey(CurrentAIPlayerId)) AIRecordsDict[CurrentAIPlayerId] = new List(); AIRecordsDict[CurrentAIPlayerId].Clear(); #endif } // 结束 AI 逻辑 public void FinishAILogic() { _playerData = null; AILogicState = AILogicState.Prepare; } // 更新 AI 逻辑 public void Update() { if (AILogicState == AILogicState.Finished || AILogicState == AILogicState.Prepare) return; #if ENABLE_SPEEDUP if (AILogicState == AILogicState.Pausing) #else if (AILogicState == AILogicState.Pausing && !PresentationManager.Busy) #endif { _targetTime -= Time.deltaTime; if (_targetTime <= 0) AILogicState = AILogicState.Playing; // if (!_isWaitFrame) _isWaitFrame = true; // else // { // _targetTime -= Time.deltaTime; // if (_targetTime <= 0) AILogicState = AILogicState.Playing; // } } if (AILogicState == AILogicState.Playing) { if (_actionCount > 200) { LogSystem.LogError($"AI 行为次数过多,可能进入死循环,强制结束 AI 逻辑 最终记录点为:{MainEditor.Instance.BTNodeId}"); AILogicState = AILogicState.Finished; return; } #if ENABLE_AIMODEL AIModelExecute(); return; #endif var index = 0; var nodeRecords = new List>(); while (true) { if (MainEditor.Instance.IsEditor && !MainEditor.Instance.IsGo) return; index++; if (index > nodeRecords.Count) nodeRecords.Add(new List()); _data.ClearCache(); nodeRecords[index - 1].Add(MainEditor.Instance.BTNodeId); _btOwner.UpdateBehaviour(); MainEditor.Instance.IsGo = false; nodeRecords[index - 1].Add(MainEditor.Instance.BTNodeId); if (_data.MaxAiAction != null || _data.IsFinish) break; if (index > 150) { LogSystem.LogError($"死循环了,最终记录点为:{MainEditor.Instance.BTNodeId}"); break; } } if (_data.MaxAiAction == null || index > 100) AILogicState = AILogicState.Finished; else { #if ENABLE_TRAIN bool isPack = TrainingState.Instance.GetActionBitCodec(_data.MaxAiAction.ActionLogic.ActionId, _data.MaxAiAction.Param, out var packed); var curPlayer = _data.MaxAiAction.Param.MapData.CurPlayer; var beforeScore = TrainingState.Instance.GetMapScore(_data.MaxAiAction.Param.MapData, curPlayer); var state = TrainingState.Instance.GetMapState(_data.MaxAiAction.Param.MapData, curPlayer); var validActions = TrainingState.Instance.GetAllActionBitCodec(_data.MaxAiAction.Param.MapData, curPlayer, out var actions); if (isPack) { bool found = false; foreach (var action in validActions) { if (action.Count != packed.Count) continue; for (int i = 0; i < packed.Count; i++) { if (action[i] - packed[i] > 0.001f) break; if (i == packed.Count - 1) found = true; } } if (!found) { validActions.Add(packed); LogSystem.LogError($"训练数据出错: {_data.MaxAiAction.ActionLogic.ActionId.GetStringLog()}"); } } #endif TrainingState.Instance.GetActionBitCodecWithoutLimit(_data.MaxAiAction.ActionLogic.ActionId, _data.MaxAiAction.Param, out var packed); if (_actionBitCodec != null && _actionBitCodec.Count == packed.Count) { bool isSame = true; for (int i = 0; i < packed.Count; i++) { if (Mathf.Approximately(packed[i], _actionBitCodec[i])) continue; isSame = false; break; } if (isSame) { _sameCount++; LogSystem.LogError($"存在相似action ,记录点为:{MainEditor.Instance.BTNodeId} ," + $"Action为:{_data.MaxAiAction.ActionLogic.ActionId.GetStringLog()} " + $"重复次数 :{_sameCount}"); } else { _sameCount = 0; } } _data.MaxAiAction.ActionLogic.CompleteExecute(_data.MaxAiAction.Param); _actionBitCodec = packed; _actionCount++; #if ENABLE_TRAIN if (isPack) { var afterScore = TrainingState.Instance.GetMapScore(_data.MaxAiAction.Param.MapData, curPlayer); var reward = afterScore - beforeScore; if (_data.MaxAiAction.ActionLogic.ActionId.UnitActionType == UnitActionType.Capture) { reward += 10; } if (_data.MaxAiAction.ActionLogic.ActionId.UnitActionType == UnitActionType.Upgrade) { reward += 10; } if (_data.MaxAiAction.ActionLogic.ActionId.UnitActionType == UnitActionType.HeroUpgrade) { reward += 10; } if (_data.MaxAiAction.ActionLogic.ActionId.UnitActionType == UnitActionType.Gather) { reward += 10; } if (_data.MaxAiAction.ActionLogic.ActionId.UnitActionType == UnitActionType.Examine) { reward += 10; } TrainingDataRecorder.Instance.RecordStep(curPlayer.Id, state, validActions.Select(x => x.ToArray()).ToArray(), packed.ToArray(), reward); } #endif _data.MaxAiAction.CheckIsActionDuration(); _targetTime = Mathf.Max(_data.MaxAiAction.Duration, 0f); #if UNITY_EDITOR var records = GetCurrentAIRecords(); if (records != null && records.Count != 0) { var record = records[^1]; record.Action = _data.MaxAiAction; } #endif _isWaitFrame = false; MainEditor.Instance.OnActionExcuted(); _data.MaxAiAction = null; AILogicState = AILogicState.Pausing; } } } private void AIModelExecute() { #if ENABLE_AIMODEL var predictState = TrainingState.Instance.GetMapState(_data.Map, _data.Map.CurPlayer); var predictActionsId = TrainingState.Instance.GetAllActionBitCodecForUse(_data.Map, _data.Map.CurPlayer, out var predictActions); if (predictActionsId.Count == 0) { AILogicState = AILogicState.Finished; } else { var predictIndex = ModelInference.Instance.Predict(predictState, predictActionsId); // for (int i = 0; i < predictActions.Count; i++) // { // if (predictActions[i].ActionLogic.ActionId.UnitActionType == UnitActionType.Capture) // predictIndex = i; // } if (!TrainingState.Instance.GetActionFromBitCodec(predictActionsId[predictIndex], _data.Map, out var actionId, out var param)) { LogSystem.LogError($"反序列化 Action 失败"); } else { _data.MaxAiAction = new AIActionBase(param, ActionLogicFactory.GetActionLogic(actionId)); #if ENABLE_TRAIN bool isPack = TrainingState.Instance.GetActionBitCodec(_data.MaxAiAction.ActionLogic.ActionId, _data.MaxAiAction.Param, out var packed); var curPlayer = _data.MaxAiAction.Param.MapData.CurPlayer; var beforeScore = TrainingState.Instance.GetMapScore(_data.MaxAiAction.Param.MapData, curPlayer); var state = TrainingState.Instance.GetMapState(_data.MaxAiAction.Param.MapData, curPlayer); var validActions = TrainingState.Instance.GetAllActionBitCodec(_data.MaxAiAction.Param.MapData, curPlayer, out var actions); if (isPack) { bool found = false; foreach (var action in validActions) { if (action.Count != packed.Count) continue; for (int i = 0; i < packed.Count; i++) { if (action[i] - packed[i] > 0.001f) break; if (i == packed.Count - 1) found = true; } } if (!found) { validActions.Add(packed); LogSystem.LogError($"训练数据出错: {_data.MaxAiAction.ActionLogic.ActionId.GetStringLog()}"); } } #endif _data.MaxAiAction.ActionLogic.CompleteExecute(_data.MaxAiAction.Param); #if ENABLE_TRAIN if (isPack) { var afterScore = TrainingState.Instance.GetMapScore(_data.MaxAiAction.Param.MapData, curPlayer); var reward = afterScore - beforeScore; if (_data.MaxAiAction.ActionLogic.ActionId.UnitActionType == UnitActionType.Capture) { LogSystem.LogError($"占领!!!"); reward += 10; } TrainingDataRecorder.Instance.RecordStep(curPlayer.Id, state, validActions.Select(x => x.ToArray()).ToArray(), packed.ToArray(), reward); } #endif LogSystem.LogWarning($"{_data.MaxAiAction.ActionLogic.ActionId.GetStringLog()}"); _data.MaxAiAction.CheckIsActionInPlayerSight(); _data.MaxAiAction.CheckIsActionDuration(); _targetTime = Mathf.Max(_data.MaxAiAction.Duration, 0f); _isWaitFrame = false; _data.MaxAiAction = null; AILogicState = AILogicState.Pausing; } } #endif } public static List GetCurrentAIRecords() { return AIRecordsDict.GetValueOrDefault(CurrentAIPlayerId); } public static List GetAIRecords(uint playerId) { return AIRecordsDict.GetValueOrDefault(playerId); } } public class AIStateRecord { public uint ID; public string Desc; public bool Result; } public class AIRecord { public List StateRecords; public AIActionBase Action; public bool IsFoldout; public AIRecord() { IsFoldout = false; StateRecords = new List(); } public string GetDesc() { if (Action == null) return "暂无动作"; return Action.ActionLogic.GetType().ToString(); } } }