Merge pull request #4 from SepComet/P2-JobSystem+Burst

P2 job system+burst
This commit is contained in:
SepComet 2026-04-02 10:29:29 +08:00 committed by GitHub
commit 057024da58
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
217 changed files with 11111 additions and 1224 deletions

1
.gitattributes vendored Normal file
View File

@ -0,0 +1 @@
* text=auto

10
.gitignore vendored
View File

@ -75,15 +75,15 @@ crashlytics-build.properties
# Packed Addressables # Packed Addressables
/[Aa]ssets/[Aa]ddressable[Aa]ssets[Dd]ata/*/*.bin* /[Aa]ssets/[Aa]ddressable[Aa]ssets[Dd]ata/*/*.bin*
# Temporary auto-generated Android Assets /Assets/StreamingAssets
/[Aa]ssets/[Ss]treamingAssets/aa.meta /Assets/StreamingAssets.meta
/[Aa]ssets/[Ss]treamingAssets/aa/*
/UI参考 /UI参考
/AGENTS.md
/bin /bin
/docs/screenshot /docs/screenshot
*.xmind *.xmind
/数据表/__pycache__/ /数据表/__pycache__/
/类吸血鬼项目.md
~$*.xlsx ~$*.xlsx
Assets/GameMain/Configs/ResourceBuilder.xml
/.dotnet

View File

@ -22,6 +22,7 @@ namespace UnityGameFramework.Editor
"UnityGameFramework.Runtime", "UnityGameFramework.Runtime",
#endif #endif
"Assembly-CSharp", "Assembly-CSharp",
"VampireLike"
}; };
private static readonly string[] RuntimeOrEditorAssemblyNames = private static readonly string[] RuntimeOrEditorAssemblyNames =
@ -34,6 +35,8 @@ namespace UnityGameFramework.Editor
"UnityGameFramework.Editor", "UnityGameFramework.Editor",
#endif #endif
"Assembly-CSharp-Editor", "Assembly-CSharp-Editor",
"VampireLike",
"VampireLike.Editor"
}; };
/// <summary> /// <summary>

View File

@ -2,17 +2,17 @@
<UnityGameFramework> <UnityGameFramework>
<ResourceBuilder> <ResourceBuilder>
<Settings> <Settings>
<InternalResourceVersion>3</InternalResourceVersion> <InternalResourceVersion>1</InternalResourceVersion>
<Platforms>33</Platforms> <Platforms>33</Platforms>
<AssetBundleCompression>1</AssetBundleCompression> <AssetBundleCompression>1</AssetBundleCompression>
<CompressionHelperTypeName>UnityGameFramework.Runtime.DefaultCompressionHelper</CompressionHelperTypeName> <CompressionHelperTypeName>UnityGameFramework.Runtime.DefaultCompressionHelper</CompressionHelperTypeName>
<AdditionalCompressionSelected>True</AdditionalCompressionSelected> <AdditionalCompressionSelected>False</AdditionalCompressionSelected>
<ForceRebuildAssetBundleSelected>False</ForceRebuildAssetBundleSelected> <ForceRebuildAssetBundleSelected>False</ForceRebuildAssetBundleSelected>
<BuildEventHandlerTypeName>StarForce.Editor.StarForceBuildEventHandler</BuildEventHandlerTypeName> <BuildEventHandlerTypeName>VampireLike.Editor.VampireLikeBuildEventHandler</BuildEventHandlerTypeName>
<OutputDirectory>D:/Learn/GameLearn/UnityProjects/VampireLike/bin/AssetBundles</OutputDirectory> <OutputDirectory>C:/UnityProjects/VampireLike/bin/AssetBundles</OutputDirectory>
<OutputPackageSelected>True</OutputPackageSelected> <OutputPackageSelected>True</OutputPackageSelected>
<OutputFullSelected>True</OutputFullSelected> <OutputFullSelected>False</OutputFullSelected>
<OutputPackedSelected>True</OutputPackedSelected> <OutputPackedSelected>False</OutputPackedSelected>
</Settings> </Settings>
</ResourceBuilder> </ResourceBuilder>
</UnityGameFramework> </UnityGameFramework>

View File

@ -1,7 +1,5 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: 5461b0fc87a2ab04fbcfd898d18f6107 guid: 5461b0fc87a2ab04fbcfd898d18f6107
labels:
- ResourceExclusive
TextScriptImporter: TextScriptImporter:
externalObjects: {} externalObjects: {}
userData: userData:

View File

@ -17,6 +17,7 @@
<Resource Name="Music/Menu" FileSystem="Resources" LoadType="0" Packed="True" ResourceGroups="Music" /> <Resource Name="Music/Menu" FileSystem="Resources" LoadType="0" Packed="True" ResourceGroups="Music" />
<Resource Name="Scenes" FileSystem="Resources" LoadType="0" Packed="True" /> <Resource Name="Scenes" FileSystem="Resources" LoadType="0" Packed="True" />
<Resource Name="SceneSettings" LoadType="0" Packed="False" /> <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="Sounds" FileSystem="Resources" LoadType="0" Packed="True" />
<Resource Name="Textures" 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" /> <Resource Name="UI/UIForms" FileSystem="UI" LoadType="0" Packed="True" />
@ -42,6 +43,7 @@
<Asset Guid="0f995b3145e0e7247a42da6cef1dbf23" ResourceName="Materials" /> <Asset Guid="0f995b3145e0e7247a42da6cef1dbf23" ResourceName="Materials" />
<Asset Guid="1046dcb12e547564d8b54bd15419a787" ResourceName="Entities" /> <Asset Guid="1046dcb12e547564d8b54bd15419a787" ResourceName="Entities" />
<Asset Guid="1053b0070685be347ab58587156842dc" ResourceName="Localization/Dictionaries" ResourceVariant="zh-tw" /> <Asset Guid="1053b0070685be347ab58587156842dc" ResourceName="Localization/Dictionaries" ResourceVariant="zh-tw" />
<Asset Guid="11143001bcbdc864b8d8fe2083142e5a" ResourceName="Entities" />
<Asset Guid="1478894bc9a1ed241b05b0862a7b8bce" ResourceName="Textures" /> <Asset Guid="1478894bc9a1ed241b05b0862a7b8bce" ResourceName="Textures" />
<Asset Guid="14869ac0d4433f04db1704e39d03412e" ResourceName="Localization/Dictionaries" ResourceVariant="en-us" /> <Asset Guid="14869ac0d4433f04db1704e39d03412e" ResourceName="Localization/Dictionaries" ResourceVariant="en-us" />
<Asset Guid="156d241f796508c4da4fc354a7fbf5a8" ResourceName="UI/UISprites/Common" /> <Asset Guid="156d241f796508c4da4fc354a7fbf5a8" ResourceName="UI/UISprites/Common" />
@ -83,6 +85,7 @@
<Asset Guid="4473d81b14ddb0143addf0e6050d8491" ResourceName="Scenes" /> <Asset Guid="4473d81b14ddb0143addf0e6050d8491" ResourceName="Scenes" />
<Asset Guid="44c8db52241385c45bbb14a1718f17bf" ResourceName="Configs" /> <Asset Guid="44c8db52241385c45bbb14a1718f17bf" ResourceName="Configs" />
<Asset Guid="44cfa1c448225554c961ad6eb667d80b" ResourceName="DataTables" /> <Asset Guid="44cfa1c448225554c961ad6eb667d80b" ResourceName="DataTables" />
<Asset Guid="47a82ffa13c291447ab895cd0bc251cd" ResourceName="Scripts" />
<Asset Guid="4c3865b2ac420cd46a9cde6ab468d016" ResourceName="Materials" /> <Asset Guid="4c3865b2ac420cd46a9cde6ab468d016" ResourceName="Materials" />
<Asset Guid="4ca22ae3bc068c84eb7858d5b9bdf3e2" ResourceName="Fonts" /> <Asset Guid="4ca22ae3bc068c84eb7858d5b9bdf3e2" ResourceName="Fonts" />
<Asset Guid="4f688097e85071841a2c3ba165000c20" ResourceName="Textures" /> <Asset Guid="4f688097e85071841a2c3ba165000c20" ResourceName="Textures" />
@ -94,6 +97,7 @@
<Asset Guid="5b5a6a737c460eb4abc105d6583d405e" ResourceName="Fonts" /> <Asset Guid="5b5a6a737c460eb4abc105d6583d405e" ResourceName="Fonts" />
<Asset Guid="5dcd89912e222bf4c87f76db4044bc5e" ResourceName="Localization/Dictionaries" ResourceVariant="ko-kr" /> <Asset Guid="5dcd89912e222bf4c87f76db4044bc5e" ResourceName="Localization/Dictionaries" ResourceVariant="ko-kr" />
<Asset Guid="5ebb46af6f16ae94e87f64a7dc0a49cb" ResourceName="Entities" /> <Asset Guid="5ebb46af6f16ae94e87f64a7dc0a49cb" ResourceName="Entities" />
<Asset Guid="602d791ab1251f74ca2470c53bf382a3" ResourceName="Scripts" />
<Asset Guid="62af9e5c8f39cfa49af9e10ccf42f1da" ResourceName="UI/UISprites/Common" /> <Asset Guid="62af9e5c8f39cfa49af9e10ccf42f1da" ResourceName="UI/UISprites/Common" />
<Asset Guid="638ff8ae4a0d15047839cd265d3bc296" ResourceName="Music/Background" /> <Asset Guid="638ff8ae4a0d15047839cd265d3bc296" ResourceName="Music/Background" />
<Asset Guid="63fe6ff9ab9e1433f8db4ebd940f2442" ResourceName="Materials" /> <Asset Guid="63fe6ff9ab9e1433f8db4ebd940f2442" ResourceName="Materials" />
@ -133,6 +137,7 @@
<Asset Guid="99d811b0183246646a2ce8df996f4bca" ResourceName="Fonts" /> <Asset Guid="99d811b0183246646a2ce8df996f4bca" ResourceName="Fonts" />
<Asset Guid="9afa958d6d8235941b9badb42aae4370" ResourceName="Meshes" /> <Asset Guid="9afa958d6d8235941b9badb42aae4370" ResourceName="Meshes" />
<Asset Guid="9be2e1e45f4edd74c8764538ad306b78" ResourceName="Localization/Dictionaries" ResourceVariant="zh-cn" /> <Asset Guid="9be2e1e45f4edd74c8764538ad306b78" ResourceName="Localization/Dictionaries" ResourceVariant="zh-cn" />
<Asset Guid="9d193ac5b4294e0e9ba6e867320944b7" ResourceName="Entities" />
<Asset Guid="9ddab293e2a8af3499dac05f5fd6169c" ResourceName="Meshes" /> <Asset Guid="9ddab293e2a8af3499dac05f5fd6169c" ResourceName="Meshes" />
<Asset Guid="9f5bba6d2f5c95049a59fcb56df2d38f" ResourceName="UI/UIItems" /> <Asset Guid="9f5bba6d2f5c95049a59fcb56df2d38f" ResourceName="UI/UIItems" />
<Asset Guid="9f847ec5e66e03e4ead1d3c5f7b510e8" ResourceName="UI/UISprites/Common" /> <Asset Guid="9f847ec5e66e03e4ead1d3c5f7b510e8" ResourceName="UI/UISprites/Common" />
@ -157,6 +162,7 @@
<Asset Guid="ba157ba55f72c424a9e88f3c029997c4" ResourceName="Textures" /> <Asset Guid="ba157ba55f72c424a9e88f3c029997c4" ResourceName="Textures" />
<Asset Guid="baedbbad82997f445a8cb4da210404e0" ResourceName="Meshes" /> <Asset Guid="baedbbad82997f445a8cb4da210404e0" ResourceName="Meshes" />
<Asset Guid="bbfd75fe6fe00e1448fe988173ede7f9" ResourceName="UI/UIForms" /> <Asset Guid="bbfd75fe6fe00e1448fe988173ede7f9" ResourceName="UI/UIForms" />
<Asset Guid="bc065bcf1474d7d4387fafd202678c37" ResourceName="Fonts" />
<Asset Guid="bf75b984df8a84987bcf3a8bf6e2862d" ResourceName="Sounds" /> <Asset Guid="bf75b984df8a84987bcf3a8bf6e2862d" ResourceName="Sounds" />
<Asset Guid="c40be3174f62c4acf8c1216858c64956" ResourceName="URPAssets" /> <Asset Guid="c40be3174f62c4acf8c1216858c64956" ResourceName="URPAssets" />
<Asset Guid="c49cffd4fc1dfb549b2b30448a0becda" ResourceName="UI/UISprites/Icons" /> <Asset Guid="c49cffd4fc1dfb549b2b30448a0becda" ResourceName="UI/UISprites/Icons" />

View File

@ -1,5 +1,6 @@
# 敌人基础属性表 # 敌人基础属性表
# Id EntityTypeId MaxHealth HpAddPerLevel Speed CoinDrop ExpDrop DropPercent # Id EntityTypeId MaxHealth HpAddPerLevel AttackDamage AttackCooldown AttackRange Speed CoinDrop ExpDrop DropPercent Params
# int int int int float int int float # int int int int int float float float int int float string
# 敌人编号 策划备注 敌人实体编号 最大生命 每关卡增加生命 移动速度 金币掉落 经验掉落 掉落概率 # 敌人编号 策划备注 敌人实体编号 最大生命 每关卡增加生命 基础伤害 攻击间隔 攻击范围 移动速度 金币掉落 经验掉落 掉落概率 额外参数
1 近战敌人 101 50 50 3 5 1 0.3 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 []

View File

@ -2,12 +2,14 @@
# Id AssetName # Id AssetName
# int string # int string
# 实体编号 策划备注 资源名称 # 实体编号 策划备注 资源名称
11 跟随相机 FollowCamera
1001 测试玩家 Player 1001 测试玩家 Player
101 近战敌人 MeleeEnemy 101 近战敌人 MeleeEnemy
102 远程敌人 RemoteEnemy 102 远程敌人 RemoteEnemy
11 跟随相机 FollowCamera
201 武器小刀 WeaponKnife 201 武器小刀 WeaponKnife
202 武器手枪 WeaponHandgun 202 武器手枪 WeaponHandgun
203 武器斧头 WeaponSlash 203 武器斧头 WeaponSlash
204 武器闪电 WeaponLightning
205 武器长枪 WeaponLance
10001 金币实体 CoinEntity 10001 金币实体 CoinEntity
10002 经验实体 ExpEntity 10002 经验实体 ExpEntity

View File

@ -1,5 +1,4 @@
# 商品表 # Id 列1 GoodsType GoodsTypeId
# Id GoodsType GoodsTypeId
# int GoodsType int # int GoodsType int
# 商品编号 策划备注 商品类型 商品对应物品Id # 商品编号 策划备注 商品类型 商品对应物品Id
101 道具:药 Prop 101 101 道具:药 Prop 101
@ -25,3 +24,5 @@
121 Prop 119 121 Prop 119
122 Prop 120 122 Prop 120
123 Weapon 3 123 Weapon 3
124 Weapon 4
125 Weapon 5

View File

@ -2,7 +2,7 @@
# Id EnemyTypes EntityCounts Interval Duration # Id EnemyTypes EntityCounts Interval Duration
# int int[] int[] float[] int # int int[] int[] float[] int
# 关卡号 策划备注 敌人类型 每次出怪数量 每次出怪间隔 关卡时间 # 关卡号 策划备注 敌人类型 每次出怪数量 每次出怪间隔 关卡时间
1 第一关 [1] [5] [2] 60 1 第一关 [1,2] [5,2] [4,5] 60
2 第二关 [1] [10] [3] 60 2 第二关 [1] [10] [3] 60
3 第三关 [1] [10] [3] 60 3 第三关 [1] [10] [3] 60
4 第四关 [1] [10] [3] 60 4 第四关 [1] [10] [3] 60

View File

@ -1,7 +1,8 @@
# 武器表 # Id 列1 EntityTypeId Title IconAssetName Rarity Price PriceRandomPercent Attack Cooldown AttackRange AttackSoundId Pramas Modifiers
# Id EntityTypeId Title IconAssetName Rarity Price PriceRandomPercent Attack Cooldown AttackRange AttackSoundId Pramas Modifiers
# int int string string RarityType int float int float float int string[] StatModifier[] # int int string string RarityType int float int float float int string[] StatModifier[]
# 武器编号 策划备注 武器实体编号 武器名 图标资源名 道具品质 武器价格 价格浮动 伤害 冷却 范围 攻击音效编号 额外参数 额外属性 # 武器编号 策划备注 武器实体编号 武器名 图标资源名 道具品质 武器价格 价格浮动 伤害 冷却 范围 攻击音效编号 额外参数 额外属性
1 玩家武器 201 小刀 Almighty_Icon White 120 0.05 100 1.5 5 10000 [hitRadius:2] [] 1 玩家武器 201 小刀 Almighty_Icon White 120 0.05 100 1.5 5 10000 {"hitRadius":2} []
2 202 手枪 Almighty_Icon White 130 0.05 120 1 15 10000 [] [] 2 202 手枪 Almighty_Icon White 130 0.05 120 1 15 10000 {} []
3 203 斧头 Almighty_Icon White 100 0.1 150 2 5 10000 [SectorAngle:120] [] 3 203 斧头 Almighty_Icon White 100 0.1 150 2 5 10000 {"sectorAngle":120} []
4 204 闪电 Almighty_Icon White 150 0.08 80 3 12 10000 {"hitRadius":3} []
5 205 长枪 Almighty_Icon White 100 0.1 100 1.5 5 10000 {"hitHalfWidth":0.7,"pierceLength":4.5,"hitHeight":0.5,"hitCenterYOffset":0}

View File

@ -11,7 +11,6 @@ GameObject:
- component: {fileID: 7683855655592166216} - component: {fileID: 7683855655592166216}
- component: {fileID: 6418687210998749921} - component: {fileID: 6418687210998749921}
- component: {fileID: 4710806460657047075} - component: {fileID: 4710806460657047075}
- component: {fileID: 8116679074104541426}
- component: {fileID: 1932268889601128120} - component: {fileID: 1932268889601128120}
- component: {fileID: 557030043145096197} - component: {fileID: 557030043145096197}
- component: {fileID: 6353753365317756414} - component: {fileID: 6353753365317756414}
@ -87,33 +86,6 @@ MeshRenderer:
m_SortingLayer: 0 m_SortingLayer: 0
m_SortingOrder: 0 m_SortingOrder: 0
m_AdditionalVertexStreams: {fileID: 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 --- !u!136 &1932268889601128120
CapsuleCollider: CapsuleCollider:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0
@ -131,7 +103,7 @@ CapsuleCollider:
m_LayerOverridePriority: 0 m_LayerOverridePriority: 0
m_IsTrigger: 1 m_IsTrigger: 1
m_ProvidesContacts: 0 m_ProvidesContacts: 0
m_Enabled: 1 m_Enabled: 0
serializedVersion: 2 serializedVersion: 2
m_Radius: 0.5 m_Radius: 0.5
m_Height: 2 m_Height: 2
@ -154,7 +126,7 @@ MonoBehaviour:
_cachedTransform: {fileID: 7683855655592166216} _cachedTransform: {fileID: 7683855655592166216}
_avoidEnemyOverlap: 0 _avoidEnemyOverlap: 0
_enemyBodyRadius: 0.45 _enemyBodyRadius: 0.45
_separationIterations: 2 _separationIterations: 5
_speedBase: 0 _speedBase: 0
--- !u!114 &6353753365317756414 --- !u!114 &6353753365317756414
MonoBehaviour: MonoBehaviour:

View File

@ -150,13 +150,13 @@ Transform:
m_PrefabAsset: {fileID: 0} m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 5383497626468778460} m_GameObject: {fileID: 5383497626468778460}
serializedVersion: 2 serializedVersion: 2
m_LocalRotation: {x: 0.7071068, y: 0, z: 0, w: 0.7071068} m_LocalRotation: {x: 0.5, y: 0, z: 0, w: 0.8660254}
m_LocalPosition: {x: 0, y: 15, z: 0} m_LocalPosition: {x: 0, y: 15, z: -10}
m_LocalScale: {x: 1, y: 1, z: 1} m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0 m_ConstrainProportionsScale: 0
m_Children: [] m_Children: []
m_Father: {fileID: 9112716898534404901} m_Father: {fileID: 9112716898534404901}
m_LocalEulerAnglesHint: {x: 90, y: 0, z: 0} m_LocalEulerAnglesHint: {x: 60, y: 0, z: 0}
--- !u!20 &4064848608618185461 --- !u!20 &4064848608618185461
Camera: Camera:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0
@ -189,9 +189,9 @@ Camera:
width: 1 width: 1
height: 1 height: 1
near clip plane: 0.3 near clip plane: 0.3
far clip plane: 100 far clip plane: 200
field of view: 80 field of view: 80
orthographic: 1 orthographic: 0
orthographic size: 15 orthographic size: 15
m_Depth: 0 m_Depth: 0
m_CullingMask: m_CullingMask:

View File

@ -14,7 +14,7 @@ GameObject:
- component: {fileID: 1932268889601128120} - component: {fileID: 1932268889601128120}
- component: {fileID: 557030043145096197} - component: {fileID: 557030043145096197}
- component: {fileID: 6353753365317756414} - component: {fileID: 6353753365317756414}
m_Layer: 7 m_Layer: 8
m_Name: RemoteEnemy m_Name: RemoteEnemy
m_TagString: Untagged m_TagString: Untagged
m_Icon: {fileID: 0} m_Icon: {fileID: 0}
@ -103,7 +103,7 @@ CapsuleCollider:
m_LayerOverridePriority: 0 m_LayerOverridePriority: 0
m_IsTrigger: 0 m_IsTrigger: 0
m_ProvidesContacts: 0 m_ProvidesContacts: 0
m_Enabled: 1 m_Enabled: 0
serializedVersion: 2 serializedVersion: 2
m_Radius: 0.5 m_Radius: 0.5
m_Height: 2 m_Height: 2
@ -124,6 +124,9 @@ MonoBehaviour:
_isMoving: 0 _isMoving: 0
_direction: {x: 0, y: 0, z: 0} _direction: {x: 0, y: 0, z: 0}
_cachedTransform: {fileID: 0} _cachedTransform: {fileID: 0}
_avoidEnemyOverlap: 0
_enemyBodyRadius: 0.45
_separationIterations: 2
_speedBase: 0 _speedBase: 0
--- !u!114 &6353753365317756414 --- !u!114 &6353753365317756414
MonoBehaviour: MonoBehaviour:

View File

@ -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}

View File

@ -1,6 +1,6 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: 9b0d24fd3a44b6f45b3794cdfefd1ac0 guid: 11143001bcbdc864b8d8fe2083142e5a
DefaultImporter: PrefabImporter:
externalObjects: {} externalObjects: {}
userData: userData:
assetBundleName: assetBundleName:

View File

@ -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}

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 9d193ac5b4294e0e9ba6e867320944b7
PrefabImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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);
}

View File

@ -1,6 +1,6 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: 0372088d74296e44c9eb9185a2d4021e guid: dba6242363c96c44eb6ec1e124d487e4
DefaultImporter: ShaderIncludeImporter:
externalObjects: {} externalObjects: {}
userData: userData:
assetBundleName: assetBundleName:

View File

@ -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;
}

View File

@ -1,6 +1,6 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: a7d40db10aa401f4db624fec03b19854 guid: d9a5c27d283a4e7429acad6face5485a
DefaultImporter: ShaderIncludeImporter:
externalObjects: {} externalObjects: {}
userData: userData:
assetBundleName: assetBundleName:

View File

@ -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;

View File

@ -1,6 +1,6 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: a5e646a0b90b55940be09d61810d429d guid: 8218173e722cac14996fc86da8882fc8
DefaultImporter: ShaderIncludeImporter:
externalObjects: {} externalObjects: {}
userData: userData:
assetBundleName: assetBundleName:

View File

@ -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;
}

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 0511409892ffdb7409b52349d8ceceee
ShaderIncludeImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -2,7 +2,6 @@ using System;
using CustomUtility; using CustomUtility;
using Definition.DataStruct; using Definition.DataStruct;
using Definition.Enum; using Definition.Enum;
using Unity.Profiling;
using UnityEngine; using UnityEngine;
using CustomDebugger; using CustomDebugger;

View File

@ -1,3 +1,4 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
using GameFramework.ObjectPool; using GameFramework.ObjectPool;
using TMPro; using TMPro;
@ -9,7 +10,7 @@ namespace CustomComponent
{ {
public class DamageTextComponent : GameFrameworkComponent public class DamageTextComponent : GameFrameworkComponent
{ {
[SerializeField] private int _instancePoolCapacity = 32; [SerializeField] private int _instancePoolCapacity = 256;
[SerializeField] private string _poolName = "DamageTextItem"; [SerializeField] private string _poolName = "DamageTextItem";
@ -43,6 +44,12 @@ namespace CustomComponent
private DamageTextItem CreateDamageTextItem() private DamageTextItem CreateDamageTextItem()
{ {
if (_activeDamageTextItems.Count == _instancePoolCapacity)
{
_instancePoolCapacity = Mathf.Min(_instancePoolCapacity * 2, 1024);
_damageTextItemPool.Capacity = _instancePoolCapacity;
}
DamageTextItemObject itemObject = _damageTextItemPool.Spawn(); DamageTextItemObject itemObject = _damageTextItemPool.Spawn();
if (itemObject != null) if (itemObject != null)
{ {
@ -63,5 +70,12 @@ namespace CustomComponent
_activeDamageTextItems.Remove(item); _activeDamageTextItems.Remove(item);
_damageTextItemPool.Unspawn(item); _damageTextItemPool.Unspawn(item);
} }
private void OnDestroy()
{
_activeDamageTextItems.Clear();
_damageTextItemPool.Release();
_damageTextItemPool = null;
}
} }
} }

View File

@ -1,6 +1,8 @@
#if UNITY_EDITOR || DEVELOPMENT_BUILD #if UNITY_EDITOR || DEVELOPMENT_BUILD
using System; using System;
using System.Linq; using System.Linq;
using Components;
using CustomEvent;
using DataTable; using DataTable;
using Definition.DataStruct; using Definition.DataStruct;
using Entity; using Entity;
@ -19,8 +21,20 @@ namespace CustomComponent
private const float MinSpawnRate = 0.1f; private const float MinSpawnRate = 0.1f;
private const float CornerTapWindow = 0.6f; private const float CornerTapWindow = 0.6f;
private const int RequiredCornerTapCount = 3; 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 bool _isPanelVisible;
private int _windowId; private int _windowId;
@ -38,6 +52,7 @@ namespace CustomComponent
private int _cornerTapCount; private int _cornerTapCount;
private float _lastCornerTapTime = -10f; private float _lastCornerTapTime = -10f;
private bool _lockPlayerHealthToMax;
protected override void Awake() protected override void Awake()
{ {
@ -54,6 +69,10 @@ namespace CustomComponent
} }
HandleCornerTapGesture(); HandleCornerTapGesture();
if (_lockPlayerHealthToMax)
{
KeepPlayerHealthAtMax();
}
} }
private void OnGUI() private void OnGUI()
@ -77,20 +96,42 @@ namespace CustomComponent
private void DrawWindow(int windowId) private void DrawWindow(int windowId)
{ {
EnsurePropList(); if (_showBuffSection)
{
EnsurePropList();
}
GUILayout.BeginVertical(); GUILayout.BeginVertical();
DrawBuffSection(); bool hasPreviousSection = false;
if (_showBuffSection)
{
DrawBuffSection();
hasPreviousSection = true;
}
GUILayout.Space(8f); if (HasVisibleBattleSection())
GUILayout.Label(string.Empty, GUI.skin.horizontalSlider); {
GUILayout.Space(8f); if (hasPreviousSection)
{
GUILayout.Space(8f);
GUILayout.Label(string.Empty, GUI.skin.horizontalSlider);
GUILayout.Space(8f);
}
DrawBattleSection(); DrawBattleSection();
hasPreviousSection = true;
}
GUILayout.Space(8f); if (_showTips)
GUILayout.Label("Tips: press `F8` or tap top-left corner 3 times to toggle.", GUILayout.Height(20f)); {
if (hasPreviousSection)
{
GUILayout.Space(8f);
}
GUILayout.Label("Tips: press `F8` or tap top-left corner 3 times to toggle.", GUILayout.Height(20f));
}
GUILayout.EndVertical(); GUILayout.EndVertical();
GUI.DragWindow(new Rect(0, 0, 10000, 22)); GUI.DragWindow(new Rect(0, 0, 10000, 22));
@ -147,6 +188,7 @@ namespace CustomComponent
ProcedureGame procedure = GameEntry.Procedure.CurrentProcedure as ProcedureGame; ProcedureGame procedure = GameEntry.Procedure.CurrentProcedure as ProcedureGame;
EnemyManagerComponent enemyManager = GameEntry.EnemyManager; EnemyManagerComponent enemyManager = GameEntry.EnemyManager;
Player player = FindPlayer(); Player player = FindPlayer();
HealthComponent playerHealth = player != null ? player.GetComponent<HealthComponent>() : null;
if (enemyManager == null) if (enemyManager == null)
{ {
@ -160,86 +202,160 @@ namespace CustomComponent
return; return;
} }
GUILayout.Label($"Spawn Rate: {enemyManager.SpawnRateScale:F2}"); if (_showBattleOverview)
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))
{ {
_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); GUILayout.Space(4f);
} GUILayout.Label(
$"Collision Queries: total {simulationWorld.LastCollisionQueryCount} (Projectile {simulationWorld.LastProjectileCollisionQueryCount} / Area {simulationWorld.LastAreaCollisionQueryCount})");
if (GUILayout.Button("x0.5", GUILayout.Width(60f))) GUILayout.Label(
{ $"Collision Candidates: total {simulationWorld.LastCollisionCandidateCount} (Projectile {simulationWorld.LastProjectileCollisionCandidateCount} / Area {simulationWorld.LastAreaCollisionCandidateCount})");
_spawnRateScaleInput = Mathf.Max(MinSpawnRate, enemyManager.SpawnRateScale * 0.5f); GUILayout.Label(
enemyManager.SetSpawnRateScale(_spawnRateScaleInput); $"Area Resolve: hits {simulationWorld.LastResolvedAreaHitCount}");
} GUILayout.Label(
$"Broad Phase: cell {simulationWorld.LastCollisionCellSize:F2}, hasEnemyTargets {(simulationWorld.LastCollisionHasEnemyTargets ? "Yes" : "No")}");
if (GUILayout.Button("x2", GUILayout.Width(60f))) if (simulationWorld.LastCollisionCandidateCount != 0)
{
_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)
{ {
gameState.AddBattleDuration(_extendDurationSeconds); Log.Info($"LastCollisionCandidateCount:{simulationWorld.LastCollisionCandidateCount}");
}
if (simulationWorld.LastResolvedAreaHitCount != 0)
{
Log.Info($"LastResolvedAreaHitCount:{simulationWorld.LastResolvedAreaHitCount}");
} }
} }
GUILayout.EndHorizontal(); if (_showSpawnControls)
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(); 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(); if (_showSeparationSolverControls)
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); 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; if (_showPlayerHealthControls)
GUILayout.EndHorizontal(); {
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) private void EnsurePropList(bool force = false)
@ -299,6 +415,52 @@ namespace CustomComponent
return UnityEngine.Object.FindObjectOfType<Player>(); 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) private static void AddSelectedBuffToPlayer(DRProp prop, int count)
{ {
Player player = FindPlayer(); Player player = FindPlayer();

View File

@ -20,6 +20,7 @@ namespace CustomComponent
private EntityComponent _entity; private EntityComponent _entity;
private List<EntityBase> _enemies; private List<EntityBase> _enemies;
private Dictionary<int, EntityBase> _enemyById;
public List<EntityBase> Enemies => _enemies; public List<EntityBase> Enemies => _enemies;
@ -58,6 +59,7 @@ namespace CustomComponent
{ {
_entity = GameEntry.Entity; _entity = GameEntry.Entity;
_enemies = new List<EntityBase>(); _enemies = new List<EntityBase>();
_enemyById = new Dictionary<int, EntityBase>();
GameEntry.Event.Subscribe(ShowEntitySuccessEventArgs.EventId, OnShowEntitySuccess); GameEntry.Event.Subscribe(ShowEntitySuccessEventArgs.EventId, OnShowEntitySuccess);
GameEntry.Event.Subscribe(HideEntityCompleteEventArgs.EventId, OnHideEntityComplete); GameEntry.Event.Subscribe(HideEntityCompleteEventArgs.EventId, OnHideEntityComplete);
@ -65,10 +67,14 @@ namespace CustomComponent
private void OnDestroy() private void OnDestroy()
{ {
GameEntry.Event.Unsubscribe(ShowEntitySuccessEventArgs.EventId, OnShowEntitySuccess); if (GameEntry.Event != null)
GameEntry.Event.Unsubscribe(HideEntityCompleteEventArgs.EventId, OnHideEntityComplete); {
GameEntry.Event.Unsubscribe(ShowEntitySuccessEventArgs.EventId, OnShowEntitySuccess);
GameEntry.Event.Unsubscribe(HideEntityCompleteEventArgs.EventId, OnHideEntityComplete);
}
_enemies = null; _enemies = null;
_enemyById = null;
_entity = null; _entity = null;
} }
@ -157,6 +163,25 @@ namespace CustomComponent
} }
_enemies.Clear(); _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) public void SetSpawnRateScale(float scale)
@ -218,6 +243,7 @@ namespace CustomComponent
enemy.SetTarget(_player); enemy.SetTarget(_player);
RemoveEnemyFromCache(enemy.Id); RemoveEnemyFromCache(enemy.Id);
_enemies.Add(enemy); _enemies.Add(enemy);
_enemyById[enemy.Id] = enemy;
} }
if (ne.EntityLogicType == typeof(Player)) if (ne.EntityLogicType == typeof(Player))
@ -245,11 +271,21 @@ namespace CustomComponent
private void RemoveEnemyFromCache(int entityId) private void RemoveEnemyFromCache(int entityId)
{ {
if (_enemyById != null)
{
_enemyById.Remove(entityId);
}
for (int i = _enemies.Count - 1; i >= 0; i--) for (int i = _enemies.Count - 1; i >= 0; i--)
{ {
EntityBase cachedEnemy = _enemies[i]; EntityBase cachedEnemy = _enemies[i];
if (cachedEnemy == null || cachedEnemy.Id == entityId) if (cachedEnemy == null || cachedEnemy.Id == entityId)
{ {
if (cachedEnemy != null && _enemyById != null)
{
_enemyById.Remove(cachedEnemy.Id);
}
_enemies.RemoveAt(i); _enemies.RemoveAt(i);
} }
} }

View File

@ -1,3 +1,5 @@
using System;
using System.Collections.Generic;
using UnityGameFramework.Runtime; using UnityGameFramework.Runtime;
namespace DataTable namespace DataTable
@ -14,6 +16,12 @@ namespace DataTable
public int HpAddPerLevel { 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 float Speed { get; private set; }
public int DropCoin { get; private set; } public int DropCoin { get; private set; }
@ -22,6 +30,8 @@ namespace DataTable
public float DropPercent { 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) public override bool ParseDataRow(string dataRowString, object userData)
{ {
string[] columnStrings = dataRowString.Split(DataTableExtension.DataSplitSeparators); string[] columnStrings = dataRowString.Split(DataTableExtension.DataSplitSeparators);
@ -33,12 +43,47 @@ namespace DataTable
EntityTypeId = int.Parse(columnStrings[index++]); EntityTypeId = int.Parse(columnStrings[index++]);
MaxHealth = int.Parse(columnStrings[index++]); MaxHealth = int.Parse(columnStrings[index++]);
HpAddPerLevel = 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++]); Speed = float.Parse(columnStrings[index++]);
DropCoin = int.Parse(columnStrings[index++]); DropCoin = int.Parse(columnStrings[index++]);
DropExp = int.Parse(columnStrings[index++]); DropExp = int.Parse(columnStrings[index++]);
DropPercent = float.Parse(columnStrings[index++]); DropPercent = float.Parse(columnStrings[index++]);
Params = DeserializeParams(columnStrings[index++]);
return true; 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;
}
} }
} }

View File

@ -1,10 +1,10 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization;
using Definition.DataStruct; using Definition.DataStruct;
using Definition.Enum; using Definition.Enum;
using GameFramework; using GameFramework;
using CustomUtility; using CustomUtility;
using Newtonsoft.Json.Linq;
using UnityEngine; using UnityEngine;
using UnityGameFramework.Runtime; using UnityGameFramework.Runtime;
@ -74,6 +74,11 @@ namespace DataTable
/// </summary> /// </summary>
public Dictionary<string, string> Pramas { get; private set; } public Dictionary<string, string> Pramas { get; private set; }
/// <summary>
/// 获取武器额外参数 Json。
/// </summary>
public string ParamsJson { get; private set; }
/// <summary> /// <summary>
/// 获取武器额外属性。 /// 获取武器额外属性。
/// </summary> /// </summary>
@ -97,7 +102,8 @@ namespace DataTable
Cooldown = float.Parse(columnStrings[index++]); Cooldown = float.Parse(columnStrings[index++]);
AttackRange = float.Parse(columnStrings[index++]); AttackRange = float.Parse(columnStrings[index++]);
AttackSoundId = int.Parse(columnStrings[index++]); AttackSoundId = int.Parse(columnStrings[index++]);
Pramas = DeserializeParams(columnStrings[index++]); ParamsJson = columnStrings[index++];
Pramas = DeserializeParams(ParamsJson);
Modifiers = Utility.Json.ToObject<StatModifier[]>(columnStrings[index++]); Modifiers = Utility.Json.ToObject<StatModifier[]>(columnStrings[index++]);
GeneratePropertyArray(); GeneratePropertyArray();
@ -109,26 +115,40 @@ namespace DataTable
{ {
} }
/// <summary>
/// 解参数
/// </summary>
/// <param name="rawParams"></param>
/// <returns></returns>
private Dictionary<string, string> DeserializeParams(string rawParams) 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>(); try
if (string.IsNullOrEmpty(rawParams)) return dict;
string[] items = rawParams.Substring(1, rawParams.Length - 2).Split(";");
foreach (var item in items)
{ {
string entry = item.Trim(); JObject paramObject = Utility.Json.ToObject<JObject>(rawParams);
if (string.IsNullOrEmpty(entry)) continue; if (paramObject == null)
{
return dict;
}
string[] pair = entry.Split(':' , StringSplitOptions.RemoveEmptyEntries); foreach (var pair in paramObject)
if (pair.Length != 2) continue; {
dict.Add(pair[0].ToLower(), pair[1]); 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; return dict;

View File

@ -4,12 +4,25 @@ namespace CustomDebugger
{ {
public static class CustomProfilerMarker public static class CustomProfilerMarker
{ {
public static readonly ProfilerMarker TickEnemies = new ProfilerMarker("TickEnemies"); public static readonly ProfilerMarker TickEnemies = new("TickEnemies");
public static readonly ProfilerMarker TickEnemies_BuildInput = new ProfilerMarker("TickEnemies.BuildInput"); public static readonly ProfilerMarker TickEnemies_BuildInput = new("TickEnemies.BuildInput");
public static readonly ProfilerMarker TickEnemies_MoveSeparation = new ProfilerMarker("TickEnemies.MoveSeparation"); public static readonly ProfilerMarker TickEnemies_StateUpdate = new("TickEnemies.StateUpdate");
public static readonly ProfilerMarker TickEnemies_StateUpdate = new ProfilerMarker("TickEnemies.StateUpdate"); public static readonly ProfilerMarker TickEnemies_Schedule = new("TickEnemies.Schedule");
public static readonly ProfilerMarker TickEnemies_WriteBack = new ProfilerMarker("TickEnemies.WriteBack"); public static readonly ProfilerMarker TickEnemies_Complete = new("TickEnemies.Complete");
public static readonly ProfilerMarker Movement_Update = new ProfilerMarker("Movement_Update"); 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 ShopUI_Update = new("UGF.ShopUI.Update");
public static readonly ProfilerMarker Inventory_Refresh = new("UGF.Inventory.Refresh"); public static readonly ProfilerMarker Inventory_Refresh = new("UGF.Inventory.Refresh");
} }

View File

@ -6,5 +6,7 @@ namespace Definition.Enum
WeaponKnife = 1, WeaponKnife = 1,
WeaponHandgun = 2, WeaponHandgun = 2,
WeaponSlash = 3, WeaponSlash = 3,
WeaponLightning = 4,
WeaponLance = 5,
} }
} }

View File

@ -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
}

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 602d791ab1251f74ca2470c53bf382a3
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,19 +1,12 @@
//------------------------------------------------------------ using GameFramework;
// Game Framework
// Copyright © 2013-2021 Jiang Yin. All rights reserved.
// Homepage: https://gameframework.cn/
// Feedback: mailto:ellan@gameframework.cn
//------------------------------------------------------------
using GameFramework;
using System.IO; using System.IO;
using UnityEditor; using UnityEditor;
using UnityEngine; using UnityEngine;
using UnityGameFramework.Editor.ResourceTools; using UnityGameFramework.Editor.ResourceTools;
namespace StarForce.Editor namespace VampireLike.Editor
{ {
public sealed class StarForceBuildEventHandler : IBuildEventHandler public sealed class VampireLikeBuildEventHandler : IBuildEventHandler
{ {
public bool ContinueOnFailure public bool ContinueOnFailure
{ {

View File

@ -1,8 +1,7 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: 64311189c3f9ae140b59a31db9831950 guid: 64311189c3f9ae140b59a31db9831950
timeCreated: 1528026151
licenseType: Pro
MonoImporter: MonoImporter:
externalObjects: {}
serializedVersion: 2 serializedVersion: 2
defaultReferences: [] defaultReferences: []
executionOrder: 0 executionOrder: 0

View File

@ -1,4 +1,5 @@
using System; using System;
using System.Collections.Generic;
using DataTable; using DataTable;
using Definition.Enum; using Definition.Enum;
using UnityEngine; using UnityEngine;
@ -8,17 +9,7 @@ namespace Entity.EntityData
[Serializable] [Serializable]
public class EnemyData : TargetableObjectData public class EnemyData : TargetableObjectData
{ {
[SerializeField] private EnemyType _enemyType; [SerializeField] private DREnemy _drEnemy;
[SerializeField] private int _entityTypeId;
[SerializeField] private float _speedBase = 0;
[SerializeField] private int _dropCoin = 0;
[SerializeField] private int _dropExp = 0;
[SerializeField] private float _dropPercent = 0;
public EnemyData(int entityId, EnemyType enemyType, int level) : base( public EnemyData(int entityId, EnemyType enemyType, int level) : base(
entityId, (int)enemyType, CampType.Enemy) entityId, (int)enemyType, CampType.Enemy)
@ -29,30 +20,46 @@ namespace Entity.EntityData
{ {
throw new Exception($"Enemy data table row is missing, EnemyType='{enemyType}'."); throw new Exception($"Enemy data table row is missing, EnemyType='{enemyType}'.");
} }
else
{
_drEnemy = enemyRow;
}
int effectiveLevel = Mathf.Max(1, level); int effectiveLevel = Mathf.Max(1, level);
_enemyType = enemyType;
_entityTypeId = enemyRow.EntityTypeId;
MaxHealthBase = enemyRow.MaxHealth + enemyRow.HpAddPerLevel * (effectiveLevel - 1); MaxHealthBase = enemyRow.MaxHealth + enemyRow.HpAddPerLevel * (effectiveLevel - 1);
_speedBase = enemyRow.Speed;
_dropCoin = enemyRow.DropCoin;
_dropExp = enemyRow.DropExp;
_dropPercent = enemyRow.DropPercent;
} }
public EnemyType EnemyType => _enemyType; public EnemyType EnemyType => (EnemyType)_drEnemy.Id;
public int EntityTypeId => _entityTypeId; public int EntityTypeId => _drEnemy.EntityTypeId;
public override int MaxHealthBase { get; } 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);
}
} }
} }

View File

@ -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; }
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 52b38f83ab6c4029803d40c189db47c7
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -3,6 +3,7 @@ using System.Collections.Generic;
using DataTable; using DataTable;
using Definition.DataStruct; using Definition.DataStruct;
using Definition.Enum; using Definition.Enum;
using GameFramework;
namespace Entity.EntityData namespace Entity.EntityData
{ {
@ -28,14 +29,35 @@ namespace Entity.EntityData
public WeaponType WeaponType => (WeaponType)_drWeapon.Id; 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> /// <summary>
@ -79,6 +101,8 @@ namespace Entity.EntityData
/// </summary> /// </summary>
public Dictionary<string, string> Params => _drWeapon.Pramas; public Dictionary<string, string> Params => _drWeapon.Pramas;
public string ParamsJson => _drWeapon.ParamsJson;
/// <summary> /// <summary>
/// 额外属性。 /// 额外属性。
/// </summary> /// </summary>

View File

@ -1,12 +1,21 @@
using System;
using Definition.Enum; using Definition.Enum;
namespace Entity.EntityData namespace Entity.EntityData
{ {
[Serializable]
public sealed class WeaponHandgunParamsData
{
}
public class WeaponHandgunData : WeaponData public class WeaponHandgunData : WeaponData
{ {
public WeaponHandgunParamsData ParamsData { get; }
public WeaponHandgunData(int entityId, int ownerId, CampType ownerCamp) public WeaponHandgunData(int entityId, int ownerId, CampType ownerCamp)
: base(entityId, WeaponType.WeaponHandgun, ownerId, ownerCamp) : base(entityId, WeaponType.WeaponHandgun, ownerId, ownerCamp)
{ {
ParamsData = ParseParams<WeaponHandgunParamsData>();
} }
} }
} }

View File

@ -1,12 +1,22 @@
using System;
using Definition.Enum; using Definition.Enum;
namespace Entity.EntityData namespace Entity.EntityData
{ {
[Serializable]
public sealed class WeaponKnifeParamsData
{
public float HitRadius { get; set; }
}
public class WeaponKnifeData : WeaponData public class WeaponKnifeData : WeaponData
{ {
public WeaponKnifeParamsData ParamsData { get; }
public WeaponKnifeData(int entityId, int ownerId, CampType ownerCamp) : base(entityId, WeaponType.WeaponKnife, public WeaponKnifeData(int entityId, int ownerId, CampType ownerCamp) : base(entityId, WeaponType.WeaponKnife,
ownerId, ownerCamp) ownerId, ownerCamp)
{ {
ParamsData = ParseParams<WeaponKnifeParamsData>();
} }
} }
} }

View File

@ -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>();
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4d1a821e8ee1a9a4b912b70b0a1616eb
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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>();
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 25b0006918fd46959c7f6b8ec1bbc8ab
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,12 +1,22 @@
using System;
using Definition.Enum; using Definition.Enum;
namespace Entity.EntityData namespace Entity.EntityData
{ {
[Serializable]
public sealed class WeaponSlashParamsData
{
public float SectorAngle { get; set; }
}
public class WeaponSlashData : WeaponData public class WeaponSlashData : WeaponData
{ {
public WeaponSlashParamsData ParamsData { get; }
public WeaponSlashData(int entityId, int ownerId, CampType ownerCamp) public WeaponSlashData(int entityId, int ownerId, CampType ownerCamp)
: base(entityId, WeaponType.WeaponSlash, ownerId, ownerCamp) : base(entityId, WeaponType.WeaponSlash, ownerId, ownerCamp)
{ {
ParamsData = ParseParams<WeaponSlashParamsData>();
} }
} }
} }

View File

@ -7,6 +7,7 @@ public abstract class EnemyBase : TargetableObject
protected Transform _target; protected Transform _target;
public abstract override ImpactData GetImpactData(); public abstract override ImpactData GetImpactData();
public virtual float AttackRange => 1f;
public virtual void SetTarget(Transform target) => _target = target; public virtual void SetTarget(Transform target) => _target = target;

View File

@ -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;
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 22624f81b9364c8681b32d993f5e618f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,7 +1,8 @@
using Components; using Components;
using CustomUtility;
using Definition.DataStruct; using Definition.DataStruct;
using Definition.Enum;
using Entity.EntityData; using Entity.EntityData;
using Entity.Weapon;
using UnityEngine; using UnityEngine;
using UnityGameFramework.Runtime; using UnityGameFramework.Runtime;
@ -9,17 +10,33 @@ namespace Entity
{ {
public class MeleeEnemy : EnemyBase public class MeleeEnemy : EnemyBase
{ {
private enum AttackStateType
{
Idle,
Check_OutRange,
Check_InRange,
Attack
}
private MovementComponent _movementComponent; private MovementComponent _movementComponent;
private float _attackRange = 1f; 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 EnemyData _meleeEnemyData;
private WeaponBase _weapon; private TargetableObject _targetableTarget;
protected override TargetableObjectData _targetableObjectData => _meleeEnemyData; protected override TargetableObjectData _targetableObjectData => _meleeEnemyData;
public override float AttackRange => _attackRange;
public override ImpactData GetImpactData() public override ImpactData GetImpactData()
{ {
return new ImpactData(_meleeEnemyData.Camp, 0); return new ImpactData(_meleeEnemyData.Camp, _attackDamage);
} }
#region FSM #region FSM
@ -42,44 +59,35 @@ namespace Entity
_healthComponent.OnInit(enemyData.MaxHealthBase); _healthComponent.OnInit(enemyData.MaxHealthBase);
_movementComponent.OnInit(_meleeEnemyData.SpeedBase, this.CachedTransform, null, true); _movementComponent.OnInit(_meleeEnemyData.SpeedBase, this.CachedTransform, null, true);
_movementComponent.SetMove(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; this.CachedTransform.position = enemyData.Position;
} }
else else
{ {
Log.Error($"Invalid data type. Data type: {userData?.GetType()}"); Log.Error($"Invalid data type. Data type: {userData?.GetType()}");
} }
} }
protected override void OnUpdate(float elapseSeconds, float realElapseSeconds) protected override void OnUpdate(float elapseSeconds, float realElapseSeconds)
{ {
base.OnUpdate(elapseSeconds, realElapseSeconds);
UpdateAttackState(elapseSeconds);
if (IsSimulationMovementEnabled()) if (IsSimulationMovementEnabled())
{ {
return; 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); _movementComponent.OnUpdate(elapseSeconds, realElapseSeconds);
} }
@ -110,12 +118,143 @@ namespace Entity
{ {
_movementComponent.OnReset(); _movementComponent.OnReset();
_healthComponent.OnReset(); _healthComponent.OnReset();
_targetableTarget = null;
_currAttackTimer = 0f;
_attackState = AttackStateType.Idle;
base.OnHide(isShutdown, userData); base.OnHide(isShutdown, userData);
} }
#endregion #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() private Vector3 GetTargetDirection()
{ {
if (_target == null) if (_target == null)

View File

@ -1,4 +1,6 @@
using Components; using Components;
using CustomUtility;
using Definition;
using Definition.DataStruct; using Definition.DataStruct;
using Entity.EntityData; using Entity.EntityData;
using UnityEngine; using UnityEngine;
@ -8,17 +10,39 @@ namespace Entity
{ {
public class RemoteEnemy : EnemyBase 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 MovementComponent _movementComponent;
private float _attackRange = 1f; private float _attackRange = 1f;
private float _attackRangeSquared; 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; private EnemyData _remoteEnemyData;
protected override TargetableObjectData _targetableObjectData => _remoteEnemyData; protected override TargetableObjectData _targetableObjectData => _remoteEnemyData;
public override float AttackRange => _attackRange;
public override ImpactData GetImpactData() public override ImpactData GetImpactData()
{ {
return new ImpactData(_remoteEnemyData.Camp, 0); return new ImpactData(_remoteEnemyData.Camp, _attackDamage);
} }
protected override void OnInit(object userData) protected override void OnInit(object userData)
@ -39,7 +63,21 @@ namespace Entity
_healthComponent.OnInit(enemyData.MaxHealthBase); _healthComponent.OnInit(enemyData.MaxHealthBase);
_movementComponent.OnInit(_remoteEnemyData.SpeedBase, this.CachedTransform, null, true); _movementComponent.OnInit(_remoteEnemyData.SpeedBase, this.CachedTransform, null, true);
_movementComponent.SetMove(true); _movementComponent.SetMove(true);
_attackRange = Mathf.Max(0.1f, _remoteEnemyData.AttackRange);
_attackRangeSquared = _attackRange * _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; this.CachedTransform.position = enemyData.Position;
} }
else else
@ -50,25 +88,28 @@ namespace Entity
protected override void OnUpdate(float elapseSeconds, float realElapseSeconds) protected override void OnUpdate(float elapseSeconds, float realElapseSeconds)
{ {
if (IsSimulationMovementEnabled())
{
return;
}
base.OnUpdate(elapseSeconds, realElapseSeconds); base.OnUpdate(elapseSeconds, realElapseSeconds);
_currAttackTimer += elapseSeconds;
if (_target == null) if (_target == null)
{ {
_movementComponent.SetMove(false); _movementComponent.SetMove(false);
_movementComponent.OnUpdate(elapseSeconds, realElapseSeconds); if (!IsSimulationMovementEnabled())
{
_movementComponent.OnUpdate(elapseSeconds, realElapseSeconds);
}
return; return;
} }
float distanceSquared = (this.CachedTransform.position - _target.position).sqrMagnitude; Vector3 toTarget = _target.position - this.CachedTransform.position;
if (distanceSquared < _attackRangeSquared) toTarget.y = 0f;
float distanceSquared = toTarget.sqrMagnitude;
if (distanceSquared <= _attackRangeSquared)
{ {
// 攻击
_movementComponent.SetMove(false); _movementComponent.SetMove(false);
TryFireProjectile();
} }
else else
{ {
@ -76,17 +117,115 @@ namespace Entity
_movementComponent.SetDirection(GetTargetDirection()); _movementComponent.SetDirection(GetTargetDirection());
} }
_movementComponent.OnUpdate(elapseSeconds, realElapseSeconds); if (!IsSimulationMovementEnabled())
{
_movementComponent.OnUpdate(elapseSeconds, realElapseSeconds);
}
} }
protected override void OnHide(bool isShutdown, object userData) protected override void OnHide(bool isShutdown, object userData)
{ {
_movementComponent.OnReset(); _movementComponent.OnReset();
_healthComponent.OnReset(); _healthComponent.OnReset();
_currAttackTimer = 0f;
base.OnHide(isShutdown, userData); 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() private Vector3 GetTargetDirection()
{ {
if (_target == null) if (_target == null)

View File

@ -61,8 +61,8 @@ namespace Entity
private void OnTriggerEnter(Collider other) private void OnTriggerEnter(Collider other)
{ {
EntityBase entity = other.gameObject.GetComponent<EntityBase>(); EntityBase entity = other.GetComponentInParent<EntityBase>();
if (entity == null) if (entity == null || entity == this)
{ {
return; return;
} }
@ -70,7 +70,7 @@ namespace Entity
if (entity is TargetableObject && entity.Id < Id) if (entity is TargetableObject && entity.Id < Id)
{ {
// 碰撞事件由 Id 大的一方处理 // 碰撞事件由 Id 大的一方处理
// 在这里规定所有的 Enemy 的 Id 均大于 0 // 在这里约定 Enemy 的 Id 为非负数(通常从 0 开始)
// 而其他的 Entity (Player, Weapon, Bullet) 的 Id 均小于 0 // 而其他的 Entity (Player, Weapon, Bullet) 的 Id 均小于 0
return; return;
} }

View File

@ -1,13 +1,23 @@
using GameFramework.ObjectPool;
using UnityEngine; using UnityEngine;
namespace Entity.Weapon namespace Entity.Weapon
{ {
public sealed class HandgunHitMarkerAttackEffect : IWeaponAttackEffect 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 _size;
private readonly float _yOffset; private readonly float _yOffset;
private readonly float _duration; private readonly float _duration;
private readonly Color _color; private readonly Color _color;
private Material _sharedMaterial;
public HandgunHitMarkerAttackEffect(float size, float yOffset, float duration, Color color) 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) public void Play(WeaponBase weapon, Vector3 position, EntityBase target, float radius)
{ {
if (target == null) return; if (target == null) return;
if (!TrySpawnMarker(out HandgunHitMarkerPooledInstance markerInstance))
{
return;
}
GameObject marker = GameObject.CreatePrimitive(PrimitiveType.Sphere); Transform targetTransform = target.CachedTransform;
marker.name = "HandgunHitMarker"; 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) if (collider != null)
{ {
Object.Destroy(collider); Object.Destroy(collider);
} }
marker.transform.SetParent(target.CachedTransform, false); markerInstance = markerGameObject.AddComponent<HandgunHitMarkerPooledInstance>();
marker.transform.localPosition = new Vector3(0f, _yOffset, 0f); markerGameObject.SetActive(false);
marker.transform.localScale = Vector3.one * Mathf.Max(0.01f, _size); pool.Register(HandgunHitMarkerPoolObject.Create(markerInstance), true);
return true;
}
Renderer renderer = marker.GetComponent<Renderer>(); private static IObjectPool<HandgunHitMarkerPoolObject> EnsurePool()
if (renderer != null) {
var poolComponent = GameEntry.ObjectPool;
if (poolComponent == null)
{ {
Shader shader = Shader.Find("Sprites/Default"); return null;
if (shader == null)
{
shader = Shader.Find("Unlit/Color");
}
Material material = new Material(shader);
material.color = _color;
renderer.material = material;
} }
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;
} }
} }
} }

View File

@ -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);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 966d34a18f5d00c49bddea3c7c5ec13d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 7ff8a67466d8f0643845c41fd5952f50
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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));
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8c77c590c7233e544a9295ed41ff8827
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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));
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 81cbdf8961ad419c91b989d56ca782d0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,5 +1,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using CustomUtility; using CustomUtility;
using CustomComponent;
namespace Entity.Weapon namespace Entity.Weapon
{ {
@ -12,6 +13,11 @@ namespace Entity.Weapon
return null; return null;
} }
if (TrySelectFromSpatialIndex(weapon, maxSqrRange, out EntityBase indexedTarget))
{
return indexedTarget;
}
EntityBase target = null; EntityBase target = null;
float minSqrMagnitude = maxSqrRange > 0f ? maxSqrRange : float.MaxValue; float minSqrMagnitude = maxSqrRange > 0f ? maxSqrRange : float.MaxValue;
@ -28,5 +34,35 @@ namespace Entity.Weapon
return target; 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;
}
} }
} }

View File

@ -220,6 +220,46 @@ namespace Entity.Weapon
return AIUtility.GetSqrMagnitudeXZ(this, target) < sqrRange; 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) protected void SetTargetSelector(TargetSelectorType selectorType)
{ {
TargetSelector = CreateSelector(selectorType); TargetSelector = CreateSelector(selectorType);
@ -278,12 +318,4 @@ namespace Entity.Weapon
public abstract void OnLeave(); public abstract void OnLeave();
public override string ToString() => State.ToString(); public override string ToString() => State.ToString();
} }
} }

View File

@ -46,22 +46,15 @@ namespace Entity.Weapon
{ {
FaceTargetImmediately(); FaceTargetImmediately();
Vector3 fireOrigin = CachedTransform.TransformPoint(_fireOriginOffset); if (!TryResolveAttackTarget(out TargetableObject targetable, out Vector3 hitPosition))
Vector3 fireDirection = CachedTransform.forward;
float maxDistance = Mathf.Max(0.1f, _weaponData.AttackRange);
if (Physics.Raycast(fireOrigin, fireDirection, out RaycastHit hit, maxDistance, _hitMask,
QueryTriggerInteraction.Collide))
{ {
TargetableObject targetable = hit.collider.GetComponentInParent<TargetableObject>(); return;
if (targetable != null && targetable.Available && !targetable.IsDead)
{
_attackEffect?.Play(this, hit.point, targetable, 0f);
_isAttacking = true;
AIUtility.PerformCollision(targetable, this);
_isAttacking = false;
}
} }
_attackEffect?.Play(this, hitPosition, targetable, 0f);
_isAttacking = true;
AIUtility.PerformCollision(targetable, this);
_isAttacking = false;
} }
protected override void Check() protected override void Check()
@ -95,6 +88,40 @@ namespace Entity.Weapon
CachedTransform.rotation = Quaternion.LookRotation(directionToTarget.normalized, Vector3.up); 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 #region Lifecycle
protected override bool OnWeaponShow(object userData) protected override bool OnWeaponShow(object userData)

View File

@ -11,8 +11,6 @@ namespace Entity.Weapon
{ {
public partial class WeaponKnife : WeaponBase public partial class WeaponKnife : WeaponBase
{ {
private const string HitRadiusParamKey = "HitRadius";
private WeaponKnifeData _weaponData; private WeaponKnifeData _weaponData;
private Quaternion _cachedRotation; private Quaternion _cachedRotation;
@ -116,7 +114,15 @@ namespace Entity.Weapon
private void ApplyGroundAreaDamage() 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, int hitCount = Physics.OverlapSphereNonAlloc(_attackCenter, _hitRadius, _hitResults, _hitMask,
QueryTriggerInteraction.Collide); QueryTriggerInteraction.Collide);
@ -149,12 +155,8 @@ namespace Entity.Weapon
_sqrRange = _weaponData.AttackRange * _weaponData.AttackRange; _sqrRange = _weaponData.AttackRange * _weaponData.AttackRange;
_cachedRotation = CachedTransform.rotation; _cachedRotation = CachedTransform.rotation;
string hitRadiusRaw = _weaponData.GetParamsString(HitRadiusParamKey); float configuredHitRadius = _weaponData.ParamsData != null ? _weaponData.ParamsData.HitRadius : 0f;
if (!float.TryParse(hitRadiusRaw, out _hitRadius)) _hitRadius = configuredHitRadius > 0f ? Mathf.Max(0.1f, configuredHitRadius) : _weaponData.AttackRange;
{
_hitRadius = _weaponData.AttackRange;
}
_hitRadius = Mathf.Max(0.1f, _hitRadius);
_hitRadiusSqr = _hitRadius * _hitRadius; _hitRadiusSqr = _hitRadius * _hitRadius;
_attackEffect = new KnifeRangeAttackEffect(); _attackEffect = new KnifeRangeAttackEffect();

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: 1b6ba43d50137a44a9cac13aee7c79b4 guid: aee5d5037e73e894cac11712e321b930
folderAsset: yes folderAsset: yes
DefaultImporter: DefaultImporter:
externalObjects: {} externalObjects: {}

View File

@ -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()
{
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d9991ee189b733b4e9d40395357f5ef2
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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()
{
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 01edc8bb0ff46a14d8028e03af932b52
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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()
{
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8009ad2c332a956438e3357ed5e30eb6
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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()
{
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4c9e98f5381e579469dcdac7bf3e1e25
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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;
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f90612be7695ee549b39473c9b706122
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 5fded69986744ded97edb5f2ac06304f
timeCreated: 1771578597

View File

@ -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()
{
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 25854832a7d3416dacab67bcfef2a2fa
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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()
{
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 25404ad172434ba38609e797b6d96919
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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()
{
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f3aa479abb79491d83e8de76806faf7a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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()
{
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3464037c29774c3ea326dc93fe33551e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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();
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a8315d9e60c4434ebde9b23c853f27d0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -13,8 +13,6 @@ namespace Entity.Weapon
{ {
#region Property #region Property
private const string SectorAngleParamKey = "SectorAngle";
private WeaponSlashData _weaponData; private WeaponSlashData _weaponData;
private Quaternion _cachedRotation; private Quaternion _cachedRotation;
@ -93,12 +91,8 @@ namespace Entity.Weapon
private void ApplySectorDamage() 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; Vector3 forward = CachedTransform.forward;
forward.y = 0f; forward.y = 0f;
if (forward.sqrMagnitude <= Mathf.Epsilon) if (forward.sqrMagnitude <= Mathf.Epsilon)
@ -107,8 +101,20 @@ namespace Entity.Weapon
} }
forward.Normalize(); forward.Normalize();
float halfAngle = _sectorAngle * 0.5f; 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++) for (int i = 0; i < hitCount; i++)
{ {
Collider collider = _hitResults[i]; Collider collider = _hitResults[i];
@ -176,15 +182,8 @@ namespace Entity.Weapon
_attackRadius = Mathf.Max(0.1f, _weaponData.AttackRange); _attackRadius = Mathf.Max(0.1f, _weaponData.AttackRange);
_attackRadiusSqr = _attackRadius * _attackRadius; _attackRadiusSqr = _attackRadius * _attackRadius;
_sectorAngle = 90f; float configuredSectorAngle = _weaponData.ParamsData != null ? _weaponData.ParamsData.SectorAngle : 0f;
if (_weaponData.Params != null && _sectorAngle = configuredSectorAngle > 0f ? Mathf.Clamp(configuredSectorAngle, 1f, 360f) : 90f;
_weaponData.Params.TryGetValue(SectorAngleParamKey.ToLower(), out string rawAngle))
{
if (float.TryParse(rawAngle, out float parsedAngle))
{
_sectorAngle = Mathf.Clamp(parsedAngle, 1f, 360f);
}
}
int capacity = Mathf.Max(1, _maxHitColliders); int capacity = Mathf.Max(1, _maxHitColliders);
if (_hitResults == null || _hitResults.Length != capacity) if (_hitResults == null || _hitResults.Length != capacity)

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: aa252b664de661c40bee3ddc89dfc7e0 guid: 7771e0f6b92ece64395ada5cdea72858
folderAsset: yes folderAsset: yes
DefaultImporter: DefaultImporter:
externalObjects: {} externalObjects: {}

View File

@ -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;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d02876e26d8342f7af024c697e451ea4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -91,6 +91,10 @@ namespace Procedure
{ {
GameEntry.Entity.HideEntity(entity.Id); GameEntry.Entity.HideEntity(entity.Id);
} }
HideEntityGroup("Bullet");
HideEntityGroup("Projectile");
HideEntityGroup("EnemyProjectile");
} }
public override void OnDestroy(IFsm<IProcedureManager> procedureOwner) public override void OnDestroy(IFsm<IProcedureManager> procedureOwner)
@ -99,6 +103,21 @@ namespace Procedure
_procedureGame = null; _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 #endregion
} }
} }

View File

@ -35,6 +35,7 @@ namespace Procedure
public Player Player; public Player Player;
public GameStateBase CurrentGameState => _gameStates[_currentGameState]; public GameStateBase CurrentGameState => _gameStates[_currentGameState];
public GameStateType CurrentGameStateType => _currentGameState;
private void InitGameState() private void InitGameState()
{ {
@ -87,7 +88,7 @@ namespace Procedure
base.OnEnter(procedureOwner); base.OnEnter(procedureOwner);
_procedureOwner = procedureOwner; _procedureOwner = procedureOwner;
GameEntry.SimulationWorld?.Clear(); GameEntry.SimulationWorld?.ClearSimulationState();
GameEntry.Event.Subscribe(OpenUIFormSuccessEventArgs.EventId, OpenUIFormSuccess); GameEntry.Event.Subscribe(OpenUIFormSuccessEventArgs.EventId, OpenUIFormSuccess);
GameEntry.Event.Subscribe(ShowEntitySuccessEventArgs.EventId, ShowEntitySuccess); GameEntry.Event.Subscribe(ShowEntitySuccessEventArgs.EventId, ShowEntitySuccess);
@ -135,7 +136,7 @@ namespace Procedure
Player = null; Player = null;
_procedureOwner = null; _procedureOwner = null;
GameEntry.SimulationWorld?.Clear(); GameEntry.SimulationWorld?.ClearSimulationState();
GameEntry.Event.Unsubscribe(OpenUIFormSuccessEventArgs.EventId, OpenUIFormSuccess); GameEntry.Event.Unsubscribe(OpenUIFormSuccessEventArgs.EventId, OpenUIFormSuccess);
GameEntry.Event.Unsubscribe(ShowEntitySuccessEventArgs.EventId, ShowEntitySuccess); GameEntry.Event.Unsubscribe(ShowEntitySuccessEventArgs.EventId, ShowEntitySuccess);

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 5f28ff313e954720a04246191b7f9ddd
timeCreated: 1771901064

View File

@ -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
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4b0eaf024db24511988ba3bde25359e7
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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