This commit is contained in:
daixiawu 2025-06-12 11:48:31 +08:00
commit 6d5222755e
16 changed files with 438 additions and 133 deletions

View File

@ -18,7 +18,7 @@ MonoBehaviour:
_version: 3.33
_category:
_comments:
_translation: {x: 1557, y: 43}
_zoomFactor: 0.9998935
_translation: {x: 1177, y: 143}
_zoomFactor: 0.73027754
_haltSerialization: 0
_externalSerializationFile: {fileID: 0}

View File

@ -6,6 +6,8 @@
*/
using System.Collections.Generic;
using System.Linq;
using Logic.AI;
using NodeCanvas.Framework;
using ParadoxNotion.Design;
@ -16,12 +18,12 @@ namespace NodeCanvas.Tasks.Actions
[Category("AIAction")]
public class AICalculateAction : ActionTask
{
public CalculateType CalculateType;
public List<CalculateType> CalculateTypes;
protected override string info
{
get { return string.Format($"Generator {CalculateType}"); }
get { return string.Format($"Calculate Action"); }
}
protected override void OnExecute()
@ -33,8 +35,11 @@ namespace NodeCanvas.Tasks.Actions
EndAction(false);
return;
}
foreach (var calculateType in CalculateTypes)
AIActionScoreCalculator.CalculateAIActionScore(data.value, calculateType);
AIActionScoreCalculator.CalculateAIActionScore(data.value, CalculateType);
data.value.AIActions = data.value.AIActions.OrderBy(a => a.Result.GetAllScore()).ToList();
EndAction(true);
}
}

View File

@ -1,57 +0,0 @@
/*
* @Author:
* @Description:
* @Date: 20250606 19:06:16
* @Modify:
*/
using Logic.AI;
using NodeCanvas.Framework;
using ParadoxNotion.Design;
namespace NodeCanvas.Tasks.Actions
{
[Category("AIAction")]
public class AIFinishAction : ActionTask
{
public Strategy Strategy;
public bool IsPlayer;
public bool IsCity;
public bool IsUnit;
public bool IsLegion;
protected override string info
{
get { return string.Format($"AI Param Action {Strategy}"); }
}
protected override void OnExecute()
{
// 直接从Blackboard获取AICalculatorData
var data = blackboard.GetVariable<AICalculatorData>("Data");
if (data?.value == null)
{
EndAction(false);
return;
}
if (IsPlayer)
{
data.value.TargetParam.PlayerData = data.value.Player;
EndAction(true);
}
if (IsCity)
{
}
var aiAction = data.value.AIActions[0];
aiAction.ActionLogic.Execute(aiAction.Param);
EndAction(true);
}
}
}

View File

@ -0,0 +1,79 @@
/*
* @Author:
* @Description:
* @Date: 20250606 19:06:16
* @Modify:
*/
using Logic.AI;
using NodeCanvas.Framework;
using ParadoxNotion.Design;
namespace NodeCanvas.Tasks.Actions
{
[Category("AIAction")]
public class AIFinishParamAction : ActionTask
{
protected override string info
{
get { return string.Format($"AI Param Action"); }
}
protected override void OnExecute()
{
// 直接从Blackboard获取AICalculatorData
var data = blackboard.GetVariable<AICalculatorData>("Data");
if (data?.value == null)
{
EndAction(false);
return;
}
var strategy = data.value.Strategy;
if (data.value.IsPlayer) EndAction(true);
if (data.value.IsCity)
{
if (data.value.StrategyCity.ContainsKey(strategy) && data.value.StrategyCity[strategy].Count != 0)
{
data.value.StrategyCity[strategy].RemoveAt(0);
EndAction(true);
return;
}
}
if (data.value.IsUnit)
{
if (data.value.StrategyUnit.ContainsKey(strategy) && data.value.StrategyUnit[strategy].Count != 0)
{
data.value.StrategyUnit[strategy].RemoveAt(0);
EndAction(true);
return;
}
}
if (data.value.IsLegion)
{
if (data.value.StrategyLegion.ContainsKey(strategy) && data.value.StrategyLegion[strategy].Count != 0)
{
var legion = data.value.StrategyLegion[strategy][0];
if (data.value.LegionUnit.ContainsKey(legion) && data.value.LegionUnit[legion].Count != 0)
{
data.value.LegionUnit[legion].RemoveAt(0);
}
else
{
data.value.StrategyLegion[strategy].RemoveAt(0);
}
EndAction(true);
return;
}
}
EndAction(false);
}
}
}

View File

@ -6,6 +6,8 @@
*/
using System.Collections.Generic;
using Logic.Action;
using Logic.AI;
using NodeCanvas.Framework;
using ParadoxNotion.Design;
@ -16,12 +18,12 @@ namespace NodeCanvas.Tasks.Actions
[Category("AIAction")]
public class AIGeneratorAction : ActionTask
{
public GeneratorType GeneratorType;
public List<CommonActionType> ActionTypes;
protected override string info
{
get { return string.Format($"Generator {GeneratorType}"); }
get { return string.Format($"Generator Action"); }
}
protected override void OnExecute()
@ -33,9 +35,9 @@ namespace NodeCanvas.Tasks.Actions
EndAction(false);
return;
}
data.value.ActionIds = AIActionGenerator.GeneratorActionIds(GeneratorType);
if (data.value.ActionIds.Count == 0)
foreach (var actionType in ActionTypes)AIActionGenerator.GeneratorActionIds(data.value, actionType);
if (data.value.AIActions.Count == 0)
{
EndAction(false);
return;

View File

@ -37,6 +37,12 @@ namespace NodeCanvas.Tasks.Actions
EndAction(false);
return;
}
data.value.Strategy = Strategy;
data.value.IsPlayer = IsPlayer;
data.value.IsCity = IsCity;
data.value.IsUnit = IsUnit;
data.value.IsLegion = IsLegion;
data.value.TargetParam.MapData = data.value.Map;
data.value.TargetParam.PlayerData = data.value.Player;

View File

