Merge pull request #4 from SepComet/P2-JobSystem+Burst
P2 job system+burst
This commit is contained in:
commit
057024da58
|
|
@ -0,0 +1 @@
|
|||
* text=auto
|
||||
|
|
@ -75,15 +75,15 @@ crashlytics-build.properties
|
|||
# Packed Addressables
|
||||
/[Aa]ssets/[Aa]ddressable[Aa]ssets[Dd]ata/*/*.bin*
|
||||
|
||||
# Temporary auto-generated Android Assets
|
||||
/[Aa]ssets/[Ss]treamingAssets/aa.meta
|
||||
/[Aa]ssets/[Ss]treamingAssets/aa/*
|
||||
|
||||
/Assets/StreamingAssets
|
||||
/Assets/StreamingAssets.meta
|
||||
/UI参考
|
||||
/AGENTS.md
|
||||
/bin
|
||||
/docs/screenshot
|
||||
*.xmind
|
||||
/数据表/__pycache__/
|
||||
/类吸血鬼项目.md
|
||||
|
||||
~$*.xlsx
|
||||
Assets/GameMain/Configs/ResourceBuilder.xml
|
||||
/.dotnet
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ namespace UnityGameFramework.Editor
|
|||
"UnityGameFramework.Runtime",
|
||||
#endif
|
||||
"Assembly-CSharp",
|
||||
"VampireLike"
|
||||
};
|
||||
|
||||
private static readonly string[] RuntimeOrEditorAssemblyNames =
|
||||
|
|
@ -34,6 +35,8 @@ namespace UnityGameFramework.Editor
|
|||
"UnityGameFramework.Editor",
|
||||
#endif
|
||||
"Assembly-CSharp-Editor",
|
||||
"VampireLike",
|
||||
"VampireLike.Editor"
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -2,17 +2,17 @@
|
|||
<UnityGameFramework>
|
||||
<ResourceBuilder>
|
||||
<Settings>
|
||||
<InternalResourceVersion>3</InternalResourceVersion>
|
||||
<InternalResourceVersion>1</InternalResourceVersion>
|
||||
<Platforms>33</Platforms>
|
||||
<AssetBundleCompression>1</AssetBundleCompression>
|
||||
<CompressionHelperTypeName>UnityGameFramework.Runtime.DefaultCompressionHelper</CompressionHelperTypeName>
|
||||
<AdditionalCompressionSelected>True</AdditionalCompressionSelected>
|
||||
<AdditionalCompressionSelected>False</AdditionalCompressionSelected>
|
||||
<ForceRebuildAssetBundleSelected>False</ForceRebuildAssetBundleSelected>
|
||||
<BuildEventHandlerTypeName>StarForce.Editor.StarForceBuildEventHandler</BuildEventHandlerTypeName>
|
||||
<OutputDirectory>D:/Learn/GameLearn/UnityProjects/VampireLike/bin/AssetBundles</OutputDirectory>
|
||||
<BuildEventHandlerTypeName>VampireLike.Editor.VampireLikeBuildEventHandler</BuildEventHandlerTypeName>
|
||||
<OutputDirectory>C:/UnityProjects/VampireLike/bin/AssetBundles</OutputDirectory>
|
||||
<OutputPackageSelected>True</OutputPackageSelected>
|
||||
<OutputFullSelected>True</OutputFullSelected>
|
||||
<OutputPackedSelected>True</OutputPackedSelected>
|
||||
<OutputFullSelected>False</OutputFullSelected>
|
||||
<OutputPackedSelected>False</OutputPackedSelected>
|
||||
</Settings>
|
||||
</ResourceBuilder>
|
||||
</UnityGameFramework>
|
||||
|
|
@ -1,7 +1,5 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 5461b0fc87a2ab04fbcfd898d18f6107
|
||||
labels:
|
||||
- ResourceExclusive
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
<Resource Name="Music/Menu" FileSystem="Resources" LoadType="0" Packed="True" ResourceGroups="Music" />
|
||||
<Resource Name="Scenes" FileSystem="Resources" LoadType="0" Packed="True" />
|
||||
<Resource Name="SceneSettings" LoadType="0" Packed="False" />
|
||||
<Resource Name="Scripts" LoadType="0" Packed="False" />
|
||||
<Resource Name="Sounds" FileSystem="Resources" LoadType="0" Packed="True" />
|
||||
<Resource Name="Textures" FileSystem="Resources" LoadType="0" Packed="True" />
|
||||
<Resource Name="UI/UIForms" FileSystem="UI" LoadType="0" Packed="True" />
|
||||
|
|
@ -42,6 +43,7 @@
|
|||
<Asset Guid="0f995b3145e0e7247a42da6cef1dbf23" ResourceName="Materials" />
|
||||
<Asset Guid="1046dcb12e547564d8b54bd15419a787" ResourceName="Entities" />
|
||||
<Asset Guid="1053b0070685be347ab58587156842dc" ResourceName="Localization/Dictionaries" ResourceVariant="zh-tw" />
|
||||
<Asset Guid="11143001bcbdc864b8d8fe2083142e5a" ResourceName="Entities" />
|
||||
<Asset Guid="1478894bc9a1ed241b05b0862a7b8bce" ResourceName="Textures" />
|
||||
<Asset Guid="14869ac0d4433f04db1704e39d03412e" ResourceName="Localization/Dictionaries" ResourceVariant="en-us" />
|
||||
<Asset Guid="156d241f796508c4da4fc354a7fbf5a8" ResourceName="UI/UISprites/Common" />
|
||||
|
|
@ -83,6 +85,7 @@
|
|||
<Asset Guid="4473d81b14ddb0143addf0e6050d8491" ResourceName="Scenes" />
|
||||
<Asset Guid="44c8db52241385c45bbb14a1718f17bf" ResourceName="Configs" />
|
||||
<Asset Guid="44cfa1c448225554c961ad6eb667d80b" ResourceName="DataTables" />
|
||||
<Asset Guid="47a82ffa13c291447ab895cd0bc251cd" ResourceName="Scripts" />
|
||||
<Asset Guid="4c3865b2ac420cd46a9cde6ab468d016" ResourceName="Materials" />
|
||||
<Asset Guid="4ca22ae3bc068c84eb7858d5b9bdf3e2" ResourceName="Fonts" />
|
||||
<Asset Guid="4f688097e85071841a2c3ba165000c20" ResourceName="Textures" />
|
||||
|
|
@ -94,6 +97,7 @@
|
|||
<Asset Guid="5b5a6a737c460eb4abc105d6583d405e" ResourceName="Fonts" />
|
||||
<Asset Guid="5dcd89912e222bf4c87f76db4044bc5e" ResourceName="Localization/Dictionaries" ResourceVariant="ko-kr" />
|
||||
<Asset Guid="5ebb46af6f16ae94e87f64a7dc0a49cb" ResourceName="Entities" />
|
||||
<Asset Guid="602d791ab1251f74ca2470c53bf382a3" ResourceName="Scripts" />
|
||||
<Asset Guid="62af9e5c8f39cfa49af9e10ccf42f1da" ResourceName="UI/UISprites/Common" />
|
||||
<Asset Guid="638ff8ae4a0d15047839cd265d3bc296" ResourceName="Music/Background" />
|
||||
<Asset Guid="63fe6ff9ab9e1433f8db4ebd940f2442" ResourceName="Materials" />
|
||||
|
|
@ -133,6 +137,7 @@
|
|||
<Asset Guid="99d811b0183246646a2ce8df996f4bca" ResourceName="Fonts" />
|
||||
<Asset Guid="9afa958d6d8235941b9badb42aae4370" ResourceName="Meshes" />
|
||||
<Asset Guid="9be2e1e45f4edd74c8764538ad306b78" ResourceName="Localization/Dictionaries" ResourceVariant="zh-cn" />
|
||||
<Asset Guid="9d193ac5b4294e0e9ba6e867320944b7" ResourceName="Entities" />
|
||||
<Asset Guid="9ddab293e2a8af3499dac05f5fd6169c" ResourceName="Meshes" />
|
||||
<Asset Guid="9f5bba6d2f5c95049a59fcb56df2d38f" ResourceName="UI/UIItems" />
|
||||
<Asset Guid="9f847ec5e66e03e4ead1d3c5f7b510e8" ResourceName="UI/UISprites/Common" />
|
||||
|
|
@ -157,6 +162,7 @@
|
|||
<Asset Guid="ba157ba55f72c424a9e88f3c029997c4" ResourceName="Textures" />
|
||||
<Asset Guid="baedbbad82997f445a8cb4da210404e0" ResourceName="Meshes" />
|
||||
<Asset Guid="bbfd75fe6fe00e1448fe988173ede7f9" ResourceName="UI/UIForms" />
|
||||
<Asset Guid="bc065bcf1474d7d4387fafd202678c37" ResourceName="Fonts" />
|
||||
<Asset Guid="bf75b984df8a84987bcf3a8bf6e2862d" ResourceName="Sounds" />
|
||||
<Asset Guid="c40be3174f62c4acf8c1216858c64956" ResourceName="URPAssets" />
|
||||
<Asset Guid="c49cffd4fc1dfb549b2b30448a0becda" ResourceName="UI/UISprites/Icons" />
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
# 敌人基础属性表
|
||||
# Id EntityTypeId MaxHealth HpAddPerLevel Speed CoinDrop ExpDrop DropPercent
|
||||
# int int int int float int int float
|
||||
# 敌人编号 策划备注 敌人实体编号 最大生命 每关卡增加生命 移动速度 金币掉落 经验掉落 掉落概率
|
||||
1 近战敌人 101 50 50 3 5 1 0.3
|
||||
# 敌人基础属性表
|
||||
# Id EntityTypeId MaxHealth HpAddPerLevel AttackDamage AttackCooldown AttackRange Speed CoinDrop ExpDrop DropPercent Params
|
||||
# int int int int int float float float int int float string
|
||||
# 敌人编号 策划备注 敌人实体编号 最大生命 每关卡增加生命 基础伤害 攻击间隔 攻击范围 移动速度 金币掉落 经验掉落 掉落概率 额外参数
|
||||
1 近战敌人 101 50 50 1 1 1.5 3 5 1 0.3 []
|
||||
2 远程敌人 102 40 40 1 2 8 2.5 4 2 0.2 []
|
||||
|
|
|
|||
|
|
@ -2,12 +2,14 @@
|
|||
# Id AssetName
|
||||
# int string
|
||||
# 实体编号 策划备注 资源名称
|
||||
11 跟随相机 FollowCamera
|
||||
1001 测试玩家 Player
|
||||
101 近战敌人 MeleeEnemy
|
||||
102 远程敌人 RemoteEnemy
|
||||
11 跟随相机 FollowCamera
|
||||
201 武器小刀 WeaponKnife
|
||||
202 武器手枪 WeaponHandgun
|
||||
203 武器斧头 WeaponSlash
|
||||
204 武器闪电 WeaponLightning
|
||||
205 武器长枪 WeaponLance
|
||||
10001 金币实体 CoinEntity
|
||||
10002 经验实体 ExpEntity
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
# 商品表
|
||||
# Id GoodsType GoodsTypeId
|
||||
# Id 列1 GoodsType GoodsTypeId
|
||||
# int GoodsType int
|
||||
# 商品编号 策划备注 商品类型 商品对应物品Id
|
||||
101 道具:药 Prop 101
|
||||
|
|
@ -25,3 +24,5 @@
|
|||
121 Prop 119
|
||||
122 Prop 120
|
||||
123 Weapon 3
|
||||
124 Weapon 4
|
||||
125 Weapon 5
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
# Id EnemyTypes EntityCounts Interval Duration
|
||||
# int int[] int[] float[] int
|
||||
# 关卡号 策划备注 敌人类型 每次出怪数量 每次出怪间隔 关卡时间
|
||||
1 第一关 [1] [5] [2] 60
|
||||
1 第一关 [1,2] [5,2] [4,5] 60
|
||||
2 第二关 [1] [10] [3] 60
|
||||
3 第三关 [1] [10] [3] 60
|
||||
4 第四关 [1] [10] [3] 60
|
||||
|
|
|
|||
|
|
@ -1,7 +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] []
|
||||
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} []
|
||||
5 205 长枪 Almighty_Icon White 100 0.1 100 1.5 5 10000 {"hitHalfWidth":0.7,"pierceLength":4.5,"hitHeight":0.5,"hitCenterYOffset":0}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ GameObject:
|
|||
- component: {fileID: 7683855655592166216}
|
||||
- component: {fileID: 6418687210998749921}
|
||||
- component: {fileID: 4710806460657047075}
|
||||
- component: {fileID: 8116679074104541426}
|
||||
- component: {fileID: 1932268889601128120}
|
||||
- component: {fileID: 557030043145096197}
|
||||
- component: {fileID: 6353753365317756414}
|
||||
|
|
@ -87,33 +86,6 @@ MeshRenderer:
|
|||
m_SortingLayer: 0
|
||||
m_SortingOrder: 0
|
||||
m_AdditionalVertexStreams: {fileID: 0}
|
||||
--- !u!54 &8116679074104541426
|
||||
Rigidbody:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 9166462022471897675}
|
||||
serializedVersion: 4
|
||||
m_Mass: 1
|
||||
m_Drag: 0
|
||||
m_AngularDrag: 0.05
|
||||
m_CenterOfMass: {x: 0, y: 0, z: 0}
|
||||
m_InertiaTensor: {x: 1, y: 1, z: 1}
|
||||
m_InertiaRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_IncludeLayers:
|
||||
serializedVersion: 2
|
||||
m_Bits: 0
|
||||
m_ExcludeLayers:
|
||||
serializedVersion: 2
|
||||
m_Bits: 5376
|
||||
m_ImplicitCom: 1
|
||||
m_ImplicitTensor: 1
|
||||
m_UseGravity: 0
|
||||
m_IsKinematic: 1
|
||||
m_Interpolate: 0
|
||||
m_Constraints: 0
|
||||
m_CollisionDetection: 0
|
||||
--- !u!136 &1932268889601128120
|
||||
CapsuleCollider:
|
||||
m_ObjectHideFlags: 0
|
||||
|
|
@ -131,7 +103,7 @@ CapsuleCollider:
|
|||
m_LayerOverridePriority: 0
|
||||
m_IsTrigger: 1
|
||||
m_ProvidesContacts: 0
|
||||
m_Enabled: 1
|
||||
m_Enabled: 0
|
||||
serializedVersion: 2
|
||||
m_Radius: 0.5
|
||||
m_Height: 2
|
||||
|
|
@ -154,7 +126,7 @@ MonoBehaviour:
|
|||
_cachedTransform: {fileID: 7683855655592166216}
|
||||
_avoidEnemyOverlap: 0
|
||||
_enemyBodyRadius: 0.45
|
||||
_separationIterations: 2
|
||||
_separationIterations: 5
|
||||
_speedBase: 0
|
||||
--- !u!114 &6353753365317756414
|
||||
MonoBehaviour:
|
||||
|
|
|
|||
|
|
@ -150,13 +150,13 @@ Transform:
|
|||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 5383497626468778460}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: 0.7071068, y: 0, z: 0, w: 0.7071068}
|
||||
m_LocalPosition: {x: 0, y: 15, z: 0}
|
||||
m_LocalRotation: {x: 0.5, y: 0, z: 0, w: 0.8660254}
|
||||
m_LocalPosition: {x: 0, y: 15, z: -10}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 9112716898534404901}
|
||||
m_LocalEulerAnglesHint: {x: 90, y: 0, z: 0}
|
||||
m_LocalEulerAnglesHint: {x: 60, y: 0, z: 0}
|
||||
--- !u!20 &4064848608618185461
|
||||
Camera:
|
||||
m_ObjectHideFlags: 0
|
||||
|
|
@ -189,9 +189,9 @@ Camera:
|
|||
width: 1
|
||||
height: 1
|
||||
near clip plane: 0.3
|
||||
far clip plane: 100
|
||||
far clip plane: 200
|
||||
field of view: 80
|
||||
orthographic: 1
|
||||
orthographic: 0
|
||||
orthographic size: 15
|
||||
m_Depth: 0
|
||||
m_CullingMask:
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ GameObject:
|
|||
- component: {fileID: 1932268889601128120}
|
||||
- component: {fileID: 557030043145096197}
|
||||
- component: {fileID: 6353753365317756414}
|
||||
m_Layer: 7
|
||||
m_Layer: 8
|
||||
m_Name: RemoteEnemy
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
|
|
@ -103,7 +103,7 @@ CapsuleCollider:
|
|||
m_LayerOverridePriority: 0
|
||||
m_IsTrigger: 0
|
||||
m_ProvidesContacts: 0
|
||||
m_Enabled: 1
|
||||
m_Enabled: 0
|
||||
serializedVersion: 2
|
||||
m_Radius: 0.5
|
||||
m_Height: 2
|
||||
|
|
@ -124,6 +124,9 @@ MonoBehaviour:
|
|||
_isMoving: 0
|
||||
_direction: {x: 0, y: 0, z: 0}
|
||||
_cachedTransform: {fileID: 0}
|
||||
_avoidEnemyOverlap: 0
|
||||
_enemyBodyRadius: 0.45
|
||||
_separationIterations: 2
|
||||
_speedBase: 0
|
||||
--- !u!114 &6353753365317756414
|
||||
MonoBehaviour:
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 9b0d24fd3a44b6f45b3794cdfefd1ac0
|
||||
DefaultImporter:
|
||||
guid: 11143001bcbdc864b8d8fe2083142e5a
|
||||
PrefabImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
|
|
@ -0,0 +1,141 @@
|
|||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!1 &3331174537915643484
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 5009597865007941286}
|
||||
- component: {fileID: 2357377495634329419}
|
||||
- component: {fileID: 5508018305229695465}
|
||||
- component: {fileID: 6560911649159431343}
|
||||
m_Layer: 11
|
||||
m_Name: Capsule
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!4 &5009597865007941286
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 3331174537915643484}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 0.4, y: 0.5, z: 0.4}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 1074967493716089666}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!33 &2357377495634329419
|
||||
MeshFilter:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 3331174537915643484}
|
||||
m_Mesh: {fileID: 10208, guid: 0000000000000000e000000000000000, type: 0}
|
||||
--- !u!23 &5508018305229695465
|
||||
MeshRenderer:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 3331174537915643484}
|
||||
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: 429ed03405bf8854eab46552b7470ac0, 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 &6560911649159431343
|
||||
CapsuleCollider:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 3331174537915643484}
|
||||
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.5
|
||||
m_Height: 2
|
||||
m_Direction: 1
|
||||
m_Center: {x: 0, y: 0, z: 0}
|
||||
--- !u!1 &4668848878531932975
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 1074967493716089666}
|
||||
m_Layer: 11
|
||||
m_Name: WeaponLightning
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!4 &1074967493716089666
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 4668848878531932975}
|
||||
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: 5009597865007941286}
|
||||
m_Father: {fileID: 0}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 9d193ac5b4294e0e9ba6e867320944b7
|
||||
PrefabImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,84 @@
|
|||
float2 UnpackUV(float uv)
|
||||
{
|
||||
float2 output;
|
||||
output.x = floor(uv / 4096);
|
||||
output.y = uv - 4096 * output.x;
|
||||
|
||||
return output * 0.001953125;
|
||||
}
|
||||
|
||||
fixed4 GetColor(half d, fixed4 faceColor, fixed4 outlineColor, half outline, half softness)
|
||||
{
|
||||
half faceAlpha = 1-saturate((d - outline * 0.5 + softness * 0.5) / (1.0 + softness));
|
||||
half outlineAlpha = saturate((d + outline * 0.5)) * sqrt(min(1.0, outline));
|
||||
|
||||
faceColor.rgb *= faceColor.a;
|
||||
outlineColor.rgb *= outlineColor.a;
|
||||
|
||||
faceColor = lerp(faceColor, outlineColor, outlineAlpha);
|
||||
|
||||
faceColor *= faceAlpha;
|
||||
|
||||
return faceColor;
|
||||
}
|
||||
|
||||
float3 GetSurfaceNormal(float4 h, float bias)
|
||||
{
|
||||
bool raisedBevel = step(1, fmod(_ShaderFlags, 2));
|
||||
|
||||
h += bias+_BevelOffset;
|
||||
|
||||
float bevelWidth = max(.01, _OutlineWidth+_BevelWidth);
|
||||
|
||||
// Track outline
|
||||
h -= .5;
|
||||
h /= bevelWidth;
|
||||
h = saturate(h+.5);
|
||||
|
||||
if(raisedBevel) h = 1 - abs(h*2.0 - 1.0);
|
||||
h = lerp(h, sin(h*3.141592/2.0), _BevelRoundness);
|
||||
h = min(h, 1.0-_BevelClamp);
|
||||
h *= _Bevel * bevelWidth * _GradientScale * -2.0;
|
||||
|
||||
float3 va = normalize(float3(1.0, 0.0, h.y - h.x));
|
||||
float3 vb = normalize(float3(0.0, -1.0, h.w - h.z));
|
||||
|
||||
return cross(va, vb);
|
||||
}
|
||||
|
||||
float3 GetSurfaceNormal(float2 uv, float bias, float3 delta)
|
||||
{
|
||||
// Read "height field"
|
||||
float4 h = {tex2D(_MainTex, uv - delta.xz).a,
|
||||
tex2D(_MainTex, uv + delta.xz).a,
|
||||
tex2D(_MainTex, uv - delta.zy).a,
|
||||
tex2D(_MainTex, uv + delta.zy).a};
|
||||
|
||||
return GetSurfaceNormal(h, bias);
|
||||
}
|
||||
|
||||
float3 GetSpecular(float3 n, float3 l)
|
||||
{
|
||||
float spec = pow(max(0.0, dot(n, l)), _Reflectivity);
|
||||
return _SpecularColor.rgb * spec * _SpecularPower;
|
||||
}
|
||||
|
||||
float4 GetGlowColor(float d, float scale)
|
||||
{
|
||||
float glow = d - (_GlowOffset*_ScaleRatioB) * 0.5 * scale;
|
||||
float t = lerp(_GlowInner, (_GlowOuter * _ScaleRatioB), step(0.0, glow)) * 0.5 * scale;
|
||||
glow = saturate(abs(glow/(1.0 + t)));
|
||||
glow = 1.0-pow(glow, _GlowPower);
|
||||
glow *= sqrt(min(1.0, t)); // Fade off glow thinner than 1 screen pixel
|
||||
return float4(_GlowColor.rgb, saturate(_GlowColor.a * glow * 2));
|
||||
}
|
||||
|
||||
float4 BlendARGB(float4 overlying, float4 underlying)
|
||||
{
|
||||
overlying.rgb *= overlying.a;
|
||||
underlying.rgb *= underlying.a;
|
||||
float3 blended = overlying.rgb + ((1-overlying.a)*underlying.rgb);
|
||||
float alpha = underlying.a + (1-underlying.a)*overlying.a;
|
||||
return float4(blended, alpha);
|
||||
}
|
||||
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 0372088d74296e44c9eb9185a2d4021e
|
||||
DefaultImporter:
|
||||
guid: dba6242363c96c44eb6ec1e124d487e4
|
||||
ShaderIncludeImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
|
|
@ -0,0 +1,157 @@
|
|||
struct vertex_t {
|
||||
UNITY_VERTEX_INPUT_INSTANCE_ID
|
||||
float4 position : POSITION;
|
||||
float3 normal : NORMAL;
|
||||
float4 color : COLOR;
|
||||
float2 texcoord0 : TEXCOORD0;
|
||||
float2 texcoord1 : TEXCOORD1;
|
||||
};
|
||||
|
||||
struct pixel_t {
|
||||
UNITY_VERTEX_INPUT_INSTANCE_ID
|
||||
UNITY_VERTEX_OUTPUT_STEREO
|
||||
float4 position : SV_POSITION;
|
||||
float4 faceColor : COLOR;
|
||||
float4 outlineColor : COLOR1;
|
||||
float4 texcoord0 : TEXCOORD0;
|
||||
float4 param : TEXCOORD1; // weight, scaleRatio
|
||||
float2 mask : TEXCOORD2;
|
||||
#if (UNDERLAY_ON || UNDERLAY_INNER)
|
||||
float4 texcoord2 : TEXCOORD3;
|
||||
float4 underlayColor : COLOR2;
|
||||
#endif
|
||||
};
|
||||
|
||||
float4 SRGBToLinear(float4 rgba) {
|
||||
return float4(lerp(rgba.rgb / 12.92f, pow((rgba.rgb + 0.055f) / 1.055f, 2.4f), step(0.04045f, rgba.rgb)), rgba.a);
|
||||
}
|
||||
|
||||
pixel_t VertShader(vertex_t input)
|
||||
{
|
||||
pixel_t output;
|
||||
|
||||
UNITY_INITIALIZE_OUTPUT(pixel_t, output);
|
||||
UNITY_SETUP_INSTANCE_ID(input);
|
||||
UNITY_TRANSFER_INSTANCE_ID(input, output);
|
||||
UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(output);
|
||||
|
||||
float bold = step(input.texcoord1.y, 0);
|
||||
|
||||
float4 vert = input.position;
|
||||
vert.x += _VertexOffsetX;
|
||||
vert.y += _VertexOffsetY;
|
||||
|
||||
float4 vPosition = UnityObjectToClipPos(vert);
|
||||
|
||||
float weight = lerp(_WeightNormal, _WeightBold, bold) / 4.0;
|
||||
weight = (weight + _FaceDilate) * _ScaleRatioA * 0.5;
|
||||
|
||||
// Generate UV for the Masking Texture
|
||||
float4 clampedRect = clamp(_ClipRect, -2e10, 2e10);
|
||||
float2 maskUV = (vert.xy - clampedRect.xy) / (clampedRect.zw - clampedRect.xy);
|
||||
|
||||
float4 color = input.color;
|
||||
#if (FORCE_LINEAR && !UNITY_COLORSPACE_GAMMA)
|
||||
color = SRGBToLinear(input.color);
|
||||
#endif
|
||||
|
||||
float opacity = color.a;
|
||||
#if (UNDERLAY_ON | UNDERLAY_INNER)
|
||||
opacity = 1.0;
|
||||
#endif
|
||||
|
||||
float4 faceColor = float4(color.rgb, opacity) * _FaceColor;
|
||||
faceColor.rgb *= faceColor.a;
|
||||
|
||||
float4 outlineColor = _OutlineColor;
|
||||
outlineColor.a *= opacity;
|
||||
outlineColor.rgb *= outlineColor.a;
|
||||
|
||||
output.position = vPosition;
|
||||
output.faceColor = faceColor;
|
||||
output.outlineColor = outlineColor;
|
||||
output.texcoord0 = float4(input.texcoord0.xy, maskUV.xy);
|
||||
output.param = float4(0.5 - weight, 1.3333 * _GradientScale * (_Sharpness + 1) / _TextureWidth, _OutlineWidth * _ScaleRatioA * 0.5, 0);
|
||||
|
||||
float2 mask = float2(0, 0);
|
||||
#if UNITY_UI_CLIP_RECT
|
||||
mask = vert.xy * 2 - clampedRect.xy - clampedRect.zw;
|
||||
#endif
|
||||
output.mask = mask;
|
||||
|
||||
#if (UNDERLAY_ON || UNDERLAY_INNER)
|
||||
float4 underlayColor = _UnderlayColor;
|
||||
underlayColor.rgb *= underlayColor.a;
|
||||
|
||||
float x = -(_UnderlayOffsetX * _ScaleRatioC) * _GradientScale / _TextureWidth;
|
||||
float y = -(_UnderlayOffsetY * _ScaleRatioC) * _GradientScale / _TextureHeight;
|
||||
|
||||
output.texcoord2 = float4(input.texcoord0 + float2(x, y), input.color.a, 0);
|
||||
output.underlayColor = underlayColor;
|
||||
#endif
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
float4 PixShader(pixel_t input) : SV_Target
|
||||
{
|
||||
UNITY_SETUP_INSTANCE_ID(input);
|
||||
|
||||
float d = tex2D(_MainTex, input.texcoord0.xy).a;
|
||||
|
||||
float2 UV = input.texcoord0.xy;
|
||||
float scale = rsqrt(abs(ddx(UV.x) * ddy(UV.y) - ddy(UV.x) * ddx(UV.y))) * input.param.y;
|
||||
|
||||
#if (UNDERLAY_ON | UNDERLAY_INNER)
|
||||
float layerScale = scale;
|
||||
layerScale /= 1 + ((_UnderlaySoftness * _ScaleRatioC) * layerScale);
|
||||
float layerBias = input.param.x * layerScale - .5 - ((_UnderlayDilate * _ScaleRatioC) * .5 * layerScale);
|
||||
#endif
|
||||
|
||||
scale /= 1 + (_OutlineSoftness * _ScaleRatioA * scale);
|
||||
|
||||
float4 faceColor = input.faceColor * saturate((d - input.param.x) * scale + 0.5);
|
||||
|
||||
#ifdef OUTLINE_ON
|
||||
float4 outlineColor = lerp(input.faceColor, input.outlineColor, sqrt(min(1.0, input.param.z * scale * 2)));
|
||||
faceColor = lerp(outlineColor, input.faceColor, saturate((d - input.param.x - input.param.z) * scale + 0.5));
|
||||
faceColor *= saturate((d - input.param.x + input.param.z) * scale + 0.5);
|
||||
#endif
|
||||
|
||||
#if UNDERLAY_ON
|
||||
d = tex2D(_MainTex, input.texcoord2.xy).a * layerScale;
|
||||
faceColor += float4(_UnderlayColor.rgb * _UnderlayColor.a, _UnderlayColor.a) * saturate(d - layerBias) * (1 - faceColor.a);
|
||||
#endif
|
||||
|
||||
#if UNDERLAY_INNER
|
||||
float bias = input.param.x * scale - 0.5;
|
||||
float sd = saturate(d * scale - bias - input.param.z);
|
||||
d = tex2D(_MainTex, input.texcoord2.xy).a * layerScale;
|
||||
faceColor += float4(_UnderlayColor.rgb * _UnderlayColor.a, _UnderlayColor.a) * (1 - saturate(d - layerBias)) * sd * (1 - faceColor.a);
|
||||
#endif
|
||||
|
||||
#ifdef MASKING
|
||||
float a = abs(_MaskInverse - tex2D(_MaskTex, input.texcoord0.zw).a);
|
||||
float t = a + (1 - _MaskWipeControl) * _MaskEdgeSoftness - _MaskWipeControl;
|
||||
a = saturate(t / _MaskEdgeSoftness);
|
||||
faceColor.rgb = lerp(_MaskEdgeColor.rgb * faceColor.a, faceColor.rgb, a);
|
||||
faceColor *= a;
|
||||
#endif
|
||||
|
||||
// Alternative implementation to UnityGet2DClipping with support for softness
|
||||
#if UNITY_UI_CLIP_RECT
|
||||
float2 maskZW = 0.25 / (0.25 * half2(_MaskSoftnessX, _MaskSoftnessY) + (1 / scale));
|
||||
float2 m = saturate((_ClipRect.zw - _ClipRect.xy - abs(input.mask.xy)) * maskZW);
|
||||
faceColor *= m.x * m.y;
|
||||
#endif
|
||||
|
||||
#if (UNDERLAY_ON | UNDERLAY_INNER)
|
||||
faceColor *= input.texcoord2.z;
|
||||
#endif
|
||||
|
||||
#if UNITY_UI_ALPHACLIP
|
||||
clip(faceColor.a - 0.001);
|
||||
#endif
|
||||
|
||||
return faceColor;
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
fileFormatVersion: 2
|
||||
guid: a7d40db10aa401f4db624fec03b19854
|
||||
DefaultImporter:
|
||||
guid: d9a5c27d283a4e7429acad6face5485a
|
||||
ShaderIncludeImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
|
|
@ -0,0 +1,85 @@
|
|||
// UI Editable properties
|
||||
uniform sampler2D _FaceTex; // Alpha : Signed Distance
|
||||
uniform float _FaceUVSpeedX;
|
||||
uniform float _FaceUVSpeedY;
|
||||
uniform fixed4 _FaceColor; // RGBA : Color + Opacity
|
||||
uniform float _FaceDilate; // v[ 0, 1]
|
||||
uniform float _OutlineSoftness; // v[ 0, 1]
|
||||
|
||||
uniform sampler2D _OutlineTex; // RGBA : Color + Opacity
|
||||
uniform float _OutlineUVSpeedX;
|
||||
uniform float _OutlineUVSpeedY;
|
||||
uniform fixed4 _OutlineColor; // RGBA : Color + Opacity
|
||||
uniform float _OutlineWidth; // v[ 0, 1]
|
||||
|
||||
uniform float _Bevel; // v[ 0, 1]
|
||||
uniform float _BevelOffset; // v[-1, 1]
|
||||
uniform float _BevelWidth; // v[-1, 1]
|
||||
uniform float _BevelClamp; // v[ 0, 1]
|
||||
uniform float _BevelRoundness; // v[ 0, 1]
|
||||
|
||||
uniform sampler2D _BumpMap; // Normal map
|
||||
uniform float _BumpOutline; // v[ 0, 1]
|
||||
uniform float _BumpFace; // v[ 0, 1]
|
||||
|
||||
uniform samplerCUBE _Cube; // Cube / sphere map
|
||||
uniform fixed4 _ReflectFaceColor; // RGB intensity
|
||||
uniform fixed4 _ReflectOutlineColor;
|
||||
//uniform float _EnvTiltX; // v[-1, 1]
|
||||
//uniform float _EnvTiltY; // v[-1, 1]
|
||||
uniform float3 _EnvMatrixRotation;
|
||||
uniform float4x4 _EnvMatrix;
|
||||
|
||||
uniform fixed4 _SpecularColor; // RGB intensity
|
||||
uniform float _LightAngle; // v[ 0,Tau]
|
||||
uniform float _SpecularPower; // v[ 0, 1]
|
||||
uniform float _Reflectivity; // v[ 5, 15]
|
||||
uniform float _Diffuse; // v[ 0, 1]
|
||||
uniform float _Ambient; // v[ 0, 1]
|
||||
|
||||
uniform fixed4 _UnderlayColor; // RGBA : Color + Opacity
|
||||
uniform float _UnderlayOffsetX; // v[-1, 1]
|
||||
uniform float _UnderlayOffsetY; // v[-1, 1]
|
||||
uniform float _UnderlayDilate; // v[-1, 1]
|
||||
uniform float _UnderlaySoftness; // v[ 0, 1]
|
||||
|
||||
uniform fixed4 _GlowColor; // RGBA : Color + Intesity
|
||||
uniform float _GlowOffset; // v[-1, 1]
|
||||
uniform float _GlowOuter; // v[ 0, 1]
|
||||
uniform float _GlowInner; // v[ 0, 1]
|
||||
uniform float _GlowPower; // v[ 1, 1/(1+4*4)]
|
||||
|
||||
// API Editable properties
|
||||
uniform float _ShaderFlags;
|
||||
uniform float _WeightNormal;
|
||||
uniform float _WeightBold;
|
||||
|
||||
uniform float _ScaleRatioA;
|
||||
uniform float _ScaleRatioB;
|
||||
uniform float _ScaleRatioC;
|
||||
|
||||
uniform float _VertexOffsetX;
|
||||
uniform float _VertexOffsetY;
|
||||
|
||||
//uniform float _UseClipRect;
|
||||
uniform float _MaskID;
|
||||
uniform sampler2D _MaskTex;
|
||||
uniform float4 _MaskCoord;
|
||||
uniform float4 _ClipRect; // bottom left(x,y) : top right(z,w)
|
||||
//uniform float _MaskWipeControl;
|
||||
//uniform float _MaskEdgeSoftness;
|
||||
//uniform fixed4 _MaskEdgeColor;
|
||||
//uniform bool _MaskInverse;
|
||||
|
||||
uniform float _MaskSoftnessX;
|
||||
uniform float _MaskSoftnessY;
|
||||
|
||||
// Font Atlas properties
|
||||
uniform sampler2D _MainTex;
|
||||
uniform float _TextureWidth;
|
||||
uniform float _TextureHeight;
|
||||
uniform float _GradientScale;
|
||||
uniform float _ScaleX;
|
||||
uniform float _ScaleY;
|
||||
uniform float _PerspectiveFilter;
|
||||
uniform float _Sharpness;
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
fileFormatVersion: 2
|
||||
guid: a5e646a0b90b55940be09d61810d429d
|
||||
DefaultImporter:
|
||||
guid: 8218173e722cac14996fc86da8882fc8
|
||||
ShaderIncludeImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
|
|
@ -0,0 +1,101 @@
|
|||
void VertShader(inout appdata_full v, out Input data)
|
||||
{
|
||||
v.vertex.x += _VertexOffsetX;
|
||||
v.vertex.y += _VertexOffsetY;
|
||||
|
||||
UNITY_INITIALIZE_OUTPUT(Input, data);
|
||||
|
||||
float bold = step(v.texcoord1.y, 0);
|
||||
|
||||
// Generate normal for backface
|
||||
float3 view = ObjSpaceViewDir(v.vertex);
|
||||
v.normal *= sign(dot(v.normal, view));
|
||||
|
||||
#if USE_DERIVATIVE
|
||||
data.param.y = 1;
|
||||
#else
|
||||
float4 vert = v.vertex;
|
||||
float4 vPosition = UnityObjectToClipPos(vert);
|
||||
float2 pixelSize = vPosition.w;
|
||||
|
||||
pixelSize /= float2(_ScaleX, _ScaleY) * mul((float2x2)UNITY_MATRIX_P, _ScreenParams.xy);
|
||||
float scale = rsqrt(dot(pixelSize, pixelSize));
|
||||
scale *= abs(v.texcoord1.y) * _GradientScale * (_Sharpness + 1);
|
||||
scale = lerp(scale * (1 - _PerspectiveFilter), scale, abs(dot(UnityObjectToWorldNormal(v.normal.xyz), normalize(WorldSpaceViewDir(vert)))));
|
||||
data.param.y = scale;
|
||||
#endif
|
||||
|
||||
data.param.x = (lerp(_WeightNormal, _WeightBold, bold) / 4.0 + _FaceDilate) * _ScaleRatioA * 0.5; //
|
||||
|
||||
v.texcoord1.xy = UnpackUV(v.texcoord1.x);
|
||||
data.viewDirEnv = mul((float3x3)_EnvMatrix, WorldSpaceViewDir(v.vertex));
|
||||
}
|
||||
|
||||
void PixShader(Input input, inout SurfaceOutput o)
|
||||
{
|
||||
|
||||
#if USE_DERIVATIVE
|
||||
float2 pixelSize = float2(ddx(input.uv_MainTex.y), ddy(input.uv_MainTex.y));
|
||||
pixelSize *= _TextureWidth * .75;
|
||||
float scale = rsqrt(dot(pixelSize, pixelSize)) * _GradientScale * (_Sharpness + 1);
|
||||
#else
|
||||
float scale = input.param.y;
|
||||
#endif
|
||||
|
||||
// Signed distance
|
||||
float c = tex2D(_MainTex, input.uv_MainTex).a;
|
||||
float sd = (.5 - c - input.param.x) * scale + .5;
|
||||
float outline = _OutlineWidth*_ScaleRatioA * scale;
|
||||
float softness = _OutlineSoftness*_ScaleRatioA * scale;
|
||||
|
||||
// Color & Alpha
|
||||
float4 faceColor = _FaceColor;
|
||||
float4 outlineColor = _OutlineColor;
|
||||
faceColor *= input.color;
|
||||
outlineColor.a *= input.color.a;
|
||||
faceColor *= tex2D(_FaceTex, float2(input.uv2_FaceTex.x + _FaceUVSpeedX * _Time.y, input.uv2_FaceTex.y + _FaceUVSpeedY * _Time.y));
|
||||
outlineColor *= tex2D(_OutlineTex, float2(input.uv2_OutlineTex.x + _OutlineUVSpeedX * _Time.y, input.uv2_OutlineTex.y + _OutlineUVSpeedY * _Time.y));
|
||||
faceColor = GetColor(sd, faceColor, outlineColor, outline, softness);
|
||||
faceColor.rgb /= max(faceColor.a, 0.0001);
|
||||
|
||||
#if BEVEL_ON
|
||||
float3 delta = float3(1.0 / _TextureWidth, 1.0 / _TextureHeight, 0.0);
|
||||
|
||||
float4 smp4x = {tex2D(_MainTex, input.uv_MainTex - delta.xz).a,
|
||||
tex2D(_MainTex, input.uv_MainTex + delta.xz).a,
|
||||
tex2D(_MainTex, input.uv_MainTex - delta.zy).a,
|
||||
tex2D(_MainTex, input.uv_MainTex + delta.zy).a };
|
||||
|
||||
// Face Normal
|
||||
float3 n = GetSurfaceNormal(smp4x, input.param.x);
|
||||
|
||||
// Bumpmap
|
||||
float3 bump = UnpackNormal(tex2D(_BumpMap, input.uv2_FaceTex.xy)).xyz;
|
||||
bump *= lerp(_BumpFace, _BumpOutline, saturate(sd + outline * 0.5));
|
||||
bump = lerp(float3(0, 0, 1), bump, faceColor.a);
|
||||
n = normalize(n - bump);
|
||||
|
||||
// Cubemap reflection
|
||||
fixed4 reflcol = texCUBE(_Cube, reflect(input.viewDirEnv, mul((float3x3)unity_ObjectToWorld, n)));
|
||||
float3 emission = reflcol.rgb * lerp(_ReflectFaceColor.rgb, _ReflectOutlineColor.rgb, saturate(sd + outline * 0.5)) * faceColor.a;
|
||||
#else
|
||||
float3 n = float3(0, 0, -1);
|
||||
float3 emission = float3(0, 0, 0);
|
||||
#endif
|
||||
|
||||
#if GLOW_ON
|
||||
float4 glowColor = GetGlowColor(sd, scale);
|
||||
glowColor.a *= input.color.a;
|
||||
emission += glowColor.rgb*glowColor.a;
|
||||
faceColor = BlendARGB(glowColor, faceColor);
|
||||
faceColor.rgb /= max(faceColor.a, 0.0001);
|
||||
#endif
|
||||
|
||||
// Set Standard output structure
|
||||
o.Albedo = faceColor.rgb;
|
||||
o.Normal = -n;
|
||||
o.Emission = emission;
|
||||
o.Specular = lerp(_FaceShininess, _OutlineShininess, saturate(sd + outline * 0.5));
|
||||
o.Gloss = 1;
|
||||
o.Alpha = faceColor.a;
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 0511409892ffdb7409b52349d8ceceee
|
||||
ShaderIncludeImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -2,7 +2,6 @@ using System;
|
|||
using CustomUtility;
|
||||
using Definition.DataStruct;
|
||||
using Definition.Enum;
|
||||
using Unity.Profiling;
|
||||
using UnityEngine;
|
||||
using CustomDebugger;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using GameFramework.ObjectPool;
|
||||
using TMPro;
|
||||
|
|
@ -9,7 +10,7 @@ namespace CustomComponent
|
|||
{
|
||||
public class DamageTextComponent : GameFrameworkComponent
|
||||
{
|
||||
[SerializeField] private int _instancePoolCapacity = 32;
|
||||
[SerializeField] private int _instancePoolCapacity = 256;
|
||||
|
||||
[SerializeField] private string _poolName = "DamageTextItem";
|
||||
|
||||
|
|
@ -43,14 +44,20 @@ namespace CustomComponent
|
|||
|
||||
private DamageTextItem CreateDamageTextItem()
|
||||
{
|
||||
if (_activeDamageTextItems.Count == _instancePoolCapacity)
|
||||
{
|
||||
_instancePoolCapacity = Mathf.Min(_instancePoolCapacity * 2, 1024);
|
||||
_damageTextItemPool.Capacity = _instancePoolCapacity;
|
||||
}
|
||||
|
||||
DamageTextItemObject itemObject = _damageTextItemPool.Spawn();
|
||||
if (itemObject != null)
|
||||
{
|
||||
return (DamageTextItem)itemObject.Target;
|
||||
}
|
||||
|
||||
|
||||
GameObject itemGo = Instantiate(_damageTextItemPrefab, _instanceRoot, false);
|
||||
|
||||
|
||||
DamageTextItem item = itemGo.GetComponent<DamageTextItem>();
|
||||
_damageTextItemPool.Register(DamageTextItemObject.Create(item), true);
|
||||
return item;
|
||||
|
|
@ -63,5 +70,12 @@ namespace CustomComponent
|
|||
_activeDamageTextItems.Remove(item);
|
||||
_damageTextItemPool.Unspawn(item);
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
_activeDamageTextItems.Clear();
|
||||
_damageTextItemPool.Release();
|
||||
_damageTextItemPool = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,8 @@
|
|||
#if UNITY_EDITOR || DEVELOPMENT_BUILD
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Components;
|
||||
using CustomEvent;
|
||||
using DataTable;
|
||||
using Definition.DataStruct;
|
||||
using Entity;
|
||||
|
|
@ -19,8 +21,20 @@ namespace CustomComponent
|
|||
private const float MinSpawnRate = 0.1f;
|
||||
private const float CornerTapWindow = 0.6f;
|
||||
private const int RequiredCornerTapCount = 3;
|
||||
private const int DebugHealAmount = 200;
|
||||
|
||||
private Rect _windowRect = new Rect(20f, 60f, 460f, 620f);
|
||||
[Header("Window Content")]
|
||||
[SerializeField] private bool _showBuffSection = true;
|
||||
[SerializeField] private bool _showBattleOverview = true;
|
||||
[SerializeField] private bool _showCollisionStats = true;
|
||||
[SerializeField] private bool _showSpawnControls = true;
|
||||
[SerializeField] private bool _showBattleDurationControls = true;
|
||||
[SerializeField] private bool _showSeparationSolverControls = true;
|
||||
[SerializeField] private bool _showPlayerWeaponControls = true;
|
||||
[SerializeField] private bool _showPlayerHealthControls = true;
|
||||
[SerializeField] private bool _showTips = true;
|
||||
|
||||
private Rect _windowRect = new Rect(20f, 60f, 460f, 800f);
|
||||
private bool _isPanelVisible;
|
||||
private int _windowId;
|
||||
|
||||
|
|
@ -38,6 +52,7 @@ namespace CustomComponent
|
|||
|
||||
private int _cornerTapCount;
|
||||
private float _lastCornerTapTime = -10f;
|
||||
private bool _lockPlayerHealthToMax;
|
||||
|
||||
protected override void Awake()
|
||||
{
|
||||
|
|
@ -54,6 +69,10 @@ namespace CustomComponent
|
|||
}
|
||||
|
||||
HandleCornerTapGesture();
|
||||
if (_lockPlayerHealthToMax)
|
||||
{
|
||||
KeepPlayerHealthAtMax();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnGUI()
|
||||
|
|
@ -77,20 +96,42 @@ namespace CustomComponent
|
|||
|
||||
private void DrawWindow(int windowId)
|
||||
{
|
||||
EnsurePropList();
|
||||
if (_showBuffSection)
|
||||
{
|
||||
EnsurePropList();
|
||||
}
|
||||
|
||||
GUILayout.BeginVertical();
|
||||
|
||||
DrawBuffSection();
|
||||
bool hasPreviousSection = false;
|
||||
if (_showBuffSection)
|
||||
{
|
||||
DrawBuffSection();
|
||||
hasPreviousSection = true;
|
||||
}
|
||||
|
||||
GUILayout.Space(8f);
|
||||
GUILayout.Label(string.Empty, GUI.skin.horizontalSlider);
|
||||
GUILayout.Space(8f);
|
||||
if (HasVisibleBattleSection())
|
||||
{
|
||||
if (hasPreviousSection)
|
||||
{
|
||||
GUILayout.Space(8f);
|
||||
GUILayout.Label(string.Empty, GUI.skin.horizontalSlider);
|
||||
GUILayout.Space(8f);
|
||||
}
|
||||
|
||||
DrawBattleSection();
|
||||
DrawBattleSection();
|
||||
hasPreviousSection = true;
|
||||
}
|
||||
|
||||
GUILayout.Space(8f);
|
||||
GUILayout.Label("Tips: press `F8` or tap top-left corner 3 times to toggle.", GUILayout.Height(20f));
|
||||
if (_showTips)
|
||||
{
|
||||
if (hasPreviousSection)
|
||||
{
|
||||
GUILayout.Space(8f);
|
||||
}
|
||||
|
||||
GUILayout.Label("Tips: press `F8` or tap top-left corner 3 times to toggle.", GUILayout.Height(20f));
|
||||
}
|
||||
|
||||
GUILayout.EndVertical();
|
||||
GUI.DragWindow(new Rect(0, 0, 10000, 22));
|
||||
|
|
@ -147,6 +188,7 @@ namespace CustomComponent
|
|||
ProcedureGame procedure = GameEntry.Procedure.CurrentProcedure as ProcedureGame;
|
||||
EnemyManagerComponent enemyManager = GameEntry.EnemyManager;
|
||||
Player player = FindPlayer();
|
||||
HealthComponent playerHealth = player != null ? player.GetComponent<HealthComponent>() : null;
|
||||
|
||||
if (enemyManager == null)
|
||||
{
|
||||
|
|
@ -160,86 +202,160 @@ namespace CustomComponent
|
|||
return;
|
||||
}
|
||||
|
||||
GUILayout.Label($"Spawn Rate: {enemyManager.SpawnRateScale:F2}");
|
||||
GUILayout.Label($"Battle Time: {enemyManager.ElapsedBattleTime:F1}s / {enemyManager.BattleDuration:F1}s");
|
||||
GUILayout.Label($"Enemy Count: {enemyManager.CurrentEnemyCount}");
|
||||
|
||||
GUILayout.BeginHorizontal();
|
||||
GUILayout.Label("Rate", GUILayout.Width(52f));
|
||||
string rateText = GUILayout.TextField(_spawnRateScaleInput.ToString("F2"), GUILayout.Width(60f));
|
||||
if (float.TryParse(rateText, out float parsedRate))
|
||||
if (_showBattleOverview)
|
||||
{
|
||||
_spawnRateScaleInput = Mathf.Clamp(parsedRate, MinSpawnRate, 50f);
|
||||
GUILayout.Label($"Spawn Rate: {enemyManager.SpawnRateScale:F2}");
|
||||
GUILayout.Label($"Battle Time: {enemyManager.ElapsedBattleTime:F1}s / {enemyManager.BattleDuration:F1}s");
|
||||
GUILayout.Label($"Enemy Count: {enemyManager.CurrentEnemyCount}");
|
||||
}
|
||||
|
||||
if (GUILayout.Button("Apply", GUILayout.Width(70f)))
|
||||
Simulation.SimulationWorld simulationWorld = GameEntry.SimulationWorld;
|
||||
if (_showCollisionStats && simulationWorld != null)
|
||||
{
|
||||
enemyManager.SetSpawnRateScale(_spawnRateScaleInput);
|
||||
}
|
||||
|
||||
if (GUILayout.Button("x0.5", GUILayout.Width(60f)))
|
||||
{
|
||||
_spawnRateScaleInput = Mathf.Max(MinSpawnRate, enemyManager.SpawnRateScale * 0.5f);
|
||||
enemyManager.SetSpawnRateScale(_spawnRateScaleInput);
|
||||
}
|
||||
|
||||
if (GUILayout.Button("x2", GUILayout.Width(60f)))
|
||||
{
|
||||
_spawnRateScaleInput = enemyManager.SpawnRateScale * 2f;
|
||||
enemyManager.SetSpawnRateScale(_spawnRateScaleInput);
|
||||
}
|
||||
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUILayout.BeginHorizontal();
|
||||
GUILayout.Label("Add Sec", GUILayout.Width(52f));
|
||||
string durationText = GUILayout.TextField(_extendDurationSeconds.ToString("F0"), GUILayout.Width(60f));
|
||||
if (float.TryParse(durationText, out float parsedDuration))
|
||||
{
|
||||
_extendDurationSeconds = Mathf.Clamp(parsedDuration, 1f, 3600f);
|
||||
}
|
||||
|
||||
if (GUILayout.Button("Extend Battle", GUILayout.Height(24f)))
|
||||
{
|
||||
if (procedure.CurrentGameState is GameStateBattle gameState)
|
||||
GUILayout.Space(4f);
|
||||
GUILayout.Label(
|
||||
$"Collision Queries: total {simulationWorld.LastCollisionQueryCount} (Projectile {simulationWorld.LastProjectileCollisionQueryCount} / Area {simulationWorld.LastAreaCollisionQueryCount})");
|
||||
GUILayout.Label(
|
||||
$"Collision Candidates: total {simulationWorld.LastCollisionCandidateCount} (Projectile {simulationWorld.LastProjectileCollisionCandidateCount} / Area {simulationWorld.LastAreaCollisionCandidateCount})");
|
||||
GUILayout.Label(
|
||||
$"Area Resolve: hits {simulationWorld.LastResolvedAreaHitCount}");
|
||||
GUILayout.Label(
|
||||
$"Broad Phase: cell {simulationWorld.LastCollisionCellSize:F2}, hasEnemyTargets {(simulationWorld.LastCollisionHasEnemyTargets ? "Yes" : "No")}");
|
||||
if (simulationWorld.LastCollisionCandidateCount != 0)
|
||||
{
|
||||
gameState.AddBattleDuration(_extendDurationSeconds);
|
||||
Log.Info($"LastCollisionCandidateCount:{simulationWorld.LastCollisionCandidateCount}");
|
||||
}
|
||||
|
||||
if (simulationWorld.LastResolvedAreaHitCount != 0)
|
||||
{
|
||||
Log.Info($"LastResolvedAreaHitCount:{simulationWorld.LastResolvedAreaHitCount}");
|
||||
}
|
||||
}
|
||||
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUILayout.Space(4f);
|
||||
GUILayout.Label($"Enemy Separation Solver: {EnemySeparationSolverProvider.CurrentSolverName}");
|
||||
GUILayout.BeginHorizontal();
|
||||
if (GUILayout.Button("Use Naive O(N^2)", GUILayout.Height(24f)))
|
||||
if (_showSpawnControls)
|
||||
{
|
||||
EnemySeparationSolverProvider.UseNaiveSolver();
|
||||
GUILayout.BeginHorizontal();
|
||||
GUILayout.Label("Rate", GUILayout.Width(52f));
|
||||
string rateText = GUILayout.TextField(_spawnRateScaleInput.ToString("F2"), GUILayout.Width(60f));
|
||||
if (float.TryParse(rateText, out float parsedRate))
|
||||
{
|
||||
_spawnRateScaleInput = Mathf.Clamp(parsedRate, MinSpawnRate, 50f);
|
||||
}
|
||||
|
||||
if (GUILayout.Button("Apply", GUILayout.Width(70f)))
|
||||
{
|
||||
enemyManager.SetSpawnRateScale(_spawnRateScaleInput);
|
||||
}
|
||||
|
||||
if (GUILayout.Button("x0.5", GUILayout.Width(60f)))
|
||||
{
|
||||
_spawnRateScaleInput = Mathf.Max(MinSpawnRate, enemyManager.SpawnRateScale * 0.5f);
|
||||
enemyManager.SetSpawnRateScale(_spawnRateScaleInput);
|
||||
}
|
||||
|
||||
if (GUILayout.Button("x2", GUILayout.Width(60f)))
|
||||
{
|
||||
_spawnRateScaleInput = enemyManager.SpawnRateScale * 2f;
|
||||
enemyManager.SetSpawnRateScale(_spawnRateScaleInput);
|
||||
}
|
||||
|
||||
GUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
if (GUILayout.Button("Use Grid Bucket", GUILayout.Height(24f)))
|
||||
if (_showBattleDurationControls)
|
||||
{
|
||||
EnemySeparationSolverProvider.UseGridBucketSolver();
|
||||
GUILayout.BeginHorizontal();
|
||||
GUILayout.Label("Add Sec", GUILayout.Width(52f));
|
||||
string durationText = GUILayout.TextField(_extendDurationSeconds.ToString("F0"), GUILayout.Width(60f));
|
||||
if (float.TryParse(durationText, out float parsedDuration))
|
||||
{
|
||||
_extendDurationSeconds = Mathf.Clamp(parsedDuration, 1f, 3600f);
|
||||
}
|
||||
|
||||
if (GUILayout.Button("Extend Battle", GUILayout.Height(24f)))
|
||||
{
|
||||
if (procedure.CurrentGameState is GameStateBattle gameState)
|
||||
{
|
||||
gameState.AddBattleDuration(_extendDurationSeconds);
|
||||
}
|
||||
}
|
||||
|
||||
GUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUILayout.Label(
|
||||
$"Player Weapon: {(player == null ? "Player not found" : (player.WeaponEnabled ? "Enabled" : "Disabled"))}");
|
||||
GUILayout.BeginHorizontal();
|
||||
GUI.enabled = player != null;
|
||||
if (GUILayout.Button("Disable Weapons", GUILayout.Height(24f)))
|
||||
if (_showSeparationSolverControls)
|
||||
{
|
||||
player.SetWeaponEnabled(false);
|
||||
GUILayout.Space(4f);
|
||||
GUILayout.Label($"Enemy Separation Solver: {EnemySeparationSolverProvider.CurrentSolverName}");
|
||||
GUILayout.BeginHorizontal();
|
||||
if (GUILayout.Button("Use Naive O(N^2)", GUILayout.Height(24f)))
|
||||
{
|
||||
EnemySeparationSolverProvider.UseNaiveSolver();
|
||||
}
|
||||
|
||||
if (GUILayout.Button("Use Grid Bucket", GUILayout.Height(24f)))
|
||||
{
|
||||
EnemySeparationSolverProvider.UseGridBucketSolver();
|
||||
}
|
||||
|
||||
GUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
if (GUILayout.Button("Enable Weapons", GUILayout.Height(24f)))
|
||||
if (_showPlayerWeaponControls)
|
||||
{
|
||||
player.SetWeaponEnabled(true);
|
||||
GUILayout.Label(
|
||||
$"Player Weapon: {(player == null ? "Player not found" : (player.WeaponEnabled ? "Enabled" : "Disabled"))}");
|
||||
GUILayout.BeginHorizontal();
|
||||
GUI.enabled = player != null;
|
||||
if (GUILayout.Button("Disable Weapons", GUILayout.Height(24f)))
|
||||
{
|
||||
player.SetWeaponEnabled(false);
|
||||
}
|
||||
|
||||
if (GUILayout.Button("Enable Weapons", GUILayout.Height(24f)))
|
||||
{
|
||||
player.SetWeaponEnabled(true);
|
||||
}
|
||||
|
||||
GUI.enabled = true;
|
||||
GUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
GUI.enabled = true;
|
||||
GUILayout.EndHorizontal();
|
||||
if (_showPlayerHealthControls)
|
||||
{
|
||||
GUILayout.Space(4f);
|
||||
GUILayout.Label(
|
||||
$"Player HP: {(playerHealth == null ? "Unavailable" : $"{playerHealth.CurrentHealth}/{playerHealth.MaxHealth}")}");
|
||||
GUILayout.BeginHorizontal();
|
||||
GUI.enabled = playerHealth != null;
|
||||
if (GUILayout.Button($"+{DebugHealAmount} HP", GUILayout.Height(24f)))
|
||||
{
|
||||
AddPlayerHealth(playerHealth, DebugHealAmount);
|
||||
}
|
||||
|
||||
if (GUILayout.Button(_lockPlayerHealthToMax ? "GodMode: ON" : "GodMode: OFF", GUILayout.Height(24f)))
|
||||
{
|
||||
_lockPlayerHealthToMax = !_lockPlayerHealthToMax;
|
||||
if (_lockPlayerHealthToMax)
|
||||
{
|
||||
RestorePlayerHealthToMax(playerHealth);
|
||||
}
|
||||
}
|
||||
|
||||
GUI.enabled = true;
|
||||
GUILayout.EndHorizontal();
|
||||
}
|
||||
}
|
||||
|
||||
private bool HasVisibleBattleSection()
|
||||
{
|
||||
return _showBattleOverview ||
|
||||
_showCollisionStats ||
|
||||
_showSpawnControls ||
|
||||
_showBattleDurationControls ||
|
||||
_showSeparationSolverControls ||
|
||||
_showPlayerWeaponControls ||
|
||||
_showPlayerHealthControls;
|
||||
}
|
||||
|
||||
private void EnsurePropList(bool force = false)
|
||||
|
|
@ -299,6 +415,52 @@ namespace CustomComponent
|
|||
return UnityEngine.Object.FindObjectOfType<Player>();
|
||||
}
|
||||
|
||||
private void KeepPlayerHealthAtMax()
|
||||
{
|
||||
Player player = FindPlayer();
|
||||
if (player == null) return;
|
||||
|
||||
HealthComponent playerHealth = player.GetComponent<HealthComponent>();
|
||||
if (playerHealth == null) return;
|
||||
|
||||
RestorePlayerHealthToMax(playerHealth);
|
||||
}
|
||||
|
||||
private static void AddPlayerHealth(HealthComponent playerHealth, int amount)
|
||||
{
|
||||
if (playerHealth == null || amount <= 0) return;
|
||||
if (playerHealth.CurrentHealth <= 0) return;
|
||||
|
||||
int maxHealth = playerHealth.MaxHealth;
|
||||
if (maxHealth <= 0) return;
|
||||
|
||||
int nextHealth = Mathf.Clamp(playerHealth.CurrentHealth + amount, 0, maxHealth);
|
||||
if (nextHealth == playerHealth.CurrentHealth) return;
|
||||
|
||||
playerHealth.CurrentHealth = nextHealth;
|
||||
PublishPlayerHealthChanged(playerHealth);
|
||||
}
|
||||
|
||||
private static void RestorePlayerHealthToMax(HealthComponent playerHealth)
|
||||
{
|
||||
if (playerHealth == null) return;
|
||||
if (playerHealth.CurrentHealth <= 0) return;
|
||||
|
||||
int maxHealth = playerHealth.MaxHealth;
|
||||
if (maxHealth <= 0 || playerHealth.CurrentHealth >= maxHealth) return;
|
||||
|
||||
playerHealth.CurrentHealth = maxHealth;
|
||||
PublishPlayerHealthChanged(playerHealth);
|
||||
}
|
||||
|
||||
private static void PublishPlayerHealthChanged(HealthComponent playerHealth)
|
||||
{
|
||||
if (playerHealth == null || GameEntry.Event == null) return;
|
||||
|
||||
GameEntry.Event.Fire(null,
|
||||
PlayerHealthChangeEventArgs.Create(0, playerHealth.CurrentHealth, playerHealth.MaxHealth));
|
||||
}
|
||||
|
||||
private static void AddSelectedBuffToPlayer(DRProp prop, int count)
|
||||
{
|
||||
Player player = FindPlayer();
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ namespace CustomComponent
|
|||
private EntityComponent _entity;
|
||||
|
||||
private List<EntityBase> _enemies;
|
||||
private Dictionary<int, EntityBase> _enemyById;
|
||||
|
||||
public List<EntityBase> Enemies => _enemies;
|
||||
|
||||
|
|
@ -58,6 +59,7 @@ namespace CustomComponent
|
|||
{
|
||||
_entity = GameEntry.Entity;
|
||||
_enemies = new List<EntityBase>();
|
||||
_enemyById = new Dictionary<int, EntityBase>();
|
||||
|
||||
GameEntry.Event.Subscribe(ShowEntitySuccessEventArgs.EventId, OnShowEntitySuccess);
|
||||
GameEntry.Event.Subscribe(HideEntityCompleteEventArgs.EventId, OnHideEntityComplete);
|
||||
|
|
@ -65,10 +67,14 @@ namespace CustomComponent
|
|||
|
||||
private void OnDestroy()
|
||||
{
|
||||
GameEntry.Event.Unsubscribe(ShowEntitySuccessEventArgs.EventId, OnShowEntitySuccess);
|
||||
GameEntry.Event.Unsubscribe(HideEntityCompleteEventArgs.EventId, OnHideEntityComplete);
|
||||
if (GameEntry.Event != null)
|
||||
{
|
||||
GameEntry.Event.Unsubscribe(ShowEntitySuccessEventArgs.EventId, OnShowEntitySuccess);
|
||||
GameEntry.Event.Unsubscribe(HideEntityCompleteEventArgs.EventId, OnHideEntityComplete);
|
||||
}
|
||||
|
||||
_enemies = null;
|
||||
_enemyById = null;
|
||||
_entity = null;
|
||||
}
|
||||
|
||||
|
|
@ -157,6 +163,25 @@ namespace CustomComponent
|
|||
}
|
||||
|
||||
_enemies.Clear();
|
||||
_enemyById?.Clear();
|
||||
}
|
||||
|
||||
public bool TryGetEnemy(int entityId, out EntityBase enemy)
|
||||
{
|
||||
enemy = null;
|
||||
if (_enemyById == null || !_enemyById.TryGetValue(entityId, out EntityBase cachedEnemy))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (cachedEnemy == null || !cachedEnemy.Available)
|
||||
{
|
||||
_enemyById.Remove(entityId);
|
||||
return false;
|
||||
}
|
||||
|
||||
enemy = cachedEnemy;
|
||||
return true;
|
||||
}
|
||||
|
||||
public void SetSpawnRateScale(float scale)
|
||||
|
|
@ -218,6 +243,7 @@ namespace CustomComponent
|
|||
enemy.SetTarget(_player);
|
||||
RemoveEnemyFromCache(enemy.Id);
|
||||
_enemies.Add(enemy);
|
||||
_enemyById[enemy.Id] = enemy;
|
||||
}
|
||||
|
||||
if (ne.EntityLogicType == typeof(Player))
|
||||
|
|
@ -245,11 +271,21 @@ namespace CustomComponent
|
|||
|
||||
private void RemoveEnemyFromCache(int entityId)
|
||||
{
|
||||
if (_enemyById != null)
|
||||
{
|
||||
_enemyById.Remove(entityId);
|
||||
}
|
||||
|
||||
for (int i = _enemies.Count - 1; i >= 0; i--)
|
||||
{
|
||||
EntityBase cachedEnemy = _enemies[i];
|
||||
if (cachedEnemy == null || cachedEnemy.Id == entityId)
|
||||
{
|
||||
if (cachedEnemy != null && _enemyById != null)
|
||||
{
|
||||
_enemyById.Remove(cachedEnemy.Id);
|
||||
}
|
||||
|
||||
_enemies.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityGameFramework.Runtime;
|
||||
|
||||
namespace DataTable
|
||||
|
|
@ -5,23 +7,31 @@ namespace DataTable
|
|||
public class DREnemy : DataRowBase
|
||||
{
|
||||
private int m_id;
|
||||
|
||||
|
||||
public override int Id => m_id;
|
||||
|
||||
|
||||
public int EntityTypeId { get; private set; }
|
||||
|
||||
|
||||
public int MaxHealth { get; private set; }
|
||||
|
||||
|
||||
public int HpAddPerLevel { get; private set; }
|
||||
|
||||
|
||||
public int AttackDamage { get; private set; }
|
||||
|
||||
public float AttackCooldown { get; private set; }
|
||||
|
||||
public float AttackRange { get; private set; }
|
||||
|
||||
public float Speed { get; private set; }
|
||||
|
||||
|
||||
public int DropCoin { get; private set; }
|
||||
|
||||
|
||||
public int DropExp { get; private set; }
|
||||
|
||||
|
||||
public float DropPercent { get; private set; }
|
||||
|
||||
public Dictionary<string, string> Params { get; private set; }
|
||||
|
||||
public override bool ParseDataRow(string dataRowString, object userData)
|
||||
{
|
||||
string[] columnStrings = dataRowString.Split(DataTableExtension.DataSplitSeparators);
|
||||
|
|
@ -33,12 +43,47 @@ namespace DataTable
|
|||
EntityTypeId = int.Parse(columnStrings[index++]);
|
||||
MaxHealth = int.Parse(columnStrings[index++]);
|
||||
HpAddPerLevel = int.Parse(columnStrings[index++]);
|
||||
AttackDamage = int.Parse(columnStrings[index++]);
|
||||
AttackCooldown = float.Parse(columnStrings[index++]);
|
||||
AttackRange = float.Parse(columnStrings[index++]);
|
||||
Speed = float.Parse(columnStrings[index++]);
|
||||
DropCoin = int.Parse(columnStrings[index++]);
|
||||
DropExp = int.Parse(columnStrings[index++]);
|
||||
DropPercent = float.Parse(columnStrings[index++]);
|
||||
Params = DeserializeParams(columnStrings[index++]);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 解参数
|
||||
/// </summary>
|
||||
/// <param name="rawParams"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="ArgumentException"></exception>
|
||||
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>();
|
||||
|
||||
if (string.IsNullOrEmpty(rawParams)) return dict;
|
||||
|
||||
string[] items = rawParams.Substring(1, rawParams.Length - 2).Split(";");
|
||||
foreach (var item in items)
|
||||
{
|
||||
string entry = item.Trim();
|
||||
if (string.IsNullOrEmpty(entry)) continue;
|
||||
|
||||
string[] pair = entry.Split(':', StringSplitOptions.RemoveEmptyEntries);
|
||||
if (pair.Length != 2) continue;
|
||||
dict.Add(pair[0].ToLower(), pair[1]);
|
||||
}
|
||||
|
||||
return dict;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
|||
/// </summary>
|
||||
public Dictionary<string, string> Pramas { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取武器额外参数 Json。
|
||||
/// </summary>
|
||||
public string ParamsJson { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取武器额外属性。
|
||||
/// </summary>
|
||||
|
|
@ -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<StatModifier[]>(columnStrings[index++]);
|
||||
|
||||
GeneratePropertyArray();
|
||||
|
|
@ -109,29 +115,43 @@ namespace DataTable
|
|||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 解参数
|
||||
/// </summary>
|
||||
/// <param name="rawParams"></param>
|
||||
/// <returns></returns>
|
||||
private Dictionary<string, string> DeserializeParams(string rawParams)
|
||||
{
|
||||
if (!rawParams.StartsWith('[') || !rawParams.EndsWith(']'))
|
||||
var dict = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
if (string.IsNullOrWhiteSpace(rawParams))
|
||||
{
|
||||
throw new ArgumentException("Input must be enclosed in square brackets.");
|
||||
return dict;
|
||||
}
|
||||
|
||||
var dict = new Dictionary<string, string>();
|
||||
|
||||
if (string.IsNullOrEmpty(rawParams)) return dict;
|
||||
|
||||
string[] items = rawParams.Substring(1, rawParams.Length - 2).Split(";");
|
||||
foreach (var item in items)
|
||||
try
|
||||
{
|
||||
string entry = item.Trim();
|
||||
if (string.IsNullOrEmpty(entry)) continue;
|
||||
JObject paramObject = Utility.Json.ToObject<JObject>(rawParams);
|
||||
if (paramObject == null)
|
||||
{
|
||||
return dict;
|
||||
}
|
||||
|
||||
string[] pair = entry.Split(':' , StringSplitOptions.RemoveEmptyEntries);
|
||||
if (pair.Length != 2) continue;
|
||||
dict.Add(pair[0].ToLower(), pair[1]);
|
||||
foreach (var pair in paramObject)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(pair.Key) || pair.Value == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
dict[pair.Key] = pair.Value.ToString();
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Log.Warning("Failed to parse weapon params json '{0}'. Error: {1}", rawParams, exception.Message);
|
||||
}
|
||||
|
||||
return dict;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,13 +4,26 @@ namespace CustomDebugger
|
|||
{
|
||||
public static class CustomProfilerMarker
|
||||
{
|
||||
public static readonly ProfilerMarker TickEnemies = new ProfilerMarker("TickEnemies");
|
||||
public static readonly ProfilerMarker TickEnemies_BuildInput = new ProfilerMarker("TickEnemies.BuildInput");
|
||||
public static readonly ProfilerMarker TickEnemies_MoveSeparation = new ProfilerMarker("TickEnemies.MoveSeparation");
|
||||
public static readonly ProfilerMarker TickEnemies_StateUpdate = new ProfilerMarker("TickEnemies.StateUpdate");
|
||||
public static readonly ProfilerMarker TickEnemies_WriteBack = new ProfilerMarker("TickEnemies.WriteBack");
|
||||
public static readonly ProfilerMarker Movement_Update = new ProfilerMarker("Movement_Update");
|
||||
public static readonly ProfilerMarker TickEnemies = new("TickEnemies");
|
||||
public static readonly ProfilerMarker TickEnemies_BuildInput = new("TickEnemies.BuildInput");
|
||||
public static readonly ProfilerMarker TickEnemies_StateUpdate = new("TickEnemies.StateUpdate");
|
||||
public static readonly ProfilerMarker TickEnemies_Schedule = new("TickEnemies.Schedule");
|
||||
public static readonly ProfilerMarker TickEnemies_Complete = new("TickEnemies.Complete");
|
||||
public static readonly ProfilerMarker TickEnemies_MainThreadCommit = new("TickEnemies.MainThreadCommit");
|
||||
public static readonly ProfilerMarker TickEnemies_WriteBack = new("TickEnemies.WriteBack");
|
||||
|
||||
public static readonly ProfilerMarker Collision = new("Collision");
|
||||
public static readonly ProfilerMarker Collision_BuildQueries = new("Collision.BuildQueries");
|
||||
public static readonly ProfilerMarker Collision_BuildBuckets = new("Collision.BuildBuckets");
|
||||
public static readonly ProfilerMarker Collision_QueryCandidates = new("Collision.QueryCandidates");
|
||||
public static readonly ProfilerMarker Collision_ResolveProjectile = new("Collision.ResolveProjectile");
|
||||
public static readonly ProfilerMarker Collision_ResolveArea = new("Collision.ResolveArea");
|
||||
|
||||
public static readonly ProfilerMarker TargetSelection_BuildBuckets = new("TargetSelection.BuildBuckets");
|
||||
public static readonly ProfilerMarker TargetSelection_QueryNeighbors = new("TargetSelection.QueryNeighbors");
|
||||
|
||||
public static readonly ProfilerMarker Movement_Update = new("Movement_Update");
|
||||
public static readonly ProfilerMarker ShopUI_Update = new("UGF.ShopUI.Update");
|
||||
public static readonly ProfilerMarker Inventory_Refresh = new("UGF.Inventory.Refresh");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -6,5 +6,7 @@ namespace Definition.Enum
|
|||
WeaponKnife = 1,
|
||||
WeaponHandgun = 2,
|
||||
WeaponSlash = 3,
|
||||
WeaponLightning = 4,
|
||||
WeaponLance = 5,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"name": "VampireLike.Editor",
|
||||
"rootNamespace": "",
|
||||
"references": [
|
||||
"VampireLike",
|
||||
"UnityGameFramework.Runtime",
|
||||
"UnityGameFramework.Editor"
|
||||
],
|
||||
"includePlatforms": [
|
||||
"Editor"
|
||||
],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": false
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 602d791ab1251f74ca2470c53bf382a3
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -1,19 +1,12 @@
|
|||
//------------------------------------------------------------
|
||||
// Game Framework
|
||||
// Copyright © 2013-2021 Jiang Yin. All rights reserved.
|
||||
// Homepage: https://gameframework.cn/
|
||||
// Feedback: mailto:ellan@gameframework.cn
|
||||
//------------------------------------------------------------
|
||||
|
||||
using GameFramework;
|
||||
using GameFramework;
|
||||
using System.IO;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using UnityGameFramework.Editor.ResourceTools;
|
||||
|
||||
namespace StarForce.Editor
|
||||
namespace VampireLike.Editor
|
||||
{
|
||||
public sealed class StarForceBuildEventHandler : IBuildEventHandler
|
||||
public sealed class VampireLikeBuildEventHandler : IBuildEventHandler
|
||||
{
|
||||
public bool ContinueOnFailure
|
||||
{
|
||||
|
|
@ -1,8 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 64311189c3f9ae140b59a31db9831950
|
||||
timeCreated: 1528026151
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using DataTable;
|
||||
using Definition.Enum;
|
||||
using UnityEngine;
|
||||
|
|
@ -8,17 +9,7 @@ namespace Entity.EntityData
|
|||
[Serializable]
|
||||
public class EnemyData : TargetableObjectData
|
||||
{
|
||||
[SerializeField] private EnemyType _enemyType;
|
||||
|
||||
[SerializeField] private int _entityTypeId;
|
||||
|
||||
[SerializeField] private float _speedBase = 0;
|
||||
|
||||
[SerializeField] private int _dropCoin = 0;
|
||||
|
||||
[SerializeField] private int _dropExp = 0;
|
||||
|
||||
[SerializeField] private float _dropPercent = 0;
|
||||
[SerializeField] private DREnemy _drEnemy;
|
||||
|
||||
public EnemyData(int entityId, EnemyType enemyType, int level) : base(
|
||||
entityId, (int)enemyType, CampType.Enemy)
|
||||
|
|
@ -29,30 +20,46 @@ namespace Entity.EntityData
|
|||
{
|
||||
throw new Exception($"Enemy data table row is missing, EnemyType='{enemyType}'.");
|
||||
}
|
||||
else
|
||||
{
|
||||
_drEnemy = enemyRow;
|
||||
}
|
||||
|
||||
int effectiveLevel = Mathf.Max(1, level);
|
||||
|
||||
_enemyType = enemyType;
|
||||
_entityTypeId = enemyRow.EntityTypeId;
|
||||
MaxHealthBase = enemyRow.MaxHealth + enemyRow.HpAddPerLevel * (effectiveLevel - 1);
|
||||
_speedBase = enemyRow.Speed;
|
||||
_dropCoin = enemyRow.DropCoin;
|
||||
_dropExp = enemyRow.DropExp;
|
||||
_dropPercent = enemyRow.DropPercent;
|
||||
}
|
||||
|
||||
public EnemyType EnemyType => _enemyType;
|
||||
|
||||
public int EntityTypeId => _entityTypeId;
|
||||
public EnemyType EnemyType => (EnemyType)_drEnemy.Id;
|
||||
|
||||
public int EntityTypeId => _drEnemy.EntityTypeId;
|
||||
|
||||
public override int MaxHealthBase { get; }
|
||||
|
||||
public float SpeedBase => _speedBase;
|
||||
public int AttackDamage => _drEnemy.AttackDamage;
|
||||
|
||||
public int DropCoin => _dropCoin;
|
||||
public float AttackCooldown => _drEnemy.AttackCooldown;
|
||||
|
||||
public int DropExp => _dropExp;
|
||||
public float AttackRange => _drEnemy.AttackRange;
|
||||
|
||||
public float DropPercent => _dropPercent;
|
||||
public float SpeedBase => _drEnemy.Speed;
|
||||
|
||||
public int DropCoin => _drEnemy.DropCoin;
|
||||
|
||||
public int DropExp => _drEnemy.DropExp;
|
||||
|
||||
public float DropPercent => _drEnemy.DropPercent;
|
||||
|
||||
public IReadOnlyDictionary<string, string> Params => _drEnemy.Params;
|
||||
|
||||
public bool TryGetParam(string key, out string value)
|
||||
{
|
||||
value = null;
|
||||
if (string.IsNullOrEmpty(key) || _drEnemy?.Params == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return _drEnemy.Params.TryGetValue(key, out value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,29 @@
|
|||
using System;
|
||||
using Definition.Enum;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Entity.EntityData
|
||||
{
|
||||
[Serializable]
|
||||
public class EnemyProjectileData : EntityDataBase
|
||||
{
|
||||
public EnemyProjectileData(int entityId, int ownerEntityId, CampType ownerCamp, int attackDamage,
|
||||
float speed, float lifeTime, Vector3 direction)
|
||||
: base(entityId, 0)
|
||||
{
|
||||
OwnerEntityId = ownerEntityId;
|
||||
OwnerCamp = ownerCamp;
|
||||
AttackDamage = attackDamage;
|
||||
Speed = speed;
|
||||
LifeTime = lifeTime;
|
||||
Direction = direction;
|
||||
}
|
||||
|
||||
public int OwnerEntityId { get; }
|
||||
public CampType OwnerCamp { get; }
|
||||
public int AttackDamage { get; }
|
||||
public float Speed { get; }
|
||||
public float LifeTime { get; }
|
||||
public Vector3 Direction { get; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 52b38f83ab6c4029803d40c189db47c7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
|||
using DataTable;
|
||||
using Definition.DataStruct;
|
||||
using Definition.Enum;
|
||||
using GameFramework;
|
||||
|
||||
namespace Entity.EntityData
|
||||
{
|
||||
|
|
@ -28,14 +29,35 @@ namespace Entity.EntityData
|
|||
|
||||
public WeaponType WeaponType => (WeaponType)_drWeapon.Id;
|
||||
|
||||
public string GetParamsString(string paramsName)
|
||||
public bool TryGetParam(string key, out string value)
|
||||
{
|
||||
if (!Params.TryGetValue(paramsName.ToLower(), out var value))
|
||||
value = null;
|
||||
if (string.IsNullOrEmpty(key) || Params == null)
|
||||
{
|
||||
throw new Exception($"Parameter '{paramsName}' not found.");
|
||||
return false;
|
||||
}
|
||||
|
||||
return value;
|
||||
return Params.TryGetValue(key, 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>
|
||||
|
|
@ -79,9 +101,11 @@ namespace Entity.EntityData
|
|||
/// </summary>
|
||||
public Dictionary<string, string> Params => _drWeapon.Pramas;
|
||||
|
||||
public string ParamsJson => _drWeapon.ParamsJson;
|
||||
|
||||
/// <summary>
|
||||
/// 额外属性。
|
||||
/// </summary>
|
||||
public StatModifier[] Modifiers => _drWeapon.Modifiers;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<WeaponHandgunParamsData>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<WeaponKnifeParamsData>();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,71 @@
|
|||
using System;
|
||||
using Definition.Enum;
|
||||
|
||||
namespace Entity.EntityData
|
||||
{
|
||||
[Serializable]
|
||||
public sealed class WeaponLanceParamsData
|
||||
{
|
||||
/// <summary>
|
||||
/// 横向半宽,表示前戳矩形判定的一半宽度。
|
||||
/// </summary>
|
||||
public float HitHalfWidth { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 旧字段兼容,未配置 HitHalfWidth 时回退使用。
|
||||
/// </summary>
|
||||
public float HitRadius { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 前戳判定盒体的总高度。
|
||||
/// </summary>
|
||||
public float HitHeight { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 判定盒体中心相对战斗平面的高度偏移。
|
||||
/// </summary>
|
||||
public float HitCenterYOffset { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 前刺距离,同时驱动武器位移和命中长度。
|
||||
/// </summary>
|
||||
public float PierceLength { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 旧字段兼容,未配置 PierceLength 时回退使用。
|
||||
/// </summary>
|
||||
public float ThrustDistance { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 判定起点相对武器当前位置的前置偏移。
|
||||
/// </summary>
|
||||
public float ForwardOffset { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 追踪目标时的转向速度。
|
||||
/// </summary>
|
||||
public float RotateSpeed { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 向前突刺阶段耗时。
|
||||
/// </summary>
|
||||
public float AttackDuration { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 收枪返回阶段耗时。
|
||||
/// </summary>
|
||||
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<WeaponLanceParamsData>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 4d1a821e8ee1a9a4b912b70b0a1616eb
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +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<WeaponLightningParamsData>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 25b0006918fd46959c7f6b8ec1bbc8ab
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -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<WeaponSlashParamsData>();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ public abstract class EnemyBase : TargetableObject
|
|||
protected Transform _target;
|
||||
|
||||
public abstract override ImpactData GetImpactData();
|
||||
public virtual float AttackRange => 1f;
|
||||
|
||||
public virtual void SetTarget(Transform target) => _target = target;
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,140 @@
|
|||
using Definition.DataStruct;
|
||||
using Definition.Enum;
|
||||
using Entity.EntityData;
|
||||
using UnityEngine;
|
||||
using UnityGameFramework.Runtime;
|
||||
|
||||
namespace Entity
|
||||
{
|
||||
public class EnemyProjectile : EntityBase
|
||||
{
|
||||
private EnemyProjectileData _projectileData;
|
||||
private Vector3 _direction = Vector3.forward;
|
||||
private float _elapsedTime;
|
||||
private bool _isActive;
|
||||
private bool _isSimulationDriven;
|
||||
private ImpactData _impactData;
|
||||
private Collider[] _cachedColliders;
|
||||
|
||||
public bool IsActive => _isActive;
|
||||
public ImpactData GetImpactData() => _impactData;
|
||||
|
||||
protected override void OnShow(object userData)
|
||||
{
|
||||
base.OnShow(userData);
|
||||
|
||||
_projectileData = userData as EnemyProjectileData;
|
||||
if (_projectileData == null)
|
||||
{
|
||||
Log.Error("Enemy projectile data is invalid.");
|
||||
_isActive = false;
|
||||
GameEntry.Entity.HideEntity(this);
|
||||
return;
|
||||
}
|
||||
|
||||
_isActive = true;
|
||||
_elapsedTime = 0f;
|
||||
_impactData = new ImpactData(_projectileData.OwnerCamp, _projectileData.AttackDamage);
|
||||
|
||||
_direction = _projectileData.Direction;
|
||||
_direction.y = 0f;
|
||||
if (_direction.sqrMagnitude <= Mathf.Epsilon)
|
||||
{
|
||||
_direction = CachedTransform.forward;
|
||||
_direction.y = 0f;
|
||||
}
|
||||
|
||||
if (_direction.sqrMagnitude <= Mathf.Epsilon)
|
||||
{
|
||||
_direction = Vector3.forward;
|
||||
}
|
||||
else
|
||||
{
|
||||
_direction.Normalize();
|
||||
}
|
||||
|
||||
CachedTransform.rotation = Quaternion.LookRotation(_direction, Vector3.up);
|
||||
|
||||
if (_projectileData.OwnerCamp == CampType.Player)
|
||||
{
|
||||
gameObject.layer = LayerMask.NameToLayer("PlayerWeapon");
|
||||
}
|
||||
else if (_projectileData.OwnerCamp == CampType.Enemy)
|
||||
{
|
||||
gameObject.layer = LayerMask.NameToLayer("EnemyWeapon");
|
||||
}
|
||||
|
||||
_isSimulationDriven = IsDrivenBySimulationWorld();
|
||||
SetColliderEnabled(!_isSimulationDriven);
|
||||
}
|
||||
|
||||
protected override void OnUpdate(float elapseSeconds, float realElapseSeconds)
|
||||
{
|
||||
base.OnUpdate(elapseSeconds, realElapseSeconds);
|
||||
|
||||
if (!_isActive || _projectileData == null) return;
|
||||
|
||||
bool isSimulationDriven = IsDrivenBySimulationWorld();
|
||||
if (isSimulationDriven != _isSimulationDriven)
|
||||
{
|
||||
_isSimulationDriven = isSimulationDriven;
|
||||
SetColliderEnabled(!_isSimulationDriven);
|
||||
}
|
||||
|
||||
if (_isSimulationDriven) return;
|
||||
|
||||
if (_projectileData.Speed > 0f)
|
||||
{
|
||||
CachedTransform.position += _direction * (_projectileData.Speed * elapseSeconds);
|
||||
}
|
||||
|
||||
_elapsedTime += elapseSeconds;
|
||||
if (_projectileData.LifeTime > 0f && _elapsedTime >= _projectileData.LifeTime)
|
||||
{
|
||||
Expire();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnHide(bool isShutdown, object userData)
|
||||
{
|
||||
_isActive = false;
|
||||
_projectileData = null;
|
||||
_elapsedTime = 0f;
|
||||
_isSimulationDriven = false;
|
||||
_impactData = default;
|
||||
_direction = Vector3.forward;
|
||||
|
||||
base.OnHide(isShutdown, userData);
|
||||
}
|
||||
|
||||
public void Expire()
|
||||
{
|
||||
if (!_isActive) return;
|
||||
_isActive = false;
|
||||
GameEntry.Entity.HideEntity(this);
|
||||
}
|
||||
|
||||
private static bool IsDrivenBySimulationWorld()
|
||||
{
|
||||
var simulationWorld = GameEntry.SimulationWorld;
|
||||
return simulationWorld != null && simulationWorld.UseSimulationMovement;
|
||||
}
|
||||
|
||||
private void SetColliderEnabled(bool enabled)
|
||||
{
|
||||
_cachedColliders ??= GetComponentsInChildren<Collider>(true);
|
||||
if (_cachedColliders == null) return;
|
||||
|
||||
for (int i = 0; i < _cachedColliders.Length; i++)
|
||||
{
|
||||
Collider collider = _cachedColliders[i];
|
||||
if (collider == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
collider.enabled = enabled;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 22624f81b9364c8681b32d993f5e618f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -1,7 +1,8 @@
|
|||
using Components;
|
||||
using CustomUtility;
|
||||
using Definition.DataStruct;
|
||||
using Definition.Enum;
|
||||
using Entity.EntityData;
|
||||
using Entity.Weapon;
|
||||
using UnityEngine;
|
||||
using UnityGameFramework.Runtime;
|
||||
|
||||
|
|
@ -9,17 +10,33 @@ namespace Entity
|
|||
{
|
||||
public class MeleeEnemy : EnemyBase
|
||||
{
|
||||
private enum AttackStateType
|
||||
{
|
||||
Idle,
|
||||
Check_OutRange,
|
||||
Check_InRange,
|
||||
Attack
|
||||
}
|
||||
|
||||
private MovementComponent _movementComponent;
|
||||
|
||||
private float _attackRange = 1f;
|
||||
private float _attackRangeSquared;
|
||||
private float _attackCooldown = 1f;
|
||||
private int _attackDamage = 1;
|
||||
|
||||
private float _sqrAttackRange;
|
||||
private float _currAttackTimer;
|
||||
|
||||
private AttackStateType _attackState = AttackStateType.Idle;
|
||||
private EnemyData _meleeEnemyData;
|
||||
private WeaponBase _weapon;
|
||||
private TargetableObject _targetableTarget;
|
||||
|
||||
protected override TargetableObjectData _targetableObjectData => _meleeEnemyData;
|
||||
public override float AttackRange => _attackRange;
|
||||
|
||||
public override ImpactData GetImpactData()
|
||||
{
|
||||
return new ImpactData(_meleeEnemyData.Camp, 0);
|
||||
return new ImpactData(_meleeEnemyData.Camp, _attackDamage);
|
||||
}
|
||||
|
||||
#region FSM
|
||||
|
|
@ -35,51 +52,42 @@ namespace Entity
|
|||
protected override void OnShow(object userData)
|
||||
{
|
||||
base.OnShow(userData);
|
||||
|
||||
|
||||
if (userData is EnemyData enemyData)
|
||||
{
|
||||
_meleeEnemyData = enemyData;
|
||||
_healthComponent.OnInit(enemyData.MaxHealthBase);
|
||||
_movementComponent.OnInit(_meleeEnemyData.SpeedBase, this.CachedTransform, null, true);
|
||||
_movementComponent.SetMove(true);
|
||||
_attackRangeSquared = _attackRange * _attackRange;
|
||||
|
||||
_attackRange = Mathf.Max(0.1f, _meleeEnemyData.AttackRange);
|
||||
_attackCooldown = Mathf.Max(0.01f, _meleeEnemyData.AttackCooldown);
|
||||
_attackDamage = Mathf.Max(1, _meleeEnemyData.AttackDamage);
|
||||
_sqrAttackRange = _attackRange * _attackRange;
|
||||
|
||||
_currAttackTimer = 0f;
|
||||
_attackState = AttackStateType.Idle;
|
||||
_targetableTarget = null;
|
||||
|
||||
this.CachedTransform.position = enemyData.Position;
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Error($"Invalid data type. Data type: {userData?.GetType()}");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected override void OnUpdate(float elapseSeconds, float realElapseSeconds)
|
||||
{
|
||||
base.OnUpdate(elapseSeconds, realElapseSeconds);
|
||||
|
||||
UpdateAttackState(elapseSeconds);
|
||||
|
||||
if (IsSimulationMovementEnabled())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
base.OnUpdate(elapseSeconds, realElapseSeconds);
|
||||
|
||||
if (_target == null)
|
||||
{
|
||||
_movementComponent.SetMove(false);
|
||||
_movementComponent.OnUpdate(elapseSeconds, realElapseSeconds);
|
||||
return;
|
||||
}
|
||||
|
||||
float distanceSquared = (this.CachedTransform.position - _target.position).sqrMagnitude;
|
||||
if (distanceSquared < _attackRangeSquared)
|
||||
{
|
||||
// 攻击
|
||||
_movementComponent.SetMove(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
_movementComponent.SetMove(true);
|
||||
_movementComponent.SetDirection(GetTargetDirection());
|
||||
}
|
||||
|
||||
_movementComponent.OnUpdate(elapseSeconds, realElapseSeconds);
|
||||
}
|
||||
|
||||
|
|
@ -102,7 +110,7 @@ namespace Entity
|
|||
};
|
||||
GameEntry.Entity.ShowExp(data);
|
||||
}
|
||||
|
||||
|
||||
base.OnDead(attacker);
|
||||
}
|
||||
|
||||
|
|
@ -110,12 +118,143 @@ namespace Entity
|
|||
{
|
||||
_movementComponent.OnReset();
|
||||
_healthComponent.OnReset();
|
||||
_targetableTarget = null;
|
||||
_currAttackTimer = 0f;
|
||||
_attackState = AttackStateType.Idle;
|
||||
|
||||
base.OnHide(isShutdown, userData);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public override void SetTarget(Transform target)
|
||||
{
|
||||
base.SetTarget(target);
|
||||
_targetableTarget = target != null ? target.GetComponent<TargetableObject>() : null;
|
||||
}
|
||||
|
||||
private void UpdateAttackState(float elapseSeconds)
|
||||
{
|
||||
_currAttackTimer += elapseSeconds;
|
||||
|
||||
switch (_attackState)
|
||||
{
|
||||
case AttackStateType.Idle:
|
||||
SetMove(false);
|
||||
if (HasValidTarget())
|
||||
{
|
||||
TransitionTo(AttackStateType.Check_OutRange);
|
||||
}
|
||||
|
||||
break;
|
||||
case AttackStateType.Check_OutRange:
|
||||
if (!HasValidTarget())
|
||||
{
|
||||
TransitionTo(AttackStateType.Idle);
|
||||
return;
|
||||
}
|
||||
|
||||
if (IsTargetInRange())
|
||||
{
|
||||
TransitionTo(AttackStateType.Check_InRange);
|
||||
return;
|
||||
}
|
||||
|
||||
SetMove(true);
|
||||
_movementComponent.SetDirection(GetTargetDirection());
|
||||
break;
|
||||
case AttackStateType.Check_InRange:
|
||||
if (!HasValidTarget())
|
||||
{
|
||||
TransitionTo(AttackStateType.Idle);
|
||||
return;
|
||||
}
|
||||
|
||||
SetMove(false);
|
||||
if (!IsTargetInRange())
|
||||
{
|
||||
TransitionTo(AttackStateType.Check_OutRange);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_currAttackTimer >= _attackCooldown)
|
||||
{
|
||||
TransitionTo(AttackStateType.Attack);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void TransitionTo(AttackStateType newState)
|
||||
{
|
||||
_attackState = newState;
|
||||
|
||||
if (_attackState == AttackStateType.Check_InRange)
|
||||
{
|
||||
SetMove(false);
|
||||
}
|
||||
|
||||
if (_attackState == AttackStateType.Check_InRange && _currAttackTimer >= _attackCooldown)
|
||||
{
|
||||
TransitionTo(AttackStateType.Attack);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_attackState == AttackStateType.Attack)
|
||||
{
|
||||
SetMove(false);
|
||||
ExecuteAttack();
|
||||
_currAttackTimer = 0f;
|
||||
TransitionTo(AttackStateType.Check_InRange);
|
||||
}
|
||||
}
|
||||
|
||||
private bool HasValidTarget()
|
||||
{
|
||||
if (_target == null) return false;
|
||||
|
||||
if (_targetableTarget == null || _targetableTarget.CachedTransform != _target)
|
||||
{
|
||||
_targetableTarget = _target.GetComponent<TargetableObject>();
|
||||
}
|
||||
|
||||
return _targetableTarget != null && _targetableTarget.Available && !_targetableTarget.IsDead;
|
||||
}
|
||||
|
||||
private bool IsTargetInRange()
|
||||
{
|
||||
if (_target == null) return false;
|
||||
|
||||
Vector3 delta = _target.position - this.CachedTransform.position;
|
||||
delta.y = 0f;
|
||||
return delta.sqrMagnitude <= _sqrAttackRange;
|
||||
}
|
||||
|
||||
private void ExecuteAttack()
|
||||
{
|
||||
if (!HasValidTarget() || !IsTargetInRange()) return;
|
||||
|
||||
ImpactData targetImpactData = _targetableTarget.GetImpactData();
|
||||
ImpactData selfImpactData = GetImpactData();
|
||||
if (AIUtility.GetRelation(selfImpactData.Camp, targetImpactData.Camp) == RelationType.Friendly)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int damage = AIUtility.CalcDamageHP(_attackDamage, null, targetImpactData.DefenseStat,
|
||||
targetImpactData.DodgeStat);
|
||||
_targetableTarget.ApplyDamage(this, damage);
|
||||
}
|
||||
|
||||
private void SetMove(bool value)
|
||||
{
|
||||
if (_movementComponent != null)
|
||||
{
|
||||
_movementComponent.SetMove(value);
|
||||
}
|
||||
}
|
||||
|
||||
private Vector3 GetTargetDirection()
|
||||
{
|
||||
if (_target == null)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
using Components;
|
||||
using CustomUtility;
|
||||
using Definition;
|
||||
using Definition.DataStruct;
|
||||
using Entity.EntityData;
|
||||
using UnityEngine;
|
||||
|
|
@ -8,17 +10,39 @@ namespace Entity
|
|||
{
|
||||
public class RemoteEnemy : EnemyBase
|
||||
{
|
||||
private float _speed;
|
||||
private const string EnemyProjectileGroupName = "Bullet";
|
||||
private const float EnemyProjectileGroupAutoReleaseInterval = 60f;
|
||||
private const int EnemyProjectileGroupCapacity = 64;
|
||||
private const float EnemyProjectileGroupExpireTime = 60f;
|
||||
private const int EnemyProjectileGroupPriority = 0;
|
||||
|
||||
private const string ProjectileSpeedParamKey = "ProjectileSpeed";
|
||||
private const string ProjectileLifeTimeParamKey = "ProjectileLifeTime";
|
||||
private const string ProjectileSpawnForwardOffsetParamKey = "ProjectileSpawnForwardOffset";
|
||||
private const string ProjectileSpawnHeightOffsetParamKey = "ProjectileSpawnHeightOffset";
|
||||
private const string ProjectileAssetNameParamKey = "ProjectileAssetName";
|
||||
|
||||
private MovementComponent _movementComponent;
|
||||
private float _attackRange = 1f;
|
||||
private float _attackRangeSquared;
|
||||
private float _attackCooldown = 1f;
|
||||
private int _attackDamage = 1;
|
||||
private float _currAttackTimer;
|
||||
|
||||
[SerializeField] private float _projectileSpeed = 12f;
|
||||
[SerializeField] private float _projectileLifeTime = 3f;
|
||||
[SerializeField] private float _projectileSpawnForwardOffset = 0.7f;
|
||||
[SerializeField] private float _projectileSpawnHeightOffset = 0.6f;
|
||||
[SerializeField] private string _projectileAssetName = "BulletHandgun";
|
||||
|
||||
private EnemyData _remoteEnemyData;
|
||||
|
||||
protected override TargetableObjectData _targetableObjectData => _remoteEnemyData;
|
||||
public override float AttackRange => _attackRange;
|
||||
|
||||
public override ImpactData GetImpactData()
|
||||
{
|
||||
return new ImpactData(_remoteEnemyData.Camp, 0);
|
||||
return new ImpactData(_remoteEnemyData.Camp, _attackDamage);
|
||||
}
|
||||
|
||||
protected override void OnInit(object userData)
|
||||
|
|
@ -39,7 +63,21 @@ namespace Entity
|
|||
_healthComponent.OnInit(enemyData.MaxHealthBase);
|
||||
_movementComponent.OnInit(_remoteEnemyData.SpeedBase, this.CachedTransform, null, true);
|
||||
_movementComponent.SetMove(true);
|
||||
|
||||
_attackRange = Mathf.Max(0.1f, _remoteEnemyData.AttackRange);
|
||||
_attackRangeSquared = _attackRange * _attackRange;
|
||||
_attackCooldown = Mathf.Max(0.01f, _remoteEnemyData.AttackCooldown);
|
||||
_attackDamage = Mathf.Max(1, _remoteEnemyData.AttackDamage);
|
||||
|
||||
_projectileSpeed = ReadPositiveParam(ProjectileSpeedParamKey, _projectileSpeed);
|
||||
_projectileLifeTime = ReadPositiveParam(ProjectileLifeTimeParamKey, _projectileLifeTime);
|
||||
_projectileSpawnForwardOffset = ReadPositiveParam(ProjectileSpawnForwardOffsetParamKey,
|
||||
_projectileSpawnForwardOffset);
|
||||
_projectileSpawnHeightOffset = ReadPositiveParam(ProjectileSpawnHeightOffsetParamKey,
|
||||
_projectileSpawnHeightOffset);
|
||||
_projectileAssetName = ReadStringParam(ProjectileAssetNameParamKey, _projectileAssetName);
|
||||
|
||||
_currAttackTimer = 0f;
|
||||
this.CachedTransform.position = enemyData.Position;
|
||||
}
|
||||
else
|
||||
|
|
@ -50,25 +88,28 @@ namespace Entity
|
|||
|
||||
protected override void OnUpdate(float elapseSeconds, float realElapseSeconds)
|
||||
{
|
||||
if (IsSimulationMovementEnabled())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
base.OnUpdate(elapseSeconds, realElapseSeconds);
|
||||
|
||||
_currAttackTimer += elapseSeconds;
|
||||
|
||||
if (_target == null)
|
||||
{
|
||||
_movementComponent.SetMove(false);
|
||||
_movementComponent.OnUpdate(elapseSeconds, realElapseSeconds);
|
||||
if (!IsSimulationMovementEnabled())
|
||||
{
|
||||
_movementComponent.OnUpdate(elapseSeconds, realElapseSeconds);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
float distanceSquared = (this.CachedTransform.position - _target.position).sqrMagnitude;
|
||||
if (distanceSquared < _attackRangeSquared)
|
||||
Vector3 toTarget = _target.position - this.CachedTransform.position;
|
||||
toTarget.y = 0f;
|
||||
float distanceSquared = toTarget.sqrMagnitude;
|
||||
if (distanceSquared <= _attackRangeSquared)
|
||||
{
|
||||
// 攻击
|
||||
_movementComponent.SetMove(false);
|
||||
TryFireProjectile();
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -76,17 +117,115 @@ namespace Entity
|
|||
_movementComponent.SetDirection(GetTargetDirection());
|
||||
}
|
||||
|
||||
_movementComponent.OnUpdate(elapseSeconds, realElapseSeconds);
|
||||
if (!IsSimulationMovementEnabled())
|
||||
{
|
||||
_movementComponent.OnUpdate(elapseSeconds, realElapseSeconds);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnHide(bool isShutdown, object userData)
|
||||
{
|
||||
_movementComponent.OnReset();
|
||||
_healthComponent.OnReset();
|
||||
_currAttackTimer = 0f;
|
||||
|
||||
base.OnHide(isShutdown, userData);
|
||||
}
|
||||
|
||||
private void TryFireProjectile()
|
||||
{
|
||||
if (_currAttackTimer < _attackCooldown || _target == null) return;
|
||||
if (!EnsureEnemyProjectileGroup()) return;
|
||||
|
||||
Vector3 spawnPosition = this.CachedTransform.position +
|
||||
this.CachedTransform.forward * _projectileSpawnForwardOffset +
|
||||
Vector3.up * _projectileSpawnHeightOffset;
|
||||
Vector3 direction = _target.position - spawnPosition;
|
||||
direction.y = 0f;
|
||||
if (direction.sqrMagnitude <= Mathf.Epsilon)
|
||||
{
|
||||
direction = this.CachedTransform.forward;
|
||||
direction.y = 0f;
|
||||
}
|
||||
|
||||
if (direction.sqrMagnitude <= Mathf.Epsilon)
|
||||
{
|
||||
direction = Vector3.forward;
|
||||
}
|
||||
else
|
||||
{
|
||||
direction.Normalize();
|
||||
}
|
||||
|
||||
int projectileEntityId = GameEntry.Entity.GenerateSerialId();
|
||||
var projectileData = new EnemyProjectileData(projectileEntityId, Id, _remoteEnemyData.Camp,
|
||||
_attackDamage, _projectileSpeed, _projectileLifeTime, direction)
|
||||
{
|
||||
Position = spawnPosition,
|
||||
Rotation = Quaternion.LookRotation(direction, Vector3.up)
|
||||
};
|
||||
|
||||
GameEntry.Entity.ShowEntity(
|
||||
entityId: projectileEntityId,
|
||||
entityLogicType: typeof(EnemyProjectile),
|
||||
entityAssetName: AssetUtility.GetEntityAsset(_projectileAssetName),
|
||||
entityGroupName: EnemyProjectileGroupName,
|
||||
priority: Constant.AssetPriority.BulletAsset,
|
||||
userData: projectileData);
|
||||
|
||||
_currAttackTimer = 0f;
|
||||
}
|
||||
|
||||
private static bool EnsureEnemyProjectileGroup()
|
||||
{
|
||||
var entityComponent = GameEntry.Entity;
|
||||
if (entityComponent == null) return false;
|
||||
|
||||
if (entityComponent.HasEntityGroup(EnemyProjectileGroupName))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool addResult = entityComponent.AddEntityGroup(
|
||||
EnemyProjectileGroupName,
|
||||
EnemyProjectileGroupAutoReleaseInterval,
|
||||
EnemyProjectileGroupCapacity,
|
||||
EnemyProjectileGroupExpireTime,
|
||||
EnemyProjectileGroupPriority);
|
||||
|
||||
if (!addResult)
|
||||
{
|
||||
Log.Warning("Can not create entity group '{0}'.", EnemyProjectileGroupName);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private float ReadPositiveParam(string paramName, float defaultValue)
|
||||
{
|
||||
if (_remoteEnemyData != null &&
|
||||
_remoteEnemyData.TryGetParam(paramName, out string rawValue) &&
|
||||
float.TryParse(rawValue, out float parsedValue))
|
||||
{
|
||||
return Mathf.Max(0.01f, parsedValue);
|
||||
}
|
||||
|
||||
return Mathf.Max(0.01f, defaultValue);
|
||||
}
|
||||
|
||||
private string ReadStringParam(string paramName, string defaultValue)
|
||||
{
|
||||
if (_remoteEnemyData != null &&
|
||||
_remoteEnemyData.TryGetParam(paramName, out string rawValue) &&
|
||||
!string.IsNullOrWhiteSpace(rawValue))
|
||||
{
|
||||
return rawValue;
|
||||
}
|
||||
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
private Vector3 GetTargetDirection()
|
||||
{
|
||||
if (_target == null)
|
||||
|
|
|
|||
|
|
@ -61,8 +61,8 @@ namespace Entity
|
|||
|
||||
private void OnTriggerEnter(Collider other)
|
||||
{
|
||||
EntityBase entity = other.gameObject.GetComponent<EntityBase>();
|
||||
if (entity == null)
|
||||
EntityBase entity = other.GetComponentInParent<EntityBase>();
|
||||
if (entity == null || entity == this)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
|
@ -70,7 +70,7 @@ namespace Entity
|
|||
if (entity is TargetableObject && entity.Id < Id)
|
||||
{
|
||||
// 碰撞事件由 Id 大的一方处理
|
||||
// 在这里规定所有的 Enemy 的 Id 均大于 0
|
||||
// 在这里约定 Enemy 的 Id 为非负数(通常从 0 开始)
|
||||
// 而其他的 Entity (Player, Weapon, Bullet) 的 Id 均小于 0
|
||||
return;
|
||||
}
|
||||
|
|
@ -78,4 +78,4 @@ namespace Entity
|
|||
AIUtility.PerformCollision(this, entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,23 @@
|
|||
using GameFramework.ObjectPool;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Entity.Weapon
|
||||
{
|
||||
public sealed class HandgunHitMarkerAttackEffect : IWeaponAttackEffect
|
||||
{
|
||||
private const string PoolName = "Weapon.HandgunHitMarker";
|
||||
private const float PoolAutoReleaseInterval = 60f;
|
||||
private const int PoolCapacity = 128;
|
||||
private const float PoolExpireTime = 120f;
|
||||
private const int PoolPriority = 0;
|
||||
|
||||
private static IObjectPool<HandgunHitMarkerPoolObject> s_Pool;
|
||||
|
||||
private readonly float _size;
|
||||
private readonly float _yOffset;
|
||||
private readonly float _duration;
|
||||
private readonly Color _color;
|
||||
private Material _sharedMaterial;
|
||||
|
||||
public HandgunHitMarkerAttackEffect(float size, float yOffset, float duration, Color color)
|
||||
{
|
||||
|
|
@ -20,35 +30,95 @@ namespace Entity.Weapon
|
|||
public void Play(WeaponBase weapon, Vector3 position, EntityBase target, float radius)
|
||||
{
|
||||
if (target == null) return;
|
||||
if (!TrySpawnMarker(out HandgunHitMarkerPooledInstance markerInstance))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GameObject marker = GameObject.CreatePrimitive(PrimitiveType.Sphere);
|
||||
marker.name = "HandgunHitMarker";
|
||||
Transform targetTransform = target.CachedTransform;
|
||||
Vector3 worldPosition = targetTransform != null ? targetTransform.position : position;
|
||||
markerInstance.transform.SetParent(null, false);
|
||||
markerInstance.transform.position = worldPosition + Vector3.up * _yOffset;
|
||||
markerInstance.transform.localScale = Vector3.one * Mathf.Max(0.01f, _size);
|
||||
markerInstance.ApplyMaterial(GetSharedMaterial());
|
||||
markerInstance.Activate(Mathf.Max(0.01f, _duration), s_Pool);
|
||||
}
|
||||
|
||||
Collider collider = marker.GetComponent<Collider>();
|
||||
private bool TrySpawnMarker(out HandgunHitMarkerPooledInstance markerInstance)
|
||||
{
|
||||
markerInstance = null;
|
||||
IObjectPool<HandgunHitMarkerPoolObject> pool = EnsurePool();
|
||||
if (pool == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
HandgunHitMarkerPoolObject pooledObject = pool.Spawn();
|
||||
if (pooledObject != null)
|
||||
{
|
||||
markerInstance = pooledObject.Target as HandgunHitMarkerPooledInstance;
|
||||
if (markerInstance != null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
GameObject markerGameObject = GameObject.CreatePrimitive(PrimitiveType.Sphere);
|
||||
markerGameObject.name = "HandgunHitMarker";
|
||||
Collider collider = markerGameObject.GetComponent<Collider>();
|
||||
if (collider != null)
|
||||
{
|
||||
Object.Destroy(collider);
|
||||
}
|
||||
|
||||
marker.transform.SetParent(target.CachedTransform, false);
|
||||
marker.transform.localPosition = new Vector3(0f, _yOffset, 0f);
|
||||
marker.transform.localScale = Vector3.one * Mathf.Max(0.01f, _size);
|
||||
markerInstance = markerGameObject.AddComponent<HandgunHitMarkerPooledInstance>();
|
||||
markerGameObject.SetActive(false);
|
||||
pool.Register(HandgunHitMarkerPoolObject.Create(markerInstance), true);
|
||||
return true;
|
||||
}
|
||||
|
||||
Renderer renderer = marker.GetComponent<Renderer>();
|
||||
if (renderer != null)
|
||||
private static IObjectPool<HandgunHitMarkerPoolObject> EnsurePool()
|
||||
{
|
||||
var poolComponent = GameEntry.ObjectPool;
|
||||
if (poolComponent == null)
|
||||
{
|
||||
Shader shader = Shader.Find("Sprites/Default");
|
||||
if (shader == null)
|
||||
{
|
||||
shader = Shader.Find("Unlit/Color");
|
||||
}
|
||||
|
||||
Material material = new Material(shader);
|
||||
material.color = _color;
|
||||
renderer.material = material;
|
||||
return null;
|
||||
}
|
||||
|
||||
Object.Destroy(marker, Mathf.Max(0.01f, _duration));
|
||||
if (s_Pool != null && poolComponent.HasObjectPool<HandgunHitMarkerPoolObject>(PoolName))
|
||||
{
|
||||
return s_Pool;
|
||||
}
|
||||
|
||||
s_Pool = poolComponent.HasObjectPool<HandgunHitMarkerPoolObject>(PoolName)
|
||||
? poolComponent.GetObjectPool<HandgunHitMarkerPoolObject>(PoolName)
|
||||
: poolComponent.CreateSingleSpawnObjectPool<HandgunHitMarkerPoolObject>(
|
||||
PoolName,
|
||||
PoolAutoReleaseInterval,
|
||||
PoolCapacity,
|
||||
PoolExpireTime,
|
||||
PoolPriority);
|
||||
return s_Pool;
|
||||
}
|
||||
|
||||
private Material GetSharedMaterial()
|
||||
{
|
||||
if (_sharedMaterial != null)
|
||||
{
|
||||
return _sharedMaterial;
|
||||
}
|
||||
|
||||
Shader shader = Shader.Find("Sprites/Default");
|
||||
if (shader == null)
|
||||
{
|
||||
shader = Shader.Find("Unlit/Color");
|
||||
}
|
||||
|
||||
_sharedMaterial = new Material(shader)
|
||||
{
|
||||
color = _color
|
||||
};
|
||||
return _sharedMaterial;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,27 @@
|
|||
using GameFramework;
|
||||
using GameFramework.ObjectPool;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Entity.Weapon
|
||||
{
|
||||
public sealed class HandgunHitMarkerPoolObject : ObjectBase
|
||||
{
|
||||
public static HandgunHitMarkerPoolObject Create(object target)
|
||||
{
|
||||
HandgunHitMarkerPoolObject pooledObject = ReferencePool.Acquire<HandgunHitMarkerPoolObject>();
|
||||
pooledObject.Initialize(target);
|
||||
return pooledObject;
|
||||
}
|
||||
|
||||
protected override void Release(bool isShutdown)
|
||||
{
|
||||
HandgunHitMarkerPooledInstance markerInstance = Target as HandgunHitMarkerPooledInstance;
|
||||
if (markerInstance == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Object.Destroy(markerInstance.gameObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 966d34a18f5d00c49bddea3c7c5ec13d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
using GameFramework.ObjectPool;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Entity.Weapon
|
||||
{
|
||||
public sealed class HandgunHitMarkerPooledInstance : MonoBehaviour
|
||||
{
|
||||
private Renderer _renderer;
|
||||
private IObjectPool<HandgunHitMarkerPoolObject> _ownerPool;
|
||||
private float _expireTime;
|
||||
private bool _isActive;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
_renderer = GetComponent<Renderer>();
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (!_isActive)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (Time.time < _expireTime)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ReturnToPool();
|
||||
}
|
||||
|
||||
public void ApplyMaterial(Material material)
|
||||
{
|
||||
if (_renderer == null)
|
||||
{
|
||||
_renderer = GetComponent<Renderer>();
|
||||
}
|
||||
|
||||
if (_renderer == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_renderer.sharedMaterial = material;
|
||||
}
|
||||
|
||||
public void Activate(float duration, IObjectPool<HandgunHitMarkerPoolObject> pool)
|
||||
{
|
||||
_ownerPool = pool;
|
||||
_expireTime = Time.time + Mathf.Max(0.01f, duration);
|
||||
_isActive = true;
|
||||
gameObject.SetActive(true);
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
_isActive = false;
|
||||
_ownerPool = null;
|
||||
}
|
||||
|
||||
private void ReturnToPool()
|
||||
{
|
||||
_isActive = false;
|
||||
IObjectPool<HandgunHitMarkerPoolObject> pool = _ownerPool;
|
||||
_ownerPool = null;
|
||||
transform.SetParent(null, false);
|
||||
|
||||
if (pool == null)
|
||||
{
|
||||
gameObject.SetActive(false);
|
||||
return;
|
||||
}
|
||||
|
||||
pool.Unspawn(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 7ff8a67466d8f0643845c41fd5952f50
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
using UnityEngine;
|
||||
|
||||
namespace Entity.Weapon
|
||||
{
|
||||
public sealed class LanceThrustAttackEffect : IWeaponAttackEffect
|
||||
{
|
||||
private readonly float _duration = 0.18f;
|
||||
private readonly float _yOffset = 0.06f;
|
||||
private readonly float _lineWidth = 0.05f;
|
||||
private readonly Color _color = new(0.2f, 0.95f, 0.7f, 0.92f);
|
||||
|
||||
public LanceThrustAttackEffect()
|
||||
{
|
||||
}
|
||||
|
||||
public LanceThrustAttackEffect(float duration, float yOffset, float lineWidth, Color color)
|
||||
{
|
||||
_duration = duration;
|
||||
_yOffset = yOffset;
|
||||
_lineWidth = lineWidth;
|
||||
_color = color;
|
||||
}
|
||||
|
||||
public void Play(WeaponBase weapon, Vector3 position, EntityBase target, float radius)
|
||||
{
|
||||
if (weapon is not WeaponLance lance) return;
|
||||
|
||||
Vector3 forward = lance.StrikeDirection;
|
||||
forward.y = 0f;
|
||||
if (forward.sqrMagnitude <= Mathf.Epsilon)
|
||||
{
|
||||
forward = Vector3.forward;
|
||||
}
|
||||
|
||||
forward.Normalize();
|
||||
Vector3 right = Vector3.Cross(Vector3.up, forward);
|
||||
float halfWidth = Mathf.Max(0.1f, lance.HitHalfWidth);
|
||||
float halfLength = Mathf.Max(0.1f, lance.PierceLength * 0.5f);
|
||||
Vector3 center = position + Vector3.up * _yOffset;
|
||||
|
||||
Vector3 frontCenter = center + forward * halfLength;
|
||||
Vector3 backCenter = center - forward * halfLength;
|
||||
|
||||
Vector3[] corners =
|
||||
{
|
||||
frontCenter - right * halfWidth,
|
||||
frontCenter + right * halfWidth,
|
||||
backCenter + right * halfWidth,
|
||||
backCenter - right * halfWidth,
|
||||
};
|
||||
|
||||
GameObject indicator = new GameObject("LanceThrustIndicator");
|
||||
indicator.transform.position = center;
|
||||
|
||||
LineRenderer line = indicator.AddComponent<LineRenderer>();
|
||||
line.loop = true;
|
||||
line.useWorldSpace = true;
|
||||
line.positionCount = corners.Length;
|
||||
line.startWidth = _lineWidth;
|
||||
line.endWidth = _lineWidth;
|
||||
line.startColor = _color;
|
||||
line.endColor = _color;
|
||||
|
||||
Shader shader = Shader.Find("Sprites/Default");
|
||||
if (shader == null)
|
||||
{
|
||||
shader = Shader.Find("Unlit/Color");
|
||||
}
|
||||
|
||||
Material material = new Material(shader);
|
||||
material.color = _color;
|
||||
line.material = material;
|
||||
|
||||
for (int i = 0; i < corners.Length; i++)
|
||||
{
|
||||
line.SetPosition(i, corners[i]);
|
||||
}
|
||||
|
||||
Object.Destroy(indicator, Mathf.Max(0.01f, _duration));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 8c77c590c7233e544a9295ed41ff8827
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,81 @@
|
|||
using UnityEngine;
|
||||
|
||||
namespace Entity.Weapon
|
||||
{
|
||||
public sealed class LightningStrikeAttackEffect : IWeaponAttackEffect
|
||||
{
|
||||
private readonly float _duration = 0.22f;
|
||||
private readonly float _yOffset = 0.2f;
|
||||
private readonly float _ringLineWidth = 0.045f;
|
||||
private readonly float _boltLineWidth = 0.08f;
|
||||
private readonly int _ringSegments = 32;
|
||||
private readonly float _boltHeight = 3f;
|
||||
private readonly Color _ringColor = new(0.35f, 0.82f, 1f, 0.92f);
|
||||
private readonly Color _boltColor = new(0.85f, 0.96f, 1f, 0.97f);
|
||||
|
||||
public void Play(WeaponBase weapon, Vector3 position, EntityBase target, float radius)
|
||||
{
|
||||
float safeRadius = Mathf.Max(0.1f, radius);
|
||||
Vector3 center = new Vector3(position.x, position.y + _yOffset, position.z);
|
||||
|
||||
GameObject root = new GameObject("LightningStrikeEffect");
|
||||
root.transform.position = center;
|
||||
|
||||
Shader shader = Shader.Find("Sprites/Default");
|
||||
if (shader == null)
|
||||
{
|
||||
shader = Shader.Find("Unlit/Color");
|
||||
}
|
||||
|
||||
Material ringMaterial = new Material(shader);
|
||||
ringMaterial.color = _ringColor;
|
||||
|
||||
LineRenderer ring = root.AddComponent<LineRenderer>();
|
||||
ring.loop = true;
|
||||
ring.useWorldSpace = true;
|
||||
ring.positionCount = _ringSegments;
|
||||
ring.startWidth = _ringLineWidth;
|
||||
ring.endWidth = _ringLineWidth;
|
||||
ring.startColor = _ringColor;
|
||||
ring.endColor = _ringColor;
|
||||
ring.material = ringMaterial;
|
||||
|
||||
float step = Mathf.PI * 2f / _ringSegments;
|
||||
for (int i = 0; i < _ringSegments; i++)
|
||||
{
|
||||
float angle = i * step;
|
||||
Vector3 offset = new Vector3(Mathf.Cos(angle) * safeRadius, 0f, Mathf.Sin(angle) * safeRadius);
|
||||
ring.SetPosition(i, center + offset);
|
||||
}
|
||||
|
||||
GameObject bolt = new GameObject("Bolt");
|
||||
bolt.transform.SetParent(root.transform, false);
|
||||
|
||||
Material boltMaterial = new Material(shader);
|
||||
boltMaterial.color = _boltColor;
|
||||
|
||||
LineRenderer boltLine = bolt.AddComponent<LineRenderer>();
|
||||
boltLine.loop = false;
|
||||
boltLine.useWorldSpace = true;
|
||||
boltLine.positionCount = 4;
|
||||
boltLine.startWidth = _boltLineWidth;
|
||||
boltLine.endWidth = _boltLineWidth * 0.55f;
|
||||
boltLine.startColor = _boltColor;
|
||||
boltLine.endColor = _boltColor;
|
||||
boltLine.material = boltMaterial;
|
||||
|
||||
Vector3 top = center + Vector3.up * _boltHeight;
|
||||
Vector3 middleA = center + Vector3.up * (_boltHeight * 0.66f) +
|
||||
new Vector3(Random.Range(-0.18f, 0.18f), 0f, Random.Range(-0.18f, 0.18f));
|
||||
Vector3 middleB = center + Vector3.up * (_boltHeight * 0.3f) +
|
||||
new Vector3(Random.Range(-0.12f, 0.12f), 0f, Random.Range(-0.12f, 0.12f));
|
||||
|
||||
boltLine.SetPosition(0, top);
|
||||
boltLine.SetPosition(1, middleA);
|
||||
boltLine.SetPosition(2, middleB);
|
||||
boltLine.SetPosition(3, center);
|
||||
|
||||
Object.Destroy(root, Mathf.Max(0.01f, _duration));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 81cbdf8961ad419c91b989d56ca782d0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
using System.Collections.Generic;
|
||||
using CustomUtility;
|
||||
using CustomComponent;
|
||||
|
||||
namespace Entity.Weapon
|
||||
{
|
||||
|
|
@ -12,6 +13,11 @@ namespace Entity.Weapon
|
|||
return null;
|
||||
}
|
||||
|
||||
if (TrySelectFromSpatialIndex(weapon, maxSqrRange, out EntityBase indexedTarget))
|
||||
{
|
||||
return indexedTarget;
|
||||
}
|
||||
|
||||
EntityBase target = null;
|
||||
float minSqrMagnitude = maxSqrRange > 0f ? maxSqrRange : float.MaxValue;
|
||||
|
||||
|
|
@ -28,5 +34,35 @@ namespace Entity.Weapon
|
|||
|
||||
return target;
|
||||
}
|
||||
|
||||
private static bool TrySelectFromSpatialIndex(WeaponBase weapon, float maxSqrRange, out EntityBase target)
|
||||
{
|
||||
target = null;
|
||||
if (weapon == null || maxSqrRange <= 0f || weapon.CachedTransform == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var simulationWorld = GameEntry.SimulationWorld;
|
||||
if (simulationWorld == null || !simulationWorld.UseSimulationMovement)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!simulationWorld.TryGetNearestEnemyEntityId(weapon.CachedTransform.position, maxSqrRange,
|
||||
out int entityId))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
EnemyManagerComponent enemyManager = GameEntry.EnemyManager;
|
||||
if (enemyManager == null || !enemyManager.TryGetEnemy(entityId, out EntityBase enemy))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
target = enemy;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -37,7 +37,7 @@ namespace Entity.Weapon
|
|||
protected ITargetSelector TargetSelector { get; set; }
|
||||
|
||||
protected Dictionary<WeaponStateType, WeaponStateBase> _states;
|
||||
|
||||
|
||||
protected WeaponStateBase _currentState;
|
||||
|
||||
protected EntityBase _target;
|
||||
|
|
@ -50,7 +50,7 @@ namespace Entity.Weapon
|
|||
|
||||
private StatComponent _attackStatComponent;
|
||||
private System.Action<StatModifier, bool> _attackStatCallback;
|
||||
|
||||
|
||||
private static readonly List<EntityBase> s_EmptyCandidates = new();
|
||||
|
||||
#region Lifecycle
|
||||
|
|
@ -220,6 +220,46 @@ namespace Entity.Weapon
|
|||
return AIUtility.GetSqrMagnitudeXZ(this, target) < sqrRange;
|
||||
}
|
||||
|
||||
protected bool TryQueueAreaCollisionQuery(in Vector3 center, float radius, int maxTargets = 16)
|
||||
{
|
||||
var simulationWorld = GameEntry.SimulationWorld;
|
||||
if (simulationWorld == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int ownerEntityId = WeaponData != null ? WeaponData.OwnerId : Id;
|
||||
return simulationWorld.TryRequestAreaCollision(Id, ownerEntityId, in center, radius, maxTargets);
|
||||
}
|
||||
|
||||
protected bool TryQueueSectorCollisionQuery(in Vector3 center, float radius, in Vector3 direction,
|
||||
float halfAngleDeg, int maxTargets = 16)
|
||||
{
|
||||
var simulationWorld = GameEntry.SimulationWorld;
|
||||
if (simulationWorld == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int ownerEntityId = WeaponData != null ? WeaponData.OwnerId : Id;
|
||||
return simulationWorld.TryRequestSectorCollision(Id, ownerEntityId, in center, radius, in direction,
|
||||
halfAngleDeg, maxTargets);
|
||||
}
|
||||
|
||||
protected bool TryQueueRectangleCollisionQuery(in Vector3 center, float halfWidth, float halfLength,
|
||||
in Vector3 direction, int maxTargets = 16)
|
||||
{
|
||||
var simulationWorld = GameEntry.SimulationWorld;
|
||||
if (simulationWorld == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int ownerEntityId = WeaponData != null ? WeaponData.OwnerId : Id;
|
||||
return simulationWorld.TryRequestRectangleCollision(Id, ownerEntityId, in center, halfWidth, halfLength,
|
||||
in direction, maxTargets);
|
||||
}
|
||||
|
||||
protected void SetTargetSelector(TargetSelectorType selectorType)
|
||||
{
|
||||
TargetSelector = CreateSelector(selectorType);
|
||||
|
|
@ -278,12 +318,4 @@ namespace Entity.Weapon
|
|||
public abstract void OnLeave();
|
||||
public override string ToString() => State.ToString();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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<TargetableObject>();
|
||||
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<TargetableObject>();
|
||||
if (raycastTarget == targetable)
|
||||
{
|
||||
hitPosition = hit.point;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#region Lifecycle
|
||||
|
||||
protected override bool OnWeaponShow(object userData)
|
||||
|
|
|
|||
|
|
@ -11,8 +11,6 @@ namespace Entity.Weapon
|
|||
{
|
||||
public partial class WeaponKnife : WeaponBase
|
||||
{
|
||||
private const string HitRadiusParamKey = "HitRadius";
|
||||
|
||||
private WeaponKnifeData _weaponData;
|
||||
|
||||
private Quaternion _cachedRotation;
|
||||
|
|
@ -27,7 +25,7 @@ namespace Entity.Weapon
|
|||
|
||||
[SerializeField] private LayerMask _hitMask = ~0;
|
||||
[SerializeField] private int _maxHitColliders = 32;
|
||||
|
||||
|
||||
private IWeaponAttackEffect _attackEffect;
|
||||
private Collider[] _hitResults;
|
||||
private readonly HashSet<int> _hitEntityIds = new();
|
||||
|
|
@ -116,7 +114,15 @@ namespace Entity.Weapon
|
|||
|
||||
private void ApplyGroundAreaDamage()
|
||||
{
|
||||
if (_hitRadius <= 0f || _hitResults == null || _hitResults.Length == 0) return;
|
||||
if (_hitRadius <= 0f) return;
|
||||
|
||||
if (TryQueueAreaCollisionQuery(_attackCenter, _hitRadius, Mathf.Max(1, _maxHitColliders)))
|
||||
{
|
||||
_hitEntityIds.Clear();
|
||||
return;
|
||||
}
|
||||
|
||||
if (_hitResults == null || _hitResults.Length == 0) return;
|
||||
|
||||
int hitCount = Physics.OverlapSphereNonAlloc(_attackCenter, _hitRadius, _hitResults, _hitMask,
|
||||
QueryTriggerInteraction.Collide);
|
||||
|
|
@ -149,12 +155,8 @@ namespace Entity.Weapon
|
|||
_sqrRange = _weaponData.AttackRange * _weaponData.AttackRange;
|
||||
_cachedRotation = CachedTransform.rotation;
|
||||
|
||||
string hitRadiusRaw = _weaponData.GetParamsString(HitRadiusParamKey);
|
||||
if (!float.TryParse(hitRadiusRaw, out _hitRadius))
|
||||
{
|
||||
_hitRadius = _weaponData.AttackRange;
|
||||
}
|
||||
_hitRadius = Mathf.Max(0.1f, _hitRadius);
|
||||
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();
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 1b6ba43d50137a44a9cac13aee7c79b4
|
||||
guid: aee5d5037e73e894cac11712e321b930
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
|
|
@ -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()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: d9991ee189b733b4e9d40395357f5ef2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -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()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 01edc8bb0ff46a14d8028e03af932b52
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -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()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 8009ad2c332a956438e3357ed5e30eb6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -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()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 4c9e98f5381e579469dcdac7bf3e1e25
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,359 @@
|
|||
using System.Collections.Generic;
|
||||
using Components;
|
||||
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<int> _hitEntityIds = new();
|
||||
|
||||
// 前戳矩形判定的横向半宽。
|
||||
private float _hitHalfWidth;
|
||||
// 前戳矩形判定盒体的总高度。
|
||||
private float _hitHeight;
|
||||
// 盒体中心相对战斗平面的高度偏移。
|
||||
private float _hitCenterYOffset;
|
||||
// 从判定起点向前延伸的有效刺击长度。
|
||||
private float _pierceLength;
|
||||
// 判定起点相对武器当前位置的前移量。
|
||||
private float _forwardOffset;
|
||||
// 本次攻击锁定的前戳方向,避免受位移动画中的武器位置影响。
|
||||
private Vector3 _strikeDirection = Vector3.forward;
|
||||
// 本次攻击锁定的矩形判定中心。
|
||||
private Vector3 _strikeCenter;
|
||||
|
||||
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();
|
||||
CacheStrikeSnapshot();
|
||||
|
||||
_isAttacking = true;
|
||||
_attackParent = CachedTransform.parent;
|
||||
CachedTransform.SetParent(null);
|
||||
|
||||
Vector3 targetPos = CachedTransform.position + _strikeDirection * _pierceLength;
|
||||
_attackSequence = DOTween.Sequence();
|
||||
_attackSequence.Append(CachedTransform.DOMove(targetPos, _attackDuration).SetEase(Ease.OutQuad));
|
||||
_attackSequence.AppendCallback(() =>
|
||||
{
|
||||
_attackEffect?.Play(this, _strikeCenter, _target, _hitHalfWidth);
|
||||
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 void CacheStrikeSnapshot()
|
||||
{
|
||||
_strikeDirection = ResolvePlanarForward();
|
||||
Vector3 strikeStart = CachedTransform.position;
|
||||
strikeStart.y = ResolveStrikePlaneY();
|
||||
strikeStart += _strikeDirection * _forwardOffset;
|
||||
_strikeCenter = strikeStart + _strikeDirection * (_pierceLength * 0.5f);
|
||||
_strikeCenter.y += _hitCenterYOffset;
|
||||
}
|
||||
|
||||
private Vector3 ResolvePlanarForward()
|
||||
{
|
||||
Vector3 forward = CachedTransform.forward;
|
||||
forward.y = 0f;
|
||||
if (forward.sqrMagnitude <= Mathf.Epsilon)
|
||||
{
|
||||
forward = Vector3.forward;
|
||||
}
|
||||
|
||||
forward.Normalize();
|
||||
return forward;
|
||||
}
|
||||
|
||||
private void ApplyPierceDamage()
|
||||
{
|
||||
if (_hitHalfWidth <= 0f || _pierceLength <= 0f) return;
|
||||
|
||||
Vector3 strikeDirection = _strikeDirection;
|
||||
if (strikeDirection.sqrMagnitude <= Mathf.Epsilon) return;
|
||||
|
||||
Vector3 broadPhaseCenter = _strikeCenter;
|
||||
Quaternion broadPhaseRotation = Quaternion.LookRotation(strikeDirection, Vector3.up);
|
||||
|
||||
if (TryQueueRectangleCollisionQuery(broadPhaseCenter, _hitHalfWidth, _pierceLength * 0.5f,
|
||||
strikeDirection, Mathf.Max(1, _maxHitColliders)))
|
||||
{
|
||||
_hitEntityIds.Clear();
|
||||
return;
|
||||
}
|
||||
|
||||
int capacity = Mathf.Max(1, _maxHitColliders);
|
||||
if (_hitResults == null || _hitResults.Length != capacity)
|
||||
{
|
||||
_hitResults = new Collider[capacity];
|
||||
}
|
||||
|
||||
Vector3 halfExtents = new(_hitHalfWidth, _hitHeight * 0.5f, _pierceLength * 0.5f);
|
||||
int hitCount = Physics.OverlapBoxNonAlloc(broadPhaseCenter, halfExtents, _hitResults, broadPhaseRotation,
|
||||
_hitMask, QueryTriggerInteraction.Collide);
|
||||
_hitEntityIds.Clear();
|
||||
for (int i = 0; i < hitCount; i++)
|
||||
{
|
||||
Collider collider = _hitResults[i];
|
||||
if (collider == null) continue;
|
||||
|
||||
TargetableObject targetable = collider.GetComponentInParent<TargetableObject>();
|
||||
if (targetable == null || !targetable.Available || targetable.IsDead) continue;
|
||||
if (!_hitEntityIds.Add(targetable.Id)) continue;
|
||||
|
||||
if (!IsTargetInsidePierce(targetable, broadPhaseCenter, strikeDirection)) continue;
|
||||
|
||||
AIUtility.PerformCollision(targetable, this);
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsTargetInsidePierce(TargetableObject targetable, Vector3 strikeCenter, Vector3 strikeDirection)
|
||||
{
|
||||
Vector3 delta = targetable.CachedTransform.position - strikeCenter;
|
||||
delta.y = 0f;
|
||||
|
||||
Vector3 right = Vector3.Cross(Vector3.up, strikeDirection);
|
||||
float forwardDistance = Vector3.Dot(delta, strikeDirection);
|
||||
float lateralDistance = Vector3.Dot(delta, right);
|
||||
float targetRadius = ResolveTargetCollisionRadius(targetable);
|
||||
|
||||
return Mathf.Abs(forwardDistance) <= _pierceLength * 0.5f + targetRadius &&
|
||||
Mathf.Abs(lateralDistance) <= _hitHalfWidth + targetRadius;
|
||||
}
|
||||
|
||||
private static float ResolveTargetCollisionRadius(TargetableObject targetable)
|
||||
{
|
||||
if (targetable == null)
|
||||
{
|
||||
return 0f;
|
||||
}
|
||||
|
||||
MovementComponent movementComponent = targetable.GetComponent<MovementComponent>();
|
||||
return movementComponent != null ? Mathf.Max(0f, movementComponent.EnemyBodyRadius) : 0f;
|
||||
}
|
||||
|
||||
private float ResolveStrikePlaneY()
|
||||
{
|
||||
if (_target != null && _target.Available)
|
||||
{
|
||||
return _target.CachedTransform.position.y;
|
||||
}
|
||||
|
||||
if (_attackParent != null)
|
||||
{
|
||||
return _attackParent.position.y;
|
||||
}
|
||||
|
||||
if (CachedTransform.parent != null)
|
||||
{
|
||||
return CachedTransform.parent.position.y;
|
||||
}
|
||||
|
||||
return CachedTransform.position.y;
|
||||
}
|
||||
|
||||
protected override bool OnWeaponShow(object userData)
|
||||
{
|
||||
_weaponData = RequireWeaponData<WeaponLanceData>(userData);
|
||||
if (_weaponData == null) return false;
|
||||
WeaponData = _weaponData;
|
||||
|
||||
_currAttackTimer = 0f;
|
||||
_sqrRange = _weaponData.AttackRange * _weaponData.AttackRange;
|
||||
_cachedRotation = CachedTransform.rotation;
|
||||
|
||||
WeaponLanceParamsData paramsData = _weaponData.ParamsData;
|
||||
float configuredHalfWidth = paramsData != null && paramsData.HitHalfWidth > 0f
|
||||
? paramsData.HitHalfWidth
|
||||
: paramsData != null && paramsData.HitRadius > 0f
|
||||
? paramsData.HitRadius
|
||||
: 0.45f;
|
||||
_hitHalfWidth = Mathf.Max(0.1f, configuredHalfWidth);
|
||||
_hitHeight = paramsData != null && paramsData.HitHeight > 0f
|
||||
? Mathf.Max(0.2f, paramsData.HitHeight)
|
||||
: 4f;
|
||||
_hitCenterYOffset = paramsData != null ? paramsData.HitCenterYOffset : 0f;
|
||||
_pierceLength = paramsData != null && paramsData.PierceLength > 0f
|
||||
? paramsData.PierceLength
|
||||
: paramsData != null && paramsData.ThrustDistance > 0f
|
||||
? paramsData.ThrustDistance
|
||||
: _weaponData.AttackRange;
|
||||
_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 LanceThrustAttackEffect();
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
public float HitHalfWidth => _hitHalfWidth;
|
||||
public float PierceLength => _pierceLength;
|
||||
public Vector3 StrikeDirection => _strikeDirection;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: f90612be7695ee549b39473c9b706122
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 5fded69986744ded97edb5f2ac06304f
|
||||
timeCreated: 1771578597
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
namespace Entity.Weapon
|
||||
{
|
||||
public partial class WeaponLightning
|
||||
{
|
||||
private class AttackState : WeaponStateBase
|
||||
{
|
||||
private WeaponLightning _weapon;
|
||||
|
||||
public override WeaponStateType State => WeaponStateType.Attack;
|
||||
public override void OnInit(WeaponBase weapon) => _weapon = weapon as WeaponLightning;
|
||||
|
||||
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()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 25854832a7d3416dacab67bcfef2a2fa
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
namespace Entity.Weapon
|
||||
{
|
||||
public partial class WeaponLightning
|
||||
{
|
||||
private class CheckInRangeState : WeaponStateBase
|
||||
{
|
||||
private WeaponLightning _weapon;
|
||||
|
||||
public override WeaponStateType State => WeaponStateType.Check_InRange;
|
||||
public override void OnInit(WeaponBase weapon) => _weapon = weapon as WeaponLightning;
|
||||
|
||||
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()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 25404ad172434ba38609e797b6d96919
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
namespace Entity.Weapon
|
||||
{
|
||||
public partial class WeaponLightning
|
||||
{
|
||||
private class CheckOutRangeState : WeaponStateBase
|
||||
{
|
||||
private WeaponLightning _weapon;
|
||||
|
||||
public override WeaponStateType State => WeaponStateType.Check_OutRange;
|
||||
public override void OnInit(WeaponBase weapon) => _weapon = weapon as WeaponLightning;
|
||||
|
||||
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()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: f3aa479abb79491d83e8de76806faf7a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
namespace Entity.Weapon
|
||||
{
|
||||
public partial class WeaponLightning
|
||||
{
|
||||
private class IdleState : WeaponStateBase
|
||||
{
|
||||
private WeaponLightning _weapon;
|
||||
|
||||
public override WeaponStateType State => WeaponStateType.Idle;
|
||||
public override void OnInit(WeaponBase weapon) => _weapon = weapon as WeaponLightning;
|
||||
|
||||
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()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 3464037c29774c3ea326dc93fe33551e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,264 @@
|
|||
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 WeaponLightning : WeaponBase
|
||||
{
|
||||
private WeaponLightningData _weaponData;
|
||||
|
||||
private Quaternion _cachedRotation;
|
||||
[SerializeField] private float _rotateSpeed = 5f;
|
||||
|
||||
[SerializeField] private float _takeOffDuration = 0.3f;
|
||||
[SerializeField] private float _flyToTargetDuration = 0.2f;
|
||||
[SerializeField] private float _strikeDuration = 0.1f;
|
||||
[SerializeField] private float _returnDuration = 0.22f;
|
||||
|
||||
[SerializeField] private float _hoverHeight = 15f;
|
||||
|
||||
[SerializeField] private LayerMask _hitMask = ~0;
|
||||
[SerializeField] private int _maxHitColliders = 32;
|
||||
|
||||
private Sequence _attackSequence;
|
||||
private Transform _attackParent;
|
||||
private Vector3 _lockedStrikePoint;
|
||||
|
||||
private Collider[] _hitResults;
|
||||
private readonly HashSet<int> _hitEntityIds = new();
|
||||
|
||||
private float _hitRadius;
|
||||
private float _hitRadiusSqr;
|
||||
|
||||
private IWeaponAttackEffect _attackEffect;
|
||||
|
||||
public override ImpactData GetImpactData()
|
||||
{
|
||||
return new ImpactData(_weaponData.OwnerCamp, _weaponData.Attack, AttackStat);
|
||||
}
|
||||
|
||||
protected override void BuildStates()
|
||||
{
|
||||
RegisterState(new IdleState());
|
||||
RegisterState(new CheckOutRangeState());
|
||||
RegisterState(new CheckInRangeState());
|
||||
RegisterState(new AttackState());
|
||||
}
|
||||
|
||||
protected override void Attack()
|
||||
{
|
||||
StopAttackTween(false);
|
||||
FaceTargetImmediately();
|
||||
|
||||
_isAttacking = true;
|
||||
_lockedStrikePoint = ResolveStrikePoint();
|
||||
_attackParent = CachedTransform.parent;
|
||||
CachedTransform.SetParent(null);
|
||||
|
||||
Vector3 takeOffPosition = CachedTransform.position;
|
||||
Vector3 hoverPosition = _lockedStrikePoint + Vector3.up * Mathf.Max(0.1f, _hoverHeight);
|
||||
|
||||
_attackSequence = DOTween.Sequence();
|
||||
_attackSequence.Append(CachedTransform.DOMove(takeOffPosition, _takeOffDuration).SetEase(Ease.OutQuad));
|
||||
_attackSequence.Append(CachedTransform.DOMove(hoverPosition, _flyToTargetDuration).SetEase(Ease.OutSine));
|
||||
_attackSequence.AppendCallback(() => CachedTransform.LookAt(_lockedStrikePoint));
|
||||
_attackSequence.Append(CachedTransform.DOMove(_lockedStrikePoint, _strikeDuration).SetEase(Ease.InQuad));
|
||||
_attackSequence.AppendCallback(() =>
|
||||
{
|
||||
_attackEffect?.Play(this, _lockedStrikePoint, _target, _hitRadius);
|
||||
ApplyGroundAreaDamage();
|
||||
|
||||
if (_attackParent != null)
|
||||
{
|
||||
CachedTransform.SetParent(_attackParent);
|
||||
}
|
||||
});
|
||||
_attackSequence.Append(CachedTransform.DOLocalMove(Vector3.zero, _returnDuration).SetEase(Ease.OutSine));
|
||||
_attackSequence.AppendCallback(() =>
|
||||
{
|
||||
_isAttacking = false;
|
||||
_attackSequence = null;
|
||||
_attackParent = null;
|
||||
});
|
||||
}
|
||||
|
||||
protected override void Check()
|
||||
{
|
||||
_target = SelectTarget(_sqrRange);
|
||||
}
|
||||
|
||||
private Vector3 ResolveStrikePoint()
|
||||
{
|
||||
if (_target != null && _target.Available)
|
||||
{
|
||||
return _target.CachedTransform.position;
|
||||
}
|
||||
|
||||
return CachedTransform.position;
|
||||
}
|
||||
|
||||
private void ApplyGroundAreaDamage()
|
||||
{
|
||||
if (_hitRadius <= 0f) return;
|
||||
|
||||
if (TryQueueAreaCollisionQuery(_lockedStrikePoint, _hitRadius, Mathf.Max(1, _maxHitColliders)))
|
||||
{
|
||||
_hitEntityIds.Clear();
|
||||
return;
|
||||
}
|
||||
|
||||
if (_hitResults == null || _hitResults.Length == 0) return;
|
||||
|
||||
int hitCount = Physics.OverlapSphereNonAlloc(_lockedStrikePoint, _hitRadius, _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<TargetableObject>();
|
||||
if (targetable == null || !targetable.Available || targetable.IsDead) continue;
|
||||
if (!_hitEntityIds.Add(targetable.Id)) continue;
|
||||
|
||||
Vector3 delta = targetable.CachedTransform.position - _lockedStrikePoint;
|
||||
delta.y = 0f;
|
||||
if (delta.sqrMagnitude > _hitRadiusSqr) continue;
|
||||
|
||||
AIUtility.PerformCollision(targetable, this);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
protected override bool OnWeaponShow(object userData)
|
||||
{
|
||||
_weaponData = RequireWeaponData<WeaponLightningData>(userData);
|
||||
if (_weaponData == null) return false;
|
||||
WeaponData = _weaponData;
|
||||
|
||||
_currAttackTimer = 0f;
|
||||
_sqrRange = _weaponData.AttackRange * _weaponData.AttackRange;
|
||||
_cachedRotation = CachedTransform.rotation;
|
||||
|
||||
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)
|
||||
{
|
||||
_hitResults = new Collider[colliderCapacity];
|
||||
}
|
||||
|
||||
_attackEffect = new LightningStrikeAttackEffect();
|
||||
|
||||
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)
|
||||
{
|
||||
if (_attackParent != null)
|
||||
{
|
||||
CachedTransform.SetParent(_attackParent);
|
||||
CachedTransform.localPosition = Vector3.zero;
|
||||
}
|
||||
else if (CachedTransform.parent != null)
|
||||
{
|
||||
CachedTransform.localPosition = Vector3.zero;
|
||||
}
|
||||
|
||||
CachedTransform.rotation = _cachedRotation;
|
||||
}
|
||||
|
||||
_attackParent = null;
|
||||
_hitEntityIds.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: a8315d9e60c4434ebde9b23c853f27d0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -13,8 +13,6 @@ namespace Entity.Weapon
|
|||
{
|
||||
#region Property
|
||||
|
||||
private const string SectorAngleParamKey = "SectorAngle";
|
||||
|
||||
private WeaponSlashData _weaponData;
|
||||
|
||||
private Quaternion _cachedRotation;
|
||||
|
|
@ -93,12 +91,8 @@ namespace Entity.Weapon
|
|||
|
||||
private void ApplySectorDamage()
|
||||
{
|
||||
if (_attackRadius <= 0f || _hitResults == null || _hitResults.Length == 0) return;
|
||||
if (_attackRadius <= 0f) return;
|
||||
|
||||
int hitCount = Physics.OverlapSphereNonAlloc(_attackCenter, _attackRadius, _hitResults, _hitMask,
|
||||
QueryTriggerInteraction.Collide);
|
||||
|
||||
_hitEntityIds.Clear();
|
||||
Vector3 forward = CachedTransform.forward;
|
||||
forward.y = 0f;
|
||||
if (forward.sqrMagnitude <= Mathf.Epsilon)
|
||||
|
|
@ -107,8 +101,20 @@ namespace Entity.Weapon
|
|||
}
|
||||
|
||||
forward.Normalize();
|
||||
|
||||
float halfAngle = _sectorAngle * 0.5f;
|
||||
if (TryQueueSectorCollisionQuery(_attackCenter, _attackRadius, in forward, halfAngle,
|
||||
Mathf.Max(1, _maxHitColliders)))
|
||||
{
|
||||
_hitEntityIds.Clear();
|
||||
return;
|
||||
}
|
||||
|
||||
if (_hitResults == null || _hitResults.Length == 0) return;
|
||||
|
||||
int hitCount = Physics.OverlapSphereNonAlloc(_attackCenter, _attackRadius, _hitResults, _hitMask,
|
||||
QueryTriggerInteraction.Collide);
|
||||
|
||||
_hitEntityIds.Clear();
|
||||
for (int i = 0; i < hitCount; i++)
|
||||
{
|
||||
Collider collider = _hitResults[i];
|
||||
|
|
@ -176,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)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
fileFormatVersion: 2
|
||||
guid: aa252b664de661c40bee3ddc89dfc7e0
|
||||
guid: 7771e0f6b92ece64395ada5cdea72858
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
using GameFramework;
|
||||
using GameFramework.Event;
|
||||
using UnityEngine;
|
||||
|
||||
namespace CustomEvent
|
||||
{
|
||||
public sealed class ProjectileHitPresentationEventArgs : GameEventArgs
|
||||
{
|
||||
public static readonly int EventId = typeof(ProjectileHitPresentationEventArgs).GetHashCode();
|
||||
|
||||
public override int Id => EventId;
|
||||
|
||||
public int ProjectileEntityId { get; private set; }
|
||||
public int SourceEntityId { get; private set; }
|
||||
public int SourceOwnerEntityId { get; private set; }
|
||||
public int TargetEntityId { get; private set; }
|
||||
public int Damage { get; private set; }
|
||||
public Vector3 HitPosition { get; private set; }
|
||||
public bool ShowHitMarker { get; private set; }
|
||||
public bool ShowHitEffect { get; private set; }
|
||||
public int EffectEntityTypeId { get; private set; }
|
||||
|
||||
public static ProjectileHitPresentationEventArgs Create(int projectileEntityId, int sourceEntityId,
|
||||
int sourceOwnerEntityId, int targetEntityId, int damage, in Vector3 hitPosition, bool showHitMarker,
|
||||
bool showHitEffect, int effectEntityTypeId)
|
||||
{
|
||||
ProjectileHitPresentationEventArgs args = ReferencePool.Acquire<ProjectileHitPresentationEventArgs>();
|
||||
args.ProjectileEntityId = projectileEntityId;
|
||||
args.SourceEntityId = sourceEntityId;
|
||||
args.SourceOwnerEntityId = sourceOwnerEntityId;
|
||||
args.TargetEntityId = targetEntityId;
|
||||
args.Damage = damage;
|
||||
args.HitPosition = hitPosition;
|
||||
args.ShowHitMarker = showHitMarker;
|
||||
args.ShowHitEffect = showHitEffect;
|
||||
args.EffectEntityTypeId = effectEntityTypeId;
|
||||
return args;
|
||||
}
|
||||
|
||||
public override void Clear()
|
||||
{
|
||||
ProjectileEntityId = 0;
|
||||
SourceEntityId = 0;
|
||||
SourceOwnerEntityId = 0;
|
||||
TargetEntityId = 0;
|
||||
Damage = 0;
|
||||
HitPosition = Vector3.zero;
|
||||
ShowHitMarker = false;
|
||||
ShowHitEffect = false;
|
||||
EffectEntityTypeId = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: d02876e26d8342f7af024c697e451ea4
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -91,6 +91,10 @@ namespace Procedure
|
|||
{
|
||||
GameEntry.Entity.HideEntity(entity.Id);
|
||||
}
|
||||
|
||||
HideEntityGroup("Bullet");
|
||||
HideEntityGroup("Projectile");
|
||||
HideEntityGroup("EnemyProjectile");
|
||||
}
|
||||
|
||||
public override void OnDestroy(IFsm<IProcedureManager> procedureOwner)
|
||||
|
|
@ -99,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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ namespace Procedure
|
|||
public Player Player;
|
||||
|
||||
public GameStateBase CurrentGameState => _gameStates[_currentGameState];
|
||||
public GameStateType CurrentGameStateType => _currentGameState;
|
||||
|
||||
private void InitGameState()
|
||||
{
|
||||
|
|
@ -87,7 +88,7 @@ namespace Procedure
|
|||
base.OnEnter(procedureOwner);
|
||||
|
||||
_procedureOwner = procedureOwner;
|
||||
GameEntry.SimulationWorld?.Clear();
|
||||
GameEntry.SimulationWorld?.ClearSimulationState();
|
||||
|
||||
GameEntry.Event.Subscribe(OpenUIFormSuccessEventArgs.EventId, OpenUIFormSuccess);
|
||||
GameEntry.Event.Subscribe(ShowEntitySuccessEventArgs.EventId, ShowEntitySuccess);
|
||||
|
|
@ -135,7 +136,7 @@ namespace Procedure
|
|||
Player = null;
|
||||
|
||||
_procedureOwner = null;
|
||||
GameEntry.SimulationWorld?.Clear();
|
||||
GameEntry.SimulationWorld?.ClearSimulationState();
|
||||
|
||||
GameEntry.Event.Unsubscribe(OpenUIFormSuccessEventArgs.EventId, OpenUIFormSuccess);
|
||||
GameEntry.Event.Unsubscribe(ShowEntitySuccessEventArgs.EventId, ShowEntitySuccess);
|
||||
|
|
@ -169,4 +170,4 @@ namespace Procedure
|
|||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 5f28ff313e954720a04246191b7f9ddd
|
||||
timeCreated: 1771901064
|
||||
|
|
@ -0,0 +1,133 @@
|
|||
using Unity.Mathematics;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Simulation
|
||||
{
|
||||
public sealed partial class SimulationWorld
|
||||
{
|
||||
#region Collision Transient
|
||||
|
||||
public int CollisionCandidateCount => _collisionCandidates.IsCreated ? _collisionCandidates.Length : 0;
|
||||
public int PendingAreaCollisionRequestCount => _areaCollisionRequests.Count;
|
||||
public int LastCollisionQueryCount => _lastCollisionQueryCount;
|
||||
public int LastProjectileCollisionQueryCount => _lastProjectileCollisionQueryCount;
|
||||
public int LastAreaCollisionQueryCount => _lastAreaCollisionQueryCount;
|
||||
public int LastCollisionCandidateCount => _lastCollisionCandidateCount;
|
||||
public int LastProjectileCollisionCandidateCount => _lastProjectileCollisionCandidateCount;
|
||||
public int LastAreaCollisionCandidateCount => _lastAreaCollisionCandidateCount;
|
||||
public int LastResolvedAreaHitCount => _lastResolvedAreaHitCount;
|
||||
public float LastCollisionCellSize => _lastCollisionCellSize;
|
||||
public bool LastCollisionHasEnemyTargets => _lastCollisionHasEnemyTargets;
|
||||
|
||||
private void ResetCollisionRuntimeStats()
|
||||
{
|
||||
_lastCollisionQueryCount = 0;
|
||||
_lastProjectileCollisionQueryCount = 0;
|
||||
_lastAreaCollisionQueryCount = 0;
|
||||
_lastCollisionCandidateCount = 0;
|
||||
_lastProjectileCollisionCandidateCount = 0;
|
||||
_lastAreaCollisionCandidateCount = 0;
|
||||
_lastResolvedAreaHitCount = 0;
|
||||
_lastCollisionCellSize = 0f;
|
||||
_lastCollisionHasEnemyTargets = false;
|
||||
}
|
||||
|
||||
private void PrepareCollisionQueryAndCandidateChannels(int queryCount, int expectedCandidateCount,
|
||||
int bucketCapacity)
|
||||
{
|
||||
InitializeJobDataChannels();
|
||||
EnsureCapacity(ref _collisionQueryInputs, queryCount);
|
||||
EnsureCapacity(ref _collisionCandidates, expectedCandidateCount);
|
||||
EnsureCapacity(ref _enemyCollisionBuckets, bucketCapacity);
|
||||
|
||||
_collisionQueryInputs.Clear();
|
||||
_collisionCandidates.Clear();
|
||||
_enemyCollisionBuckets.Clear();
|
||||
}
|
||||
|
||||
private void AddProjectileCollisionQuery(int queryId, in ProjectileJobOutputData projectile, float radius,
|
||||
int maxTargets = 1)
|
||||
{
|
||||
if (!_collisionQueryInputs.IsCreated || radius <= 0f)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_collisionQueryInputs.Add(new CollisionQueryData
|
||||
{
|
||||
QueryId = queryId,
|
||||
SourceType = CollisionSourceTypeProjectile,
|
||||
SourceEntityId = projectile.EntityId,
|
||||
SourceOwnerEntityId = projectile.OwnerEntityId,
|
||||
SourceWasActiveAtQueryTime = true,
|
||||
Position = projectile.Position,
|
||||
Radius = radius,
|
||||
MaxTargets = math.max(1, maxTargets),
|
||||
ShapeType = CollisionShapeCircle,
|
||||
Direction = new float3(0f, 0f, 1f),
|
||||
HalfAngleDeg = 180f,
|
||||
HalfWidth = 0f,
|
||||
HalfLength = 0f
|
||||
});
|
||||
}
|
||||
|
||||
private void AddAreaCollisionQuery(int queryId, int sourceEntityId, int sourceOwnerEntityId,
|
||||
bool sourceWasActiveAtQueryTime, in Vector3 center, float radius, int maxTargets, int shapeType,
|
||||
in Vector3 direction, float halfAngleDeg, float halfWidth, float halfLength)
|
||||
{
|
||||
if (!_collisionQueryInputs.IsCreated || radius <= 0f)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Vector3 normalizedDirection = direction;
|
||||
normalizedDirection.y = 0f;
|
||||
if (normalizedDirection.sqrMagnitude <= Mathf.Epsilon)
|
||||
{
|
||||
normalizedDirection = Vector3.forward;
|
||||
}
|
||||
else
|
||||
{
|
||||
normalizedDirection.Normalize();
|
||||
}
|
||||
|
||||
_collisionQueryInputs.Add(new CollisionQueryData
|
||||
{
|
||||
QueryId = queryId,
|
||||
SourceType = CollisionSourceTypeArea,
|
||||
SourceEntityId = sourceEntityId,
|
||||
SourceOwnerEntityId = sourceOwnerEntityId,
|
||||
SourceWasActiveAtQueryTime = sourceWasActiveAtQueryTime,
|
||||
Position = new float3(center.x, center.y, center.z),
|
||||
Radius = radius,
|
||||
MaxTargets = math.max(1, maxTargets),
|
||||
ShapeType = shapeType,
|
||||
Direction = new float3(normalizedDirection.x, normalizedDirection.y, normalizedDirection.z),
|
||||
HalfAngleDeg = Mathf.Clamp(halfAngleDeg, 0f, 180f),
|
||||
HalfWidth = Mathf.Max(0f, halfWidth),
|
||||
HalfLength = Mathf.Max(0f, halfLength)
|
||||
});
|
||||
}
|
||||
|
||||
private void AddCollisionCandidate(int queryId, int sourceType, int sourceEntityId, int sourceOwnerEntityId,
|
||||
int targetEntityId, float sqrDistance)
|
||||
{
|
||||
if (!_collisionCandidates.IsCreated)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_collisionCandidates.Add(new CollisionCandidateData
|
||||
{
|
||||
QueryId = queryId,
|
||||
SourceType = sourceType,
|
||||
SourceEntityId = sourceEntityId,
|
||||
SourceOwnerEntityId = sourceOwnerEntityId,
|
||||
TargetEntityId = targetEntityId,
|
||||
SqrDistance = sqrDistance
|
||||
});
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 4b0eaf024db24511988ba3bde25359e7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
using Unity.Mathematics;
|
||||
|
||||
namespace Simulation
|
||||
{
|
||||
public sealed partial class SimulationWorld
|
||||
{
|
||||
#region Enemy Separation Temporal
|
||||
|
||||
private void PrepareEnemySeparationJobBuffers(int enemyCount, int bucketCapacity)
|
||||
{
|
||||
InitializeJobDataChannels();
|
||||
EnsureCapacity(ref _enemyJobSeparationOutputs, enemyCount);
|
||||
_enemyJobSeparationOutputs.Clear();
|
||||
if (enemyCount > 0)
|
||||
{
|
||||
_enemyJobSeparationOutputs.ResizeUninitialized(enemyCount);
|
||||
}
|
||||
|
||||
EnsureCapacity(ref _enemySeparationBuckets, bucketCapacity);
|
||||
_enemySeparationBuckets.Clear();
|
||||
|
||||
EnsureCapacity(ref _enemySeparationPreviousPushes, enemyCount);
|
||||
EnsureCapacity(ref _enemySeparationCurrentPushes, enemyCount);
|
||||
|
||||
if (_enemySeparationPreviousPushes.Length < enemyCount)
|
||||
{
|
||||
int oldLength = _enemySeparationPreviousPushes.Length;
|
||||
_enemySeparationPreviousPushes.ResizeUninitialized(enemyCount);
|
||||
for (int i = oldLength; i < enemyCount; i++)
|
||||
{
|
||||
_enemySeparationPreviousPushes[i] = float2.zero;
|
||||
}
|
||||
}
|
||||
else if (_enemySeparationPreviousPushes.Length > enemyCount)
|
||||
{
|
||||
_enemySeparationPreviousPushes.ResizeUninitialized(enemyCount);
|
||||
}
|
||||
|
||||
_enemySeparationCurrentPushes.Clear();
|
||||
if (enemyCount > 0)
|
||||
{
|
||||
_enemySeparationCurrentPushes.ResizeUninitialized(enemyCount);
|
||||
}
|
||||
}
|
||||
|
||||
private void CommitEnemySeparationTemporalBuffers(int enemyCount)
|
||||
{
|
||||
if (!_enemySeparationPreviousPushes.IsCreated || !_enemySeparationCurrentPushes.IsCreated)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int copyCount = math.min(enemyCount,
|
||||
math.min(_enemySeparationPreviousPushes.Length, _enemySeparationCurrentPushes.Length));
|
||||
for (int i = 0; i < copyCount; i++)
|
||||
{
|
||||
_enemySeparationPreviousPushes[i] = _enemySeparationCurrentPushes[i];
|
||||
}
|
||||
}
|
||||
|
||||
private void OnEnemyAddedToSeparationTemporalBuffers()
|
||||
{
|
||||
if (_enemySeparationPreviousPushes.IsCreated)
|
||||
{
|
||||
_enemySeparationPreviousPushes.Add(float2.zero);
|
||||
}
|
||||
|
||||
if (_enemySeparationCurrentPushes.IsCreated)
|
||||
{
|
||||
_enemySeparationCurrentPushes.Add(float2.zero);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnEnemyRemovedFromSeparationTemporalBuffers(int removedIndex)
|
||||
{
|
||||
if (_enemySeparationPreviousPushes.IsCreated && removedIndex >= 0 &&
|
||||
removedIndex < _enemySeparationPreviousPushes.Length)
|
||||
{
|
||||
_enemySeparationPreviousPushes.RemoveAtSwapBack(removedIndex);
|
||||
}
|
||||
|
||||
if (_enemySeparationCurrentPushes.IsCreated && removedIndex >= 0 &&
|
||||
removedIndex < _enemySeparationCurrentPushes.Length)
|
||||
{
|
||||
_enemySeparationCurrentPushes.RemoveAtSwapBack(removedIndex);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue