2026-01-10 17:14:33 +08:00

407 lines
16 KiB
C#

/*
* @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<AIActionBase> 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<float> _actionBitCodec;
private int _sameCount;
public static uint CurrentAIPlayerId;
public static Dictionary<uint, List<AIRecord>> AIRecordsDict;
public AILogic()
{
AIRecordsDict = new Dictionary<uint, List<AIRecord>>();
AILogicState = AILogicState.Prepare;
RecordActions = new List<AIActionBase>();
_scoreCalculator = new AIActionScoreCalculator();
_cfg = Resources.Load<AIConfigAsset>("Export/AIConfig");
_generator = new AIActionGenerator();
_logicObject = GameObject.Find("AIBT");
_btOwner = _logicObject.GetComponent<BehaviourTreeOwner>();
var data = _btOwner.blackboard.GetVariable<AICalculatorData>("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<AIRecord>();
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<List<uint>>();
while (true)
{
if (MainEditor.Instance.IsEditor && !MainEditor.Instance.IsGo) return;
index++;
if (index > nodeRecords.Count) nodeRecords.Add(new List<uint>());
_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<AIRecord> GetCurrentAIRecords()
{
return AIRecordsDict.GetValueOrDefault(CurrentAIPlayerId);
}
public static List<AIRecord> GetAIRecords(uint playerId)
{
return AIRecordsDict.GetValueOrDefault(playerId);
}
}
public class AIStateRecord
{
public uint ID;
public string Desc;
public bool Result;
}
public class AIRecord
{
public List<AIStateRecord> StateRecords;
public AIActionBase Action;
public bool IsFoldout;
public AIRecord()
{
IsFoldout = false;
StateRecords = new List<AIStateRecord>();
}
public string GetDesc()
{
if (Action == null) return "暂无动作";
return Action.ActionLogic.GetType().ToString();
}
}
}