From 8d21d53c4d4c9a66b2eb541498b9c2764d1f3c4d Mon Sep 17 00:00:00 2001 From: SepComet <2428390463@qq.com> Date: Thu, 19 Mar 2026 17:53:24 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=AD=A6=E5=99=A8=E9=95=BF?= =?UTF-8?q?=E6=9E=AA=20+=20bug=20fix?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - WeaponLance:Entity/Goods/Weapon 表,状态机 - Battle -> Shop/LevelUp:隐藏子弹 --- Assets/GameMain/DataTables/Entity.txt | 1 + Assets/GameMain/DataTables/Goods.txt | 4 +- Assets/GameMain/DataTables/Weapon.txt | 6 +- Assets/GameMain/Entities/WeaponLance.prefab | 225 ++++++++++++ .../GameMain/Entities/WeaponLance.prefab.meta | 7 + Assets/GameMain/Scripts/DataTable/DRWeapon.cs | 4 +- .../Scripts/Definition/Enum/WeaponType.cs | 1 + .../Entity/EntityData/Enemy/EnemyData.cs | 2 +- .../Entity/EntityData/Weapon/WeaponData.cs | 2 +- .../EntityData/Weapon/WeaponLanceData.cs | 56 +++ .../EntityData/Weapon/WeaponLanceData.cs.meta | 11 + .../Entity/EntityLogic/Weapon/WeaponBase.cs | 13 +- .../Weapon/WeaponHandgun/WeaponHandgun.cs | 55 ++- .../EntityLogic/Weapon/WeaponLance.meta | 8 + .../WeaponLance/WeaponLance.AttackState.cs | 31 ++ .../WeaponLance.AttackState.cs.meta | 11 + .../WeaponLance.CheckInRangeState.cs | 49 +++ .../WeaponLance.CheckInRangeState.cs.meta | 11 + .../WeaponLance.CheckOutRangeState.cs | 39 +++ .../WeaponLance.CheckOutRangeState.cs.meta | 11 + .../WeaponLance/WeaponLance.IdleState.cs | 33 ++ .../WeaponLance/WeaponLance.IdleState.cs.meta | 11 + .../Weapon/WeaponLance/WeaponLance.cs | 325 ++++++++++++++++++ .../Weapon/WeaponLance/WeaponLance.cs.meta | 11 + .../Scripts/Procedure/Game/GameStateBattle.cs | 26 +- .../SimulationWorld.HitPresentation.cs | 16 +- .../DisplayItemInfoFormController.cs | 21 +- .../Controller/ShopFormController.cs | 8 +- .../UI/GameScene/UseCase/ShopFormUseCase.cs | 2 + .../UI/GameScene/View/DisplayItemInfoForm.cs | 2 +- .../Scripts/Utility/ItemDescUtility.cs | 20 +- Assets/Launcher.unity | 2 +- 数据表/Entity/Entity.xlsx | Bin 10125 -> 10161 bytes 数据表/Entity/Weapon.xlsx | Bin 11316 -> 12936 bytes 数据表/Goods.xlsx | Bin 10888 -> 12408 bytes 35 files changed, 945 insertions(+), 79 deletions(-) create mode 100644 Assets/GameMain/Entities/WeaponLance.prefab create mode 100644 Assets/GameMain/Entities/WeaponLance.prefab.meta create mode 100644 Assets/GameMain/Scripts/Entity/EntityData/Weapon/WeaponLanceData.cs create mode 100644 Assets/GameMain/Scripts/Entity/EntityData/Weapon/WeaponLanceData.cs.meta create mode 100644 Assets/GameMain/Scripts/Entity/EntityLogic/Weapon/WeaponLance.meta create mode 100644 Assets/GameMain/Scripts/Entity/EntityLogic/Weapon/WeaponLance/WeaponLance.AttackState.cs create mode 100644 Assets/GameMain/Scripts/Entity/EntityLogic/Weapon/WeaponLance/WeaponLance.AttackState.cs.meta create mode 100644 Assets/GameMain/Scripts/Entity/EntityLogic/Weapon/WeaponLance/WeaponLance.CheckInRangeState.cs create mode 100644 Assets/GameMain/Scripts/Entity/EntityLogic/Weapon/WeaponLance/WeaponLance.CheckInRangeState.cs.meta create mode 100644 Assets/GameMain/Scripts/Entity/EntityLogic/Weapon/WeaponLance/WeaponLance.CheckOutRangeState.cs create mode 100644 Assets/GameMain/Scripts/Entity/EntityLogic/Weapon/WeaponLance/WeaponLance.CheckOutRangeState.cs.meta create mode 100644 Assets/GameMain/Scripts/Entity/EntityLogic/Weapon/WeaponLance/WeaponLance.IdleState.cs create mode 100644 Assets/GameMain/Scripts/Entity/EntityLogic/Weapon/WeaponLance/WeaponLance.IdleState.cs.meta create mode 100644 Assets/GameMain/Scripts/Entity/EntityLogic/Weapon/WeaponLance/WeaponLance.cs create mode 100644 Assets/GameMain/Scripts/Entity/EntityLogic/Weapon/WeaponLance/WeaponLance.cs.meta diff --git a/Assets/GameMain/DataTables/Entity.txt b/Assets/GameMain/DataTables/Entity.txt index 773b0a3..bd1e86b 100644 --- a/Assets/GameMain/DataTables/Entity.txt +++ b/Assets/GameMain/DataTables/Entity.txt @@ -10,5 +10,6 @@ 202 武器手枪 WeaponHandgun 203 武器斧头 WeaponSlash 204 武器闪电 WeaponLightning + 205 武器长枪 WeaponLance 10001 金币实体 CoinEntity 10002 经验实体 ExpEntity diff --git a/Assets/GameMain/DataTables/Goods.txt b/Assets/GameMain/DataTables/Goods.txt index 3a18b18..7b83641 100644 --- a/Assets/GameMain/DataTables/Goods.txt +++ b/Assets/GameMain/DataTables/Goods.txt @@ -1,5 +1,4 @@ -# 商品表 -# Id GoodsType GoodsTypeId +# Id 列1 GoodsType GoodsTypeId # int GoodsType int # 商品编号 策划备注 商品类型 商品对应物品Id 101 道具:药 Prop 101 @@ -26,3 +25,4 @@ 122 Prop 120 123 Weapon 3 124 Weapon 4 + 125 Weapon 5 diff --git a/Assets/GameMain/DataTables/Weapon.txt b/Assets/GameMain/DataTables/Weapon.txt index 9c72174..5075f13 100644 --- a/Assets/GameMain/DataTables/Weapon.txt +++ b/Assets/GameMain/DataTables/Weapon.txt @@ -1,8 +1,8 @@ -# 武器表 -# Id EntityTypeId Title IconAssetName Rarity Price PriceRandomPercent Attack Cooldown AttackRange AttackSoundId Pramas Modifiers +# Id 列1 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} [] + 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} [] + 5 205 长枪 Almighty_Icon White 100 0.1 100 1.5 7 10000 {"hitRadius":0.3,"thrustDistance":1.2,"pierceLength":0.3} diff --git a/Assets/GameMain/Entities/WeaponLance.prefab b/Assets/GameMain/Entities/WeaponLance.prefab new file mode 100644 index 0000000..6cf8f57 --- /dev/null +++ b/Assets/GameMain/Entities/WeaponLance.prefab @@ -0,0 +1,225 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1 &6354441506395502586 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 8872382261416578947} + - component: {fileID: 1092941560137749238} + - component: {fileID: 2293075059394330032} + m_Layer: 11 + m_Name: Cylinder + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &8872382261416578947 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6354441506395502586} + serializedVersion: 2 + m_LocalRotation: {x: 0.7071068, y: 0, z: 0, w: 0.7071068} + m_LocalPosition: {x: 0, y: 0, z: 0.4} + m_LocalScale: {x: 0.5, y: 0.3, z: 0.5} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 5097192555115739519} + m_LocalEulerAnglesHint: {x: 90, y: 0, z: 0} +--- !u!33 &1092941560137749238 +MeshFilter: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6354441506395502586} + m_Mesh: {fileID: 10206, guid: 0000000000000000e000000000000000, type: 0} +--- !u!23 &2293075059394330032 +MeshRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6354441506395502586} + m_Enabled: 1 + m_CastShadows: 1 + m_ReceiveShadows: 1 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 2 + m_RayTraceProcedural: 0 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 2100000, guid: c4f37184fcb9306428d7d002f7dca96d, type: 2} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 1 + m_PreserveUVs: 0 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + m_SelectedEditorRenderState: 3 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 + m_AdditionalVertexStreams: {fileID: 0} +--- !u!1 &6722279723536450523 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 4238244161129684256} + - component: {fileID: 8136283925019532162} + - component: {fileID: 452598937405325984} + - component: {fileID: 6200741578935482964} + m_Layer: 11 + m_Name: Cylinder 1 + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &4238244161129684256 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6722279723536450523} + serializedVersion: 2 + m_LocalRotation: {x: 0.7071068, y: 0, z: 0, w: 0.7071068} + m_LocalPosition: {x: 0, y: 0, z: 0.7} + m_LocalScale: {x: 0.2, y: 0.3, z: 0.2} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 5097192555115739519} + m_LocalEulerAnglesHint: {x: 90, y: 0, z: 0} +--- !u!33 &8136283925019532162 +MeshFilter: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6722279723536450523} + m_Mesh: {fileID: 10206, guid: 0000000000000000e000000000000000, type: 0} +--- !u!23 &452598937405325984 +MeshRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6722279723536450523} + m_Enabled: 1 + m_CastShadows: 1 + m_ReceiveShadows: 1 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 2 + m_RayTraceProcedural: 0 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 2100000, guid: 31321ba15b8f8eb4c954353edc038b1d, type: 2} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 1 + m_PreserveUVs: 0 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + m_SelectedEditorRenderState: 3 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 + m_AdditionalVertexStreams: {fileID: 0} +--- !u!136 &6200741578935482964 +CapsuleCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6722279723536450523} + m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_IsTrigger: 0 + m_ProvidesContacts: 0 + m_Enabled: 1 + serializedVersion: 2 + m_Radius: 0.5000001 + m_Height: 2 + m_Direction: 1 + m_Center: {x: 0.000000059604645, y: 0, z: -0.00000008940697} +--- !u!1 &7825103691467368365 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 5097192555115739519} + m_Layer: 11 + m_Name: WeaponLance + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &5097192555115739519 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7825103691467368365} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 8872382261416578947} + - {fileID: 4238244161129684256} + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} diff --git a/Assets/GameMain/Entities/WeaponLance.prefab.meta b/Assets/GameMain/Entities/WeaponLance.prefab.meta new file mode 100644 index 0000000..541f6e3 --- /dev/null +++ b/Assets/GameMain/Entities/WeaponLance.prefab.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 11143001bcbdc864b8d8fe2083142e5a +PrefabImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameMain/Scripts/DataTable/DRWeapon.cs b/Assets/GameMain/Scripts/DataTable/DRWeapon.cs index edf6b37..b2346c1 100644 --- a/Assets/GameMain/Scripts/DataTable/DRWeapon.cs +++ b/Assets/GameMain/Scripts/DataTable/DRWeapon.cs @@ -122,7 +122,7 @@ namespace DataTable /// private Dictionary DeserializeParams(string rawParams) { - var dict = new Dictionary(); + var dict = new Dictionary(StringComparer.OrdinalIgnoreCase); if (string.IsNullOrWhiteSpace(rawParams)) { return dict; @@ -143,7 +143,7 @@ namespace DataTable continue; } - dict[pair.Key.ToLower()] = pair.Value.ToString(); + dict[pair.Key] = pair.Value.ToString(); } } catch (Exception exception) diff --git a/Assets/GameMain/Scripts/Definition/Enum/WeaponType.cs b/Assets/GameMain/Scripts/Definition/Enum/WeaponType.cs index f8c9520..cddd437 100644 --- a/Assets/GameMain/Scripts/Definition/Enum/WeaponType.cs +++ b/Assets/GameMain/Scripts/Definition/Enum/WeaponType.cs @@ -7,5 +7,6 @@ namespace Definition.Enum WeaponHandgun = 2, WeaponSlash = 3, WeaponLightning = 4, + WeaponLance = 5, } } diff --git a/Assets/GameMain/Scripts/Entity/EntityData/Enemy/EnemyData.cs b/Assets/GameMain/Scripts/Entity/EntityData/Enemy/EnemyData.cs index 117a255..490eb8c 100644 --- a/Assets/GameMain/Scripts/Entity/EntityData/Enemy/EnemyData.cs +++ b/Assets/GameMain/Scripts/Entity/EntityData/Enemy/EnemyData.cs @@ -59,7 +59,7 @@ namespace Entity.EntityData return false; } - return _drEnemy.Params.TryGetValue(key.ToLower(), out value); + return _drEnemy.Params.TryGetValue(key, out value); } } } diff --git a/Assets/GameMain/Scripts/Entity/EntityData/Weapon/WeaponData.cs b/Assets/GameMain/Scripts/Entity/EntityData/Weapon/WeaponData.cs index b47958a..4483584 100644 --- a/Assets/GameMain/Scripts/Entity/EntityData/Weapon/WeaponData.cs +++ b/Assets/GameMain/Scripts/Entity/EntityData/Weapon/WeaponData.cs @@ -37,7 +37,7 @@ namespace Entity.EntityData return false; } - return Params.TryGetValue(key.ToLower(), out value); + return Params.TryGetValue(key, out value); } protected TParams ParseParams() where TParams : new() diff --git a/Assets/GameMain/Scripts/Entity/EntityData/Weapon/WeaponLanceData.cs b/Assets/GameMain/Scripts/Entity/EntityData/Weapon/WeaponLanceData.cs new file mode 100644 index 0000000..c3c3cce --- /dev/null +++ b/Assets/GameMain/Scripts/Entity/EntityData/Weapon/WeaponLanceData.cs @@ -0,0 +1,56 @@ +using System; +using Definition.Enum; + +namespace Entity.EntityData +{ + [Serializable] + public sealed class WeaponLanceParamsData + { + /// + /// 枪尖命中半径。 + /// + public float HitRadius { get; set; } + + /// + /// 武器模型前刺的位移距离。 + /// + public float ThrustDistance { get; set; } + + /// + /// 实际判定的前刺长度。 + /// + public float PierceLength { get; set; } + + /// + /// 判定起点相对武器当前位置的前置偏移。 + /// + public float ForwardOffset { get; set; } + + /// + /// 追踪目标时的转向速度。 + /// + public float RotateSpeed { get; set; } + + /// + /// 向前突刺阶段耗时。 + /// + public float AttackDuration { get; set; } + + /// + /// 收枪返回阶段耗时。 + /// + public float ReturnDuration { get; set; } + } + + [Serializable] + public class WeaponLanceData : WeaponData + { + public WeaponLanceParamsData ParamsData { get; } + + public WeaponLanceData(int entityId, int ownerId, CampType ownerCamp) + : base(entityId, WeaponType.WeaponLance, ownerId, ownerCamp) + { + ParamsData = ParseParams(); + } + } +} diff --git a/Assets/GameMain/Scripts/Entity/EntityData/Weapon/WeaponLanceData.cs.meta b/Assets/GameMain/Scripts/Entity/EntityData/Weapon/WeaponLanceData.cs.meta new file mode 100644 index 0000000..f16bacc --- /dev/null +++ b/Assets/GameMain/Scripts/Entity/EntityData/Weapon/WeaponLanceData.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4d1a821e8ee1a9a4b912b70b0a1616eb +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameMain/Scripts/Entity/EntityLogic/Weapon/WeaponBase.cs b/Assets/GameMain/Scripts/Entity/EntityLogic/Weapon/WeaponBase.cs index 754a296..3a4fd7c 100644 --- a/Assets/GameMain/Scripts/Entity/EntityLogic/Weapon/WeaponBase.cs +++ b/Assets/GameMain/Scripts/Entity/EntityLogic/Weapon/WeaponBase.cs @@ -37,7 +37,7 @@ namespace Entity.Weapon protected ITargetSelector TargetSelector { get; set; } protected Dictionary _states; - + protected WeaponStateBase _currentState; protected EntityBase _target; @@ -50,7 +50,7 @@ namespace Entity.Weapon private StatComponent _attackStatComponent; private System.Action _attackStatCallback; - + private static readonly List s_EmptyCandidates = new(); #region Lifecycle @@ -304,11 +304,4 @@ namespace Entity.Weapon public abstract void OnLeave(); public override string ToString() => State.ToString(); } - - - - - -} - - +} \ No newline at end of file diff --git a/Assets/GameMain/Scripts/Entity/EntityLogic/Weapon/WeaponHandgun/WeaponHandgun.cs b/Assets/GameMain/Scripts/Entity/EntityLogic/Weapon/WeaponHandgun/WeaponHandgun.cs index cae096f..22bd47d 100644 --- a/Assets/GameMain/Scripts/Entity/EntityLogic/Weapon/WeaponHandgun/WeaponHandgun.cs +++ b/Assets/GameMain/Scripts/Entity/EntityLogic/Weapon/WeaponHandgun/WeaponHandgun.cs @@ -46,22 +46,15 @@ namespace Entity.Weapon { FaceTargetImmediately(); - Vector3 fireOrigin = CachedTransform.TransformPoint(_fireOriginOffset); - Vector3 fireDirection = CachedTransform.forward; - float maxDistance = Mathf.Max(0.1f, _weaponData.AttackRange); - - if (Physics.Raycast(fireOrigin, fireDirection, out RaycastHit hit, maxDistance, _hitMask, - QueryTriggerInteraction.Collide)) + if (!TryResolveAttackTarget(out TargetableObject targetable, out Vector3 hitPosition)) { - TargetableObject targetable = hit.collider.GetComponentInParent(); - if (targetable != null && targetable.Available && !targetable.IsDead) - { - _attackEffect?.Play(this, hit.point, targetable, 0f); - _isAttacking = true; - AIUtility.PerformCollision(targetable, this); - _isAttacking = false; - } + return; } + + _attackEffect?.Play(this, hitPosition, targetable, 0f); + _isAttacking = true; + AIUtility.PerformCollision(targetable, this); + _isAttacking = false; } protected override void Check() @@ -95,6 +88,40 @@ namespace Entity.Weapon CachedTransform.rotation = Quaternion.LookRotation(directionToTarget.normalized, Vector3.up); } + private bool TryResolveAttackTarget(out TargetableObject targetable, out Vector3 hitPosition) + { + targetable = _target as TargetableObject; + hitPosition = CachedTransform.position; + if (targetable == null || !targetable.Available || targetable.IsDead) + { + return false; + } + + Transform targetTransform = targetable.CachedTransform; + if (targetTransform == null) + { + return false; + } + + hitPosition = targetTransform.position; + + Vector3 fireOrigin = CachedTransform.TransformPoint(_fireOriginOffset); + Vector3 directionToTarget = targetTransform.position - fireOrigin; + float maxDistance = Mathf.Max(0.1f, _weaponData.AttackRange); + if (directionToTarget.sqrMagnitude > Mathf.Epsilon && + Physics.Raycast(fireOrigin, directionToTarget.normalized, out RaycastHit hit, maxDistance, _hitMask, + QueryTriggerInteraction.Collide)) + { + TargetableObject raycastTarget = hit.collider.GetComponentInParent(); + if (raycastTarget == targetable) + { + hitPosition = hit.point; + } + } + + return true; + } + #region Lifecycle protected override bool OnWeaponShow(object userData) diff --git a/Assets/GameMain/Scripts/Entity/EntityLogic/Weapon/WeaponLance.meta b/Assets/GameMain/Scripts/Entity/EntityLogic/Weapon/WeaponLance.meta new file mode 100644 index 0000000..4d75326 --- /dev/null +++ b/Assets/GameMain/Scripts/Entity/EntityLogic/Weapon/WeaponLance.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: aee5d5037e73e894cac11712e321b930 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameMain/Scripts/Entity/EntityLogic/Weapon/WeaponLance/WeaponLance.AttackState.cs b/Assets/GameMain/Scripts/Entity/EntityLogic/Weapon/WeaponLance/WeaponLance.AttackState.cs new file mode 100644 index 0000000..394b447 --- /dev/null +++ b/Assets/GameMain/Scripts/Entity/EntityLogic/Weapon/WeaponLance/WeaponLance.AttackState.cs @@ -0,0 +1,31 @@ +namespace Entity.Weapon +{ + public partial class WeaponLance + { + private class AttackState : WeaponStateBase + { + private WeaponLance _weapon; + + public override WeaponStateType State => WeaponStateType.Attack; + public override void OnInit(WeaponBase weapon) => _weapon = weapon as WeaponLance; + + public override void OnEnter() + { + _weapon._currAttackTimer = 0f; + _weapon.Attack(); + } + + public override void OnUpdate(float elapseSeconds, float realElapseSeconds) + { + if (!_weapon._isAttacking) + { + _weapon.TransitionTo(WeaponStateType.Check_InRange); + } + } + + public override void OnLeave() + { + } + } + } +} diff --git a/Assets/GameMain/Scripts/Entity/EntityLogic/Weapon/WeaponLance/WeaponLance.AttackState.cs.meta b/Assets/GameMain/Scripts/Entity/EntityLogic/Weapon/WeaponLance/WeaponLance.AttackState.cs.meta new file mode 100644 index 0000000..2ad423b --- /dev/null +++ b/Assets/GameMain/Scripts/Entity/EntityLogic/Weapon/WeaponLance/WeaponLance.AttackState.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d9991ee189b733b4e9d40395357f5ef2 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameMain/Scripts/Entity/EntityLogic/Weapon/WeaponLance/WeaponLance.CheckInRangeState.cs b/Assets/GameMain/Scripts/Entity/EntityLogic/Weapon/WeaponLance/WeaponLance.CheckInRangeState.cs new file mode 100644 index 0000000..2d1d3d2 --- /dev/null +++ b/Assets/GameMain/Scripts/Entity/EntityLogic/Weapon/WeaponLance/WeaponLance.CheckInRangeState.cs @@ -0,0 +1,49 @@ +namespace Entity.Weapon +{ + public partial class WeaponLance + { + private class CheckInRangeState : WeaponStateBase + { + private WeaponLance _weapon; + + public override WeaponStateType State => WeaponStateType.Check_InRange; + public override void OnInit(WeaponBase weapon) => _weapon = weapon as WeaponLance; + + public override void OnEnter() + { + if (_weapon._currAttackTimer >= _weapon._weaponData.Cooldown) + { + _weapon.TransitionTo(WeaponStateType.Attack); + } + } + + public override void OnUpdate(float elapseSeconds, float realElapseSeconds) + { + _weapon.Check(); + _weapon.RotateToTarget(elapseSeconds); + _weapon._currAttackTimer += elapseSeconds; + + if (_weapon._target == null || !_weapon._target.Available) + { + _weapon.TransitionTo(WeaponStateType.Idle); + return; + } + + if (!_weapon.IsInRange(_weapon._target, _weapon._sqrRange)) + { + _weapon.TransitionTo(WeaponStateType.Check_OutRange); + return; + } + + if (_weapon._currAttackTimer >= _weapon._weaponData.Cooldown) + { + _weapon.TransitionTo(WeaponStateType.Attack); + } + } + + public override void OnLeave() + { + } + } + } +} diff --git a/Assets/GameMain/Scripts/Entity/EntityLogic/Weapon/WeaponLance/WeaponLance.CheckInRangeState.cs.meta b/Assets/GameMain/Scripts/Entity/EntityLogic/Weapon/WeaponLance/WeaponLance.CheckInRangeState.cs.meta new file mode 100644 index 0000000..3e6f575 --- /dev/null +++ b/Assets/GameMain/Scripts/Entity/EntityLogic/Weapon/WeaponLance/WeaponLance.CheckInRangeState.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 01edc8bb0ff46a14d8028e03af932b52 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameMain/Scripts/Entity/EntityLogic/Weapon/WeaponLance/WeaponLance.CheckOutRangeState.cs b/Assets/GameMain/Scripts/Entity/EntityLogic/Weapon/WeaponLance/WeaponLance.CheckOutRangeState.cs new file mode 100644 index 0000000..93bf972 --- /dev/null +++ b/Assets/GameMain/Scripts/Entity/EntityLogic/Weapon/WeaponLance/WeaponLance.CheckOutRangeState.cs @@ -0,0 +1,39 @@ +namespace Entity.Weapon +{ + public partial class WeaponLance + { + private class CheckOutRangeState : WeaponStateBase + { + private WeaponLance _weapon; + + public override WeaponStateType State => WeaponStateType.Check_OutRange; + public override void OnInit(WeaponBase weapon) => _weapon = weapon as WeaponLance; + + public override void OnEnter() + { + } + + public override void OnUpdate(float elapseSeconds, float realElapseSeconds) + { + _weapon.Check(); + _weapon.RotateToTarget(elapseSeconds); + _weapon._currAttackTimer += elapseSeconds; + + if (_weapon._target == null || !_weapon._target.Available) + { + _weapon.TransitionTo(WeaponStateType.Idle); + return; + } + + if (_weapon.IsInRange(_weapon._target, _weapon._sqrRange)) + { + _weapon.TransitionTo(WeaponStateType.Check_InRange); + } + } + + public override void OnLeave() + { + } + } + } +} diff --git a/Assets/GameMain/Scripts/Entity/EntityLogic/Weapon/WeaponLance/WeaponLance.CheckOutRangeState.cs.meta b/Assets/GameMain/Scripts/Entity/EntityLogic/Weapon/WeaponLance/WeaponLance.CheckOutRangeState.cs.meta new file mode 100644 index 0000000..416d391 --- /dev/null +++ b/Assets/GameMain/Scripts/Entity/EntityLogic/Weapon/WeaponLance/WeaponLance.CheckOutRangeState.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8009ad2c332a956438e3357ed5e30eb6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameMain/Scripts/Entity/EntityLogic/Weapon/WeaponLance/WeaponLance.IdleState.cs b/Assets/GameMain/Scripts/Entity/EntityLogic/Weapon/WeaponLance/WeaponLance.IdleState.cs new file mode 100644 index 0000000..f0ab6f0 --- /dev/null +++ b/Assets/GameMain/Scripts/Entity/EntityLogic/Weapon/WeaponLance/WeaponLance.IdleState.cs @@ -0,0 +1,33 @@ +namespace Entity.Weapon +{ + public partial class WeaponLance + { + public class IdleState : WeaponStateBase + { + private WeaponLance _weapon; + + public override WeaponStateType State => WeaponStateType.Idle; + public override void OnInit(WeaponBase weapon) => _weapon = weapon as WeaponLance; + + public override void OnEnter() + { + } + + public override void OnUpdate(float elapseSeconds, float realElapseSeconds) + { + _weapon.Check(); + _weapon.RotateToOrigin(elapseSeconds); + _weapon._currAttackTimer += elapseSeconds; + + if (_weapon._target != null && _weapon._target.Available) + { + _weapon.TransitionTo(WeaponStateType.Check_OutRange); + } + } + + public override void OnLeave() + { + } + } + } +} diff --git a/Assets/GameMain/Scripts/Entity/EntityLogic/Weapon/WeaponLance/WeaponLance.IdleState.cs.meta b/Assets/GameMain/Scripts/Entity/EntityLogic/Weapon/WeaponLance/WeaponLance.IdleState.cs.meta new file mode 100644 index 0000000..4ad5fa9 --- /dev/null +++ b/Assets/GameMain/Scripts/Entity/EntityLogic/Weapon/WeaponLance/WeaponLance.IdleState.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4c9e98f5381e579469dcdac7bf3e1e25 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameMain/Scripts/Entity/EntityLogic/Weapon/WeaponLance/WeaponLance.cs b/Assets/GameMain/Scripts/Entity/EntityLogic/Weapon/WeaponLance/WeaponLance.cs new file mode 100644 index 0000000..2a94a49 --- /dev/null +++ b/Assets/GameMain/Scripts/Entity/EntityLogic/Weapon/WeaponLance/WeaponLance.cs @@ -0,0 +1,325 @@ +using System.Collections.Generic; +using CustomUtility; +using Definition.DataStruct; +using Definition.Enum; +using DG.Tweening; +using Entity.EntityData; +using UnityEngine; +using UnityGameFramework.Runtime; + +namespace Entity.Weapon +{ + public partial class WeaponLance : WeaponBase + { + // 长枪专用数据,包含强类型 ParamsData。 + [SerializeField] private WeaponLanceData _weaponData; + + private Quaternion _cachedRotation; + // 朝向目标时的旋转速度,可被 ParamsData.RotateSpeed 覆盖。 + [SerializeField] private float _rotateSpeed = 5f; + + private Sequence _attackSequence; + private Transform _attackParent; + + // 前刺动画耗时。 + [SerializeField] private float _attackDuration = 0.12f; + // 收枪返回耗时。 + [SerializeField] private float _returnDuration = 0.18f; + + [SerializeField] private LayerMask _hitMask = ~0; + [SerializeField] private int _maxHitColliders = 32; + + private IWeaponAttackEffect _attackEffect; + private Collider[] _hitResults; + private readonly HashSet _hitEntityIds = new(); + + // 枪尖判定半径。 + private float _hitRadius; + // 从判定起点向前延伸的有效刺击长度。 + private float _pierceLength; + // 判定起点相对武器当前位置的前移量。 + private float _forwardOffset; + // 武器模型本身向前突刺的位移长度。 + private float _thrustDistance; + + public override ImpactData GetImpactData() + { + return new ImpactData(_weaponData.OwnerCamp, _weaponData.Attack, AttackStat); + } + + protected override void BuildStates() + { + RegisterState(new IdleState()); + RegisterState(new CheckInRangeState()); + RegisterState(new CheckOutRangeState()); + RegisterState(new AttackState()); + } + + protected override void Attack() + { + StopAttackTween(false); + FaceTargetImmediately(); + + _isAttacking = true; + _attackParent = CachedTransform.parent; + CachedTransform.SetParent(null); + + Vector3 targetPos = CachedTransform.position + CachedTransform.forward * _thrustDistance; + _attackSequence = DOTween.Sequence(); + _attackSequence.Append(CachedTransform.DOMove(targetPos, _attackDuration).SetEase(Ease.OutQuad)); + _attackSequence.AppendCallback(() => + { + Vector3 strikeCenter = GetStrikeCenter(); + _attackEffect?.Play(this, strikeCenter, _target, _hitRadius); + ApplyPierceDamage(); + + if (_attackParent != null) + { + CachedTransform.SetParent(_attackParent); + } + }); + _attackSequence.Append(CachedTransform.DOLocalMove(Vector3.zero, _returnDuration).SetEase(Ease.InQuad)); + _attackSequence.AppendCallback(() => + { + _isAttacking = false; + _attackSequence = null; + _attackParent = null; + }); + } + + protected override void Check() + { + _target = SelectTarget(_sqrRange); + } + + private void RotateToTarget(float elapseSeconds) + { + if (_target == null || !_target.Available) return; + + Vector3 directionToTarget = _target.CachedTransform.position - CachedTransform.position; + directionToTarget.y = 0f; + if (directionToTarget.sqrMagnitude <= Mathf.Epsilon) return; + + Quaternion targetRotation = Quaternion.LookRotation(directionToTarget.normalized, Vector3.up); + CachedTransform.rotation = + Quaternion.Slerp(CachedTransform.rotation, targetRotation, _rotateSpeed * elapseSeconds); + } + + private void RotateToOrigin(float elapseSeconds) + { + CachedTransform.rotation = + Quaternion.Slerp(CachedTransform.rotation, _cachedRotation, _rotateSpeed * elapseSeconds); + } + + private void FaceTargetImmediately() + { + if (_target == null || !_target.Available) return; + + Vector3 directionToTarget = _target.CachedTransform.position - CachedTransform.position; + directionToTarget.y = 0f; + if (directionToTarget.sqrMagnitude <= Mathf.Epsilon) return; + + CachedTransform.rotation = Quaternion.LookRotation(directionToTarget.normalized, Vector3.up); + } + + private Vector3 GetStrikeStart() + { + Vector3 forward = CachedTransform.forward; + forward.y = 0f; + if (forward.sqrMagnitude <= Mathf.Epsilon) + { + forward = Vector3.forward; + } + + forward.Normalize(); + return CachedTransform.position + forward * _forwardOffset; + } + + private Vector3 GetStrikeEnd() + { + Vector3 start = GetStrikeStart(); + Vector3 forward = CachedTransform.forward; + forward.y = 0f; + if (forward.sqrMagnitude <= Mathf.Epsilon) + { + forward = Vector3.forward; + } + + forward.Normalize(); + return start + forward * _pierceLength; + } + + private Vector3 GetStrikeCenter() + { + return Vector3.Lerp(GetStrikeStart(), GetStrikeEnd(), 0.5f); + } + + private void ApplyPierceDamage() + { + if (_hitRadius <= 0f || _pierceLength <= 0f) return; + + Vector3 strikeStart = GetStrikeStart(); + Vector3 strikeEnd = GetStrikeEnd(); + Vector3 strikeDirection = strikeEnd - strikeStart; + strikeDirection.y = 0f; + if (strikeDirection.sqrMagnitude <= Mathf.Epsilon) return; + + strikeDirection.Normalize(); + float halfAngle = Mathf.Rad2Deg * Mathf.Atan2(_hitRadius, Mathf.Max(0.01f, _pierceLength)); + if (TryQueueSectorCollisionQuery(strikeStart, _pierceLength, in strikeDirection, halfAngle, + Mathf.Max(1, _maxHitColliders))) + { + _hitEntityIds.Clear(); + return; + } + + int capacity = Mathf.Max(1, _maxHitColliders); + float broadPhaseRadius = _pierceLength * 0.5f + _hitRadius; + Vector3 broadPhaseCenter = Vector3.Lerp(strikeStart, strikeEnd, 0.5f); + + if (_hitResults == null || _hitResults.Length != capacity) + { + _hitResults = new Collider[capacity]; + } + + int hitCount = Physics.OverlapSphereNonAlloc(broadPhaseCenter, broadPhaseRadius, _hitResults, _hitMask, + QueryTriggerInteraction.Collide); + + _hitEntityIds.Clear(); + for (int i = 0; i < hitCount; i++) + { + Collider collider = _hitResults[i]; + if (collider == null) continue; + + TargetableObject targetable = collider.GetComponentInParent(); + if (targetable == null || !targetable.Available || targetable.IsDead) continue; + if (!_hitEntityIds.Add(targetable.Id)) continue; + + if (!IsTargetInsidePierce(targetable, strikeStart, strikeDirection)) continue; + + AIUtility.PerformCollision(targetable, this); + } + } + + private bool IsTargetInsidePierce(TargetableObject targetable, Vector3 strikeStart, Vector3 strikeDirection) + { + Vector3 toTarget = targetable.CachedTransform.position - strikeStart; + toTarget.y = 0f; + + float projection = Vector3.Dot(toTarget, strikeDirection); + if (projection < 0f || projection > _pierceLength) return false; + + Vector3 closestPoint = strikeStart + strikeDirection * projection; + Vector3 delta = targetable.CachedTransform.position - closestPoint; + delta.y = 0f; + return delta.sqrMagnitude <= _hitRadius * _hitRadius; + } + + protected override bool OnWeaponShow(object userData) + { + _weaponData = RequireWeaponData(userData); + if (_weaponData == null) return false; + WeaponData = _weaponData; + + _currAttackTimer = 0f; + _sqrRange = _weaponData.AttackRange * _weaponData.AttackRange; + _cachedRotation = CachedTransform.rotation; + + WeaponLanceParamsData paramsData = _weaponData.ParamsData; + _hitRadius = paramsData != null && paramsData.HitRadius > 0f + ? Mathf.Max(0.1f, paramsData.HitRadius) + : 0.45f; + _thrustDistance = paramsData != null && paramsData.ThrustDistance > 0f + ? paramsData.ThrustDistance + : _weaponData.AttackRange; + _pierceLength = paramsData != null && paramsData.PierceLength > 0f + ? paramsData.PierceLength + : Mathf.Max(_weaponData.AttackRange, _thrustDistance); + _forwardOffset = paramsData != null && paramsData.ForwardOffset > 0f + ? paramsData.ForwardOffset + : 0f; + + if (paramsData != null && paramsData.RotateSpeed > 0f) + { + _rotateSpeed = paramsData.RotateSpeed; + } + + if (paramsData != null && paramsData.AttackDuration > 0f) + { + _attackDuration = paramsData.AttackDuration; + } + + if (paramsData != null && paramsData.ReturnDuration > 0f) + { + _returnDuration = paramsData.ReturnDuration; + } + + int colliderCapacity = Mathf.Max(1, _maxHitColliders); + if (_hitResults == null || _hitResults.Length != colliderCapacity) + { + _hitResults = new Collider[colliderCapacity]; + } + + _attackEffect = new KnifeRangeAttackEffect(); + + if (_weaponData.OwnerCamp == CampType.Player) + { + gameObject.layer = LayerMask.NameToLayer("PlayerWeapon"); + _hitMask = LayerMask.GetMask("Enemy"); + } + else if (_weaponData.OwnerCamp == CampType.Enemy) + { + gameObject.layer = LayerMask.NameToLayer("EnemyWeapon"); + _hitMask = LayerMask.GetMask("Player"); + } + + return true; + } + + protected override void OnWeaponHide(object userData) + { + StopAttackTween(true); + _attackEffect = null; + } + + protected override void OnWeaponAttach(EntityLogic parentEntity, Transform parentTransform, object userData) + { + BindAttackStatFromOwner(parentEntity); + } + + protected override void OnWeaponDetach(EntityLogic parentEntity, object userData) + { + StopAttackTween(true); + ReleaseAttackStatSubscription(); + } + + protected override void OnEnabledChanged(bool enabled) + { + if (!enabled) + { + StopAttackTween(true); + } + } + + private void StopAttackTween(bool resetTransform) + { + if (_attackSequence != null) + { + _attackSequence.Kill(); + _attackSequence = null; + } + + _isAttacking = false; + + if (resetTransform && _attackParent != null) + { + CachedTransform.SetParent(_attackParent); + CachedTransform.localPosition = Vector3.zero; + CachedTransform.rotation = _cachedRotation; + } + + _attackParent = null; + _hitEntityIds.Clear(); + } + } +} diff --git a/Assets/GameMain/Scripts/Entity/EntityLogic/Weapon/WeaponLance/WeaponLance.cs.meta b/Assets/GameMain/Scripts/Entity/EntityLogic/Weapon/WeaponLance/WeaponLance.cs.meta new file mode 100644 index 0000000..b54effa --- /dev/null +++ b/Assets/GameMain/Scripts/Entity/EntityLogic/Weapon/WeaponLance/WeaponLance.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f90612be7695ee549b39473c9b706122 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/GameMain/Scripts/Procedure/Game/GameStateBattle.cs b/Assets/GameMain/Scripts/Procedure/Game/GameStateBattle.cs index b6a4ba2..2e3f13b 100644 --- a/Assets/GameMain/Scripts/Procedure/Game/GameStateBattle.cs +++ b/Assets/GameMain/Scripts/Procedure/Game/GameStateBattle.cs @@ -92,14 +92,9 @@ namespace Procedure GameEntry.Entity.HideEntity(entity.Id); } - var enemyProjectiles = GameEntry.Entity.GetEntityGroup("EnemyProjectile")?.GetAllEntities(); - if (enemyProjectiles != null) - { - foreach (var projectile in enemyProjectiles) - { - GameEntry.Entity.HideEntity(projectile.Id); - } - } + HideEntityGroup("Bullet"); + HideEntityGroup("Projectile"); + HideEntityGroup("EnemyProjectile"); } public override void OnDestroy(IFsm procedureOwner) @@ -108,6 +103,21 @@ namespace Procedure _procedureGame = null; } + private static void HideEntityGroup(string groupName) + { + var entityGroup = GameEntry.Entity.GetEntityGroup(groupName); + var entities = entityGroup?.GetAllEntities(); + if (entities == null) + { + return; + } + + foreach (var entity in entities) + { + GameEntry.Entity.HideEntity(entity.Id); + } + } + #endregion } } diff --git a/Assets/GameMain/Scripts/Simulation/Presentation/SimulationWorld.HitPresentation.cs b/Assets/GameMain/Scripts/Simulation/Presentation/SimulationWorld.HitPresentation.cs index d2776e7..4c681ff 100644 --- a/Assets/GameMain/Scripts/Simulation/Presentation/SimulationWorld.HitPresentation.cs +++ b/Assets/GameMain/Scripts/Simulation/Presentation/SimulationWorld.HitPresentation.cs @@ -104,18 +104,8 @@ namespace Simulation private void PlayHitMarker(ProjectileHitPresentationEventArgs args) { - EntityBase targetEntity = TryGetEntityById(args.TargetEntityId); - if (targetEntity == null || !targetEntity.Available) - { - return; - } - - _projectileHitMarkerEffect ??= new HandgunHitMarkerAttackEffect( - Mathf.Max(0.01f, _world._projectileHitMarkerSize), - _world._projectileHitMarkerYOffset, - Mathf.Max(0.01f, _world._projectileHitMarkerDuration), - _world._projectileHitMarkerColor); - _projectileHitMarkerEffect.Play(null, args.HitPosition, targetEntity, 0f); + // Projectile hit markers were reusing the handgun marker effect and were + // visually indistinguishable from handgun lock/hit feedback. } private void PlayHitEffect(ProjectileHitPresentationEventArgs args) @@ -142,4 +132,4 @@ namespace Simulation } } } -} \ No newline at end of file +} diff --git a/Assets/GameMain/Scripts/UI/GameScene/Controller/DisplayItemInfoFormController.cs b/Assets/GameMain/Scripts/UI/GameScene/Controller/DisplayItemInfoFormController.cs index 19ce4dc..47019ee 100644 --- a/Assets/GameMain/Scripts/UI/GameScene/Controller/DisplayItemInfoFormController.cs +++ b/Assets/GameMain/Scripts/UI/GameScene/Controller/DisplayItemInfoFormController.cs @@ -139,19 +139,7 @@ namespace UI { return; } - - if (Context == null) - { - Log.Error("DisplayItemInfoFormController.DisplayItemInfoHide() Context is null."); - return; - } - - if (Form == null) - { - Log.Error("DisplayItemInfoFormController.DisplayItemInfoHide() Form is null."); - return; - } - + if (args.Force) { GameEntry.UIRouter.CloseUI(UIFormType.DisplayItemInfoForm); @@ -159,11 +147,8 @@ namespace UI return; } - if (_locked && !IsCurrentFormSender(sender) && sender is not DisplayItem) - { - return; - } - + if (_locked && !args.Force) return; + GameEntry.UIRouter.CloseUI(UIFormType.DisplayItemInfoForm); _locked = false; } diff --git a/Assets/GameMain/Scripts/UI/GameScene/Controller/ShopFormController.cs b/Assets/GameMain/Scripts/UI/GameScene/Controller/ShopFormController.cs index 59b2a7b..97924b3 100644 --- a/Assets/GameMain/Scripts/UI/GameScene/Controller/ShopFormController.cs +++ b/Assets/GameMain/Scripts/UI/GameScene/Controller/ShopFormController.cs @@ -199,6 +199,12 @@ namespace UI #region UI Methods + public override void CloseUI() + { + base.CloseUI(); + GameEntry.Event.Fire(this, DisplayItemInfoHideEventArgs.Create(true)); + } + public int? OpenUI(ShopFormRawData rawData) { ShopFormContext context = BuildContext(rawData); @@ -535,4 +541,4 @@ namespace UI #endregion } -} +} \ No newline at end of file diff --git a/Assets/GameMain/Scripts/UI/GameScene/UseCase/ShopFormUseCase.cs b/Assets/GameMain/Scripts/UI/GameScene/UseCase/ShopFormUseCase.cs index b7f37d5..32d6ac8 100644 --- a/Assets/GameMain/Scripts/UI/GameScene/UseCase/ShopFormUseCase.cs +++ b/Assets/GameMain/Scripts/UI/GameScene/UseCase/ShopFormUseCase.cs @@ -397,6 +397,8 @@ namespace UI return new WeaponSlashData(entityId, ownerId, ownerCamp); case WeaponType.WeaponLightning: return new WeaponLightningData(entityId, ownerId, ownerCamp); + case WeaponType.WeaponLance: + return new WeaponLanceData(entityId, ownerId, ownerCamp); default: return null; } diff --git a/Assets/GameMain/Scripts/UI/GameScene/View/DisplayItemInfoForm.cs b/Assets/GameMain/Scripts/UI/GameScene/View/DisplayItemInfoForm.cs index 0e49b07..0b15368 100644 --- a/Assets/GameMain/Scripts/UI/GameScene/View/DisplayItemInfoForm.cs +++ b/Assets/GameMain/Scripts/UI/GameScene/View/DisplayItemInfoForm.cs @@ -199,7 +199,7 @@ namespace UI public void OnCancelButtonClick() { - GameEntry.Event.Fire(this, DisplayItemInfoHideEventArgs.Create()); + GameEntry.Event.Fire(this, DisplayItemInfoHideEventArgs.Create(true)); } } } diff --git a/Assets/GameMain/Scripts/Utility/ItemDescUtility.cs b/Assets/GameMain/Scripts/Utility/ItemDescUtility.cs index 313a308..35f582d 100644 --- a/Assets/GameMain/Scripts/Utility/ItemDescUtility.cs +++ b/Assets/GameMain/Scripts/Utility/ItemDescUtility.cs @@ -4,16 +4,23 @@ using DataTable; using Definition.DataStruct; using Entity.EntityData; using Entity.Weapon; +using System; using UnityGameFramework.Runtime; namespace CustomUtility { public static class ItemDescUtility { - private static readonly Dictionary _paramsDict = new() + private static readonly Dictionary _paramsDict = new(StringComparer.OrdinalIgnoreCase) { - {"hitradius", "伤害范围"}, - {"sectorangle", "攻击角度"} + {"hitRadius", "伤害范围"}, + {"sectorAngle", "攻击角度"}, + {"pierceLength", "前戳距离"}, + {"thrustDistance", "枪尖长度"}, + {"forwardOffset", "前置偏移"}, + {"rotateSpeed", "转向速度"}, + {"attackDuration", "突刺时长"}, + {"returnDuration", "收枪时长"} }; public static string CreatePropDescription(StatModifier[] modifiers) @@ -89,6 +96,11 @@ namespace CustomUtility sb.Append(modifiersDesc); } + if (@params == null || @params.Count == 0) + { + return sb.ToString(); + } + foreach (var kvp in @params) { if (!_paramsDict.TryGetValue(kvp.Key, out string value)) @@ -102,4 +114,4 @@ namespace CustomUtility return sb.ToString(); } } -} \ No newline at end of file +} diff --git a/Assets/Launcher.unity b/Assets/Launcher.unity index ebdc71f..d1fd2c2 100644 --- a/Assets/Launcher.unity +++ b/Assets/Launcher.unity @@ -731,7 +731,7 @@ PrefabInstance: objectReference: {fileID: 0} - target: {fileID: 11499388, guid: adb3eb1c35fcff14f89fba7b05c9d71c, type: 3} propertyPath: m_EditorResourceMode - value: 0 + value: 1 objectReference: {fileID: 0} - target: {fileID: 11499388, guid: adb3eb1c35fcff14f89fba7b05c9d71c, type: 3} propertyPath: m_JsonHelperTypeName diff --git a/数据表/Entity/Entity.xlsx b/数据表/Entity/Entity.xlsx index 6e9a339fdc244af908483dc3590bb5350ba795b2..12ab48e2f8aeb1786864d23fd1e423838c69321e 100644 GIT binary patch delta 2507 zcmY*bX*|@68lJx`VeI>ou}vX+#?FjXj4au*W(hGR4Z~QQjI62fx9iBh?_)WRJieNsMp%TXx3h-_TPO5dE;+ts$?(+MR3zR9=HkfTJC^RTB_h`tCjImCcwORNxP* zd_Q0?P}lhWrUJ5vRQF&t)FUAH8M6+hc)vH#1NE zbU6Cw?p-3`$+KDhwfY`C0a+T$m$Z#H>kj&t9gGSaEDQ}KwGx&eaPtJrcXZ;r6K0|P zh~Do06!CMPJh`v2hh2ivAP%|VCr+Le5#^ov2a&+aE+l+Ip zHw=upEDoBabv@NDbl}VN{k=v1SAs5=`Gq^@SKdw;R5q2~xy>{})#zA$&?i7R2V(Mjy>FRH}cRae0 zqzBsTk@+-}ooGU{t54TFt6Mt?(219Me+G9zOdc}Ce^UEsH&_iCKgpJ-^^7NWb#8Cm*<`;vFdjf=%rIsPXy?y)6Sr- zDoOpGs6$3dRJ#n|%8i`|#x5`=x9Sh2C!vkk>Fx4hdTb@ zLh74@4)RD%-}@=}ZjPN>NFUL=|M_CSWh}S@v(^>J%o(4)lv=kW3MOPveHn zTB9E1t|63~VlM)P&^jTibHMc%zt~3&jC!eR=lUPWZC{UKaeBNhnuUAre$VK+cfer6 zY`c7|&8%TkMyTHuzlUmo=`wk?mo0-XoPeK5XK`Y_E4|4C(ojiAo|l8uLWUs+6X{_C zPVp&}Y4~Eapzoe6Dr3!eBFn04IWlhzfoN76pe^OoM4AP4$Yol0%ubG(%Pt(^+BU>#|pjJ;RduZ?H8< zL_ma?zLua@D#ff~`zv?((V5O&hSmIye=3wu5JQ)>h@sE+6g6A+L=Pzv9Dlv1y?Z&c z{;A;Xef6=Av7E52DbDLXmN_ZOSVU9y>Od-m{;dweAwb#kx0@J$GlJ4y>N8iu+Hl5P zSoRn3>|0?+zCQ{wBXr;B@PGEdS_BlM+|EK3}mG+TuV>sY<`CjSzdJCPUZ}-`h?z`U>ByOlUsvi0Gl}_=>qP)+#YIP9|mzG0B-l5;i2AI|1 z_i5$M?b$W1q!{>%gy_iTgFa9D(9SaeUspna-;dz54x!wOGa2^eO#4PQwPwN(ie2V~ zX;vQdnU=T1yAj}6qU}U|Z0hhUdFk#j@|&w|woXs44^QY99|%~Q(lhWvAP{CqocS`6 zW*3S9$2l($eiJlaOTAzj1NTYKsYH`-;W?t3MBDn5&7|w(!oHq7Jo(_(@0zG zx3S^1=sjq$2Qh9|q)ia8xH9-nrT=Acg4%$w?}(Zs3%kTII}7FN7DYi=!j;3YLsF{8 zHlDR+LrX$VA-@nKjiIhe3p5ta74I0^C`5TzLM92eCJ{585`12wc$Ppdqst$)Ox4^*>LMz9s8D9*)G}@ThN%}5gSF31aLp?wIsHFKcP7g<7D~(Ab$P~FM{!&p z6jlP=hp@OvhTw}$LXS7hU8=4U*7I{F!PM6QsU$!cni+e^Cr5u;ja;ye1gjxk*q zOT2zar$803t8K?)s?=)nwEI%pCj86dSmmdS+hcfmLZ&8<|Ee}NLJdo_Rb|%?scH{O zKKfFl?=*1q8P^T)nU+{e&J@|$6pJjl>Nru&Oq`%LFN+;&eBM-yE95v&%4gwwO7;yD zv;1$iq?_)a#J~1#HWf5B|4N^Lf1bPO4Lfk}Jp7T$CZnXWjnR71y>LI%HF)FoPri>Y zW4YA=NzOk%nw)4{>xPiVxyLzyBQRGA33!1EQffc~$SDo}PyeZwwxnaG2gT|6z=ZwPG#M1I4JyO6 zfgJEI90Bx#g>bmYFSo6nh6)!01j6x4emehLP{0pxUEntmc3wjCzkLA$5&TE=Z`CjZ RNM#TP@#i_2(xiTE{snydg3SN` delta 2453 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 delta 5694 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 a5H27+-d|A@2tEPH!+gRY2$op?^!q=bXw-E8