模拟攻击
This commit is contained in:
parent
28991eefb4
commit
c040c58724
601
.github/skills/th1-logic/SKILL.md
vendored
Normal file
601
.github/skills/th1-logic/SKILL.md
vendored
Normal file
@ -0,0 +1,601 @@
|
||||
---
|
||||
name: th1-logic
|
||||
description: '用于 TH1 回合制策略游戏逻辑开发。使用场景:新增技能(Skill)、行为(Action)、数据(Data)、逻辑(Logic)的标准开发流程。触发词:技能、action、skill、game logic、th1'
|
||||
user-invocable: true
|
||||
---
|
||||
|
||||
# TH1_Logic 游戏逻辑开发 Skill
|
||||
|
||||
> 用于 TH1 回合制策略游戏的逻辑层开发,包括:新增技能(Skill)、行为(Action)、数据(Data)、逻辑(Logic) 的标准开发流程。
|
||||
|
||||
---
|
||||
|
||||
## 架构概览
|
||||
|
||||
```
|
||||
TH1_Data/ ← 纯数据层 (namespace RuntimeData)
|
||||
TH1_Logic/
|
||||
├── Action/ ← 行为系统 (namespace Logic.Action)
|
||||
├── Skill/ ← 技能系统 (namespace Logic.Skill)
|
||||
├── Unit/ ← 单位逻辑 (namespace Logic)
|
||||
├── Player/ ← 玩家逻辑 (namespace Logic)
|
||||
└── City/ ← 城市逻辑 (namespace Logic)
|
||||
```
|
||||
|
||||
### 核心设计原则
|
||||
|
||||
1. **数据与逻辑分离**:`RuntimeData` 命名空间放纯数据,逻辑通过 `Main.UnitLogic`、`Main.PlayerLogic`、`Main.CityLogic` 静态入口访问
|
||||
2. **MapData 是中心枢纽**:所有跨实体查询都通过 `MapData` 中转(如 `mapData.GetPlayerDataByUnitId()`、`mapData.GetGridDataByUnitId()`)
|
||||
3. **IdentifierBase 四大实体**:`PlayerData`、`UnitData`、`CityData`、`GridData` 均继承 `IdentifierBase`,拥有 `uint Id` 和 `List<SkillBase> Skills`
|
||||
4. **逻辑-视觉分离**:逻辑操作不直接操控视觉,通过 `Renderer(mapData)?.InstantUpdateXxx()` 间接更新
|
||||
5. **序列化兼容**:数据类使用 `[MemoryPackable]`,必须提供 `[MemoryPackConstructor]`,私有字段需 `[MemoryPackInclude]`,非序列化字段用 `[MemoryPackIgnore]`
|
||||
|
||||
---
|
||||
|
||||
## 一、新增技能 (Skill)
|
||||
|
||||
### 步骤
|
||||
|
||||
#### Step 1:在 `SkillType` 枚举中注册
|
||||
|
||||
在 `SkillBase.cs` 的 `SkillType` 枚举末尾(`Max = 999` 之前)新增枚举项。**只能新增,不能删除已有项**。
|
||||
|
||||
```csharp
|
||||
// SkillBase.cs 中的 SkillType 枚举
|
||||
public enum SkillType
|
||||
{
|
||||
// ... 已有技能 ...
|
||||
NewSkillName = <下一个可用数字>,
|
||||
Max = 999,
|
||||
}
|
||||
```
|
||||
|
||||
#### Step 2:创建技能类文件
|
||||
|
||||
在 `TH1_Logic/Skill/AllSkill/` 目录下创建 `NewSkillNameSkill.cs`:
|
||||
|
||||
```csharp
|
||||
using RuntimeData;
|
||||
using MemoryPack;
|
||||
|
||||
namespace Logic.Skill
|
||||
{
|
||||
public partial class NewSkillNameSkill : SkillBase
|
||||
{
|
||||
public NewSkillNameSkill()
|
||||
{
|
||||
IsPermanent = true; // true=永久技能, false=有回合限制
|
||||
TurnsLimit = 0; // 如果非永久,设置持续回合数
|
||||
Score = 2; // AI 评估用的分数权重
|
||||
// IsLevelSkill = false; // 是否为叠层技能
|
||||
// _autoDisappear = true; // 叠层为0时是否自动消失
|
||||
}
|
||||
|
||||
public override SkillType GetSkillType()
|
||||
{
|
||||
return SkillType.NewSkillName;
|
||||
}
|
||||
|
||||
// === 根据需求选择性重写以下生命周期/判断方法 ===
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Step 3:选择性重写方法
|
||||
|
||||
根据技能功能,从以下分类中选择需要重写的方法:
|
||||
|
||||
**生命周期类**(事件触发时调用):
|
||||
|
||||
| 方法 | 触发时机 | 典型场景 |
|
||||
|------|---------|---------|
|
||||
| `OnTurnStart(IdentifierBase self, MapData map)` | 回合开始 | 自动治疗、回合buff |
|
||||
| `OnAfterTurnStart(IdentifierBase self, MapData map)` | 回合开始之后 | 避免技能时序冲突 |
|
||||
| `OnTurnEnd(IdentifierBase self, MapData map)` | 回合结束 | 回合结束效果 |
|
||||
| `OnMove(UnitData self, GridData grid, MapData map, MoveType moveType, ...)` | 移动后 | 冲锋(Dash)、移动触发技能 |
|
||||
| `BeforeMove(UnitData self, GridData grid, MapData map, MoveType moveType, ...)` | 移动前 | 移动前判断 |
|
||||
| `BeforeDamagedSupportStage(MapData map, SettlementInfo info)` | 受伤结算前(支援阶段) | 承伤减免 |
|
||||
| `BeforeDamagedTransformStage(MapData map, SettlementInfo info)` | 受伤结算前(转移阶段) | 伤害转移 |
|
||||
| `OnDamaged(MapData map, SettlementInfo info)` | 受伤结算时 | 受伤反应 |
|
||||
| `BeforeDamageOther(MapData map, SettlementInfo info)` | 对他人伤害结算前 | 额外伤害 |
|
||||
| `OnDamageOther(MapData map, SettlementInfo info)` | 对他人伤害结算时 | 击杀/转化效果 |
|
||||
| `AfterDamageOther(MapData map, SettlementInfo info)` | 对他人伤害结算后 | 击后效果 |
|
||||
| `BeforeActiveAttackOther(MapData map, UnitData origin, UnitData target, out int addDmg)` | 主动攻击之前 | 额外伤害加成 |
|
||||
| `AfterActiveAttackOther(MapData map, UnitData origin, UnitData target)` | 主动攻击之后 | 攻击后效果 |
|
||||
| `OnSkillAdd(MapData map, uint originId)` | 技能被添加时 | 初始化效果 |
|
||||
| `OnSkillOverride(MapData map, uint originId, uint selfId)` | 技能被覆盖时 | 叠层技能增层 |
|
||||
| `OnFinished(IdentifierBase self, MapData map)` | 技能结束(超时/移除)时 | 清理效果 |
|
||||
|
||||
**全局事件类**(附着在任意实体上,监听全局事件):
|
||||
|
||||
| 方法 | 触发时机 |
|
||||
|------|---------|
|
||||
| `OnAnyUnitMove(MapData, IdentifierBase, UnitData, GridData, MoveType)` | 任何单位移动时 |
|
||||
| `OnAnyUnitDie(MapData, UnitData self, UnitData dieUnit)` | 任何单位死亡时 |
|
||||
| `OnAnyUnitCreate(MapData, IdentifierBase, UnitData newUnit)` | 任何单位创建时 |
|
||||
| `BeforeUnitDamaged(UnitData self, MapData, SettlementInfo)` | 任何单位受伤前 |
|
||||
| `OnUnitDamaged(UnitData self, MapData, SettlementInfo)` | 任何单位受伤时 |
|
||||
|
||||
**判断属性类**(影响单位能力判定):
|
||||
|
||||
| 方法 | 返回值 | 用途 |
|
||||
|------|--------|------|
|
||||
| `IsLimitSelfMove(UnitData, MapData)` | `bool` | 限制自身移动 |
|
||||
| `IsLimitSelfAttack(UnitData, MapData)` | `bool` | 限制自身攻击 |
|
||||
| `IsLimitTargetCounterAttack(UnitData, MapData)` | `bool` | 限制敌方反击 |
|
||||
| `IsLimitSelfCounterAttack(UnitData, MapData)` | `bool` | 限制自身反击 |
|
||||
| `IsIgnoreControlArea(UnitData, MapData)` | `bool` | 无视敌人控制区 |
|
||||
| `IsCanMoveOnTerrain(UnitData, MapData, TerrainType)` | `bool` | 能在指定地形移动 |
|
||||
| `IsCanMoveOnFeature(UnitData, MapData, TerrainFeature)` | `bool` | 能在指定地貌移动 |
|
||||
| `IsIgnoreMoveLoss(UnitData, MapData, TerrainFeature/Vegetation)` | `bool` | 无视地形移动惩罚 |
|
||||
| `IsInvisible(UnitData, MapData)` | `bool` | 隐身状态 |
|
||||
| `CanAttackAll(UnitData, MapData)` | `bool` | 能攻击所有人 |
|
||||
| `IsCanBeKill(UnitData, MapData)` | `bool` | 能否被击杀 |
|
||||
|
||||
**数值修正类**(影响战斗计算):
|
||||
|
||||
| 方法 | 返回值 | 用途 |
|
||||
|------|--------|------|
|
||||
| `GetAttackAdditionParam(MapData, UnitData, UnitData)` | `float` | 攻击加法修正 |
|
||||
| `GetAttackMultiplicationParam(MapData, UnitData, UnitData)` | `float` | 攻击乘法修正 (1.0=无修正) |
|
||||
| `GetDefenseAdditionParam(MapData, UnitData, UnitData)` | `float` | 防御加法修正 |
|
||||
| `GetDefenseMultiplicationParam(MapData, UnitData, UnitData)` | `float` | 防御乘法修正 |
|
||||
| `GetExtraSight(UnitData, MapData)` | `int` | 额外视野范围 |
|
||||
| `GetExtraMoveRange(MapData, UnitData)` | `int` | 额外移动范围 |
|
||||
| `GetExtraAttackRange(MapData, UnitData)` | `int` | 额外攻击范围 |
|
||||
| `GetCriticalHitRate(UnitData, MapData)` | `float` | 暴击率 |
|
||||
|
||||
**变形保留判断**:
|
||||
|
||||
| 方法 | 说明 |
|
||||
|------|------|
|
||||
| `ReservedOnTransform(UnitData self, UnitFullType fullType)` | 单位类型变换时是否保留此技能 |
|
||||
| `ReservedOnTransformBoat(UnitData, UnitFullType)` | 陆→船变换时保留 |
|
||||
| `ReservedOnTransformFromBoat(UnitData, UnitFullType)` | 船→陆变换时保留 |
|
||||
| `ReservedOnTransformUpgrade(UnitData, UnitFullType)` | 升级变换时保留 |
|
||||
|
||||
#### Step 4:配置技能数据
|
||||
|
||||
在表格配置 `SkillDataAssets` 中注册新技能的 `skillPriority` 等信息。
|
||||
|
||||
#### Step 5:挂载技能
|
||||
|
||||
技能通过 `IdentifierBase` 的方法挂载到实体:
|
||||
|
||||
```csharp
|
||||
// 标准添加
|
||||
entity.AddOrOverrideSkill(SkillType.NewSkillName, mapData, originId);
|
||||
|
||||
// 初始化添加(新建单位时)
|
||||
entity.AddInitSkill(SkillType.NewSkillName, mapData);
|
||||
|
||||
// Legacy 添加(兼容旧逻辑,含回合限制/叠层/强制覆盖)
|
||||
entity.AddSkill_Legacy(SkillType.NewSkillName, mapData,
|
||||
IsPermanent, turnLimit, IsLevel, level, autoDisappear,
|
||||
SpecialAddSkillType.Normal, originId);
|
||||
```
|
||||
|
||||
### 技能示例模板
|
||||
|
||||
**简单属性技能(攻击力翻倍1回合)**:
|
||||
```csharp
|
||||
public partial class AttackUpSkill : SkillBase
|
||||
{
|
||||
public AttackUpSkill()
|
||||
{
|
||||
IsPermanent = false;
|
||||
TurnsLimit = 1;
|
||||
Score = 2;
|
||||
}
|
||||
public override SkillType GetSkillType() => SkillType.ATTACKUP;
|
||||
public override float GetAttackMultiplicationParam(MapData mapData, UnitData self, UnitData target = null) => 1.5f;
|
||||
public override bool ReservedOnTransform(UnitData self, UnitFullType fullType) => true;
|
||||
}
|
||||
```
|
||||
|
||||
**生命周期技能(回合开始治疗周围友军)**:
|
||||
```csharp
|
||||
public partial class AutoHealSkill : SkillBase
|
||||
{
|
||||
public AutoHealSkill()
|
||||
{
|
||||
IsPermanent = true;
|
||||
TurnsLimit = 0;
|
||||
Score = 2;
|
||||
}
|
||||
public override SkillType GetSkillType() => SkillType.AUTOHEAL;
|
||||
public override void OnTurnStart(IdentifierBase identifier, MapData mapData)
|
||||
{
|
||||
var self = identifier as UnitData;
|
||||
if (self == null) return;
|
||||
var player = self.Player(mapData);
|
||||
if (player == null) return;
|
||||
var selfUnitList = new HashSet<UnitData>();
|
||||
mapData.GetUnitDataListByPlayerId(player.Id, selfUnitList);
|
||||
if (!mapData.GetGridDataByUnitId(self.Id, out var targetGrid)) return;
|
||||
var roundGrid = mapData.GridMap.GetAroundGridData(1, 1, targetGrid);
|
||||
foreach (var grid in roundGrid)
|
||||
{
|
||||
if (!mapData.GetUnitDataByGid(grid.Id, out var unit)) continue;
|
||||
if (!selfUnitList.Contains(unit)) continue;
|
||||
unit.Health += 2;
|
||||
if (unit.Health > unit.GetMaxHealth())
|
||||
unit.Health = unit.GetMaxHealth();
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**事件触发技能(移动后保留攻击力)**:
|
||||
```csharp
|
||||
public partial class DashSkill : SkillBase
|
||||
{
|
||||
public bool IsTrigger = false;
|
||||
public override void OnRefresh() { IsTrigger = false; }
|
||||
public override void OnTurnStart(IdentifierBase identifier, MapData mapData) { IsTrigger = false; }
|
||||
public DashSkill() { IsPermanent = true; Score = 4; }
|
||||
public override SkillType GetSkillType() => SkillType.DASH;
|
||||
public override void OnDamageOther(MapData mapData, SettlementInfo info)
|
||||
{
|
||||
if (info.DamageType != DamageType.ActiveAttack) return;
|
||||
IsTrigger = true;
|
||||
}
|
||||
public override void OnMove(UnitData self, GridData grid, MapData mapData, MoveType moveType, List<Vector2Int> path = null)
|
||||
{
|
||||
if (IsTrigger) return;
|
||||
if (moveType == MoveType.ActiveMove) self.AP = 1;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 二、新增行为 (Action)
|
||||
|
||||
### 行为类型
|
||||
|
||||
| ActionType | 说明 | 基类 | 操作主体 |
|
||||
|-----------|------|------|---------|
|
||||
| `Build` | 建造建筑 | `BuildAction` | Grid |
|
||||
| `TrainUnit` | 训练单位 | `TrainUnitAction` | City/Grid |
|
||||
| `UnitAction` | 单位行动 | `UnitActionAction` | Unit |
|
||||
| `GridMisc` | 格子杂项 | `GridMiscAction` | Grid |
|
||||
| `PlayerAction` | 玩家行动 | `PlayerActionAction` | Player |
|
||||
| `UnitMove` | 单位移动 | `UnitMoveAction` | Unit |
|
||||
| `UnitAttack` | 单位攻击 | `UnitAttackAction` | Unit |
|
||||
|
||||
### 标准开发流程
|
||||
|
||||
#### Step 1:注册行为子类型枚举(如需)
|
||||
|
||||
在对应枚举中新增,如 `UnitActionType`、`GridMiscActionType`、`PlayerActionType`。
|
||||
|
||||
#### Step 2:创建或扩展行为类
|
||||
|
||||
每个行为必须实现三个核心方法:
|
||||
|
||||
```csharp
|
||||
public class NewAction : ActionLogicBase
|
||||
{
|
||||
public NewAction(CommonActionId id) : base(id) { }
|
||||
|
||||
// 执行行为逻辑
|
||||
protected override bool Execute(CommonActionParams actionParams)
|
||||
{
|
||||
// Step 1: 鲁棒性检查
|
||||
if (!CheckCan(actionParams)) return false;
|
||||
|
||||
// Step 2: 扣费/消耗
|
||||
actionParams.PlayerData.SpendCoin(GetCost(actionParams));
|
||||
|
||||
// Step 3: 核心逻辑修改 MapData
|
||||
// ...
|
||||
|
||||
// Step 4: 视觉更新
|
||||
// grid.Renderer(mapData)?.InstantUpdateGrid(true);
|
||||
// unit.Renderer(mapData)?.InstantUpdateUnit(true);
|
||||
|
||||
// Step 5: 后续逻辑(更新连通性、建筑等级等)
|
||||
// Main.PlayerLogic.UpdateCityConnect(mapData, playerData);
|
||||
// Main.PlayerLogic.UpdateTerritoryAllBuildingLevel(mapData, playerId);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// 判断能否执行(逻辑严格判断,用于AI和玩家操作)
|
||||
public override bool CheckCan(CommonActionParams actionParam)
|
||||
{
|
||||
// 1. 鲁棒性:MainObjectType检查
|
||||
// 2. 科技检查:playerData.TechTree.CheckActionCan(_actionId)
|
||||
// 3. 资源检查:GetCost() <= playerData.PlayerCoin
|
||||
// 4. 领土检查:是否在自己领土
|
||||
// 5. 敌人检查:格子上有无敌人
|
||||
// 6. 特殊条件
|
||||
return true;
|
||||
}
|
||||
|
||||
// 判断是否显示在UI中(比CheckCan宽松,不检查金钱)
|
||||
public override bool CheckShow(CommonActionParams actionParam, out ShowType showType)
|
||||
{
|
||||
showType = ShowType.None;
|
||||
// 类似CheckCan但不检查金钱
|
||||
return true;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Step 3:注册到工厂
|
||||
|
||||
在 `ActionLogicFactory` 中注册新行为,使 `GetActionLogic()` 能通过 `CommonActionId` 创建实例。
|
||||
|
||||
#### Step 4:构建调用参数
|
||||
|
||||
```csharp
|
||||
var actionId = new CommonActionId
|
||||
{
|
||||
ActionType = CommonActionType.UnitAction,
|
||||
UnitActionType = UnitActionType.NewAction
|
||||
};
|
||||
var action = ActionLogicFactory.GetActionLogic(actionId);
|
||||
var param = new CommonActionParams(
|
||||
mapData: mapData,
|
||||
playerData: playerData,
|
||||
unitData: unitData,
|
||||
gridData: gridData,
|
||||
mainObjectType: MainObjectType.Unit
|
||||
);
|
||||
action?.CompleteExecute(param);
|
||||
```
|
||||
|
||||
### CommonActionParams 参数规范
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `MapData` | `MapData` | 当前地图数据(必填) |
|
||||
| `PlayerData` | `PlayerData` | 执行操作的玩家 |
|
||||
| `UnitData` | `UnitData` | 操作的单位 |
|
||||
| `CityData` | `CityData` | 操作的城市 |
|
||||
| `GridData` | `GridData` | 操作的格子 |
|
||||
| `TargetUnitData` | `UnitData` | 目标单位 |
|
||||
| `TargetGridData` | `GridData` | 目标格子 |
|
||||
| `TargetPlayerData` | `PlayerData` | 目标玩家 |
|
||||
| `MainObjectType` | `MainObjectType` | 操作主体类型(Unit/City/Grid/Player) |
|
||||
|
||||
---
|
||||
|
||||
## 三、数据类 (Data) 开发规范
|
||||
|
||||
### IdentifierBase 继承体系
|
||||
|
||||
```
|
||||
IdentifierBase (uint Id, List<SkillBase> Skills)
|
||||
├── PlayerData ← 玩家数据
|
||||
├── UnitData ← 单位数据
|
||||
├── CityData ← 城市数据
|
||||
└── GridData ← 格子数据
|
||||
```
|
||||
|
||||
### 数据类必须遵循
|
||||
|
||||
1. 标记 `[MemoryPackable]` 和 `partial`
|
||||
2. 提供无参 `[MemoryPackConstructor]` 构造函数
|
||||
3. 提供拷贝构造函数 `new XxxData(XxxData copyData)` 用于 AI 深拷贝
|
||||
4. 提供 `DeepCopy(XxxData copyData)` 方法用于复用对象池的深拷贝
|
||||
5. 如有字典等非序列化字段,提供 `[MemoryPackOnDeserialized]` 标记的后处理方法
|
||||
6. 视觉标记字段用 `[MemoryPackIgnore]`
|
||||
|
||||
### 实体关系查询 (通过 MapData)
|
||||
|
||||
```csharp
|
||||
// Unit → Player
|
||||
mapData.GetPlayerDataByUnitId(unitId, out PlayerData player);
|
||||
|
||||
// Unit → Grid
|
||||
mapData.GetGridDataByUnitId(unitId, out GridData grid);
|
||||
|
||||
// Unit → City
|
||||
mapData.GetCityDataByUnitId(unitId, out CityData city);
|
||||
|
||||
// Grid → Unit
|
||||
mapData.GetUnitDataByGid(gridId, out UnitData unit);
|
||||
|
||||
// Grid → City (领土)
|
||||
mapData.GetCityDataByTerritoryGid(gridId, out CityData city);
|
||||
|
||||
// Grid → Player (领土)
|
||||
mapData.GetPlayerDataByTerritoryGridId(gridId, out PlayerData player);
|
||||
|
||||
// City → Player
|
||||
mapData.GetPlayerDataByCityId(cityId, out PlayerData player);
|
||||
|
||||
// City → Grid (城中心)
|
||||
mapData.GetGridDataByCityId(cityId, out GridData grid);
|
||||
|
||||
// Player → 首都
|
||||
mapData.GetCapitalCityDataByPlayerId(playerId, out CityData capital);
|
||||
|
||||
// 快捷方式(从UnitData直接访问)
|
||||
unitData.Player(mapData) // → PlayerData
|
||||
unitData.Grid(mapData) // → GridData
|
||||
unitData.City(mapData) // → CityData
|
||||
```
|
||||
|
||||
### MapData 核心子结构
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `GridMap` | `GridMapData` | 所有格子(按坐标/ID索引) |
|
||||
| `UnitMap` | `UnitMapData` | 所有单位(按ID索引) |
|
||||
| `CityMap` | `CityMapData` | 所有城市(按ID索引) |
|
||||
| `PlayerMap` | `PlayerMapData` | 所有玩家 |
|
||||
| `Net` | `NetData` | 网络/回合信息 |
|
||||
| `MapConfig` | `MapConfig` | 地图配置 |
|
||||
|
||||
---
|
||||
|
||||
## 四、逻辑层 (Logic) 开发规范
|
||||
|
||||
### 入口访问
|
||||
|
||||
```csharp
|
||||
Main.UnitLogic // UnitLogic 实例
|
||||
Main.PlayerLogic // PlayerLogic 实例
|
||||
Main.CityLogic // CityLogic 实例
|
||||
Main.MapData // 当前真实 MapData(非 AI 模拟用)
|
||||
```
|
||||
|
||||
### 视觉更新模式
|
||||
|
||||
```csharp
|
||||
// 格子视觉更新
|
||||
gridData.Renderer(mapData)?.InstantUpdateGrid(true);
|
||||
|
||||
// 单位视觉更新
|
||||
unitData.Renderer(mapData)?.InstantUpdateUnit(true);
|
||||
|
||||
// 城市信息视觉更新
|
||||
cityData.SetCityRenderer(mapData);
|
||||
cityData.CityInfoRenderer(mapData)?.InstantUpdateCityInfo();
|
||||
|
||||
// 播放特效(仅在玩家视野内)
|
||||
gridData.Renderer(mapData)?.PlayVFXInSight(new GridVFXParams(GridVFXType.Fog));
|
||||
|
||||
// 判断是否在玩家视野内
|
||||
gridData.InMainSight()
|
||||
unitData.InMainSight()
|
||||
```
|
||||
|
||||
### AI 模拟 vs 真实 MapData
|
||||
|
||||
```csharp
|
||||
// 判断是否为真实地图(非AI模拟副本)
|
||||
if (mapData == Main.MapData) { /* 只在真实地图执行 */ }
|
||||
|
||||
// 判断是否为当前显示的地图
|
||||
if (mapData.IsCurrentShowMap()) { /* 执行视觉更新 */ }
|
||||
|
||||
// AI模拟使用的DeepCopy不拷贝 Players 和 Actions(性能优化)
|
||||
```
|
||||
|
||||
### 随机数规范
|
||||
|
||||
```csharp
|
||||
// 必须使用 MapData 的确定性随机数(保证网络同步)
|
||||
var random = mapData.Net.GetRandom(mapData);
|
||||
var value = random.Next(0, 100);
|
||||
// 禁止使用 System.Random 或 UnityEngine.Random(会导致不同步)
|
||||
```
|
||||
|
||||
### 常用操作模板
|
||||
|
||||
```csharp
|
||||
// 视野更新
|
||||
var gidList = mapData.GridMap.GetAroundGridIdList(sightRange, gridData);
|
||||
Main.PlayerLogic.UpdateSight_LogicView(mapData, playerData, gidList);
|
||||
|
||||
// 城市经验更新
|
||||
Main.CityLogic.GridGiveCityExp_LogicView(mapData, playerData, gridData, cityData, expAmount);
|
||||
|
||||
// 建筑等级刷新
|
||||
Main.PlayerLogic.UpdateTerritoryAllBuildingLevel(mapData, playerData.Id);
|
||||
|
||||
// 城市连通性更新
|
||||
Main.PlayerLogic.UpdateCityConnect(mapData, playerData);
|
||||
|
||||
// 伤害结算
|
||||
Main.UnitLogic.DamageSettlement(mapData, attacker, target, damage, DamageType.ActiveAttack);
|
||||
|
||||
// 单位移动
|
||||
Main.UnitLogic.MoveToLogic(mapData, unitData, gridData, MoveType.ActiveMove);
|
||||
|
||||
// 单位死亡
|
||||
Main.UnitLogic.UnitUnnaturalDie(mapData, unitData);
|
||||
|
||||
// 单位类型变换
|
||||
Main.UnitLogic.UnitTypeTransform(mapData, unitData, newUnitType);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 五、关键枚举速查
|
||||
|
||||
### UnitType(单位类型)
|
||||
```
|
||||
Warrior, Archer, Rider, Defender, Swordsman, Catapult, Knight,
|
||||
Ship, RammerShip, BomberShip, Giant(英雄), Minder, ...
|
||||
```
|
||||
|
||||
### TerrainType(地形)
|
||||
```
|
||||
Land, ShallowSea, DeepSea
|
||||
```
|
||||
|
||||
### TerrainFeature(地貌)
|
||||
```
|
||||
None, Mountain, Road
|
||||
```
|
||||
|
||||
### ResourceType(资源/建筑)
|
||||
```
|
||||
None, Crop, Metal, Fruit, Animal, Fish,
|
||||
Farm, Mine, LumberHut, Port, Bridge,
|
||||
Windmill, Sawmill, Forge, Market, Academy, Military,
|
||||
Temple, ForestTemple, WaterTemple, MountainTemple, KingTemple,
|
||||
CityCenter, Wonder, NavalBase, ...
|
||||
```
|
||||
|
||||
### MoveType(移动方式)
|
||||
```
|
||||
ActiveMove, PassiveMove, AttackMove, SkillMove
|
||||
```
|
||||
|
||||
### DamageType(伤害类型)
|
||||
```
|
||||
ActiveAttack, CounterAttack, FollowAttack, Splash, True, KillSelf, DelayAttack, PushAttack
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 六、开发检查清单
|
||||
|
||||
### 新增技能时
|
||||
|
||||
- [ ] `SkillType` 枚举已添加(只增不删)
|
||||
- [ ] 技能类文件在 `AllSkill/` 目录下,类名为 `XxxSkill`,使用 `partial class`
|
||||
- [ ] 构造函数设置了 `IsPermanent`、`TurnsLimit`、`Score`
|
||||
- [ ] `GetSkillType()` 返回正确的枚举值
|
||||
- [ ] 如有自定义字段需要序列化,用 `[MemoryPackInclude]` 标记
|
||||
- [ ] 如有自定义字段需要拷贝,重写 `GetSkillCopy()` 和 `DeepCopy()`
|
||||
- [ ] 表格配置已注册 `skillPriority`
|
||||
- [ ] 涉及视觉更新时判断了 `mapData == Main.MapData` 或 `InMainSight()`
|
||||
|
||||
### 新增行为时
|
||||
|
||||
- [ ] 行为子类型枚举已添加
|
||||
- [ ] 实现了 `Execute()`、`CheckCan()`、`CheckShow()` 三个方法
|
||||
- [ ] `Execute()` 开头调用 `CheckCan()` 做鲁棒性保护
|
||||
- [ ] 已在 `ActionLogicFactory` 注册
|
||||
- [ ] 金钱消耗使用 `playerData.SpendCoin()`,金钱增加使用 `playerData.AddCoin()`
|
||||
- [ ] 建设/改造后调用 `UpdateTerritoryAllBuildingLevel` 和 `UpdateCityConnect`
|
||||
|
||||
### 修改数据类时
|
||||
|
||||
- [ ] 新增字段兼容 MemoryPack 序列化
|
||||
- [ ] 拷贝构造函数和 `DeepCopy()` 已更新
|
||||
- [ ] `OnAfterMemoryPackDeserialize()` 已更新(如涉及字典)
|
||||
|
||||
---
|
||||
|
||||
## 七、常见问题
|
||||
|
||||
**Q: 技能应该挂到哪个实体上?**
|
||||
A: 看技能作用范围。影响单个单位→挂UnitData;影响玩家全局→挂PlayerData;影响城市→挂CityData;影响特定格子→挂GridData。
|
||||
|
||||
**Q: `CheckCan` 和 `CheckShow` 有什么区别?**
|
||||
A: `CheckCan` 用于实际执行前的完整校验(包括金钱);`CheckShow` 用于UI展示(不检查金钱,只检查条件是否可见)。
|
||||
|
||||
**Q: 为什么要用 `mapData.GetXxxByYyy()` 而不是直接引用?**
|
||||
A: 实体间关系是动态的(如单位可以被转化、城市可以被占领),必须通过 MapData 的关系表实时查询。
|
||||
|
||||
**Q: AI 模拟时要注意什么?**
|
||||
A: AI 模拟使用 MapData 的深拷贝副本。不要在非 `Main.MapData` 上执行视觉更新。随机数必须使用 `mapData.Net.GetRandom(mapData)` 保证确定性。
|
||||
@ -545,6 +545,46 @@ namespace Logic.AI
|
||||
return CalMap;
|
||||
}
|
||||
|
||||
// 模拟攻击,获取伤害和是否死亡
|
||||
public static void SimulateAttack(MapData map, UnitData attacker, UnitData defender, out bool isDie, out float dmg)
|
||||
{
|
||||
isDie = false;
|
||||
dmg = 0;
|
||||
|
||||
if (attacker == null || defender == null) return;
|
||||
if (!attacker.IsAlive() || !defender.IsAlive()) return;
|
||||
|
||||
// 记录攻击前血量
|
||||
var defenderHealthBefore = defender.Health;
|
||||
|
||||
// 在副本 map 上执行完整攻击 action
|
||||
RefreshCalMap(map);
|
||||
CalMap.DeepCopy(map);
|
||||
|
||||
var attackId = new CommonActionId { ActionType = CommonActionType.UnitAttack };
|
||||
var attackAction = new UnitAttackAction(attackId);
|
||||
|
||||
if (!CalMap.GetPlayerDataByUnitId(attacker.Id, out var calPlayer)) return;
|
||||
if (!CalMap.UnitMap.GetUnitDataByUnitId(attacker.Id, out var calAttacker)) return;
|
||||
if (!CalMap.UnitMap.GetUnitDataByUnitId(defender.Id, out var calDefender)) return;
|
||||
|
||||
var param = new CommonActionParams(
|
||||
mapData: CalMap,
|
||||
playerData: calPlayer,
|
||||
unitData: calAttacker,
|
||||
targetUnit: calDefender,
|
||||
mainObjectType: MainObjectType.Unit
|
||||
);
|
||||
param.OnParamChanged();
|
||||
|
||||
attackAction.CompleteExecute(param);
|
||||
|
||||
// 读取结果
|
||||
isDie = !calDefender.IsAlive();
|
||||
dmg = isDie ? defenderHealthBefore : defenderHealthBefore - calDefender.Health;
|
||||
}
|
||||
|
||||
|
||||
// 这里的得分是和当前情况的差值
|
||||
public static AIActionBase CalculateAIActionScore(AICalculatorData data, List<CalculateType> types, out string log)
|
||||
{
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user