@ -0,0 +1,36 @@
/*
* @Author:
* @Description:
* @Date: 20250606 19:06:16
* @Modify:
*/
using Logic.AI;
using NodeCanvas.Framework;
using ParadoxNotion.Design;
namespace NodeCanvas.Tasks.Actions
{
[Category("AIAction")]
public class AIRefreshDataAction : ActionTask
{
protected override string info
{
get { return string.Format($"AI Refresh Data Action"); }
}
protected override void OnExecute()
{
// 直接从Blackboard获取AICalculatorData
var data = blackboard.GetVariable<AICalculatorData>("Data");
if (data?.value == null)
{
EndAction(false);
return;
}
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 876677133d9449e9b14228a2c53db6d3
timeCreated: 1749548918

View File

@ -19,32 +19,23 @@ namespace Logic.AI
Defend,
Development,
}
public enum GeneratorType
{
CountryDefendTech,
CountryAttackTech,
CountryDevelopmentTech,
CityDefendUrgent,
CityMilitaryMobilization,
CityAttack,
CityDefend,
CityDevelopment,
UnitCollect, // 收集
UnitRetreat, // 撤退
UnitExplore, // 探索
UnitDefend, // 防御
UnityAttack, // 进攻
UnitStationed, // 驻扎
}
public enum CalculateType
{
PlayerTechDefend,
PlayerTechScore,
CityLevelUpDefend,
CityTrainDefend,
CityTrainAttack,
CityDevelopment,
UnitExplore,
UnitDefendAttackMove,
UnitDefendMove,
UnitDefendAttack,
}
@ -59,15 +50,42 @@ namespace Logic.AI
public Dictionary<Strategy, List<uint>> StrategyLegion;
public Dictionary<uint, List<UnitData>> LegionUnit;
public HashSet<string> Marks;
public List<uint> ActionIds;
public Strategy Strategy;
public bool IsPlayer;
public bool IsCity;
public bool IsUnit;
public bool IsLegion;
public List<AIActionBase> AIActions;
public CommonActionParams TargetParam;
public void Calculate(MapData map, PlayerData player)
public HashSet<string> Marks;
public AICalculatorData()
{
Map = map;
Player = player;
StrategyCity = new Dictionary<Strategy, List<CityData>>();
StrategyUnit = new Dictionary<Strategy, List<UnitData>>();
StrategyLegion = new Dictionary<Strategy, List<uint>>();
LegionUnit = new Dictionary<uint, List<UnitData>>();
AIActions = new List<AIActionBase>();
TargetParam = new CommonActionParams();
}
public void Refresh(MapData map, PlayerData player)
{
Strategy = Strategy.Development;
IsPlayer = false;
IsCity = false;
IsPlayer = false;
IsLegion = false;
StrategyCity.Clear();
StrategyUnit.Clear();
StrategyLegion.Clear();
LegionUnit.Clear();
AIActions.Clear();
TargetParam = new CommonActionParams();
}
}

View File

@ -262,9 +262,105 @@ namespace Logic.AI
}
}
public static List<uint> GeneratorActionIds(GeneratorType type)
public static void GeneratorActionIds(AICalculatorData data, CommonActionType type)
{
return null;
var actions = ActionLogicFactory.GetActionLogicByType(type);
if (actions == null || actions.Count == 0) return;
if (type == CommonActionType.Gain || type == CommonActionType.Build ||
type == CommonActionType.BuildWonder || type == CommonActionType.GridMisc)
{
if (data.TargetParam.CityData == null) return;
data.TargetParam.MainObjectType = ActionLogicFactory.GetMainObjectType(type);
foreach (var gridId in data.TargetParam.CityData.Territory.TerritoryArea)
{
if (!data.Map.GridMap.GetGridDataByGid(gridId, out var grid)) continue;
data.TargetParam.GridData = grid;
foreach (var action in actions)
{
if (!action.CheckCan(data.TargetParam)) continue;
data.AIActions.Add(new AIActionBase(data.TargetParam.GetCopyParam(), action));
}
}
}
if (type == CommonActionType.StartWonder || type == CommonActionType.LearnTech)
{
data.TargetParam.MainObjectType = ActionLogicFactory.GetMainObjectType(type);
foreach (var action in actions)
{
if (!action.CheckCan(data.TargetParam)) continue;
data.AIActions.Add(new AIActionBase(data.TargetParam.GetCopyParam(), action));
}
}
if (type == CommonActionType.TrainUnit || type == CommonActionType.CityLevelUpAction)
{
if (data.TargetParam.CityData == null) return;
data.TargetParam.MainObjectType = ActionLogicFactory.GetMainObjectType(type);
foreach (var action in actions)
{
if (!action.CheckCan(data.TargetParam)) continue;
data.AIActions.Add(new AIActionBase(data.TargetParam.GetCopyParam(), action));
}
}
if (type == CommonActionType.UnitAction || type == CommonActionType.UnitSkill)
{
if (data.TargetParam.UnitData == null) return;
data.TargetParam.MainObjectType = ActionLogicFactory.GetMainObjectType(type);
foreach (var action in actions)
{
if (!action.CheckCan(data.TargetParam)) continue;
data.AIActions.Add(new AIActionBase(data.TargetParam.GetCopyParam(), action));
}
}
if (type == CommonActionType.UnitMove)
{
if (data.TargetParam.UnitData == null) return;
if (!data.TargetParam.UnitData.Alive) return;
data.TargetParam.MainObjectType = ActionLogicFactory.GetMainObjectType(type);
Main.UnitLogic.CalcUnitMoveInfo(data.Map, data.TargetParam.UnitData.Id);
foreach (var grid in data.Map.GridMap.GridList)
{
var result = Main.UnitLogic.CheckUnitCanMoveOrAttack(data.Map, data.TargetParam.UnitData, grid);
if (result != MoveAttackType.Move) continue;
if (data.TargetParam.UnitData.MP <= 0) continue;
data.TargetParam.GridData = grid;
foreach (var action in actions)
{
if (!action.CheckCan(data.TargetParam)) continue;
data.AIActions.Add(new AIActionBase(data.TargetParam.GetCopyParam(), action));
}
}
}
if (type == CommonActionType.UnitAttack)
{
if (data.TargetParam.UnitData == null) return;
if (!data.TargetParam.UnitData.Alive) return;
data.TargetParam.MainObjectType = ActionLogicFactory.GetMainObjectType(type);
Main.UnitLogic.CalcUnitMoveInfo(data.Map, data.TargetParam.UnitData.Id);
foreach (var grid in data.Map.GridMap.GridList)
{
if (!data.Map.GetUnitDataByGid(grid.Id, out var targetUnit)) continue;
var result = Main.UnitLogic.CheckUnitCanMoveOrAttack(data.Map, data.TargetParam.UnitData, grid);
if (result != MoveAttackType.Attack) continue;
data.TargetParam.GridData = grid;
data.TargetParam.TargetUnitData = targetUnit;
foreach (var action in actions)
{
if (!action.CheckCan(data.TargetParam)) continue;
data.AIActions.Add(new AIActionBase(data.TargetParam.GetCopyParam(), action));
}
}
}
}
}
}

