Compare commits

...

2 Commits

Author SHA1 Message Date
1a37eed852 Merge branch 'main' of http://10.27.16.144:3000/kawagiri/TH1 into main 2025-05-13 11:45:02 +08:00
cf72dfcc12 AI 调整 2025-05-13 11:44:59 +08:00
6 changed files with 333 additions and 126 deletions

View File

@ -14,76 +14,193 @@ namespace Logic.AI
{
public class AIActionGenerator
{
private MapData _mapData;
private PlayerData _player;
private List<UnitData> _waitUnits;
private List<GridData> _waitGrids;
private List<CityData> _waitCity;
private uint _actionRecord;
private uint _recordCount;
public AIActionType ActionType => (AIActionType)(_actionRecord % (int)AIActionType.Max);
public void Init(MapData map, PlayerData player)
{
_mapData = map;
_player = player;
_waitUnits ??= new List<UnitData>();
_waitGrids ??= new List<GridData>();
_waitCity ??= new List<CityData>();
var gridSet = map.GetPlayerTerritoryGridIdSet(player.Id);
var unitList = new List<UnitData>();
map.GetUnitDataListByPlayerId(player.Id, unitList);
var cityList = new List<CityData>();
map.GetCityDataListByPlayerId(player.Id, cityList);
foreach (var unit in unitList) _waitUnits.Add(unit);
foreach (var city in cityList) _waitCity.Add(city);
foreach (var grid in _mapData.GridMap.GridList)
if(gridSet.Contains(grid.Id)) _waitGrids.Add(grid);
_actionRecord = 0;
_recordCount = 0;
}
public void GeneratorOneStepActions(MapData map, PlayerData player, List<AIActionBase> aiActions = null)
{
if (aiActions == null) aiActions = new List<AIActionBase>();
for (int i = 0; i < (int)AIActionType.Max; i++)
{
if (_recordCount > 100) break;
_recordCount++;
_actionRecord++;
var actionType = (AIActionType)(_actionRecord % (int)AIActionType.Max);
if (actionType == AIActionType.Unit) GeneratorUnitActions(aiActions);
else if (actionType == AIActionType.Grid) GeneratorGridActions(aiActions);
else if (actionType == AIActionType.City) GeneratorCityActions(aiActions);
if (aiActions.Count != 0) break;
}
}
private void GeneratorUnitActions(List<AIActionBase> aiActions)
{
if (_waitUnits.Count == 0 || aiActions == null) return;
for (int i = _waitUnits.Count - 1; i >= 0; i--)
{
GeneratorOneUnitActions(_waitUnits[i], aiActions);
if (aiActions.Count != 0) break;
_waitUnits.RemoveAt(i);
}
}
private void GeneratorOneUnitActions(UnitData unit, List<AIActionBase> aiActions)
{
if (ActionLogicFactory.UnitHasMoveAndAttackAction(_mapData, unit, out var aiActionList))
{
foreach (var aiAction in aiActionList) aiActions.Add(aiAction);
}
var param = new CommonActionParams(_mapData, playerData:_player, unitData:unit);
param.RefreshParams();
if (!ActionLogicFactory.UnitHasAction(param, out var actions)) return;
foreach (var action in actions)
{
if (param.PlayerData.PlayerWealth < action.GetCost()) continue;
aiActions.Add(new AIActionBase(param, action));
}
}
private void GeneratorGridActions(List<AIActionBase> aiActions)
{
if (_waitUnits.Count == 0 || aiActions == null) return;
for (int i = _waitGrids.Count - 1; i >= 0; i--)
{
GeneratorOneGridActions(_waitGrids[i], aiActions);
if (aiActions.Count != 0) break;
_waitGrids.RemoveAt(i);
}
}
private void GeneratorOneGridActions(GridData grid, List<AIActionBase> aiActions)
{
var gridParam = new CommonActionParams(_mapData, playerData:_player, gridData:grid, mainObjectType:MainObjectType.Grid);
var actionList = new List<ActionLogicBase>();
if (!ActionLogicFactory.GridHasAction(gridParam, actionList)) return;
foreach (var action in actionList)
{
if (gridParam.PlayerData.PlayerWealth < action.GetCost()) continue;
aiActions.Add(new AIActionBase(gridParam, action));
}
}
private void GeneratorCityActions(List<AIActionBase> aiActions)
{
if (_waitCity.Count == 0 || aiActions == null) return;
for (int i = _waitCity.Count - 1; i >= 0; i--)
{
GeneratorOneCityActions(_waitCity[i], aiActions);
if (aiActions.Count != 0) break;
_waitCity.RemoveAt(i);
}
}
private void GeneratorOneCityActions(CityData city, List<AIActionBase> aiActions)
{
var param = new CommonActionParams(_mapData, playerData:_player, cityData:city);
param.RefreshParams();
if (!ActionLogicFactory.CityHasAction(param, out var actions)) return;
foreach (var action in actions)
{
if (param.PlayerData.PlayerWealth < action.GetCost()) continue;
aiActions.Add(new AIActionBase(param, action));
}
}
// 穷举式生成所有可能的行动
public static void GeneratorActions(MapData map, PlayerData player, AIActionType actionType, List<AIActionBase> aiActions = null)
public void GeneratorActions(MapData map, PlayerData player, List<AIActionBase> aiActions = null)
{
if (aiActions == null) aiActions = new List<AIActionBase>();
// 小兵行为
if (actionType == AIActionType.Unit)
var unitList = new List<UnitData>();
map.GetUnitDataListByPlayerId(player.Id, unitList);
foreach (var unit in unitList)
{
var unitList = new List<UnitData>();
map.GetUnitDataListByPlayerId(player.Id, unitList);
foreach (var unit in unitList)
if (ActionLogicFactory.UnitHasMoveAndAttackAction(map, unit, out var aiActionList))
{
if (ActionLogicFactory.UnitHasMoveAndAttackAction(map, unit, out var aiActionList))
{
foreach (var aiAction in aiActionList) aiActions.Add(aiAction);
}
var param = new CommonActionParams(map, playerData:player, unitData:unit);
param.RefreshParams();
if (!ActionLogicFactory.UnitHasAction(param, out var actions)) continue;
foreach (var action in actions)
{
if (param.PlayerData.PlayerWealth < action.GetCost()) continue;
aiActions.Add(new AIActionBase(param, action));
}
foreach (var aiAction in aiActionList) aiActions.Add(aiAction);
}
var param = new CommonActionParams(map, playerData: player, unitData: unit);
param.RefreshParams();
if (!ActionLogicFactory.UnitHasAction(param, out var actions)) continue;
foreach (var action in actions)
{
if (param.PlayerData.PlayerWealth < action.GetCost()) continue;
aiActions.Add(new AIActionBase(param, action));
}
}
if (actionType == AIActionType.Grid)
// 地块行为
var gridSet = map.GetPlayerTerritoryGridIdSet(player.Id);
var gridParam = new CommonActionParams(map, playerData: player, gridData: null,
mainObjectType: MainObjectType.Grid);
var actionList = new List<ActionLogicBase>();
foreach (var gird in map.GridMap.GridList)
{
// 地块行为
var gridSet = map.GetPlayerTerritoryGridIdSet(player.Id);
var gridParam = new CommonActionParams(map, playerData:player, gridData:null, mainObjectType:MainObjectType.Grid);
var actionList = new List<ActionLogicBase>();
foreach (var gird in map.GridMap.GridList)
if (!gridSet.Contains(gird.Id)) continue;
gridParam.GridData = gird;
gridParam.RefreshParams();
if (!ActionLogicFactory.GridHasAction(gridParam, actionList)) continue;
var param = new CommonActionParams(map, playerData: player, gridData: gird,
mainObjectType: MainObjectType.Grid);
foreach (var action in actionList)
{
if (!gridSet.Contains(gird.Id)) continue;
gridParam.GridData = gird;
gridParam.RefreshParams();
if (!ActionLogicFactory.GridHasAction(gridParam, actionList)) continue;
var param = new CommonActionParams(map, playerData:player, gridData:gird, mainObjectType:MainObjectType.Grid);
foreach (var action in actionList)
{
if (param.PlayerData.PlayerWealth < action.GetCost()) continue;
aiActions.Add(new AIActionBase(param, action));
}
if (param.PlayerData.PlayerWealth < action.GetCost()) continue;
aiActions.Add(new AIActionBase(param, action));
}
}
if (actionType == AIActionType.City)
// 城市行为
var cityList = new List<CityData>();
map.GetCityDataListByPlayerId(player.Id, cityList);
foreach (var city in cityList)
{
// 城市行为
var cityList = new List<CityData>();
map.GetCityDataListByPlayerId(player.Id, cityList);
foreach (var city in cityList)
var param = new CommonActionParams(map, playerData: player, cityData: city);
param.RefreshParams();
if (!ActionLogicFactory.CityHasAction(param, out var actions)) continue;
foreach (var action in actions)
{
var param = new CommonActionParams(map, playerData:player, cityData:city);
param.RefreshParams();
if (!ActionLogicFactory.CityHasAction(param, out var actions)) continue;
foreach (var action in actions)
{
if (param.PlayerData.PlayerWealth < action.GetCost()) continue;
aiActions.Add(new AIActionBase(param, action));
}
if (param.PlayerData.PlayerWealth < action.GetCost()) continue;
aiActions.Add(new AIActionBase(param, action));
}
}
if (actionType == AIActionType.Tech) GeneratorTechActions(map, player, aiActions);
GeneratorTechActions(map, player, aiActions);
}
// 穷举科技树行动
public static void GeneratorTechActions(MapData map, PlayerData player, List<AIActionBase> aiActions = null)
{
@ -105,9 +222,7 @@ namespace Logic.AI
public static void GeneratorFutureActions(MapData map, PlayerData player, List<AIActionBase> aiActions = null)
{
if (aiActions == null) aiActions = new List<AIActionBase>();
GeneratorActions(map, player, AIActionType.Grid, aiActions);
GeneratorActions(map, player, AIActionType.City, aiActions);
GeneratorActions(map, player, AIActionType.Unit, aiActions);
GeneratorTechActions(map, player, aiActions);
for (int i = aiActions.Count - 1; i >= 0; i--)
{

View File

@ -79,6 +79,7 @@ namespace Logic.AI
private Dictionary<UnitData, float> _unitScore;
private Dictionary<CityData, float> _cityScore;
private Dictionary<CityData, float> _cityDefendScore;
public HashSet<UnitData> _unitOnCity;
private UnitTargetMap _unitTargetMap;
private List<CommonActionId> _actionList;
@ -86,6 +87,7 @@ namespace Logic.AI
public Dictionary<UnitData, float> UnitScore => _unitScore;
public Dictionary<CityData, float> CityScore => _cityScore;
public Dictionary<CityData, float> CityDefendScore => _cityDefendScore;
public HashSet<UnitData> UnitOnCity => _unitOnCity;
public UnitTargetMap UnitTargetMap => _unitTargetMap;
@ -95,10 +97,12 @@ namespace Logic.AI
if (aiAction.ActionLogic.ActionId.UnitActionType == UnitActionType.Capture) return true;
if (aiAction.ActionLogic.ActionId.UnitActionType == UnitActionType.Gather) return true;
if (aiAction.ActionLogic.ActionId.UnitActionType == UnitActionType.Examine) return true;
if (aiAction.ActionLogic is UnitAttackAction && UnitOnCity.Contains(aiAction.Param.TargetUnitData)) return true;
return false;
}
public AIActionBase CalculateMaxScoreAIAction(MapData mapData, PlayerData player, List<AIActionBase> actions, AIConfigAsset cfg, AIActionType actionType)
public AIActionBase CalculateMaxScoreAIAction(MapData mapData, PlayerData player, List<AIActionBase> actions, AIConfigAsset cfg)
{
_cfg = cfg;
AIActionBase maxAction = null;
@ -107,40 +111,39 @@ namespace Logic.AI
var maxScoreOffset = 0f;
var futureActions = new List<AIActionBase>();
if (actionType == AIActionType.Tech)
// if (actionType == AIActionType.Tech)
// {
// AIActionGenerator.GeneratorFutureActions(mapData, player, futureActions);
// foreach (var aiAction in actions)
// {
// calMap.DeepCopy(mapData);
// aiAction.Param.MapData = calMap;
// aiAction.Param.RefreshParams();
// aiAction.ActionLogic.Execute(aiAction.Param);
// if (aiAction.ActionLogic.ActionId == null)continue;
// aiAction.Result = CalculateFutureScore(aiAction.Param.MapData, player);
// if (maxAction == null || aiAction.Result.GetAllScore() > 0) maxAction = aiAction;
// }
// }
foreach (var aiAction in actions)
{
AIActionGenerator.GeneratorFutureActions(mapData, player, futureActions);
foreach (var aiAction in actions)
{
calMap.DeepCopy(mapData);
aiAction.Param.MapData = calMap;
aiAction.Param.RefreshParams();
aiAction.ActionLogic.Execute(aiAction.Param);
if (aiAction.ActionLogic.ActionId == null)continue;
aiAction.Result = CalculateFutureScore(aiAction.Param.MapData, player);
if (maxAction == null || aiAction.Result.GetAllScore() > 0) maxAction = aiAction;
}
}
else
{
foreach (var aiAction in actions)
{
calMap.DeepCopy(mapData);
aiAction.Param.MapData = calMap;
aiAction.Param.RefreshParams();
aiAction.ActionLogic.Execute(aiAction.Param);
aiAction.Result = CalculateScore(aiAction.Param.MapData, player, cfg);
calMap.DeepCopy(mapData);
aiAction.Param.MapData = calMap;
aiAction.Param.RefreshParams();
aiAction.ActionLogic.Execute(aiAction.Param);
aiAction.Result = CalculateScore(aiAction.Param.MapData, player, cfg);
if (IsTrueAction(aiAction)) return aiAction;
var scoreOffset = aiAction.Result.GetAllScore() - startResult.GetAllScore();
if (scoreOffset < 0) continue;
if (maxAction == null || scoreOffset > maxScoreOffset)
{
maxAction = aiAction;
maxScoreOffset = scoreOffset;
}
if (IsTrueAction(aiAction)) return aiAction;
var scoreOffset = aiAction.Result.GetAllScore() - startResult.GetAllScore();
if (scoreOffset < 0) continue;
if (maxAction == null || scoreOffset > maxScoreOffset)
{
maxAction = aiAction;
maxScoreOffset = scoreOffset;
}
}
return maxAction;
}
@ -150,17 +153,19 @@ namespace Logic.AI
_unitScore ??= new Dictionary<UnitData, float>();
_cityScore ??= new Dictionary<CityData, float>();
_cityDefendScore ??= new Dictionary<CityData, float>();
_unitOnCity ??= new HashSet<UnitData>();
_canMoveGrid ??= new HashSet<GridData>();
_unitTargetMap ??= new UnitTargetMap();
_unitScore.Clear();
_cityScore.Clear();
_cityDefendScore.Clear();
_unitOnCity.Clear();
_canMoveGrid.Clear();
UnitTargetMap.Clear();
var result = new CalculateResult();
for (int i = 0; i < (int)AICalculatorType.Max; i++)
{
RefreshUnitPos(mapData, player, result);
if (i == (int)AICalculatorType.Sight) CalculatePlayerSightScore(mapData, player, result);
if (i == (int)AICalculatorType.UnitCanMove) CalculateUnitCanMoveScore(mapData, player, result);
if (i == (int)AICalculatorType.MoneyScore) CalculateMoneyScore(mapData, player, result);
@ -301,6 +306,30 @@ namespace Logic.AI
result.ScoreDict[AICalculatorType.CityDefendScore] = cityDefendScore;
}
// 刷新小兵位置
private void RefreshUnitPos(MapData mapData, PlayerData playerData, CalculateResult result)
{
var selfCities = mapData.GetCityDataSetByPlayerId(playerData.Id);
var cityGridSet = new HashSet<GridData>();
foreach (var city in mapData.CityMap.CityList)
{
if (selfCities.Contains(city)) continue;
if (!mapData.GetGridDataByCityId(city.Id, out var gridData)) continue;
cityGridSet.Add(gridData);
}
var selfUnits = new HashSet<UnitData>();
mapData.GetUnitDataListByPlayerId(playerData.Id, selfUnits);
foreach (var unit in mapData.UnitMap.UnitList)
{
if (selfUnits.Contains(unit)) continue;
if (!mapData.GetGridDataByUnitId(unit.Id, out var gridData)) continue;
if (!cityGridSet.Contains(gridData)) continue;
_unitOnCity.Add(unit);
}
}
// 刷新小兵攻击评分
private void RefreshUnitAttackScore(MapData mapData, PlayerData playerData, CalculateResult result)
{
@ -520,7 +549,6 @@ namespace Logic.AI
var actions = new List<AIActionBase>();
AIActionGenerator.GeneratorFutureActions(mapData, player, actions);
if (actions.Count == 0) return result;
var calMap = mapData.GetDeepCopyMapData();
float score = 0;

View File

@ -7,6 +7,7 @@
using System;
using System.Collections.Generic;
using UnityEngine;
@ -32,21 +33,33 @@ namespace Logic.AI
public float UnitExploreStarfishScore;
public float FutureScoreTransformValue;
public List<AICalculatorTechInfo> TechInfoList = new List<AICalculatorTechInfo>();
public AICalculatorTechInfo GetTechInfo(TechType techType)
{
foreach (var techInfo in TechInfoList)
{
if (techInfo.TechType != techType) continue;
return techInfo;
}
var newTechInfo = new AICalculatorTechInfo()
{
TechType = techType,
Ratio = 1
};
TechInfoList.Add(newTechInfo);
return newTechInfo;
}
}
[Serializable]
public class AICalculatorConfig
public class AICalculatorTechInfo
{
public AICalculatorType CalType;
public float CalScore;
public float FutureScoreTransformValue;
public AICalculatorConfig(AICalculatorType type, float score)
{
CalType = type;
CalScore = score;
}
public TechType TechType;
public float Ratio;
}
}

View File

@ -7,9 +7,11 @@
using System.Collections.Generic;
using System.Diagnostics;
using Logic.Action;
using UnityEngine;
using RuntimeData;
using Debug = UnityEngine.Debug;
namespace Logic.AI
@ -38,6 +40,7 @@ namespace Logic.AI
private float _recordTime;
private AIActionScoreCalculator _scoreCalculator;
private AIActionGenerator _generator;
private List<AIActionBase> RecordActions;
private AIActionBase MaxScoreAction;
@ -45,10 +48,6 @@ namespace Logic.AI
private MapData _mapData;
private PlayerData _playerData;
private AIConfigAsset _cfg;
private Dictionary<AIActionType, bool> _actionFinishState;
private uint _curActionState;
private uint _actionLimit;
public AILogic()
@ -57,9 +56,8 @@ namespace Logic.AI
RecordActions = new List<AIActionBase>();
_scoreCalculator = new AIActionScoreCalculator();
_cfg = Resources.Load<AIConfigAsset>("DataAssets/AiConfig");
_actionFinishState = new Dictionary<AIActionType, bool>();
for (int i = 0; i < (int)AIActionType.Max; i++) _actionFinishState[(AIActionType)i] = false;
_generator = new AIActionGenerator();
}
// 开始 AI 逻辑
@ -68,10 +66,7 @@ namespace Logic.AI
AILogicState = AILogicState.Playing;
_mapData = mapData;
_playerData = playerData;
for (int i = 0; i < (int)AIActionType.Max; i++) _actionFinishState[(AIActionType)i] = false;
_curActionState = 0;
_actionLimit = 0;
_generator.Init(_mapData, _playerData);
}
// 结束 AI 逻辑
@ -91,18 +86,12 @@ namespace Logic.AI
if (AILogicState == AILogicState.Playing)
{
for (int i = 0; i < (int)AIActionType.Max; i++)
{
if (_actionFinishState[(AIActionType)i]) continue;
var actionType = (AIActionType)(_curActionState % (int)AIActionType.Max);
AIActionGenerator.GeneratorActions(_mapData, _playerData, actionType, RecordActions);
CalculateMaxScoreAction(actionType);
_curActionState++;
if (MaxScoreAction != null) break;
_actionFinishState[(AIActionType)i] = true;
}
if (_actionLimit >= 20) MaxScoreAction = null;
Stopwatch sw = new Stopwatch();
sw.Start();
RecordActions.Clear();
_generator.GeneratorOneStepActions(_mapData, _playerData, RecordActions);
CalculateMaxScoreAction();
if (sw.Elapsed.TotalMilliseconds > 20) Debug.Log($"{_generator.ActionType} 耗时:{sw.Elapsed.TotalMilliseconds} ms");
// AI 执行已结束
if (!GetAILogicPermission())
@ -121,12 +110,10 @@ namespace Logic.AI
MaxScoreAction.Param.MapData = _mapData;
MaxScoreAction.Param.RefreshParams();
MaxScoreAction.ActionLogic.Execute(MaxScoreAction.Param);
_actionLimit++;
MaxScoreAction = null;
AILogicState = AILogicState.Pausing;
_recordTime = Time.time;
}
RecordActions.Clear();
}
}
@ -137,9 +124,10 @@ namespace Logic.AI
}
// 选取最高得分行动
private void CalculateMaxScoreAction(AIActionType actionType)
private void CalculateMaxScoreAction()
{
MaxScoreAction = _scoreCalculator.CalculateMaxScoreAIAction(_mapData, _playerData, RecordActions, _cfg, actionType);
if (RecordActions.Count == 0) return;
MaxScoreAction = _scoreCalculator.CalculateMaxScoreAIAction(_mapData, _playerData, RecordActions, _cfg);
if (MaxScoreAction != null && MaxScoreAction.ActionLogic.ActionId != null &&
MaxScoreAction.ActionLogic.ActionId.ActionType == CommonActionType.LearnTech)
{

View File

@ -0,0 +1,60 @@
/*
* @Author:
* @Description:
* @Date: 20250512 20:05:44
* @Modify:
*/
using RuntimeData;
namespace Logic.AI
{
public class AITechScoreCalculator
{
public float CalculateTechScore(MapData map, PlayerData player, TechType tech, AIConfigAsset cfg)
{
if (tech == TechType.Climbing)
{
var count = 0;
foreach (var gridId in player.Sight.SightGidSet)
{
if (!map.GridMap.GetGridDataByGid(gridId, out var gridData)) continue;
if (gridData.Feature != TerrainFeature.Mountain) continue;
count++;
}
return cfg.GetTechInfo(TechType.Climbing).Ratio * count;
}
if (tech == TechType.Fishing)
{
var count = 0;
foreach (var gridId in player.Sight.SightGidSet)
{
if (!map.GridMap.GetGridDataByGid(gridId, out var gridData)) continue;
if (gridData.Terrain != TerrainType.ShallowSea) continue;
count++;
}
return cfg.GetTechInfo(TechType.Climbing).Ratio * count;
}
if (tech == TechType.Sailing)
{
var count = 0;
foreach (var gridId in player.Sight.SightGidSet)
{
if (!map.GridMap.GetGridDataByGid(gridId, out var gridData)) continue;
if (gridData.Terrain != TerrainType.DeepSea) continue;
count++;
}
return cfg.GetTechInfo(TechType.Climbing).Ratio * count;
}
return 0;
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: e604e03efac4414e9511f983bcbe8611
timeCreated: 1747053040