调整 convert.py 转换逻辑 + 规划后续开发
This commit is contained in:
parent
59f1d02a18
commit
c5d113b35f
|
|
@ -2,7 +2,7 @@
|
||||||
# Id EntityTypeId Title IconAssetName Rarity Price PriceRandomPercent Attack Cooldown AttackRange AttackSoundId Pramas Modifiers
|
# 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[]
|
# 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] []
|
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 [] []
|
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] []
|
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] []
|
4 204 闪电 Almighty_Icon White 150 0.08 80 3 12 10000 {"hitRadius":3} []
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
|
||||||
using Definition.DataStruct;
|
using Definition.DataStruct;
|
||||||
using Definition.Enum;
|
using Definition.Enum;
|
||||||
using GameFramework;
|
using GameFramework;
|
||||||
using CustomUtility;
|
using CustomUtility;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityGameFramework.Runtime;
|
using UnityGameFramework.Runtime;
|
||||||
|
|
||||||
|
|
@ -74,6 +74,11 @@ namespace DataTable
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Dictionary<string, string> Pramas { get; private set; }
|
public Dictionary<string, string> Pramas { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取武器额外参数 Json。
|
||||||
|
/// </summary>
|
||||||
|
public string ParamsJson { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 获取武器额外属性。
|
/// 获取武器额外属性。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -97,7 +102,8 @@ namespace DataTable
|
||||||
Cooldown = float.Parse(columnStrings[index++]);
|
Cooldown = float.Parse(columnStrings[index++]);
|
||||||
AttackRange = float.Parse(columnStrings[index++]);
|
AttackRange = float.Parse(columnStrings[index++]);
|
||||||
AttackSoundId = int.Parse(columnStrings[index++]);
|
AttackSoundId = int.Parse(columnStrings[index++]);
|
||||||
Pramas = DeserializeParams(columnStrings[index++]);
|
ParamsJson = columnStrings[index++];
|
||||||
|
Pramas = DeserializeParams(ParamsJson);
|
||||||
Modifiers = Utility.Json.ToObject<StatModifier[]>(columnStrings[index++]);
|
Modifiers = Utility.Json.ToObject<StatModifier[]>(columnStrings[index++]);
|
||||||
|
|
||||||
GeneratePropertyArray();
|
GeneratePropertyArray();
|
||||||
|
|
@ -114,30 +120,38 @@ namespace DataTable
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="rawParams"></param>
|
/// <param name="rawParams"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
/// <exception cref="ArgumentException"></exception>
|
|
||||||
private Dictionary<string, string> DeserializeParams(string rawParams)
|
private Dictionary<string, string> DeserializeParams(string rawParams)
|
||||||
{
|
{
|
||||||
if (!rawParams.StartsWith('[') || !rawParams.EndsWith(']'))
|
|
||||||
{
|
|
||||||
throw new ArgumentException("Input must be enclosed in square brackets.");
|
|
||||||
}
|
|
||||||
|
|
||||||
var dict = new Dictionary<string, string>();
|
var dict = new Dictionary<string, string>();
|
||||||
|
if (string.IsNullOrWhiteSpace(rawParams))
|
||||||
if (string.IsNullOrEmpty(rawParams)) return dict;
|
|
||||||
|
|
||||||
string[] items = rawParams.Substring(1, rawParams.Length - 2).Split(";");
|
|
||||||
foreach (var item in items)
|
|
||||||
{
|
{
|
||||||
string entry = item.Trim();
|
return dict;
|
||||||
if (string.IsNullOrEmpty(entry)) continue;
|
}
|
||||||
|
|
||||||
string[] pair = entry.Split(':' , StringSplitOptions.RemoveEmptyEntries);
|
try
|
||||||
if (pair.Length != 2) continue;
|
{
|
||||||
dict.Add(pair[0].ToLower(), pair[1]);
|
JObject paramObject = Utility.Json.ToObject<JObject>(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;
|
return dict;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||||
using DataTable;
|
using DataTable;
|
||||||
using Definition.DataStruct;
|
using Definition.DataStruct;
|
||||||
using Definition.Enum;
|
using Definition.Enum;
|
||||||
|
using GameFramework;
|
||||||
|
|
||||||
namespace Entity.EntityData
|
namespace Entity.EntityData
|
||||||
{
|
{
|
||||||
|
|
@ -39,6 +40,26 @@ namespace Entity.EntityData
|
||||||
return Params.TryGetValue(key.ToLower(), out value);
|
return Params.TryGetValue(key.ToLower(), out value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected TParams ParseParams<TParams>() where TParams : new()
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(_drWeapon.ParamsJson))
|
||||||
|
{
|
||||||
|
return new TParams();
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
TParams parsed = Utility.Json.ToObject<TParams>(_drWeapon.ParamsJson);
|
||||||
|
return parsed ?? new TParams();
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
throw new Exception(
|
||||||
|
$"Failed to parse weapon params, WeaponType='{WeaponType}', Json='{_drWeapon.ParamsJson}'.",
|
||||||
|
exception);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 攻击力。
|
/// 攻击力。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -80,9 +101,11 @@ namespace Entity.EntityData
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Dictionary<string, string> Params => _drWeapon.Pramas;
|
public Dictionary<string, string> Params => _drWeapon.Pramas;
|
||||||
|
|
||||||
|
public string ParamsJson => _drWeapon.ParamsJson;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 额外属性。
|
/// 额外属性。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public StatModifier[] Modifiers => _drWeapon.Modifiers;
|
public StatModifier[] Modifiers => _drWeapon.Modifiers;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,21 @@
|
||||||
|
using System;
|
||||||
using Definition.Enum;
|
using Definition.Enum;
|
||||||
|
|
||||||
namespace Entity.EntityData
|
namespace Entity.EntityData
|
||||||
{
|
{
|
||||||
|
[Serializable]
|
||||||
|
public sealed class WeaponHandgunParamsData
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
public class WeaponHandgunData : WeaponData
|
public class WeaponHandgunData : WeaponData
|
||||||
{
|
{
|
||||||
|
public WeaponHandgunParamsData ParamsData { get; }
|
||||||
|
|
||||||
public WeaponHandgunData(int entityId, int ownerId, CampType ownerCamp)
|
public WeaponHandgunData(int entityId, int ownerId, CampType ownerCamp)
|
||||||
: base(entityId, WeaponType.WeaponHandgun, ownerId, ownerCamp)
|
: base(entityId, WeaponType.WeaponHandgun, ownerId, ownerCamp)
|
||||||
{
|
{
|
||||||
|
ParamsData = ParseParams<WeaponHandgunParamsData>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,22 @@
|
||||||
|
using System;
|
||||||
using Definition.Enum;
|
using Definition.Enum;
|
||||||
|
|
||||||
namespace Entity.EntityData
|
namespace Entity.EntityData
|
||||||
{
|
{
|
||||||
|
[Serializable]
|
||||||
|
public sealed class WeaponKnifeParamsData
|
||||||
|
{
|
||||||
|
public float HitRadius { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
public class WeaponKnifeData : WeaponData
|
public class WeaponKnifeData : WeaponData
|
||||||
{
|
{
|
||||||
|
public WeaponKnifeParamsData ParamsData { get; }
|
||||||
|
|
||||||
public WeaponKnifeData(int entityId, int ownerId, CampType ownerCamp) : base(entityId, WeaponType.WeaponKnife,
|
public WeaponKnifeData(int entityId, int ownerId, CampType ownerCamp) : base(entityId, WeaponType.WeaponKnife,
|
||||||
ownerId, ownerCamp)
|
ownerId, ownerCamp)
|
||||||
{
|
{
|
||||||
|
ParamsData = ParseParams<WeaponKnifeParamsData>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,23 @@
|
||||||
|
using System;
|
||||||
using Definition.Enum;
|
using Definition.Enum;
|
||||||
|
|
||||||
namespace Entity.EntityData
|
namespace Entity.EntityData
|
||||||
{
|
{
|
||||||
|
[Serializable]
|
||||||
|
public sealed class WeaponLightningParamsData
|
||||||
|
{
|
||||||
|
public float HitRadius { get; set; }
|
||||||
|
public float HoverHeight { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
public class WeaponLightningData : WeaponData
|
public class WeaponLightningData : WeaponData
|
||||||
{
|
{
|
||||||
|
public WeaponLightningParamsData ParamsData { get; }
|
||||||
|
|
||||||
public WeaponLightningData(int entityId, int ownerId, CampType ownerCamp)
|
public WeaponLightningData(int entityId, int ownerId, CampType ownerCamp)
|
||||||
: base(entityId, WeaponType.WeaponLightning, ownerId, ownerCamp)
|
: base(entityId, WeaponType.WeaponLightning, ownerId, ownerCamp)
|
||||||
{
|
{
|
||||||
|
ParamsData = ParseParams<WeaponLightningParamsData>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,22 @@
|
||||||
|
using System;
|
||||||
using Definition.Enum;
|
using Definition.Enum;
|
||||||
|
|
||||||
namespace Entity.EntityData
|
namespace Entity.EntityData
|
||||||
{
|
{
|
||||||
|
[Serializable]
|
||||||
|
public sealed class WeaponSlashParamsData
|
||||||
|
{
|
||||||
|
public float SectorAngle { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
public class WeaponSlashData : WeaponData
|
public class WeaponSlashData : WeaponData
|
||||||
{
|
{
|
||||||
|
public WeaponSlashParamsData ParamsData { get; }
|
||||||
|
|
||||||
public WeaponSlashData(int entityId, int ownerId, CampType ownerCamp)
|
public WeaponSlashData(int entityId, int ownerId, CampType ownerCamp)
|
||||||
: base(entityId, WeaponType.WeaponSlash, ownerId, ownerCamp)
|
: base(entityId, WeaponType.WeaponSlash, ownerId, ownerCamp)
|
||||||
{
|
{
|
||||||
|
ParamsData = ParseParams<WeaponSlashParamsData>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,8 +11,6 @@ namespace Entity.Weapon
|
||||||
{
|
{
|
||||||
public partial class WeaponKnife : WeaponBase
|
public partial class WeaponKnife : WeaponBase
|
||||||
{
|
{
|
||||||
private const string HitRadiusParamKey = "HitRadius";
|
|
||||||
|
|
||||||
private WeaponKnifeData _weaponData;
|
private WeaponKnifeData _weaponData;
|
||||||
|
|
||||||
private Quaternion _cachedRotation;
|
private Quaternion _cachedRotation;
|
||||||
|
|
@ -157,14 +155,8 @@ namespace Entity.Weapon
|
||||||
_sqrRange = _weaponData.AttackRange * _weaponData.AttackRange;
|
_sqrRange = _weaponData.AttackRange * _weaponData.AttackRange;
|
||||||
_cachedRotation = CachedTransform.rotation;
|
_cachedRotation = CachedTransform.rotation;
|
||||||
|
|
||||||
if (_weaponData.TryGetParam(HitRadiusParamKey, out string hitRadiusRaw))
|
float configuredHitRadius = _weaponData.ParamsData != null ? _weaponData.ParamsData.HitRadius : 0f;
|
||||||
{
|
_hitRadius = configuredHitRadius > 0f ? Mathf.Max(0.1f, configuredHitRadius) : _weaponData.AttackRange;
|
||||||
_hitRadius = Mathf.Max(0.1f, float.Parse(hitRadiusRaw));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_hitRadius = _weaponData.AttackRange;
|
|
||||||
}
|
|
||||||
|
|
||||||
_hitRadiusSqr = _hitRadius * _hitRadius;
|
_hitRadiusSqr = _hitRadius * _hitRadius;
|
||||||
_attackEffect = new KnifeRangeAttackEffect();
|
_attackEffect = new KnifeRangeAttackEffect();
|
||||||
|
|
|
||||||
|
|
@ -11,8 +11,6 @@ namespace Entity.Weapon
|
||||||
{
|
{
|
||||||
public partial class WeaponLightning : WeaponBase
|
public partial class WeaponLightning : WeaponBase
|
||||||
{
|
{
|
||||||
private const string HitRadiusParamKey = "HitRadius";
|
|
||||||
|
|
||||||
private WeaponLightningData _weaponData;
|
private WeaponLightningData _weaponData;
|
||||||
|
|
||||||
private Quaternion _cachedRotation;
|
private Quaternion _cachedRotation;
|
||||||
|
|
@ -178,8 +176,14 @@ namespace Entity.Weapon
|
||||||
_sqrRange = _weaponData.AttackRange * _weaponData.AttackRange;
|
_sqrRange = _weaponData.AttackRange * _weaponData.AttackRange;
|
||||||
_cachedRotation = CachedTransform.rotation;
|
_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;
|
_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);
|
int colliderCapacity = Mathf.Max(1, _maxHitColliders);
|
||||||
if (_hitResults == null || _hitResults.Length != colliderCapacity)
|
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)
|
private void StopAttackTween(bool resetTransform)
|
||||||
{
|
{
|
||||||
if (_attackSequence != null)
|
if (_attackSequence != null)
|
||||||
|
|
|
||||||
|
|
@ -13,8 +13,6 @@ namespace Entity.Weapon
|
||||||
{
|
{
|
||||||
#region Property
|
#region Property
|
||||||
|
|
||||||
private const string SectorAngleParamKey = "SectorAngle";
|
|
||||||
|
|
||||||
private WeaponSlashData _weaponData;
|
private WeaponSlashData _weaponData;
|
||||||
|
|
||||||
private Quaternion _cachedRotation;
|
private Quaternion _cachedRotation;
|
||||||
|
|
@ -184,15 +182,8 @@ namespace Entity.Weapon
|
||||||
_attackRadius = Mathf.Max(0.1f, _weaponData.AttackRange);
|
_attackRadius = Mathf.Max(0.1f, _weaponData.AttackRange);
|
||||||
_attackRadiusSqr = _attackRadius * _attackRadius;
|
_attackRadiusSqr = _attackRadius * _attackRadius;
|
||||||
|
|
||||||
_sectorAngle = 90f;
|
float configuredSectorAngle = _weaponData.ParamsData != null ? _weaponData.ParamsData.SectorAngle : 0f;
|
||||||
if (_weaponData.Params != null &&
|
_sectorAngle = configuredSectorAngle > 0f ? Mathf.Clamp(configuredSectorAngle, 1f, 360f) : 90f;
|
||||||
_weaponData.Params.TryGetValue(SectorAngleParamKey.ToLower(), out string rawAngle))
|
|
||||||
{
|
|
||||||
if (float.TryParse(rawAngle, out float parsedAngle))
|
|
||||||
{
|
|
||||||
_sectorAngle = Mathf.Clamp(parsedAngle, 1f, 360f);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int capacity = Mathf.Max(1, _maxHitColliders);
|
int capacity = Mathf.Max(1, _maxHitColliders);
|
||||||
if (_hitResults == null || _hitResults.Length != capacity)
|
if (_hitResults == null || _hitResults.Length != capacity)
|
||||||
|
|
|
||||||
|
|
@ -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<string, string>`。
|
||||||
|
- 仅用于描述/UI 等弱类型读取场景。
|
||||||
|
|
||||||
|
- `Assets/GameMain/Scripts/Entity/EntityData/Weapon/WeaponData.cs`
|
||||||
|
- 保留所有武器共用字段:
|
||||||
|
- `Attack`
|
||||||
|
- `Cooldown`
|
||||||
|
- `AttackRange`
|
||||||
|
- `Price`
|
||||||
|
- `Rarity`
|
||||||
|
- `Modifiers`
|
||||||
|
- `ParamsJson`
|
||||||
|
- `Params`
|
||||||
|
- 提供 `ParseParams<TParams>()` 作为武器子类的强类型参数解析入口。
|
||||||
|
|
||||||
|
- `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<TParams>()`
|
||||||
|
|
||||||
|
4. 新增武器逻辑类
|
||||||
|
- 继承 `WeaponBase`
|
||||||
|
- 接入状态机
|
||||||
|
- 读取 `ParamsData`
|
||||||
|
|
||||||
|
5. 接入生成入口
|
||||||
|
- 玩家初始武器
|
||||||
|
- 商店购买武器
|
||||||
|
- 其他掉落/奖励入口
|
||||||
|
|
||||||
|
6. 验证点
|
||||||
|
- 武器生成正确
|
||||||
|
- 参数生效正确
|
||||||
|
- 描述文本正确
|
||||||
|
- Simulation 模式和非 Simulation 模式都能命中
|
||||||
|
|
@ -13,20 +13,30 @@ description: Develop and extend the VampireLike weapon system. Use when creating
|
||||||
- state flow (`Idle`, `Check_OutRange`, `Check_InRange`, `Attack`)
|
- state flow (`Idle`, `Check_OutRange`, `Check_InRange`, `Attack`)
|
||||||
- target selector (`ITargetSelector`, `TargetSelectorType`)
|
- target selector (`ITargetSelector`, `TargetSelectorType`)
|
||||||
- effect layer (`IWeaponAttackEffect`)
|
- effect layer (`IWeaponAttackEffect`)
|
||||||
- data contract (`DRWeapon`, `WeaponData`)
|
- data contract (`DRWeapon`, `WeaponData`, `ParamsData`)
|
||||||
3. Keep behavior compatibility with current gameplay loop and UI/event chain.
|
3. Keep behavior compatibility with current gameplay loop, shop flow, inventory flow, and UI/event chain.
|
||||||
|
|
||||||
## Source Map
|
## Source Map
|
||||||
|
|
||||||
- Weapon base: `../../Assets/GameMain/Scripts/Entity/EntityLogic/Weapon/WeaponBase.cs`
|
- Weapon base:
|
||||||
|
- `../../Assets/GameMain/Scripts/Entity/EntityLogic/Weapon/WeaponBase.cs`
|
||||||
- Existing weapons:
|
- Existing weapons:
|
||||||
- `../../Assets/GameMain/Scripts/Entity/EntityLogic/Weapon/WeaponKnife/`
|
- `../../Assets/GameMain/Scripts/Entity/EntityLogic/Weapon/WeaponKnife/`
|
||||||
- `../../Assets/GameMain/Scripts/Entity/EntityLogic/Weapon/WeaponHandgun/`
|
- `../../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:
|
- Selectors:
|
||||||
- `../../Assets/GameMain/Scripts/Entity/EntityLogic/Weapon/TargetSelector/`
|
- `../../Assets/GameMain/Scripts/Entity/EntityLogic/Weapon/TargetSelector/`
|
||||||
|
- Attack effects:
|
||||||
|
- `../../Assets/GameMain/Scripts/Entity/EntityLogic/Weapon/AttackEffects/`
|
||||||
- Weapon data:
|
- Weapon data:
|
||||||
- `../../Assets/GameMain/Scripts/Entity/EntityData/Weapon/`
|
- `../../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:
|
- Entity show flow:
|
||||||
- `../../Assets/GameMain/Scripts/Entity/EntityExtension.cs`
|
- `../../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 state transitions explicit and non-blocking.
|
||||||
- Keep cooldown accumulation valid even when no target is found.
|
- Keep cooldown accumulation valid even when no target is found.
|
||||||
- Keep attack logic and visual effect logic decoupled.
|
- 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.
|
- Preserve compatibility with shop/inventory/UI refresh flow.
|
||||||
|
|
||||||
## Change Recipes
|
## Change Recipes
|
||||||
|
|
@ -44,17 +55,18 @@ description: Develop and extend the VampireLike weapon system. Use when creating
|
||||||
### Add a New Weapon
|
### Add a New Weapon
|
||||||
|
|
||||||
1. Extend `WeaponType` without reordering existing enum values.
|
1. Extend `WeaponType` without reordering existing enum values.
|
||||||
2. Add `WeaponXxxData : WeaponData` for strong-typed fields.
|
2. Add `WeaponXxxData : WeaponData` and `WeaponXxxParamsData` for weapon-specific parameters.
|
||||||
3. Add `WeaponXxx : WeaponBase` and implement only weapon-specific behavior.
|
3. Parse `ParamsJson` through `ParseParams<TParams>()` inside the weapon data constructor.
|
||||||
4. Build state files under `Weapon/WeaponXxx/` with partial class layout.
|
4. Add `WeaponXxx : WeaponBase` and implement only weapon-specific behavior.
|
||||||
5. Register display/data mapping path so `ShowWeapon` can instantiate correctly.
|
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
|
### Add or Update a Target Selector
|
||||||
|
|
||||||
1. Implement `ITargetSelector`.
|
1. Implement `ITargetSelector`.
|
||||||
2. Update `TargetSelectorType`.
|
2. Update `TargetSelectorType`.
|
||||||
3. Register creation in `WeaponBase.CreateSelector`.
|
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
|
### 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.
|
2. Trigger effect from weapon attack state only.
|
||||||
3. Keep damage resolution outside effect code.
|
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
|
## Validation Checklist
|
||||||
|
|
||||||
- Weapon can be shown, attached, and updated without exceptions.
|
- Weapon can be shown, attached, and updated without exceptions.
|
||||||
- State machine does not stall across target loss/reacquire.
|
- State machine does not stall across target loss/reacquire.
|
||||||
- Cooldown and range checks match design expectation.
|
- Cooldown and range checks match design expectation.
|
||||||
- Damage path and effect path remain decoupled.
|
- Damage path and effect path remain decoupled.
|
||||||
|
- `ParamsData` matches table content and default fallback behavior.
|
||||||
- UI/shop/inventory interactions stay stable after the change.
|
- 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.
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,17 @@
|
||||||
# Weapon Development Skill(VampireLike)
|
# Weapon Development Skill(VampireLike)
|
||||||
|
|
||||||
## 目标
|
## 目标
|
||||||
本文件是 `Entity.Weapon` 体系的开发规范与速查手册。
|
本文件是 `Entity.Weapon` 体系的开发规范与速查手册。
|
||||||
后续新增武器或扩展机制时,优先按本文档执行,避免重复通读历史上下文。
|
后续新增武器、扩展参数、调整状态机或联动商店/背包时,优先按本文档执行,避免重复通读历史上下文。
|
||||||
|
|
||||||
## 当前架构总览
|
## 当前架构总览
|
||||||
- 武器运行时入口:`WeaponBase`(`Assets/GameMain/Scripts/Entity/EntityLogic/Weapon/WeaponBase.cs`)
|
- 武器运行时入口:`WeaponBase`(`Assets/GameMain/Scripts/Entity/EntityLogic/Weapon/WeaponBase.cs`)
|
||||||
- 武器具体实现:`WeaponKnife`、`WeaponHandgun`、`WeaponSlash`
|
- 武器具体实现:`WeaponKnife`、`WeaponHandgun`、`WeaponSlash`、`WeaponLightning`
|
||||||
- 目标选择策略:`ITargetSelector` + `TargetSelectorType`
|
- 目标选择策略:`ITargetSelector` + `TargetSelectorType`
|
||||||
- 攻击可视化:`IWeaponAttackEffect`
|
- 攻击可视化:`IWeaponAttackEffect`
|
||||||
- 数据入口:`DRWeapon` -> `WeaponData`(及其子类)
|
- 数据入口:`DRWeapon` -> `WeaponData`(及其子类)
|
||||||
- 实体生成:`EntityExtension.ShowWeapon`
|
- 实体生成:`EntityExtension.ShowWeapon`
|
||||||
|
- 商店接入:`ShopFormUseCase.CreateWeaponData`
|
||||||
|
|
||||||
## WeaponBase 统一职责(已上收)
|
## WeaponBase 统一职责(已上收)
|
||||||
`WeaponBase` 负责以下通用逻辑,子类不要重复实现:
|
`WeaponBase` 负责以下通用逻辑,子类不要重复实现:
|
||||||
|
|
@ -20,8 +21,34 @@
|
||||||
- 启用门控:`OnUpdate` 中统一 `if (!_isEnabled) return`
|
- 启用门控:`OnUpdate` 中统一 `if (!_isEnabled) return`
|
||||||
- 目标选择入口:`SelectTarget`、`SetTargetSelector`、`CreateSelector`
|
- 目标选择入口:`SelectTarget`、`SetTargetSelector`、`CreateSelector`
|
||||||
- 距离判定:`IsInRange`(基于 XZ 平面距离)
|
- 距离判定:`IsInRange`(基于 XZ 平面距离)
|
||||||
|
- Simulation 命中请求:`TryQueueAreaCollisionQuery`、`TryQueueSectorCollisionQuery`
|
||||||
- 玩家攻击属性订阅:`BindAttackStatFromOwner` / `ReleaseAttackStatSubscription`
|
- 玩家攻击属性订阅:`BindAttackStatFromOwner` / `ReleaseAttackStatSubscription`
|
||||||
|
|
||||||
|
约束:
|
||||||
|
- 不要在子类里重复实现 `WeaponBase` 已有能力。
|
||||||
|
- 行为差异优先收敛在:`BuildStates`、`Check`、`Attack`、少量专属辅助方法。
|
||||||
|
|
||||||
|
## 当前武器模板分类
|
||||||
|
### 1. `WeaponKnife`
|
||||||
|
- 模式:近身前刺 + 圆形范围命中
|
||||||
|
- 典型参数:`HitRadius`
|
||||||
|
- 适合派生:长枪、刺剑、短矛、震地锤近身版
|
||||||
|
|
||||||
|
### 2. `WeaponHandgun`
|
||||||
|
- 模式:单次 `Raycast` 瞬发命中
|
||||||
|
- 当前参数化程度较低,仍适合作为远程枪械母版继续扩展
|
||||||
|
- 适合派生:手枪、霰弹枪、狙击枪、三连发枪
|
||||||
|
|
||||||
|
### 3. `WeaponSlash`
|
||||||
|
- 模式:扇形范围命中
|
||||||
|
- 典型参数:`SectorAngle`
|
||||||
|
- 适合派生:大剑、斧头、半月斩、横扫类武器
|
||||||
|
|
||||||
|
### 4. `WeaponLightning`
|
||||||
|
- 模式:锁定目标点 + 落点范围打击
|
||||||
|
- 典型参数:`HitRadius`、`HoverHeight`
|
||||||
|
- 适合派生:闪电、陨石杖、圣光柱、空袭类武器
|
||||||
|
|
||||||
## 状态机约定
|
## 状态机约定
|
||||||
统一状态枚举:
|
统一状态枚举:
|
||||||
- `Idle`
|
- `Idle`
|
||||||
|
|
@ -39,12 +66,14 @@
|
||||||
关键规则:
|
关键规则:
|
||||||
- 即使没有目标,也允许蓄力(计时器持续走)。
|
- 即使没有目标,也允许蓄力(计时器持续走)。
|
||||||
- 一旦进入 `Check_InRange` 且冷却已满,应立即触发攻击。
|
- 一旦进入 `Check_InRange` 且冷却已满,应立即触发攻击。
|
||||||
|
- 攻击过程中若需要多帧动画,使用 `_isAttacking` 控制状态退出时机。
|
||||||
|
|
||||||
## 3D 场景下的距离/朝向原则
|
## 3D 场景下的距离/朝向原则
|
||||||
- 射程与目标筛选:优先使用 XZ 平面距离(忽略 Y),调用 `AIUtility.GetSqrMagnitudeXZ`。
|
- 射程与目标筛选:优先使用 XZ 平面距离(忽略 Y),调用 `AIUtility.GetSqrMagnitudeXZ`。
|
||||||
- 视觉朝向与弹道:可按武器设计决定是否使用完整 3D 向量。
|
- 视觉朝向与弹道:可按武器设计决定是否使用完整 3D 向量。
|
||||||
- `WeaponHandgun`:允许俯仰瞄准,逻辑上用射线命中对象判定伤害。
|
- `WeaponHandgun`:允许俯仰瞄准,逻辑上用射线命中对象判定伤害。
|
||||||
- 近战地面范围类(Knife/Slash):伤害检测建议投影到地面(XZ)再判定。
|
- 近战地面范围类(Knife/Slash):伤害检测建议投影到地面(XZ)再判定。
|
||||||
|
- 落点类(Lightning):锁点可取目标当前位置,但范围判定仍建议按地面距离收口。
|
||||||
|
|
||||||
## 目标选择策略规范
|
## 目标选择策略规范
|
||||||
接口:`ITargetSelector.SelectTarget(WeaponBase weapon, IEnumerable<EntityBase> candidates, float maxSqrRange)`
|
接口:`ITargetSelector.SelectTarget(WeaponBase weapon, IEnumerable<EntityBase> candidates, float maxSqrRange)`
|
||||||
|
|
@ -55,14 +84,15 @@
|
||||||
- `LowestHealthTargetSelector`
|
- `LowestHealthTargetSelector`
|
||||||
|
|
||||||
语义约定:
|
语义约定:
|
||||||
- `HighestHealth` / `LowestHealth` 必须按“当前血量”筛选。
|
- `NearestTargetSelector` 在 Simulation 启用时优先走空间索引。
|
||||||
- 当前实现读取 `HealthComponent.CurrentHealth`。
|
- `HighestHealth` / `LowestHealth` 当前基于运行时生命值选择目标。
|
||||||
|
- 若新增新策略,必须明确“按当前值”还是“按最大值”筛选,避免语义漂移。
|
||||||
|
|
||||||
扩展策略步骤:
|
扩展策略步骤:
|
||||||
1. 新建 selector 类并实现 `ITargetSelector`。
|
1. 新建 selector 类并实现 `ITargetSelector`。
|
||||||
2. 更新 `TargetSelectorType` 枚举。
|
2. 更新 `TargetSelectorType` 枚举。
|
||||||
3. 在 `WeaponBase.CreateSelector` 中注册。
|
3. 在 `WeaponBase.CreateSelector` 中注册。
|
||||||
4. 武器在 `OnWeaponShow` 或构造阶段选择策略。
|
4. 武器在 `OnWeaponShow` 或初始化阶段选择策略。
|
||||||
|
|
||||||
## 攻击可视化效果规范
|
## 攻击可视化效果规范
|
||||||
接口:`IWeaponAttackEffect.Play(WeaponBase weapon, Vector3 position, EntityBase target, float radius)`
|
接口:`IWeaponAttackEffect.Play(WeaponBase weapon, Vector3 position, EntityBase target, float radius)`
|
||||||
|
|
@ -71,25 +101,68 @@
|
||||||
- 可视化逻辑与伤害逻辑解耦。
|
- 可视化逻辑与伤害逻辑解耦。
|
||||||
- 武器类只负责触发 `Play`,不把可视化细节塞回武器核心逻辑。
|
- 武器类只负责触发 `Play`,不把可视化细节塞回武器核心逻辑。
|
||||||
- 当前阶段允许临时对象创建;后续若有性能压力再统一对象池化。
|
- 当前阶段允许临时对象创建;后续若有性能压力再统一对象池化。
|
||||||
|
- 命中特效、范围预警、扇形描边都属于 effect 层,不属于伤害层。
|
||||||
|
|
||||||
## 数据层规范(DRWeapon / WeaponData)
|
## 数据层规范(DRWeapon / WeaponData)
|
||||||
`DRWeapon` 提供通用字段:
|
### `DRWeapon`
|
||||||
- `Attack`、`Cooldown`、`AttackRange`、`AttackSoundId`
|
提供通用字段:
|
||||||
- `Pramas`(字典,Key 建议统一转小写)
|
- `Attack`
|
||||||
|
- `Cooldown`
|
||||||
|
- `AttackRange`
|
||||||
|
- `AttackSoundId`
|
||||||
|
- `ParamsJson`
|
||||||
|
- `Pramas`
|
||||||
- `Modifiers`
|
- `Modifiers`
|
||||||
|
|
||||||
约定:
|
约定:
|
||||||
- 参数解析尽量在数据层/初始化阶段完成。
|
- `Params` 列现在使用标准 JSON 对象。
|
||||||
- 武器逻辑层读取 `WeaponData` 的强类型结果,不要散落 `Parse`。
|
- 空参数统一写 `{}`。
|
||||||
- 解析时必须容错:优先 `TryParse` + 默认值,避免运行时异常。
|
- 不再兼容 `[]`。
|
||||||
|
- `Pramas` 保留为描述/UI 的兼容字典视图,不再作为武器逻辑主读取入口。
|
||||||
|
|
||||||
|
### `WeaponData`
|
||||||
|
提供公共武器字段与通用解析入口:
|
||||||
|
- 公共战斗字段:`Attack`、`Cooldown`、`AttackRange`
|
||||||
|
- 公共展示字段:`Title`、`IconAssetName`、`Rarity`、`Price`
|
||||||
|
- 参数入口:`ParamsJson`、`Params`
|
||||||
|
- 通用强类型解析:`ParseParams<TParams>()`
|
||||||
|
|
||||||
|
约定:
|
||||||
|
- 参数解析优先在数据层完成。
|
||||||
|
- 武器逻辑层读取强类型 `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. 定义枚举
|
1. 定义枚举
|
||||||
- 更新 `WeaponType`(保持递增值,避免重排已有值)。
|
- 更新 `WeaponType`,保持递增值,避免重排已有值。
|
||||||
|
|
||||||
2. 建立数据类
|
2. 建立数据类
|
||||||
- 新建 `WeaponXxxData : WeaponData`。
|
- 新建 `WeaponXxxData : WeaponData`。
|
||||||
- 如果有独有参数,提供强类型字段或统一初始化逻辑。
|
- 新建 `WeaponXxxParamsData`。
|
||||||
|
- 在构造阶段调用 `ParseParams<TParams>()`,把参数初始化为强类型字段。
|
||||||
|
|
||||||
3. 建立行为类
|
3. 建立行为类
|
||||||
- 新建 `WeaponXxx : WeaponBase`。
|
- 新建 `WeaponXxx : WeaponBase`。
|
||||||
|
|
@ -106,29 +179,84 @@
|
||||||
5. 建立可视化(可选)
|
5. 建立可视化(可选)
|
||||||
- 新建 `WeaponXxxAttackEffect : IWeaponAttackEffect`,在武器中组合调用。
|
- 新建 `WeaponXxxAttackEffect : IWeaponAttackEffect`,在武器中组合调用。
|
||||||
|
|
||||||
6. 若为远程武器,建立子弹实体
|
6. 接入实体展示与数据表
|
||||||
- `BulletXxx : Bullet`
|
|
||||||
- `BulletXxxData : BulletData`
|
|
||||||
- 通过武器赋予伤害/阵营参数。
|
|
||||||
- 自动销毁应走对象池回收。
|
|
||||||
|
|
||||||
7. 接入实体展示与数据表
|
|
||||||
- 确保 `DRWeapon` / `DREntity` 配置齐全。
|
- 确保 `DRWeapon` / `DREntity` 配置齐全。
|
||||||
- `EntityExtension.ShowWeapon` 可正确映射到 `Entity.Weapon.WeaponXxx`。
|
- `EntityExtension.ShowWeapon` 可正确映射到 `Entity.Weapon.WeaponXxx`。
|
||||||
|
- 商店/背包创建入口能正确构造 `WeaponXxxData`。
|
||||||
|
|
||||||
8. 联动系统
|
7. 联动系统
|
||||||
- 背包、商店购买/出售、UI 展示、事件流刷新。
|
- 背包、商店购买/出售、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` 已有通用逻辑。
|
- 是否重复实现了 `WeaponBase` 已有通用逻辑。
|
||||||
- 状态流转是否会卡死或漏转场。
|
- 状态流转是否会卡死或漏转场。
|
||||||
- 冷却计时是否在无目标时仍正常累积。
|
- 冷却计时是否在无目标时仍正常累积。
|
||||||
- 目标失效(null/Unavailable/死亡)是否安全处理。
|
- 目标失效(null/Unavailable/死亡)是否安全处理。
|
||||||
- 命中检测是否符合武器设计(地面范围/射线/扇形)。
|
- 命中检测是否符合武器设计(地面范围/射线/扇形/落点)。
|
||||||
- 数据参数是否全部容错解析。
|
- 数据参数是否全部收敛到 `ParamsData`。
|
||||||
- 可视化是否与伤害逻辑解耦。
|
- 可视化是否与伤害逻辑解耦。
|
||||||
- 是否正确订阅/解绑攻击属性(或复用基类方法)。
|
- 是否正确订阅/解绑攻击属性(或复用基类方法)。
|
||||||
|
- 商店、背包、武器描述是否仍保持兼容。
|
||||||
|
|
||||||
## 已知注意点
|
## 已知注意点
|
||||||
- 目录中存在 `Pramas` 命名拼写历史包袱,保持兼容即可。
|
- 目录中存在 `Pramas` 命名拼写历史包袱,当前保持兼容即可。
|
||||||
- 文档中的“规范”优先级高于历史实现;历史实现若偏离,按本规范逐步收敛。
|
- 文档中的“规范”优先级高于历史实现;历史实现若偏离,按本规范逐步收敛。
|
||||||
|
- 当前更推荐“公共 `WeaponData` + 专属 `ParamsData`”结构,不建议把每种武器做成一套完全独立的数据总线。
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -1,6 +1,9 @@
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
import os
|
import os
|
||||||
import csv
|
def format_cell(value):
|
||||||
|
if value is None:
|
||||||
|
return ''
|
||||||
|
return str(value)
|
||||||
|
|
||||||
def convert_excel_to_txt(folder_path='.'):
|
def convert_excel_to_txt(folder_path='.'):
|
||||||
# 计数器,用于最后汇总
|
# 计数器,用于最后汇总
|
||||||
|
|
@ -28,24 +31,15 @@ def convert_excel_to_txt(folder_path='.'):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# 读取 Excel
|
# 读取 Excel
|
||||||
df = pd.read_excel(file_path, header=None)
|
df = pd.read_excel(
|
||||||
|
file_path,
|
||||||
# 预处理:将 NaN 替换为空字符串,否则导出会变成 "nan"
|
header=None,
|
||||||
df = df.fillna('')
|
keep_default_na=False,
|
||||||
|
|
||||||
# 导出设置:
|
|
||||||
# 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='\\'
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
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}")
|
print(f"成功转换 -> {output_file}")
|
||||||
count += 1
|
count += 1
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue