diff --git a/Assets/GameMain/DataTables/Weapon.txt b/Assets/GameMain/DataTables/Weapon.txt index 6b69ff2..9c72174 100644 --- a/Assets/GameMain/DataTables/Weapon.txt +++ b/Assets/GameMain/DataTables/Weapon.txt @@ -2,7 +2,7 @@ # Id EntityTypeId Title IconAssetName Rarity Price PriceRandomPercent Attack Cooldown AttackRange AttackSoundId Pramas Modifiers # int int string string RarityType int float int float float int string[] StatModifier[] # 武器编号 策划备注 武器实体编号 武器名 图标资源名 道具品质 武器价格 价格浮动 伤害 冷却 范围 攻击音效编号 额外参数 额外属性 - 1 玩家武器 201 小刀 Almighty_Icon White 120 0.05 100 1.5 5 10000 [hitRadius:2] [] - 2 202 手枪 Almighty_Icon White 130 0.05 120 1 15 10000 [] [] - 3 203 斧头 Almighty_Icon White 100 0.1 150 2 5 10000 [SectorAngle:120] [] - 4 204 闪电 Almighty_Icon White 150 0.08 80 3 12 10000 [hitRadius:3;HoverHeight:10] [] + 1 玩家武器 201 小刀 Almighty_Icon White 120 0.05 100 1.5 5 10000 {"hitRadius":2} [] + 2 202 手枪 Almighty_Icon White 130 0.05 120 1 15 10000 {} [] + 3 203 斧头 Almighty_Icon White 100 0.1 150 2 5 10000 {"SectorAngle":120} [] + 4 204 闪电 Almighty_Icon White 150 0.08 80 3 12 10000 {"hitRadius":3} [] diff --git a/Assets/GameMain/Scripts/DataTable/DRWeapon.cs b/Assets/GameMain/Scripts/DataTable/DRWeapon.cs index 01bba93..edf6b37 100644 --- a/Assets/GameMain/Scripts/DataTable/DRWeapon.cs +++ b/Assets/GameMain/Scripts/DataTable/DRWeapon.cs @@ -1,10 +1,10 @@ using System; using System.Collections.Generic; -using System.Globalization; using Definition.DataStruct; using Definition.Enum; using GameFramework; using CustomUtility; +using Newtonsoft.Json.Linq; using UnityEngine; using UnityGameFramework.Runtime; @@ -74,6 +74,11 @@ namespace DataTable /// public Dictionary Pramas { get; private set; } + /// + /// 获取武器额外参数 Json。 + /// + public string ParamsJson { get; private set; } + /// /// 获取武器额外属性。 /// @@ -97,7 +102,8 @@ namespace DataTable Cooldown = float.Parse(columnStrings[index++]); AttackRange = float.Parse(columnStrings[index++]); AttackSoundId = int.Parse(columnStrings[index++]); - Pramas = DeserializeParams(columnStrings[index++]); + ParamsJson = columnStrings[index++]; + Pramas = DeserializeParams(ParamsJson); Modifiers = Utility.Json.ToObject(columnStrings[index++]); GeneratePropertyArray(); @@ -114,30 +120,38 @@ namespace DataTable /// /// /// - /// private Dictionary DeserializeParams(string rawParams) { - if (!rawParams.StartsWith('[') || !rawParams.EndsWith(']')) - { - throw new ArgumentException("Input must be enclosed in square brackets."); - } - var dict = new Dictionary(); - - if (string.IsNullOrEmpty(rawParams)) return dict; - - string[] items = rawParams.Substring(1, rawParams.Length - 2).Split(";"); - foreach (var item in items) + if (string.IsNullOrWhiteSpace(rawParams)) { - string entry = item.Trim(); - if (string.IsNullOrEmpty(entry)) continue; + return dict; + } - string[] pair = entry.Split(':' , StringSplitOptions.RemoveEmptyEntries); - if (pair.Length != 2) continue; - dict.Add(pair[0].ToLower(), pair[1]); + try + { + JObject paramObject = Utility.Json.ToObject(rawParams); + if (paramObject == null) + { + return dict; + } + + foreach (var pair in paramObject) + { + if (string.IsNullOrWhiteSpace(pair.Key) || pair.Value == null) + { + continue; + } + + dict[pair.Key.ToLower()] = pair.Value.ToString(); + } + } + catch (Exception exception) + { + Log.Warning("Failed to parse weapon params json '{0}'. Error: {1}", rawParams, exception.Message); } return dict; } } -} \ No newline at end of file +} diff --git a/Assets/GameMain/Scripts/Entity/EntityData/Weapon/WeaponData.cs b/Assets/GameMain/Scripts/Entity/EntityData/Weapon/WeaponData.cs index 2ac3551..b47958a 100644 --- a/Assets/GameMain/Scripts/Entity/EntityData/Weapon/WeaponData.cs +++ b/Assets/GameMain/Scripts/Entity/EntityData/Weapon/WeaponData.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using DataTable; using Definition.DataStruct; using Definition.Enum; +using GameFramework; namespace Entity.EntityData { @@ -39,6 +40,26 @@ namespace Entity.EntityData return Params.TryGetValue(key.ToLower(), out value); } + protected TParams ParseParams() where TParams : new() + { + if (string.IsNullOrWhiteSpace(_drWeapon.ParamsJson)) + { + return new TParams(); + } + + try + { + TParams parsed = Utility.Json.ToObject(_drWeapon.ParamsJson); + return parsed ?? new TParams(); + } + catch (Exception exception) + { + throw new Exception( + $"Failed to parse weapon params, WeaponType='{WeaponType}', Json='{_drWeapon.ParamsJson}'.", + exception); + } + } + /// /// 攻击力。 /// @@ -80,9 +101,11 @@ namespace Entity.EntityData /// public Dictionary Params => _drWeapon.Pramas; + public string ParamsJson => _drWeapon.ParamsJson; + /// /// 额外属性。 /// public StatModifier[] Modifiers => _drWeapon.Modifiers; } -} \ No newline at end of file +} diff --git a/Assets/GameMain/Scripts/Entity/EntityData/Weapon/WeaponHandgunData.cs b/Assets/GameMain/Scripts/Entity/EntityData/Weapon/WeaponHandgunData.cs index b9b1914..c47c6ee 100644 --- a/Assets/GameMain/Scripts/Entity/EntityData/Weapon/WeaponHandgunData.cs +++ b/Assets/GameMain/Scripts/Entity/EntityData/Weapon/WeaponHandgunData.cs @@ -1,12 +1,21 @@ +using System; using Definition.Enum; namespace Entity.EntityData { + [Serializable] + public sealed class WeaponHandgunParamsData + { + } + public class WeaponHandgunData : WeaponData { + public WeaponHandgunParamsData ParamsData { get; } + public WeaponHandgunData(int entityId, int ownerId, CampType ownerCamp) : base(entityId, WeaponType.WeaponHandgun, ownerId, ownerCamp) { + ParamsData = ParseParams(); } } } diff --git a/Assets/GameMain/Scripts/Entity/EntityData/Weapon/WeaponKnifeData.cs b/Assets/GameMain/Scripts/Entity/EntityData/Weapon/WeaponKnifeData.cs index 2bbe261..4fcf4bf 100644 --- a/Assets/GameMain/Scripts/Entity/EntityData/Weapon/WeaponKnifeData.cs +++ b/Assets/GameMain/Scripts/Entity/EntityData/Weapon/WeaponKnifeData.cs @@ -1,12 +1,22 @@ +using System; using Definition.Enum; namespace Entity.EntityData { + [Serializable] + public sealed class WeaponKnifeParamsData + { + public float HitRadius { get; set; } + } + public class WeaponKnifeData : WeaponData { + public WeaponKnifeParamsData ParamsData { get; } + public WeaponKnifeData(int entityId, int ownerId, CampType ownerCamp) : base(entityId, WeaponType.WeaponKnife, ownerId, ownerCamp) { + ParamsData = ParseParams(); } } -} \ No newline at end of file +} diff --git a/Assets/GameMain/Scripts/Entity/EntityData/Weapon/WeaponLightningData.cs b/Assets/GameMain/Scripts/Entity/EntityData/Weapon/WeaponLightningData.cs index 5d5ed5e..abe5a03 100644 --- a/Assets/GameMain/Scripts/Entity/EntityData/Weapon/WeaponLightningData.cs +++ b/Assets/GameMain/Scripts/Entity/EntityData/Weapon/WeaponLightningData.cs @@ -1,12 +1,23 @@ +using System; using Definition.Enum; namespace Entity.EntityData { + [Serializable] + public sealed class WeaponLightningParamsData + { + public float HitRadius { get; set; } + public float HoverHeight { get; set; } + } + public class WeaponLightningData : WeaponData { + public WeaponLightningParamsData ParamsData { get; } + public WeaponLightningData(int entityId, int ownerId, CampType ownerCamp) : base(entityId, WeaponType.WeaponLightning, ownerId, ownerCamp) { + ParamsData = ParseParams(); } } } diff --git a/Assets/GameMain/Scripts/Entity/EntityData/Weapon/WeaponSlashData.cs b/Assets/GameMain/Scripts/Entity/EntityData/Weapon/WeaponSlashData.cs index 99a7381..f500788 100644 --- a/Assets/GameMain/Scripts/Entity/EntityData/Weapon/WeaponSlashData.cs +++ b/Assets/GameMain/Scripts/Entity/EntityData/Weapon/WeaponSlashData.cs @@ -1,12 +1,22 @@ +using System; using Definition.Enum; namespace Entity.EntityData { + [Serializable] + public sealed class WeaponSlashParamsData + { + public float SectorAngle { get; set; } + } + public class WeaponSlashData : WeaponData { + public WeaponSlashParamsData ParamsData { get; } + public WeaponSlashData(int entityId, int ownerId, CampType ownerCamp) : base(entityId, WeaponType.WeaponSlash, ownerId, ownerCamp) { + ParamsData = ParseParams(); } } -} \ No newline at end of file +} diff --git a/Assets/GameMain/Scripts/Entity/EntityLogic/Weapon/WeaponKnife/WeaponKnife.cs b/Assets/GameMain/Scripts/Entity/EntityLogic/Weapon/WeaponKnife/WeaponKnife.cs index 038c5ce..6458605 100644 --- a/Assets/GameMain/Scripts/Entity/EntityLogic/Weapon/WeaponKnife/WeaponKnife.cs +++ b/Assets/GameMain/Scripts/Entity/EntityLogic/Weapon/WeaponKnife/WeaponKnife.cs @@ -11,8 +11,6 @@ namespace Entity.Weapon { public partial class WeaponKnife : WeaponBase { - private const string HitRadiusParamKey = "HitRadius"; - private WeaponKnifeData _weaponData; private Quaternion _cachedRotation; @@ -157,14 +155,8 @@ namespace Entity.Weapon _sqrRange = _weaponData.AttackRange * _weaponData.AttackRange; _cachedRotation = CachedTransform.rotation; - if (_weaponData.TryGetParam(HitRadiusParamKey, out string hitRadiusRaw)) - { - _hitRadius = Mathf.Max(0.1f, float.Parse(hitRadiusRaw)); - } - else - { - _hitRadius = _weaponData.AttackRange; - } + float configuredHitRadius = _weaponData.ParamsData != null ? _weaponData.ParamsData.HitRadius : 0f; + _hitRadius = configuredHitRadius > 0f ? Mathf.Max(0.1f, configuredHitRadius) : _weaponData.AttackRange; _hitRadiusSqr = _hitRadius * _hitRadius; _attackEffect = new KnifeRangeAttackEffect(); diff --git a/Assets/GameMain/Scripts/Entity/EntityLogic/Weapon/WeaponLightning/WeaponLightning.cs b/Assets/GameMain/Scripts/Entity/EntityLogic/Weapon/WeaponLightning/WeaponLightning.cs index 83de6b9..746a343 100644 --- a/Assets/GameMain/Scripts/Entity/EntityLogic/Weapon/WeaponLightning/WeaponLightning.cs +++ b/Assets/GameMain/Scripts/Entity/EntityLogic/Weapon/WeaponLightning/WeaponLightning.cs @@ -11,8 +11,6 @@ namespace Entity.Weapon { public partial class WeaponLightning : WeaponBase { - private const string HitRadiusParamKey = "HitRadius"; - private WeaponLightningData _weaponData; private Quaternion _cachedRotation; @@ -178,8 +176,14 @@ namespace Entity.Weapon _sqrRange = _weaponData.AttackRange * _weaponData.AttackRange; _cachedRotation = CachedTransform.rotation; - _hitRadius = ReadPositiveParam(HitRadiusParamKey, _weaponData.AttackRange); + float configuredHitRadius = _weaponData.ParamsData != null ? _weaponData.ParamsData.HitRadius : 0f; + _hitRadius = configuredHitRadius > 0f ? Mathf.Max(0.1f, configuredHitRadius) : _weaponData.AttackRange; _hitRadiusSqr = _hitRadius * _hitRadius; + float configuredHoverHeight = _weaponData.ParamsData != null ? _weaponData.ParamsData.HoverHeight : 0f; + if (configuredHoverHeight > 0f) + { + _hoverHeight = Mathf.Max(0.1f, configuredHoverHeight); + } int colliderCapacity = Mathf.Max(1, _maxHitColliders); if (_hitResults == null || _hitResults.Length != colliderCapacity) @@ -228,18 +232,6 @@ namespace Entity.Weapon } } - private float ReadPositiveParam(string paramName, float defaultValue) - { - if (_weaponData.Params != null && - _weaponData.Params.TryGetValue(paramName.ToLower(), out string rawValue) && - float.TryParse(rawValue, out float parsedValue)) - { - return Mathf.Max(0.1f, parsedValue); - } - - return Mathf.Max(0.1f, defaultValue); - } - private void StopAttackTween(bool resetTransform) { if (_attackSequence != null) diff --git a/Assets/GameMain/Scripts/Entity/EntityLogic/Weapon/WeaponSlash/WeaponSlash.cs b/Assets/GameMain/Scripts/Entity/EntityLogic/Weapon/WeaponSlash/WeaponSlash.cs index 3067dc9..53c0d35 100644 --- a/Assets/GameMain/Scripts/Entity/EntityLogic/Weapon/WeaponSlash/WeaponSlash.cs +++ b/Assets/GameMain/Scripts/Entity/EntityLogic/Weapon/WeaponSlash/WeaponSlash.cs @@ -13,8 +13,6 @@ namespace Entity.Weapon { #region Property - private const string SectorAngleParamKey = "SectorAngle"; - private WeaponSlashData _weaponData; private Quaternion _cachedRotation; @@ -184,15 +182,8 @@ namespace Entity.Weapon _attackRadius = Mathf.Max(0.1f, _weaponData.AttackRange); _attackRadiusSqr = _attackRadius * _attackRadius; - _sectorAngle = 90f; - if (_weaponData.Params != null && - _weaponData.Params.TryGetValue(SectorAngleParamKey.ToLower(), out string rawAngle)) - { - if (float.TryParse(rawAngle, out float parsedAngle)) - { - _sectorAngle = Mathf.Clamp(parsedAngle, 1f, 360f); - } - } + float configuredSectorAngle = _weaponData.ParamsData != null ? _weaponData.ParamsData.SectorAngle : 0f; + _sectorAngle = configuredSectorAngle > 0f ? Mathf.Clamp(configuredSectorAngle, 1f, 360f) : 90f; int capacity = Mathf.Max(1, _maxHitColliders); if (_hitResults == null || _hitResults.Length != capacity) diff --git a/docs/CodeX-TODO.md b/docs/CodeX-TODO.md new file mode 100644 index 0000000..4ef1548 --- /dev/null +++ b/docs/CodeX-TODO.md @@ -0,0 +1,255 @@ +# CodeX TODO + +## Weapon 现状梳理 + +### 1. 数据层结构 + +- `Assets/GameMain/DataTables/Weapon.txt` + - 武器基础数据表。 + - 当前 `Params` 列已经切换为标准 JSON 对象。 + - 约束: + - 空参数统一写 `{}` + - 不再兼容 `[]` + - key 名应与对应 `ParamsData` 属性名一致 + +- `Assets/GameMain/Scripts/DataTable/DRWeapon.cs` + - 负责解析武器表基础字段。 + - 保留两份参数视图: + - `ParamsJson` + - 原始 JSON 字符串,供 `WeaponData` 强类型反序列化使用。 + - `Pramas` + - 由 `ParamsJson` 转成的 `Dictionary`。 + - 仅用于描述/UI 等弱类型读取场景。 + +- `Assets/GameMain/Scripts/Entity/EntityData/Weapon/WeaponData.cs` + - 保留所有武器共用字段: + - `Attack` + - `Cooldown` + - `AttackRange` + - `Price` + - `Rarity` + - `Modifiers` + - `ParamsJson` + - `Params` + - 提供 `ParseParams()` 作为武器子类的强类型参数解析入口。 + +- `Assets/GameMain/Scripts/Entity/EntityData/Weapon/*` + - 每个具体武器子类持有自己的 `ParamsData`: + - `WeaponKnifeData -> WeaponKnifeParamsData` + - `WeaponHandgunData -> WeaponHandgunParamsData` + - `WeaponSlashData -> WeaponSlashParamsData` + - `WeaponLightningData -> WeaponLightningParamsData` + +### 2. 逻辑层结构 + +- `Assets/GameMain/Scripts/Entity/EntityLogic/Weapon/WeaponBase.cs` + - 武器统一基类。 + - 负责: + - 生命周期 + - 状态机切换 + - 选敌 + - Simulation area/sector query 接入 + - 绑定玩家攻击属性 + +- 当前已有四种武器实现模板: + - `WeaponKnife` + - 近身前刺 + 圆形范围命中 + - `WeaponHandgun` + - 单次 Raycast 瞬发命中 + - `WeaponSlash` + - 扇形范围命中 + - `WeaponLightning` + - 锁定目标点 + 落点范围打击 + +- 参数读取方式 + - 已改为在具体武器中直接读取对应 `ParamsData` + - 不再在武器逻辑中手动解析字符串参数 + +### 3. 当前已接通的参数 + +- `WeaponKnifeParamsData` + - `HitRadius` + +- `WeaponHandgunParamsData` + - 暂无字段,待扩展 + +- `WeaponSlashParamsData` + - `SectorAngle` + +- `WeaponLightningParamsData` + - `HitRadius` + - `HoverHeight` + +### 4. 当前结构的优点 + +- 公共字段仍集中在 `WeaponData`,不会把通用逻辑拆散 +- 武器专属参数已经强类型化,初始化更稳定 +- 数据表可读性比旧的 KV 字符串格式更高 +- 新武器扩展时,可以复用: + - 表 + - `DRWeapon` + - `WeaponData` + - `WeaponBase` + - AttackEffect + - Simulation area/sector query + +### 5. 当前结构的限制 + +- 仍然不是“纯配置驱动行为” + - 行为差异主要还在具体武器类里 +- `Handgun` 参数化程度还不够 + - 目前还不适合直接高效派生霰弹枪、狙击枪、 burst 枪 +- `Pramas` 命名拼写仍保留旧名字 + - 目前为了兼容存量调用,暂不改 + +## Weapon 扩展计划 + +### P0: 稳定当前数据链路 + +- 检查 `Weapon.txt` 中全部行: + - `Params` 必须都是 JSON 对象 + - 空参数统一为 `{}` +- 检查后续新增字段时: + - 数据表 key 与 `ParamsData` 属性名保持一致 +- 补一轮基础验证: + - 商店描述 + - 玩家初始武器生成 + - 商店购买武器生成 + +### P1: 先把 Handgun 参数化做完整 + +目标: +- 把 `WeaponHandgun` 做成远程枪械母版,而不是只有一把“手枪” + +建议新增参数: +- `PelletCount` +- `SpreadAngle` +- `PenetrationCount` +- `FireOriginOffsetX` +- `FireOriginOffsetY` +- `FireOriginOffsetZ` +- `HitMarkerSize` +- `HitMarkerYOffset` +- `HitMarkerDuration` + +落地后可直接派生: +- 手枪 +- 霰弹枪 +- 狙击枪 +- 三连发手炮 + +### P2: 优先做低成本高收益的新武器 + +优先顺序建议: + +1. 长枪 / 刺剑 +- 基于 `WeaponKnife` +- 重点调: + - 前刺距离 + - 命中半径 + - 冷却 + +2. 大剑 / 半月斩 +- 基于 `WeaponSlash` +- 重点调: + - `SectorAngle` + - 攻击范围 + - 动画时长 + +3. 战锤 / 震地锤 +- 基于 `WeaponLightning` 或 `WeaponKnife` +- 重点调: + - 落点半径 + - 前摇 + - 低频高伤 + +4. 霰弹枪 +- 基于参数化后的 `WeaponHandgun` +- 重点调: + - 散射 + - 多 pellet + - 近距离爆发 + +5. 狙击枪 +- 基于参数化后的 `WeaponHandgun` +- 重点调: + - 单发高伤 + - 超远射程 + - 慢冷却 + +6. 陨石杖 / 圣光柱 +- 基于 `WeaponLightning` +- 重点调: + - `HoverHeight` + - 爆炸半径 + - 冷却 + +### P3: 中成本扩展 + +1. 链式闪电 +- 在首目标命中后,继续寻找附近目标 +- 需要新增: + - 连锁次数 + - 连锁半径 + - 每跳衰减 + +2. 穿透弹 / 火球 +- 复用现有 projectile/simulation 基础 +- 需要明确: + - 穿透次数 + - 命中后是否爆炸 + +3. 地雷 / 陷阱 +- 本质是延时触发 area hit +- 需要新增: + - 布置后触发时机 + - 持续时间 + - 触发半径 + +4. 回旋镖 +- 需要双阶段投射物状态 +- 成本高于普通枪械/范围武器 + +### P4: 暂缓项 + +以下方向暂不建议优先投入: + +- 持续激光 +- 喷火器 +- 环绕飞剑 +- 常驻法球 +- 冰冻/中毒/击退等状态驱动武器流派 + +原因: +- 当前武器框架核心仍是“单次攻击结算” +- 持续伤害与异常状态还没有形成统一挂点 + +## 新武器接入步骤模板 + +1. 在 `Weapon.txt` 新增一行 +- 配好基础字段 +- `Params` 写 JSON 对象 + +2. 新增 `WeaponType` +- 在 `Assets/GameMain/Scripts/Definition/Enum/WeaponType.cs` + +3. 新增武器数据子类 +- 新建 `WeaponXXXData` +- 新建 `WeaponXXXParamsData` +- 在构造里调用 `ParseParams()` + +4. 新增武器逻辑类 +- 继承 `WeaponBase` +- 接入状态机 +- 读取 `ParamsData` + +5. 接入生成入口 +- 玩家初始武器 +- 商店购买武器 +- 其他掉落/奖励入口 + +6. 验证点 +- 武器生成正确 +- 参数生效正确 +- 描述文本正确 +- Simulation 模式和非 Simulation 模式都能命中 diff --git a/skills/weapon-development/SKILL.md b/skills/weapon-development/SKILL.md index 4ec3047..d0bdc7b 100644 --- a/skills/weapon-development/SKILL.md +++ b/skills/weapon-development/SKILL.md @@ -13,20 +13,30 @@ description: Develop and extend the VampireLike weapon system. Use when creating - state flow (`Idle`, `Check_OutRange`, `Check_InRange`, `Attack`) - target selector (`ITargetSelector`, `TargetSelectorType`) - effect layer (`IWeaponAttackEffect`) - - data contract (`DRWeapon`, `WeaponData`) -3. Keep behavior compatibility with current gameplay loop and UI/event chain. + - data contract (`DRWeapon`, `WeaponData`, `ParamsData`) +3. Keep behavior compatibility with current gameplay loop, shop flow, inventory flow, and UI/event chain. ## Source Map -- Weapon base: `../../Assets/GameMain/Scripts/Entity/EntityLogic/Weapon/WeaponBase.cs` +- Weapon base: + - `../../Assets/GameMain/Scripts/Entity/EntityLogic/Weapon/WeaponBase.cs` - Existing weapons: - `../../Assets/GameMain/Scripts/Entity/EntityLogic/Weapon/WeaponKnife/` - `../../Assets/GameMain/Scripts/Entity/EntityLogic/Weapon/WeaponHandgun/` - - `../../Assets/GameMain/Scripts/Entity/EntityLogic/Weapon/WeaponSlash.cs` + - `../../Assets/GameMain/Scripts/Entity/EntityLogic/Weapon/WeaponSlash/` + - `../../Assets/GameMain/Scripts/Entity/EntityLogic/Weapon/WeaponLightning/` - Selectors: - `../../Assets/GameMain/Scripts/Entity/EntityLogic/Weapon/TargetSelector/` +- Attack effects: + - `../../Assets/GameMain/Scripts/Entity/EntityLogic/Weapon/AttackEffects/` - Weapon data: - `../../Assets/GameMain/Scripts/Entity/EntityData/Weapon/` +- Weapon table: + - `../../Assets/GameMain/DataTables/Weapon.txt` +- Data row: + - `../../Assets/GameMain/Scripts/DataTable/DRWeapon.cs` +- Shop integration: + - `../../Assets/GameMain/Scripts/UI/GameScene/UseCase/ShopFormUseCase.cs` - Entity show flow: - `../../Assets/GameMain/Scripts/Entity/EntityExtension.cs` @@ -36,7 +46,8 @@ description: Develop and extend the VampireLike weapon system. Use when creating - Keep state transitions explicit and non-blocking. - Keep cooldown accumulation valid even when no target is found. - Keep attack logic and visual effect logic decoupled. -- Use safe parsing (`TryParse` + defaults) for runtime weapon parameters. +- Parse weapon-specific parameters into strong-typed `ParamsData` at data initialization time. +- Treat `Weapon.txt` `Params` as a JSON object column; empty params must use `{}`. - Preserve compatibility with shop/inventory/UI refresh flow. ## Change Recipes @@ -44,17 +55,18 @@ description: Develop and extend the VampireLike weapon system. Use when creating ### Add a New Weapon 1. Extend `WeaponType` without reordering existing enum values. -2. Add `WeaponXxxData : WeaponData` for strong-typed fields. -3. Add `WeaponXxx : WeaponBase` and implement only weapon-specific behavior. -4. Build state files under `Weapon/WeaponXxx/` with partial class layout. -5. Register display/data mapping path so `ShowWeapon` can instantiate correctly. +2. Add `WeaponXxxData : WeaponData` and `WeaponXxxParamsData` for weapon-specific parameters. +3. Parse `ParamsJson` through `ParseParams()` inside the weapon data constructor. +4. Add `WeaponXxx : WeaponBase` and implement only weapon-specific behavior. +5. Build state files under `Weapon/WeaponXxx/` with partial class layout. +6. Register display/data mapping path so `ShowWeapon` and shop purchase can instantiate correctly. ### Add or Update a Target Selector 1. Implement `ITargetSelector`. 2. Update `TargetSelectorType`. 3. Register creation in `WeaponBase.CreateSelector`. -4. Validate target semantics with current-health rules where applicable. +4. Validate target semantics with current-health/runtime-health rules where applicable. ### Add or Update Attack Effect @@ -62,11 +74,19 @@ description: Develop and extend the VampireLike weapon system. Use when creating 2. Trigger effect from weapon attack state only. 3. Keep damage resolution outside effect code. +### Add or Update Weapon Parameters + +1. Update `Weapon.txt` `Params` JSON object. +2. Add or update the corresponding `WeaponXxxParamsData` fields. +3. Keep key names aligned with `ParamsData` property names. +4. Read values from `ParamsData` in weapon initialization, not by manual string parsing in runtime logic. + ## Validation Checklist - Weapon can be shown, attached, and updated without exceptions. - State machine does not stall across target loss/reacquire. - Cooldown and range checks match design expectation. - Damage path and effect path remain decoupled. +- `ParamsData` matches table content and default fallback behavior. - UI/shop/inventory interactions stay stable after the change. -- Update `./references/WeaponDevelopmentSkill.md` if contracts changed. +- Update `./references/WeaponDevelopmentSkill.md` if contracts or recommended patterns changed. diff --git a/skills/weapon-development/references/WeaponDevelopmentSkill.md b/skills/weapon-development/references/WeaponDevelopmentSkill.md index 875a989..b8f75c1 100644 --- a/skills/weapon-development/references/WeaponDevelopmentSkill.md +++ b/skills/weapon-development/references/WeaponDevelopmentSkill.md @@ -1,16 +1,17 @@ -# Weapon Development Skill(VampireLike) +# Weapon Development Skill(VampireLike) ## 目标 本文件是 `Entity.Weapon` 体系的开发规范与速查手册。 -后续新增武器或扩展机制时,优先按本文档执行,避免重复通读历史上下文。 +后续新增武器、扩展参数、调整状态机或联动商店/背包时,优先按本文档执行,避免重复通读历史上下文。 ## 当前架构总览 - 武器运行时入口:`WeaponBase`(`Assets/GameMain/Scripts/Entity/EntityLogic/Weapon/WeaponBase.cs`) -- 武器具体实现:`WeaponKnife`、`WeaponHandgun`、`WeaponSlash` +- 武器具体实现:`WeaponKnife`、`WeaponHandgun`、`WeaponSlash`、`WeaponLightning` - 目标选择策略:`ITargetSelector` + `TargetSelectorType` - 攻击可视化:`IWeaponAttackEffect` - 数据入口:`DRWeapon` -> `WeaponData`(及其子类) - 实体生成:`EntityExtension.ShowWeapon` +- 商店接入:`ShopFormUseCase.CreateWeaponData` ## WeaponBase 统一职责(已上收) `WeaponBase` 负责以下通用逻辑,子类不要重复实现: @@ -20,8 +21,34 @@ - 启用门控:`OnUpdate` 中统一 `if (!_isEnabled) return` - 目标选择入口:`SelectTarget`、`SetTargetSelector`、`CreateSelector` - 距离判定:`IsInRange`(基于 XZ 平面距离) +- Simulation 命中请求:`TryQueueAreaCollisionQuery`、`TryQueueSectorCollisionQuery` - 玩家攻击属性订阅:`BindAttackStatFromOwner` / `ReleaseAttackStatSubscription` +约束: +- 不要在子类里重复实现 `WeaponBase` 已有能力。 +- 行为差异优先收敛在:`BuildStates`、`Check`、`Attack`、少量专属辅助方法。 + +## 当前武器模板分类 +### 1. `WeaponKnife` +- 模式:近身前刺 + 圆形范围命中 +- 典型参数:`HitRadius` +- 适合派生:长枪、刺剑、短矛、震地锤近身版 + +### 2. `WeaponHandgun` +- 模式:单次 `Raycast` 瞬发命中 +- 当前参数化程度较低,仍适合作为远程枪械母版继续扩展 +- 适合派生:手枪、霰弹枪、狙击枪、三连发枪 + +### 3. `WeaponSlash` +- 模式:扇形范围命中 +- 典型参数:`SectorAngle` +- 适合派生:大剑、斧头、半月斩、横扫类武器 + +### 4. `WeaponLightning` +- 模式:锁定目标点 + 落点范围打击 +- 典型参数:`HitRadius`、`HoverHeight` +- 适合派生:闪电、陨石杖、圣光柱、空袭类武器 + ## 状态机约定 统一状态枚举: - `Idle` @@ -39,12 +66,14 @@ 关键规则: - 即使没有目标,也允许蓄力(计时器持续走)。 - 一旦进入 `Check_InRange` 且冷却已满,应立即触发攻击。 +- 攻击过程中若需要多帧动画,使用 `_isAttacking` 控制状态退出时机。 ## 3D 场景下的距离/朝向原则 - 射程与目标筛选:优先使用 XZ 平面距离(忽略 Y),调用 `AIUtility.GetSqrMagnitudeXZ`。 - 视觉朝向与弹道:可按武器设计决定是否使用完整 3D 向量。 - `WeaponHandgun`:允许俯仰瞄准,逻辑上用射线命中对象判定伤害。 - 近战地面范围类(Knife/Slash):伤害检测建议投影到地面(XZ)再判定。 + - 落点类(Lightning):锁点可取目标当前位置,但范围判定仍建议按地面距离收口。 ## 目标选择策略规范 接口:`ITargetSelector.SelectTarget(WeaponBase weapon, IEnumerable candidates, float maxSqrRange)` @@ -55,14 +84,15 @@ - `LowestHealthTargetSelector` 语义约定: -- `HighestHealth` / `LowestHealth` 必须按“当前血量”筛选。 -- 当前实现读取 `HealthComponent.CurrentHealth`。 +- `NearestTargetSelector` 在 Simulation 启用时优先走空间索引。 +- `HighestHealth` / `LowestHealth` 当前基于运行时生命值选择目标。 +- 若新增新策略,必须明确“按当前值”还是“按最大值”筛选,避免语义漂移。 扩展策略步骤: 1. 新建 selector 类并实现 `ITargetSelector`。 2. 更新 `TargetSelectorType` 枚举。 3. 在 `WeaponBase.CreateSelector` 中注册。 -4. 武器在 `OnWeaponShow` 或构造阶段选择策略。 +4. 武器在 `OnWeaponShow` 或初始化阶段选择策略。 ## 攻击可视化效果规范 接口:`IWeaponAttackEffect.Play(WeaponBase weapon, Vector3 position, EntityBase target, float radius)` @@ -71,25 +101,68 @@ - 可视化逻辑与伤害逻辑解耦。 - 武器类只负责触发 `Play`,不把可视化细节塞回武器核心逻辑。 - 当前阶段允许临时对象创建;后续若有性能压力再统一对象池化。 +- 命中特效、范围预警、扇形描边都属于 effect 层,不属于伤害层。 ## 数据层规范(DRWeapon / WeaponData) -`DRWeapon` 提供通用字段: -- `Attack`、`Cooldown`、`AttackRange`、`AttackSoundId` -- `Pramas`(字典,Key 建议统一转小写) +### `DRWeapon` +提供通用字段: +- `Attack` +- `Cooldown` +- `AttackRange` +- `AttackSoundId` +- `ParamsJson` +- `Pramas` - `Modifiers` 约定: -- 参数解析尽量在数据层/初始化阶段完成。 -- 武器逻辑层读取 `WeaponData` 的强类型结果,不要散落 `Parse`。 -- 解析时必须容错:优先 `TryParse` + 默认值,避免运行时异常。 +- `Params` 列现在使用标准 JSON 对象。 +- 空参数统一写 `{}`。 +- 不再兼容 `[]`。 +- `Pramas` 保留为描述/UI 的兼容字典视图,不再作为武器逻辑主读取入口。 + +### `WeaponData` +提供公共武器字段与通用解析入口: +- 公共战斗字段:`Attack`、`Cooldown`、`AttackRange` +- 公共展示字段:`Title`、`IconAssetName`、`Rarity`、`Price` +- 参数入口:`ParamsJson`、`Params` +- 通用强类型解析:`ParseParams()` + +约定: +- 参数解析优先在数据层完成。 +- 武器逻辑层读取强类型 `ParamsData`,不要散落字符串 `Parse`。 +- 只有描述/UI 等弱类型场景才继续读取 `Params` 字典。 + +### 具体武器数据子类 +每种武器数据子类都应持有自己的 `ParamsData`: +- `WeaponKnifeData -> WeaponKnifeParamsData` +- `WeaponHandgunData -> WeaponHandgunParamsData` +- `WeaponSlashData -> WeaponSlashParamsData` +- `WeaponLightningData -> WeaponLightningParamsData` + +当前已接通字段: +- `WeaponKnifeParamsData` + - `HitRadius` +- `WeaponHandgunParamsData` + - 暂无字段 +- `WeaponSlashParamsData` + - `SectorAngle` +- `WeaponLightningParamsData` + - `HitRadius` + - `HoverHeight` + +JSON 约束: +- key 名应与 `ParamsData` 属性名一致。 +- 统一使用 JSON 对象,不要再使用自定义 KV 串。 +- 不建议在 `ParamsJson` 中使用制表符和跨行内容,避免 txt 表分列出错。 ## 新增武器标准流程 1. 定义枚举 - - 更新 `WeaponType`(保持递增值,避免重排已有值)。 + - 更新 `WeaponType`,保持递增值,避免重排已有值。 2. 建立数据类 - 新建 `WeaponXxxData : WeaponData`。 - - 如果有独有参数,提供强类型字段或统一初始化逻辑。 + - 新建 `WeaponXxxParamsData`。 + - 在构造阶段调用 `ParseParams()`,把参数初始化为强类型字段。 3. 建立行为类 - 新建 `WeaponXxx : WeaponBase`。 @@ -106,29 +179,84 @@ 5. 建立可视化(可选) - 新建 `WeaponXxxAttackEffect : IWeaponAttackEffect`,在武器中组合调用。 -6. 若为远程武器,建立子弹实体 - - `BulletXxx : Bullet` - - `BulletXxxData : BulletData` - - 通过武器赋予伤害/阵营参数。 - - 自动销毁应走对象池回收。 - -7. 接入实体展示与数据表 +6. 接入实体展示与数据表 - 确保 `DRWeapon` / `DREntity` 配置齐全。 - `EntityExtension.ShowWeapon` 可正确映射到 `Entity.Weapon.WeaponXxx`。 + - 商店/背包创建入口能正确构造 `WeaponXxxData`。 -8. 联动系统 +7. 联动系统 - 背包、商店购买/出售、UI 展示、事件流刷新。 +8. 验证点 + - 武器能正确生成、附着、更新。 + - `ParamsData` 与数据表一致。 + - 描述文本仍正确展示。 + - Simulation 模式和非 Simulation 模式都能命中。 + +## 扩展优先级建议 +### 第一批:低成本高收益 +- 长枪 / 刺剑 + - 基于 `WeaponKnife` +- 大剑 / 半月斩 + - 基于 `WeaponSlash` +- 战锤 / 震地锤 + - 基于 `WeaponLightning` 或 `WeaponKnife` +- 霰弹枪 + - 基于参数化后的 `WeaponHandgun` +- 狙击枪 + - 基于参数化后的 `WeaponHandgun` +- 陨石杖 / 圣光柱 + - 基于 `WeaponLightning` + +### 第二批:中成本扩展 +- 链式闪电 +- 穿透弹 / 火球 +- 地雷 / 陷阱 +- 回旋镖 + +### 暂缓项 +- 持续激光 +- 喷火器 +- 环绕飞剑 +- 常驻法球 +- 状态异常驱动的复杂武器流派 + +原因: +- 当前武器主流程仍是单次攻击结算。 +- 持续伤害、异常状态和常驻 orbit 行为尚未形成统一挂点。 + +## `WeaponHandgun` 后续建议 +当前 `WeaponHandgun` 参数化仍偏弱,建议作为下一阶段母版优先扩展。 + +建议新增字段: +- `PelletCount` +- `SpreadAngle` +- `PenetrationCount` +- `FireOriginOffsetX` +- `FireOriginOffsetY` +- `FireOriginOffsetZ` +- `HitMarkerSize` +- `HitMarkerYOffset` +- `HitMarkerDuration` + +完成后可快速派生: +- 手枪 +- 霰弹枪 +- 狙击枪 +- 三连发枪 + ## 代码检查清单(提交前) - - 是否重复实现了 `WeaponBase` 已有通用逻辑。 - - 状态流转是否会卡死或漏转场。 - - 冷却计时是否在无目标时仍正常累积。 - - 目标失效(null/Unavailable/死亡)是否安全处理。 - - 命中检测是否符合武器设计(地面范围/射线/扇形)。 - - 数据参数是否全部容错解析。 - - 可视化是否与伤害逻辑解耦。 - - 是否正确订阅/解绑攻击属性(或复用基类方法)。 +- 是否重复实现了 `WeaponBase` 已有通用逻辑。 +- 状态流转是否会卡死或漏转场。 +- 冷却计时是否在无目标时仍正常累积。 +- 目标失效(null/Unavailable/死亡)是否安全处理。 +- 命中检测是否符合武器设计(地面范围/射线/扇形/落点)。 +- 数据参数是否全部收敛到 `ParamsData`。 +- 可视化是否与伤害逻辑解耦。 +- 是否正确订阅/解绑攻击属性(或复用基类方法)。 +- 商店、背包、武器描述是否仍保持兼容。 ## 已知注意点 - - 目录中存在 `Pramas` 命名拼写历史包袱,保持兼容即可。 - - 文档中的“规范”优先级高于历史实现;历史实现若偏离,按本规范逐步收敛。 +- 目录中存在 `Pramas` 命名拼写历史包袱,当前保持兼容即可。 +- 文档中的“规范”优先级高于历史实现;历史实现若偏离,按本规范逐步收敛。 +- 当前更推荐“公共 `WeaponData` + 专属 `ParamsData`”结构,不建议把每种武器做成一套完全独立的数据总线。 diff --git a/数据表/Entity/Weapon.xlsx b/数据表/Entity/Weapon.xlsx index ed04d94..15e9b26 100644 Binary files a/数据表/Entity/Weapon.xlsx and b/数据表/Entity/Weapon.xlsx differ diff --git a/数据表/convert.py b/数据表/convert.py index 99ce634..d606043 100644 --- a/数据表/convert.py +++ b/数据表/convert.py @@ -1,6 +1,9 @@ import pandas as pd import os -import csv +def format_cell(value): + if value is None: + return '' + return str(value) def convert_excel_to_txt(folder_path='.'): # 计数器,用于最后汇总 @@ -28,24 +31,15 @@ def convert_excel_to_txt(folder_path='.'): try: # 读取 Excel - df = pd.read_excel(file_path, header=None) - - # 预处理:将 NaN 替换为空字符串,否则导出会变成 "nan" - df = df.fillna('') - - # 导出设置: - # 1. sep='\t' : 使用制表符分隔 - # 2. quoting=csv.QUOTE_NONE : 不使用引号包裹字段,也不会把 " 变成 "" - # 3. escapechar='\\' : 如果单元格内恰好有 Tab 键,会用反斜杠转义,防止数据列错位 - df.to_csv( - output_file, - sep='\t', - index=False, - header=False, - encoding='utf-8', - quoting=csv.QUOTE_NONE, - escapechar='\\' + df = pd.read_excel( + file_path, + header=None, + keep_default_na=False, ) + + with open(output_file, 'w', encoding='utf-8', newline='') as f: + for row in df.itertuples(index=False, name=None): + f.write('\t'.join(format_cell(cell) for cell in row) + '\n') print(f"成功转换 -> {output_file}") count += 1