View File

@ -51,25 +51,16 @@ namespace Logic.AI
public class CalculateResult
{
public Dictionary<AICalculatorType, float> ScoreDict = new Dictionary<AICalculatorType, float>();
public Dictionary<CalculateType, float> Score = new Dictionary<CalculateType, float>();
public float GetAllScore()
{
var score = 0f;
foreach (var kv in ScoreDict) score += kv.Value;
foreach (var kv in Score) score += kv.Value;
return score;
}
public string GetAllLog()
{
var log = "";
foreach (var kv in ScoreDict)
{
log += $"{kv.Key} : {kv.Value}\n";
}
return log;
}
}
@ -565,9 +556,32 @@ namespace Logic.AI
result.ScoreDict[AICalculatorType.TechScore] = _techCalculator.CalculateTechScorePro(mapData, playerData, techType, _cfg) * techInfo.Ratio;
}
// 这里的得分是和当前情况的差值
public static void CalculateAIActionScore(AICalculatorData data, CalculateType type)
{
data.AIActions = data.AIActions.OrderBy(a => a.Result.GetAllScore()).ToList();
if (type == CalculateType.PlayerTechDefend)
{
foreach (var aiAction in data.AIActions)
{
aiAction.Result = new CalculateResult();
if (aiAction.ActionLogic.ActionId.ActionType == CommonActionType.LearnTech &&
aiAction.ActionLogic.ActionId.TechType == TechType.Strategy)
{
aiAction.Result.Score[type] = 2;
}
if (aiAction.ActionLogic.ActionId.ActionType == CommonActionType.LearnTech &&
aiAction.ActionLogic.ActionId.TechType == TechType.Organization)
{
aiAction.Result.Score[type] = 1;
}
}
}
}
public static void CalculatePlayerTechDefend(AICalculatorData data)
{
}
}

View File

@ -33,13 +33,14 @@ namespace Logic.Action
UnitAction,
CityLevelUpAction,
UnitSkill,
LearnTech
LearnTech,
UnitMove,
UnitAttack,
}
// 通用行为参数类
public class CommonActionParams
{
public MapData MapData { get; set; }
public PlayerData PlayerData { get; set; }
public UnitData UnitData { get; set; }
@ -100,10 +101,29 @@ namespace Logic.Action
TargetUnitData = target;
}
}
public CommonActionParams GetCopyParam()
{
var param = new CommonActionParams();
param.MapData = MapData;
param.PlayerData = PlayerData;
param.UnitData = UnitData;
param.CityData = CityData;
param.GridData = GridData;
param.TargetUnitData = TargetUnitData;
param.MainObjectType = MainObjectType;
param.PlayerId = PlayerId;
param.UnitId = UnitId;
param.CityId = CityId;
param.GridId = GridId;
param.TargetUnitId = TargetUnitId;
RefreshParams();
return param;
}
}
// 通用行为ID类
[Serializable]
public class CommonActionId
@ -121,6 +141,7 @@ namespace Logic.Action
public GridMiscActionType GridMiscActionType;
public SkillType SkillType;
public TechType TechType;
//自动生成唯一Id Hash
public uint Id => ComputeId();
@ -184,19 +205,34 @@ namespace Logic.Action
public static class ActionLogicFactory
{
private static Dictionary<CommonActionId, ActionLogicBase> ActionLogicDict;
private static Dictionary<uint, ActionLogicBase> _actionLogicIdDict;
public static Dictionary<CommonActionId, ActionLogicBase> GetActionLogicDict()
{
Refresh();
return ActionLogicDict;
}
public static Dictionary<uint, ActionLogicBase> GetActionLogicIDDict()
{
Refresh();
return _actionLogicIdDict;
}
public static void Refresh()
{
if (ActionLogicDict != null)
return;
ActionLogicDict = new Dictionary<CommonActionId, ActionLogicBase>();
CommonActionId commonActionId;
commonActionId = new CommonActionId { ActionType = CommonActionType.UnitMove };
ActionLogicDict[commonActionId] = new UnitMoveAction(commonActionId);
commonActionId = new CommonActionId { ActionType = CommonActionType.UnitAttack };
ActionLogicDict[commonActionId] = new UnitAttackAction(commonActionId);
foreach (ResourceType resourceType in System.Enum.GetValues(typeof(ResourceType)))
{
if (resourceType == ResourceType.None) continue;
@ -352,14 +388,14 @@ namespace Logic.Action
};
ActionLogicDict[commonActionId] = new LearnTechAction(commonActionId);
}
//登记6种unitskill类的科技该科技会让该玩家当前的和未来的所有unit都拥有某个skill
foreach (SkillType skillType in System.Enum.GetValues(typeof(SkillType)))
if (skillType == SkillType.MOUNTAINMOVE || skillType == SkillType.MOUNTAINDEFENSE
|| skillType == SkillType.WATERMOVE || skillType == SkillType.WATERDEFENSE
|| skillType == SkillType.OCEANMOVE || skillType == SkillType.OCEANDEFENSE
|| skillType == SkillType.FORESTDEFENSE)
{
if (skillType == SkillType.MOUNTAINMOVE || skillType == SkillType.MOUNTAINDEFENSE ||
skillType == SkillType.WATERMOVE || skillType == SkillType.WATERDEFENSE ||
skillType == SkillType.OCEANMOVE || skillType == SkillType.OCEANDEFENSE ||
skillType == SkillType.FORESTDEFENSE)
{
commonActionId = new CommonActionId
{
@ -368,7 +404,16 @@ namespace Logic.Action
};
ActionLogicDict[commonActionId] = new UnitSkillAction(commonActionId);
}
}
if (_actionLogicIdDict == null)
{
_actionLogicIdDict = new Dictionary<uint, ActionLogicBase>();
foreach (var kv in ActionLogicDict)
{
_actionLogicIdDict[kv.Key.Id] = kv.Value;
}
}
}
public static ActionLogicBase GetActionLogic(CommonActionId actionId)
@ -462,9 +507,11 @@ namespace Logic.Action
actionList = null;
return false;
}
var attackAction = new UnitAttackAction(null);
var moveAction = new UnitMoveAction(null);
var attackID = new CommonActionId { ActionType = CommonActionType.UnitAttack };
var moveId = new CommonActionId { ActionType = CommonActionType.UnitMove };
var attackAction = new UnitAttackAction(attackID);
var moveAction = new UnitMoveAction(moveId);
actionList = new List<AIActionBase>();
Main.UnitLogic.CalcUnitMoveInfo(map, unit.Id);
@ -512,6 +559,35 @@ namespace Logic.Action
}
return actionList.Count > 0;
}
public static List<ActionLogicBase> GetActionLogicByType(CommonActionType type)
{
var actionList = new List<ActionLogicBase>();
foreach (var action in GetActionLogicDict().Values)
{
if (action.ActionId.ActionType != type) continue;
actionList.Add(action);
}
return actionList;
}
public static MainObjectType GetMainObjectType(CommonActionType actionType)
{
if (actionType == CommonActionType.Gain) return MainObjectType.Grid;
if (actionType == CommonActionType.Build) return MainObjectType.Grid;
if (actionType == CommonActionType.StartWonder) return MainObjectType.Player;
if (actionType == CommonActionType.BuildWonder) return MainObjectType.Grid;
if (actionType == CommonActionType.TrainUnit) return MainObjectType.City;
if (actionType == CommonActionType.GridMisc) return MainObjectType.Grid;
if (actionType == CommonActionType.UnitAction) return MainObjectType.Unit;
if (actionType == CommonActionType.CityLevelUpAction) return MainObjectType.City;
if (actionType == CommonActionType.UnitSkill) return MainObjectType.Unit;
if (actionType == CommonActionType.LearnTech) return MainObjectType.Player;
if (actionType == CommonActionType.UnitMove) return MainObjectType.Unit;
if (actionType == CommonActionType.UnitAttack) return MainObjectType.Unit;
return MainObjectType.Player;
}
}
@ -552,7 +628,7 @@ namespace Logic.Action
Debug.Log($"{_actionId.ActionType} : {_actionId.ResourceType}/{_actionId.UnitType}/{_actionId.WonderType}");
}
}
// 建造奇观逻辑类 #buildwonder
public class BuildWonderAction : ActionLogicBase
@ -1793,7 +1869,7 @@ namespace Logic.Action
}
}
//GridMisc行为逻辑类包括Destroy,ClearForest GrowForest BurnForest
//GridMisc行为逻辑类包括Destroy, ClearForest GrowForest BurnForest
public class GridMiscAction : ActionLogicBase
{
public GridMiscAction(CommonActionId id) : base(id)

View File

@ -20,6 +20,7 @@ using TMPro;
using UnityEditor;
using UnityEngine;
using Debug = UnityEngine.Debug;
using Object = UnityEngine.Object;
namespace Logic.Editor
@ -338,16 +339,24 @@ namespace Logic.Editor
private ScriptableObject GetExportAsset(ScriptableObject origin)
{
var target = $"Assets/Resources/Export/{origin.name}.asset";
if (File.Exists(target)) AssetDatabase.DeleteAsset(target);
bool success = AssetDatabase.CopyAsset(AssetDatabase.GetAssetPath(origin), target);
if (!success)
if (origin == null) return null;
// 创建新实例并深拷贝数据
ScriptableObject newInstance = Object.Instantiate(origin);
// 处理目标路径
string targetPath = $"Assets/Resources/Export/{origin.name}.asset";
if (AssetDatabase.LoadAssetAtPath<ScriptableObject>(targetPath) != null)
{
Debug.LogError($"拷贝失败!!!");
return null;
AssetDatabase.DeleteAsset(targetPath);
}
return AssetDatabase.LoadAssetAtPath<ScriptableObject>(target);
// 保存新实例
AssetDatabase.CreateAsset(newInstance, targetPath);
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
return newInstance;
}
private void TraverseObject(object asset)

View File

@ -55,7 +55,8 @@ namespace Logic.Multilingual
return _multilingualData.GetFontGroupID(font);
}
public void SetUIText(TextMeshProUGUI textCom, string id)
// 这里的 ID 是 ID 字符串paramList 是实际的字符串
public void SetUIText(TextMeshProUGUI textCom, string id, List<string> paramList=null)
{
if (!textCom) return;
var multilingual = textCom.gameObject.GetComponent<MultilingualTextMono>();
@ -63,6 +64,7 @@ namespace Logic.Multilingual
bool isNumeric = uint.TryParse(id, out uint realId); // 仅整数
if (!isNumeric) return;
multilingual.ID = realId;
multilingual.ParamList = paramList;
multilingual.OnMultilingualChanged();
}

View File

@ -6,6 +6,8 @@
*/
using System.Collections.Generic;
using System.Text.RegularExpressions;
using TMPro;
using UnityEngine;
@ -17,6 +19,13 @@ namespace Logic.Multilingual
public bool Ban;
public uint ID = 0;
public uint FontID = 0;
private List<string> _paramList;
public List<string> ParamList
{
get => _paramList ??= new List<string>();
set => _paramList = value;
}
private void OnEnable()
@ -42,6 +51,13 @@ namespace Logic.Multilingual
var font = MultilingualManager.Instance.GetMultilingualFont(FontID);
if (font)text.font = font;
}
if (_paramList != null && _paramList.Count != 0)
{
int index = 0;
text.text = Regex.Replace(text.text, "{param}", m =>
index < _paramList.Count ? _paramList[index++] : m.Value);
}
}
public void BindFontID()