第一次提交
This commit is contained in:
91
.editorconfig
Normal file
91
.editorconfig
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
[*.{cpp,h}]
|
||||||
|
|
||||||
|
# Naming convention rules (note: currently need to be ordered from more to less specific)
|
||||||
|
|
||||||
|
cpp_naming_rule.aactor_prefixed.symbols = aactor_class
|
||||||
|
cpp_naming_rule.aactor_prefixed.style = aactor_style
|
||||||
|
|
||||||
|
cpp_naming_rule.swidget_prefixed.symbols = swidget_class
|
||||||
|
cpp_naming_rule.swidget_prefixed.style = swidget_style
|
||||||
|
|
||||||
|
cpp_naming_rule.uobject_prefixed.symbols = uobject_class
|
||||||
|
cpp_naming_rule.uobject_prefixed.style = uobject_style
|
||||||
|
|
||||||
|
cpp_naming_rule.booleans_prefixed.symbols = boolean_vars
|
||||||
|
cpp_naming_rule.booleans_prefixed.style = boolean_style
|
||||||
|
|
||||||
|
cpp_naming_rule.structs_prefixed.symbols = structs
|
||||||
|
cpp_naming_rule.structs_prefixed.style = unreal_engine_structs
|
||||||
|
|
||||||
|
cpp_naming_rule.enums_prefixed.symbols = enums
|
||||||
|
cpp_naming_rule.enums_prefixed.style = unreal_engine_enums
|
||||||
|
|
||||||
|
cpp_naming_rule.templates_prefixed.symbols = templates
|
||||||
|
cpp_naming_rule.templates_prefixed.style = unreal_engine_templates
|
||||||
|
|
||||||
|
cpp_naming_rule.general_names.symbols = all_symbols
|
||||||
|
cpp_naming_rule.general_names.style = unreal_engine_default
|
||||||
|
|
||||||
|
# Naming convention symbols
|
||||||
|
|
||||||
|
cpp_naming_symbols.aactor_class.applicable_kinds = class
|
||||||
|
cpp_naming_symbols.aactor_class.applicable_type = AActor
|
||||||
|
|
||||||
|
cpp_naming_symbols.swidget_class.applicable_kinds = class
|
||||||
|
cpp_naming_symbols.swidget_class.applicable_type = SWidget
|
||||||
|
|
||||||
|
cpp_naming_symbols.uobject_class.applicable_kinds = class
|
||||||
|
cpp_naming_symbols.uobject_class.applicable_type = UObject
|
||||||
|
|
||||||
|
cpp_naming_symbols.boolean_vars.applicable_kinds = local,parameter,field
|
||||||
|
cpp_naming_symbols.boolean_vars.applicable_type = bool
|
||||||
|
|
||||||
|
cpp_naming_symbols.enums.applicable_kinds = enum
|
||||||
|
|
||||||
|
cpp_naming_symbols.templates.applicable_kinds = template_class
|
||||||
|
|
||||||
|
cpp_naming_symbols.structs.applicable_kinds = struct
|
||||||
|
|
||||||
|
cpp_naming_symbols.all_symbols.applicable_kinds = *
|
||||||
|
|
||||||
|
# Naming convention styles
|
||||||
|
|
||||||
|
cpp_naming_style.unreal_engine_default.capitalization = pascal_case
|
||||||
|
cpp_naming_style.unreal_engine_default.required_prefix =
|
||||||
|
cpp_naming_style.unreal_engine_default.required_suffix =
|
||||||
|
cpp_naming_style.unreal_engine_default.word_separator =
|
||||||
|
|
||||||
|
cpp_naming_style.unreal_engine_enums.capitalization = pascal_case
|
||||||
|
cpp_naming_style.unreal_engine_enums.required_prefix = E
|
||||||
|
cpp_naming_style.unreal_engine_enums.required_suffix =
|
||||||
|
cpp_naming_style.unreal_engine_enums.word_separator =
|
||||||
|
|
||||||
|
cpp_naming_style.unreal_engine_templates.capitalization = pascal_case
|
||||||
|
cpp_naming_style.unreal_engine_templates.required_prefix = T
|
||||||
|
cpp_naming_style.unreal_engine_templates.required_suffix =
|
||||||
|
cpp_naming_style.unreal_engine_templates.word_separator =
|
||||||
|
|
||||||
|
cpp_naming_style.unreal_engine_structs.capitalization = pascal_case
|
||||||
|
cpp_naming_style.unreal_engine_structs.required_prefix = F
|
||||||
|
cpp_naming_style.unreal_engine_structs.required_suffix =
|
||||||
|
cpp_naming_style.unreal_engine_structs.word_separator =
|
||||||
|
|
||||||
|
cpp_naming_style.uobject_style.capitalization = pascal_case
|
||||||
|
cpp_naming_style.uobject_style.required_prefix = U
|
||||||
|
cpp_naming_style.uobject_style.required_suffix =
|
||||||
|
cpp_naming_style.uobject_style.word_separator =
|
||||||
|
|
||||||
|
cpp_naming_style.aactor_style.capitalization = pascal_case
|
||||||
|
cpp_naming_style.aactor_style.required_prefix = A
|
||||||
|
cpp_naming_style.aactor_style.required_suffix =
|
||||||
|
cpp_naming_style.aactor_style.word_separator =
|
||||||
|
|
||||||
|
cpp_naming_style.swidget_style.capitalization = pascal_case
|
||||||
|
cpp_naming_style.swidget_style.required_prefix = S
|
||||||
|
cpp_naming_style.swidget_style.required_suffix =
|
||||||
|
cpp_naming_style.swidget_style.word_separator =
|
||||||
|
|
||||||
|
cpp_naming_style.boolean_style.capitalization = pascal_case
|
||||||
|
cpp_naming_style.boolean_style.required_prefix = b
|
||||||
|
cpp_naming_style.boolean_style.required_suffix =
|
||||||
|
cpp_naming_style.boolean_style.word_separator =
|
||||||
83
.gitignore
vendored
Normal file
83
.gitignore
vendored
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
# Unreal Engine - minimal tracked project
|
||||||
|
# Goal: only keep source/config/plugins + required game content (Content/AGame)
|
||||||
|
|
||||||
|
########################
|
||||||
|
# Always keep (tracked)
|
||||||
|
########################
|
||||||
|
!.gitignore
|
||||||
|
|
||||||
|
# Project files
|
||||||
|
!*.uproject
|
||||||
|
!*.uplugin
|
||||||
|
|
||||||
|
# Config + source
|
||||||
|
!Config/**
|
||||||
|
!Source/**
|
||||||
|
!Plugins/**
|
||||||
|
|
||||||
|
########################
|
||||||
|
# Content: keep only required game content
|
||||||
|
########################
|
||||||
|
# Ignore all cooked/asset content by default...
|
||||||
|
Content/**
|
||||||
|
# ...but keep the minimal required folders
|
||||||
|
!Content/AGame/**
|
||||||
|
|
||||||
|
# (Optional) keep top-level Content structure files if you use them
|
||||||
|
!Content/*.uprojectdirs
|
||||||
|
|
||||||
|
########################
|
||||||
|
# Build / generated / cache (ignore)
|
||||||
|
########################
|
||||||
|
Binaries/**
|
||||||
|
Build/**
|
||||||
|
DerivedDataCache/**
|
||||||
|
Intermediate/**
|
||||||
|
Saved/**
|
||||||
|
.vs/**
|
||||||
|
.idea/**
|
||||||
|
*.VC.db
|
||||||
|
*.VC.VC.opendb
|
||||||
|
*.suo
|
||||||
|
*.user
|
||||||
|
*.userprefs
|
||||||
|
*.opensdf
|
||||||
|
*.sdf
|
||||||
|
*.tmp
|
||||||
|
*.log
|
||||||
|
|
||||||
|
########################
|
||||||
|
# Visual Studio / Rider / JetBrains
|
||||||
|
########################
|
||||||
|
.vscode/**
|
||||||
|
*.code-workspace
|
||||||
|
*.DotSettings.user
|
||||||
|
|
||||||
|
########################
|
||||||
|
# Platform-specific
|
||||||
|
########################
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
Desktop.ini
|
||||||
|
|
||||||
|
########################
|
||||||
|
# UBT / UHT / build metadata
|
||||||
|
########################
|
||||||
|
*.modules
|
||||||
|
*.target
|
||||||
|
*.version
|
||||||
|
|
||||||
|
########################
|
||||||
|
# Optional: ignore solution (regen-able)
|
||||||
|
########################
|
||||||
|
*.sln
|
||||||
|
|
||||||
|
########################
|
||||||
|
# Plugins: ignore build artifacts inside plugins
|
||||||
|
########################
|
||||||
|
Plugins/**/Binaries/**
|
||||||
|
Plugins/**/Intermediate/**
|
||||||
|
Plugins/**/DerivedDataCache/**
|
||||||
|
Plugins/**/Saved/**
|
||||||
|
Plugins/**/.vs/**
|
||||||
|
Plugins/**/.idea/**
|
||||||
19
.vsconfig
Normal file
19
.vsconfig
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"version": "1.0",
|
||||||
|
"components": [
|
||||||
|
"Component.Unreal.Debugger",
|
||||||
|
"Component.Unreal.Ide",
|
||||||
|
"Microsoft.Net.Component.4.6.2.TargetingPack",
|
||||||
|
"Microsoft.VisualStudio.Component.VC.14.38.17.8.ATL",
|
||||||
|
"Microsoft.VisualStudio.Component.VC.14.38.17.8.x86.x64",
|
||||||
|
"Microsoft.VisualStudio.Component.VC.14.44.17.14.ATL",
|
||||||
|
"Microsoft.VisualStudio.Component.VC.14.44.17.14.x86.x64",
|
||||||
|
"Microsoft.VisualStudio.Component.VC.Llvm.Clang",
|
||||||
|
"Microsoft.VisualStudio.Component.VC.Tools.x86.x64",
|
||||||
|
"Microsoft.VisualStudio.Component.Windows11SDK.22621",
|
||||||
|
"Microsoft.VisualStudio.Workload.CoreEditor",
|
||||||
|
"Microsoft.VisualStudio.Workload.ManagedDesktop",
|
||||||
|
"Microsoft.VisualStudio.Workload.NativeDesktop",
|
||||||
|
"Microsoft.VisualStudio.Workload.NativeGame"
|
||||||
|
]
|
||||||
|
}
|
||||||
5
Config/DefaultEditor.ini
Normal file
5
Config/DefaultEditor.ini
Normal file
File diff suppressed because one or more lines are too long
162
Config/DefaultEngine.ini
Normal file
162
Config/DefaultEngine.ini
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
|
||||||
|
|
||||||
|
[/Script/EngineSettings.GameMapsSettings]
|
||||||
|
GameDefaultMap=/Game/AGame/Map/Map_Test.Map_Test
|
||||||
|
EditorStartupMap=/Game/AGame/Map/Map_Test.Map_Test
|
||||||
|
GameInstanceClass=/Game/AGame/Gameplay/GameInstance_Default.GameInstance_Default_C
|
||||||
|
GlobalDefaultGameMode=/Game/AGame/Gameplay/GameMode_InGame.GameMode_InGame_C
|
||||||
|
|
||||||
|
[/Script/Engine.RendererSettings]
|
||||||
|
r.AllowStaticLighting=False
|
||||||
|
|
||||||
|
r.GenerateMeshDistanceFields=True
|
||||||
|
|
||||||
|
r.DynamicGlobalIlluminationMethod=1
|
||||||
|
|
||||||
|
r.ReflectionMethod=1
|
||||||
|
|
||||||
|
r.SkinCache.CompileShaders=True
|
||||||
|
|
||||||
|
r.RayTracing=True
|
||||||
|
|
||||||
|
r.RayTracing.RayTracingProxies.ProjectEnabled=True
|
||||||
|
|
||||||
|
r.Substrate=True
|
||||||
|
|
||||||
|
r.Substrate.ProjectGBufferFormat=0
|
||||||
|
|
||||||
|
r.Shadow.Virtual.Enable=1
|
||||||
|
|
||||||
|
r.DefaultFeature.AutoExposure.ExtendDefaultLuminanceRange=True
|
||||||
|
|
||||||
|
r.DefaultFeature.LocalExposure.HighlightContrastScale=0.8
|
||||||
|
|
||||||
|
r.DefaultFeature.LocalExposure.ShadowContrastScale=0.8
|
||||||
|
|
||||||
|
[/Script/WindowsTargetPlatform.WindowsTargetSettings]
|
||||||
|
DefaultGraphicsRHI=DefaultGraphicsRHI_DX12
|
||||||
|
DefaultGraphicsRHI=DefaultGraphicsRHI_DX12
|
||||||
|
-D3D12TargetedShaderFormats=PCD3D_SM5
|
||||||
|
+D3D12TargetedShaderFormats=PCD3D_SM6
|
||||||
|
-D3D11TargetedShaderFormats=PCD3D_SM5
|
||||||
|
+D3D11TargetedShaderFormats=PCD3D_SM5
|
||||||
|
Compiler=VisualStudio2026
|
||||||
|
AudioSampleRate=48000
|
||||||
|
AudioCallbackBufferFrameSize=1024
|
||||||
|
AudioNumBuffersToEnqueue=1
|
||||||
|
AudioMaxChannels=0
|
||||||
|
AudioNumSourceWorkers=4
|
||||||
|
SpatializationPlugin=
|
||||||
|
SourceDataOverridePlugin=
|
||||||
|
ReverbPlugin=
|
||||||
|
OcclusionPlugin=
|
||||||
|
CompressionOverrides=(bOverrideCompressionTimes=False,DurationThreshold=5.000000,MaxNumRandomBranches=0,SoundCueQualityIndex=0)
|
||||||
|
CacheSizeKB=65536
|
||||||
|
MaxChunkSizeOverrideKB=0
|
||||||
|
bResampleForDevice=False
|
||||||
|
MaxSampleRate=48000.000000
|
||||||
|
HighSampleRate=32000.000000
|
||||||
|
MedSampleRate=24000.000000
|
||||||
|
LowSampleRate=12000.000000
|
||||||
|
MinSampleRate=8000.000000
|
||||||
|
CompressionQualityModifier=1.000000
|
||||||
|
AutoStreamingThreshold=0.000000
|
||||||
|
SoundCueCookQualityIndex=-1
|
||||||
|
|
||||||
|
[/Script/LinuxTargetPlatform.LinuxTargetSettings]
|
||||||
|
-TargetedRHIs=SF_VULKAN_SM5
|
||||||
|
+TargetedRHIs=SF_VULKAN_SM6
|
||||||
|
|
||||||
|
[/Script/MacTargetPlatform.MacTargetSettings]
|
||||||
|
-TargetedRHIs=SF_METAL_SM5
|
||||||
|
+TargetedRHIs=SF_METAL_SM6
|
||||||
|
|
||||||
|
[/Script/HardwareTargeting.HardwareTargetingSettings]
|
||||||
|
TargetedHardwareClass=Desktop
|
||||||
|
AppliedTargetedHardwareClass=Desktop
|
||||||
|
DefaultGraphicsPerformance=Maximum
|
||||||
|
AppliedDefaultGraphicsPerformance=Maximum
|
||||||
|
|
||||||
|
[/Script/WorldPartitionEditor.WorldPartitionEditorSettings]
|
||||||
|
CommandletClass=Class'/Script/UnrealEd.WorldPartitionConvertCommandlet'
|
||||||
|
|
||||||
|
[/Script/Engine.UserInterfaceSettings]
|
||||||
|
bAuthorizeAutomaticWidgetVariableCreation=False
|
||||||
|
FontDPIPreset=Standard
|
||||||
|
FontDPI=72
|
||||||
|
|
||||||
|
[/Script/Engine.Engine]
|
||||||
|
+ActiveGameNameRedirects=(OldGameName="TP_Blank",NewGameName="/Script/PHY")
|
||||||
|
+ActiveGameNameRedirects=(OldGameName="/Script/TP_Blank",NewGameName="/Script/PHY")
|
||||||
|
|
||||||
|
[/Script/AndroidFileServerEditor.AndroidFileServerRuntimeSettings]
|
||||||
|
bEnablePlugin=True
|
||||||
|
bAllowNetworkConnection=True
|
||||||
|
SecurityToken=66F18DE642E3AA7501A711AC1A3E40C9
|
||||||
|
bIncludeInShipping=False
|
||||||
|
bAllowExternalStartInShipping=False
|
||||||
|
bCompileAFSProject=False
|
||||||
|
bUseCompression=False
|
||||||
|
bLogFiles=False
|
||||||
|
bReportStats=False
|
||||||
|
ConnectionType=USBOnly
|
||||||
|
bUseManualIPAddress=False
|
||||||
|
ManualIPAddress=
|
||||||
|
|
||||||
|
[/Script/Engine.CollisionProfile]
|
||||||
|
-Profiles=(Name="NoCollision",CollisionEnabled=NoCollision,ObjectTypeName="WorldStatic",CustomResponses=((Channel="Visibility",Response=ECR_Ignore),(Channel="Camera",Response=ECR_Ignore)),HelpMessage="No collision",bCanModify=False)
|
||||||
|
-Profiles=(Name="BlockAll",CollisionEnabled=QueryAndPhysics,ObjectTypeName="WorldStatic",CustomResponses=,HelpMessage="WorldStatic object that blocks all actors by default. All new custom channels will use its own default response. ",bCanModify=False)
|
||||||
|
-Profiles=(Name="OverlapAll",CollisionEnabled=QueryOnly,ObjectTypeName="WorldStatic",CustomResponses=((Channel="WorldStatic",Response=ECR_Overlap),(Channel="Pawn",Response=ECR_Overlap),(Channel="Visibility",Response=ECR_Overlap),(Channel="WorldDynamic",Response=ECR_Overlap),(Channel="Camera",Response=ECR_Overlap),(Channel="PhysicsBody",Response=ECR_Overlap),(Channel="Vehicle",Response=ECR_Overlap),(Channel="Destructible",Response=ECR_Overlap)),HelpMessage="WorldStatic object that overlaps all actors by default. All new custom channels will use its own default response. ",bCanModify=False)
|
||||||
|
-Profiles=(Name="BlockAllDynamic",CollisionEnabled=QueryAndPhysics,ObjectTypeName="WorldDynamic",CustomResponses=,HelpMessage="WorldDynamic object that blocks all actors by default. All new custom channels will use its own default response. ",bCanModify=False)
|
||||||
|
-Profiles=(Name="OverlapAllDynamic",CollisionEnabled=QueryOnly,ObjectTypeName="WorldDynamic",CustomResponses=((Channel="WorldStatic",Response=ECR_Overlap),(Channel="Pawn",Response=ECR_Overlap),(Channel="Visibility",Response=ECR_Overlap),(Channel="WorldDynamic",Response=ECR_Overlap),(Channel="Camera",Response=ECR_Overlap),(Channel="PhysicsBody",Response=ECR_Overlap),(Channel="Vehicle",Response=ECR_Overlap),(Channel="Destructible",Response=ECR_Overlap)),HelpMessage="WorldDynamic object that overlaps all actors by default. All new custom channels will use its own default response. ",bCanModify=False)
|
||||||
|
-Profiles=(Name="IgnoreOnlyPawn",CollisionEnabled=QueryOnly,ObjectTypeName="WorldDynamic",CustomResponses=((Channel="Pawn",Response=ECR_Ignore),(Channel="Vehicle",Response=ECR_Ignore)),HelpMessage="WorldDynamic object that ignores Pawn and Vehicle. All other channels will be set to default.",bCanModify=False)
|
||||||
|
-Profiles=(Name="OverlapOnlyPawn",CollisionEnabled=QueryOnly,ObjectTypeName="WorldDynamic",CustomResponses=((Channel="Pawn",Response=ECR_Overlap),(Channel="Vehicle",Response=ECR_Overlap),(Channel="Camera",Response=ECR_Ignore)),HelpMessage="WorldDynamic object that overlaps Pawn, Camera, and Vehicle. All other channels will be set to default. ",bCanModify=False)
|
||||||
|
-Profiles=(Name="Pawn",CollisionEnabled=QueryAndPhysics,ObjectTypeName="Pawn",CustomResponses=((Channel="Visibility",Response=ECR_Ignore)),HelpMessage="Pawn object. Can be used for capsule of any playerable character or AI. ",bCanModify=False)
|
||||||
|
-Profiles=(Name="Spectator",CollisionEnabled=QueryOnly,ObjectTypeName="Pawn",CustomResponses=((Channel="WorldStatic",Response=ECR_Block),(Channel="Pawn",Response=ECR_Ignore),(Channel="Visibility",Response=ECR_Ignore),(Channel="WorldDynamic",Response=ECR_Ignore),(Channel="Camera",Response=ECR_Ignore),(Channel="PhysicsBody",Response=ECR_Ignore),(Channel="Vehicle",Response=ECR_Ignore),(Channel="Destructible",Response=ECR_Ignore)),HelpMessage="Pawn object that ignores all other actors except WorldStatic.",bCanModify=False)
|
||||||
|
-Profiles=(Name="CharacterMesh",CollisionEnabled=QueryOnly,ObjectTypeName="Pawn",CustomResponses=((Channel="Pawn",Response=ECR_Ignore),(Channel="Vehicle",Response=ECR_Ignore),(Channel="Visibility",Response=ECR_Ignore)),HelpMessage="Pawn object that is used for Character Mesh. All other channels will be set to default.",bCanModify=False)
|
||||||
|
-Profiles=(Name="PhysicsActor",CollisionEnabled=QueryAndPhysics,ObjectTypeName="PhysicsBody",CustomResponses=,HelpMessage="Simulating actors",bCanModify=False)
|
||||||
|
-Profiles=(Name="Destructible",CollisionEnabled=QueryAndPhysics,ObjectTypeName="Destructible",CustomResponses=,HelpMessage="Destructible actors",bCanModify=False)
|
||||||
|
-Profiles=(Name="InvisibleWall",CollisionEnabled=QueryAndPhysics,ObjectTypeName="WorldStatic",CustomResponses=((Channel="Visibility",Response=ECR_Ignore)),HelpMessage="WorldStatic object that is invisible.",bCanModify=False)
|
||||||
|
-Profiles=(Name="InvisibleWallDynamic",CollisionEnabled=QueryAndPhysics,ObjectTypeName="WorldDynamic",CustomResponses=((Channel="Visibility",Response=ECR_Ignore)),HelpMessage="WorldDynamic object that is invisible.",bCanModify=False)
|
||||||
|
-Profiles=(Name="Trigger",CollisionEnabled=QueryOnly,ObjectTypeName="WorldDynamic",CustomResponses=((Channel="WorldStatic",Response=ECR_Overlap),(Channel="Pawn",Response=ECR_Overlap),(Channel="Visibility",Response=ECR_Ignore),(Channel="WorldDynamic",Response=ECR_Overlap),(Channel="Camera",Response=ECR_Overlap),(Channel="PhysicsBody",Response=ECR_Overlap),(Channel="Vehicle",Response=ECR_Overlap),(Channel="Destructible",Response=ECR_Overlap)),HelpMessage="WorldDynamic object that is used for trigger. All other channels will be set to default.",bCanModify=False)
|
||||||
|
-Profiles=(Name="Ragdoll",CollisionEnabled=QueryAndPhysics,ObjectTypeName="PhysicsBody",CustomResponses=((Channel="Pawn",Response=ECR_Ignore),(Channel="Visibility",Response=ECR_Ignore)),HelpMessage="Simulating Skeletal Mesh Component. All other channels will be set to default.",bCanModify=False)
|
||||||
|
-Profiles=(Name="Vehicle",CollisionEnabled=QueryAndPhysics,ObjectTypeName="Vehicle",CustomResponses=,HelpMessage="Vehicle object that blocks Vehicle, WorldStatic, and WorldDynamic. All other channels will be set to default.",bCanModify=False)
|
||||||
|
-Profiles=(Name="UI",CollisionEnabled=QueryOnly,ObjectTypeName="WorldDynamic",CustomResponses=((Channel="WorldStatic",Response=ECR_Overlap),(Channel="Pawn",Response=ECR_Overlap),(Channel="Visibility",Response=ECR_Block),(Channel="WorldDynamic",Response=ECR_Overlap),(Channel="Camera",Response=ECR_Overlap),(Channel="PhysicsBody",Response=ECR_Overlap),(Channel="Vehicle",Response=ECR_Overlap),(Channel="Destructible",Response=ECR_Overlap)),HelpMessage="WorldStatic object that overlaps all actors by default. All new custom channels will use its own default response. ",bCanModify=False)
|
||||||
|
+Profiles=(Name="NoCollision",CollisionEnabled=NoCollision,bCanModify=False,ObjectTypeName="WorldStatic",CustomResponses=((Channel="Visibility",Response=ECR_Ignore),(Channel="Camera",Response=ECR_Ignore)),HelpMessage="No collision")
|
||||||
|
+Profiles=(Name="BlockAll",CollisionEnabled=QueryAndPhysics,bCanModify=False,ObjectTypeName="WorldStatic",CustomResponses=,HelpMessage="WorldStatic object that blocks all actors by default. All new custom channels will use its own default response. ")
|
||||||
|
+Profiles=(Name="OverlapAll",CollisionEnabled=QueryOnly,bCanModify=False,ObjectTypeName="WorldStatic",CustomResponses=((Channel="WorldStatic",Response=ECR_Overlap),(Channel="Pawn",Response=ECR_Overlap),(Channel="Visibility",Response=ECR_Overlap),(Channel="WorldDynamic",Response=ECR_Overlap),(Channel="Camera",Response=ECR_Overlap),(Channel="PhysicsBody",Response=ECR_Overlap),(Channel="Vehicle",Response=ECR_Overlap),(Channel="Destructible",Response=ECR_Overlap)),HelpMessage="WorldStatic object that overlaps all actors by default. All new custom channels will use its own default response. ")
|
||||||
|
+Profiles=(Name="BlockAllDynamic",CollisionEnabled=QueryAndPhysics,bCanModify=False,ObjectTypeName="WorldDynamic",CustomResponses=,HelpMessage="WorldDynamic object that blocks all actors by default. All new custom channels will use its own default response. ")
|
||||||
|
+Profiles=(Name="OverlapAllDynamic",CollisionEnabled=QueryOnly,bCanModify=False,ObjectTypeName="WorldDynamic",CustomResponses=((Channel="WorldStatic",Response=ECR_Overlap),(Channel="Pawn",Response=ECR_Overlap),(Channel="Visibility",Response=ECR_Overlap),(Channel="WorldDynamic",Response=ECR_Overlap),(Channel="Camera",Response=ECR_Overlap),(Channel="PhysicsBody",Response=ECR_Overlap),(Channel="Vehicle",Response=ECR_Overlap),(Channel="Destructible",Response=ECR_Overlap)),HelpMessage="WorldDynamic object that overlaps all actors by default. All new custom channels will use its own default response. ")
|
||||||
|
+Profiles=(Name="IgnoreOnlyPawn",CollisionEnabled=QueryOnly,bCanModify=False,ObjectTypeName="WorldDynamic",CustomResponses=((Channel="Pawn",Response=ECR_Ignore),(Channel="Vehicle",Response=ECR_Ignore)),HelpMessage="WorldDynamic object that ignores Pawn and Vehicle. All other channels will be set to default.")
|
||||||
|
+Profiles=(Name="OverlapOnlyPawn",CollisionEnabled=QueryOnly,bCanModify=False,ObjectTypeName="WorldDynamic",CustomResponses=((Channel="Pawn",Response=ECR_Overlap),(Channel="Vehicle",Response=ECR_Overlap),(Channel="Camera",Response=ECR_Ignore)),HelpMessage="WorldDynamic object that overlaps Pawn, Camera, and Vehicle. All other channels will be set to default. ")
|
||||||
|
+Profiles=(Name="Pawn",CollisionEnabled=QueryAndPhysics,bCanModify=False,ObjectTypeName="Pawn",CustomResponses=((Channel="Visibility",Response=ECR_Ignore)),HelpMessage="Pawn object. Can be used for capsule of any playerable character or AI. ")
|
||||||
|
+Profiles=(Name="Spectator",CollisionEnabled=QueryOnly,bCanModify=False,ObjectTypeName="Pawn",CustomResponses=((Channel="WorldStatic"),(Channel="Pawn",Response=ECR_Ignore),(Channel="Visibility",Response=ECR_Ignore),(Channel="WorldDynamic",Response=ECR_Ignore),(Channel="Camera",Response=ECR_Ignore),(Channel="PhysicsBody",Response=ECR_Ignore),(Channel="Vehicle",Response=ECR_Ignore),(Channel="Destructible",Response=ECR_Ignore)),HelpMessage="Pawn object that ignores all other actors except WorldStatic.")
|
||||||
|
+Profiles=(Name="CharacterMesh",CollisionEnabled=QueryOnly,bCanModify=False,ObjectTypeName="Pawn",CustomResponses=((Channel="Pawn",Response=ECR_Ignore),(Channel="Vehicle",Response=ECR_Ignore),(Channel="Visibility",Response=ECR_Ignore)),HelpMessage="Pawn object that is used for Character Mesh. All other channels will be set to default.")
|
||||||
|
+Profiles=(Name="PhysicsActor",CollisionEnabled=QueryAndPhysics,bCanModify=False,ObjectTypeName="PhysicsBody",CustomResponses=,HelpMessage="Simulating actors")
|
||||||
|
+Profiles=(Name="Destructible",CollisionEnabled=QueryAndPhysics,bCanModify=False,ObjectTypeName="Destructible",CustomResponses=,HelpMessage="Destructible actors")
|
||||||
|
+Profiles=(Name="InvisibleWall",CollisionEnabled=QueryAndPhysics,bCanModify=False,ObjectTypeName="WorldStatic",CustomResponses=((Channel="Visibility",Response=ECR_Ignore)),HelpMessage="WorldStatic object that is invisible.")
|
||||||
|
+Profiles=(Name="InvisibleWallDynamic",CollisionEnabled=QueryAndPhysics,bCanModify=False,ObjectTypeName="WorldDynamic",CustomResponses=((Channel="Visibility",Response=ECR_Ignore)),HelpMessage="WorldDynamic object that is invisible.")
|
||||||
|
+Profiles=(Name="Trigger",CollisionEnabled=QueryOnly,bCanModify=False,ObjectTypeName="WorldDynamic",CustomResponses=((Channel="WorldStatic",Response=ECR_Overlap),(Channel="Pawn",Response=ECR_Overlap),(Channel="Visibility",Response=ECR_Ignore),(Channel="WorldDynamic",Response=ECR_Overlap),(Channel="Camera",Response=ECR_Overlap),(Channel="PhysicsBody",Response=ECR_Overlap),(Channel="Vehicle",Response=ECR_Overlap),(Channel="Destructible",Response=ECR_Overlap)),HelpMessage="WorldDynamic object that is used for trigger. All other channels will be set to default.")
|
||||||
|
+Profiles=(Name="Ragdoll",CollisionEnabled=QueryAndPhysics,bCanModify=False,ObjectTypeName="PhysicsBody",CustomResponses=((Channel="Pawn",Response=ECR_Ignore),(Channel="Visibility",Response=ECR_Ignore)),HelpMessage="Simulating Skeletal Mesh Component. All other channels will be set to default.")
|
||||||
|
+Profiles=(Name="Vehicle",CollisionEnabled=QueryAndPhysics,bCanModify=False,ObjectTypeName="Vehicle",CustomResponses=,HelpMessage="Vehicle object that blocks Vehicle, WorldStatic, and WorldDynamic. All other channels will be set to default.")
|
||||||
|
+Profiles=(Name="UI",CollisionEnabled=QueryOnly,bCanModify=False,ObjectTypeName="WorldDynamic",CustomResponses=((Channel="WorldStatic",Response=ECR_Overlap),(Channel="Pawn",Response=ECR_Overlap),(Channel="Visibility"),(Channel="WorldDynamic",Response=ECR_Overlap),(Channel="Camera",Response=ECR_Overlap),(Channel="PhysicsBody",Response=ECR_Overlap),(Channel="Vehicle",Response=ECR_Overlap),(Channel="Destructible",Response=ECR_Overlap)),HelpMessage="WorldStatic object that overlaps all actors by default. All new custom channels will use its own default response. ")
|
||||||
|
+Profiles=(Name="WaterBodyCollision",CollisionEnabled=QueryOnly,bCanModify=False,ObjectTypeName="",CustomResponses=((Channel="WorldDynamic",Response=ECR_Overlap),(Channel="Pawn",Response=ECR_Overlap),(Channel="Visibility",Response=ECR_Ignore),(Channel="Camera",Response=ECR_Ignore),(Channel="PhysicsBody",Response=ECR_Overlap),(Channel="Vehicle",Response=ECR_Overlap),(Channel="Destructible",Response=ECR_Overlap)),HelpMessage="Default Water Collision Profile (Created by Water Plugin)")
|
||||||
|
-ProfileRedirects=(OldName="BlockingVolume",NewName="InvisibleWall")
|
||||||
|
-ProfileRedirects=(OldName="InterpActor",NewName="IgnoreOnlyPawn")
|
||||||
|
-ProfileRedirects=(OldName="StaticMeshComponent",NewName="BlockAllDynamic")
|
||||||
|
-ProfileRedirects=(OldName="SkeletalMeshActor",NewName="PhysicsActor")
|
||||||
|
-ProfileRedirects=(OldName="InvisibleActor",NewName="InvisibleWallDynamic")
|
||||||
|
+ProfileRedirects=(OldName="BlockingVolume",NewName="InvisibleWall")
|
||||||
|
+ProfileRedirects=(OldName="InterpActor",NewName="IgnoreOnlyPawn")
|
||||||
|
+ProfileRedirects=(OldName="StaticMeshComponent",NewName="BlockAllDynamic")
|
||||||
|
+ProfileRedirects=(OldName="SkeletalMeshActor",NewName="PhysicsActor")
|
||||||
|
+ProfileRedirects=(OldName="InvisibleActor",NewName="InvisibleWallDynamic")
|
||||||
|
-CollisionChannelRedirects=(OldName="Static",NewName="WorldStatic")
|
||||||
|
-CollisionChannelRedirects=(OldName="Dynamic",NewName="WorldDynamic")
|
||||||
|
-CollisionChannelRedirects=(OldName="VehicleMovement",NewName="Vehicle")
|
||||||
|
-CollisionChannelRedirects=(OldName="PawnMovement",NewName="Pawn")
|
||||||
|
+CollisionChannelRedirects=(OldName="Static",NewName="WorldStatic")
|
||||||
|
+CollisionChannelRedirects=(OldName="Dynamic",NewName="WorldDynamic")
|
||||||
|
+CollisionChannelRedirects=(OldName="VehicleMovement",NewName="Vehicle")
|
||||||
|
+CollisionChannelRedirects=(OldName="PawnMovement",NewName="Pawn")
|
||||||
|
|
||||||
11
Config/DefaultGame.ini
Normal file
11
Config/DefaultGame.ini
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
[/Script/CommonUI.CommonUISettings]
|
||||||
|
CommonButtonAcceptKeyHandling=TriggerClick
|
||||||
|
|
||||||
|
[/Script/EngineSettings.GeneralProjectSettings]
|
||||||
|
ProjectID=7DF2A45E490F29FEA209488756C669ED
|
||||||
|
CopyrightNotice=
|
||||||
|
|
||||||
|
[/Script/GenericUISystem.GUIS_GenericUISystemSettings]
|
||||||
|
; Set this in editor to a BP subclass of UPHYGameUIPolicy (recommended), e.g.
|
||||||
|
; /Game/AGame/UI/BP_PHYGameUIPolicy.BP_PHYGameUIPolicy_C
|
||||||
|
GameUIPolicyClass=
|
||||||
14
Config/DefaultGameplayTags.ini
Normal file
14
Config/DefaultGameplayTags.ini
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
;METADATA=(Diff=true, UseCommands=true)
|
||||||
|
[/Script/GameplayTags.GameplayTagsSettings]
|
||||||
|
ImportTagsFromConfig=True
|
||||||
|
WarnOnInvalidTags=True
|
||||||
|
ClearInvalidTags=False
|
||||||
|
AllowEditorTagUnloading=True
|
||||||
|
AllowGameTagUnloading=False
|
||||||
|
FastReplication=False
|
||||||
|
bDynamicReplication=False
|
||||||
|
InvalidTagCharacters="\"\',"
|
||||||
|
NumBitsForContainerSize=6
|
||||||
|
NetIndexFirstBitSegment=16
|
||||||
|
+GameplayTagList=(Tag="Data.Regen.InnerPower",DevComment="SetByCaller: InnerPower regen per tick")
|
||||||
|
|
||||||
84
Config/DefaultInput.ini
Normal file
84
Config/DefaultInput.ini
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
[/Script/Engine.InputSettings]
|
||||||
|
-AxisConfig=(AxisKeyName="Gamepad_LeftX",AxisProperties=(DeadZone=0.25,Exponent=1.f,Sensitivity=1.f))
|
||||||
|
-AxisConfig=(AxisKeyName="Gamepad_LeftY",AxisProperties=(DeadZone=0.25,Exponent=1.f,Sensitivity=1.f))
|
||||||
|
-AxisConfig=(AxisKeyName="Gamepad_RightX",AxisProperties=(DeadZone=0.25,Exponent=1.f,Sensitivity=1.f))
|
||||||
|
-AxisConfig=(AxisKeyName="Gamepad_RightY",AxisProperties=(DeadZone=0.25,Exponent=1.f,Sensitivity=1.f))
|
||||||
|
-AxisConfig=(AxisKeyName="MouseX",AxisProperties=(DeadZone=0.f,Exponent=1.f,Sensitivity=0.07f))
|
||||||
|
-AxisConfig=(AxisKeyName="MouseY",AxisProperties=(DeadZone=0.f,Exponent=1.f,Sensitivity=0.07f))
|
||||||
|
-AxisConfig=(AxisKeyName="Mouse2D",AxisProperties=(DeadZone=0.f,Exponent=1.f,Sensitivity=0.07f))
|
||||||
|
+AxisConfig=(AxisKeyName="Gamepad_LeftX",AxisProperties=(DeadZone=0.250000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
|
||||||
|
+AxisConfig=(AxisKeyName="Gamepad_LeftY",AxisProperties=(DeadZone=0.250000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
|
||||||
|
+AxisConfig=(AxisKeyName="Gamepad_RightX",AxisProperties=(DeadZone=0.250000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
|
||||||
|
+AxisConfig=(AxisKeyName="Gamepad_RightY",AxisProperties=(DeadZone=0.250000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
|
||||||
|
+AxisConfig=(AxisKeyName="MouseX",AxisProperties=(DeadZone=0.000000,Sensitivity=0.070000,Exponent=1.000000,bInvert=False))
|
||||||
|
+AxisConfig=(AxisKeyName="MouseY",AxisProperties=(DeadZone=0.000000,Sensitivity=0.070000,Exponent=1.000000,bInvert=False))
|
||||||
|
+AxisConfig=(AxisKeyName="Mouse2D",AxisProperties=(DeadZone=0.000000,Sensitivity=0.070000,Exponent=1.000000,bInvert=False))
|
||||||
|
+AxisConfig=(AxisKeyName="MouseWheelAxis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
|
||||||
|
+AxisConfig=(AxisKeyName="Gamepad_LeftTriggerAxis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
|
||||||
|
+AxisConfig=(AxisKeyName="Gamepad_RightTriggerAxis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
|
||||||
|
+AxisConfig=(AxisKeyName="Gamepad_Special_Left_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
|
||||||
|
+AxisConfig=(AxisKeyName="Gamepad_Special_Left_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
|
||||||
|
+AxisConfig=(AxisKeyName="Vive_Left_Trigger_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
|
||||||
|
+AxisConfig=(AxisKeyName="Vive_Left_Trackpad_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
|
||||||
|
+AxisConfig=(AxisKeyName="Vive_Left_Trackpad_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
|
||||||
|
+AxisConfig=(AxisKeyName="Vive_Right_Trigger_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
|
||||||
|
+AxisConfig=(AxisKeyName="Vive_Right_Trackpad_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
|
||||||
|
+AxisConfig=(AxisKeyName="Vive_Right_Trackpad_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
|
||||||
|
+AxisConfig=(AxisKeyName="MixedReality_Left_Trigger_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
|
||||||
|
+AxisConfig=(AxisKeyName="MixedReality_Left_Thumbstick_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
|
||||||
|
+AxisConfig=(AxisKeyName="MixedReality_Left_Thumbstick_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
|
||||||
|
+AxisConfig=(AxisKeyName="MixedReality_Left_Trackpad_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
|
||||||
|
+AxisConfig=(AxisKeyName="MixedReality_Left_Trackpad_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
|
||||||
|
+AxisConfig=(AxisKeyName="MixedReality_Right_Trigger_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
|
||||||
|
+AxisConfig=(AxisKeyName="MixedReality_Right_Thumbstick_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
|
||||||
|
+AxisConfig=(AxisKeyName="MixedReality_Right_Thumbstick_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
|
||||||
|
+AxisConfig=(AxisKeyName="MixedReality_Right_Trackpad_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
|
||||||
|
+AxisConfig=(AxisKeyName="MixedReality_Right_Trackpad_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
|
||||||
|
+AxisConfig=(AxisKeyName="OculusTouch_Left_Grip_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
|
||||||
|
+AxisConfig=(AxisKeyName="OculusTouch_Left_Trigger_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
|
||||||
|
+AxisConfig=(AxisKeyName="OculusTouch_Left_Thumbstick_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
|
||||||
|
+AxisConfig=(AxisKeyName="OculusTouch_Left_Thumbstick_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
|
||||||
|
+AxisConfig=(AxisKeyName="OculusTouch_Right_Grip_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
|
||||||
|
+AxisConfig=(AxisKeyName="OculusTouch_Right_Trigger_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
|
||||||
|
+AxisConfig=(AxisKeyName="OculusTouch_Right_Thumbstick_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
|
||||||
|
+AxisConfig=(AxisKeyName="OculusTouch_Right_Thumbstick_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
|
||||||
|
+AxisConfig=(AxisKeyName="ValveIndex_Left_Grip_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
|
||||||
|
+AxisConfig=(AxisKeyName="ValveIndex_Left_Grip_Force",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
|
||||||
|
+AxisConfig=(AxisKeyName="ValveIndex_Left_Trigger_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
|
||||||
|
+AxisConfig=(AxisKeyName="ValveIndex_Left_Thumbstick_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
|
||||||
|
+AxisConfig=(AxisKeyName="ValveIndex_Left_Thumbstick_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
|
||||||
|
+AxisConfig=(AxisKeyName="ValveIndex_Left_Trackpad_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
|
||||||
|
+AxisConfig=(AxisKeyName="ValveIndex_Left_Trackpad_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
|
||||||
|
+AxisConfig=(AxisKeyName="ValveIndex_Left_Trackpad_Force",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
|
||||||
|
+AxisConfig=(AxisKeyName="ValveIndex_Right_Grip_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
|
||||||
|
+AxisConfig=(AxisKeyName="ValveIndex_Right_Grip_Force",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
|
||||||
|
+AxisConfig=(AxisKeyName="ValveIndex_Right_Trigger_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
|
||||||
|
+AxisConfig=(AxisKeyName="ValveIndex_Right_Thumbstick_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
|
||||||
|
+AxisConfig=(AxisKeyName="ValveIndex_Right_Thumbstick_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
|
||||||
|
+AxisConfig=(AxisKeyName="ValveIndex_Right_Trackpad_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
|
||||||
|
+AxisConfig=(AxisKeyName="ValveIndex_Right_Trackpad_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
|
||||||
|
+AxisConfig=(AxisKeyName="ValveIndex_Right_Trackpad_Force",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
|
||||||
|
bAltEnterTogglesFullscreen=True
|
||||||
|
bF11TogglesFullscreen=True
|
||||||
|
bUseMouseForTouch=False
|
||||||
|
bEnableMouseSmoothing=True
|
||||||
|
bEnableFOVScaling=True
|
||||||
|
bCaptureMouseOnLaunch=True
|
||||||
|
bEnableLegacyInputScales=True
|
||||||
|
bEnableMotionControls=True
|
||||||
|
bFilterInputByPlatformUser=False
|
||||||
|
bShouldFlushPressedKeysOnViewportFocusLost=True
|
||||||
|
bAlwaysShowTouchInterface=False
|
||||||
|
bShowConsoleOnFourFingerTap=True
|
||||||
|
bEnableGestureRecognizer=False
|
||||||
|
bUseAutocorrect=False
|
||||||
|
DefaultViewportMouseCaptureMode=CapturePermanently_IncludingInitialMouseDown
|
||||||
|
DefaultViewportMouseLockMode=LockOnCapture
|
||||||
|
FOVScale=0.011110
|
||||||
|
DoubleClickTime=0.200000
|
||||||
|
DefaultPlayerInputClass=/Script/EnhancedInput.EnhancedPlayerInput
|
||||||
|
DefaultInputComponentClass=/Script/EnhancedInput.EnhancedInputComponent
|
||||||
|
DefaultTouchInterface=/Engine/MobileResources/HUD/DefaultVirtualJoysticks.DefaultVirtualJoysticks
|
||||||
|
-ConsoleKeys=Tilde
|
||||||
|
+ConsoleKeys=Tilde
|
||||||
|
|
||||||
254
GAS_AttributeSystem_Notes.md
Normal file
254
GAS_AttributeSystem_Notes.md
Normal file
@@ -0,0 +1,254 @@
|
|||||||
|
# GAS 属性系统实现记录(PHY)
|
||||||
|
|
||||||
|
> 说明:本文记录在本项目中,为古风游戏(四维:臂力/根骨/内息/身法)搭建 GAS 属性体系、派生属性、职业初始化、以及服务器端定时回复(回血/回内)等功能的实现过程。
|
||||||
|
>
|
||||||
|
> 本项目模块不对外暴露,因此代码全部放在 `Source/PHY/Private` 内(AttributeSet/GE/MMC/GameplayTags 等)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 0. 总体目标
|
||||||
|
|
||||||
|
- 基础属性(Primary):
|
||||||
|
- **Strength(臂力)**
|
||||||
|
- **Constitution(根骨)**
|
||||||
|
- **InnerBreath(内息)**
|
||||||
|
- **Agility(身法)**
|
||||||
|
- 其他属性由四维派生(MMC 计算)或由装备/功法 GE 附加。
|
||||||
|
- 不对外提供公共模块 API:代码全部在 `Private`。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. GAS 组件挂载位置与初始化
|
||||||
|
|
||||||
|
### 1.1 AbilitySystemComponent 放在 PlayerState
|
||||||
|
|
||||||
|
- 在 `APHYPlayerState` 的构造函数中创建:
|
||||||
|
- `UAbilitySystemComponent* AbilitySystemComponent`
|
||||||
|
- `UPHYAttributeSet* AttributeSet`
|
||||||
|
- 设置复制:
|
||||||
|
- `AbilitySystemComponent->SetIsReplicated(true)`
|
||||||
|
- `AbilitySystemComponent->SetReplicationMode(EGameplayEffectReplicationMode::Minimal)`(适合 PlayerState)
|
||||||
|
|
||||||
|
相关文件:
|
||||||
|
- `Source/PHY/Private/Gameplay/Player/PHYPlayerState.cpp`
|
||||||
|
|
||||||
|
### 1.2 Character 初始化 GAS:Owner=PlayerState,Avatar=Character
|
||||||
|
|
||||||
|
在 `APHYPlayerCharacter`:
|
||||||
|
- `PossessedBy()`(服务器)调用 `InitializeGAS()`
|
||||||
|
- `OnRep_PlayerState()`(客户端)也调用 `InitializeGAS()`
|
||||||
|
|
||||||
|
初始化逻辑(核心):
|
||||||
|
- `ASC->InitAbilityActorInfo(PlayerState, this)`
|
||||||
|
- 服务器:
|
||||||
|
- Apply `UPHYGE_InitPrimary`(设置四维初值)
|
||||||
|
- Apply `UPHYGE_DerivedAttributes`(无限期派生属性:Override + MMC)
|
||||||
|
- 设置资源/当前值:
|
||||||
|
- `Health = MaxHealth`
|
||||||
|
- `InnerPower = MaxInnerPower`
|
||||||
|
|
||||||
|
相关文件:
|
||||||
|
- `Source/PHY/Private/Character/PHYPlayerCharacter.h/.cpp`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. AttributeSet(全部在 Private)
|
||||||
|
|
||||||
|
### 2.1 AttributeSet 位置
|
||||||
|
|
||||||
|
- `Source/PHY/Private/AbilitySystem/Attributes/PHYAttributeSet.h`
|
||||||
|
- `Source/PHY/Private/AbilitySystem/Attributes/PHYAttributeSet.cpp`
|
||||||
|
|
||||||
|
### 2.2 属性分组
|
||||||
|
|
||||||
|
#### Primary(基础四维)
|
||||||
|
- `Strength`
|
||||||
|
- `Constitution`
|
||||||
|
- `InnerBreath`
|
||||||
|
- `Agility`
|
||||||
|
|
||||||
|
#### Vitals(生命)
|
||||||
|
- `Health`
|
||||||
|
- `MaxHealth`
|
||||||
|
|
||||||
|
#### Resource(资源)
|
||||||
|
- `InnerPower`
|
||||||
|
- `MaxInnerPower`
|
||||||
|
|
||||||
|
#### Derived(示例派生)
|
||||||
|
- `MoveSpeed`
|
||||||
|
- `PhysicalAttack`
|
||||||
|
- `PhysicalDefense`
|
||||||
|
|
||||||
|
#### Secondary(二级属性)
|
||||||
|
- `Tenacity`(韧性)
|
||||||
|
- `CritChance`(暴击率 0~1)
|
||||||
|
- `CritDamage`(暴击伤害倍率 >=1)
|
||||||
|
- `DodgeChance`(闪避 0~1)
|
||||||
|
- `HitChance`(命中 0~1)
|
||||||
|
- `ParryChance`(招架 0~1)
|
||||||
|
- `CounterChance`(反击 0~1;业务逻辑要求闪避/招架成功后再判定)
|
||||||
|
- `ArmorPenetration`(穿甲 0~1;目前字段存在,数值来源更多偏装备/功法)
|
||||||
|
- `DamageReduction`(免伤 0~1)
|
||||||
|
- `LifeSteal`(吸血 0~1;目前字段存在,数值来源更多偏装备/功法)
|
||||||
|
- `HealthRegenRate`(生命回复/秒)
|
||||||
|
- `InnerPowerRegenRate`(内力回复/秒)
|
||||||
|
|
||||||
|
### 2.3 Clamp 与复制
|
||||||
|
|
||||||
|
在 AttributeSet 内实现:
|
||||||
|
- `PreAttributeChange`:
|
||||||
|
- `Health` / `InnerPower` clamp 到 `[0, Max]`
|
||||||
|
- 概率类 clamp 到 `[0, 1]`
|
||||||
|
- `CritDamage` clamp `>= 1`
|
||||||
|
- `PostGameplayEffectExecute`:
|
||||||
|
- 对关键属性再兜底 clamp
|
||||||
|
- 完整实现:
|
||||||
|
- `OnRep_XXX`
|
||||||
|
- `GetLifetimeReplicatedProps` + `DOREPLIFETIME_CONDITION_NOTIFY`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 派生属性计算(MMC)
|
||||||
|
|
||||||
|
### 3.1 MMC 放置位置
|
||||||
|
|
||||||
|
`Source/PHY/Private/AbilitySystem/MMC/`
|
||||||
|
|
||||||
|
### 3.2 已实现的 MMC(示例)
|
||||||
|
|
||||||
|
#### 基础派生
|
||||||
|
- `UPHY_MMC_MaxHealth`:`MaxHealth = 100 + Constitution * 25`
|
||||||
|
- `UPHY_MMC_MoveSpeed`:`MoveSpeed = 600 + Agility * 2`
|
||||||
|
- `UPHY_MMC_PhysicalAttack`:`PhysicalAttack = 10 + Strength * 2`
|
||||||
|
- `UPHY_MMC_PhysicalDefense`:`PhysicalDefense = 5 + Constitution * 1.5`
|
||||||
|
|
||||||
|
#### 二级属性派生(示例系数,后续建议数据化)
|
||||||
|
- `UPHY_MMC_MaxInnerPower`:`MaxInnerPower = 100 + InnerBreath * 20`
|
||||||
|
- `UPHY_MMC_Tenacity`:`Tenacity = InnerBreath * 1`
|
||||||
|
- `UPHY_MMC_CritChance`:`CritChance = 0.05 + Agility * 0.002 (clamp 0..1)`
|
||||||
|
- `UPHY_MMC_CritDamage`:`CritDamage = 1.5 + Strength * 0.005 (>=1)`
|
||||||
|
- `UPHY_MMC_DodgeChance`:`DodgeChance = 0.02 + Agility * 0.002`
|
||||||
|
- `UPHY_MMC_HitChance`:`HitChance = 0.9 + Agility * 0.001`
|
||||||
|
- `UPHY_MMC_ParryChance`:`ParryChance = 0.03 + Strength * 0.001`
|
||||||
|
- `UPHY_MMC_CounterChance`:`CounterChance = 0.1 + Agility * 0.001`
|
||||||
|
- `UPHY_MMC_DamageReduction`:`DamageReduction = Constitution * 0.001`
|
||||||
|
- `UPHY_MMC_HealthRegenRate`:`HealthRegenRate = Constitution * 0.1`
|
||||||
|
- `UPHY_MMC_InnerPowerRegenRate`:`InnerPowerRegenRate = InnerBreath * 0.15`
|
||||||
|
|
||||||
|
> 注:部分属性如穿甲/吸血更适合由装备/功法 GE 直接加成,而非完全从四维硬算。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. GameplayEffect(初始化 + 派生)
|
||||||
|
|
||||||
|
### 4.1 初始化四维:`UPHYGE_InitPrimary`
|
||||||
|
|
||||||
|
- 文件:`Source/PHY/Private/AbilitySystem/Effects/PHYGE_InitPrimary.h/.cpp`
|
||||||
|
- 类型:Instant
|
||||||
|
- 用 `SetByCaller` 写入四维(复用同一个 GE 支持所有职业)
|
||||||
|
|
||||||
|
SetByCaller Tag(NativeGameplayTags 管理):
|
||||||
|
- `Data.Init.Primary.Strength`
|
||||||
|
- `Data.Init.Primary.Constitution`
|
||||||
|
- `Data.Init.Primary.InnerBreath`
|
||||||
|
- `Data.Init.Primary.Agility`
|
||||||
|
|
||||||
|
### 4.2 派生属性:`UPHYGE_DerivedAttributes`
|
||||||
|
|
||||||
|
- 文件:`Source/PHY/Private/AbilitySystem/Effects/PHYGE_DerivedAttributes.h/.cpp`
|
||||||
|
- 类型:Infinite
|
||||||
|
- Modifiers:全部使用 `Override + MMC`
|
||||||
|
- 包含 `MaxHealth/MoveSpeed/PhysicalAttack/PhysicalDefense`
|
||||||
|
- 包含新增二级属性/资源上限(如 `MaxInnerPower`、暴击等)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. GameplayTags 组织(集中管理,避免硬编码 FName)
|
||||||
|
|
||||||
|
### 5.1 Init 属性 Tag
|
||||||
|
|
||||||
|
- `Source/PHY/Private/GameplayTags/InitAttributeTags.h/.cpp`
|
||||||
|
|
||||||
|
### 5.2 Regen Tag
|
||||||
|
|
||||||
|
- `Source/PHY/Private/GameplayTags/RegenTags.h/.cpp`
|
||||||
|
|
||||||
|
### 5.3 ini 配置
|
||||||
|
|
||||||
|
- 新增/维护:`Config/DefaultGameplayTags.ini`
|
||||||
|
- 包含 `Data.Init.Primary.*`
|
||||||
|
- 包含 `Data.Regen.*`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 职业/门派初始属性(全局配置)
|
||||||
|
|
||||||
|
### 6.1 职业枚举
|
||||||
|
|
||||||
|
- `Source/PHY/Private/AbilitySystem/PHYCharacterClass.h`
|
||||||
|
|
||||||
|
### 6.2 DataAsset:全局职业默认值表
|
||||||
|
|
||||||
|
- `Source/PHY/Private/AbilitySystem/PHYClassDefaults.h`
|
||||||
|
- `UPHYClassDefaults`:
|
||||||
|
- `FallbackPrimary`
|
||||||
|
- `Classes[]`:每个职业对应一套 `FPHYPrimaryAttributes`
|
||||||
|
|
||||||
|
### 6.3 配置位置:GameInstance
|
||||||
|
|
||||||
|
考虑到 `ClassDefaults` 是全局数据,不应放在每个 Character 上:
|
||||||
|
- `UPHYGameInstance` 增加 `ClassDefaults` 引用
|
||||||
|
- `GetClassDefaults()`
|
||||||
|
|
||||||
|
文件:
|
||||||
|
- `Source/PHY/Private/Gameplay/PHYGameInstance.h/.cpp`
|
||||||
|
|
||||||
|
使用方式:
|
||||||
|
- 在编辑器中的项目 GameInstance 蓝图(`DefaultEngine.ini` 指定的 `GameInstance_Default`)里配置 `ClassDefaults` 资产。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. 服务器端定时回复(方案 B:代码驱动)
|
||||||
|
|
||||||
|
> 选择方案 B:不使用 Period GE,而使用 **服务器 Timer + Instant GE**。
|
||||||
|
|
||||||
|
### 7.1 Regen 每跳 GE:`UPHYGE_RegenTick`
|
||||||
|
|
||||||
|
- 文件:`Source/PHY/Private/AbilitySystem/Effects/PHYGE_RegenTick.h/.cpp`
|
||||||
|
- 类型:Instant
|
||||||
|
- 使用 SetByCaller:
|
||||||
|
- `Data.Regen.Health`(加到 Health)
|
||||||
|
- `Data.Regen.InnerPower`(加到 InnerPower)
|
||||||
|
|
||||||
|
### 7.2 Character 计时器逻辑
|
||||||
|
|
||||||
|
在 `APHYPlayerCharacter`:
|
||||||
|
- 属性:`RegenInterval`(默认 1 秒)
|
||||||
|
- 在 `InitializeGAS()` 服务器端启动 Timer:
|
||||||
|
- `SetTimer(RegenTimerHandle, RegenTick, RegenInterval, true)`
|
||||||
|
- `RegenTick()`:
|
||||||
|
- `HealthDelta = HealthRegenRate * RegenInterval`
|
||||||
|
- `InnerPowerDelta = InnerPowerRegenRate * RegenInterval`
|
||||||
|
- Apply `UPHYGE_RegenTick`(SetByCaller 写入本次增量)
|
||||||
|
|
||||||
|
> 后续可拓展:脱战回复/坐下打坐/战斗中禁回等,均通过 GameplayTag 或状态判断来控制 `RegenTick()` 是否执行。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. 代码风格/工程注意事项
|
||||||
|
|
||||||
|
### 8.1 UTF-8 BOM 引发的编译/IDE 问题
|
||||||
|
|
||||||
|
过程中多次遇到文件头 BOM 导致的解析错误(如 `无法解析符号 ''`)。
|
||||||
|
已对相关文件移除 BOM(例如 PlayerState / PlayerCharacter / GameInstance 等)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. 下一步建议(可选)
|
||||||
|
|
||||||
|
- 将二级属性派生系数数据化(DataAsset/CurveTable),减少硬编码常量。
|
||||||
|
- 装备/功法:用 GE 直接对二级属性加成(穿甲/吸血等更适合走装备词条)。
|
||||||
|
- 战斗结算库:统一命中/闪避/招架/反击触发顺序与公式。
|
||||||
|
- Regen 增加“脱战 N 秒后生效”等状态控制(GameplayTag 驱动)。
|
||||||
|
|
||||||
30
PHY.uproject
Normal file
30
PHY.uproject
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
"FileVersion": 3,
|
||||||
|
"EngineAssociation": "5.7",
|
||||||
|
"Category": "",
|
||||||
|
"Description": "",
|
||||||
|
"Modules": [
|
||||||
|
{
|
||||||
|
"Name": "PHY",
|
||||||
|
"Type": "Runtime",
|
||||||
|
"LoadingPhase": "Default",
|
||||||
|
"AdditionalDependencies": [
|
||||||
|
"Engine"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "PHYInventory",
|
||||||
|
"Type": "Runtime",
|
||||||
|
"LoadingPhase": "Default"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Plugins": [
|
||||||
|
{
|
||||||
|
"Name": "ModelingToolsEditorMode",
|
||||||
|
"Enabled": true,
|
||||||
|
"TargetAllowList": [
|
||||||
|
"Editor"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
2
PHY_MMC_MaxHealth.cpp
Normal file
2
PHY_MMC_MaxHealth.cpp
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
// This file is deprecated and intentionally left empty.
|
||||||
|
|
||||||
29
Plugins/GCS/Config/BaseGenericCombatSystem.ini
Normal file
29
Plugins/GCS/Config/BaseGenericCombatSystem.ini
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
[CoreRedirects]
|
||||||
|
;GGA1.5 migration.
|
||||||
|
+ClassRedirects = (OldName="/Script/GenericGameplayAbilities.GGA_AbilitySystemBPLibrary",NewName="/Script/GenericGameplayAbilities.GGA_AbilitySystemFunctionLibrary")
|
||||||
|
+FunctionRedirects = (OldName="/Script/GenericGameplayAbilities.GGA_GameplayEffectContainerFunctionLibrary.MakeEffectContainerSpecFromContainerWithEventData",NewName="/Script/GenericGameplayAbilities.GGA_GameplayEffectContainerFunctionLibrary.MakeEffectContainerSpec")
|
||||||
|
+FunctionRedirects = (OldName="/Script/GenericGameplayAttributes.GGA_AttributeSystemComponent.HandlePoseGameplayEffectExecute",NewName="/Script/GenericGameplayAttributes.GGA_AttributeSystemComponent.HandlePostGameplayEffectExecute")
|
||||||
|
|
||||||
|
|
||||||
|
+PropertyRedirects = (OldName="/Script/GenericCombatSystem.GCS_TraceDefinition.DistanceTickInterval",NewName="/Script/GenericCombatSystem.GCS_TraceDefinition.DistanceTickThreshold")
|
||||||
|
+EnumRedirects = (OldName="/Script/GenericCombatSystem.EGCS_AttackResultProcessorPolicy",ValueChanges=(("Always","Default")))
|
||||||
|
+PropertyRedirects = (OldName="/Script/GenericCombatSystem.GCS_ComboDefinition.PayloadTag",NewName="/Script/GenericCombatSystem.GCS_ComboDefinition.EventTag")
|
||||||
|
|
||||||
|
;GCS1.5 migration
|
||||||
|
+PropertyRedirects = (OldName="/Script/GenericCombatSystem.GCS_CollisionSystemComponent.OnTraceInstanceHitEvent",NewName="/Script/GenericCombatSystem.GCS_TraceSystemComponent.OnTraceHitEvent")
|
||||||
|
+ClassRedirects = (OldName="/Script/GenericCombatSystem.GCS_CollisionSystemComponent",NewName="/Script/GenericCombatSystem.GCS_TraceSystemComponent")
|
||||||
|
+FunctionRedirects = (OldName="/Script/GenericCombatSystem.GCS_TraceSystemComponent.GetCollisionSystemComponent",NewName="/Script/GenericCombatSystem.GCS_TraceSystemComponent.GetTraceSystemComponent")
|
||||||
|
+FunctionRedirects = (OldName="/Script/GenericCombatSystem.GCS_TraceSystemComponent.FindCollisionSystemComponent",NewName="/Script/GenericCombatSystem.GCS_TraceSystemComponent.FindTraceSystemComponent")
|
||||||
|
+StructRedirects = (OldName="/Script/GenericCombatSystem.GCS_CollisionTraceDefinition",NewName="/Script/GenericCombatSystem.GCS_TraceDefinition")
|
||||||
|
+PackageRedirects = (OldName="/Game/GenericGame/CombatSystem/Core/Abilities/LightAttack/GA_GCS_SkillAttack",NewName="/Game/GenericGame/CombatSystem/Core/Abilities/Attack/GA_GCS_SkillAttack")
|
||||||
|
+PackageRedirects = (OldName="GA_GCS_HoldAttack_C",NewName="GA_GCS_SkillAttack_Charged_C")
|
||||||
|
+ClassRedirects = (OldName="/Game/GenericGame/CombatSystem/Core/BC_GCS_AttributeSystemComponent.BC_GCS_AttributeSystemComponent_C",NewName="/Game/GenericGame/CombatSystem/Core/BC_GCS_AttributeSystem.BC_GCS_AttributeSystem_C")
|
||||||
|
+ClassRedirects = (OldName="/Game/GenericGame/CombatSystem/Core/BC_GCS_CombatComponent.BC_GCS_CombatComponent_C",NewName="/Game/GenericGame/CombatSystem/Core/BC_GCS_CombatSystem.BC_GCS_CombatSystem_C")
|
||||||
|
+ClassRedirects = (OldName="/Game/GenericGame/CombatSystem/Core/BC_GCS_CombatCore.BC_GCS_CombatCore_C",NewName="/Game/GenericGame/CombatSystem/Core/BC_GCS_CombatEntity.BC_GCS_CombatEntity_C")
|
||||||
|
+ClassRedirects = (OldName="/Game/GenericDemo/GCS/Blueprints/BC_DemoCombatCore.BC_DemoCombatCore_C",NewName="/Game/GenericDemo/GCS/Blueprints/BC_DemoCombatEntity.BC_DemoCombatEntity_C")
|
||||||
|
+ClassRedirects = (OldName="/Script/GenericCombatSystem.GCS_CombatInterface", NewName="/Script/GenericCombatSystem.GCS_CombatEntityInterface")
|
||||||
|
+FunctionRedirects = (OldName="/Script/GenericCombatSystem.GCS_CombatEntityInterface.GCS_SetWeapon",NewName="/Script/GenericCombatSystem.GCS_CombatEntityInterface.SetCurrentWeapon")
|
||||||
|
+FunctionRedirects = (OldName="/Script/GenericCombatSystem.GCS_CombatEntityInterface.GCS_GetWeapon",NewName="/Script/GenericCombatSystem.GCS_CombatEntityInterface.GetCurrentWeapon")
|
||||||
|
+FunctionRedirects = (OldName="/Script/GenericCombatSystem.GCS_CombatEntityInterface.GCS_GetRelativeTransformToSocket",NewName="/Script/GenericCombatSystem.GCS_CombatEntityInterface.GetRelativeTransformToSocket")
|
||||||
|
+FunctionRedirects = (OldName="/Script/GenericCombatSystem.GCS_CombatEntityInterface.GetMovementInputDirection",NewName="/Script/GenericCombatSystem.GCS_CombatEntityInterface.GetMovementIntent")
|
||||||
|
+FunctionRedirects = (OldName="/Script/GenericCombatSystem.GCS_CombatFunctionLibrary.GetCombatInterface",NewName="/Script/GenericCombatSystem.GCS_CombatFunctionLibrary.GetCombatEntityInterface")
|
||||||
9
Plugins/GCS/Config/FilterPlugin.ini
Normal file
9
Plugins/GCS/Config/FilterPlugin.ini
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
[FilterPlugin]
|
||||||
|
; This section lists additional files which will be packaged along with your plugin. Paths should be listed relative to the root plugin directory, and
|
||||||
|
; may include "...", "*", and "?" wildcards to match directories, files, and individual characters respectively.
|
||||||
|
;
|
||||||
|
; Examples:
|
||||||
|
; /README.txt
|
||||||
|
; /Extras/...
|
||||||
|
; /Binaries/ThirdParty/*.dll
|
||||||
|
/Config/*
|
||||||
96
Plugins/GCS/GenericCombatSystem.uplugin
Normal file
96
Plugins/GCS/GenericCombatSystem.uplugin
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
{
|
||||||
|
"FileVersion": 3,
|
||||||
|
"Version": 8,
|
||||||
|
"VersionName": "1.5",
|
||||||
|
"FriendlyName": "GenericCombatSystem",
|
||||||
|
"Description": "Advanced GAS based Multiplayer combat framework.",
|
||||||
|
"Category": "Gameplay",
|
||||||
|
"CreatedBy": "YuewuDev",
|
||||||
|
"CreatedByURL": "https://yuewu.dev/en",
|
||||||
|
"DocsURL": "https://www.yuewu.dev/en/wiki",
|
||||||
|
"MarketplaceURL": "com.epicgames.launcher://ue/Fab/product/d4d45c2c-c698-4274-bb14-5474b7880a01",
|
||||||
|
"SupportURL": "https://discord.com/invite/xMRXAB2",
|
||||||
|
"EngineVersion": "5.7.0",
|
||||||
|
"CanContainContent": false,
|
||||||
|
"Installed": true,
|
||||||
|
"Modules": [
|
||||||
|
{
|
||||||
|
"Name": "GenericInputSystem",
|
||||||
|
"Type": "Runtime",
|
||||||
|
"LoadingPhase": "Default",
|
||||||
|
"PlatformAllowList": [
|
||||||
|
"Win64",
|
||||||
|
"Android",
|
||||||
|
"Linux"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "GenericGameplayAbilities",
|
||||||
|
"Type": "Runtime",
|
||||||
|
"LoadingPhase": "Default",
|
||||||
|
"PlatformAllowList": [
|
||||||
|
"Win64",
|
||||||
|
"Android",
|
||||||
|
"Linux"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "GenericGameplayAttributes",
|
||||||
|
"Type": "Runtime",
|
||||||
|
"LoadingPhase": "Default",
|
||||||
|
"PlatformAllowList": [
|
||||||
|
"Win64",
|
||||||
|
"Android",
|
||||||
|
"Linux"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "GenericGameplayAbilitiesEditor",
|
||||||
|
"Type": "Editor",
|
||||||
|
"LoadingPhase": "Default",
|
||||||
|
"PlatformAllowList": [
|
||||||
|
"Win64"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "GenericCombatSystem",
|
||||||
|
"Type": "Runtime",
|
||||||
|
"LoadingPhase": "Default",
|
||||||
|
"PlatformAllowList": [
|
||||||
|
"Win64",
|
||||||
|
"Android",
|
||||||
|
"Linux"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Plugins": [
|
||||||
|
{
|
||||||
|
"Name": "EnhancedInput",
|
||||||
|
"Enabled": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "GameplayAbilities",
|
||||||
|
"Enabled": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "ModularGameplay",
|
||||||
|
"Enabled": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "TargetingSystem",
|
||||||
|
"Enabled": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "StateTree",
|
||||||
|
"Enabled": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "GameplayStateTree",
|
||||||
|
"Enabled": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "MotionWarping",
|
||||||
|
"Enabled": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
BIN
Plugins/GCS/Resources/Icon128.png
Normal file
BIN
Plugins/GCS/Resources/Icon128.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
@@ -0,0 +1,55 @@
|
|||||||
|
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||||
|
|
||||||
|
using System.IO;
|
||||||
|
using UnrealBuildTool;
|
||||||
|
|
||||||
|
public class GenericCombatSystem : ModuleRules
|
||||||
|
{
|
||||||
|
public GenericCombatSystem(ReadOnlyTargetRules Target) : base(Target)
|
||||||
|
{
|
||||||
|
PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
|
||||||
|
|
||||||
|
PublicIncludePaths.AddRange(
|
||||||
|
new[]
|
||||||
|
{
|
||||||
|
Path.Combine(ModuleDirectory, "Public/AbilitySystem"),
|
||||||
|
// ... add public include paths required here ...
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
PrivateIncludePaths.AddRange(
|
||||||
|
new[]
|
||||||
|
{
|
||||||
|
Path.Combine(ModuleDirectory, "Public/AbilitySystem"),
|
||||||
|
// ... add public include paths required here ...
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
PublicDependencyModuleNames.AddRange(
|
||||||
|
new string[]
|
||||||
|
{
|
||||||
|
"Core",
|
||||||
|
"GameplayTags",
|
||||||
|
"GameplayTasks",
|
||||||
|
"GameplayAbilities",
|
||||||
|
"GenericGameplayAbilities",
|
||||||
|
"GenericGameplayAttributes",
|
||||||
|
"ModularGameplay",
|
||||||
|
"TargetingSystem"
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
PrivateDependencyModuleNames.AddRange(
|
||||||
|
new string[]
|
||||||
|
{
|
||||||
|
"CoreUObject",
|
||||||
|
"NetCore",
|
||||||
|
"Engine",
|
||||||
|
"Niagara",
|
||||||
|
"AIModule",
|
||||||
|
"StateTreeModule",
|
||||||
|
"DeveloperSettings"
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||||
|
|
||||||
|
|
||||||
|
#include "Abilities/GCS_CombatAbility.h"
|
||||||
|
|
||||||
|
#include "GCS_CombatSystemComponent.h"
|
||||||
|
#include "Utility/GCS_CombatFunctionLibrary.h"
|
||||||
|
|
||||||
|
UGCS_CombatSystemComponent* UGCS_CombatAbility::GetCombatSystemFromActorInfo() const
|
||||||
|
{
|
||||||
|
if (UGCS_CombatSystemComponent* CSS = UGCS_CombatSystemComponent::GetCombatSystemComponent(GetAvatarActorFromActorInfo()))
|
||||||
|
{
|
||||||
|
return CSS;
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
UObject* UGCS_CombatAbility::GetCombatEntityFromActorInfo() const
|
||||||
|
{
|
||||||
|
return UGCS_CombatFunctionLibrary::GetCombatEntity(GetAvatarActorFromActorInfo());
|
||||||
|
}
|
||||||
|
|
||||||
|
TScriptInterface<IGCS_CombatEntityInterface> UGCS_CombatAbility::GetCombatEntityInterfaceFromActorInfo() const
|
||||||
|
{
|
||||||
|
return UGCS_CombatFunctionLibrary::GetCombatEntityInterface(GetAvatarActorFromActorInfo());
|
||||||
|
}
|
||||||
@@ -0,0 +1,357 @@
|
|||||||
|
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||||
|
|
||||||
|
|
||||||
|
#include "Abilities/GCS_ComboAbility.h"
|
||||||
|
|
||||||
|
#include "AbilitySystemComponent.h"
|
||||||
|
#include "GCS_CombatEntityInterface.h"
|
||||||
|
#include "GCS_CombatSystemComponent.h"
|
||||||
|
#include "GCS_LogChannels.h"
|
||||||
|
#include "GGA_GameplayTags.h"
|
||||||
|
#include "Abilities/Tasks/AbilityTask_WaitInputPress.h"
|
||||||
|
#include "Combo/GCS_ComboDefinition.h"
|
||||||
|
#include "Utilities/GGA_AbilitySystemFunctionLibrary.h"
|
||||||
|
#include "Weapon/GCS_WeaponInterface.h"
|
||||||
|
|
||||||
|
UGCS_ComboAbility::UGCS_ComboAbility()
|
||||||
|
{
|
||||||
|
ReplicationPolicy = EGameplayAbilityReplicationPolicy::ReplicateYes;
|
||||||
|
NetExecutionPolicy = EGameplayAbilityNetExecutionPolicy::LocalPredicted;
|
||||||
|
AbilityTags.AddTagFast(GGA_AbilityTraitTags::ActivationOnSpawn);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UGCS_ComboAbility::PreActivate(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo,
|
||||||
|
FOnGameplayAbilityEnded::FDelegate* OnGameplayAbilityEndedDelegate, const FGameplayEventData* TriggerEventData)
|
||||||
|
{
|
||||||
|
Super::PreActivate(Handle, ActorInfo, ActivationInfo, OnGameplayAbilityEndedDelegate, TriggerEventData);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UGCS_ComboAbility::ActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo,
|
||||||
|
const FGameplayEventData* TriggerEventData)
|
||||||
|
{
|
||||||
|
UGCS_CombatSystemComponent* CombatSys = GetCombatSystemFromActorInfo();
|
||||||
|
if (!CombatSys)
|
||||||
|
{
|
||||||
|
EndAbility(CurrentSpecHandle, CurrentActorInfo, CurrentActivationInfo, true, false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
UAbilitySystemComponent* ASC = GetAbilitySystemComponentFromActorInfo();
|
||||||
|
AbilityEndedDelegateHandle = ASC->OnAbilityEnded.AddUObject(this, &ThisClass::HandleAbilityEnd);
|
||||||
|
|
||||||
|
Super::ActivateAbility(Handle, ActorInfo, ActivationInfo, TriggerEventData);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UGCS_ComboAbility::CanActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayTagContainer* SourceTags,
|
||||||
|
const FGameplayTagContainer* TargetTags, FGameplayTagContainer* OptionalRelevantTags) const
|
||||||
|
{
|
||||||
|
return Super::CanActivateAbility(Handle, ActorInfo, SourceTags, TargetTags, OptionalRelevantTags);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UGCS_ComboAbility::OnGiveAbility(const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilitySpec& Spec)
|
||||||
|
{
|
||||||
|
Super::OnGiveAbility(ActorInfo, Spec);
|
||||||
|
// GiveSubAbilities(Spec);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UGCS_ComboAbility::OnRemoveAbility(const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilitySpec& Spec)
|
||||||
|
{
|
||||||
|
Super::OnRemoveAbility(ActorInfo, Spec);
|
||||||
|
// RemoveSubAbilities();
|
||||||
|
}
|
||||||
|
|
||||||
|
void UGCS_ComboAbility::EndAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, bool bReplicateEndAbility,
|
||||||
|
bool bWasCancelled)
|
||||||
|
{
|
||||||
|
UAbilitySystemComponent* ASC = GetAbilitySystemComponentFromActorInfo();
|
||||||
|
|
||||||
|
if (IsValid(ASC) && AbilityEndedDelegateHandle.IsValid())
|
||||||
|
{
|
||||||
|
ASC->OnAbilityEnded.Remove(AbilityEndedDelegateHandle);
|
||||||
|
AbilityEndedDelegateHandle.Reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
// final check to make current ability ends(no callback will fired.)!
|
||||||
|
if (CurrentAbility.IsValid() && !bCurrentAbilityEnded)
|
||||||
|
{
|
||||||
|
ASC->CancelAbilityHandle(CurrentAbility);
|
||||||
|
CurrentAbility = FGameplayAbilitySpecHandle();
|
||||||
|
CurrentAbilityClass = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
ResetCombo();
|
||||||
|
|
||||||
|
Super::EndAbility(Handle, ActorInfo, ActivationInfo, bReplicateEndAbility, bWasCancelled);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UGCS_ComboAbility::AllowAdvanceCombo_Implementation() const
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UGCS_ComboAbility::StartCombo_Implementation(const FGameplayEventData& ComboEvent)
|
||||||
|
{
|
||||||
|
HandleComboExecution(ComboEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UGCS_ComboAbility::AdvanceCombo_Implementation(const FGameplayEventData& ComboEventData)
|
||||||
|
{
|
||||||
|
if (!AllowAdvanceCombo())
|
||||||
|
{
|
||||||
|
GCS_CLOG(Verbose, "Can't advanced combo due to AllowAdvanceCombo.")
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
HandleComboExecution(ComboEventData);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UGCS_ComboAbility::ResetCombo_Implementation()
|
||||||
|
{
|
||||||
|
DesiredComboStep = INDEX_NONE;
|
||||||
|
bCurrentAbilityEnded = false;
|
||||||
|
UGCS_CombatSystemComponent* CombatSys = GetCombatSystemFromActorInfo();
|
||||||
|
if (IsValid(CombatSys))
|
||||||
|
{
|
||||||
|
CombatSys->ResetComboState();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UGCS_ComboAbility::HandleAbilityEnd(const FAbilityEndedData& AbilityEndedData)
|
||||||
|
{
|
||||||
|
//Already ends
|
||||||
|
if (bCurrentAbilityEnded)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//no ability running.
|
||||||
|
if (!CurrentAbility.IsValid() || CurrentAbility != AbilityEndedData.AbilitySpecHandle)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// GCS_CLOG(Verbose, "Current ability:%s ended. bWasCancelled:%d", *CurrentAbilityClass->GetName(), AbilityEndedData.bWasCancelled)
|
||||||
|
bCurrentAbilityEnded = true;
|
||||||
|
CurrentAbility = FGameplayAbilitySpecHandle();
|
||||||
|
CurrentAbilityClass = nullptr;
|
||||||
|
|
||||||
|
// No next combo.
|
||||||
|
if (DesiredComboStep == INDEX_NONE)
|
||||||
|
{
|
||||||
|
ResetCombo();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UGCS_ComboAbility::SelectComboDefinition(const FGameplayEventData& ComboEventData, int32 CurrentStep, FGCS_ComboDefinition& OutDefinition)
|
||||||
|
{
|
||||||
|
UAbilitySystemComponent* ASC = GetAbilitySystemComponentFromActorInfo();
|
||||||
|
UObject* Combat = GetCombatEntityFromActorInfo();
|
||||||
|
if (!ASC || !Combat) return false;
|
||||||
|
|
||||||
|
TObjectPtr<const UDataTable> ComboDefinitionTable = IGCS_CombatEntityInterface::Execute_GetComboDefinitionTable(Combat);
|
||||||
|
if (ComboDefinitionTable == nullptr)
|
||||||
|
{
|
||||||
|
GCS_CLOG(Error, "No combo definition table found from combat interface:%s, check your GetComboDefinitionTable implementation!", *GetNameSafe(Combat));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
FGameplayTagContainer CurrentTags;
|
||||||
|
ASC->GetOwnedGameplayTags(CurrentTags);
|
||||||
|
|
||||||
|
// Find best matching row in data table
|
||||||
|
for (const auto& RowPair : ComboDefinitionTable->GetRowMap())
|
||||||
|
{
|
||||||
|
const FGCS_ComboDefinition* Row = reinterpret_cast<const FGCS_ComboDefinition*>(RowPair.Value);
|
||||||
|
if (!Row || Row->AbilityClass.IsNull()) continue;
|
||||||
|
|
||||||
|
if (!Row->TagQuery.IsEmpty() && !Row->TagQuery.Matches(CurrentTags))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// skip if event tag doesn't match.
|
||||||
|
if (ComboEventData.EventTag.IsValid() && Row->EventTag.IsValid() && ComboEventData.EventTag != Row->EventTag)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// skip if event instigatorTags
|
||||||
|
if (!Row->EventInstigatorTagQuery.IsEmpty() && !Row->EventInstigatorTagQuery.Matches(ComboEventData.InstigatorTags))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (CurrentStep > 0 && Row->MinComboStep == 0)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// skip if MinComboStep < CurrentStep(only for non resetting combo)
|
||||||
|
if (!Row->bResetComboStep && Row->MinComboStep > 0 && Row->MinComboStep < CurrentStep) continue;
|
||||||
|
|
||||||
|
// skip if ability class is invalid.
|
||||||
|
TSubclassOf<UGameplayAbility> TempAbilityClass = Row->AbilityClass.LoadSynchronous();
|
||||||
|
if (TempAbilityClass == nullptr)
|
||||||
|
{
|
||||||
|
GCS_CLOG(Verbose, "invalid ability class found on row name:%s", *RowPair.Key.ToString());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
FGameplayAbilitySpecHandle FoundAbility;
|
||||||
|
if (!UGGA_AbilitySystemFunctionLibrary::FindAbilityFromClass(ASC, FoundAbility, TempAbilityClass, GetCurrentSourceObject()))
|
||||||
|
{
|
||||||
|
GCS_CLOG(Verbose, "skipped ability of class(%s) as it is not exists on ASC.", *TempAbilityClass->GetName());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Row->bRunActivationTest)
|
||||||
|
{
|
||||||
|
FGameplayTagContainer RelevantTags;
|
||||||
|
if (!UGGA_AbilitySystemFunctionLibrary::CanActivateAbility(ASC, FoundAbility, RelevantTags))
|
||||||
|
{
|
||||||
|
if (Row->bAbortIfActivationTestFailed)
|
||||||
|
{
|
||||||
|
GCS_CLOG(Verbose, "Activation test failed for ability:%s, reason:%s. Combo will be aborted.", *TempAbilityClass->GetName(), *RelevantTags.ToString());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
{
|
||||||
|
GCS_CLOG(Verbose, "skipped ability of class(%s) due to activation test failed, reason:%s", *TempAbilityClass->GetName(), *RelevantTags.ToString());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// skip if custom rule doesn't met.
|
||||||
|
if (!CanSelectedComboDefinition(ComboEventData, CurrentStep, OutDefinition))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
GCS_CLOG(Verbose, "selected ability of class(%s) as next combo(%d).", *TempAbilityClass->GetName(), CurrentStep);
|
||||||
|
OutDefinition = *Row;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UGCS_ComboAbility::CanSelectedComboDefinition_Implementation(const FGameplayEventData& ComboEvent, int32 CurrentStep, const FGCS_ComboDefinition& ComboDefinition) const
|
||||||
|
{
|
||||||
|
// Example code.
|
||||||
|
// FYourCustomData Data = ComboDefinition.Extension.Get<FYourCustomData>();
|
||||||
|
// UObject* Weapon = IGCS_CombatInterface::Execute_GCS_GetWeapon(GetCombatInterfaceFromActorInfo(), nullptr);
|
||||||
|
// return IGCS_WeaponInterface::Execute_GetWeaponTags(Weapon).MatchesQuery(Data.WeaponTagRequirements);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UGCS_ComboAbility::HandleComboExecution(const FGameplayEventData& ComboEventData)
|
||||||
|
{
|
||||||
|
if (!IsLocallyControlled())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
UAbilitySystemComponent* ASC = GetAbilitySystemComponentFromActorInfo();
|
||||||
|
if (!ASC) return;
|
||||||
|
|
||||||
|
UGCS_CombatSystemComponent* CombatSys = GetCombatSystemFromActorInfo();
|
||||||
|
int32 CurrentStep = CombatSys ? CombatSys->GetComboStep() : 0;
|
||||||
|
|
||||||
|
bool bStartingCombo = CurrentStep == 0;
|
||||||
|
|
||||||
|
FGCS_ComboDefinition ComboDefinition;
|
||||||
|
|
||||||
|
if (!SelectComboDefinition(ComboEventData, CurrentStep, ComboDefinition))
|
||||||
|
{
|
||||||
|
GCS_CLOG(Verbose, "No next combo definition with current combo step:%d", CurrentStep);
|
||||||
|
DesiredComboStep = INDEX_NONE; // mark for no next action.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
TSubclassOf<UGameplayAbility> FoundAbilityClass = ComboDefinition.AbilityClass.Get();
|
||||||
|
|
||||||
|
FGameplayAbilitySpecHandle FoundAbility;
|
||||||
|
UGGA_AbilitySystemFunctionLibrary::FindAbilityFromClass(ASC, FoundAbility, FoundAbilityClass, GetCurrentSourceObject());
|
||||||
|
check(FoundAbility.IsValid())
|
||||||
|
|
||||||
|
bool bShouldResetComboStep = !bStartingCombo && ComboDefinition.MinComboStep > 0 && ComboDefinition.bResetComboStep;
|
||||||
|
|
||||||
|
// Mark the desired step.
|
||||||
|
DesiredComboStep = bShouldResetComboStep ? 0 : CombatSys->GetComboStep() + 1;
|
||||||
|
|
||||||
|
// Cancel current ability if still running.
|
||||||
|
if (CurrentAbility.IsValid() && !bCurrentAbilityEnded)
|
||||||
|
{
|
||||||
|
ASC->CancelAbilityHandle(CurrentAbility);
|
||||||
|
CurrentAbility = FGameplayAbilitySpecHandle();
|
||||||
|
CurrentAbilityClass = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bCurrentAbilityEnded = false;
|
||||||
|
|
||||||
|
//Combo Ability本身是预测激活的.
|
||||||
|
if (ASC->TryActivateAbility(FoundAbility, true))
|
||||||
|
{
|
||||||
|
CurrentAbility = FoundAbility;
|
||||||
|
CurrentAbilityClass = FoundAbilityClass;
|
||||||
|
CombatSys->UpdateComboStep(DesiredComboStep);
|
||||||
|
GCS_CLOG(Verbose, "combo advanced from step %d to %d with ability:%s", CurrentStep, DesiredComboStep, *FoundAbilityClass->GetName());
|
||||||
|
DesiredComboStep = INDEX_NONE;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
GCS_CLOG(Verbose, "combo reset as the new ability(%s) can't be activated when advancing from step %d to %d.", *FoundAbilityClass->GetName(), CurrentStep, DesiredComboStep);
|
||||||
|
ResetCombo();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// void UGCS_ComboAbility::GiveSubAbilities(const FGameplayAbilitySpec& CurrentSpec)
|
||||||
|
// {
|
||||||
|
// UAbilitySystemComponent* ASC = GetAbilitySystemComponentFromActorInfo();
|
||||||
|
// if (!IsValid(ASC) || !ASC->IsOwnerActorAuthoritative())
|
||||||
|
// {
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // Find best matching row in data table
|
||||||
|
// for (const auto& RowPair : ComboDefinitionTable->GetRowMap())
|
||||||
|
// {
|
||||||
|
// const FGCS_ComboDefinition* Row = reinterpret_cast<const FGCS_ComboDefinition*>(RowPair.Value);
|
||||||
|
// if (!Row || Row->AbilityClass.IsNull()) continue;
|
||||||
|
// TSubclassOf<UGameplayAbility> AbilityClass = Row->AbilityClass.LoadSynchronous();
|
||||||
|
//
|
||||||
|
// if (FGameplayAbilitySpec* ExistingSpec = UGGA_AbilitySystemFunctionLibrary::FindAbilitySpecFromClass(ASC, AbilityClass, CurrentSpec.SourceObject.Get()))
|
||||||
|
// {
|
||||||
|
// GCS_CLOG(Warning, "Found existing ability with same class(%s) and source object(%s),skipped.", *AbilityClass->GetName(), *GetNameSafe(CurrentSpec.SourceObject.Get()))
|
||||||
|
// continue;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// FGameplayAbilitySpec NewSpec = ASC->BuildAbilitySpecFromClass(AbilityClass, Row->MinComboStep > 0 ? Row->MinComboStep : 0);
|
||||||
|
// NewSpec.SourceObject = CurrentSpec.SourceObject;
|
||||||
|
// const FGameplayAbilitySpecHandle& AbilitySpecHandle = ASC->GiveAbility(NewSpec);
|
||||||
|
// if (AbilitySpecHandle.IsValid())
|
||||||
|
// {
|
||||||
|
// AvailableAbilities.Add(AbilitySpecHandle);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// void UGCS_ComboAbility::RemoveSubAbilities()
|
||||||
|
// {
|
||||||
|
// UAbilitySystemComponent* ASC = GetAbilitySystemComponentFromActorInfo();
|
||||||
|
// if (!IsValid(ASC))
|
||||||
|
// {
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// for (FGameplayAbilitySpecHandle SubAbilityHandle : AvailableAbilities)
|
||||||
|
// {
|
||||||
|
// ASC->ClearAbility(SubAbilityHandle);
|
||||||
|
// }
|
||||||
|
// AvailableAbilities.Empty();
|
||||||
|
// }
|
||||||
|
|
||||||
|
#if WITH_EDITOR
|
||||||
|
EDataValidationResult UGCS_ComboAbility::IsDataValid(class FDataValidationContext& Context) const
|
||||||
|
{
|
||||||
|
return Super::IsDataValid(Context);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,255 @@
|
|||||||
|
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||||
|
|
||||||
|
#include "AbilitySystem/Attributes/AS_Poise.h"
|
||||||
|
|
||||||
|
#include "Net/UnrealNetwork.h"
|
||||||
|
#include "AbilitySystemBlueprintLibrary.h"
|
||||||
|
#include "GameplayEffectExtension.h"
|
||||||
|
#include "GGA_GameplayAttributesHelper.h"
|
||||||
|
#include "GGA_AttributeSystemComponent.h"
|
||||||
|
|
||||||
|
namespace AS_Poise
|
||||||
|
{
|
||||||
|
|
||||||
|
UE_DEFINE_GAMEPLAY_TAG_COMMENT(Poise, TEXT("GGF.Attribute.PoiseSet.Poise"), "Current Poise value of an actor.(actor的当前抗打击值)")
|
||||||
|
|
||||||
|
UE_DEFINE_GAMEPLAY_TAG_COMMENT(MaxPoise, TEXT("GGF.Attribute.PoiseSet.MaxPoise"), "Max Poise value of an actor.(actor的最大抗打击值)")
|
||||||
|
|
||||||
|
UE_DEFINE_GAMEPLAY_TAG_COMMENT(PoiseRecover, TEXT("GGF.Attribute.PoiseSet.PoiseRecover"), "How many Poise to recover per second.(每秒恢复抗打击值)")
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
UAS_Poise::UAS_Poise()
|
||||||
|
{
|
||||||
|
|
||||||
|
UGGA_GameplayAttributesHelper::RegisterTagToAttribute(AS_Poise::Poise,GetPoiseAttribute());
|
||||||
|
|
||||||
|
UGGA_GameplayAttributesHelper::RegisterTagToAttribute(AS_Poise::MaxPoise,GetMaxPoiseAttribute());
|
||||||
|
|
||||||
|
UGGA_GameplayAttributesHelper::RegisterTagToAttribute(AS_Poise::PoiseRecover,GetPoiseRecoverAttribute());
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void UAS_Poise::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
|
||||||
|
{
|
||||||
|
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
|
||||||
|
|
||||||
|
DOREPLIFETIME_CONDITION_NOTIFY(ThisClass, Poise, COND_None, REPNOTIFY_Always);
|
||||||
|
|
||||||
|
DOREPLIFETIME_CONDITION_NOTIFY(ThisClass, MaxPoise, COND_None, REPNOTIFY_Always);
|
||||||
|
|
||||||
|
DOREPLIFETIME_CONDITION_NOTIFY(ThisClass, PoiseRecover, COND_None, REPNOTIFY_Always);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void UAS_Poise::PreAttributeChange(const FGameplayAttribute& Attribute, float& NewValue)
|
||||||
|
{
|
||||||
|
Super::PreAttributeChange(Attribute, NewValue);
|
||||||
|
|
||||||
|
|
||||||
|
if (Attribute == GetPoiseAttribute())
|
||||||
|
{
|
||||||
|
NewValue = FMath::Clamp(NewValue,0,GetMaxPoise());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if (AActor* Actor = GetOwningActor())
|
||||||
|
{
|
||||||
|
if (UGGA_AttributeSystemComponent* ASS = Actor->FindComponentByClass<UGGA_AttributeSystemComponent>())
|
||||||
|
{
|
||||||
|
ASS->ReceivePreAttributeChange(this,Attribute,NewValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UAS_Poise::PreGameplayEffectExecute(FGameplayEffectModCallbackData& Data)
|
||||||
|
{
|
||||||
|
if (AActor* Actor = GetOwningActor())
|
||||||
|
{
|
||||||
|
if (UGGA_AttributeSystemComponent* ASS = Actor->FindComponentByClass<UGGA_AttributeSystemComponent>())
|
||||||
|
{
|
||||||
|
return ASS->ReceivePreGameplayEffectExecute(this, Data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Super::PreGameplayEffectExecute(Data);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UAS_Poise::PostAttributeChange(const FGameplayAttribute& Attribute, float OldValue, float NewValue)
|
||||||
|
{
|
||||||
|
Super::PostAttributeChange(Attribute, OldValue, NewValue);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if (Attribute == GetMaxPoiseAttribute())
|
||||||
|
{
|
||||||
|
AdjustAttributeForMaxChange(Poise, OldValue, NewValue, GetPoiseAttribute());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if (AActor* Actor = GetOwningActor())
|
||||||
|
{
|
||||||
|
if (UGGA_AttributeSystemComponent* ASS = Actor->FindComponentByClass<UGGA_AttributeSystemComponent>())
|
||||||
|
{
|
||||||
|
ASS->ReceivePostAttributeChange(this, Attribute, OldValue, NewValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UAS_Poise::PostGameplayEffectExecute(const FGameplayEffectModCallbackData& Data)
|
||||||
|
{
|
||||||
|
Super::PostGameplayEffectExecute(Data);
|
||||||
|
|
||||||
|
|
||||||
|
if (Data.EvaluatedData.Attribute == GetPoiseAttribute())
|
||||||
|
{
|
||||||
|
SetPoise(FMath::Clamp(GetPoise(),0,GetMaxPoise()));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if (AActor* Actor = GetOwningActor())
|
||||||
|
{
|
||||||
|
if (UGGA_AttributeSystemComponent* ASS = Actor->FindComponentByClass<UGGA_AttributeSystemComponent>())
|
||||||
|
{
|
||||||
|
ASS->ReceivePostGameplayEffectExecute(this,Data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UAS_Poise::AdjustAttributeForMaxChange(FGameplayAttributeData& AffectedAttribute, const FGameplayAttributeData& MaxAttribute, float NewMaxValue,
|
||||||
|
const FGameplayAttribute& AffectedAttributeProperty)
|
||||||
|
{
|
||||||
|
UAbilitySystemComponent* AbilityComp = GetOwningAbilitySystemComponent();
|
||||||
|
const float CurrentMaxValue = MaxAttribute.GetCurrentValue();
|
||||||
|
if (!FMath::IsNearlyEqual(CurrentMaxValue, NewMaxValue) && AbilityComp)
|
||||||
|
{
|
||||||
|
// Change current value to maintain the current Val / Max percent
|
||||||
|
const float CurrentValue = AffectedAttribute.GetCurrentValue();
|
||||||
|
float NewDelta = (CurrentMaxValue > 0.f) ? (CurrentValue * NewMaxValue / CurrentMaxValue) - CurrentValue : NewMaxValue;
|
||||||
|
|
||||||
|
AbilityComp->ApplyModToAttributeUnsafe(AffectedAttributeProperty, EGameplayModOp::Additive, NewDelta);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
FGameplayAttribute UAS_Poise::Bp_GetPoiseAttribute()
|
||||||
|
{
|
||||||
|
return ThisClass::GetPoiseAttribute();
|
||||||
|
}
|
||||||
|
|
||||||
|
float UAS_Poise::Bp_GetPoise() const
|
||||||
|
{
|
||||||
|
return GetPoise();
|
||||||
|
}
|
||||||
|
|
||||||
|
void UAS_Poise::Bp_SetPoise(float NewValue)
|
||||||
|
{
|
||||||
|
SetPoise(NewValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UAS_Poise::Bp_InitPoise(float NewValue)
|
||||||
|
{
|
||||||
|
InitPoise(NewValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UAS_Poise::OnRep_Poise(const FGameplayAttributeData& OldValue)
|
||||||
|
{
|
||||||
|
GAMEPLAYATTRIBUTE_REPNOTIFY(ThisClass, Poise, OldValue);
|
||||||
|
if (AActor* Actor = GetOwningActor())
|
||||||
|
{
|
||||||
|
if (UGGA_AttributeSystemComponent* ASS = Actor->FindComponentByClass<UGGA_AttributeSystemComponent>())
|
||||||
|
{
|
||||||
|
ASS->ReceiveAttributeChange(this,GetPoiseAttribute(),GetPoise(),OldValue.GetCurrentValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
FGameplayAttribute UAS_Poise::Bp_GetMaxPoiseAttribute()
|
||||||
|
{
|
||||||
|
return ThisClass::GetMaxPoiseAttribute();
|
||||||
|
}
|
||||||
|
|
||||||
|
float UAS_Poise::Bp_GetMaxPoise() const
|
||||||
|
{
|
||||||
|
return GetMaxPoise();
|
||||||
|
}
|
||||||
|
|
||||||
|
void UAS_Poise::Bp_SetMaxPoise(float NewValue)
|
||||||
|
{
|
||||||
|
SetMaxPoise(NewValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UAS_Poise::Bp_InitMaxPoise(float NewValue)
|
||||||
|
{
|
||||||
|
InitMaxPoise(NewValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UAS_Poise::OnRep_MaxPoise(const FGameplayAttributeData& OldValue)
|
||||||
|
{
|
||||||
|
GAMEPLAYATTRIBUTE_REPNOTIFY(ThisClass, MaxPoise, OldValue);
|
||||||
|
if (AActor* Actor = GetOwningActor())
|
||||||
|
{
|
||||||
|
if (UGGA_AttributeSystemComponent* ASS = Actor->FindComponentByClass<UGGA_AttributeSystemComponent>())
|
||||||
|
{
|
||||||
|
ASS->ReceiveAttributeChange(this,GetMaxPoiseAttribute(),GetMaxPoise(),OldValue.GetCurrentValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
FGameplayAttribute UAS_Poise::Bp_GetPoiseRecoverAttribute()
|
||||||
|
{
|
||||||
|
return ThisClass::GetPoiseRecoverAttribute();
|
||||||
|
}
|
||||||
|
|
||||||
|
float UAS_Poise::Bp_GetPoiseRecover() const
|
||||||
|
{
|
||||||
|
return GetPoiseRecover();
|
||||||
|
}
|
||||||
|
|
||||||
|
void UAS_Poise::Bp_SetPoiseRecover(float NewValue)
|
||||||
|
{
|
||||||
|
SetPoiseRecover(NewValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UAS_Poise::Bp_InitPoiseRecover(float NewValue)
|
||||||
|
{
|
||||||
|
InitPoiseRecover(NewValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UAS_Poise::OnRep_PoiseRecover(const FGameplayAttributeData& OldValue)
|
||||||
|
{
|
||||||
|
GAMEPLAYATTRIBUTE_REPNOTIFY(ThisClass, PoiseRecover, OldValue);
|
||||||
|
if (AActor* Actor = GetOwningActor())
|
||||||
|
{
|
||||||
|
if (UGGA_AttributeSystemComponent* ASS = Actor->FindComponentByClass<UGGA_AttributeSystemComponent>())
|
||||||
|
{
|
||||||
|
ASS->ReceiveAttributeChange(this,GetPoiseRecoverAttribute(),GetPoiseRecover(),OldValue.GetCurrentValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||||
|
|
||||||
|
|
||||||
|
#include "AbilitySystem/DEPRECATED_GCS_AbilitySystemGlobals.h"
|
||||||
|
|
||||||
|
FGameplayEffectContext* UDEPRECATED_GCS_AbilitySystemGlobals::AllocGameplayEffectContext() const
|
||||||
|
{
|
||||||
|
return Super::AllocGameplayEffectContext();
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||||
|
|
||||||
|
|
||||||
|
#include "AbilitySystem/Effects/GCS_GEComponent_PredictivelyExecute.h"
|
||||||
|
|
||||||
|
#include "GameplayEffect.h"
|
||||||
|
#include "GCS_LogChannels.h"
|
||||||
|
#include "Utility/GCS_CombatFunctionLibrary.h"
|
||||||
|
|
||||||
|
void UGCS_GEComponent_PredictivelyExecute::OnGameplayEffectApplied(FActiveGameplayEffectsContainer& ActiveGEContainer, FGameplayEffectSpec& GESpec, FPredictionKey& PredictionKey) const
|
||||||
|
{
|
||||||
|
FGCS_ContextPayload_Combat* Payload = UGCS_CombatFunctionLibrary::EffectContextGetMutableCombatPayload(GESpec.GetEffectContext());
|
||||||
|
|
||||||
|
if (Payload)
|
||||||
|
{
|
||||||
|
Payload->PredictionKey = PredictionKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (GESpec.GetEffectContext().GetInstigator()->GetLocalRole() == ROLE_AutonomousProxy)
|
||||||
|
{
|
||||||
|
if (Payload)
|
||||||
|
{
|
||||||
|
Payload->bIsPredictingContext = true;
|
||||||
|
}
|
||||||
|
GCS_LOG(Verbose, "Ctx:%s %s predictively execute:%s", *GetClientServerContextString(GESpec.GetEffectContext().GetInstigator()), *GetNameSafe(GESpec.GetEffectContext().GetInstigator()),
|
||||||
|
*GetNameSafe(GESpec.Def))
|
||||||
|
ActiveGEContainer.PredictivelyExecuteEffectSpec(GESpec, PredictionKey, bPredictGameplayCues);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||||
|
|
||||||
|
|
||||||
|
#include "AbilitySystem/GCS_GameplayEffectContext.h"
|
||||||
|
|
||||||
|
void FGCS_ContextPayload_Combat::SetTaggedValue(const FGameplayTag& Tag, float NewValue)
|
||||||
|
{
|
||||||
|
if (Tag.IsValid())
|
||||||
|
{
|
||||||
|
bool bFound = false;
|
||||||
|
for (FGCS_TaggedValue& TaggedValue : TaggedValues)
|
||||||
|
{
|
||||||
|
if (TaggedValue.Attribute == Tag)
|
||||||
|
{
|
||||||
|
TaggedValue.Value = NewValue;
|
||||||
|
bFound = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!bFound)
|
||||||
|
{
|
||||||
|
FGCS_TaggedValue Temp;
|
||||||
|
Temp.Attribute = Tag;
|
||||||
|
Temp.Value = NewValue;
|
||||||
|
TaggedValues.Add(Temp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
float FGCS_ContextPayload_Combat::GetTaggedValue(const FGameplayTag& Tag) const
|
||||||
|
{
|
||||||
|
if (Tag.IsValid())
|
||||||
|
{
|
||||||
|
for (const FGCS_TaggedValue& TaggedValue : TaggedValues)
|
||||||
|
{
|
||||||
|
if (TaggedValue.Attribute == Tag)
|
||||||
|
{
|
||||||
|
return TaggedValue.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
@@ -0,0 +1,122 @@
|
|||||||
|
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||||
|
|
||||||
|
|
||||||
|
#include "GenericCombatSystem/Public/AbilitySystem/Tasks/GCS_AbilityTask_CollisionTrace.h"
|
||||||
|
#include "GCS_LogChannels.h"
|
||||||
|
#include "Collision/GCS_TraceSystemComponent.h"
|
||||||
|
#include "Collision/DEPRECATED_GCS_CollisionTraceInstance.h"
|
||||||
|
#include "CombatFlow/GCS_AttackRequest.h"
|
||||||
|
#include "Utility/GCS_CombatFunctionLibrary.h"
|
||||||
|
|
||||||
|
|
||||||
|
UGCS_AbilityTask_CollisionTrace::UGCS_AbilityTask_CollisionTrace()
|
||||||
|
{
|
||||||
|
// make sure this task runs on simulated proxy.
|
||||||
|
bSimulatedTask = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
UGCS_AbilityTask_CollisionTrace* UGCS_AbilityTask_CollisionTrace::HandleCollisionTraces(UGameplayAbility* OwningAbility, FName TaskInstanceName, bool bAdjustVisibilityBasedAnimTickOption)
|
||||||
|
{
|
||||||
|
UGCS_AbilityTask_CollisionTrace* MyTask = NewAbilityTask<UGCS_AbilityTask_CollisionTrace>(OwningAbility, TaskInstanceName);
|
||||||
|
MyTask->bAdjustAnimTickOption = bAdjustVisibilityBasedAnimTickOption;
|
||||||
|
return MyTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UGCS_AbilityTask_CollisionTrace::Activate()
|
||||||
|
{
|
||||||
|
Super::Activate();
|
||||||
|
|
||||||
|
if (bAdjustAnimTickOption && GetAvatarActor()->GetNetMode() == NM_DedicatedServer)
|
||||||
|
{
|
||||||
|
if (USkeletalMeshComponent* SkeletalMeshComponent = UGCS_CombatFunctionLibrary::GetMainCharacterMeshComponent(GetAvatarActor()))
|
||||||
|
{
|
||||||
|
if (SkeletalMeshComponent->VisibilityBasedAnimTickOption != EVisibilityBasedAnimTickOption::AlwaysTickPoseAndRefreshBones)
|
||||||
|
{
|
||||||
|
PrevAnimTickOption = SkeletalMeshComponent->VisibilityBasedAnimTickOption;
|
||||||
|
SkeletalMeshComponent->VisibilityBasedAnimTickOption = EVisibilityBasedAnimTickOption::AlwaysTickPoseAndRefreshBones;
|
||||||
|
bAdjustAnimTickOption = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (UGCS_TraceSystemComponent* TSC = UGCS_TraceSystemComponent::GetTraceSystemComponent(GetAvatarActor()))
|
||||||
|
{
|
||||||
|
TSC->OnTraceHitEvent.AddDynamic(this, &ThisClass::TraceHitCallback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UGCS_AbilityTask_CollisionTrace::OnDestroy(bool bInOwnerFinished)
|
||||||
|
{
|
||||||
|
if (bAdjustAnimTickOption && bAdjustedAnimTickOption)
|
||||||
|
{
|
||||||
|
if (USkeletalMeshComponent* SkeletalMeshComponent = UGCS_CombatFunctionLibrary::GetMainCharacterMeshComponent(GetAvatarActor()))
|
||||||
|
{
|
||||||
|
SkeletalMeshComponent->VisibilityBasedAnimTickOption = PrevAnimTickOption;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (UGCS_TraceSystemComponent* TSC = UGCS_TraceSystemComponent::GetTraceSystemComponent(GetAvatarActor()))
|
||||||
|
{
|
||||||
|
TSC->OnTraceHitEvent.RemoveDynamic(this, &ThisClass::TraceHitCallback);
|
||||||
|
for (auto& MeleeRequest : MeleeRequests)
|
||||||
|
{
|
||||||
|
TSC->StopTraces(MeleeRequest.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
MeleeRequests.Empty();
|
||||||
|
|
||||||
|
Super::OnDestroy(bInOwnerFinished);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UGCS_AbilityTask_CollisionTrace::TraceHitCallback(const FGCS_TraceHandle& TraceHandle, const FHitResult& HitResult)
|
||||||
|
{
|
||||||
|
if (ShouldBroadcastAbilityTaskDelegates() && !MeleeRequests.IsEmpty() && TraceHandle.IsValidHandle())
|
||||||
|
{
|
||||||
|
TObjectPtr<const UGCS_AttackRequest_Melee> Req = nullptr;
|
||||||
|
bool bFound = false;
|
||||||
|
for (auto& MeleeRequest : MeleeRequests)
|
||||||
|
{
|
||||||
|
if (!MeleeRequest.Value.Contains(TraceHandle))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Req = MeleeRequest.Key;
|
||||||
|
bFound = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (bFound)
|
||||||
|
{
|
||||||
|
OnTargetsFound.Broadcast(Req, TraceHandle, HitResult);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UGCS_AbilityTask_CollisionTrace::AddMeleeRequest(const UGCS_AttackRequest_Melee* Request, UObject* SourceObject)
|
||||||
|
{
|
||||||
|
if (IsValid(Request) && !MeleeRequests.Contains(Request))
|
||||||
|
{
|
||||||
|
const FGameplayTagContainer& TracesToControl = Request->TracesToControl;
|
||||||
|
if (UGCS_TraceSystemComponent* TSC = UGCS_TraceSystemComponent::GetTraceSystemComponent(GetAvatarActor()))
|
||||||
|
{
|
||||||
|
TArray<FGCS_TraceHandle> Handles = TSC->StartTraces(TracesToControl, SourceObject);
|
||||||
|
if (Handles.IsEmpty())
|
||||||
|
{
|
||||||
|
GCS_LOG(Warning, "Ability:(%s), No any trace started by melee request(%s) with source object(%s)", *GetNameSafe(Ability.Get()), *Request->GetPathName(), *GetNameSafe(SourceObject));
|
||||||
|
}
|
||||||
|
MeleeRequests.Emplace(Request, Handles);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UGCS_AbilityTask_CollisionTrace::RemoveMeleeRequest(const UGCS_AttackRequest_Melee* Request)
|
||||||
|
{
|
||||||
|
if (IsValid(Request) && MeleeRequests.Contains(Request))
|
||||||
|
{
|
||||||
|
if (UGCS_TraceSystemComponent* TSC = UGCS_TraceSystemComponent::GetTraceSystemComponent(GetAvatarActor()))
|
||||||
|
{
|
||||||
|
TSC->StopTraces(MeleeRequests[Request]);
|
||||||
|
}
|
||||||
|
MeleeRequests.Remove(Request);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||||
|
|
||||||
|
|
||||||
|
#include "Bullet/GCS_BulletContainer.h"
|
||||||
|
|
||||||
|
void FGCS_BulletContainer::PreReplicatedRemove(const TArrayView<int32> RemovedIndices, int32 FinalSize)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void FGCS_BulletContainer::PostReplicatedAdd(const TArrayView<int32> AddedIndices, int32 FinalSize)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void FGCS_BulletContainer::PostReplicatedChange(const TArrayView<int32> ChangedIndices, int32 FinalSize)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
int32 FGCS_BulletContainer::IndexOfById(const FGuid& Id) const
|
||||||
|
{
|
||||||
|
if (!Id.IsValid())
|
||||||
|
{
|
||||||
|
return INDEX_NONE;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
@@ -0,0 +1,441 @@
|
|||||||
|
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||||
|
|
||||||
|
|
||||||
|
#include "Bullet/GCS_BulletInstance.h"
|
||||||
|
#include "AbilitySystemBlueprintLibrary.h"
|
||||||
|
#include "AbilitySystemComponent.h"
|
||||||
|
#include "GCS_GameplayTags.h"
|
||||||
|
#include "GCS_LogChannels.h"
|
||||||
|
#include "Engine/World.h"
|
||||||
|
#include "GameFramework/Pawn.h"
|
||||||
|
#include "Bullet/GCS_BulletStructLibrary.h"
|
||||||
|
#include "Bullet/GCS_BulletSubsystem.h"
|
||||||
|
#include "CombatFlow/GCS_AttackDefinition.h"
|
||||||
|
#include "GameFramework/ProjectileMovementComponent.h"
|
||||||
|
#include "NiagaraSystem.h"
|
||||||
|
#include "Net/UnrealNetwork.h"
|
||||||
|
#include "Utilities/GGA_GameplayEffectContainerFunctionLibrary.h"
|
||||||
|
#include "Utilities/GGA_GameplayEffectFunctionLibrary.h"
|
||||||
|
#include "Utility/GCS_CombatFunctionLibrary.h"
|
||||||
|
|
||||||
|
// Sets default values
|
||||||
|
AGCS_BulletInstance::AGCS_BulletInstance()
|
||||||
|
{
|
||||||
|
// Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it.
|
||||||
|
PrimaryActorTick.bCanEverTick = true;
|
||||||
|
PrimaryActorTick.bAllowTickBatching = true;
|
||||||
|
|
||||||
|
bReplicates = true;
|
||||||
|
|
||||||
|
ProjectileMovement = CreateDefaultSubobject<UProjectileMovementComponent>("ProjectileMovement");
|
||||||
|
}
|
||||||
|
|
||||||
|
void AGCS_BulletInstance::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
|
||||||
|
{
|
||||||
|
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
|
||||||
|
|
||||||
|
FDoRepLifetimeParams SharedParams;
|
||||||
|
SharedParams.bIsPushBased = true;
|
||||||
|
|
||||||
|
DOREPLIFETIME_WITH_PARAMS_FAST(ThisClass, DefinitionHandle, SharedParams);
|
||||||
|
DOREPLIFETIME_WITH_PARAMS_FAST(ThisClass, BulletId, SharedParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
UProjectileMovementComponent* AGCS_BulletInstance::GetProjectileMovementComponent() const
|
||||||
|
{
|
||||||
|
return ProjectileMovement;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AGCS_BulletInstance::SetDefinitionHandle(FDataTableRowHandle NewHandle)
|
||||||
|
{
|
||||||
|
if ((GetOwner() != nullptr && GetOwner()->HasAuthority()) || HasAuthority())
|
||||||
|
{
|
||||||
|
MARK_PROPERTY_DIRTY_FROM_NAME(ThisClass, DefinitionHandle, this);
|
||||||
|
DefinitionHandle = NewHandle;
|
||||||
|
ForceNetUpdate();
|
||||||
|
OnRep_BulletDefinition();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AGCS_BulletInstance::SetBulletId(const FGuid& NewId)
|
||||||
|
{
|
||||||
|
if (NewId.IsValid())
|
||||||
|
{
|
||||||
|
if ((GetOwner() != nullptr && GetOwner()->HasAuthority()) || HasAuthority())
|
||||||
|
{
|
||||||
|
MARK_PROPERTY_DIRTY_FROM_NAME(ThisClass, DefinitionHandle, this);
|
||||||
|
}
|
||||||
|
BulletId = NewId;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
UE_LOG(LogGCS, Error, TEXT("Attempt to set invalid guid for bullet(%s)"), *GetName())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FGuid AGCS_BulletInstance::GetBulletId() const
|
||||||
|
{
|
||||||
|
return BulletId;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AGCS_BulletInstance::SetParentBulletId_Implementation(FGuid NewParentId)
|
||||||
|
{
|
||||||
|
ParentBulletId = NewParentId;
|
||||||
|
}
|
||||||
|
|
||||||
|
FGuid AGCS_BulletInstance::GetParentBulletId() const
|
||||||
|
{
|
||||||
|
return ParentBulletId;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AGCS_BulletInstance::SetHitResult(const FHitResult& NewHitResult)
|
||||||
|
{
|
||||||
|
LastHitResult = NewHitResult;
|
||||||
|
// HitActors.Push(NewHitResult.GetActor());
|
||||||
|
}
|
||||||
|
|
||||||
|
const FHitResult& AGCS_BulletInstance::GetHitResult() const
|
||||||
|
{
|
||||||
|
return LastHitResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AGCS_BulletInstance::HasGameplayAuthority() const
|
||||||
|
{
|
||||||
|
return HasAuthority() && !bIsLocalPredicting;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AGCS_BulletInstance::LaunchBullet_Implementation()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AGCS_BulletInstance::GetEffectSpecHandle_Implementation(FGameplayEffectSpecHandle& OutHandle)
|
||||||
|
{
|
||||||
|
OutHandle = EffectSpecHandle;
|
||||||
|
return EffectSpecHandle.IsValid();
|
||||||
|
}
|
||||||
|
|
||||||
|
FGGA_GameplayEffectContainer AGCS_BulletInstance::GetEffectContainer_Implementation() const
|
||||||
|
{
|
||||||
|
if (FGCS_AttackDefinition* AtkDef = Definition.AttackDefinition.GetRow<FGCS_AttackDefinition>(TEXT("AGCS_BulletInstance::GetEffectContainer")))
|
||||||
|
{
|
||||||
|
return AtkDef->TargetEffectContainer;
|
||||||
|
}
|
||||||
|
return FGGA_GameplayEffectContainer();
|
||||||
|
}
|
||||||
|
|
||||||
|
int32 AGCS_BulletInstance::GetEffectContainerLevelOverride_Implementation() const
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AGCS_BulletInstance::SetEffectContainerSpec_Implementation(const FGGA_GameplayEffectContainerSpec& InEffectContainerSpec)
|
||||||
|
{
|
||||||
|
EffectContainerSpec = InEffectContainerSpec;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AGCS_BulletInstance::SetEffectSpec_Implementation(FGameplayEffectSpecHandle& InEffectSpec)
|
||||||
|
{
|
||||||
|
EffectSpecHandle = InEffectSpec;
|
||||||
|
}
|
||||||
|
|
||||||
|
UShapeComponent* AGCS_BulletInstance::GetBulletShape_Implementation() const
|
||||||
|
{
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
FGGA_GameplayEffectContainerSpec AGCS_BulletInstance::GetEffectContainerSpec_Implementation() const
|
||||||
|
{
|
||||||
|
return EffectContainerSpec;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AGCS_BulletInstance::PostNetInit()
|
||||||
|
{
|
||||||
|
Super::PostNetInit();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AGCS_BulletInstance::PostNetReceive()
|
||||||
|
{
|
||||||
|
Super::PostNetReceive();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AGCS_BulletInstance::FoundLocalPredictedBullet_Implementation(AGCS_BulletInstance* PredictedBullet)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
TSubclassOf<UGameplayEffect> AGCS_BulletInstance::GetEffectClass_Implementation() const
|
||||||
|
{
|
||||||
|
if (FGCS_AttackDefinition* AtkDef = Definition.AttackDefinition.GetRow<FGCS_AttackDefinition>(TEXT("AGCS_BulletInstance::GetEffectClass")))
|
||||||
|
{
|
||||||
|
if (!AtkDef->TargetEffectClass.IsNull())
|
||||||
|
{
|
||||||
|
return AtkDef->TargetEffectClass.LoadSynchronous();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32 AGCS_BulletInstance::GetEffectLevel_Implementation() const
|
||||||
|
{
|
||||||
|
if (FGCS_AttackDefinition* AtkDef = Definition.AttackDefinition.GetRow<FGCS_AttackDefinition>(TEXT("AGCS_BulletInstance::GetEffectClass")))
|
||||||
|
{
|
||||||
|
return AtkDef->TargetEffectClassLevel;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Called when the game starts or when spawned
|
||||||
|
void AGCS_BulletInstance::BeginPlay()
|
||||||
|
{
|
||||||
|
Super::BeginPlay();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AGCS_BulletInstance::EndPlay(const EEndPlayReason::Type EndPlayReason)
|
||||||
|
{
|
||||||
|
OnBulletEndPlay();
|
||||||
|
Super::EndPlay(EndPlayReason);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AGCS_BulletInstance::OnBulletBeginPlay_Implementation()
|
||||||
|
{
|
||||||
|
SetActorHiddenInGame(false);
|
||||||
|
SetActorTickEnabled(true);
|
||||||
|
SetActorEnableCollision(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AGCS_BulletInstance::OnBulletEndPlay_Implementation()
|
||||||
|
{
|
||||||
|
SetActorHiddenInGame(true);
|
||||||
|
SetActorTickEnabled(false);
|
||||||
|
SetActorEnableCollision(false);
|
||||||
|
bIsLocalPredicting = false;
|
||||||
|
Definition = FGCS_BulletDefinition();
|
||||||
|
Request = nullptr;
|
||||||
|
EffectSpecHandle = FGameplayEffectSpecHandle();
|
||||||
|
EffectContainerSpec = FGGA_GameplayEffectContainerSpec();
|
||||||
|
SetActorTransform(FTransform::Identity);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AGCS_BulletInstance::SetupInitialLocationAndRotation()
|
||||||
|
{
|
||||||
|
InitialActorLocation = GetActorLocation();
|
||||||
|
InitialActorRotation = GetActorRotation();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AGCS_BulletInstance::RefreshTravelStates()
|
||||||
|
{
|
||||||
|
if (HasAuthority() && GetProjectileMovementComponent() && GetProjectileMovementComponent()->IsActive())
|
||||||
|
{
|
||||||
|
// update Traveled distance and gravity scale.
|
||||||
|
TraveledDistance = FVector::Dist2D(GetActorLocation(), InitialActorLocation);
|
||||||
|
float DesiredGravityScale = TraveledDistance <= Definition.AttenuationRange ? Definition.GravityScaleInRange : Definition.GravityScaleOutRage;
|
||||||
|
if (GetProjectileMovementComponent()->ProjectileGravityScale != DesiredGravityScale)
|
||||||
|
{
|
||||||
|
GetProjectileMovementComponent()->ProjectileGravityScale = DesiredGravityScale;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// bool AGCS_BulletInstance::AlreadyHit(const FHitResult& InHitResult) const
|
||||||
|
// {
|
||||||
|
// for (int i = 0; i < HitActors.Num(); ++i)
|
||||||
|
// {
|
||||||
|
// if (HitActors[i] == InHitResult.GetActor())
|
||||||
|
// {
|
||||||
|
// return true;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// return false;
|
||||||
|
// }
|
||||||
|
|
||||||
|
bool AGCS_BulletInstance::ShouldPenetrateHitResult(const FHitResult& InHitResult) const
|
||||||
|
{
|
||||||
|
if (InHitResult.GetActor() != nullptr)
|
||||||
|
{
|
||||||
|
if (Definition.bPenetrateCharacter && InHitResult.GetActor()->GetClass()->IsChildOf(APawn::StaticClass()))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return Definition.bPenetrateMap;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AGCS_BulletInstance::ShouldGenerateBullet_Implementation()
|
||||||
|
{
|
||||||
|
if (Definition.HitBulletDefinition.IsNull() || Definition.HitBulletDefinition == DefinitionHandle)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (Definition.LaunchCondition == GCS_BulletLaunch::Always || Definition.LaunchCondition == FGameplayTag::EmptyTag)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (Definition.LaunchCondition == GCS_BulletLaunch::DidNotHitPawn)
|
||||||
|
{
|
||||||
|
if (LastHitResult.GetActor() && !LastHitResult.GetActor()->GetClass()->IsChildOf(APawn::StaticClass()))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (Definition.LaunchCondition == GCS_BulletLaunch::HitPawn)
|
||||||
|
{
|
||||||
|
if (LastHitResult.GetActor() && LastHitResult.GetActor()->GetClass()->IsChildOf(APawn::StaticClass()))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void AGCS_BulletInstance::HandleBulletHitChains_Implementation()
|
||||||
|
{
|
||||||
|
if (!HasGameplayAuthority() || !ShouldGenerateBullet())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
UAbilitySystemComponent* AbilitySystem = UAbilitySystemBlueprintLibrary::GetAbilitySystemComponent(GetOwner());
|
||||||
|
if (AbilitySystem == nullptr)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
FGCS_BulletDefinition* SubBullet = Definition.HitBulletDefinition.GetRow<FGCS_BulletDefinition>(TEXT("HandleBulletHitChains"));
|
||||||
|
if (SubBullet == nullptr)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Setup bullet gameplay effect instance and launch.
|
||||||
|
|
||||||
|
FGCS_BulletSpawnParameters SpawnParams;
|
||||||
|
SpawnParams.Owner = GetOwner();
|
||||||
|
SpawnParams.DefinitionHandle = Definition.HitBulletDefinition;
|
||||||
|
|
||||||
|
//TODO Various different launch location.
|
||||||
|
FTransform SpawnTransform = FTransform::Identity;
|
||||||
|
SpawnTransform.SetLocation(GetHitResult().Location);
|
||||||
|
SpawnTransform.SetRotation(GetActorRotation().Quaternion());
|
||||||
|
SpawnTransform.SetScale3D(FVector::One());
|
||||||
|
SpawnParams.SpawnTransform = SpawnTransform;
|
||||||
|
SpawnParams.Request = Request;
|
||||||
|
SpawnParams.ParentId = BulletId;
|
||||||
|
|
||||||
|
FGameplayEventData EventData;
|
||||||
|
EventData.Instigator = GetOwner();
|
||||||
|
EventData.EventMagnitude = GetEffectContainerLevelOverride_Implementation();
|
||||||
|
|
||||||
|
TArray<AGCS_BulletInstance*> BulletInstances = GetWorld()->GetSubsystem<UGCS_BulletSubsystem>()->SpawnBullets(SpawnParams);
|
||||||
|
|
||||||
|
//Setup each bullets
|
||||||
|
for (AGCS_BulletInstance* BulletInstance : BulletInstances)
|
||||||
|
{
|
||||||
|
// Setup normal gameplay effects.
|
||||||
|
TSubclassOf<UGameplayEffect> GE = Execute_GetEffectClass(BulletInstance);
|
||||||
|
int32 GELevel = Execute_GetEffectLevel(BulletInstance);
|
||||||
|
if (GE != nullptr)
|
||||||
|
{
|
||||||
|
FGameplayEffectSpecHandle GESpec = AbilitySystem->MakeOutgoingSpec(GE, GELevel, AbilitySystem->MakeEffectContext());
|
||||||
|
UGCS_CombatFunctionLibrary::AddAttackHandleToGameplayEffectSpec(GESpec, SubBullet->AttackDefinition);
|
||||||
|
FGameplayEffectContextHandle ContextHandle = UAbilitySystemBlueprintLibrary::GetEffectContext(GESpec);
|
||||||
|
UGGA_GameplayEffectFunctionLibrary::SetEffectCauser(ContextHandle, BulletInstance);
|
||||||
|
Execute_SetEffectSpec(BulletInstance, GESpec);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup gameplay effects container.
|
||||||
|
const FGGA_GameplayEffectContainer& GEContainer = Execute_GetEffectContainer(BulletInstance);
|
||||||
|
|
||||||
|
if (UGGA_GameplayEffectContainerFunctionLibrary::IsValidContainer(GEContainer))
|
||||||
|
{
|
||||||
|
FGGA_GameplayEffectContainerSpec GEContainerSpec = UGGA_GameplayEffectContainerFunctionLibrary::MakeEffectContainerSpec(
|
||||||
|
GEContainer, EventData);
|
||||||
|
|
||||||
|
// Setup each gameplay effect instance.
|
||||||
|
for (const FGameplayEffectSpecHandle& GESpec : GEContainerSpec.TargetGameplayEffectSpecs)
|
||||||
|
{
|
||||||
|
UGCS_CombatFunctionLibrary::AddAttackHandleToGameplayEffectSpec(GESpec, SubBullet->AttackDefinition);
|
||||||
|
FGameplayEffectContextHandle ContextHandle = UAbilitySystemBlueprintLibrary::GetEffectContext(GESpec);
|
||||||
|
UGGA_GameplayEffectFunctionLibrary::SetEffectCauser(ContextHandle, BulletInstance);
|
||||||
|
}
|
||||||
|
Execute_SetEffectContainerSpec(BulletInstance, GEContainerSpec);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Launch each bullets
|
||||||
|
for (AGCS_BulletInstance* BulletInstance : BulletInstances)
|
||||||
|
{
|
||||||
|
BulletInstance->LaunchBullet();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AGCS_BulletInstance::ApplyGameplayEffects_Implementation(FHitResult HitResult)
|
||||||
|
{
|
||||||
|
if (!HasGameplayAuthority())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (UAbilitySystemComponent* TargetAsc = UAbilitySystemBlueprintLibrary::GetAbilitySystemComponent(HitResult.GetActor()))
|
||||||
|
{
|
||||||
|
FGameplayEffectSpecHandle SpecHandle;
|
||||||
|
if (Execute_GetEffectSpecHandle(this, SpecHandle))
|
||||||
|
{
|
||||||
|
FGameplayEffectContextHandle ContextHandle = UAbilitySystemBlueprintLibrary::GetEffectContext(SpecHandle);
|
||||||
|
ContextHandle.AddHitResult(HitResult, true);
|
||||||
|
ContextHandle.GetInstigatorAbilitySystemComponent()->BP_ApplyGameplayEffectSpecToTarget(SpecHandle, TargetAsc);
|
||||||
|
}
|
||||||
|
|
||||||
|
FGGA_GameplayEffectContainerSpec ContainerSpec = Execute_GetEffectContainerSpec(this);
|
||||||
|
if (ContainerSpec.HasValidEffects())
|
||||||
|
{
|
||||||
|
FGameplayAbilityTargetData_SingleTargetHit* NewData = new FGameplayAbilityTargetData_SingleTargetHit(HitResult);
|
||||||
|
ContainerSpec.TargetData.Add(NewData);
|
||||||
|
for (const FGameplayEffectSpecHandle& TargetGameplayEffectSpec : ContainerSpec.TargetGameplayEffectSpecs)
|
||||||
|
{
|
||||||
|
TargetGameplayEffectSpec.Data->GetContext().AddHitResult(HitResult, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
UGGA_GameplayEffectContainerFunctionLibrary::ApplyExternalEffectContainerSpec(ContainerSpec);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AGCS_BulletInstance::OnRep_BulletId(FGuid Prev)
|
||||||
|
{
|
||||||
|
if (UGCS_BulletSubsystem* BulletSubsystem = GetWorld()->GetSubsystem<UGCS_BulletSubsystem>())
|
||||||
|
{
|
||||||
|
if (!bIsLocalPredicting && BulletSubsystem->BulletInstances.Contains(BulletId))
|
||||||
|
{
|
||||||
|
UE_LOG(LogGCS, Warning, TEXT("Found local predicted bullet(%s)"), *BulletSubsystem->BulletInstances[BulletId]->GetName());
|
||||||
|
FoundLocalPredictedBullet(BulletSubsystem->BulletInstances[BulletId]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AGCS_BulletInstance::OnRep_BulletDefinition()
|
||||||
|
{
|
||||||
|
if (DefinitionHandle.IsNull())
|
||||||
|
{
|
||||||
|
Definition = FGCS_BulletDefinition();
|
||||||
|
OnBulletEndPlay();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (const FGCS_BulletDefinition* NewDefinition = DefinitionHandle.GetRow<FGCS_BulletDefinition>(TEXT("RefreshDefinition")))
|
||||||
|
{
|
||||||
|
Definition = *NewDefinition;
|
||||||
|
OnBulletBeginPlay();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
UE_LOG(LogGCS, Verbose, TEXT("Failed to load definition(%s) for bullet(%s)"), *DefinitionHandle.ToDebugString(), *GetPathName(this));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called every frame
|
||||||
|
void AGCS_BulletInstance::Tick(float DeltaTime)
|
||||||
|
{
|
||||||
|
Super::Tick(DeltaTime);
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||||
|
|
||||||
|
|
||||||
|
#include "Bullet/GCS_BulletStructLibrary.h"
|
||||||
|
#include "CombatFlow/GCS_AttackRequest.h"
|
||||||
|
|
||||||
|
FString FGCS_BulletSpawnParameters::ToDebugString() const
|
||||||
|
{
|
||||||
|
return FString::Format(TEXT("Owner:{0} DefinitionHandle:{1}"), {Owner ? Owner->GetName() : "Null", DefinitionHandle.ToDebugString()});
|
||||||
|
}
|
||||||
@@ -0,0 +1,197 @@
|
|||||||
|
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||||
|
|
||||||
|
|
||||||
|
#include "Bullet/GCS_BulletSubsystem.h"
|
||||||
|
|
||||||
|
#include "GCS_LogChannels.h"
|
||||||
|
#include "Bullet/GCS_BulletInstance.h"
|
||||||
|
#include "Engine/World.h"
|
||||||
|
#include "Kismet/KismetMathLibrary.h"
|
||||||
|
|
||||||
|
UGCS_BulletSubsystem* UGCS_BulletSubsystem::Get(const UWorld* World)
|
||||||
|
{
|
||||||
|
if (IsValid(World))
|
||||||
|
{
|
||||||
|
return World->GetSubsystem<UGCS_BulletSubsystem>();
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
TArray<AGCS_BulletInstance*> UGCS_BulletSubsystem::SpawnBullets(const FGCS_BulletSpawnParameters& SpawnParameters)
|
||||||
|
{
|
||||||
|
TArray<AGCS_BulletInstance*> RetInstances{};
|
||||||
|
|
||||||
|
FGCS_BulletDefinition Definition;
|
||||||
|
if (SpawnParameters.DefinitionHandle.IsNull() || !LoadBulletDefinition(SpawnParameters.DefinitionHandle, Definition))
|
||||||
|
{
|
||||||
|
return RetInstances;
|
||||||
|
}
|
||||||
|
|
||||||
|
RetInstances = GetOrCreateBulletInstances(SpawnParameters, Definition);
|
||||||
|
|
||||||
|
for (int i = 0; i < RetInstances.Num(); ++i)
|
||||||
|
{
|
||||||
|
AGCS_BulletInstance* Instance = RetInstances[i];
|
||||||
|
|
||||||
|
Instance->bServerInitiated = GetWorld()->GetNetMode() < NM_Client;
|
||||||
|
Instance->bIsLocalPredicting = SpawnParameters.bIsLocalPredicting;
|
||||||
|
|
||||||
|
if (SpawnParameters.OverrideBulletIds.IsValidIndex(i))
|
||||||
|
{
|
||||||
|
Instance->SetBulletId(SpawnParameters.OverrideBulletIds[i]);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Instance->SetBulletId(FGuid::NewGuid());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (SpawnParameters.ParentId.IsValid() && BulletInstances.Contains(SpawnParameters.ParentId))
|
||||||
|
{
|
||||||
|
Instance->SetParentBulletId(SpawnParameters.ParentId);
|
||||||
|
// if (AGCS_BulletInstance* ParentBulletInstance = BulletInstances[SpawnParameters.ParentId])
|
||||||
|
// {
|
||||||
|
// if (ParentBulletInstance->Definition.bUseSharedHitList)
|
||||||
|
// {
|
||||||
|
// Instance->HitActors = ParentBulletInstance->HitActors;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (SpawnParameters.Request)
|
||||||
|
{
|
||||||
|
Instance->Request = SpawnParameters.Request;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (SpawnParameters.Owner)
|
||||||
|
{
|
||||||
|
Instance->SetOwner(SpawnParameters.Owner);
|
||||||
|
}
|
||||||
|
Instance->SetDefinitionHandle(SpawnParameters.DefinitionHandle);
|
||||||
|
BulletInstances.Emplace(Instance->BulletId, Instance);
|
||||||
|
}
|
||||||
|
for (int i = 0; i < RetInstances.Num(); ++i)
|
||||||
|
{
|
||||||
|
FRotator RotationAdjustment(Definition.LaunchElevationAngle, Definition.LaunchAngle + Definition.LaunchAngleInterval * i, 0);
|
||||||
|
|
||||||
|
// Start with the spawn transform
|
||||||
|
// FTransform ModifiedTransform = SpawnParameters.SpawnTransform;
|
||||||
|
|
||||||
|
// Compose the rotations: apply adjustment relative to original rotation
|
||||||
|
// FRotator FinalRotation = UKismetMathLibrary::ComposeRotators(ModifiedTransform.Rotator(), RotationAdjustment);
|
||||||
|
// ModifiedTransform.SetRotation(FinalRotation.Quaternion());
|
||||||
|
|
||||||
|
RetInstances[i]->SetActorTransform(FTransform(RotationAdjustment, FVector::ZeroVector) * SpawnParameters.SpawnTransform, false, nullptr, ETeleportType::ResetPhysics);
|
||||||
|
}
|
||||||
|
|
||||||
|
//batch beginplay.
|
||||||
|
for (AGCS_BulletInstance* Instance : RetInstances)
|
||||||
|
{
|
||||||
|
Instance->OnBulletBeginPlay();
|
||||||
|
}
|
||||||
|
return RetInstances;
|
||||||
|
}
|
||||||
|
|
||||||
|
TArray<FGuid> UGCS_BulletSubsystem::GetIdsFromBullets(TArray<AGCS_BulletInstance*> Instances)
|
||||||
|
{
|
||||||
|
TArray<FGuid> Ids;
|
||||||
|
for (AGCS_BulletInstance* BulletInstance : Instances)
|
||||||
|
{
|
||||||
|
Ids.Add(BulletInstance->BulletId);
|
||||||
|
}
|
||||||
|
return Ids;
|
||||||
|
}
|
||||||
|
|
||||||
|
TArray<AGCS_BulletInstance*> UGCS_BulletSubsystem::GetOrCreateBulletInstances(const FGCS_BulletSpawnParameters& SpawnParameters, const FGCS_BulletDefinition& Definition)
|
||||||
|
{
|
||||||
|
TArray<AGCS_BulletInstance*> OutInstances;
|
||||||
|
|
||||||
|
static int32 MaxAllowedLoops = 30;
|
||||||
|
|
||||||
|
int32 Counter = 0;
|
||||||
|
while (OutInstances.Num() < Definition.BulletCount)
|
||||||
|
{
|
||||||
|
if (AGCS_BulletInstance* Instance = TakeBulletFromPool(Definition.BulletActorClass.LoadSynchronous()))
|
||||||
|
{
|
||||||
|
OutInstances.Add(Instance);
|
||||||
|
}
|
||||||
|
else if (AGCS_BulletInstance* Instance2 = CreateBulletInstance(SpawnParameters, Definition))
|
||||||
|
{
|
||||||
|
OutInstances.Add(Instance2);
|
||||||
|
}
|
||||||
|
Counter++;
|
||||||
|
if (Counter >= MaxAllowedLoops)
|
||||||
|
{
|
||||||
|
UE_LOG(LogGCS, Warning, TEXT("BulletSubsystem reach max allowed bullet spawn loops(%d)."), MaxAllowedLoops);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return OutInstances;
|
||||||
|
}
|
||||||
|
|
||||||
|
AGCS_BulletInstance* UGCS_BulletSubsystem::TakeBulletFromPool(TSubclassOf<AGCS_BulletInstance> BulletClass)
|
||||||
|
{
|
||||||
|
int32 Found = INDEX_NONE;
|
||||||
|
for (int i = 0; i < BulletPools.Num(); i++)
|
||||||
|
{
|
||||||
|
if (BulletPools[i].GetClass() == BulletClass)
|
||||||
|
{
|
||||||
|
Found = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (Found != INDEX_NONE)
|
||||||
|
{
|
||||||
|
AGCS_BulletInstance* FoundInstance = BulletPools[Found];
|
||||||
|
UE_LOG(LogGCS, Verbose, TEXT("Taking bullet(%s) from pool."), *BulletClass->GetName());
|
||||||
|
BulletPools.RemoveAtSwap(Found);
|
||||||
|
return FoundInstance;
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UGCS_BulletSubsystem::DestroyBullet(FGuid BulletId)
|
||||||
|
{
|
||||||
|
if (BulletInstances.Contains(BulletId))
|
||||||
|
{
|
||||||
|
AGCS_BulletInstance* BulletToRemove = BulletInstances[BulletId];
|
||||||
|
BulletToRemove->SetDefinitionHandle(FDataTableRowHandle());
|
||||||
|
BulletToRemove->SetOwner(nullptr);
|
||||||
|
BulletInstances.Remove(BulletId);
|
||||||
|
BulletPools.Add(BulletToRemove);
|
||||||
|
UE_LOG(LogGCS, Verbose, TEXT("Return bullet(%s) back to pool."), *BulletToRemove->GetClass()->GetName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AGCS_BulletInstance* UGCS_BulletSubsystem::CreateBulletInstance(const FGCS_BulletSpawnParameters& SpawnParameters, const FGCS_BulletDefinition& Definition)
|
||||||
|
{
|
||||||
|
if (Definition.BulletActorClass.IsNull())
|
||||||
|
{
|
||||||
|
UE_LOG(LogGCS, Error, TEXT("Failed to create bullet instance for definition(%s),missing BulletActorClass!!!"), *SpawnParameters.DefinitionHandle.ToDebugString());
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
UClass* BulletClass = Definition.BulletActorClass.LoadSynchronous();
|
||||||
|
check(BulletClass);
|
||||||
|
FActorSpawnParameters ActorSpawnParameters;
|
||||||
|
ActorSpawnParameters.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
|
||||||
|
|
||||||
|
AGCS_BulletInstance* NewInstance = GetWorld()->SpawnActor<AGCS_BulletInstance>(BulletClass, FTransform::Identity, ActorSpawnParameters);
|
||||||
|
if (NewInstance)
|
||||||
|
{
|
||||||
|
UE_LOG(LogGCS, Verbose, TEXT("Create new bullet instance for class(%s)"), *BulletClass->GetName());
|
||||||
|
return NewInstance;
|
||||||
|
}
|
||||||
|
|
||||||
|
UE_LOG(LogGCS, Error, TEXT("Failed to create new bullet instance for class(%s)"), *BulletClass->GetName());
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UGCS_BulletSubsystem::LoadBulletDefinition(const FDataTableRowHandle& Handle, FGCS_BulletDefinition& OutDefinition)
|
||||||
|
{
|
||||||
|
if (FGCS_BulletDefinition* Definition = Handle.GetRow<FGCS_BulletDefinition>(TEXT("LoadBulletDefinition")))
|
||||||
|
{
|
||||||
|
OutDefinition = *Definition;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
@@ -0,0 +1,126 @@
|
|||||||
|
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||||
|
|
||||||
|
|
||||||
|
#include "Bullet/GCS_BulletSystemComponent.h"
|
||||||
|
|
||||||
|
#include "GCS_LogChannels.h"
|
||||||
|
#include "Bullet/GCS_BulletInstance.h"
|
||||||
|
#include "Bullet/GCS_BulletSubsystem.h"
|
||||||
|
|
||||||
|
|
||||||
|
// Sets default values for this component's properties
|
||||||
|
UGCS_BulletSystemComponent::UGCS_BulletSystemComponent()
|
||||||
|
{
|
||||||
|
// Set this component to be initialized when the game starts, and to be ticked every frame. You can turn these features
|
||||||
|
// off to improve performance if you don't need them.
|
||||||
|
PrimaryComponentTick.bCanEverTick = false;
|
||||||
|
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Called when the game starts
|
||||||
|
void UGCS_BulletSystemComponent::BeginPlay()
|
||||||
|
{
|
||||||
|
Super::BeginPlay();
|
||||||
|
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
|
UGCS_BulletSystemComponent* UGCS_BulletSystemComponent::GetBulletSystemComponent(const AActor* Actor)
|
||||||
|
{
|
||||||
|
return IsValid(Actor) ? Actor->FindComponentByClass<UGCS_BulletSystemComponent>() : nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UGCS_BulletSystemComponent::FindBulletSystemComponent(const AActor* Actor, UGCS_BulletSystemComponent*& Component)
|
||||||
|
{
|
||||||
|
Component = GetBulletSystemComponent(Actor);
|
||||||
|
return Component != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UGCS_BulletSystemComponent::SpawnBullet(const FGCS_BulletSpawnParameters& SpawnParameters)
|
||||||
|
{
|
||||||
|
//SpawnBulletInternal(SpawnParameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TArray<AGCS_BulletInstance*> UGCS_BulletSystemComponent::SpawnBulletInternal(const FGCS_BulletSpawnParameters& SpawnParameters)
|
||||||
|
// {
|
||||||
|
// TArray<AGCS_BulletInstance*> RetInstances{};
|
||||||
|
//
|
||||||
|
// if (GetOwner()->GetLocalRole() < ROLE_AutonomousProxy)
|
||||||
|
// {
|
||||||
|
// GCS_CLOG(Warning, "No authority to spawn bullet!")
|
||||||
|
// return RetInstances;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// UGCS_BulletSubsystem* Subsystem = UGCS_BulletSubsystem::Get(GetWorld());
|
||||||
|
// if (Subsystem == nullptr)
|
||||||
|
// {
|
||||||
|
// return RetInstances;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// FGCS_BulletDefinition Definition;
|
||||||
|
// if (SpawnParameters.DefinitionHandle.IsNull() || !Subsystem->LoadBulletDefinition(SpawnParameters.DefinitionHandle, Definition))
|
||||||
|
// {
|
||||||
|
// return RetInstances;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// RetInstances = Subsystem->GetOrCreateBulletInstances(SpawnParameters, Definition);
|
||||||
|
//
|
||||||
|
// for (int i = 0; i < RetInstances.Num(); ++i)
|
||||||
|
// {
|
||||||
|
// AGCS_BulletInstance* Instance = RetInstances[i];
|
||||||
|
//
|
||||||
|
// Instance->bServerInitiated = GetWorld()->GetNetMode() < NM_Client;
|
||||||
|
// Instance->bIsLocalPredicting = SpawnParameters.bIsLocalPredicting;
|
||||||
|
//
|
||||||
|
// if (SpawnParameters.OverrideBulletIds.IsValidIndex(i))
|
||||||
|
// {
|
||||||
|
// Instance->SetBulletId(SpawnParameters.OverrideBulletIds[0]);
|
||||||
|
// }
|
||||||
|
// else
|
||||||
|
// {
|
||||||
|
// Instance->SetBulletId(FGuid::NewGuid());
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// if (SpawnParameters.ParentId.IsValid() && BulletInstances.Contains(SpawnParameters.ParentId))
|
||||||
|
// {
|
||||||
|
// Instance->SetParentBulletId(SpawnParameters.ParentId);
|
||||||
|
// // if (AGCS_BulletInstance* ParentBulletInstance = BulletInstances[SpawnParameters.ParentId])
|
||||||
|
// // {
|
||||||
|
// // if (ParentBulletInstance->Definition.bUseSharedHitList)
|
||||||
|
// // {
|
||||||
|
// // Instance->HitActors = ParentBulletInstance->HitActors;
|
||||||
|
// // }
|
||||||
|
// // }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// if (SpawnParameters.Request)
|
||||||
|
// {
|
||||||
|
// Instance->Request = SpawnParameters.Request;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// if (SpawnParameters.Owner)
|
||||||
|
// {
|
||||||
|
// Instance->SetOwner(SpawnParameters.Owner);
|
||||||
|
// }
|
||||||
|
// Instance->SetDefinitionHandle(SpawnParameters.DefinitionHandle);
|
||||||
|
// BulletInstances.Emplace(Instance->BulletId, Instance);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// FRotator OriginalRotation = SpawnParameters.SpawnTransform.Rotator();
|
||||||
|
// for (int i = 0; i < RetInstances.Num(); ++i)
|
||||||
|
// {
|
||||||
|
// FTransform ModifiedTransform = SpawnParameters.SpawnTransform;
|
||||||
|
// FRotator RotationYawOffset(Definition.LaunchElevationAngle, Definition.LaunchAngle + Definition.LaunchAngleInterval * i, 0);
|
||||||
|
// ModifiedTransform.SetRotation(UKismetMathLibrary::ComposeRotators(OriginalRotation, RotationYawOffset).Quaternion());
|
||||||
|
// RetInstances[i]->SetActorTransform(ModifiedTransform, false, nullptr, ETeleportType::ResetPhysics);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// //batch beginplay.
|
||||||
|
// for (AGCS_BulletInstance* Instance : RetInstances)
|
||||||
|
// {
|
||||||
|
// Instance->OnBulletBeginPlay();
|
||||||
|
// }
|
||||||
|
// return RetInstances;
|
||||||
|
// }
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||||
|
|
||||||
|
|
||||||
|
#include "Bullet/GCS_SphereBulletInstance.h"
|
||||||
|
|
||||||
|
#include "Components/SphereComponent.h"
|
||||||
|
|
||||||
|
|
||||||
|
// Sets default values
|
||||||
|
AGCS_SphereBulletInstance::AGCS_SphereBulletInstance()
|
||||||
|
{
|
||||||
|
// Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it.
|
||||||
|
PrimaryActorTick.bCanEverTick = true;
|
||||||
|
|
||||||
|
Sphere = CreateDefaultSubobject<USphereComponent>("Sphere");
|
||||||
|
SetRootComponent(Sphere);
|
||||||
|
}
|
||||||
|
|
||||||
|
UShapeComponent* AGCS_SphereBulletInstance::GetBulletShape_Implementation() const
|
||||||
|
{
|
||||||
|
return Sphere;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called when the game starts or when spawned
|
||||||
|
void AGCS_SphereBulletInstance::BeginPlay()
|
||||||
|
{
|
||||||
|
Super::BeginPlay();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called every frame
|
||||||
|
void AGCS_SphereBulletInstance::Tick(float DeltaTime)
|
||||||
|
{
|
||||||
|
Super::Tick(DeltaTime);
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||||
|
|
||||||
|
|
||||||
|
#include "Collision/DEPRECATED_GCS_CollisionTraceInstance.h"
|
||||||
|
|
||||||
|
#include "GCS_LogChannels.h"
|
||||||
|
#include "Collision/GCS_TraceSystemComponent.h"
|
||||||
|
#include "Components/PrimitiveComponent.h"
|
||||||
|
|
||||||
|
void UDEPRECATED_GCS_CollisionTraceInstance::OnTraceBeginPlay_Implementation()
|
||||||
|
{
|
||||||
|
ActiveTime = 0.0f;
|
||||||
|
HitActors.Empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
void UDEPRECATED_GCS_CollisionTraceInstance::OnTraceEndPlay_Implementation()
|
||||||
|
{
|
||||||
|
ActiveTime = 0.0f;
|
||||||
|
bTraceActive = false;
|
||||||
|
TracePrimitiveComponent = nullptr;
|
||||||
|
TracePrimitiveComponentSocketNames.Empty();
|
||||||
|
HitActors.Empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
void UDEPRECATED_GCS_CollisionTraceInstance::BroadcastHit(const FHitResult& HitResult)
|
||||||
|
{
|
||||||
|
if (!bTraceActive)
|
||||||
|
{
|
||||||
|
UE_LOG(LogGCS_Collision, Warning, TEXT("Hit while inactive,%s"), *TraceOwner->GetName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UDEPRECATED_GCS_CollisionTraceInstance::BroadcastStateChanged(bool bNewState)
|
||||||
|
{
|
||||||
|
OnTraceStateChanged(bNewState);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UDEPRECATED_GCS_CollisionTraceInstance::OnTraceTick_Implementation(float DeltaSeconds)
|
||||||
|
{
|
||||||
|
ActiveTime += DeltaSeconds;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UDEPRECATED_GCS_CollisionTraceInstance::OnTraceStateChanged_Implementation(bool bNewState)
|
||||||
|
{
|
||||||
|
bTraceActive = bNewState;
|
||||||
|
HitActors.Empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
void UDEPRECATED_GCS_CollisionTraceInstance::SetTraceMeshInfo(UPrimitiveComponent* NewPrimitiveComponent, TArray<FName> PrimitiveComponentSocketNames)
|
||||||
|
{
|
||||||
|
TracePrimitiveComponent = NewPrimitiveComponent;
|
||||||
|
TracePrimitiveComponentSocketNames = PrimitiveComponentSocketNames;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UDEPRECATED_GCS_CollisionTraceInstance::CanHitActor_Implementation(const AActor* ActorToCheck) const
|
||||||
|
{
|
||||||
|
//in active trace can not hit anything. TODO make it checkf?
|
||||||
|
if (!bTraceActive)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ActorToCheck != GetTraceSourceActor() && ActorToCheck != TraceOwner && !HitActors.Contains(ActorToCheck);
|
||||||
|
}
|
||||||
|
|
||||||
|
AActor* UDEPRECATED_GCS_CollisionTraceInstance::GetTraceSourceActor() const
|
||||||
|
{
|
||||||
|
return TracePrimitiveComponent->GetOwner();
|
||||||
|
}
|
||||||
|
|
||||||
|
void UDEPRECATED_GCS_CollisionTraceInstance::ToggleTraceState(bool bNewState)
|
||||||
|
{
|
||||||
|
if (TraceOwner && bTraceActive != bNewState)
|
||||||
|
{
|
||||||
|
BroadcastStateChanged(bNewState);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UDEPRECATED_GCS_CollisionTraceInstance::OnTraceHit_Implementation(const FHitResult& HitResult)
|
||||||
|
{
|
||||||
|
if (HitResult.GetHitObjectHandle().IsValid())
|
||||||
|
{
|
||||||
|
if (CanHitActor(HitResult.GetActor()))
|
||||||
|
{
|
||||||
|
UE_LOG(LogGCS_Collision, VeryVerbose, TEXT("%s's Trace(%s, SourceActor:%s) hit actor(%s,Comp:%s)"), *TraceOwner->GetName(),
|
||||||
|
*(TraceGameplayTag.IsValid()?TraceGameplayTag.ToString():GetClass()->GetName()),
|
||||||
|
*GetTraceSourceActor()->GetName(),
|
||||||
|
*HitResult.GetActor()->GetName(), *(HitResult.Component.IsValid()?HitResult.GetComponent()->GetName():TEXT("Null")));
|
||||||
|
HitActors.Add(HitResult.GetActor());
|
||||||
|
BroadcastHit(HitResult);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||||
|
|
||||||
|
|
||||||
|
#include "Collision/GCS_AsyncAction_CollisionTrace.h"
|
||||||
|
#include "Collision/GCS_TraceSystemComponent.h"
|
||||||
|
#include "Components/PrimitiveComponent.h"
|
||||||
|
#include "Engine/Engine.h"
|
||||||
|
|
||||||
|
UGCS_AsyncAction_CollisionTrace* UGCS_AsyncAction_CollisionTrace::SetupAndListenForCollisionTraceHit(UGCS_TraceSystemComponent* TraceSystem,
|
||||||
|
const TArray<FGCS_TraceDefinition>& TraceDefinitions,
|
||||||
|
UPrimitiveComponent* PrimitiveComponent,
|
||||||
|
UObject* OptionalSourceObject)
|
||||||
|
{
|
||||||
|
if (TraceSystem == nullptr)
|
||||||
|
{
|
||||||
|
FFrame::KismetExecutionMessage(TEXT("SetupAndListenForCollisionTraceHit was passed a null TraceSystem"), ELogVerbosity::Error);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (PrimitiveComponent == nullptr)
|
||||||
|
{
|
||||||
|
FFrame::KismetExecutionMessage(TEXT("SetupAndListenForCollisionTraceHit was passed a null PrimitiveComponent"), ELogVerbosity::Error);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (TraceDefinitions.IsEmpty())
|
||||||
|
{
|
||||||
|
FFrame::KismetExecutionMessage(TEXT("SetupAndListenForCollisionTraceHit was passed empty TraceDefinitions"), ELogVerbosity::Error);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
UWorld* World = GEngine->GetWorldFromContextObject(TraceSystem, EGetWorldErrorMode::LogAndReturnNull);
|
||||||
|
if (!World)
|
||||||
|
{
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
UGCS_AsyncAction_CollisionTrace* Action = NewObject<UGCS_AsyncAction_CollisionTrace>();
|
||||||
|
|
||||||
|
Action->TraceDefinitions = TraceDefinitions;
|
||||||
|
Action->TraceSystem = TraceSystem;
|
||||||
|
Action->SourceComponent = PrimitiveComponent;
|
||||||
|
Action->SourceObject = OptionalSourceObject;
|
||||||
|
Action->RegisterWithGameInstance(World);
|
||||||
|
|
||||||
|
return Action;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UGCS_AsyncAction_CollisionTrace::Activate()
|
||||||
|
{
|
||||||
|
UGCS_TraceSystemComponent* TSC = TraceSystem.Get();
|
||||||
|
UPrimitiveComponent* Primitive = SourceComponent.Get();
|
||||||
|
UObject* SourceObj = SourceObject.Get();
|
||||||
|
|
||||||
|
if (TSC && Primitive)
|
||||||
|
{
|
||||||
|
TSC->OnTraceHitEvent.AddDynamic(this, &ThisClass::TraceHitCallback);
|
||||||
|
|
||||||
|
static FHitResult EmptyHitResult;
|
||||||
|
TraceHandles = TSC->AddTraces(TraceDefinitions, Primitive, SourceObj);
|
||||||
|
for (auto& Handle : TraceHandles)
|
||||||
|
{
|
||||||
|
BeforeActive.Broadcast(Handle, EmptyHitResult);
|
||||||
|
TSC->StartTrace(Handle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SetReadyToDestroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UGCS_AsyncAction_CollisionTrace::Cancel()
|
||||||
|
{
|
||||||
|
Super::Cancel();
|
||||||
|
UGCS_TraceSystemComponent* TSC = TraceSystem.Get();
|
||||||
|
UPrimitiveComponent* Primitive = SourceComponent.Get();
|
||||||
|
if (TSC && Primitive)
|
||||||
|
{
|
||||||
|
TSC->OnTraceHitEvent.RemoveAll(this);
|
||||||
|
//Deactivate traces.
|
||||||
|
for (const FGCS_TraceHandle& Handle : TraceHandles)
|
||||||
|
{
|
||||||
|
if (Handle.IsValidHandle())
|
||||||
|
{
|
||||||
|
TSC->RemoveTrace(Handle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TraceHandles.Empty();
|
||||||
|
SourceComponent = nullptr;
|
||||||
|
TraceSystem = nullptr;
|
||||||
|
SourceObject = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UGCS_AsyncAction_CollisionTrace::TraceHitCallback(const FGCS_TraceHandle& TraceHandle, const FHitResult& HitResult)
|
||||||
|
{
|
||||||
|
if (ShouldBroadcastDelegates() && TraceHandle.IsValidHandle())
|
||||||
|
{
|
||||||
|
if (TraceHandles.Contains(TraceHandle))
|
||||||
|
{
|
||||||
|
OnHit.Broadcast(TraceHandle, HitResult);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||||
|
|
||||||
|
|
||||||
|
#include "Collision/GCS_TraceDelegates.h"
|
||||||
|
|
||||||
|
#include UE_INLINE_GENERATED_CPP_BY_NAME(GCS_TraceDelegates)
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||||
|
|
||||||
|
|
||||||
|
#include "Collision/GCS_TraceEnumLibrary.h"
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||||
|
|
||||||
|
|
||||||
|
#include "Collision/GCS_TraceFunctionLibrary.h"
|
||||||
|
|
||||||
|
|
||||||
|
TArray<FGCS_TraceDefinition> UGCS_TraceFunctionLibrary::FilterTraceDefinitionsByTag(const TArray<FGCS_TraceDefinition>& Definitions, const FGameplayTag& TagToMatch)
|
||||||
|
{
|
||||||
|
return Definitions.FilterByPredicate([TagToMatch](const FGCS_TraceDefinition& Definition)
|
||||||
|
{
|
||||||
|
return Definition.TraceTag.IsValid() && Definition.CollisionShape.IsValid() && Definition.TraceTag.MatchesTag(TagToMatch);
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,249 @@
|
|||||||
|
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||||
|
|
||||||
|
#include "Collision/GCS_TraceStructLibrary.h"
|
||||||
|
|
||||||
|
#include "GCS_LogChannels.h"
|
||||||
|
#include "Components/BoxComponent.h"
|
||||||
|
#include "Components/CapsuleComponent.h"
|
||||||
|
#include "Components/SphereComponent.h"
|
||||||
|
#include "TargetingSystem/TargetingPreset.h"
|
||||||
|
|
||||||
|
|
||||||
|
bool FGCS_CollisionShape::InitializeShape(const UPrimitiveComponent* SourceComponent)
|
||||||
|
{
|
||||||
|
GCS_OWNED_CLOG(SourceComponent, Warning, "Should never use this shape:%s", *FGCS_CollisionShape::StaticStruct()->GetName());
|
||||||
|
FDebug::DumpStackTraceToLog(ELogVerbosity::VeryVerbose);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
FTransform FGCS_CollisionShape::GetTransform(const UPrimitiveComponent* SourceComponent, const float& Time) const
|
||||||
|
{
|
||||||
|
GCS_OWNED_CLOG(SourceComponent, Warning, "Should never use this shape:%s", *FGCS_CollisionShape::StaticStruct()->GetName());
|
||||||
|
FDebug::DumpStackTraceToLog(ELogVerbosity::VeryVerbose);
|
||||||
|
return SourceComponent->GetComponentTransform();
|
||||||
|
}
|
||||||
|
|
||||||
|
FCollisionShape FGCS_CollisionShape::GetDynamicCollisionShape(const UPrimitiveComponent* SourceComponent, const float& Time) const
|
||||||
|
{
|
||||||
|
GCS_OWNED_CLOG(SourceComponent, Warning, "Should never use this shape:%s", *FGCS_CollisionShape::StaticStruct()->GetName());
|
||||||
|
FDebug::DumpStackTraceToLog(ELogVerbosity::VeryVerbose);
|
||||||
|
return FCollisionShape::MakeSphere(30);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FGCS_CollisionShape_Static::InitializeShape(const UPrimitiveComponent* SourceComponent)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
FTransform FGCS_CollisionShape_Static::GetTransform(const UPrimitiveComponent* SourceComponent, const float& Time) const
|
||||||
|
{
|
||||||
|
const auto ComponentCurrentTransform = SourceComponent->GetComponentTransform();
|
||||||
|
return FTransform(Orientation, Offset) * ComponentCurrentTransform;
|
||||||
|
}
|
||||||
|
|
||||||
|
FCollisionShape FGCS_CollisionShape_Static::GetDynamicCollisionShape(const UPrimitiveComponent* SourceComponent, const float& Time) const
|
||||||
|
{
|
||||||
|
switch (ShapeType)
|
||||||
|
{
|
||||||
|
case EGCS_CollisionShapeType::Sphere:
|
||||||
|
{
|
||||||
|
return FCollisionShape::MakeSphere(Radius);
|
||||||
|
}
|
||||||
|
case EGCS_CollisionShapeType::Box:
|
||||||
|
{
|
||||||
|
return FCollisionShape::MakeBox(HalfSize);
|
||||||
|
}
|
||||||
|
case EGCS_CollisionShapeType::Capsule:
|
||||||
|
{
|
||||||
|
return FCollisionShape::MakeCapsule(Radius, HalfHeight);
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
return FCollisionShape::MakeSphere(Radius);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FGCS_CollisionShape_ShapeBased::InitializeShape(const UPrimitiveComponent* SourceComponent)
|
||||||
|
{
|
||||||
|
if (ShapeType == EGCS_CollisionShapeType::Sphere)
|
||||||
|
{
|
||||||
|
if (const auto SphereCollision = Cast<USphereComponent>(SourceComponent))
|
||||||
|
{
|
||||||
|
Radius = SphereCollision->GetScaledSphereRadius();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
GCS_OWNED_CLOG(SourceComponent->GetOwner(), Warning, "No compatible shape component was found! Requires SphereComponent, Got %s(%s)", *GetNameSafe(SourceComponent),
|
||||||
|
*SourceComponent->GetClass()->GetName())
|
||||||
|
}
|
||||||
|
if (ShapeType == EGCS_CollisionShapeType::Box)
|
||||||
|
{
|
||||||
|
if (const auto BoxCollision = Cast<UBoxComponent>(SourceComponent))
|
||||||
|
{
|
||||||
|
HalfSize = BoxCollision->GetScaledBoxExtent();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
GCS_OWNED_CLOG(SourceComponent->GetOwner(), Warning, "No compatible shape component was found! Requires BoxComponent, Got %s(%s)", *GetNameSafe(SourceComponent),
|
||||||
|
*SourceComponent->GetClass()->GetName())
|
||||||
|
}
|
||||||
|
if (ShapeType == EGCS_CollisionShapeType::Capsule)
|
||||||
|
{
|
||||||
|
if (const auto CapsuleCollision = Cast<UCapsuleComponent>(SourceComponent))
|
||||||
|
{
|
||||||
|
HalfHeight = CapsuleCollision->GetScaledCapsuleHalfHeight();
|
||||||
|
Radius = CapsuleCollision->GetScaledCapsuleRadius();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
GCS_OWNED_CLOG(SourceComponent->GetOwner(), Warning, "No compatible shape component was found! Requires CapsuleComponent, Got %s(%s)", *GetNameSafe(SourceComponent),
|
||||||
|
*SourceComponent->GetClass()->GetName())
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
FTransform FGCS_CollisionShape_ShapeBased::GetTransform(const UPrimitiveComponent* SourceComponent, const float& Time) const
|
||||||
|
{
|
||||||
|
const auto ComponentCurrentTransform = SourceComponent->GetComponentTransform();
|
||||||
|
return FTransform(Orientation, Offset) * ComponentCurrentTransform;
|
||||||
|
}
|
||||||
|
|
||||||
|
FCollisionShape FGCS_CollisionShape_ShapeBased::GetDynamicCollisionShape(const UPrimitiveComponent* SourceComponent, const float& Time) const
|
||||||
|
{
|
||||||
|
switch (ShapeType)
|
||||||
|
{
|
||||||
|
case EGCS_CollisionShapeType::Sphere:
|
||||||
|
{
|
||||||
|
return FCollisionShape::MakeSphere(Radius);
|
||||||
|
}
|
||||||
|
case EGCS_CollisionShapeType::Box:
|
||||||
|
{
|
||||||
|
return FCollisionShape::MakeBox(HalfSize);
|
||||||
|
}
|
||||||
|
case EGCS_CollisionShapeType::Capsule:
|
||||||
|
{
|
||||||
|
return FCollisionShape::MakeCapsule(Radius, HalfHeight);
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
return FCollisionShape::MakeSphere(Radius);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FGCS_CollisionShape_Attached::InitializeShape(const UPrimitiveComponent* SourceComponent)
|
||||||
|
{
|
||||||
|
if (!IsValid(SourceComponent) || !SourceComponent->DoesSocketExist(SocketOrBoneName))
|
||||||
|
{
|
||||||
|
GCS_OWNED_CLOG(SourceComponent->GetOwner(), Warning, "No SocketOrBone(%s) exists on mesh component! Got %s(%s)", *SocketOrBoneName.ToString(), *GetNameSafe(SourceComponent),
|
||||||
|
*SourceComponent->GetClass()->GetName())
|
||||||
|
FDebug::DumpStackTraceToLog(ELogVerbosity::VeryVerbose);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
FTransform FGCS_CollisionShape_Attached::GetTransform(const UPrimitiveComponent* SourceComponent, const float& Time) const
|
||||||
|
{
|
||||||
|
const auto BoneTransform = SourceComponent->GetSocketTransform(SocketOrBoneName);
|
||||||
|
return FTransform(Orientation, Offset) * BoneTransform;
|
||||||
|
}
|
||||||
|
|
||||||
|
FTransform FGCS_CollisionShape_SocketBased::GetTransform(const UPrimitiveComponent* SourceComponent, const float& Time) const
|
||||||
|
{
|
||||||
|
const auto ComponentCurrentTransform = SourceComponent->GetComponentTransform();
|
||||||
|
return FTransform(Orientation, Offset) * ComponentCurrentTransform;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FGCS_CollisionShape_SocketBased::InitializeShape(const UPrimitiveComponent* SourceComponent)
|
||||||
|
{
|
||||||
|
if (const UMeshComponent* MeshComponent = Cast<UMeshComponent>(SourceComponent))
|
||||||
|
{
|
||||||
|
const auto SocketStartTransform = MeshComponent->GetSocketTransform(MeshSocketStart, RTS_Component);
|
||||||
|
const auto SocketStartLocation = SocketStartTransform.GetLocation();
|
||||||
|
|
||||||
|
const auto SocketEndTransform = MeshComponent->GetSocketTransform(MeshSocketEnd, RTS_Component);
|
||||||
|
const auto SocketEndLocation = SocketEndTransform.GetLocation();
|
||||||
|
|
||||||
|
const FVector CenterLocation = (SocketStartLocation + SocketEndLocation) / 2.f;
|
||||||
|
|
||||||
|
HalfHeight = FMath::Max((SocketStartLocation - SocketEndLocation).Length() / 2.f + MeshSocketLengthOffset, 1.f);
|
||||||
|
Offset = CenterLocation;
|
||||||
|
Orientation = FRotationMatrix::MakeFromZ(SocketStartLocation - SocketEndLocation).Rotator();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
GCS_OWNED_CLOG(SourceComponent->GetOwner(), Warning, "No compatible mesh component was found! Got %s(%s)", *GetNameSafe(SourceComponent),
|
||||||
|
*SourceComponent->GetClass()->GetName())
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
FCollisionShape FGCS_CollisionShape_SocketBased::GetDynamicCollisionShape(const UPrimitiveComponent* SourceComponent, const float& Time) const
|
||||||
|
{
|
||||||
|
return FCollisionShape::MakeCapsule(Radius, HalfHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
FGCS_TraceDefinition::FGCS_TraceDefinition()
|
||||||
|
{
|
||||||
|
CollisionShape.InitializeAs(FGCS_CollisionShape_Static::StaticStruct());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FGCS_TraceDefinition::IsValidDefinition() const
|
||||||
|
{
|
||||||
|
// Base collision shape is not allowed!
|
||||||
|
return TraceTag.IsValid() && CollisionShape.IsValid() && CollisionShape.GetScriptStruct() != FGCS_CollisionShape::StaticStruct();
|
||||||
|
}
|
||||||
|
|
||||||
|
FString FGCS_TraceDefinition::ToString() const
|
||||||
|
{
|
||||||
|
return FString::Format(TEXT("{0} {1}"), {TraceTag.ToString(), GetNameSafe(CollisionShape.GetScriptStruct())});
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FGCS_TraceHandle::IsValidHandle() const
|
||||||
|
{
|
||||||
|
return TraceTag.IsValid() && Guid.IsValid() && SourceObject.IsValid();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FGCS_TraceState::ChangeExecutionState(const bool bNewTraceState, const bool bStopImmediate)
|
||||||
|
{
|
||||||
|
if (bNewTraceState)
|
||||||
|
{
|
||||||
|
this->ExecutionState = EGCS_TraceExecutionState::InProgress;
|
||||||
|
this->TimeSinceLastTick = 0;
|
||||||
|
this->TimeSinceActive = 0;
|
||||||
|
this->TotalTickNumDuringExecution = 0;
|
||||||
|
this->HitActors.Reset();
|
||||||
|
if (SourceComponent)
|
||||||
|
{
|
||||||
|
UpdatePreviousTransform(GetCurrentTransform());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (this->ExecutionState == EGCS_TraceExecutionState::InProgress && !bStopImmediate)
|
||||||
|
{
|
||||||
|
this->ExecutionState = EGCS_TraceExecutionState::PendingStop;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this->ExecutionState = EGCS_TraceExecutionState::Stopped;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FTransform FGCS_TraceState::GetCurrentTransform() const
|
||||||
|
{
|
||||||
|
check(Shape.IsValid())
|
||||||
|
return Shape.Get<FGCS_CollisionShape>().GetTransform(SourceComponent.Get(), TimeSinceActive);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FGCS_TraceState::UpdatePreviousTransform(const FTransform& Transform)
|
||||||
|
{
|
||||||
|
if (TransformsOverTime.Num() == 0)
|
||||||
|
{
|
||||||
|
TransformsOverTime.Add(Transform);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
TransformsOverTime[0] = Transform;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,485 @@
|
|||||||
|
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||||
|
|
||||||
|
#include "Collision/GCS_TraceSubsystem.h"
|
||||||
|
|
||||||
|
#include "GCS_LogChannels.h"
|
||||||
|
#include "KismetTraceUtils.h"
|
||||||
|
#include "Async/ParallelFor.h"
|
||||||
|
#include "Components/StaticMeshComponent.h"
|
||||||
|
#include "Collision/GCS_TraceSystemComponent.h"
|
||||||
|
#include "Kismet/KismetMathLibrary.h"
|
||||||
|
|
||||||
|
#define LOCTEXT_NAMESPACE "UGCS_CollisionTraceSubsystem"
|
||||||
|
|
||||||
|
// CVars
|
||||||
|
namespace GenericCombatSystemCVars
|
||||||
|
{
|
||||||
|
#if ENABLE_DRAW_DEBUG
|
||||||
|
|
||||||
|
static bool bEnableTraceDebugging = false;
|
||||||
|
FAutoConsoleVariableRef CvarForceEnableTraceDebugging(
|
||||||
|
TEXT("gcs.debug.EnableTraceDebugging"),
|
||||||
|
bEnableTraceDebugging,
|
||||||
|
TEXT("Toggles whether enable draw debugs for all traces. (Enabled: true, Disabled: false)"));
|
||||||
|
|
||||||
|
static float OverrideTraceDebuggingLifeTime = 0.f;
|
||||||
|
FAutoConsoleVariableRef CvarOverrideTraceDebuggingLifeTime(
|
||||||
|
TEXT("gcs.debug.OverrideTraceDebuggingLifeTime"),
|
||||||
|
OverrideTraceDebuggingLifeTime,
|
||||||
|
TEXT("Overrides the draws life time to ease the trace debugging"));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void UGCS_TraceSubsystem::Initialize(FSubsystemCollectionBase& Collection)
|
||||||
|
{
|
||||||
|
Super::Initialize(Collection);
|
||||||
|
this->TraceStates.Reserve(4068);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UGCS_TraceSubsystem::Tick(float DeltaTime)
|
||||||
|
{
|
||||||
|
DECLARE_SCOPE_CYCLE_COUNTER(TEXT("UGCS_TraceSubsystem::Tick"), STAT_UGCS_TraceSubsystem_Tick, STATGROUP_GCS)
|
||||||
|
TRACE_CPUPROFILER_EVENT_SCOPE_STR(__FUNCTION__)
|
||||||
|
|
||||||
|
TickIdx++;
|
||||||
|
// Lock removals so we don't get any modifications to the array while we are iterating
|
||||||
|
RemovalLock = true;
|
||||||
|
|
||||||
|
PreTraceTick(DeltaTime);
|
||||||
|
|
||||||
|
PrepareSubTicks(DeltaTime);
|
||||||
|
PerformSubTicks(DeltaTime);
|
||||||
|
|
||||||
|
PostTraceTick();
|
||||||
|
|
||||||
|
// Reverse iterate, remove pending removals
|
||||||
|
RemovalLock = false;
|
||||||
|
for (int Idx = TraceStates.Num() - 1; Idx >= 0; Idx--)
|
||||||
|
{
|
||||||
|
const auto& TraceState = TraceStates[Idx];
|
||||||
|
if (!TraceStates.IsValidIndex(Idx))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (TraceState.IsPendingRemoval)
|
||||||
|
{
|
||||||
|
RemoveTraceStateAt(Idx, TraceState.Handle.Guid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UGCS_TraceSubsystem::RemoveTraceState(const int Idx, const FGuid Guid)
|
||||||
|
{
|
||||||
|
if (RemovalLock)
|
||||||
|
{
|
||||||
|
if (TraceStates.IsValidIndex(Idx) && TraceStates[Idx].Handle.Guid == Guid)
|
||||||
|
{
|
||||||
|
TraceStates[Idx].IsPendingRemoval = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
RemoveTraceStateAt(Idx, Guid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int32 UGCS_TraceSubsystem::AddTraceState()
|
||||||
|
{
|
||||||
|
FScopeLock ScopeLock(&CriticalSection);
|
||||||
|
return TraceStates.AddDefaulted();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UGCS_TraceSubsystem::IsValidStateIdx(int32 StateIdx) const
|
||||||
|
{
|
||||||
|
return TraceStates.IsValidIndex(StateIdx);
|
||||||
|
}
|
||||||
|
|
||||||
|
FGCS_TraceState& UGCS_TraceSubsystem::GetTraceStateAt(const int Index)
|
||||||
|
{
|
||||||
|
return TraceStates[Index];
|
||||||
|
}
|
||||||
|
|
||||||
|
void UGCS_TraceSubsystem::RemoveTraceStateAt(const int Idx, const FGuid Guid)
|
||||||
|
{
|
||||||
|
DECLARE_SCOPE_CYCLE_COUNTER(TEXT("UGCS_TraceSubsystem::RemoveTraceStateAt"), STAT_UGCS_TraceSubsystem_RemoveTraceStateAt, STATGROUP_GCS)
|
||||||
|
TRACE_CPUPROFILER_EVENT_SCOPE_STR(__FUNCTION__)
|
||||||
|
FScopeLock ScopeLock(&CriticalSection);
|
||||||
|
if (TraceStates.IsValidIndex(Idx) && TraceStates[Idx].Handle.Guid == Guid)
|
||||||
|
{
|
||||||
|
// 移除并交换
|
||||||
|
TraceStates.RemoveAtSwap(Idx);
|
||||||
|
|
||||||
|
// If a state was moved to Idx, update its corresponding handle in the OwningSystem
|
||||||
|
if (TraceStates.IsValidIndex(Idx))
|
||||||
|
{
|
||||||
|
FGCS_TraceState& MovedState = TraceStates[Idx];
|
||||||
|
if (IsValid(MovedState.OwningSystem))
|
||||||
|
{
|
||||||
|
// Update the Component's HandleToStateIdx map
|
||||||
|
MovedState.OwningSystem->HandleToStateIdx.Remove(MovedState.Handle); // Remove old mapping
|
||||||
|
MovedState.OwningSystem->HandleToStateIdx.Add(MovedState.Handle, Idx); // Add new mapping
|
||||||
|
}
|
||||||
|
// mark pending removal if owning system become invalid.
|
||||||
|
else
|
||||||
|
{
|
||||||
|
MovedState.IsPendingRemoval = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UGCS_TraceSubsystem::PreTraceTick(const float DeltaTime)
|
||||||
|
{
|
||||||
|
DECLARE_SCOPE_CYCLE_COUNTER(TEXT("UGCS_TraceSubsystem::PreTraceTick"), STAT_UGCS_TraceSubsystem_PreTraceTick, STATGROUP_GCS)
|
||||||
|
TRACE_CPUPROFILER_EVENT_SCOPE_STR(__FUNCTION__)
|
||||||
|
ParallelFor(TraceStates.Num(), [&](const int32 Idx)
|
||||||
|
{
|
||||||
|
if (!TraceStates.IsValidIndex(Idx))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto& TraceState = TraceStates[Idx];
|
||||||
|
if (TraceState.ExecutionState == EGCS_TraceExecutionState::Stopped)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
check(TraceState.bShouldTickThisFrame == false)
|
||||||
|
|
||||||
|
if (!IsValid(TraceState.SourceComponent))
|
||||||
|
{
|
||||||
|
TraceState.ExecutionState = EGCS_TraceExecutionState::Stopped;
|
||||||
|
TraceState.bShouldTickThisFrame = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const FTransform& CurrentTransform = TraceState.GetCurrentTransform();
|
||||||
|
|
||||||
|
if (TraceState.TransformsOverTime.IsEmpty())
|
||||||
|
{
|
||||||
|
TraceState.TransformsOverTime.Add(CurrentTransform);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (TraceState.ExecutionState == EGCS_TraceExecutionState::PendingStop)
|
||||||
|
{
|
||||||
|
TraceState.bShouldTickThisFrame = true;
|
||||||
|
TraceState.TransformsOverTime.Add(CurrentTransform);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (TraceState.TickPolicy)
|
||||||
|
{
|
||||||
|
case EGCS_TraceTickType::Default:
|
||||||
|
TraceState.TransformsOverTime.Add(CurrentTransform);
|
||||||
|
TraceState.bShouldTickThisFrame = true;
|
||||||
|
return;
|
||||||
|
case EGCS_TraceTickType::DistanceBased:
|
||||||
|
{
|
||||||
|
const FVector& PreviousLocation = TraceState.TransformsOverTime[0].GetLocation();
|
||||||
|
const FVector& CurrentLocation = CurrentTransform.GetLocation();
|
||||||
|
const FRotator& PreviousRotation = TraceState.TransformsOverTime[0].GetRotation().Rotator();
|
||||||
|
const FRotator& CurrentRotation = CurrentTransform.GetRotation().Rotator();
|
||||||
|
|
||||||
|
bool bDistanceThresholdMet = (PreviousLocation - CurrentLocation).Length() >= TraceState.TickInterval;
|
||||||
|
bool bAngleThresholdMet = FMath::Abs(PreviousRotation.Yaw - CurrentRotation.Yaw) >= TraceState.AngleThreshold ||
|
||||||
|
FMath::Abs(PreviousRotation.Pitch - CurrentRotation.Pitch) >= TraceState.AngleThreshold ||
|
||||||
|
FMath::Abs(PreviousRotation.Roll - CurrentRotation.Roll) >= TraceState.AngleThreshold;
|
||||||
|
|
||||||
|
if (bDistanceThresholdMet || bAngleThresholdMet)
|
||||||
|
{
|
||||||
|
TraceState.TransformsOverTime.Add(CurrentTransform);
|
||||||
|
TraceState.bShouldTickThisFrame = true;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
case EGCS_TraceTickType::FixedFrameRate:
|
||||||
|
TraceState.TimeSinceLastTick += DeltaTime;
|
||||||
|
int32 SubTickCountPreview = FMath::FloorToInt(TraceState.TimeSinceLastTick / TraceState.TickInterval);
|
||||||
|
if (SubTickCountPreview > 0)
|
||||||
|
{
|
||||||
|
TraceState.TransformsOverTime.Add(CurrentTransform);
|
||||||
|
TraceState.bShouldTickThisFrame = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void UGCS_TraceSubsystem::PostTraceTick()
|
||||||
|
{
|
||||||
|
DECLARE_SCOPE_CYCLE_COUNTER(TEXT("UGCS_TraceSubsystem::PostTraceTick"), STAT_UGCS_TraceSubsystem_PostTraceTick, STATGROUP_GCS)
|
||||||
|
TRACE_CPUPROFILER_EVENT_SCOPE_STR(__FUNCTION__)
|
||||||
|
ParallelFor(TraceStates.Num(), [&](const int32 Idx)
|
||||||
|
{
|
||||||
|
auto& TraceState = TraceStates[Idx];
|
||||||
|
if (!TraceState.bShouldTickThisFrame)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset time since last tick.
|
||||||
|
if (TraceState.TickPolicy == EGCS_TraceTickType::FixedFrameRate)
|
||||||
|
{
|
||||||
|
int32 SubTickCount = TraceState.SubTicks.Num();
|
||||||
|
TraceState.TimeSinceLastTick -= SubTickCount * TraceState.TickInterval;
|
||||||
|
|
||||||
|
TraceState.TimeSinceLastTick = FMath::Max(TraceState.TimeSinceLastTick, 0.0f);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
TraceState.TimeSinceLastTick = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
TraceState.bShouldTickThisFrame = false;
|
||||||
|
|
||||||
|
if (TraceState.ExecutionState == EGCS_TraceExecutionState::PendingStop)
|
||||||
|
{
|
||||||
|
TraceState.ExecutionState = EGCS_TraceExecutionState::Stopped;
|
||||||
|
TraceState.TransformsOverTime.Empty();
|
||||||
|
TraceState.TimeSinceActive = 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!TraceState.TransformsOverTime.IsEmpty())
|
||||||
|
{
|
||||||
|
TraceState.TransformsOverTime.RemoveAtSwap(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void UGCS_TraceSubsystem::PrepareSubTicks(const float DeltaTime)
|
||||||
|
{
|
||||||
|
DECLARE_SCOPE_CYCLE_COUNTER(TEXT("UGCS_TraceSubsystem::PrepareSubTicks"), STAT_UGCS_TraceSubsystem_PrepareSubTicks, STATGROUP_GCS)
|
||||||
|
TRACE_CPUPROFILER_EVENT_SCOPE_STR(__FUNCTION__)
|
||||||
|
|
||||||
|
ParallelFor(TraceStates.Num(), [&](const int32 Idx)
|
||||||
|
{
|
||||||
|
if (!TraceStates.IsValidIndex(Idx))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto& TraceState = TraceStates[Idx];
|
||||||
|
|
||||||
|
TraceState.SubTicks.Reset();
|
||||||
|
if (TraceState.bShouldTickThisFrame)
|
||||||
|
{
|
||||||
|
int32 SubTickCount = 1;
|
||||||
|
if (TraceState.TickPolicy == EGCS_TraceTickType::DistanceBased)
|
||||||
|
{
|
||||||
|
SubTickCount = FMath::CeilToInt((TraceState.TransformsOverTime[0].GetLocation() - TraceState.TransformsOverTime[1].GetLocation()).Length() / TraceState.TickInterval);
|
||||||
|
}
|
||||||
|
else if (TraceState.TickPolicy == EGCS_TraceTickType::FixedFrameRate)
|
||||||
|
{
|
||||||
|
SubTickCount = FMath::FloorToInt(TraceState.TimeSinceLastTick / TraceState.TickInterval);
|
||||||
|
}
|
||||||
|
SubTickCount = FMath::Min(10, SubTickCount);
|
||||||
|
|
||||||
|
if (TraceState.TransformsOverTime.Num() > 1)
|
||||||
|
{
|
||||||
|
const float SubTickRatio = 1.0 / SubTickCount;
|
||||||
|
const FTransform CurrentTransform = TraceState.TransformsOverTime.Last();
|
||||||
|
const FTransform PreviousTransform = TraceState.TransformsOverTime[0];
|
||||||
|
TraceState.CollisionShapeOverTime = TraceState.Shape.Get<FGCS_CollisionShape>().GetDynamicCollisionShape(TraceState.SourceComponent, TraceState.TimeSinceActive);
|
||||||
|
for (int32 i = 0; i < SubTickCount; i++)
|
||||||
|
{
|
||||||
|
FGCS_TraceSubTick SubTick{};
|
||||||
|
SubTick.StartTransform = UKismetMathLibrary::TLerp(PreviousTransform, CurrentTransform, SubTickRatio * i, ELerpInterpolationMode::DualQuatInterp);
|
||||||
|
SubTick.EndTransform = UKismetMathLibrary::TLerp(PreviousTransform, CurrentTransform, SubTickRatio * (i + 1), ELerpInterpolationMode::DualQuatInterp);
|
||||||
|
SubTick.AverageTransform = UKismetMathLibrary::TLerp(SubTick.StartTransform, SubTick.EndTransform, 0.5, ELerpInterpolationMode::DualQuatInterp);
|
||||||
|
TraceState.SubTicks.Add(SubTick);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
GCS_LOG(Warning, "Trace [%s] has less than 2 transforms in TransformsOverTime array!", *TraceState.Handle.TraceTag.ToString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (TraceState.ExecutionState == EGCS_TraceExecutionState::InProgress)
|
||||||
|
{
|
||||||
|
TraceState.TimeSinceActive += DeltaTime;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void UGCS_TraceSubsystem::PerformSubTicks(const float DeltaTime)
|
||||||
|
{
|
||||||
|
DECLARE_SCOPE_CYCLE_COUNTER(TEXT("UGCS_TraceSubsystem::PerformSubTicks"), STAT_UGCS_TraceSubsystem_PerformSubTicks, STATGROUP_GCS)
|
||||||
|
TRACE_CPUPROFILER_EVENT_SCOPE_STR(__FUNCTION__)
|
||||||
|
|
||||||
|
for (int32 Idx = 0; Idx < TraceStates.Num(); Idx++)
|
||||||
|
{
|
||||||
|
if (!TraceStates.IsValidIndex(Idx))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
auto& TraceState = TraceStates[Idx];
|
||||||
|
|
||||||
|
if (TraceState.bShouldTickThisFrame)
|
||||||
|
{
|
||||||
|
for (int32 SubTickIdx = 0; SubTickIdx < TraceState.SubTicks.Num(); SubTickIdx++)
|
||||||
|
{
|
||||||
|
const FGCS_TraceSubTick& SubTick = TraceState.SubTicks[SubTickIdx];
|
||||||
|
|
||||||
|
// Respect cancellations by user-defined code immediately.
|
||||||
|
if (TraceState.ExecutionState == EGCS_TraceExecutionState::Stopped)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
TraceState.TotalTickNumDuringExecution += 1;
|
||||||
|
|
||||||
|
FTraceDelegate Delegate = FTraceDelegate::CreateUObject(this, &UGCS_TraceSubsystem::HandleTraceResults, Idx, TickIdx, TraceState.TimeSinceActive);
|
||||||
|
|
||||||
|
PerformAsyncTrace(SubTick.StartTransform, SubTick.EndTransform, SubTick.AverageTransform, TraceState.World, TraceState.SweepSetting,
|
||||||
|
TraceState.CollisionShapeOverTime,
|
||||||
|
TraceState.CollisionParams,
|
||||||
|
TraceState.ResponseParams,
|
||||||
|
TraceState.ObjectQueryParams, &Delegate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UGCS_TraceSubsystem::HandleTraceResults(const FTraceHandle& InTraceHandle, FTraceDatum& InTraceDatum, int32 InTraceStateIdx, uint32 InTickIdx, float InShapeTime)
|
||||||
|
{
|
||||||
|
DECLARE_SCOPE_CYCLE_COUNTER(TEXT("UGCS_TraceSubsystem::HandleTraceResults"), STAT_UGCS_TraceSubsystem_HandleTraceResults, STATGROUP_GCS)
|
||||||
|
TRACE_CPUPROFILER_EVENT_SCOPE_STR(__FUNCTION__)
|
||||||
|
|
||||||
|
if (!TraceStates.IsValidIndex(InTraceStateIdx))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& TraceState = TraceStates[InTraceStateIdx];
|
||||||
|
|
||||||
|
if (!IsValid(TraceState.OwningSystem) || TraceState.ExecutionState == EGCS_TraceExecutionState::Stopped)
|
||||||
|
{
|
||||||
|
TraceState.TimeSinceActive = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// GCS_LOG(Warning, "Trace [%s] has ticked %d within %f s", *TraceState.Handle.TraceTag.ToString(), TraceState.TotalTickNumDuringExecution, TraceState.TimeSinceActive)
|
||||||
|
|
||||||
|
#if ENABLE_DRAW_DEBUG
|
||||||
|
if (GenericCombatSystemCVars::bEnableTraceDebugging)
|
||||||
|
{
|
||||||
|
const EDrawDebugTrace::Type DrawDebugType = GenericCombatSystemCVars::OverrideTraceDebuggingLifeTime > 0 ? EDrawDebugTrace::ForDuration : EDrawDebugTrace::ForOneFrame;
|
||||||
|
const float DrawDebugTime = GenericCombatSystemCVars::OverrideTraceDebuggingLifeTime > 0
|
||||||
|
? GenericCombatSystemCVars::OverrideTraceDebuggingLifeTime
|
||||||
|
: (DrawDebugType == EDrawDebugTrace::ForOneFrame ? 0.0f : 0.5f);
|
||||||
|
|
||||||
|
const bool bHasAuthority = TraceState.SourceComponent->GetOwner()->HasAuthority();
|
||||||
|
|
||||||
|
|
||||||
|
DrawDebug(InTraceDatum.Start,
|
||||||
|
InTraceDatum.End,
|
||||||
|
InTraceDatum.Rot,
|
||||||
|
InTraceDatum.OutHits, TraceState.Shape.Get<FGCS_CollisionShape>().GetDynamicCollisionShape(TraceState.SourceComponent, InShapeTime), TraceState.World,
|
||||||
|
DrawDebugType,
|
||||||
|
DrawDebugTime,
|
||||||
|
bHasAuthority ? FLinearColor::Red : FLinearColor::White,
|
||||||
|
bHasAuthority ? FLinearColor::Green : FLinearColor::Black);
|
||||||
|
// GCS_OWNED_CLOG(TraceState.SourceComponent, Display, "Did collision trace: start(%s) end(%s)", *InTraceDatum.Start.ToCompactString(), *InTraceDatum.End.ToCompactString())
|
||||||
|
}
|
||||||
|
#endif // ENABLE_DRAW_DEBUG
|
||||||
|
|
||||||
|
TArray<FHitResult> FilteredHits;
|
||||||
|
for (const FHitResult& NewHit : InTraceDatum.OutHits)
|
||||||
|
{
|
||||||
|
if (!TraceState.HitActors.Contains(NewHit.GetActor()))
|
||||||
|
{
|
||||||
|
FilteredHits.Add(NewHit);
|
||||||
|
TraceState.HitActors.Add(NewHit.GetActor());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (FilteredHits.Num() > 0)
|
||||||
|
{
|
||||||
|
TraceState.OwningSystem->OnTraceHitDetected(TraceState.Handle, FilteredHits, TraceState.TimeSinceLastTick, InTickIdx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UGCS_TraceSubsystem::PerformAsyncTrace(const FTransform& StartTransform, const FTransform& EndTransform, const FTransform& AverageTransform, UWorld* World,
|
||||||
|
const FGCS_TraceSweepSetting& TraceSettings, const FCollisionShape& CollisionShape, const FCollisionQueryParams& CollisionParams,
|
||||||
|
const FCollisionResponseParams& CollisionResponseParams,
|
||||||
|
const FCollisionObjectQueryParams& ObjectQueryParams, const FTraceDelegate* InDelegate)
|
||||||
|
{
|
||||||
|
switch (TraceSettings.SweepType)
|
||||||
|
{
|
||||||
|
case EGCS_TraceSweepType::ByChannel:
|
||||||
|
World->AsyncSweepByChannel(
|
||||||
|
EAsyncTraceType::Multi,
|
||||||
|
StartTransform.GetLocation(),
|
||||||
|
EndTransform.GetLocation(),
|
||||||
|
AverageTransform.GetRotation(),
|
||||||
|
TraceSettings.TraceChannel,
|
||||||
|
CollisionShape,
|
||||||
|
CollisionParams,
|
||||||
|
CollisionResponseParams, InDelegate);
|
||||||
|
break;
|
||||||
|
case EGCS_TraceSweepType::ByObject:
|
||||||
|
World->AsyncSweepByObjectType(
|
||||||
|
EAsyncTraceType::Multi,
|
||||||
|
StartTransform.GetLocation(),
|
||||||
|
EndTransform.GetLocation(),
|
||||||
|
AverageTransform.GetRotation(),
|
||||||
|
ObjectQueryParams,
|
||||||
|
CollisionShape,
|
||||||
|
CollisionParams, InDelegate);
|
||||||
|
break;
|
||||||
|
case EGCS_TraceSweepType::ByProfile:
|
||||||
|
World->AsyncSweepByProfile(
|
||||||
|
EAsyncTraceType::Multi,
|
||||||
|
StartTransform.GetLocation(),
|
||||||
|
EndTransform.GetLocation(),
|
||||||
|
AverageTransform.GetRotation(),
|
||||||
|
TraceSettings.ProfileName,
|
||||||
|
CollisionShape,
|
||||||
|
CollisionParams, InDelegate);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if ENABLE_DRAW_DEBUG
|
||||||
|
void UGCS_TraceSubsystem::DrawDebug(const FVector& StartLocation, const FVector& EndLocation, const FQuat& Orientation, TArray<FHitResult> Hits, const FCollisionShape& CollisionShape,
|
||||||
|
const UWorld* World,
|
||||||
|
const EDrawDebugTrace::Type DrawDebugType, float DrawDebugTime, const FLinearColor& DrawDebugColor,
|
||||||
|
const FLinearColor& DrawDebugHitColor)
|
||||||
|
{
|
||||||
|
DECLARE_SCOPE_CYCLE_COUNTER(TEXT("UGCS_TraceSubsystem::DrawDebug"), STAT_UGCS_TraceSubsystem_DrawDebug, STATGROUP_GCS)
|
||||||
|
TRACE_CPUPROFILER_EVENT_SCOPE_STR(__FUNCTION__)
|
||||||
|
|
||||||
|
// We have to manually find if there is a blocking hit.
|
||||||
|
bool bHasBlockingHit = false;
|
||||||
|
for (const FHitResult& HitResult : Hits)
|
||||||
|
{
|
||||||
|
if (HitResult.bBlockingHit)
|
||||||
|
{
|
||||||
|
bHasBlockingHit = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (CollisionShape.ShapeType)
|
||||||
|
{
|
||||||
|
case ECollisionShape::Sphere:
|
||||||
|
DrawDebugSphereTraceMulti(World, StartLocation, EndLocation, CollisionShape.GetSphereRadius(), DrawDebugType, bHasBlockingHit, Hits, DrawDebugColor, DrawDebugHitColor, DrawDebugTime);
|
||||||
|
break;
|
||||||
|
case ECollisionShape::Capsule:
|
||||||
|
DrawDebugCapsuleTraceMulti(World, StartLocation, EndLocation, CollisionShape.GetCapsuleRadius(), CollisionShape.GetCapsuleHalfHeight(), Orientation.Rotator(), DrawDebugType,
|
||||||
|
bHasBlockingHit, Hits, DrawDebugColor, DrawDebugHitColor, DrawDebugTime);
|
||||||
|
break;
|
||||||
|
case ECollisionShape::Box:
|
||||||
|
DrawDebugBoxTraceMulti(World, StartLocation, EndLocation, CollisionShape.GetBox(), Orientation.Rotator(), DrawDebugType, bHasBlockingHit, Hits, DrawDebugColor, DrawDebugHitColor,
|
||||||
|
DrawDebugTime);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
case ECollisionShape::Line:
|
||||||
|
DrawDebugLineTraceMulti(World, StartLocation, EndLocation, DrawDebugType, bHasBlockingHit, Hits, DrawDebugColor, DrawDebugHitColor, DrawDebugTime);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#undef LOCTEXT_NAMESPACE
|
||||||
@@ -0,0 +1,489 @@
|
|||||||
|
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||||
|
|
||||||
|
#include "Collision/GCS_TraceSystemComponent.h"
|
||||||
|
#include "GCS_LogChannels.h"
|
||||||
|
#include "Collision/DEPRECATED_GCS_CollisionTraceInstance.h"
|
||||||
|
#include "Collision/GCS_TraceSubsystem.h"
|
||||||
|
#include "Components/SkeletalMeshComponent.h"
|
||||||
|
#include "Components/PrimitiveComponent.h"
|
||||||
|
#include "Utility/GCS_CombatFunctionLibrary.h"
|
||||||
|
|
||||||
|
UGCS_TraceSystemComponent::UGCS_TraceSystemComponent(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer)
|
||||||
|
{
|
||||||
|
PrimaryComponentTick.bCanEverTick = false;
|
||||||
|
SetIsReplicatedByDefault(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
UGCS_TraceSystemComponent* UGCS_TraceSystemComponent::GetTraceSystemComponent(const AActor* Actor)
|
||||||
|
{
|
||||||
|
return IsValid(Actor) ? Actor->FindComponentByClass<UGCS_TraceSystemComponent>() : nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UGCS_TraceSystemComponent::FindTraceSystemComponent(const AActor* Actor, UGCS_TraceSystemComponent*& Component)
|
||||||
|
{
|
||||||
|
Component = GetTraceSystemComponent(Actor);
|
||||||
|
return Component != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UGCS_TraceSystemComponent::OnInitialize_Implementation()
|
||||||
|
{
|
||||||
|
if (UMeshComponent* PrimitiveComp = UGCS_CombatFunctionLibrary::GetMainMeshComponent(GetOwner()))
|
||||||
|
{
|
||||||
|
AddTraces(TraceDefinitions, PrimitiveComp, GetOwner());
|
||||||
|
}
|
||||||
|
|
||||||
|
bInitialized = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UGCS_TraceSystemComponent::OnDeinitialize_Implementation()
|
||||||
|
{
|
||||||
|
if (bInitialized)
|
||||||
|
{
|
||||||
|
RemoveAllTraces();
|
||||||
|
bInitialized = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called when the game starts
|
||||||
|
void UGCS_TraceSystemComponent::BeginPlay()
|
||||||
|
{
|
||||||
|
if (bAutoInitialize)
|
||||||
|
{
|
||||||
|
OnInitialize();
|
||||||
|
}
|
||||||
|
Super::BeginPlay();
|
||||||
|
}
|
||||||
|
|
||||||
|
void UGCS_TraceSystemComponent::EndPlay(const EEndPlayReason::Type EndPlayReason)
|
||||||
|
{
|
||||||
|
Super::EndPlay(EndPlayReason);
|
||||||
|
OnDeinitialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
void UGCS_TraceSystemComponent::OnComponentDestroyed(bool bDestroyingHierarchy)
|
||||||
|
{
|
||||||
|
Super::OnComponentDestroyed(bDestroyingHierarchy);
|
||||||
|
OnDestroyedEvent.Broadcast();
|
||||||
|
|
||||||
|
OnDeinitialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
TArray<FGCS_TraceHandle> UGCS_TraceSystemComponent::AddTraces(const TArray<FGCS_TraceDefinition>& Definitions, UPrimitiveComponent* SourceComponent, UObject* SourceObject)
|
||||||
|
{
|
||||||
|
TArray<FGCS_TraceHandle> Handles;
|
||||||
|
for (const FGCS_TraceDefinition& Def : Definitions)
|
||||||
|
{
|
||||||
|
FGCS_TraceHandle Handle = AddTrace(Def, SourceComponent, SourceObject);
|
||||||
|
if (Handle.IsValidHandle())
|
||||||
|
{
|
||||||
|
Handles.Add(Handle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Handles;
|
||||||
|
}
|
||||||
|
|
||||||
|
TArray<FGCS_TraceHandle> UGCS_TraceSystemComponent::AddTraces(const TArray<FDataTableRowHandle>& DefinitionHandles, UPrimitiveComponent* SourceComponent, UObject* SourceObject)
|
||||||
|
{
|
||||||
|
TArray<FGCS_TraceHandle> Handles;
|
||||||
|
for (const FDataTableRowHandle& DefHandle : DefinitionHandles)
|
||||||
|
{
|
||||||
|
FGCS_TraceHandle Handle = AddTrace(DefHandle, SourceComponent, SourceObject);
|
||||||
|
if (Handle.IsValidHandle())
|
||||||
|
{
|
||||||
|
Handles.Add(Handle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Handles;
|
||||||
|
}
|
||||||
|
|
||||||
|
FGCS_TraceHandle UGCS_TraceSystemComponent::AddTrace(const FGCS_TraceDefinition& TraceDefinition, UPrimitiveComponent* SourceComponent, UObject* SourceObject)
|
||||||
|
{
|
||||||
|
if (!TraceDefinition.IsValidDefinition())
|
||||||
|
{
|
||||||
|
GCS_CLOG(Warning, "Try adding invalid trace definition!")
|
||||||
|
return FGCS_TraceHandle();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!IsValid(SourceObject))
|
||||||
|
{
|
||||||
|
GCS_CLOG(Warning, "Missing source object for trace definition:%s", *TraceDefinition.ToString())
|
||||||
|
return FGCS_TraceHandle();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!IsValid(SourceComponent))
|
||||||
|
{
|
||||||
|
GCS_CLOG(Warning, "Missing source component for trace definition:%s", *TraceDefinition.ToString())
|
||||||
|
return FGCS_TraceHandle();
|
||||||
|
}
|
||||||
|
|
||||||
|
FInstancedStruct Shape = TraceDefinition.CollisionShape;
|
||||||
|
bool bWasInitialized = Shape.GetMutable<FGCS_CollisionShape>().InitializeShape(SourceComponent);
|
||||||
|
|
||||||
|
if (!bWasInitialized)
|
||||||
|
{
|
||||||
|
return FGCS_TraceHandle();
|
||||||
|
}
|
||||||
|
|
||||||
|
UWorld* World = GetWorld();
|
||||||
|
if (!IsValid(World))
|
||||||
|
{
|
||||||
|
GCS_CLOG(Error, "Invalid world context!")
|
||||||
|
return FGCS_TraceHandle();
|
||||||
|
}
|
||||||
|
|
||||||
|
UGCS_TraceSubsystem* Subsystem = World->GetSubsystem<UGCS_TraceSubsystem>();
|
||||||
|
if (!IsValid(Subsystem))
|
||||||
|
{
|
||||||
|
GCS_CLOG(Error, "Failed to get TraceSubsystem!")
|
||||||
|
return FGCS_TraceHandle();
|
||||||
|
}
|
||||||
|
int32 StateIdx = Subsystem->AddTraceState();
|
||||||
|
|
||||||
|
FGCS_TraceHandle Handle = {TraceDefinition.TraceTag, FGuid::NewGuid(), SourceObject};
|
||||||
|
|
||||||
|
float TickInterval = 0;
|
||||||
|
float AngleThreshold = TraceDefinition.AngleTickThreshold;
|
||||||
|
if (TraceDefinition.TraceTickType == EGCS_TraceTickType::DistanceBased)
|
||||||
|
{
|
||||||
|
TickInterval = TraceDefinition.DistanceTickThreshold;
|
||||||
|
}
|
||||||
|
else if (TraceDefinition.TraceTickType == EGCS_TraceTickType::FixedFrameRate)
|
||||||
|
{
|
||||||
|
TickInterval = 1.0f / static_cast<float>(TraceDefinition.FixedTickFrameRate);
|
||||||
|
}
|
||||||
|
auto& TraceState = Subsystem->GetTraceStateAt(StateIdx);
|
||||||
|
TraceState.World = GetWorld();
|
||||||
|
TraceState.SourceComponent = SourceComponent;
|
||||||
|
TraceState.OwningSystem = this;
|
||||||
|
|
||||||
|
TraceState.SweepSetting = TraceDefinition.SweepSetting;
|
||||||
|
TraceState.Shape = Shape;
|
||||||
|
|
||||||
|
TraceState.Handle = Handle;
|
||||||
|
TraceState.ExecutionState = EGCS_TraceExecutionState::Stopped;
|
||||||
|
TraceState.TimeSinceLastTick = 0;
|
||||||
|
TraceState.TimeSinceActive = 0;
|
||||||
|
TraceState.TickPolicy = TraceDefinition.TraceTickType;
|
||||||
|
TraceState.TickInterval = TickInterval;
|
||||||
|
TraceState.AngleThreshold = AngleThreshold;
|
||||||
|
TraceState.bShouldTickThisFrame = false;
|
||||||
|
if (AActor* Owner = GetOwner())
|
||||||
|
{
|
||||||
|
TraceState.CollisionParams.AddIgnoredActor(Owner);
|
||||||
|
}
|
||||||
|
TraceState.CollisionParams.bTraceComplex = TraceDefinition.SweepSetting.bTraceComplex;
|
||||||
|
TraceState.CollisionParams.bReturnPhysicalMaterial = true;
|
||||||
|
for (const auto& ObjType : TraceDefinition.SweepSetting.ObjectTypes)
|
||||||
|
{
|
||||||
|
TraceState.ObjectQueryParams.AddObjectTypesToQuery(ObjType);
|
||||||
|
}
|
||||||
|
|
||||||
|
HandleToStateIdx.Add(Handle, StateIdx);
|
||||||
|
TagToHandles.Add(TraceDefinition.TraceTag, Handle);
|
||||||
|
GCS_CLOG(Verbose, "Added trace(%s) with source component:%s", *Handle.ToDebugString(), *GetNameSafe(SourceComponent))
|
||||||
|
return Handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
FGCS_TraceHandle UGCS_TraceSystemComponent::AddTrace(const FDataTableRowHandle& TraceDefinitionHandle, UPrimitiveComponent* SourceComponent, UObject* SourceObject)
|
||||||
|
{
|
||||||
|
if (!TraceDefinitionHandle.IsNull())
|
||||||
|
{
|
||||||
|
if (FGCS_TraceDefinition* TraceDefinition = TraceDefinitionHandle.GetRow<FGCS_TraceDefinition>(TEXT("AddTrace")))
|
||||||
|
{
|
||||||
|
AddTrace(*TraceDefinition, SourceComponent, SourceObject);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
GCS_CLOG(Warning, "definition handle doesn't point to valid TraceDefinition Table. %s", *TraceDefinitionHandle.ToDebugString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
GCS_CLOG(Warning, "Passed in invalid trace definition handle! %s", *TraceDefinitionHandle.ToDebugString())
|
||||||
|
return FGCS_TraceHandle();
|
||||||
|
}
|
||||||
|
|
||||||
|
void UGCS_TraceSystemComponent::RemoveTraces(const TArray<FGCS_TraceHandle>& TraceHandles)
|
||||||
|
{
|
||||||
|
for (const FGCS_TraceHandle& TraceHandle : TraceHandles)
|
||||||
|
{
|
||||||
|
RemoveTrace(TraceHandle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UGCS_TraceSystemComponent::RemoveTrace(const FGCS_TraceHandle& TraceHandle)
|
||||||
|
{
|
||||||
|
if (TraceHandle.IsValidHandle())
|
||||||
|
{
|
||||||
|
if (const int32* StateIdx = HandleToStateIdx.Find(TraceHandle))
|
||||||
|
{
|
||||||
|
UGCS_TraceSubsystem* Subsystem = GetWorld()->GetSubsystem<UGCS_TraceSubsystem>();
|
||||||
|
if (IsValid(Subsystem) && Subsystem->IsValidStateIdx(*StateIdx))
|
||||||
|
{
|
||||||
|
auto& State = Subsystem->GetTraceStateAt(*StateIdx);
|
||||||
|
State.ChangeExecutionState(false);
|
||||||
|
OnTraceStateChanged(TraceHandle, false);
|
||||||
|
Subsystem->RemoveTraceState(*StateIdx, TraceHandle.Guid);
|
||||||
|
GCS_CLOG(Verbose, "Removed trace(%s)", *TraceHandle.ToDebugString())
|
||||||
|
}
|
||||||
|
HandleToStateIdx.Remove(TraceHandle);
|
||||||
|
TagToHandles.RemoveSingle(TraceHandle.TraceTag, TraceHandle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TArray<FGCS_TraceHandle> UGCS_TraceSystemComponent::GetTraceHandlesBySource(const UObject* SourceObject) const
|
||||||
|
{
|
||||||
|
TArray<FGCS_TraceHandle> Handles;
|
||||||
|
for (const auto& Pair : HandleToStateIdx)
|
||||||
|
{
|
||||||
|
if (Pair.Key.SourceObject == SourceObject)
|
||||||
|
{
|
||||||
|
Handles.Add(Pair.Key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Handles;
|
||||||
|
}
|
||||||
|
|
||||||
|
TArray<FGCS_TraceHandle> UGCS_TraceSystemComponent::GetTraceHandlesByTagsAndSource(const FGameplayTagContainer& TraceTags, const UObject* SourceObject) const
|
||||||
|
{
|
||||||
|
TArray<FGCS_TraceHandle> Handles;
|
||||||
|
for (const FGameplayTag& Tag : TraceTags)
|
||||||
|
{
|
||||||
|
TArray<FGCS_TraceHandle> TagHandles;
|
||||||
|
TagToHandles.MultiFind(Tag, TagHandles);
|
||||||
|
for (const FGCS_TraceHandle& Handle : TagHandles)
|
||||||
|
{
|
||||||
|
if (!IsValid(SourceObject) || Handle.SourceObject == SourceObject)
|
||||||
|
{
|
||||||
|
Handles.AddUnique(Handle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Handles;
|
||||||
|
}
|
||||||
|
|
||||||
|
TArray<FGCS_TraceHandle> UGCS_TraceSystemComponent::StartTraces(const FGameplayTagContainer& TraceTags, const UObject* SourceObject)
|
||||||
|
{
|
||||||
|
TArray<FGCS_TraceHandle> Handles = GetTraceHandlesByTagsAndSource(TraceTags, SourceObject);
|
||||||
|
for (const FGCS_TraceHandle& Handle : Handles)
|
||||||
|
{
|
||||||
|
StartTrace(Handle);
|
||||||
|
}
|
||||||
|
return Handles;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UGCS_TraceSystemComponent::StartTraces(const TArray<FGCS_TraceHandle>& TraceHandles)
|
||||||
|
{
|
||||||
|
for (const FGCS_TraceHandle& Handle : TraceHandles)
|
||||||
|
{
|
||||||
|
StartTrace(Handle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UGCS_TraceSystemComponent::StartTrace(const FGCS_TraceHandle& TraceHandle)
|
||||||
|
{
|
||||||
|
if (TraceHandle.IsValidHandle())
|
||||||
|
{
|
||||||
|
if (const int32* StateIdx = HandleToStateIdx.Find(TraceHandle))
|
||||||
|
{
|
||||||
|
auto& State = GetWorld()->GetSubsystem<UGCS_TraceSubsystem>()->GetTraceStateAt(*StateIdx);
|
||||||
|
GCS_CLOG(Verbose, "Started trace(%s).", *TraceHandle.ToDebugString())
|
||||||
|
State.ChangeExecutionState(true);
|
||||||
|
OnTraceStateChanged(TraceHandle, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UGCS_TraceSystemComponent::StopTraces(const TArray<FGCS_TraceHandle>& TraceHandles)
|
||||||
|
{
|
||||||
|
UGCS_TraceSubsystem* Subsystem = GetWorld()->GetSubsystem<UGCS_TraceSubsystem>();
|
||||||
|
for (const FGCS_TraceHandle& TraceHandle : TraceHandles)
|
||||||
|
{
|
||||||
|
if (TraceHandle.IsValidHandle())
|
||||||
|
{
|
||||||
|
if (const int32* StateIdx = HandleToStateIdx.Find(TraceHandle))
|
||||||
|
{
|
||||||
|
auto& State = Subsystem->GetTraceStateAt(*StateIdx);
|
||||||
|
GCS_CLOG(Verbose, "Stopped trace(%s).", *TraceHandle.ToDebugString())
|
||||||
|
State.ChangeExecutionState(false);
|
||||||
|
OnTraceStateChanged(TraceHandle, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UGCS_TraceSystemComponent::StopTrace(const FGCS_TraceHandle& TraceHandle)
|
||||||
|
{
|
||||||
|
if (TraceHandle.IsValidHandle())
|
||||||
|
{
|
||||||
|
if (const int32* StateIdx = HandleToStateIdx.Find(TraceHandle))
|
||||||
|
{
|
||||||
|
auto& State = GetWorld()->GetSubsystem<UGCS_TraceSubsystem>()->GetTraceStateAt(*StateIdx);
|
||||||
|
State.ChangeExecutionState(false);
|
||||||
|
OnTraceStateChanged(TraceHandle, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TArray<FGCS_TraceHandle> UGCS_TraceSystemComponent::AddTracesByDefinitions(const TArray<FGCS_TraceDefinition>& Definitions, UPrimitiveComponent* SourceComponent,
|
||||||
|
UObject* SourceObject)
|
||||||
|
{
|
||||||
|
return AddTraces(Definitions, SourceComponent, SourceObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
TArray<FGCS_TraceHandle> UGCS_TraceSystemComponent::AddTracesByDefinitionHandles(const TArray<FDataTableRowHandle>& DefinitionHandles, UPrimitiveComponent* SourceComponent,
|
||||||
|
UObject* SourceObject)
|
||||||
|
{
|
||||||
|
return AddTraces(DefinitionHandles, SourceComponent, SourceObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
FGCS_TraceHandle UGCS_TraceSystemComponent::AddTraceByDefinition(const FGCS_TraceDefinition& Definition, UPrimitiveComponent* SourceComponent, UObject* SourceObject)
|
||||||
|
{
|
||||||
|
return AddTrace(Definition, SourceComponent, SourceObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
TArray<FGCS_TraceHandle> UGCS_TraceSystemComponent::StartTracesByTagsAndSource(const FGameplayTagContainer& TraceTags, const UObject* SourceObject)
|
||||||
|
{
|
||||||
|
return StartTraces(TraceTags, SourceObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UGCS_TraceSystemComponent::StartTracesByHandles(const TArray<FGCS_TraceHandle>& TraceHandles)
|
||||||
|
{
|
||||||
|
return StartTraces(TraceHandles);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UGCS_TraceSystemComponent::StartTraceByHandle(const FGCS_TraceHandle& TraceHandle)
|
||||||
|
{
|
||||||
|
return StartTrace(TraceHandle);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UGCS_TraceSystemComponent::StopTracesByHandles(const TArray<FGCS_TraceHandle>& TraceHandles)
|
||||||
|
{
|
||||||
|
StopTraces(TraceHandles);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UGCS_TraceSystemComponent::StopTraceByHandle(const FGCS_TraceHandle& TraceHandle)
|
||||||
|
{
|
||||||
|
StopTrace(TraceHandle);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UGCS_TraceSystemComponent::RemoveTraceByHandle(const FGCS_TraceHandle& TraceHandle)
|
||||||
|
{
|
||||||
|
RemoveTrace(TraceHandle);
|
||||||
|
}
|
||||||
|
|
||||||
|
TArray<FGCS_TraceHandle> UGCS_TraceSystemComponent::GetTraceHandlesByTag(FGameplayTag TraceToFind) const
|
||||||
|
{
|
||||||
|
TArray<FGCS_TraceHandle> Handles;
|
||||||
|
TagToHandles.MultiFind(TraceToFind, Handles);
|
||||||
|
return Handles;
|
||||||
|
}
|
||||||
|
|
||||||
|
AActor* UGCS_TraceSystemComponent::GetTraceSourceActor(const FGCS_TraceHandle& TraceHandle) const
|
||||||
|
{
|
||||||
|
if (UPrimitiveComponent* SourceComponent = GetTraceSourceComponent(TraceHandle))
|
||||||
|
{
|
||||||
|
return SourceComponent->GetOwner();
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
UPrimitiveComponent* UGCS_TraceSystemComponent::GetTraceSourceComponent(const FGCS_TraceHandle& TraceHandle) const
|
||||||
|
{
|
||||||
|
if (TraceHandle.IsValidHandle())
|
||||||
|
{
|
||||||
|
if (const int32* StateIdx = HandleToStateIdx.Find(TraceHandle))
|
||||||
|
{
|
||||||
|
FGCS_TraceState& State = GetWorld()->GetSubsystem<UGCS_TraceSubsystem>()->GetTraceStateAt(*StateIdx);
|
||||||
|
return State.SourceComponent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UGCS_TraceSystemComponent::RemoveAllTraces()
|
||||||
|
{
|
||||||
|
UWorld* World = GetWorld();
|
||||||
|
if (!IsValid(World))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
UGCS_TraceSubsystem* Subsystem = World->GetSubsystem<UGCS_TraceSubsystem>();
|
||||||
|
if (!IsValid(Subsystem))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 新增:收集所有要移除的Idx和Guid,避免边遍历边修改
|
||||||
|
TArray<TPair<int32, FGuid>> ToRemove;
|
||||||
|
for (const TPair<FGCS_TraceHandle, int32>& Pair : HandleToStateIdx)
|
||||||
|
{
|
||||||
|
if (Pair.Key.IsValidHandle())
|
||||||
|
{
|
||||||
|
if (Subsystem->IsValidStateIdx(Pair.Value)) // 额外验证
|
||||||
|
{
|
||||||
|
const FGCS_TraceState& State = Subsystem->GetTraceStateAt(Pair.Value);
|
||||||
|
ToRemove.Add(TPair<int32, FGuid>(Pair.Value, Pair.Key.Guid));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 先停止所有状态
|
||||||
|
for (const auto& Pending : ToRemove)
|
||||||
|
{
|
||||||
|
if (Subsystem->IsValidStateIdx(Pending.Key))
|
||||||
|
{
|
||||||
|
auto& State = Subsystem->GetTraceStateAt(Pending.Key);
|
||||||
|
State.ChangeExecutionState(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 然后批量移除
|
||||||
|
for (const auto& Pending : ToRemove)
|
||||||
|
{
|
||||||
|
Subsystem->RemoveTraceState(Pending.Key, Pending.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清空映射
|
||||||
|
for (const TPair<FGCS_TraceHandle, int32>& Pair : HandleToStateIdx)
|
||||||
|
{
|
||||||
|
TagToHandles.RemoveSingle(Pair.Key.TraceTag, Pair.Key);
|
||||||
|
}
|
||||||
|
HandleToStateIdx.Empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UGCS_TraceSystemComponent::IsTraceActive(const FGCS_TraceHandle& TraceHandle) const
|
||||||
|
{
|
||||||
|
if (const int32* StateIdx = HandleToStateIdx.Find(TraceHandle))
|
||||||
|
{
|
||||||
|
const FGCS_TraceState& State = GetWorld()->GetSubsystem<UGCS_TraceSubsystem>()->GetTraceStateAt(*StateIdx);
|
||||||
|
return State.ExecutionState == EGCS_TraceExecutionState::InProgress;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UGCS_TraceSystemComponent::OnTraceHitDetected(const FGCS_TraceHandle& TraceHandle, const TArray<FHitResult>& HitResults, const float DeltaTime, const uint32 TickIdx)
|
||||||
|
{
|
||||||
|
DECLARE_SCOPE_CYCLE_COUNTER(TEXT("UGCS_TraceSystemComponent::OnTraceHitDetected"), UGCS_TraceSystemComponent_OnTraceHitDetected, STATGROUP_GCS)
|
||||||
|
|
||||||
|
FScopeLock ScopedLock(&TraceDoneScopeLock);
|
||||||
|
|
||||||
|
for (const FHitResult& HitResult : HitResults)
|
||||||
|
{
|
||||||
|
// GCS_CLOG(Verbose, "Trace(%s) hit:%s at location(%s)", *TraceHandle.ToDebugString(), *GetNameSafe(HitResult.GetActor()), *HitResult.Location.ToString())
|
||||||
|
GCS_CLOG(Verbose, "Trace(%s) hit:%s", *TraceHandle.ToDebugString(), *HitResult.ToString())
|
||||||
|
OnTraceHitEvent.Broadcast(TraceHandle, HitResult);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UGCS_TraceSystemComponent::OnTraceStateChanged(const FGCS_TraceHandle& TraceHandle, bool bNewState)
|
||||||
|
{
|
||||||
|
if (bNewState)
|
||||||
|
{
|
||||||
|
OnTraceStartedEvent.Broadcast(TraceHandle);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
OnTraceStoppedEvent.Broadcast(TraceHandle);
|
||||||
|
}
|
||||||
|
OnTraceStateChangedEvent.Broadcast(TraceHandle, bNewState);
|
||||||
|
}
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||||
|
|
||||||
|
|
||||||
|
#include "CombatFlow/GCS_AbilityActionSetSettings.h"
|
||||||
|
|
||||||
|
bool UGCS_AbilityActionSetSettings::SelectBestAbilityActions(const FGameplayTagContainer& SourceTags, const FGameplayTagContainer& TargetTags, const FGameplayTagContainer& AbilityTags,
|
||||||
|
TArray<FGCS_AbilityAction>& Actions) const
|
||||||
|
{
|
||||||
|
FGCS_AbilityActionSet ActionSet;
|
||||||
|
bool bFound = false;
|
||||||
|
|
||||||
|
for (int32 i = 0; i < ActionSets.Num(); i++)
|
||||||
|
{
|
||||||
|
if (ActionSets[i].AbilityTag.IsValid() && ActionSets[i].AbilityTag.MatchesAny(AbilityTags))
|
||||||
|
{
|
||||||
|
ActionSet = ActionSets[i];
|
||||||
|
bFound = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!bFound)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// try finds in layers.
|
||||||
|
|
||||||
|
for (int32 i = 0; i < ActionSet.Layered.Num(); i++)
|
||||||
|
{
|
||||||
|
bool bMatchingSource = ActionSet.Layered[i].SourceTagQuery.IsEmpty() || ActionSet.Layered[i].SourceTagQuery.Matches(SourceTags);
|
||||||
|
bool bMatchingTarget = ActionSet.Layered[i].TargetTagQuery.IsEmpty() || ActionSet.Layered[i].TargetTagQuery.Matches(TargetTags);
|
||||||
|
if (bMatchingSource && bMatchingTarget)
|
||||||
|
{
|
||||||
|
Actions = ActionSet.Layered[i].Actions;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// falback to default.
|
||||||
|
Actions = ActionSet.Actions;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if WITH_EDITORONLY_DATA
|
||||||
|
#include "UObject/ObjectSaveContext.h"
|
||||||
|
|
||||||
|
void UGCS_AbilityActionSetSettings::PreSave(FObjectPreSaveContext SaveContext)
|
||||||
|
{
|
||||||
|
Super::PreSave(SaveContext);
|
||||||
|
|
||||||
|
for (FGCS_AbilityActionSet& ActionSet : ActionSets)
|
||||||
|
{
|
||||||
|
for (FGCS_AbilityAction& Action : ActionSet.Actions)
|
||||||
|
{
|
||||||
|
Action.EditorFriendlyName = Action.Animation != nullptr ? Action.Animation.GetName() : TEXT("Empty Action");
|
||||||
|
}
|
||||||
|
for (FGCS_AbilityActionsWithQuery& Layered : ActionSet.Layered)
|
||||||
|
{
|
||||||
|
Layered.EditorFriendlyName = FString::Format(TEXT("Source:({0}) Target({1})"), {
|
||||||
|
Layered.SourceTagQuery.IsEmpty() ? TEXT("Empty") : Layered.SourceTagQuery.GetDescription(),
|
||||||
|
Layered.TargetTagQuery.IsEmpty() ? TEXT("Empty") : Layered.TargetTagQuery.GetDescription()
|
||||||
|
});
|
||||||
|
for (FGCS_AbilityAction& Action : Layered.Actions)
|
||||||
|
{
|
||||||
|
Action.EditorFriendlyName = Action.Animation != nullptr ? Action.Animation.GetName() : TEXT("Empty Action");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||||
|
|
||||||
|
|
||||||
|
#include "CombatFlow/GCS_AttackDefinition.h"
|
||||||
@@ -0,0 +1,189 @@
|
|||||||
|
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||||
|
|
||||||
|
|
||||||
|
#include "CombatFlow/GCS_AttackRequest.h"
|
||||||
|
#include "Engine/DataTable.h"
|
||||||
|
#include "GCS_CombatEntityInterface.h"
|
||||||
|
#include "GameFramework/Character.h"
|
||||||
|
#include "Components/SkeletalMeshComponent.h"
|
||||||
|
#include "Components/PrimitiveComponent.h"
|
||||||
|
#include "GameFramework/PlayerController.h"
|
||||||
|
#include "Utility/GCS_CombatFunctionLibrary.h"
|
||||||
|
#include "Weapon/GCS_WeaponInterface.h"
|
||||||
|
|
||||||
|
FDataTableRowHandle UGCS_AttackRequest_Base::GetAttackDefinitionHandle_Implementation() const
|
||||||
|
{
|
||||||
|
return FDataTableRowHandle();
|
||||||
|
}
|
||||||
|
|
||||||
|
FGCS_AttackDefinition UGCS_AttackRequest_Base::GetAttackDefinition() const
|
||||||
|
{
|
||||||
|
if (const FGCS_AttackDefinition* Def = GetAttackDefinitionHandle().GetRow<FGCS_AttackDefinition>(TEXT("Get Attack Definition from AttackRequest")))
|
||||||
|
{
|
||||||
|
return *Def;
|
||||||
|
}
|
||||||
|
return FGCS_AttackDefinition();
|
||||||
|
}
|
||||||
|
|
||||||
|
FDataTableRowHandle UGCS_AttackRequest_Melee::GetAttackDefinitionHandle_Implementation() const
|
||||||
|
{
|
||||||
|
return AttackDefinitionHandle;
|
||||||
|
}
|
||||||
|
|
||||||
|
FGCS_BulletDefinition UGCS_AttackRequest_Bullet::GetBulletDefinition() const
|
||||||
|
{
|
||||||
|
if (auto Def = BulletDefinitionHandle.GetRow<FGCS_BulletDefinition>(TEXT("Get Bullet Definition from AttackRequest_Bullet")))
|
||||||
|
{
|
||||||
|
return *Def;
|
||||||
|
}
|
||||||
|
return FGCS_BulletDefinition();
|
||||||
|
}
|
||||||
|
|
||||||
|
FDataTableRowHandle UGCS_AttackRequest_Bullet::GetAttackDefinitionHandle_Implementation() const
|
||||||
|
{
|
||||||
|
return GetBulletDefinition().AttackDefinition;
|
||||||
|
}
|
||||||
|
|
||||||
|
FVector UGCS_AttackRequest_Bullet::GetPawnTargetingSourceLocation_Implementation(APawn* SourcePawn) const
|
||||||
|
{
|
||||||
|
check(SourcePawn);
|
||||||
|
|
||||||
|
FVector RetLocation = SourcePawn->GetActorLocation();
|
||||||
|
if (SourceSocketName != NAME_None)
|
||||||
|
{
|
||||||
|
if (UMeshComponent* Mesh = UGCS_CombatFunctionLibrary::GetMainMeshComponent(SourcePawn))
|
||||||
|
{
|
||||||
|
if (Mesh->DoesSocketExist(SourceSocketName))
|
||||||
|
{
|
||||||
|
RetLocation = Mesh->GetSocketLocation(SourceSocketName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ACharacter* Char = Cast<ACharacter>(SourcePawn))
|
||||||
|
{
|
||||||
|
if (Char->GetMesh() && Char->GetMesh()->DoesSocketExist(SourceSocketName))
|
||||||
|
{
|
||||||
|
RetLocation = Char->GetMesh()->GetSocketLocation(SourceSocketName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FVector LocalOffsetInWorld = SourcePawn->GetActorTransform().TransformVector(LocationOffset);
|
||||||
|
return RetLocation + LocalOffsetInWorld;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
FVector UGCS_AttackRequest_Bullet::GetWeaponTargetingSourceLocation_Implementation(APawn* SourcePawn) const
|
||||||
|
{
|
||||||
|
check(SourcePawn)
|
||||||
|
|
||||||
|
FVector RetLocation = SourcePawn->GetActorLocation();
|
||||||
|
|
||||||
|
if (UObject* CombatImplementer = UGCS_CombatFunctionLibrary::GetCombatEntity(SourcePawn))
|
||||||
|
{
|
||||||
|
if (UObject* WeaponImplementer = IGCS_CombatEntityInterface::Execute_GetCurrentWeapon(CombatImplementer, nullptr))
|
||||||
|
{
|
||||||
|
if (UPrimitiveComponent* PrimitiveComponent = IGCS_WeaponInterface::Execute_GetPrimitiveComponent(WeaponImplementer))
|
||||||
|
{
|
||||||
|
if (SourceWeaponSocketName != NAME_None && PrimitiveComponent->DoesSocketExist(SourceWeaponSocketName))
|
||||||
|
{
|
||||||
|
RetLocation = PrimitiveComponent->GetSocketLocation(SourceWeaponSocketName);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
RetLocation = PrimitiveComponent->GetOwner()->GetActorLocation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FVector LocalOffsetInWorld = SourcePawn->GetActorTransform().TransformVector(LocationOffset);
|
||||||
|
return RetLocation + LocalOffsetInWorld;
|
||||||
|
}
|
||||||
|
|
||||||
|
FTransform UGCS_AttackRequest_Bullet::GetTargetingTransform_Implementation(APawn* SourcePawn, EGCS_AbilityTargetingSourceType Source) const
|
||||||
|
{
|
||||||
|
check(SourcePawn);
|
||||||
|
|
||||||
|
// The caller should determine the transform without calling this if the mode is custom!
|
||||||
|
check(Source != EGCS_AbilityTargetingSourceType::Custom);
|
||||||
|
|
||||||
|
|
||||||
|
static double FocalDistance = 1024.0f;
|
||||||
|
FVector FocalLoc;
|
||||||
|
|
||||||
|
bool bFocalBased = SourcePawn->Controller != nullptr && (Source == EGCS_AbilityTargetingSourceType::CameraTowardsFocus || Source == EGCS_AbilityTargetingSourceType::PawnTowardsFocus || Source ==
|
||||||
|
EGCS_AbilityTargetingSourceType::WeaponTowardsFocus);
|
||||||
|
|
||||||
|
if (bFocalBased)
|
||||||
|
{
|
||||||
|
APlayerController* PC = Cast<APlayerController>(SourcePawn->Controller);
|
||||||
|
FVector CamLoc;
|
||||||
|
FRotator CamRot;
|
||||||
|
|
||||||
|
if (PC != nullptr)
|
||||||
|
{
|
||||||
|
PC->GetPlayerViewPoint(CamLoc, CamRot);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
CamLoc = SourceSocketName != NAME_None ? GetPawnTargetingSourceLocation(SourcePawn) : SourcePawn->GetActorLocation() + FVector(0, 0, SourcePawn->BaseEyeHeight);
|
||||||
|
CamRot = SourcePawn->Controller->GetControlRotation();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine initial focal point to
|
||||||
|
FVector AimDir = CamRot.Vector().GetSafeNormal();
|
||||||
|
FocalLoc = CamLoc + (AimDir * FocalDistance);
|
||||||
|
|
||||||
|
// Move the start and focal point up in front of pawn
|
||||||
|
// if (PC)
|
||||||
|
// {
|
||||||
|
// const FVector WeaponLoc = GetWeaponTargetingSourceLocation(SourcePawn);
|
||||||
|
// CamLoc = FocalLoc + (((WeaponLoc - FocalLoc) | AimDir) * AimDir);
|
||||||
|
// FocalLoc = CamLoc + (AimDir * FocalDistance);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// valid camera and want camera's location
|
||||||
|
if (Source == EGCS_AbilityTargetingSourceType::CameraTowardsFocus)
|
||||||
|
{
|
||||||
|
// If we're camera -> focus then we're done
|
||||||
|
return FTransform(CamRot, CamLoc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// valid camera nd want pawn's location.
|
||||||
|
if (bFocalBased && Source == EGCS_AbilityTargetingSourceType::PawnTowardsFocus)
|
||||||
|
{
|
||||||
|
FVector SourceLoc = GetPawnTargetingSourceLocation(SourcePawn);
|
||||||
|
|
||||||
|
// Return a rotator pointing at the focal point from the source
|
||||||
|
return FTransform((FocalLoc - SourceLoc).Rotation(), SourceLoc);
|
||||||
|
}
|
||||||
|
|
||||||
|
// valid camera and want weapon's location.
|
||||||
|
if (bFocalBased && Source == EGCS_AbilityTargetingSourceType::WeaponTowardsFocus)
|
||||||
|
{
|
||||||
|
FVector SourceLoc = GetWeaponTargetingSourceLocation(SourcePawn);
|
||||||
|
|
||||||
|
// Return a rotator pointing at the focal point from the source
|
||||||
|
return FTransform((FocalLoc - SourceLoc).Rotation(), SourceLoc);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Either we want the weapon's location, or we failed to find a camera
|
||||||
|
if (Source == EGCS_AbilityTargetingSourceType::WeaponForward || Source == EGCS_AbilityTargetingSourceType::WeaponTowardsFocus)
|
||||||
|
{
|
||||||
|
FVector SourceLoc = GetWeaponTargetingSourceLocation(SourcePawn);
|
||||||
|
return FTransform(SourcePawn->GetActorQuat(), SourceLoc);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Either we want the pawn's location, or we failed to find a camera
|
||||||
|
if (Source == EGCS_AbilityTargetingSourceType::PawnForward || Source == EGCS_AbilityTargetingSourceType::PawnTowardsFocus)
|
||||||
|
{
|
||||||
|
// Either we want the pawn's location, or we failed to find a camera
|
||||||
|
FVector SourceLoc = GetPawnTargetingSourceLocation(SourcePawn);
|
||||||
|
return FTransform(SourcePawn->GetActorQuat(), SourceLoc);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we got here, either we don't have a camera or we don't want to use it, either way go forward
|
||||||
|
return FTransform(SourcePawn->GetActorQuat(), SourcePawn->GetActorLocation());
|
||||||
|
}
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||||
|
|
||||||
|
|
||||||
|
#include "CombatFlow/GCS_AttackResult.h"
|
||||||
|
|
||||||
|
#include "GameplayEffect.h"
|
||||||
|
#include "GCS_LogChannels.h"
|
||||||
|
#include "GCS_CombatSystemComponent.h"
|
||||||
|
#include "CombatFlow/GCS_CombatFlow.h"
|
||||||
|
#include "Utility/GCS_CombatFunctionLibrary.h"
|
||||||
|
|
||||||
|
|
||||||
|
void FGCS_AttackResult::PostReplicatedAdd(const struct FGCS_AttackResultContainer& InArray)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
FGCS_AttackResultContainer::FGCS_AttackResultContainer(): CombatFlow(nullptr), CombatSystemComponent(nullptr), MaxSize(5)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
FGCS_AttackResultContainer::FGCS_AttackResultContainer(UGCS_CombatFlow* InCombatFlow, int32 InMaxSize): CombatFlow(InCombatFlow), CombatSystemComponent(nullptr), MaxSize(InMaxSize)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
FGCS_AttackResultContainer::FGCS_AttackResultContainer(UGCS_CombatSystemComponent* InCombatSystemComponent, int32 InMaxSize): CombatFlow(nullptr), CombatSystemComponent(InCombatSystemComponent),
|
||||||
|
MaxSize(InMaxSize)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void FGCS_AttackResultContainer::AddEntry(FGCS_AttackResult& NewEntry)
|
||||||
|
{
|
||||||
|
if (Results.Num() >= 5)
|
||||||
|
{
|
||||||
|
Results.RemoveAtSwap(0);
|
||||||
|
MarkArrayDirty();
|
||||||
|
}
|
||||||
|
|
||||||
|
Results.Add(NewEntry);
|
||||||
|
check(CombatSystemComponent != nullptr && CombatSystemComponent->GetOwner());
|
||||||
|
|
||||||
|
if (CombatSystemComponent->GetCombatFlow())
|
||||||
|
{
|
||||||
|
CombatSystemComponent->GetCombatFlow()->HandleAttackResult(NewEntry);
|
||||||
|
NewEntry.bConsumed = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
UE_LOG(LogGCS, Error, TEXT("Missing Combat Flow on %s's combat system component."), *CombatSystemComponent->GetOwner()->GetName());
|
||||||
|
}
|
||||||
|
|
||||||
|
MarkItemDirty(NewEntry);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FGCS_AttackResultContainer::PostReplicatedAdd(const TArrayView<int32> AddedIndices, int32 FinalSize)
|
||||||
|
{
|
||||||
|
for (int32 Index : AddedIndices)
|
||||||
|
{
|
||||||
|
FGCS_AttackResult& Entry = Results[Index];
|
||||||
|
|
||||||
|
FGCS_ContextPayload_Combat* CombatPayload = UGCS_CombatFunctionLibrary::EffectContextGetMutableCombatPayload(Results[Index].EffectContextHandle);
|
||||||
|
if (CombatPayload != nullptr && CombatPayload->PredictionKey.IsLocalClientKey())
|
||||||
|
{
|
||||||
|
checkf(!CombatPayload->bIsPredictingContext, TEXT("PredictingContext never should hit this!"))
|
||||||
|
// PredictionKey will only be valid on the client that predicted it. So if this has a valid PredictionKey, we can assume we already predicted it and shouldn't handle attack results.
|
||||||
|
if (HasPredictedResultWithPredictedKey(CombatPayload->PredictionKey))
|
||||||
|
{
|
||||||
|
GCS_LOG(Verbose, "Found already predicted attack result!")
|
||||||
|
Entry.bWasPredicated = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Entry.bWasReplicated = true;
|
||||||
|
|
||||||
|
CombatSystemComponent->GetCombatFlow()->HandleAttackResult(Entry);
|
||||||
|
|
||||||
|
Entry.bConsumed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FGCS_AttackResultContainer::PostReplicatedChange(const TArrayView<int32>& ChangedIndices, int32 FinalSize)
|
||||||
|
{
|
||||||
|
for (int32 Index : ChangedIndices)
|
||||||
|
{
|
||||||
|
if (!Results[Index].bConsumed)
|
||||||
|
{
|
||||||
|
CombatSystemComponent->GetCombatFlow()->HandleAttackResult(Results[Index]);
|
||||||
|
Results[Index].bConsumed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FGCS_AttackResultContainer::HasPredictedResultWithPredictedKey(FPredictionKey PredictionKey) const
|
||||||
|
{
|
||||||
|
for (const FGCS_AttackResult& Result : Results)
|
||||||
|
{
|
||||||
|
if (Result.bConsumed)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (const FGCS_ContextPayload_Combat* CombatPayload = UGCS_CombatFunctionLibrary::EffectContextGetMutableCombatPayload(Result.EffectContextHandle))
|
||||||
|
{
|
||||||
|
if (CombatPayload->PredictionKey.IsValidKey() && CombatPayload->PredictionKey == PredictionKey && CombatPayload->PredictionKey.WasReceived() == false)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
@@ -0,0 +1,242 @@
|
|||||||
|
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||||
|
|
||||||
|
|
||||||
|
#include "CombatFlow/GCS_AttackResultProcessor.h"
|
||||||
|
#include "AbilitySystemBlueprintLibrary.h"
|
||||||
|
#include "AbilitySystemComponent.h"
|
||||||
|
#include "AbilitySystemGlobals.h"
|
||||||
|
#include "GameplayCueFunctionLibrary.h"
|
||||||
|
#include "GameFramework/Pawn.h"
|
||||||
|
#include "AbilitySystem/GCS_GameplayEffectContext.h"
|
||||||
|
#include "CombatFlow/GCS_CombatFlow.h"
|
||||||
|
#include "UObject/ObjectSaveContext.h"
|
||||||
|
#include "Utilities/GGA_GameplayCueFunctionLibrary.h"
|
||||||
|
#include "Utility/GCS_CombatFunctionLibrary.h"
|
||||||
|
|
||||||
|
bool UGCS_AttackResultProcessor::ProcessIncomingAttackResult(const FGCS_AttackResult& AttackResult)
|
||||||
|
{
|
||||||
|
if (!AttackResult.bConsumed)
|
||||||
|
{
|
||||||
|
HandleIncomingAttackResult(AttackResult);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
EGCS_AttackResultProcessorPolicy UGCS_AttackResultProcessor::GetExecutePolicy_Implementation() const
|
||||||
|
{
|
||||||
|
return ExecutePolicy;
|
||||||
|
}
|
||||||
|
|
||||||
|
class UWorld* UGCS_AttackResultProcessor::GetWorld() const
|
||||||
|
{
|
||||||
|
if (AActor* OwningActor = GetOwningActor())
|
||||||
|
{
|
||||||
|
return OwningActor->GetWorld();
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
AActor* UGCS_AttackResultProcessor::GetOwningActor() const
|
||||||
|
{
|
||||||
|
if (UGCS_CombatFlow* Flow = Cast<UGCS_CombatFlow>(GetOuter()))
|
||||||
|
{
|
||||||
|
return Flow->GetFlowOwner();
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
UAbilitySystemComponent* UGCS_AttackResultProcessor::GetOwningAbilitySystemComponent() const
|
||||||
|
{
|
||||||
|
if (AActor* OwningActor = GetOwningActor())
|
||||||
|
{
|
||||||
|
return UAbilitySystemGlobals::GetAbilitySystemComponentFromActor(OwningActor);
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
FString UGCS_AttackResultProcessor::GetEditorFriendlyName_Implementation() const
|
||||||
|
{
|
||||||
|
return TEXT("");
|
||||||
|
}
|
||||||
|
|
||||||
|
void UGCS_AttackResultProcessor::HandleIncomingAttackResult_Implementation(const FGCS_AttackResult& AttackResult) const
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#if WITH_EDITORONLY_DATA
|
||||||
|
void UGCS_AttackResultProcessor::PreSave(FObjectPreSaveContext SaveContext)
|
||||||
|
{
|
||||||
|
EditorFriendlyName = GetEditorFriendlyName();
|
||||||
|
Super::PreSave(SaveContext);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
bool UGCS_AttackResultProcessor_WithTagRequirement::ProcessIncomingAttackResult(const FGCS_AttackResult& AttackResult)
|
||||||
|
{
|
||||||
|
if (!AttackResult.bConsumed)
|
||||||
|
{
|
||||||
|
bool bMatchSourceQuery = SourceTagQuery.IsEmpty() || SourceTagQuery.Matches(AttackResult.AggregatedSourceTags);
|
||||||
|
bool bMatchTargetQuery = TargetTagQuery.IsEmpty() || TargetTagQuery.Matches(AttackResult.AggregatedTargetTags);
|
||||||
|
|
||||||
|
if (bMatchSourceQuery && bMatchTargetQuery)
|
||||||
|
{
|
||||||
|
HandleIncomingAttackResult(AttackResult);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
FString UGCS_AttackResultProcessor_WithTagRequirement::GetSourceTagQueryDesc() const
|
||||||
|
{
|
||||||
|
return SourceTagQuery.GetDescription();
|
||||||
|
}
|
||||||
|
|
||||||
|
FString UGCS_AttackResultProcessor_WithTagRequirement::GetTargetTagQueryDesc() const
|
||||||
|
{
|
||||||
|
return TargetTagQuery.GetDescription();
|
||||||
|
}
|
||||||
|
|
||||||
|
void UGCS_AttackResultProcessor_Death::HandleIncomingAttackResult_Implementation(const FGCS_AttackResult& AttackResult) const
|
||||||
|
{
|
||||||
|
UAbilitySystemComponent* ASC = GetOwningAbilitySystemComponent();
|
||||||
|
if (ASC)
|
||||||
|
{
|
||||||
|
if (true)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Super::HandleIncomingAttackResult_Implementation(AttackResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UGCS_AttackResultProcessor_GameplayEvent::HandleIncomingAttackResult_Implementation(const FGCS_AttackResult& AttackResult) const
|
||||||
|
{
|
||||||
|
APawn* OwningPawn = Cast<APawn>(GetOwningActor());
|
||||||
|
if (OwningPawn == nullptr)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (OwningPawn->HasAuthority() || OwningPawn->IsLocallyControlled())
|
||||||
|
{
|
||||||
|
UAbilitySystemComponent* ASC = bSendToAttacker ? AttackResult.EffectContextHandle.GetInstigatorAbilitySystemComponent() : GetOwningAbilitySystemComponent();
|
||||||
|
if (IsValid(ASC))
|
||||||
|
{
|
||||||
|
FGameplayEventData Payload = FGameplayEventData();
|
||||||
|
|
||||||
|
FGameplayTagContainer InstigatorTags = AttackResult.AggregatedSourceTags;
|
||||||
|
if (const FGCS_ContextPayload_Combat* CombatPayload = UGCS_CombatFunctionLibrary::EffectContextGetMutableCombatPayload(AttackResult.EffectContextHandle))
|
||||||
|
{
|
||||||
|
InstigatorTags.AppendTags(CombatPayload->DynamicTags);
|
||||||
|
}
|
||||||
|
|
||||||
|
Payload.Instigator = AttackResult.EffectContextHandle.GetInstigator();
|
||||||
|
// Payload.OptionalObject = AttackResult.OptionalObject;
|
||||||
|
Payload.Target = OwningPawn;
|
||||||
|
Payload.ContextHandle = AttackResult.EffectContextHandle;
|
||||||
|
Payload.InstigatorTags = InstigatorTags;
|
||||||
|
Payload.TargetTags = AttackResult.AggregatedTargetTags;
|
||||||
|
|
||||||
|
for (const FGameplayTag& Tag : EventTriggers)
|
||||||
|
{
|
||||||
|
// UAbilitySystemBlueprintLibrary::SendGameplayEventToActor(OwningPawn, Tag, Payload);
|
||||||
|
Payload.EventTag = Tag;
|
||||||
|
// FScopedPredictionWindow NewScopedWindow(ASC, true);
|
||||||
|
ASC->HandleGameplayEvent(Tag, &Payload);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FString UGCS_AttackResultProcessor_GameplayEvent::GetEditorFriendlyName_Implementation() const
|
||||||
|
{
|
||||||
|
FString Result;
|
||||||
|
|
||||||
|
for (FGameplayTag EventTrigger : EventTriggers)
|
||||||
|
{
|
||||||
|
if (EventTrigger.IsValid())
|
||||||
|
{
|
||||||
|
TArray<FString> Parts;
|
||||||
|
EventTrigger.GetTagName().ToString().ParseIntoArray(Parts,TEXT("."));
|
||||||
|
if (Parts.Num() > 0)
|
||||||
|
{
|
||||||
|
Result.Append(FString::Format(TEXT(" ({0}) "), {Parts.Last()}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return FString::Format(TEXT("Send Event:{0}"), {Result});
|
||||||
|
}
|
||||||
|
|
||||||
|
void UGCS_AttackResultProcessor_GameplayCue::HandleIncomingAttackResult_Implementation(const FGCS_AttackResult& AttackResult) const
|
||||||
|
{
|
||||||
|
AActor* OwningActor = GetOwningActor();
|
||||||
|
if (!OwningActor)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
TArray<FGameplayTag> CuesToTrigger = GameplayCues;
|
||||||
|
|
||||||
|
FGCS_AttackDefinition Atk = UGCS_CombatFunctionLibrary::EffectContextGetAttackDefinition(AttackResult.EffectContextHandle);
|
||||||
|
|
||||||
|
if (!Atk.TargetGameplayCues.IsEmpty())
|
||||||
|
{
|
||||||
|
CuesToTrigger = Atk.TargetGameplayCues;
|
||||||
|
}
|
||||||
|
|
||||||
|
FGameplayCueParameters Params = FGameplayCueParameters();
|
||||||
|
if (const FHitResult* HitResult = AttackResult.EffectContextHandle.GetHitResult())
|
||||||
|
{
|
||||||
|
if (HitResult->GetActor() == OwningActor)
|
||||||
|
{
|
||||||
|
Params = UGameplayCueFunctionLibrary::MakeGameplayCueParametersFromHitResult(*HitResult);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FGameplayTagContainer InstigatorTags = AttackResult.AggregatedSourceTags;
|
||||||
|
if (const FGCS_ContextPayload_Combat* CombatPayload = UGCS_CombatFunctionLibrary::EffectContextGetMutableCombatPayload(AttackResult.EffectContextHandle))
|
||||||
|
{
|
||||||
|
InstigatorTags.AppendTags(CombatPayload->DynamicTags);
|
||||||
|
Params.RawMagnitude = CombatPayload->GetTaggedValue(RawMagnitudeTag);
|
||||||
|
Params.NormalizedMagnitude = CombatPayload->GetTaggedValue(NormalizedMagnitudeTag);
|
||||||
|
}
|
||||||
|
|
||||||
|
Params.AggregatedSourceTags = InstigatorTags;
|
||||||
|
Params.AggregatedTargetTags = AttackResult.AggregatedTargetTags;
|
||||||
|
Params.EffectCauser = AttackResult.EffectContextHandle.GetEffectCauser();
|
||||||
|
Params.Instigator = AttackResult.EffectContextHandle.GetInstigator();
|
||||||
|
|
||||||
|
Params.EffectContext = AttackResult.EffectContextHandle;
|
||||||
|
|
||||||
|
ModifyGameplayCueParametersBeforeExecute(Params);
|
||||||
|
|
||||||
|
for (const FGameplayTag& Cue : CuesToTrigger)
|
||||||
|
{
|
||||||
|
if (!Cue.IsValid())
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
UGGA_GameplayCueFunctionLibrary::ExecuteGameplayCueLocal(OwningActor, Cue, Params);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FString UGCS_AttackResultProcessor_GameplayCue::GetEditorFriendlyName_Implementation() const
|
||||||
|
{
|
||||||
|
FString Result;
|
||||||
|
for (FGameplayTag EventTrigger : GameplayCues)
|
||||||
|
{
|
||||||
|
if (EventTrigger.IsValid())
|
||||||
|
{
|
||||||
|
TArray<FString> Parts;
|
||||||
|
EventTrigger.GetTagName().ToString().ParseIntoArray(Parts,TEXT("."));
|
||||||
|
if (Parts.Num() > 0)
|
||||||
|
{
|
||||||
|
Result.Append(FString::Format(TEXT(" ({0}) "), {Parts.Last()}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return FString::Format(TEXT("Execute cues:{0}"), {Result});
|
||||||
|
}
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||||
|
|
||||||
|
|
||||||
|
#include "CombatFlow/GCS_CombatFlow.h"
|
||||||
|
#include "AbilitySystemBlueprintLibrary.h"
|
||||||
|
#include "GameFramework/Pawn.h"
|
||||||
|
#include "Utilities/GGA_AbilitySystemFunctionLibrary.h"
|
||||||
|
#include "GCS_CombatSystemComponent.h"
|
||||||
|
#include "GCS_LogChannels.h"
|
||||||
|
#include "CombatFlow/GCS_AttackResultProcessor.h"
|
||||||
|
#include "Utility/GCS_CombatFunctionLibrary.h"
|
||||||
|
|
||||||
|
|
||||||
|
UGCS_CombatFlow::UGCS_CombatFlow()
|
||||||
|
{
|
||||||
|
Owner = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UGCS_CombatFlow::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
|
||||||
|
{
|
||||||
|
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UGCS_CombatFlow::Initialize(AActor* NewOwner)
|
||||||
|
{
|
||||||
|
Owner = NewOwner;
|
||||||
|
// ProcessedAttacks.SetCombatFlow(this);
|
||||||
|
CombatComponent = UGCS_CombatSystemComponent::GetCombatSystemComponent(Owner);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UGCS_CombatFlow::HandlePreGameplayEffectSpecApply_Implementation(const FGameplayEffectSpec& Spec, UAbilitySystemComponent* AbilitySystemComponent,
|
||||||
|
FGameplayTagContainer& OutDynamicTagsAppendToSpec)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void UGCS_CombatFlow::HandleGameplayEffectExecute_Implementation(const FGGA_GameplayEffectModCallbackData& Payload)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void UGCS_CombatFlow::HandleAttackResult_Implementation(const FGCS_AttackResult& InPayload)
|
||||||
|
{
|
||||||
|
FGCS_AttackResult ModifiedPayload = InPayload;
|
||||||
|
bool bPredictingContext = UGCS_CombatFunctionLibrary::EffectContextGetIsPredictingContext(InPayload.EffectContextHandle);
|
||||||
|
const FGCS_ContextPayload_Combat* CombatPayload = UGCS_CombatFunctionLibrary::EffectContextGetMutableCombatPayload(ModifiedPayload.EffectContextHandle);
|
||||||
|
|
||||||
|
if (CombatPayload)
|
||||||
|
{
|
||||||
|
ModifiedPayload.AggregatedSourceTags.AppendTags(CombatPayload->DynamicTags);
|
||||||
|
}
|
||||||
|
|
||||||
|
CombatComponent->SetLastProcessedAttackResult(ModifiedPayload);
|
||||||
|
|
||||||
|
for (int32 i = 0; i < AttackResultProcessors.Num(); i++)
|
||||||
|
{
|
||||||
|
auto& Processor = AttackResultProcessors[i];
|
||||||
|
if (!IsValid(Processor))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
bool bShouldExecute = Processor->GetExecutePolicy() == EGCS_AttackResultProcessorPolicy::Default && !bPredictingContext;
|
||||||
|
|
||||||
|
if (Processor->GetExecutePolicy() == EGCS_AttackResultProcessorPolicy::LocalPredicted)
|
||||||
|
{
|
||||||
|
bShouldExecute = bPredictingContext || !InPayload.bWasPredicated;
|
||||||
|
if (!bShouldExecute)
|
||||||
|
{
|
||||||
|
GCS_OWNED_CLOG(GetFlowOwner(), VeryVerbose, "Skipped local predicted processor at idx(%d) of type(%s)", i, *Processor->GetClass()->GetName())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Processor->GetExecutePolicy() == EGCS_AttackResultProcessorPolicy::ServerOnly)
|
||||||
|
{
|
||||||
|
bShouldExecute = !bPredictingContext && !InPayload.bWasReplicated;
|
||||||
|
if (!bShouldExecute)
|
||||||
|
{
|
||||||
|
GCS_OWNED_CLOG(GetFlowOwner(), VeryVerbose, "Skipped server only processor at idx(%d) of type(%s)", i, *Processor->GetClass()->GetName())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bShouldExecute)
|
||||||
|
{
|
||||||
|
#if WITH_EDITOR
|
||||||
|
// Debugging says not enabled.
|
||||||
|
if (!Processor->GetEditorEnableState())
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
if (Processor->ProcessIncomingAttackResult(ModifiedPayload))
|
||||||
|
{
|
||||||
|
GCS_OWNED_CLOG(GetFlowOwner(), VeryVerbose, "%sExecuted processor at idx(%d) of type(%s)", bPredictingContext?TEXT("Predicate "):TEXT(""), i, *Processor->GetClass()->GetName())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||||
|
|
||||||
|
|
||||||
|
#include "Combo/GCS_ComboDefinition.h"
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||||
|
|
||||||
|
|
||||||
|
#include "GCS_ActorOwnedObject.h"
|
||||||
|
#include "GameFramework/Actor.h"
|
||||||
|
|
||||||
|
UWorld* UGCS_ActorOwnedObject::GetWorld() const
|
||||||
|
{
|
||||||
|
// To Make sure the outer is Valid and can be used
|
||||||
|
if (!HasAnyFlags(RF_ClassDefaultObject) && !GetOuter()->HasAnyFlags(RF_BeginDestroyed) && !GetOuter()->IsUnreachable())
|
||||||
|
{
|
||||||
|
//Attempt to get the world
|
||||||
|
AActor* Outer = GetTypedOuter<AActor>();
|
||||||
|
if (Outer != nullptr)
|
||||||
|
{
|
||||||
|
return Outer->GetWorld();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||||
|
|
||||||
|
|
||||||
|
#include "GCS_CombatEntityInterface.h"
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||||
|
|
||||||
|
|
||||||
|
#include "GCS_CombatEnumLibrary.h"
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||||
|
|
||||||
|
|
||||||
|
#include "GCS_CombatStructLibrary.h"
|
||||||
|
#include "Animation/AnimMontage.h"
|
||||||
|
#include "Collision/DEPRECATED_GCS_CollisionTraceInstance.h"
|
||||||
@@ -0,0 +1,318 @@
|
|||||||
|
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||||
|
|
||||||
|
|
||||||
|
#include "GCS_CombatSystemComponent.h"
|
||||||
|
#include "AbilitySystemBlueprintLibrary.h"
|
||||||
|
#include "TimerManager.h"
|
||||||
|
#include "GCS_LogChannels.h"
|
||||||
|
#include "Engine/World.h"
|
||||||
|
#include "GameFramework/Controller.h"
|
||||||
|
#include "Components/SkeletalMeshComponent.h"
|
||||||
|
#include "CombatFlow/GCS_AttackRequest.h"
|
||||||
|
#include "CombatFlow/GCS_CombatFlow.h"
|
||||||
|
#include "GameFramework/GameStateBase.h"
|
||||||
|
#include "Net/UnrealNetwork.h"
|
||||||
|
#include "Animation/AnimMontage.h"
|
||||||
|
#include "Animation/AnimInstance.h"
|
||||||
|
#include "Utility/GCS_CombatFunctionLibrary.h"
|
||||||
|
|
||||||
|
|
||||||
|
UGCS_CombatSystemComponent::UGCS_CombatSystemComponent() : AttackResultContainer(this, 10)
|
||||||
|
{
|
||||||
|
SetIsReplicatedByDefault(true);
|
||||||
|
bWantsInitializeComponent = true;
|
||||||
|
bReplicateUsingRegisteredSubObjectList = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UGCS_CombatSystemComponent::InitializeComponent()
|
||||||
|
{
|
||||||
|
AttackResultContainer.SetOwningCombatSystem(this);
|
||||||
|
Super::InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
void UGCS_CombatSystemComponent::BeginPlay()
|
||||||
|
{
|
||||||
|
if (GetWorld()->IsGameWorld())
|
||||||
|
{
|
||||||
|
if (GetOwner()->HasAuthority() && CombatFlowClass)
|
||||||
|
{
|
||||||
|
UGCS_CombatFlow* LocalNewProperty = NewObject<UGCS_CombatFlow>(GetOwner(), CombatFlowClass);
|
||||||
|
LocalNewProperty->Initialize(GetOwner());
|
||||||
|
CombatFlow = LocalNewProperty;
|
||||||
|
AddReplicatedSubObject(CombatFlow);
|
||||||
|
}
|
||||||
|
UGGA_AbilitySystemGlobals::RegisterEventReceiver(this);
|
||||||
|
}
|
||||||
|
Super::BeginPlay();
|
||||||
|
}
|
||||||
|
|
||||||
|
void UGCS_CombatSystemComponent::EndPlay(const EEndPlayReason::Type EndPlayReason)
|
||||||
|
{
|
||||||
|
Super::EndPlay(EndPlayReason);
|
||||||
|
UGGA_AbilitySystemGlobals::UnregisterEventReceiver(this);
|
||||||
|
if (GetOwner() && GetOwner()->HasAuthority())
|
||||||
|
{
|
||||||
|
if (CombatFlow)
|
||||||
|
{
|
||||||
|
RemoveReplicatedSubObject(CombatFlow);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UGCS_CombatSystemComponent::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
|
||||||
|
{
|
||||||
|
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
|
||||||
|
|
||||||
|
DOREPLIFETIME(ThisClass, CombatFlow);
|
||||||
|
DOREPLIFETIME(ThisClass, AttackResultContainer);
|
||||||
|
DOREPLIFETIME(ThisClass, ReplicatedMontageInfo);
|
||||||
|
|
||||||
|
FDoRepLifetimeParams Parameters;
|
||||||
|
Parameters.bIsPushBased = true;
|
||||||
|
|
||||||
|
Parameters.Condition = COND_SkipOwner;
|
||||||
|
|
||||||
|
DOREPLIFETIME_WITH_PARAMS_FAST(ThisClass, ComboStep, Parameters)
|
||||||
|
}
|
||||||
|
|
||||||
|
UGCS_CombatSystemComponent* UGCS_CombatSystemComponent::GetCombatSystemComponent(const AActor* Actor)
|
||||||
|
{
|
||||||
|
return Actor ? Actor->FindComponentByClass<UGCS_CombatSystemComponent>() : nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UGCS_CombatSystemComponent::FindCombatSystemComponent(const AActor* Actor, UGCS_CombatSystemComponent*& CombatComponent)
|
||||||
|
{
|
||||||
|
CombatComponent = Actor ? Actor->FindComponentByClass<UGCS_CombatSystemComponent>() : nullptr;
|
||||||
|
return IsValid(CombatComponent);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UGCS_CombatSystemComponent::FindTypedCombatSystemComponent(AActor* Actor, TSubclassOf<UGCS_CombatSystemComponent> DesiredClass, UGCS_CombatSystemComponent*& Component)
|
||||||
|
{
|
||||||
|
if (UGCS_CombatSystemComponent* Instance = GetCombatSystemComponent(Actor))
|
||||||
|
{
|
||||||
|
if (Instance->GetClass()->IsChildOf(DesiredClass))
|
||||||
|
{
|
||||||
|
Component = Instance;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
UGCS_CombatFlow* UGCS_CombatSystemComponent::GetCombatFlow() const
|
||||||
|
{
|
||||||
|
return CombatFlow;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UGCS_CombatSystemComponent::RegisterAttackResult(FGCS_AttackResult& Payload)
|
||||||
|
{
|
||||||
|
//TODO Should make this server only?
|
||||||
|
if (GetOwner() && GetOwner()->HasAuthority())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
AttackResultContainer.AddEntry(Payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
FGCS_AttackResult UGCS_CombatSystemComponent::GetLastProcessedAttackResult() const
|
||||||
|
{
|
||||||
|
return LastProcessedAttackResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UGCS_CombatSystemComponent::SetLastProcessedAttackResult(const FGCS_AttackResult& Payload)
|
||||||
|
{
|
||||||
|
LastProcessedAttackResult = Payload;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UGCS_CombatSystemComponent::PlayPredictableMontageForTarget(UGCS_CombatSystemComponent* TargetCSC, FGCS_PlayMontageRequest Request)
|
||||||
|
{
|
||||||
|
if (GetOwnerRole() >= ROLE_Authority)
|
||||||
|
{
|
||||||
|
TargetCSC->SetReplicatedMontage(Request);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (GetOwnerRole() == ROLE_AutonomousProxy)
|
||||||
|
{
|
||||||
|
TargetCSC->PlayPredictedMontage(Request);
|
||||||
|
ServerPlayPredictableMontageForTarget(TargetCSC, Request);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void UGCS_CombatSystemComponent::ServerPlayPredictableMontageForTarget_Implementation(UGCS_CombatSystemComponent* TargetCSC, FGCS_PlayMontageRequest Request)
|
||||||
|
{
|
||||||
|
TargetCSC->SetReplicatedMontage(Request);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void UGCS_CombatSystemComponent::SetReplicatedMontage(const FGCS_PlayMontageRequest& Request)
|
||||||
|
{
|
||||||
|
TimerHandle.Invalidate();
|
||||||
|
ReplicatedMontageInfo.AnimMontage = Request.AnimMontage;
|
||||||
|
ReplicatedMontageInfo.PlayRate = Request.PlayRate;
|
||||||
|
ReplicatedMontageInfo.TriggeredTime = GetWorld()->GetGameState()->GetServerWorldTimeSeconds();
|
||||||
|
ReplicatedMontageInfo.StartSectionName = Request.StartSectionName;
|
||||||
|
|
||||||
|
GetWorld()->GetTimerManager().SetTimer(TimerHandle, [&]()
|
||||||
|
{
|
||||||
|
ReplicatedMontageInfo.AnimMontage = nullptr;
|
||||||
|
TimerHandle.Invalidate();
|
||||||
|
}, Request.AnimMontage->GetPlayLength() * Request.PlayRate, false);
|
||||||
|
|
||||||
|
|
||||||
|
OnRep_ReplicatedMontageInfo();
|
||||||
|
}
|
||||||
|
|
||||||
|
//server tell me to play montage.
|
||||||
|
void UGCS_CombatSystemComponent::OnRep_ReplicatedMontageInfo()
|
||||||
|
{
|
||||||
|
if (ReplicatedMontageInfo.AnimMontage == nullptr)
|
||||||
|
{
|
||||||
|
PredictedMontageInfo.AnimMontage = nullptr;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (USkeletalMeshComponent* MeshComponent = GetCharacterMeshComponent())
|
||||||
|
{
|
||||||
|
UAnimMontage* MontageToPlay = ReplicatedMontageInfo.AnimMontage;
|
||||||
|
float TimeDiff = GetWorld()->GetGameState()->GetServerWorldTimeSeconds() - ReplicatedMontageInfo.TriggeredTime;
|
||||||
|
float StartTime = FMath::Clamp(TimeDiff, 0, ReplicatedMontageInfo.AnimMontage->GetPlayLength() * ReplicatedMontageInfo.PlayRate);
|
||||||
|
|
||||||
|
//If local montage ahead of replicated montage
|
||||||
|
if (PredictedMontageInfo.AnimMontage != nullptr)
|
||||||
|
{
|
||||||
|
//And it's the same.
|
||||||
|
if (ReplicatedMontageInfo.AnimMontage == PredictedMontageInfo.AnimMontage)
|
||||||
|
{
|
||||||
|
PredictedMontageInfo.AnimMontage = nullptr;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
PredictedMontageInfo.AnimMontage = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
MeshComponent->GetAnimInstance()->Montage_Play(MontageToPlay, ReplicatedMontageInfo.PlayRate, EMontagePlayReturnType::MontageLength, StartTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UGCS_CombatSystemComponent::PlayPredictedMontage(const FGCS_PlayMontageRequest& Request)
|
||||||
|
{
|
||||||
|
PredictedMontageInfo.AnimMontage = Request.AnimMontage;
|
||||||
|
PredictedMontageInfo.PlayRate = Request.PlayRate;
|
||||||
|
PredictedMontageInfo.TriggeredTime = GetWorld()->GetGameState()->GetServerWorldTimeSeconds();
|
||||||
|
PredictedMontageInfo.StartSectionName = Request.StartSectionName;
|
||||||
|
if (USkeletalMeshComponent* MeshComponent = GetCharacterMeshComponent())
|
||||||
|
{
|
||||||
|
float Duration = MeshComponent->GetAnimInstance()->Montage_Play(Request.AnimMontage, Request.PlayRate, EMontagePlayReturnType::MontageLength, Request.StartTimeSeconds);
|
||||||
|
if (Duration > 0)
|
||||||
|
{
|
||||||
|
// Start at a given Section.
|
||||||
|
if (Request.StartSectionName != NAME_None)
|
||||||
|
{
|
||||||
|
MeshComponent->GetAnimInstance()->Montage_JumpToSection(Request.StartSectionName, Request.AnimMontage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
USkeletalMeshComponent* UGCS_CombatSystemComponent::GetCharacterMeshComponent() const
|
||||||
|
{
|
||||||
|
static FName CharacterMeshTagName = "CharacterMesh";
|
||||||
|
|
||||||
|
return Cast<USkeletalMeshComponent>(GetOwner()->FindComponentByTag(USkeletalMeshComponent::StaticClass(), CharacterMeshTagName));
|
||||||
|
}
|
||||||
|
|
||||||
|
void UGCS_CombatSystemComponent::OnGlobalPreGameplayEffectSpecApply(FGameplayEffectSpec& Spec, UAbilitySystemComponent* AbilitySystemComponent)
|
||||||
|
{
|
||||||
|
if (IsValid(CombatFlow))
|
||||||
|
{
|
||||||
|
FGameplayTagContainer DynamicTags;
|
||||||
|
CombatFlow->HandlePreGameplayEffectSpecApply(Spec, AbilitySystemComponent, DynamicTags);
|
||||||
|
if (!DynamicTags.IsEmpty())
|
||||||
|
{
|
||||||
|
Spec.AppendDynamicAssetTags(DynamicTags);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UGCS_CombatSystemComponent::OnRep_CombatFlow()
|
||||||
|
{
|
||||||
|
CombatFlow->Initialize(GetOwner());
|
||||||
|
UE_LOG(LogGCS, Display, TEXT("Combat flow replicated for %s"), *GetOwner()->GetName());
|
||||||
|
}
|
||||||
|
|
||||||
|
int32 UGCS_CombatSystemComponent::GetComboStep() const
|
||||||
|
{
|
||||||
|
return ComboStep;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UGCS_CombatSystemComponent::UpdateComboStep(int32 NewComboStep)
|
||||||
|
{
|
||||||
|
if (NewComboStep >= 0)
|
||||||
|
{
|
||||||
|
UpdateComboStep(NewComboStep, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UGCS_CombatSystemComponent::ResetComboState()
|
||||||
|
{
|
||||||
|
if (ComboStep != 0)
|
||||||
|
{
|
||||||
|
UpdateComboStep(0,true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UGCS_CombatSystemComponent::UpdateComboStep(int32 NewComboStep, bool bSendRpc)
|
||||||
|
{
|
||||||
|
if (ComboStep == NewComboStep || GetOwner()->GetLocalRole() <= ROLE_SimulatedProxy || ComboStep == NewComboStep)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto PrevComboStep{ComboStep};
|
||||||
|
|
||||||
|
ComboStep = NewComboStep;
|
||||||
|
|
||||||
|
MARK_PROPERTY_DIRTY_FROM_NAME(ThisClass, ComboStep, this)
|
||||||
|
|
||||||
|
OnComboStepChanged(PrevComboStep);
|
||||||
|
|
||||||
|
if (bSendRpc)
|
||||||
|
{
|
||||||
|
if (GetOwner()->GetLocalRole() >= ROLE_Authority)
|
||||||
|
{
|
||||||
|
ClientUpdateComboStep(ComboStep);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ServerUpdateComboStep(ComboStep);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UGCS_CombatSystemComponent::OnReplicated_ComboStep(int32 PrevComboStep)
|
||||||
|
{
|
||||||
|
OnComboStepChanged(PrevComboStep);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UGCS_CombatSystemComponent::ClientUpdateComboStep_Implementation(int32 NewComboStep)
|
||||||
|
{
|
||||||
|
UpdateComboStep(NewComboStep, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UGCS_CombatSystemComponent::ClientUpdateComboStep_Validate(int32 NewComboStep)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UGCS_CombatSystemComponent::ServerUpdateComboStep_Implementation(int32 NewComboStep)
|
||||||
|
{
|
||||||
|
UpdateComboStep(NewComboStep, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UGCS_CombatSystemComponent::ServerUpdateComboStep_Validate(int32 NewComboStep)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UGCS_CombatSystemComponent::OnComboStepChanged_Implementation(int32 PrevComboStep)
|
||||||
|
{
|
||||||
|
OnComboStepChangedEvent.Broadcast(PrevComboStep);
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||||
|
|
||||||
|
|
||||||
|
#include "GCS_CombatSystemSettings.h"
|
||||||
|
|
||||||
|
const UGCS_CombatSystemSettings* UGCS_CombatSystemSettings::Get()
|
||||||
|
{
|
||||||
|
return GetDefault<UGCS_CombatSystemSettings>();
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||||
|
|
||||||
|
|
||||||
|
#include "GCS_EffectCauserInterface.h"
|
||||||
|
|
||||||
|
|
||||||
|
// Add default functionality here for any IGCS_EffectCauserInterface functions that are not pure virtual.
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||||
|
|
||||||
|
|
||||||
|
#include "GCS_GameplayTags.h"
|
||||||
|
|
||||||
|
namespace GCS_BulletLaunch
|
||||||
|
{
|
||||||
|
UE_DEFINE_GAMEPLAY_TAG_COMMENT(Always, "GGF.Combat.Bullet.LaunchCond.Always", "Always generate bullet");
|
||||||
|
UE_DEFINE_GAMEPLAY_TAG_COMMENT(DidNotHitPawn, "GGF.Combat.Bullet.LaunchCond.DidNotHitPawn", "Only generate bullet if not hit any pawn");
|
||||||
|
UE_DEFINE_GAMEPLAY_TAG_COMMENT(HitPawn, "GGF.Combat.Bullet.LaunchCond.HitPawn", "Only generate bullet if hit any pawn");
|
||||||
|
}
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||||
|
|
||||||
|
|
||||||
|
#include "GCS_LogChannels.h"
|
||||||
|
#include "Engine/EngineTypes.h"
|
||||||
|
#include "GameFramework/Actor.h"
|
||||||
|
#include "Components/ActorComponent.h"
|
||||||
|
#include "GameplayTask.h"
|
||||||
|
#include "Abilities/GameplayAbility.h"
|
||||||
|
|
||||||
|
DEFINE_LOG_CATEGORY(LogGCS)
|
||||||
|
DEFINE_LOG_CATEGORY(LogGCS_Targeting)
|
||||||
|
DEFINE_LOG_CATEGORY(LogGCS_Collision)
|
||||||
|
DEFINE_LOG_CATEGORY(LogGCS_Trace)
|
||||||
|
|
||||||
|
|
||||||
|
FString GetGCSLogContextString(const UObject* ContextObject)
|
||||||
|
{
|
||||||
|
ENetRole Role = ROLE_None;
|
||||||
|
FString RoleName = TEXT("None");
|
||||||
|
FString Name = "None";
|
||||||
|
|
||||||
|
if (const AActor* Actor = Cast<AActor>(ContextObject))
|
||||||
|
{
|
||||||
|
Role = Actor->GetLocalRole();
|
||||||
|
Name = Actor->GetName();
|
||||||
|
}
|
||||||
|
else if (const UActorComponent* Component = Cast<UActorComponent>(ContextObject))
|
||||||
|
{
|
||||||
|
Role = Component->GetOwnerRole();
|
||||||
|
Name = Component->GetOwner()->GetName();
|
||||||
|
}
|
||||||
|
else if (const UGameplayTask* Task = Cast<UGameplayTask>(ContextObject))
|
||||||
|
{
|
||||||
|
Role = Task->GetAvatarActor()->GetLocalRole();
|
||||||
|
Name = Task->GetAvatarActor()->GetName();
|
||||||
|
}
|
||||||
|
else if (const UGameplayAbility* Ability = Cast<UGameplayAbility>(ContextObject))
|
||||||
|
{
|
||||||
|
Role = Ability->GetAvatarActorFromActorInfo()->GetLocalRole();
|
||||||
|
Name = Ability->GetAvatarActorFromActorInfo()->GetName();
|
||||||
|
}
|
||||||
|
else if (IsValid(ContextObject))
|
||||||
|
{
|
||||||
|
Name = ContextObject->GetName();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Role != ROLE_None)
|
||||||
|
{
|
||||||
|
RoleName = (Role == ROLE_Authority) ? TEXT("Server") : TEXT("Client");
|
||||||
|
}
|
||||||
|
return FString::Printf(TEXT("[%s] (%s)"), *RoleName, *Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
FString GetClientServerContextString(UObject* ContextObject)
|
||||||
|
{
|
||||||
|
ENetRole Role = ROLE_None;
|
||||||
|
|
||||||
|
if (AActor* Actor = Cast<AActor>(ContextObject))
|
||||||
|
{
|
||||||
|
Role = Actor->GetLocalRole();
|
||||||
|
}
|
||||||
|
else if (UActorComponent* Component = Cast<UActorComponent>(ContextObject))
|
||||||
|
{
|
||||||
|
Role = Component->GetOwnerRole();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Role != ROLE_None)
|
||||||
|
{
|
||||||
|
return (Role == ROLE_Authority) ? TEXT("Server") : TEXT("Client");
|
||||||
|
}
|
||||||
|
#if WITH_EDITOR
|
||||||
|
if (GIsEditor)
|
||||||
|
{
|
||||||
|
extern ENGINE_API FString GPlayInEditorContextString;
|
||||||
|
return GPlayInEditorContextString;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return TEXT("[]");
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||||
|
|
||||||
|
#include "GenericCombatSystem.h"
|
||||||
|
|
||||||
|
#define LOCTEXT_NAMESPACE "FGenericCombatSystemModule"
|
||||||
|
|
||||||
|
void FGenericCombatSystemModule::StartupModule()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void FGenericCombatSystemModule::ShutdownModule()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#undef LOCTEXT_NAMESPACE
|
||||||
|
|
||||||
|
IMPLEMENT_MODULE(FGenericCombatSystemModule, GenericCombatSystem)
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||||
|
|
||||||
|
|
||||||
|
#include "Notifies/GCS_ANS_AttackTrace.h"
|
||||||
|
|
||||||
|
#include "CombatFlow/GCS_AttackRequest.h"
|
||||||
|
#include "UObject/ObjectSaveContext.h"
|
||||||
|
|
||||||
|
|
||||||
|
UGCS_ANS_AttackTrace::UGCS_ANS_AttackTrace(const FObjectInitializer& ObjectInitializer): Super(ObjectInitializer)
|
||||||
|
{
|
||||||
|
#if WITH_EDITORONLY_DATA
|
||||||
|
bShouldFireInEditor = false;
|
||||||
|
#endif
|
||||||
|
AttackRequest = ObjectInitializer.CreateDefaultSubobject<UGCS_AttackRequest_Melee>(this, TEXT("AttackRequest"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void UGCS_ANS_AttackTrace::PostInitProperties()
|
||||||
|
{
|
||||||
|
Super::PostInitProperties();
|
||||||
|
}
|
||||||
|
|
||||||
|
#if WITH_EDITORONLY_DATA
|
||||||
|
void UGCS_ANS_AttackTrace::PreSave(FObjectPreSaveContext SaveContext)
|
||||||
|
{
|
||||||
|
Super::PreSave(SaveContext);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||||
|
|
||||||
|
|
||||||
|
#include "Notifies/GCS_ANS_BulletTrace.h"
|
||||||
|
|
||||||
|
#include "CombatFlow/GCS_AttackRequest.h"
|
||||||
|
#include "UObject/ObjectSaveContext.h"
|
||||||
|
|
||||||
|
|
||||||
|
UGCS_ANS_BulletTrace::UGCS_ANS_BulletTrace(const FObjectInitializer& ObjectInitializer): Super(ObjectInitializer)
|
||||||
|
{
|
||||||
|
#if WITH_EDITORONLY_DATA
|
||||||
|
bShouldFireInEditor = false;
|
||||||
|
#endif
|
||||||
|
AttackRequest = ObjectInitializer.CreateDefaultSubobject<UGCS_AttackRequest_Bullet>(this, TEXT("AttackRequest"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#if WITH_EDITOR
|
||||||
|
#include "Misc/DataValidation.h"
|
||||||
|
|
||||||
|
EDataValidationResult UGCS_ANS_BulletTrace::IsDataValid(FDataValidationContext& Context) const
|
||||||
|
{
|
||||||
|
if (AttackRequest && AttackRequest->bRequireTargeting && AttackRequest->TargetingPreset == nullptr)
|
||||||
|
{
|
||||||
|
Context.AddError(FText::FromString(TEXT("TargetingPreset is required!")));
|
||||||
|
return EDataValidationResult::Invalid;
|
||||||
|
}
|
||||||
|
return Super::IsDataValid(Context);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UGCS_ANS_BulletTrace::PreSave(FObjectPreSaveContext SaveContext)
|
||||||
|
{
|
||||||
|
Super::PreSave(SaveContext);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||||
|
|
||||||
|
|
||||||
|
#include "Notifies/GCS_ANS_MovementCancellation.h"
|
||||||
|
#include "Components/SkeletalMeshComponent.h"
|
||||||
|
#include "Animation/AnimInstance.h"
|
||||||
|
#include "GameFramework/Character.h"
|
||||||
|
#include "GameFramework/CharacterMovementComponent.h"
|
||||||
|
|
||||||
|
UGCS_ANS_MovementCancellation::UGCS_ANS_MovementCancellation(const FObjectInitializer& ObjectInitializer): Super(ObjectInitializer)
|
||||||
|
{
|
||||||
|
bIsNativeBranchingPoint = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UGCS_ANS_MovementCancellation::BranchingPointNotifyBegin(FBranchingPointNotifyPayload& BranchingPointPayload)
|
||||||
|
{
|
||||||
|
Super::BranchingPointNotifyBegin(BranchingPointPayload);
|
||||||
|
|
||||||
|
IsRootMotionDisabled = false;
|
||||||
|
|
||||||
|
if (USkeletalMeshComponent* MeshComp = BranchingPointPayload.SkelMeshComponent)
|
||||||
|
{
|
||||||
|
if (UAnimInstance* AnimInstance = MeshComp->GetAnimInstance())
|
||||||
|
{
|
||||||
|
if (FAnimMontageInstance* MontageInstance = AnimInstance->GetMontageInstanceForID(BranchingPointPayload.MontageInstanceID))
|
||||||
|
{
|
||||||
|
if (ACharacter* Character = Cast<ACharacter>(MeshComp->GetOwner()))
|
||||||
|
{
|
||||||
|
if (Character->GetCharacterMovement()->GetCurrentAcceleration().SizeSquared2D() > 10.0)
|
||||||
|
{
|
||||||
|
MontageInstance->PushDisableRootMotion();
|
||||||
|
|
||||||
|
IsRootMotionDisabled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UGCS_ANS_MovementCancellation::BranchingPointNotifyTick(FBranchingPointNotifyPayload& BranchingPointPayload, float FrameDeltaTime)
|
||||||
|
{
|
||||||
|
Super::BranchingPointNotifyTick(BranchingPointPayload, FrameDeltaTime);
|
||||||
|
if (USkeletalMeshComponent* MeshComp = BranchingPointPayload.SkelMeshComponent)
|
||||||
|
{
|
||||||
|
if (UAnimInstance* AnimInstance = MeshComp->GetAnimInstance())
|
||||||
|
{
|
||||||
|
if (FAnimMontageInstance* MontageInstance = AnimInstance->GetMontageInstanceForID(BranchingPointPayload.MontageInstanceID))
|
||||||
|
{
|
||||||
|
if (ACharacter* Character = Cast<ACharacter>(MeshComp->GetOwner())) {
|
||||||
|
if (Character->GetCharacterMovement()->GetCurrentAcceleration().SizeSquared2D() > 10.0) {
|
||||||
|
if (!IsRootMotionDisabled) {
|
||||||
|
MontageInstance->PushDisableRootMotion();
|
||||||
|
|
||||||
|
IsRootMotionDisabled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (IsRootMotionDisabled) {
|
||||||
|
MontageInstance->PopDisableRootMotion();
|
||||||
|
|
||||||
|
IsRootMotionDisabled = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UGCS_ANS_MovementCancellation::BranchingPointNotifyEnd(FBranchingPointNotifyPayload& BranchingPointPayload)
|
||||||
|
{
|
||||||
|
Super::BranchingPointNotifyEnd(BranchingPointPayload);
|
||||||
|
if (USkeletalMeshComponent* MeshComp = BranchingPointPayload.SkelMeshComponent)
|
||||||
|
{
|
||||||
|
if (UAnimInstance* AnimInstance = MeshComp->GetAnimInstance())
|
||||||
|
{
|
||||||
|
if (FAnimMontageInstance* MontageInstance = AnimInstance->GetMontageInstanceForID(BranchingPointPayload.MontageInstanceID))
|
||||||
|
{
|
||||||
|
if (IsRootMotionDisabled) {
|
||||||
|
IsRootMotionDisabled = false;
|
||||||
|
|
||||||
|
MontageInstance->PopDisableRootMotion();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UGCS_ANS_MovementCancellation::IsMoving_Implementation(USkeletalMeshComponent* MeshComp) const
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if WITH_EDITOR
|
||||||
|
|
||||||
|
|
||||||
|
bool UGCS_ANS_MovementCancellation::CanBePlaced(UAnimSequenceBase* Animation) const
|
||||||
|
{
|
||||||
|
return (Animation && Animation->IsA(UAnimMontage::StaticClass()));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||||
|
|
||||||
|
|
||||||
|
#include "Targeting/Filters/GCS_TargetingFilterTask_Affiliation.h"
|
||||||
|
#include "GCS_CombatSystemSettings.h"
|
||||||
|
#include "GameFramework/Pawn.h"
|
||||||
|
#include "GameFramework/Controller.h"
|
||||||
|
#include "Team/GCS_CombatTeamAgentInterface.h"
|
||||||
|
#include "Utility/GCS_CombatFunctionLibrary.h"
|
||||||
|
|
||||||
|
bool UGCS_TargetingFilterTask_Affiliation::ShouldFilterTarget(const FTargetingRequestHandle& TargetingHandle, const FTargetingDefaultResultData& TargetData) const
|
||||||
|
{
|
||||||
|
if (const UGCS_CombatSystemSettings* Settings = UGCS_CombatSystemSettings::Get())
|
||||||
|
{
|
||||||
|
if (Settings->bDisableAffiliationCheck)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
FGenericTeamId SourceTeamId = GetSourceTeamId(TargetingHandle, TargetData);
|
||||||
|
FGenericTeamId TargetTeamId = GetTargetTeamId(TargetingHandle, TargetData);
|
||||||
|
|
||||||
|
if (TargetTeamId == FGenericTeamId::NoTeam && bIgnoreTargetWithNoTeam)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return FAISenseAffiliationFilter::ShouldSenseTeam(SourceTeamId, TargetTeamId, DetectionByAffiliation.GetAsFlags()) == false;
|
||||||
|
}
|
||||||
|
|
||||||
|
FGenericTeamId UGCS_TargetingFilterTask_Affiliation::GetSourceTeamId(const FTargetingRequestHandle& TargetingHandle, const FTargetingDefaultResultData& TargetData) const
|
||||||
|
{
|
||||||
|
if (const FTargetingSourceContext* SourceContext = FTargetingSourceContext::Find(TargetingHandle))
|
||||||
|
{
|
||||||
|
AActor* Actor = SourceContext->InstigatorActor ? SourceContext->InstigatorActor : SourceContext->SourceActor;
|
||||||
|
if (IsValid(Actor))
|
||||||
|
{
|
||||||
|
return UGCS_CombatFunctionLibrary::QueryCombatTeamId(Actor, true, true);;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return FGenericTeamId::NoTeam;
|
||||||
|
}
|
||||||
|
|
||||||
|
FGenericTeamId UGCS_TargetingFilterTask_Affiliation::GetTargetTeamId(const FTargetingRequestHandle& TargetingHandle, const FTargetingDefaultResultData& TargetData) const
|
||||||
|
{
|
||||||
|
if (const AActor* TargetActor = TargetData.HitResult.GetActor())
|
||||||
|
{
|
||||||
|
return UGCS_CombatFunctionLibrary::QueryCombatTeamId(TargetActor, true, true);;
|
||||||
|
}
|
||||||
|
return FGenericTeamId::NoTeam;
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||||
|
|
||||||
|
|
||||||
|
#include "Targeting/Filters/GCS_TargetingFilterTask_IsDead.h"
|
||||||
|
|
||||||
|
#include "GCS_CombatEntityInterface.h"
|
||||||
|
#include "Utility/GCS_CombatFunctionLibrary.h"
|
||||||
|
|
||||||
|
bool UGCS_TargetingFilterTask_IsDead::ShouldFilterTarget(const FTargetingRequestHandle& TargetingHandle, const FTargetingDefaultResultData& TargetData) const
|
||||||
|
{
|
||||||
|
AActor* TargetActor = TargetData.HitResult.GetActor();
|
||||||
|
|
||||||
|
if (UObject* Implementer = UGCS_CombatFunctionLibrary::GetCombatEntity(TargetActor))
|
||||||
|
{
|
||||||
|
return IGCS_CombatEntityInterface::Execute_IsDead(Implementer);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||||
|
|
||||||
|
|
||||||
|
#include "Targeting/Filters/GCS_TargetingFilterTask_TagsRequirements.h"
|
||||||
|
|
||||||
|
#include "AbilitySystemComponent.h"
|
||||||
|
#include "AbilitySystemGlobals.h"
|
||||||
|
|
||||||
|
bool UGCS_TargetingFilterTask_TagsRequirements::ShouldFilterTarget(const FTargetingRequestHandle& TargetingHandle, const FTargetingDefaultResultData& TargetData) const
|
||||||
|
{
|
||||||
|
const AActor* TargetActor = TargetData.HitResult.GetActor();
|
||||||
|
|
||||||
|
if (UAbilitySystemComponent* ASC = UAbilitySystemGlobals::GetAbilitySystemComponentFromActor(TargetActor))
|
||||||
|
{
|
||||||
|
FGameplayTagContainer ActorTags;
|
||||||
|
ASC->GetOwnedGameplayTags(ActorTags);
|
||||||
|
|
||||||
|
return bInvert ? !TagQuery.Matches(ActorTags) : TagQuery.Matches(ActorTags);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bLookingForTagAssetInterface)
|
||||||
|
{
|
||||||
|
const IGameplayTagAssetInterface* TagAssetInterface = Cast<IGameplayTagAssetInterface>(TargetActor);
|
||||||
|
if (!TagAssetInterface)
|
||||||
|
{
|
||||||
|
const TArray<UActorComponent*> Components = TargetActor->GetComponentsByInterface(UGameplayTagAssetInterface::StaticClass());
|
||||||
|
TagAssetInterface = Components.IsValidIndex(0) ? Cast<IGameplayTagAssetInterface>(Components[0]) : nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (TagAssetInterface)
|
||||||
|
{
|
||||||
|
FGameplayTagContainer ActorTags;
|
||||||
|
TagAssetInterface->GetOwnedGameplayTags(ActorTags);
|
||||||
|
|
||||||
|
return bInvert ? !TagQuery.Matches(ActorTags) : TagQuery.Matches(ActorTags);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||||
|
|
||||||
|
|
||||||
|
#include "Targeting/Filters/GCS_TargetingFilterTask_TraceInstance.h"
|
||||||
|
|
||||||
|
#include "Collision/DEPRECATED_GCS_CollisionTraceInstance.h"
|
||||||
|
|
||||||
|
bool UGCS_TargetingFilterTask_TraceInstance::ShouldFilterTarget(const FTargetingRequestHandle& TargetingHandle, const FTargetingDefaultResultData& TargetData) const
|
||||||
|
{
|
||||||
|
const AActor* TargetActor = TargetData.HitResult.GetActor();
|
||||||
|
|
||||||
|
if (const FTargetingSourceContext* SourceContext = FTargetingSourceContext::Find(TargetingHandle))
|
||||||
|
{
|
||||||
|
if (const UDEPRECATED_GCS_CollisionTraceInstance* TraceInstance = Cast<UDEPRECATED_GCS_CollisionTraceInstance>(SourceContext->SourceObject))
|
||||||
|
{
|
||||||
|
return !TraceInstance->CanHitActor(TargetActor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||||
|
|
||||||
|
|
||||||
|
#include "Targeting/GCS_TargetingFunctionLibrary.h"
|
||||||
|
#include "Components/MeshComponent.h"
|
||||||
|
|
||||||
|
FTargetingSourceContext UGCS_TargetingFunctionLibrary::GetTargetingSourceContext(FTargetingRequestHandle TargetingHandle)
|
||||||
|
{
|
||||||
|
if (TargetingHandle.IsValid())
|
||||||
|
{
|
||||||
|
if (FTargetingSourceContext* SourceContext = FTargetingSourceContext::Find(TargetingHandle))
|
||||||
|
{
|
||||||
|
return *SourceContext;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return FTargetingSourceContext();
|
||||||
|
}
|
||||||
|
|
||||||
|
FString UGCS_TargetingFunctionLibrary::GetTargetingSourceContextDebugString(FTargetingRequestHandle TargetingHandle)
|
||||||
|
{
|
||||||
|
if (TargetingHandle.IsValid())
|
||||||
|
{
|
||||||
|
if (FTargetingSourceContext* SourceContext = FTargetingSourceContext::Find(TargetingHandle))
|
||||||
|
{
|
||||||
|
return FString::Format(TEXT("ctx(actor:{0},instigator:{1},source:{2})"), {
|
||||||
|
*GetNameSafe(SourceContext->SourceActor), *GetNameSafe(SourceContext->InstigatorActor), *GetNameSafe(SourceContext->SourceObject)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return TEXT("None");
|
||||||
|
}
|
||||||
|
|
||||||
|
void UGCS_TargetingFunctionLibrary::GetTargetingResultsActors(FTargetingRequestHandle TargetingHandle, TArray<AActor*>& Targets)
|
||||||
|
{
|
||||||
|
if (TargetingHandle.IsValid())
|
||||||
|
{
|
||||||
|
if (FTargetingDefaultResultsSet* Results = FTargetingDefaultResultsSet::Find(TargetingHandle))
|
||||||
|
{
|
||||||
|
for (const FTargetingDefaultResultData& ResultData : Results->TargetResults)
|
||||||
|
{
|
||||||
|
if (AActor* Target = ResultData.HitResult.GetActor())
|
||||||
|
{
|
||||||
|
Targets.Add(Target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UGCS_TargetingFunctionLibrary::GetTargetingResults(FTargetingRequestHandle TargetingHandle, TArray<FHitResult>& OutTargets)
|
||||||
|
{
|
||||||
|
if (TargetingHandle.IsValid())
|
||||||
|
{
|
||||||
|
if (FTargetingDefaultResultsSet* Results = FTargetingDefaultResultsSet::Find(TargetingHandle))
|
||||||
|
{
|
||||||
|
for (const FTargetingDefaultResultData& ResultData : Results->TargetResults)
|
||||||
|
{
|
||||||
|
OutTargets.Add(ResultData.HitResult);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FTargetingSourceContext UGCS_TargetingFunctionLibrary::ConvertTargetingLocationInfoToSourceContext(FGameplayAbilityTargetingLocationInfo LocationInfo)
|
||||||
|
{
|
||||||
|
FTargetingSourceContext Context = FTargetingSourceContext();
|
||||||
|
|
||||||
|
//Return or calculate based on LocationType.
|
||||||
|
switch (LocationInfo.LocationType)
|
||||||
|
{
|
||||||
|
case EGameplayAbilityTargetingLocationType::ActorTransform:
|
||||||
|
if (LocationInfo.SourceActor)
|
||||||
|
{
|
||||||
|
Context.SourceActor = LocationInfo.SourceActor;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case EGameplayAbilityTargetingLocationType::SocketTransform:
|
||||||
|
if (LocationInfo.SourceComponent)
|
||||||
|
{
|
||||||
|
// Bad socket name will just return component transform anyway, so we're safe
|
||||||
|
Context.SourceLocation = LocationInfo.SourceComponent->GetSocketTransform(LocationInfo.SourceSocketName).GetLocation();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case EGameplayAbilityTargetingLocationType::LiteralTransform:
|
||||||
|
Context.SourceLocation = LocationInfo.LiteralTransform.GetLocation();
|
||||||
|
default:
|
||||||
|
check(false);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Context;
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||||
|
|
||||||
|
|
||||||
|
#include "Targeting/GCS_TargetingSourceInterface.h"
|
||||||
|
|
||||||
|
|
||||||
|
// Add default functionality here for any IGCS_TargetingSourceInterface functions that are not pure virtual.
|
||||||
@@ -0,0 +1,412 @@
|
|||||||
|
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||||
|
|
||||||
|
|
||||||
|
#include "Targeting/GCS_TargetingSystemComponent.h"
|
||||||
|
|
||||||
|
#include "GCS_LogChannels.h"
|
||||||
|
#include "Kismet/KismetMathLibrary.h"
|
||||||
|
#include "Camera/CameraComponent.h"
|
||||||
|
#include "GameFramework/Character.h"
|
||||||
|
#include "Camera/PlayerCameraManager.h"
|
||||||
|
#include "Net/UnrealNetwork.h"
|
||||||
|
#include "TargetingSystem/TargetingSubsystem.h"
|
||||||
|
|
||||||
|
UGCS_TargetingSystemComponent::UGCS_TargetingSystemComponent(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer)
|
||||||
|
{
|
||||||
|
PrimaryComponentTick.bCanEverTick = true;
|
||||||
|
SetIsReplicatedByDefault(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
UGCS_TargetingSystemComponent* UGCS_TargetingSystemComponent::GetTargetingSystemComponent(const AActor* Actor)
|
||||||
|
{
|
||||||
|
return Actor ? Actor->FindComponentByClass<UGCS_TargetingSystemComponent>() : nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UGCS_TargetingSystemComponent::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
|
||||||
|
{
|
||||||
|
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
|
||||||
|
|
||||||
|
DOREPLIFETIME_CONDITION(ThisClass, TargetedActor, COND_OwnerOnly);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Called when the game starts
|
||||||
|
void UGCS_TargetingSystemComponent::BeginPlay()
|
||||||
|
{
|
||||||
|
Super::BeginPlay();
|
||||||
|
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Called every frame
|
||||||
|
void UGCS_TargetingSystemComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
|
||||||
|
{
|
||||||
|
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
|
||||||
|
|
||||||
|
RefreshTargeting(DeltaTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UGCS_TargetingSystemComponent::RefreshTargeting(float DeltaTime)
|
||||||
|
{
|
||||||
|
if (GetOwnerRole() >= ROLE_AutonomousProxy)
|
||||||
|
{
|
||||||
|
if (bAutoUpdatePotentialTargets || IsValid(TargetedActor))
|
||||||
|
{
|
||||||
|
RefreshPotentialTargets();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LocalBool = PotentialTargets.Contains(TargetedActor);
|
||||||
|
|
||||||
|
// Current TargetedActor no longer consider valid.
|
||||||
|
if (TargetedActor && !LocalBool)
|
||||||
|
{
|
||||||
|
OnLockOff();
|
||||||
|
SetTargetedActor(nullptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UGCS_TargetingSystemComponent::SearchForActorToTarget()
|
||||||
|
{
|
||||||
|
RefreshPotentialTargets();
|
||||||
|
SelectFromPotentialTargets();
|
||||||
|
//No target found/unlocked.
|
||||||
|
if (!IsValid(TargetedActor) && !bAutoUpdatePotentialTargets)
|
||||||
|
{
|
||||||
|
PotentialTargets.Empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AActor* UGCS_TargetingSystemComponent::SelectClosestActorFromPotentialTargets(float Radius) const
|
||||||
|
{
|
||||||
|
// 检查 Owner 是否有效
|
||||||
|
if (!GetOwner())
|
||||||
|
{
|
||||||
|
UE_LOG(LogTemp, Warning, TEXT("SelectClosestActorFromPotentialTargets: Owner is null"));
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查 PotentialTargets 是否为空
|
||||||
|
if (PotentialTargets.Num() == 0)
|
||||||
|
{
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 过滤有效 Actor 并在范围内
|
||||||
|
TArray<AActor*> FilteredPotentialTargets;
|
||||||
|
const FVector OwnerLocation = GetOwner()->GetActorLocation();
|
||||||
|
|
||||||
|
for (AActor* Actor : PotentialTargets)
|
||||||
|
{
|
||||||
|
// 检查 Actor 是否有效且未被销毁
|
||||||
|
if (IsValid(Actor))
|
||||||
|
{
|
||||||
|
float Distance = FVector::Dist(OwnerLocation, Actor->GetActorLocation());
|
||||||
|
if (Distance <= Radius)
|
||||||
|
{
|
||||||
|
FilteredPotentialTargets.Add(Actor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果过滤后没有有效目标
|
||||||
|
if (FilteredPotentialTargets.Num() == 0)
|
||||||
|
{
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 寻找最近的 Actor
|
||||||
|
AActor* ClosestActor = FilteredPotentialTargets[0];
|
||||||
|
float MinDistance = FVector::Dist(OwnerLocation, ClosestActor->GetActorLocation());
|
||||||
|
|
||||||
|
for (int32 i = 1; i < FilteredPotentialTargets.Num(); ++i)
|
||||||
|
{
|
||||||
|
AActor* CurrentActor = FilteredPotentialTargets[i];
|
||||||
|
if (IsValid(CurrentActor))
|
||||||
|
{
|
||||||
|
float Distance = FVector::Dist(OwnerLocation, CurrentActor->GetActorLocation());
|
||||||
|
if (Distance < MinDistance)
|
||||||
|
{
|
||||||
|
MinDistance = Distance;
|
||||||
|
ClosestActor = CurrentActor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ClosestActor;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UGCS_TargetingSystemComponent::FilterActorsWithPreset(UTargetingPreset* InTargetingPreset, const TArray<AActor*> InTargets, TArray<AActor*>& OutActors)
|
||||||
|
{
|
||||||
|
if (InTargetingPreset == nullptr)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (UTargetingSubsystem* TargetingSubsystem = UTargetingSubsystem::Get(GetWorld()))
|
||||||
|
{
|
||||||
|
FTargetingSourceContext SourceContext;
|
||||||
|
SourceContext.SourceActor = GetOwner();
|
||||||
|
|
||||||
|
FTargetingRequestHandle TargetingHandle = UTargetingSubsystem::MakeTargetRequestHandle(TargetingPreset, SourceContext);
|
||||||
|
|
||||||
|
if (TargetingHandle.IsValid() && InTargets.Num() > 0)
|
||||||
|
{
|
||||||
|
FTargetingDefaultResultsSet& TargetingResults = FTargetingDefaultResultsSet::FindOrAdd(TargetingHandle);
|
||||||
|
for (AActor* Target : InTargets)
|
||||||
|
{
|
||||||
|
if (!Target)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool bAddResult = !TargetingResults.TargetResults.ContainsByPredicate([Target](const FTargetingDefaultResultData& Data) -> bool
|
||||||
|
{
|
||||||
|
return (Data.HitResult.GetActor() == Target);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (bAddResult)
|
||||||
|
{
|
||||||
|
FTargetingDefaultResultData* ResultData = new(TargetingResults.TargetResults) FTargetingDefaultResultData();
|
||||||
|
ResultData->HitResult.HitObjectHandle = FActorInstanceHandle(Target);
|
||||||
|
ResultData->HitResult.Location = Target->GetActorLocation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
FTargetingRequestDelegate Delegate = FTargetingRequestDelegate::CreateWeakLambda(this, [&](FTargetingRequestHandle InTargetingHandle)
|
||||||
|
{
|
||||||
|
TargetingSubsystem->GetTargetingResultsActors(InTargetingHandle, OutActors);
|
||||||
|
});
|
||||||
|
|
||||||
|
FTargetingImmediateTaskData& ImmeidateTaskData = FTargetingImmediateTaskData::FindOrAdd(TargetingHandle);
|
||||||
|
ImmeidateTaskData.bReleaseOnCompletion = true;
|
||||||
|
|
||||||
|
TargetingSubsystem->ExecuteTargetingRequestWithHandle(TargetingHandle, Delegate);
|
||||||
|
}
|
||||||
|
|
||||||
|
return !OutActors.IsEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
void UGCS_TargetingSystemComponent::SetTargetedActor(AActor* NewActor)
|
||||||
|
{
|
||||||
|
SetTargetedActor(NewActor, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UGCS_TargetingSystemComponent::SetTargetedActor(AActor* NewActor, bool bSendRpc)
|
||||||
|
{
|
||||||
|
if (GetOwnerRole() < ROLE_AutonomousProxy)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bSendRpc)
|
||||||
|
{
|
||||||
|
if (GetOwnerRole() >= ROLE_Authority)
|
||||||
|
{
|
||||||
|
ClientSetTargetedActor(NewActor);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ServerSetTargetedActor(NewActor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TargetedActor = NewActor;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UGCS_TargetingSystemComponent::ClientSetTargetedActor_Implementation(AActor* NewActor)
|
||||||
|
{
|
||||||
|
SetTargetedActor(NewActor, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UGCS_TargetingSystemComponent::ServerSetTargetedActor_Implementation(AActor* NewActor)
|
||||||
|
{
|
||||||
|
SetTargetedActor(NewActor, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UGCS_TargetingSystemComponent::OnLockOff_Implementation()
|
||||||
|
{
|
||||||
|
OnTargetLockOffEvent.Broadcast(TargetedActor);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UGCS_TargetingSystemComponent::OnLockOn_Implementation()
|
||||||
|
{
|
||||||
|
OnTargetLockOnEvent.Broadcast(TargetedActor);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UGCS_TargetingSystemComponent::SelectFromPotentialTargets()
|
||||||
|
{
|
||||||
|
if (!IsValid(TargetedActor))
|
||||||
|
{
|
||||||
|
TMap<AActor*, float> LocalPotentialTargets;
|
||||||
|
|
||||||
|
for (TObjectPtr<AActor>& Elem : PotentialTargets)
|
||||||
|
{
|
||||||
|
if (CanBeTargeted(Elem))
|
||||||
|
{
|
||||||
|
LocalPotentialTargets.Add(Elem, UKismetMathLibrary::Abs(CalculateViewAngle(Elem)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (LocalPotentialTargets.Num() > 0)
|
||||||
|
{
|
||||||
|
TArray<AActor*> LocalTargets;
|
||||||
|
TArray<float> LocalAngles;
|
||||||
|
LocalPotentialTargets.GenerateKeyArray(LocalTargets);
|
||||||
|
LocalPotentialTargets.GenerateValueArray(LocalAngles);
|
||||||
|
|
||||||
|
int32 MinIndex;
|
||||||
|
const float MaxValue = FMath::Min<float>(LocalAngles, &MinIndex);
|
||||||
|
SetTargetedActor(LocalTargets[MinIndex]);
|
||||||
|
OnLockOn();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
OnLockOff();
|
||||||
|
SetTargetedActor(nullptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UGCS_TargetingSystemComponent::RefreshPotentialTargets()
|
||||||
|
{
|
||||||
|
DECLARE_SCOPE_CYCLE_COUNTER(TEXT("UGCS_TargetingSystemComponent::RefreshPotentialTargets"), UGCS_TargetingSystemComponent_RefreshPotentialTargets, STATGROUP_GCS)
|
||||||
|
TRACE_CPUPROFILER_EVENT_SCOPE_STR(__FUNCTION__)
|
||||||
|
if (TargetingPreset == nullptr)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (UTargetingSubsystem* TargetingSubsystem = UTargetingSubsystem::Get(GetWorld()))
|
||||||
|
{
|
||||||
|
FTargetingSourceContext SourceContext;
|
||||||
|
SourceContext.SourceActor = GetOwner();
|
||||||
|
SourceContext.SourceObject = this;
|
||||||
|
|
||||||
|
FTargetingRequestHandle TargetingHandle = UTargetingSubsystem::MakeTargetRequestHandle(TargetingPreset, SourceContext);
|
||||||
|
FTargetingRequestDelegate Delegate = FTargetingRequestDelegate::CreateWeakLambda(this, [this,TargetingSubsystem](FTargetingRequestHandle InTargetingHandle)
|
||||||
|
{
|
||||||
|
TArray<AActor*> Results;
|
||||||
|
TargetingSubsystem->GetTargetingResultsActors(InTargetingHandle, Results);
|
||||||
|
PotentialTargets.Empty();
|
||||||
|
PotentialTargets = Results;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (bUseAsyncTargeting)
|
||||||
|
{
|
||||||
|
FTargetingAsyncTaskData& AsyncTaskData = FTargetingAsyncTaskData::FindOrAdd(TargetingHandle);
|
||||||
|
AsyncTaskData.bReleaseOnCompletion = true;
|
||||||
|
|
||||||
|
TargetingSubsystem->StartAsyncTargetingRequestWithHandle(TargetingHandle, Delegate);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
FTargetingImmediateTaskData& ImmeidateTaskData = FTargetingImmediateTaskData::FindOrAdd(TargetingHandle);
|
||||||
|
ImmeidateTaskData.bReleaseOnCompletion = true;
|
||||||
|
|
||||||
|
TargetingSubsystem->ExecuteTargetingRequestWithHandle(TargetingHandle, Delegate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UGCS_TargetingSystemComponent::CanBeTargeted_Implementation(AActor* ActorToTarget)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void UGCS_TargetingSystemComponent::StaticSwitchToNewTarget(bool RightDirection)
|
||||||
|
{
|
||||||
|
if (TargetedActor)
|
||||||
|
{
|
||||||
|
TMap<AActor*, float> LocalPotentialTargets;
|
||||||
|
|
||||||
|
for (TObjectPtr<AActor>& Elem : PotentialTargets)
|
||||||
|
{
|
||||||
|
if (Elem != TargetedActor)
|
||||||
|
{
|
||||||
|
float LocalDistance = UKismetMathLibrary::Vector_Distance(GetOwner()->GetActorLocation(), Elem->GetActorLocation());
|
||||||
|
|
||||||
|
FRotator RequiredRotation = UKismetMathLibrary::FindLookAtRotation(GetOwner()->GetActorLocation(), Elem->GetActorLocation());
|
||||||
|
FRotator DeltaRotation;
|
||||||
|
|
||||||
|
ACharacter* LocalOwnerCharacter = Cast<ACharacter>(GetOwner());
|
||||||
|
|
||||||
|
DeltaRotation = UKismetMathLibrary::NormalizedDeltaRotator(LocalOwnerCharacter->GetControlRotation(), RequiredRotation);
|
||||||
|
|
||||||
|
if (RightDirection == 1)
|
||||||
|
{
|
||||||
|
if (DeltaRotation.Yaw < 0.f && DeltaRotation.Yaw > -100.f)
|
||||||
|
{
|
||||||
|
LocalPotentialTargets.Add(Elem, DeltaRotation.Yaw);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LocalPotentialTargets.Add(Elem, -10000.f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (DeltaRotation.Yaw > 0.f && DeltaRotation.Yaw < 100.f)
|
||||||
|
{
|
||||||
|
LocalPotentialTargets.Add(Elem, DeltaRotation.Yaw);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LocalPotentialTargets.Add(Elem, 10000.f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (LocalPotentialTargets.Num() > 0)
|
||||||
|
{
|
||||||
|
TArray<AActor*> LocalTargets;
|
||||||
|
TArray<float> LocalAngles;
|
||||||
|
LocalPotentialTargets.GenerateKeyArray(LocalTargets);
|
||||||
|
LocalPotentialTargets.GenerateValueArray(LocalAngles);
|
||||||
|
|
||||||
|
if (RightDirection == 1)
|
||||||
|
{
|
||||||
|
int32 FoundIndex;
|
||||||
|
const float MaxValue = FMath::Max<float>(LocalAngles, &FoundIndex);
|
||||||
|
if (MaxValue > -10000.f)
|
||||||
|
{
|
||||||
|
OnLockOff();
|
||||||
|
SetTargetedActor(LocalTargets[FoundIndex]);
|
||||||
|
OnLockOn();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
int32 FoundIndex;
|
||||||
|
const float MinValue = FMath::Min<float>(LocalAngles, &FoundIndex);
|
||||||
|
if (MinValue < 10000.f)
|
||||||
|
{
|
||||||
|
OnLockOff();
|
||||||
|
SetTargetedActor(LocalTargets[FoundIndex]);
|
||||||
|
OnLockOn();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
float UGCS_TargetingSystemComponent::CalculateViewAngle(const AActor* TargetActor)
|
||||||
|
{
|
||||||
|
APawn* OwningPawn = GetPawn<APawn>();
|
||||||
|
FRotator FinalRotation = FRotator::ZeroRotator;
|
||||||
|
if (TargetActor && OwningPawn)
|
||||||
|
{
|
||||||
|
FRotator RequiredRotation = UKismetMathLibrary::FindLookAtRotation(OwningPawn->GetPawnViewLocation(), TargetActor->GetActorLocation());
|
||||||
|
|
||||||
|
FRotator DeltaRotation = UKismetMathLibrary::NormalizedDeltaRotator(RequiredRotation, OwningPawn->GetViewRotation());
|
||||||
|
FinalRotation = DeltaRotation;
|
||||||
|
}
|
||||||
|
|
||||||
|
return UKismetMathLibrary::Abs(UKismetMathLibrary::Abs(FinalRotation.Yaw) + UKismetMathLibrary::Abs(FinalRotation.Pitch));
|
||||||
|
}
|
||||||
@@ -0,0 +1,369 @@
|
|||||||
|
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||||
|
|
||||||
|
#include "Targeting/Selections/GCS_TargetingSelectionTask_LineTrace.h"
|
||||||
|
|
||||||
|
#include "CollisionQueryParams.h"
|
||||||
|
#include "KismetTraceUtils.h"
|
||||||
|
#include "Components/PrimitiveComponent.h"
|
||||||
|
#include "Engine/EngineTypes.h"
|
||||||
|
#include "Engine/World.h"
|
||||||
|
#include "GameFramework/Actor.h"
|
||||||
|
#include "GameFramework/Pawn.h"
|
||||||
|
#include "Kismet/KismetSystemLibrary.h"
|
||||||
|
#include "TargetingSystem/TargetingSubsystem.h"
|
||||||
|
|
||||||
|
#if ENABLE_DRAW_DEBUG
|
||||||
|
#include "Engine/Canvas.h"
|
||||||
|
#endif // ENABLE_DRAW_DEBUG
|
||||||
|
|
||||||
|
|
||||||
|
UGCS_TargetingSelectionTask_LineTrace::UGCS_TargetingSelectionTask_LineTrace(const FObjectInitializer& ObjectInitializer)
|
||||||
|
: Super(ObjectInitializer)
|
||||||
|
{
|
||||||
|
bComplexTrace = false;
|
||||||
|
bIgnoreSourceActor = false;
|
||||||
|
bIgnoreInstigatorActor = false;
|
||||||
|
bGenerateDefaultHitResult = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UGCS_TargetingSelectionTask_LineTrace::Execute(const FTargetingRequestHandle& TargetingHandle) const
|
||||||
|
{
|
||||||
|
Super::Execute(TargetingHandle);
|
||||||
|
|
||||||
|
SetTaskAsyncState(TargetingHandle, ETargetingTaskAsyncState::Executing);
|
||||||
|
|
||||||
|
if (IsAsyncTargetingRequest(TargetingHandle))
|
||||||
|
{
|
||||||
|
ExecuteAsyncTrace(TargetingHandle);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ExecuteImmediateTrace(TargetingHandle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FVector UGCS_TargetingSelectionTask_LineTrace::GetSourceLocation_Implementation(const FTargetingRequestHandle& TargetingHandle) const
|
||||||
|
{
|
||||||
|
if (const FTargetingSourceContext* SourceContext = FTargetingSourceContext::Find(TargetingHandle))
|
||||||
|
{
|
||||||
|
if (SourceContext->SourceActor)
|
||||||
|
{
|
||||||
|
return SourceContext->SourceActor->GetActorLocation();
|
||||||
|
}
|
||||||
|
|
||||||
|
return SourceContext->SourceLocation;
|
||||||
|
}
|
||||||
|
|
||||||
|
return FVector::ZeroVector;
|
||||||
|
}
|
||||||
|
|
||||||
|
FVector UGCS_TargetingSelectionTask_LineTrace::GetSourceOffset_Implementation(const FTargetingRequestHandle& TargetingHandle) const
|
||||||
|
{
|
||||||
|
return DefaultSourceOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
FVector UGCS_TargetingSelectionTask_LineTrace::GetTraceDirection_Implementation(const FTargetingRequestHandle& TargetingHandle) const
|
||||||
|
{
|
||||||
|
if (const FTargetingSourceContext* SourceContext = FTargetingSourceContext::Find(TargetingHandle))
|
||||||
|
{
|
||||||
|
if (SourceContext->SourceActor)
|
||||||
|
{
|
||||||
|
if (APawn* Pawn = Cast<APawn>(SourceContext->SourceActor))
|
||||||
|
{
|
||||||
|
return Pawn->GetControlRotation().Vector();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return SourceContext->SourceActor->GetActorForwardVector();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return FVector::ZeroVector;
|
||||||
|
}
|
||||||
|
|
||||||
|
float UGCS_TargetingSelectionTask_LineTrace::GetTraceLength_Implementation(const FTargetingRequestHandle& TargetingHandle) const
|
||||||
|
{
|
||||||
|
return DefaultTraceLength.GetValueAtLevel(GetTraceLevel(TargetingHandle));
|
||||||
|
}
|
||||||
|
|
||||||
|
void UGCS_TargetingSelectionTask_LineTrace::GetAdditionalActorsToIgnore_Implementation(const FTargetingRequestHandle& TargetingHandle, TArray<AActor*>& OutAdditionalActorsToIgnore) const
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
float UGCS_TargetingSelectionTask_LineTrace::GetTraceLevel_Implementation(const FTargetingRequestHandle& TargetingHandle) const
|
||||||
|
{
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UGCS_TargetingSelectionTask_LineTrace::ExecuteImmediateTrace(const FTargetingRequestHandle& TargetingHandle) const
|
||||||
|
{
|
||||||
|
if (UWorld* World = GetSourceContextWorld(TargetingHandle))
|
||||||
|
{
|
||||||
|
#if ENABLE_DRAW_DEBUG
|
||||||
|
ResetTraceResultsDebugString(TargetingHandle);
|
||||||
|
#endif // ENABLE_DRAW_DEBUG
|
||||||
|
|
||||||
|
const FVector Direction = GetTraceDirection(TargetingHandle).GetSafeNormal();
|
||||||
|
const FVector Start = (GetSourceLocation(TargetingHandle) + GetSourceOffset(TargetingHandle));
|
||||||
|
const FVector End = Start + (Direction * GetTraceLength(TargetingHandle));
|
||||||
|
|
||||||
|
FCollisionQueryParams Params(SCENE_QUERY_STAT(ExecuteImmediateTrace), bComplexTrace);
|
||||||
|
InitCollisionParams(TargetingHandle, Params);
|
||||||
|
|
||||||
|
bool bHasBlockingHit = false;
|
||||||
|
TArray<FHitResult> Hits;
|
||||||
|
if (CollisionProfileName.Name != TEXT("NoCollision"))
|
||||||
|
{
|
||||||
|
bHasBlockingHit = World->LineTraceMultiByProfile(Hits, Start, End, CollisionProfileName.Name, Params);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const ECollisionChannel CollisionChannel = UEngineTypes::ConvertToCollisionChannel(TraceChannel);
|
||||||
|
bHasBlockingHit = World->LineTraceMultiByChannel(Hits, Start, End, CollisionChannel, Params);
|
||||||
|
}
|
||||||
|
|
||||||
|
#if ENABLE_DRAW_DEBUG
|
||||||
|
DrawDebugTrace(TargetingHandle, Start, End, bHasBlockingHit, Hits);
|
||||||
|
#endif // ENABLE_DRAW_DEBUG
|
||||||
|
|
||||||
|
ProcessHitResults(TargetingHandle, Hits);
|
||||||
|
}
|
||||||
|
|
||||||
|
SetTaskAsyncState(TargetingHandle, ETargetingTaskAsyncState::Completed);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UGCS_TargetingSelectionTask_LineTrace::ExecuteAsyncTrace(const FTargetingRequestHandle& TargetingHandle) const
|
||||||
|
{
|
||||||
|
if (UWorld* World = GetSourceContextWorld(TargetingHandle))
|
||||||
|
{
|
||||||
|
AActor* SourceActor = nullptr;
|
||||||
|
if (const FTargetingSourceContext* SourceContext = FTargetingSourceContext::Find(TargetingHandle))
|
||||||
|
{
|
||||||
|
SourceActor = SourceContext->SourceActor;
|
||||||
|
}
|
||||||
|
const FVector Direction = GetTraceDirection(TargetingHandle).GetSafeNormal();
|
||||||
|
const FVector Start = (GetSourceLocation(TargetingHandle) + GetSourceOffset(TargetingHandle));
|
||||||
|
const FVector End = Start + (Direction * GetTraceLength(TargetingHandle));
|
||||||
|
|
||||||
|
FCollisionQueryParams Params(SCENE_QUERY_STAT(ExecuteAsyncTrace), bComplexTrace);
|
||||||
|
InitCollisionParams(TargetingHandle, Params);
|
||||||
|
|
||||||
|
FTraceDelegate Delegate = FTraceDelegate::CreateUObject(this, &UGCS_TargetingSelectionTask_LineTrace::HandleAsyncTraceComplete, TargetingHandle);
|
||||||
|
if (CollisionProfileName.Name != TEXT("NoCollision"))
|
||||||
|
{
|
||||||
|
World->AsyncLineTraceByProfile(EAsyncTraceType::Multi, Start, End, CollisionProfileName.Name, Params, &Delegate);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const ECollisionChannel CollisionChannel = UEngineTypes::ConvertToCollisionChannel(TraceChannel);
|
||||||
|
World->AsyncLineTraceByChannel(EAsyncTraceType::Multi, Start, End, CollisionChannel, Params, FCollisionResponseParams::DefaultResponseParam, &Delegate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SetTaskAsyncState(TargetingHandle, ETargetingTaskAsyncState::Completed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UGCS_TargetingSelectionTask_LineTrace::HandleAsyncTraceComplete(const FTraceHandle& InTraceHandle, FTraceDatum& InTraceDatum, FTargetingRequestHandle TargetingHandle) const
|
||||||
|
{
|
||||||
|
if (TargetingHandle.IsValid())
|
||||||
|
{
|
||||||
|
#if ENABLE_DRAW_DEBUG
|
||||||
|
ResetTraceResultsDebugString(TargetingHandle);
|
||||||
|
|
||||||
|
// We have to manually find if there is a blocking hit.
|
||||||
|
bool bHasBlockingHit = false;
|
||||||
|
for (const FHitResult& HitResult : InTraceDatum.OutHits)
|
||||||
|
{
|
||||||
|
if (HitResult.bBlockingHit)
|
||||||
|
{
|
||||||
|
bHasBlockingHit = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DrawDebugTrace(TargetingHandle, InTraceDatum.Start, InTraceDatum.End, bHasBlockingHit, InTraceDatum.OutHits);
|
||||||
|
|
||||||
|
#endif // ENABLE_DRAW_DEBUG
|
||||||
|
|
||||||
|
ProcessHitResults(TargetingHandle, InTraceDatum.OutHits);
|
||||||
|
}
|
||||||
|
|
||||||
|
SetTaskAsyncState(TargetingHandle, ETargetingTaskAsyncState::Completed);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UGCS_TargetingSelectionTask_LineTrace::ProcessHitResults(const FTargetingRequestHandle& TargetingHandle, const TArray<FHitResult>& Hits) const
|
||||||
|
{
|
||||||
|
if (!TargetingHandle.IsValid())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
FTargetingDefaultResultsSet& TargetingResults = FTargetingDefaultResultsSet::FindOrAdd(TargetingHandle);
|
||||||
|
|
||||||
|
if (Hits.Num() > 0)
|
||||||
|
{
|
||||||
|
for (const FHitResult& HitResult : Hits)
|
||||||
|
{
|
||||||
|
if (!HitResult.GetActor())
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool bAddResult = true;
|
||||||
|
for (const FTargetingDefaultResultData& ResultData : TargetingResults.TargetResults)
|
||||||
|
{
|
||||||
|
if (ResultData.HitResult.GetActor() == HitResult.GetActor())
|
||||||
|
{
|
||||||
|
bAddResult = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bAddResult)
|
||||||
|
{
|
||||||
|
FTargetingDefaultResultData* ResultData = new(TargetingResults.TargetResults) FTargetingDefaultResultData();
|
||||||
|
ResultData->HitResult = HitResult;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if ENABLE_DRAW_DEBUG
|
||||||
|
BuildTraceResultsDebugString(TargetingHandle, TargetingResults.TargetResults);
|
||||||
|
#endif // ENABLE_DRAW_DEBUG
|
||||||
|
}
|
||||||
|
else if (bGenerateDefaultHitResult)
|
||||||
|
{
|
||||||
|
// If there were no hits, add a default HitResult at the end of the trace
|
||||||
|
FHitResult HitResult;
|
||||||
|
const FVector Start = (GetSourceLocation(TargetingHandle) + GetSourceOffset(TargetingHandle));
|
||||||
|
const FVector End = Start + (GetTraceDirection(TargetingHandle) * GetTraceLength(TargetingHandle));
|
||||||
|
// Start param could be player ViewPoint. We want HitResult to always display the StartLocation.
|
||||||
|
HitResult.TraceStart = Start;
|
||||||
|
HitResult.TraceEnd = End;
|
||||||
|
HitResult.Location = End;
|
||||||
|
HitResult.ImpactPoint = End;
|
||||||
|
FTargetingDefaultResultData* ResultData = new(TargetingResults.TargetResults) FTargetingDefaultResultData();
|
||||||
|
ResultData->HitResult = HitResult;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UGCS_TargetingSelectionTask_LineTrace::InitCollisionParams(const FTargetingRequestHandle& TargetingHandle, FCollisionQueryParams& OutParams) const
|
||||||
|
{
|
||||||
|
if (const FTargetingSourceContext* SourceContext = FTargetingSourceContext::Find(TargetingHandle))
|
||||||
|
{
|
||||||
|
if (bIgnoreSourceActor && SourceContext->SourceActor)
|
||||||
|
{
|
||||||
|
OutParams.AddIgnoredActor(SourceContext->SourceActor);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bIgnoreInstigatorActor && SourceContext->InstigatorActor)
|
||||||
|
{
|
||||||
|
OutParams.AddIgnoredActor(SourceContext->InstigatorActor);
|
||||||
|
}
|
||||||
|
|
||||||
|
TArray<AActor*> AdditionalActorsToIgnoreArray;
|
||||||
|
GetAdditionalActorsToIgnore(TargetingHandle, AdditionalActorsToIgnoreArray);
|
||||||
|
|
||||||
|
if (AdditionalActorsToIgnoreArray.Num() > 0)
|
||||||
|
{
|
||||||
|
OutParams.AddIgnoredActors(AdditionalActorsToIgnoreArray);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if WITH_EDITOR
|
||||||
|
bool UGCS_TargetingSelectionTask_LineTrace::CanEditChange(const FProperty* InProperty) const
|
||||||
|
{
|
||||||
|
bool bCanEdit = Super::CanEditChange(InProperty);
|
||||||
|
|
||||||
|
if (bCanEdit && InProperty)
|
||||||
|
{
|
||||||
|
const FName PropertyName = InProperty->GetFName();
|
||||||
|
|
||||||
|
if (PropertyName == GET_MEMBER_NAME_CHECKED(UGCS_TargetingSelectionTask_LineTrace, TraceChannel))
|
||||||
|
{
|
||||||
|
return (CollisionProfileName.Name == TEXT("NoCollision"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
#endif // WITH_EDITOR
|
||||||
|
|
||||||
|
|
||||||
|
#if ENABLE_DRAW_DEBUG
|
||||||
|
void UGCS_TargetingSelectionTask_LineTrace::DrawDebug(UTargetingSubsystem* TargetingSubsystem, FTargetingDebugInfo& Info, const FTargetingRequestHandle& TargetingHandle, float XOffset, float YOffset,
|
||||||
|
int32 MinTextRowsToAdvance) const
|
||||||
|
{
|
||||||
|
#if WITH_EDITORONLY_DATA
|
||||||
|
if (UTargetingSubsystem::IsTargetingDebugEnabled())
|
||||||
|
{
|
||||||
|
FTargetingDebugData& DebugData = FTargetingDebugData::FindOrAdd(TargetingHandle);
|
||||||
|
FString& ScratchPadString = DebugData.DebugScratchPadStrings.FindOrAdd(GetNameSafe(this));
|
||||||
|
if (!ScratchPadString.IsEmpty())
|
||||||
|
{
|
||||||
|
if (Info.Canvas)
|
||||||
|
{
|
||||||
|
Info.Canvas->SetDrawColor(FColor::Yellow);
|
||||||
|
}
|
||||||
|
|
||||||
|
FString TaskString = FString::Printf(TEXT("Results : %s"), *ScratchPadString);
|
||||||
|
TargetingSubsystem->DebugLine(Info, TaskString, XOffset, YOffset, MinTextRowsToAdvance);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif // WITH_EDITORONLY_DATA
|
||||||
|
}
|
||||||
|
|
||||||
|
void UGCS_TargetingSelectionTask_LineTrace::DrawDebugTrace(const FTargetingRequestHandle TargetingHandle, const FVector& StartLocation, const FVector& EndLocation, const bool bHit,
|
||||||
|
const TArray<FHitResult>& Hits) const
|
||||||
|
{
|
||||||
|
if (UTargetingSubsystem::IsTargetingDebugEnabled())
|
||||||
|
{
|
||||||
|
if (UWorld* World = GetSourceContextWorld(TargetingHandle))
|
||||||
|
{
|
||||||
|
const float DrawTime = UTargetingSubsystem::GetOverrideTargetingLifeTime();
|
||||||
|
const EDrawDebugTrace::Type DrawDebugType = DrawTime <= 0.0f ? EDrawDebugTrace::Type::ForOneFrame : EDrawDebugTrace::Type::ForDuration;
|
||||||
|
const FLinearColor TraceColor = FLinearColor::Red;
|
||||||
|
const FLinearColor TraceHitColor = FLinearColor::Green;
|
||||||
|
DrawDebugLineTraceMulti(World, StartLocation, EndLocation, DrawDebugType, bHit, Hits, TraceColor, TraceHitColor, DrawTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UGCS_TargetingSelectionTask_LineTrace::BuildTraceResultsDebugString(const FTargetingRequestHandle& TargetingHandle, const TArray<FTargetingDefaultResultData>& TargetResults) const
|
||||||
|
{
|
||||||
|
#if WITH_EDITORONLY_DATA
|
||||||
|
if (UTargetingSubsystem::IsTargetingDebugEnabled())
|
||||||
|
{
|
||||||
|
FTargetingDebugData& DebugData = FTargetingDebugData::FindOrAdd(TargetingHandle);
|
||||||
|
FString& ScratchPadString = DebugData.DebugScratchPadStrings.FindOrAdd(GetNameSafe(this));
|
||||||
|
|
||||||
|
for (const FTargetingDefaultResultData& TargetData : TargetResults)
|
||||||
|
{
|
||||||
|
if (const AActor* Target = TargetData.HitResult.GetActor())
|
||||||
|
{
|
||||||
|
if (ScratchPadString.IsEmpty())
|
||||||
|
{
|
||||||
|
ScratchPadString = FString::Printf(TEXT("%s"), *GetNameSafe(Target));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ScratchPadString += FString::Printf(TEXT(", %s"), *GetNameSafe(Target));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif // WITH_EDITORONLY_DATA
|
||||||
|
}
|
||||||
|
|
||||||
|
void UGCS_TargetingSelectionTask_LineTrace::ResetTraceResultsDebugString(const FTargetingRequestHandle& TargetingHandle) const
|
||||||
|
{
|
||||||
|
#if WITH_EDITORONLY_DATA
|
||||||
|
FTargetingDebugData& DebugData = FTargetingDebugData::FindOrAdd(TargetingHandle);
|
||||||
|
FString& ScratchPadString = DebugData.DebugScratchPadStrings.FindOrAdd(GetNameSafe(this));
|
||||||
|
ScratchPadString.Reset();
|
||||||
|
#endif // WITH_EDITORONLY_DATA
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // ENABLE_DRAW_DEBUG
|
||||||
@@ -0,0 +1,120 @@
|
|||||||
|
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||||
|
|
||||||
|
|
||||||
|
#include "Targeting/Selections/GCS_TargetingSelectionTask_TraceExt.h"
|
||||||
|
|
||||||
|
#include "GCS_LogChannels.h"
|
||||||
|
#include "Collision/DEPRECATED_GCS_CollisionTraceInstance.h"
|
||||||
|
#include "Targeting/GCS_TargetingSourceInterface.h"
|
||||||
|
|
||||||
|
UDEPRECATED_GCS_CollisionTraceInstance* UGCS_TargetingSelectionTask_TraceExt::GetSourceTraceInstance_Implementation(const FTargetingRequestHandle& TargetingHandle) const
|
||||||
|
{
|
||||||
|
if (const FTargetingSourceContext* SourceContext = FTargetingSourceContext::Find(TargetingHandle))
|
||||||
|
{
|
||||||
|
if (SourceContext->SourceObject)
|
||||||
|
{
|
||||||
|
return Cast<UDEPRECATED_GCS_CollisionTraceInstance>(SourceContext->SourceObject);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
UE_LOG(LogGCS, Error, TEXT("No valid CollisionTraceInstance passed in as SourceObject! TargetingPreset:%s"), *GetOuter()->GetName());
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UGCS_TargetingSelectionTask_TraceExt::Execute(const FTargetingRequestHandle& TargetingHandle) const
|
||||||
|
{
|
||||||
|
Super::Execute(TargetingHandle);
|
||||||
|
}
|
||||||
|
|
||||||
|
FVector UGCS_TargetingSelectionTask_TraceExt::GetSourceLocation_Implementation(const FTargetingRequestHandle& TargetingHandle) const
|
||||||
|
{
|
||||||
|
if (const FTargetingSourceContext* SourceContext = FTargetingSourceContext::Find(TargetingHandle))
|
||||||
|
{
|
||||||
|
if (bUseContextLocationAsSourceLocation)
|
||||||
|
{
|
||||||
|
return SourceContext->SourceLocation;
|
||||||
|
}
|
||||||
|
if (SourceContext->SourceActor)
|
||||||
|
{
|
||||||
|
return SourceContext->SourceActor->GetActorLocation();
|
||||||
|
}
|
||||||
|
|
||||||
|
return SourceContext->SourceLocation;
|
||||||
|
}
|
||||||
|
|
||||||
|
return FVector::ZeroVector;
|
||||||
|
}
|
||||||
|
|
||||||
|
FVector UGCS_TargetingSelectionTask_TraceExt::GetTraceDirection_Implementation(const FTargetingRequestHandle& TargetingHandle) const
|
||||||
|
{
|
||||||
|
if (const FTargetingSourceContext* SourceContext = FTargetingSourceContext::Find(TargetingHandle))
|
||||||
|
{
|
||||||
|
if (SourceContext->SourceObject && SourceContext->SourceObject->GetClass()->ImplementsInterface(UGCS_TargetingSourceInterface::StaticClass()))
|
||||||
|
{
|
||||||
|
FVector TraceDirection;
|
||||||
|
if (IGCS_TargetingSourceInterface::Execute_GetTraceDirection(SourceContext->SourceObject, TraceDirection))
|
||||||
|
{
|
||||||
|
return TraceDirection;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return SourceContext->SourceLocation;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Super::GetTraceDirection_Implementation(TargetingHandle);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UGCS_TargetingSelectionTask_TraceExt::GetAdditionalActorsToIgnore_Implementation(const FTargetingRequestHandle& TargetingHandle, TArray<AActor*>& OutAdditionalActorsToIgnore) const
|
||||||
|
{
|
||||||
|
if (const FTargetingSourceContext* SourceContext = FTargetingSourceContext::Find(TargetingHandle))
|
||||||
|
{
|
||||||
|
if (SourceContext->SourceObject && SourceContext->SourceObject->GetClass()->ImplementsInterface(UGCS_TargetingSourceInterface::StaticClass()))
|
||||||
|
{
|
||||||
|
OutAdditionalActorsToIgnore.Append(IGCS_TargetingSourceInterface::Execute_GetAdditionalActorsToIgnore(SourceContext->SourceObject));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
float UGCS_TargetingSelectionTask_TraceExt::GetTraceLevel_Implementation(const FTargetingRequestHandle& TargetingHandle) const
|
||||||
|
{
|
||||||
|
if (const FTargetingSourceContext* SourceContext = FTargetingSourceContext::Find(TargetingHandle))
|
||||||
|
{
|
||||||
|
if (!IsValid(SourceContext->SourceObject))
|
||||||
|
{
|
||||||
|
UE_LOG(LogGCS, Error, TEXT("No valid Context Source Object found! TargetingPreset:%s"), *GetOuter()->GetName());
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (!SourceContext->SourceObject->GetClass()->ImplementsInterface(UGCS_TargetingSourceInterface::StaticClass()))
|
||||||
|
{
|
||||||
|
UE_LOG(LogGCS, Error, TEXT("Source Object(%s) doesn't implements GCS_TargetingSourceInterface.! TargetingPreset:%s"),
|
||||||
|
*SourceContext->SourceObject->GetName(), *GetOuter()->GetName());
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
float UGCS_TargetingSelectionTask_TraceExt::GetTraceLength_Implementation(const FTargetingRequestHandle& TargetingHandle) const
|
||||||
|
{
|
||||||
|
return bTraceLengthLevel ? DefaultTraceLength.GetValueAtLevel(GetTraceLevel(TargetingHandle)) : DefaultTraceLength.GetValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
float UGCS_TargetingSelectionTask_TraceExt::GetSweptTraceRadius_Implementation(const FTargetingRequestHandle& TargetingHandle) const
|
||||||
|
{
|
||||||
|
return bSweptTraceRadiusLevel ? DefaultSweptTraceRadius.GetValueAtLevel(GetTraceLevel(TargetingHandle)) : DefaultSweptTraceRadius.GetValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
float UGCS_TargetingSelectionTask_TraceExt::GetSweptTraceCapsuleHalfHeight_Implementation(const FTargetingRequestHandle& TargetingHandle) const
|
||||||
|
{
|
||||||
|
return bSweptTraceCapsuleHalfHeightLevel ? DefaultSweptTraceCapsuleHalfHeight.GetValueAtLevel(GetTraceLevel(TargetingHandle)) : DefaultSweptTraceCapsuleHalfHeight.GetValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
FVector UGCS_TargetingSelectionTask_TraceExt::GetSweptTraceBoxHalfExtents_Implementation(const FTargetingRequestHandle& TargetingHandle) const
|
||||||
|
{
|
||||||
|
if (bSweptTraceBoxHalfExtentLevel)
|
||||||
|
{
|
||||||
|
float Level = GetTraceLevel(TargetingHandle);
|
||||||
|
return FVector(DefaultSweptTraceBoxHalfExtentX.GetValueAtLevel(Level), DefaultSweptTraceBoxHalfExtentY.GetValueAtLevel(Level), DefaultSweptTraceBoxHalfExtentZ.GetValueAtLevel(Level));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Super::GetSweptTraceBoxHalfExtents_Implementation(TargetingHandle);
|
||||||
|
}
|
||||||
@@ -0,0 +1,212 @@
|
|||||||
|
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||||
|
|
||||||
|
|
||||||
|
#include "Targeting/Selections/GCS_TargetingSelectionTask_TraceExt_BindShape.h"
|
||||||
|
|
||||||
|
#include "GCS_LogChannels.h"
|
||||||
|
#include "Components/BoxComponent.h"
|
||||||
|
#include "Components/CapsuleComponent.h"
|
||||||
|
#include "Components/SphereComponent.h"
|
||||||
|
#include "Components/ShapeComponent.h"
|
||||||
|
#include "Misc/DataValidation.h"
|
||||||
|
#include "Targeting/GCS_TargetingFunctionLibrary.h"
|
||||||
|
#include "Targeting/GCS_TargetingSourceInterface.h"
|
||||||
|
|
||||||
|
void UGCS_TargetingSelectionTask_TraceExt_BindShape::Execute(const FTargetingRequestHandle& TargetingHandle) const
|
||||||
|
{
|
||||||
|
bool bMatchShapeType = true;
|
||||||
|
UShapeComponent* Shape = GetTraceShape(TargetingHandle);
|
||||||
|
|
||||||
|
if (!IsValid(Shape))
|
||||||
|
{
|
||||||
|
GCS_LOG(Warning, "")
|
||||||
|
bMatchShapeType = false;
|
||||||
|
}
|
||||||
|
if (TraceType == ETargetingTraceType::Box && !Shape->IsA<UBoxComponent>())
|
||||||
|
{
|
||||||
|
GCS_LOG(Warning, "%s: Trace type mismatched! want Box, got %s. %s",
|
||||||
|
*GetNameSafe(GetOuter()),
|
||||||
|
*GetNameSafe(Shape->GetClass()),
|
||||||
|
*UGCS_TargetingFunctionLibrary::GetTargetingSourceContextDebugString(TargetingHandle)
|
||||||
|
)
|
||||||
|
bMatchShapeType = false;
|
||||||
|
}
|
||||||
|
if (TraceType == ETargetingTraceType::Capsule && !Shape->IsA<UCapsuleComponent>())
|
||||||
|
{
|
||||||
|
GCS_LOG(Warning, "%s: Trace type mismatched! want Capsule, got %s. %s",
|
||||||
|
*GetNameSafe(GetOuter()),
|
||||||
|
*GetNameSafe(Shape->GetClass()),
|
||||||
|
*UGCS_TargetingFunctionLibrary::GetTargetingSourceContextDebugString(TargetingHandle)
|
||||||
|
)
|
||||||
|
bMatchShapeType = false;
|
||||||
|
}
|
||||||
|
if (TraceType == ETargetingTraceType::Sphere && !Shape->IsA<USphereComponent>())
|
||||||
|
{
|
||||||
|
GCS_LOG(Warning, "%s: Trace type mismatched! want Capsule, got %s. %s",
|
||||||
|
*GetNameSafe(GetOuter()),
|
||||||
|
*GetNameSafe(Shape->GetClass()),
|
||||||
|
*UGCS_TargetingFunctionLibrary::GetTargetingSourceContextDebugString(TargetingHandle)
|
||||||
|
)
|
||||||
|
bMatchShapeType = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bMatchShapeType)
|
||||||
|
{
|
||||||
|
Super::Execute(TargetingHandle);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SetTaskAsyncState(TargetingHandle, ETargetingTaskAsyncState::Completed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
float UGCS_TargetingSelectionTask_TraceExt_BindShape::GetSweptTraceRadius_Implementation(const FTargetingRequestHandle& TargetingHandle) const
|
||||||
|
{
|
||||||
|
float BaseValue = -1;
|
||||||
|
|
||||||
|
if (USphereComponent* Shape = Cast<USphereComponent>(GetTraceShape(TargetingHandle)))
|
||||||
|
{
|
||||||
|
BaseValue = Shape->GetScaledSphereRadius();
|
||||||
|
}
|
||||||
|
if (const UCapsuleComponent* Shape = Cast<UCapsuleComponent>(GetTraceShape(TargetingHandle)))
|
||||||
|
{
|
||||||
|
BaseValue = Shape->GetScaledCapsuleRadius();
|
||||||
|
}
|
||||||
|
|
||||||
|
float Value = Super::GetSweptTraceRadius_Implementation(TargetingHandle);
|
||||||
|
|
||||||
|
if (BaseValue < 0)
|
||||||
|
{
|
||||||
|
return Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (SweptTraceRadiusModType == EGCS_TraceDataModifyType::Add)
|
||||||
|
{
|
||||||
|
return BaseValue + Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (SweptTraceRadiusModType == EGCS_TraceDataModifyType::Multiply)
|
||||||
|
{
|
||||||
|
return BaseValue * Value;
|
||||||
|
}
|
||||||
|
return BaseValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
float UGCS_TargetingSelectionTask_TraceExt_BindShape::GetSweptTraceCapsuleHalfHeight_Implementation(const FTargetingRequestHandle& TargetingHandle) const
|
||||||
|
{
|
||||||
|
float BaseValue = -1;
|
||||||
|
|
||||||
|
if (const UCapsuleComponent* Capsule = Cast<UCapsuleComponent>(GetTraceShape(TargetingHandle)))
|
||||||
|
{
|
||||||
|
BaseValue = Capsule->GetScaledCapsuleHalfHeight();
|
||||||
|
}
|
||||||
|
|
||||||
|
float Value = Super::GetSweptTraceCapsuleHalfHeight_Implementation(TargetingHandle);
|
||||||
|
|
||||||
|
if (BaseValue < 0)
|
||||||
|
{
|
||||||
|
return Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (SweptTraceCapsuleHalfHeightModType == EGCS_TraceDataModifyType::Add)
|
||||||
|
{
|
||||||
|
return BaseValue + Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (SweptTraceCapsuleHalfHeightModType == EGCS_TraceDataModifyType::Multiply)
|
||||||
|
{
|
||||||
|
return BaseValue * Value;
|
||||||
|
}
|
||||||
|
return BaseValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
FVector UGCS_TargetingSelectionTask_TraceExt_BindShape::GetSweptTraceBoxHalfExtents_Implementation(const FTargetingRequestHandle& TargetingHandle) const
|
||||||
|
{
|
||||||
|
FVector BaseValue = FVector::ZeroVector;
|
||||||
|
|
||||||
|
if (const UBoxComponent* Shape = Cast<UBoxComponent>(GetTraceShape(TargetingHandle)))
|
||||||
|
{
|
||||||
|
BaseValue = Shape->GetScaledBoxExtent();
|
||||||
|
}
|
||||||
|
|
||||||
|
FVector Value = Super::GetSweptTraceBoxHalfExtents_Implementation(TargetingHandle);
|
||||||
|
|
||||||
|
if (BaseValue == FVector::ZeroVector)
|
||||||
|
{
|
||||||
|
return Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (SweptTraceBoxHalfExtentModType == EGCS_TraceDataModifyType::Add)
|
||||||
|
{
|
||||||
|
return BaseValue + Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (SweptTraceBoxHalfExtentModType == EGCS_TraceDataModifyType::Multiply)
|
||||||
|
{
|
||||||
|
return BaseValue * Value;
|
||||||
|
}
|
||||||
|
return BaseValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
UShapeComponent* UGCS_TargetingSelectionTask_TraceExt_BindShape::GetTraceShape(const FTargetingRequestHandle& TargetingHandle) const
|
||||||
|
{
|
||||||
|
UShapeComponent* ShapeComponent = nullptr;
|
||||||
|
if (const FTargetingSourceContext* SourceContext = FTargetingSourceContext::Find(TargetingHandle))
|
||||||
|
{
|
||||||
|
if (SourceContext->SourceObject)
|
||||||
|
{
|
||||||
|
if (SourceContext->SourceObject->GetClass()->ImplementsInterface(UGCS_TargetingSourceInterface::StaticClass()))
|
||||||
|
{
|
||||||
|
if (IGCS_TargetingSourceInterface::Execute_GetTraceShape(SourceContext->SourceObject, ShapeComponent))
|
||||||
|
{
|
||||||
|
return ShapeComponent;
|
||||||
|
}
|
||||||
|
UE_LOG(LogGCS, VeryVerbose, TEXT("Source Object(%s) doesn't provide valid ShapeComponent! TargetingPreset:%s"),
|
||||||
|
*SourceContext->SourceObject->GetName(), *GetOuter()->GetName());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
UE_LOG(LogGCS, VeryVerbose, TEXT("Source Object(%s) doesn't implements GCS_TargetingSourceInterface.! TargetingPreset:%s"),
|
||||||
|
*SourceContext->SourceObject->GetName(), *GetOuter()->GetName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
UE_LOG(LogGCS, Error, TEXT("No valid Context Source Object found! TargetingPreset:%s"), *GetOuter()->GetName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
FRotator UGCS_TargetingSelectionTask_TraceExt_BindShape::GetSweptTraceRotation_Implementation(const FTargetingRequestHandle& TargetingHandle) const
|
||||||
|
{
|
||||||
|
if (const FTargetingSourceContext* SourceContext = FTargetingSourceContext::Find(TargetingHandle))
|
||||||
|
{
|
||||||
|
if (SourceContext->SourceObject && SourceContext->SourceObject->GetClass()->ImplementsInterface(UGCS_TargetingSourceInterface::StaticClass()))
|
||||||
|
{
|
||||||
|
FRotator TraceRotation;
|
||||||
|
if (IGCS_TargetingSourceInterface::Execute_GetSweptTraceRotation(SourceContext->SourceObject, TraceRotation))
|
||||||
|
{
|
||||||
|
return TraceRotation;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Super::GetSweptTraceRotation_Implementation(TargetingHandle);
|
||||||
|
}
|
||||||
|
|
||||||
|
#if WITH_EDITORONLY_DATA
|
||||||
|
EDataValidationResult UGCS_TargetingSelectionTask_TraceExt_BindShape::IsDataValid(class FDataValidationContext& Context) const
|
||||||
|
{
|
||||||
|
if (TraceType == ETargetingTraceType::Line)
|
||||||
|
{
|
||||||
|
FString Txt = FString::Format(TEXT("TraceType == Line is not allowed in this type of task:{0} "), {GetClass()->GetName()});
|
||||||
|
Context.AddError(FText::FromString(Txt));
|
||||||
|
}
|
||||||
|
if (bUseContextLocationAsSourceLocation)
|
||||||
|
{
|
||||||
|
FString Txt = FString::Format(TEXT("bUseContextLocationAsSourceLocation is not allowed in this type of task:{0} "), {GetClass()->GetName()});
|
||||||
|
Context.AddError(FText::FromString(Txt));
|
||||||
|
}
|
||||||
|
return Super::IsDataValid(Context);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||||
|
|
||||||
|
#include "Team/GCS_CombatTeamAgentComponent.h"
|
||||||
|
#include "GCS_LogChannels.h"
|
||||||
|
#include "GameFramework/Controller.h"
|
||||||
|
#include "GameFramework/Pawn.h"
|
||||||
|
#include "GenericTeamAgentInterface.h"
|
||||||
|
#include "Net/UnrealNetwork.h"
|
||||||
|
#include "Net/Core/PushModel/PushModel.h"
|
||||||
|
|
||||||
|
// Sets default values for this component's properties
|
||||||
|
UGCS_CombatTeamAgentComponent::UGCS_CombatTeamAgentComponent()
|
||||||
|
{
|
||||||
|
// Set this component to be initialized when the game starts, and to be ticked every frame. You can turn these features
|
||||||
|
// off to improve performance if you don't need them.
|
||||||
|
PrimaryComponentTick.bCanEverTick = false;
|
||||||
|
SetIsReplicatedByDefault(true);
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
|
void UGCS_CombatTeamAgentComponent::GetLifetimeReplicatedProps(TArray<class FLifetimeProperty>& OutLifetimeProps) const
|
||||||
|
{
|
||||||
|
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
|
||||||
|
|
||||||
|
FDoRepLifetimeParams SharedParams;
|
||||||
|
SharedParams.bIsPushBased = true;
|
||||||
|
|
||||||
|
DOREPLIFETIME_WITH_PARAMS_FAST(ThisClass, CombatTeamId, SharedParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
FGCS_CombatTeamIdChangedSignature* UGCS_CombatTeamAgentComponent::GetOnTeamIdChangedDelegate()
|
||||||
|
{
|
||||||
|
return &OnTeamIdChangedEvent;
|
||||||
|
}
|
||||||
|
|
||||||
|
FGenericTeamId UGCS_CombatTeamAgentComponent::GetCombatTeamId_Implementation() const
|
||||||
|
{
|
||||||
|
return CombatTeamId;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UGCS_CombatTeamAgentComponent::SetCombatTeamId_Implementation(FGenericTeamId NewTeamId)
|
||||||
|
{
|
||||||
|
if (GetOwner()->HasAuthority())
|
||||||
|
{
|
||||||
|
const FGenericTeamId OldTeamID = CombatTeamId;
|
||||||
|
|
||||||
|
MARK_PROPERTY_DIRTY_FROM_NAME(ThisClass, CombatTeamId, this);
|
||||||
|
CombatTeamId = NewTeamId;
|
||||||
|
if (bAssignTeamIdToController)
|
||||||
|
{
|
||||||
|
if (APawn* Pawn = Cast<APawn>(GetOwner()))
|
||||||
|
{
|
||||||
|
if (IGenericTeamAgentInterface* AgentInterface = Cast<IGenericTeamAgentInterface>(Pawn->GetController()))
|
||||||
|
{
|
||||||
|
AgentInterface->SetGenericTeamId(NewTeamId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ConditionalBroadcastTeamChanged(this, OldTeamID, NewTeamId);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
UE_LOG(LogGCS, Error, TEXT("Cannot set team for %s on non-authority"), *GetPathName(this));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UGCS_CombatTeamAgentComponent::OnRep_CombatTeamId(FGenericTeamId OldTeamID)
|
||||||
|
{
|
||||||
|
ConditionalBroadcastTeamChanged(this, OldTeamID, CombatTeamId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called when the game starts
|
||||||
|
void UGCS_CombatTeamAgentComponent::BeginPlay()
|
||||||
|
{
|
||||||
|
Super::BeginPlay();
|
||||||
|
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called every frame
|
||||||
|
void UGCS_CombatTeamAgentComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
|
||||||
|
{
|
||||||
|
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
|
||||||
|
|
||||||
|
// ...
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||||
|
|
||||||
|
|
||||||
|
#include "Team/GCS_CombatTeamAgentInterface.h"
|
||||||
|
|
||||||
|
#include "GCS_LogChannels.h"
|
||||||
|
|
||||||
|
|
||||||
|
// Add default functionality here for any IGCS_CombatTeamAgentInterface functions that are not pure virtual.
|
||||||
|
void IGCS_CombatTeamAgentInterface::SetCombatTeamId_Implementation(FGenericTeamId NewTeamId)
|
||||||
|
{
|
||||||
|
if (IGenericTeamAgentInterface* TeamAgentInterface = Cast<IGenericTeamAgentInterface>(_getUObject()))
|
||||||
|
{
|
||||||
|
TeamAgentInterface->SetGenericTeamId(NewTeamId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FGenericTeamId IGCS_CombatTeamAgentInterface::GetCombatTeamId_Implementation() const
|
||||||
|
{
|
||||||
|
if (IGenericTeamAgentInterface* TeamAgentInterface = Cast<IGenericTeamAgentInterface>(_getUObject()))
|
||||||
|
{
|
||||||
|
return TeamAgentInterface->GetGenericTeamId();
|
||||||
|
}
|
||||||
|
|
||||||
|
return FGenericTeamId::NoTeam;
|
||||||
|
}
|
||||||
|
|
||||||
|
void IGCS_CombatTeamAgentInterface::ConditionalBroadcastTeamChanged(TScriptInterface<IGCS_CombatTeamAgentInterface> This, FGenericTeamId OldTeamID, FGenericTeamId NewTeamID)
|
||||||
|
{
|
||||||
|
if (OldTeamID != NewTeamID)
|
||||||
|
{
|
||||||
|
UObject* ThisObj = This.GetObject();
|
||||||
|
UE_LOG(LogGCS, Verbose, TEXT("[%s] %s assigned team %d"), *GetClientServerContextString(ThisObj), *GetPathNameSafe(ThisObj), NewTeamID.GetId());
|
||||||
|
This.GetInterface()->GetTeamChangedDelegateChecked().Broadcast(ThisObj, OldTeamID, NewTeamID);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||||
|
|
||||||
|
|
||||||
|
#include "Utility/GCS_AttackDefinitionFunctionLibrary.h"
|
||||||
|
|
||||||
|
#include "CombatFlow/GCS_AttackDefinition.h"
|
||||||
|
|
||||||
|
#if WITH_EDITOR
|
||||||
|
void UGCS_AttackDefinitionFunctionLibrary::MigrateAttackDefinitionTable(UDataTable* InTable)
|
||||||
|
{
|
||||||
|
if (InTable && InTable->GetRowStruct()->IsChildOf(FGCS_AttackDefinition::StaticStruct()))
|
||||||
|
{
|
||||||
|
TArray<FGCS_AttackDefinition*> Rows;
|
||||||
|
InTable->GetAllRows<FGCS_AttackDefinition>(TEXT("Migration"),Rows);
|
||||||
|
|
||||||
|
InTable->MarkPackageDirty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,473 @@
|
|||||||
|
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||||
|
|
||||||
|
|
||||||
|
#include "Utility/GCS_CombatFunctionLibrary.h"
|
||||||
|
#include "Components/SkeletalMeshComponent.h"
|
||||||
|
#include "GCS_CombatEntityInterface.h"
|
||||||
|
#include "GCS_CombatSystemSettings.h"
|
||||||
|
#include "Team/GCS_CombatTeamAgentInterface.h"
|
||||||
|
#include "GCS_LogChannels.h"
|
||||||
|
#include "GGA_GameplayEffectContext.h"
|
||||||
|
#include "GameFramework/Pawn.h"
|
||||||
|
#include "GameFramework/Controller.h"
|
||||||
|
#include "AbilitySystem/GCS_GameplayEffectContext.h"
|
||||||
|
#include "CombatFlow/GCS_AttackRequest.h"
|
||||||
|
#include "GameFramework/Character.h"
|
||||||
|
#include "Kismet/KismetMathLibrary.h"
|
||||||
|
#include "Utilities/GGA_GameplayEffectFunctionLibrary.h"
|
||||||
|
#include "Weapon/GCS_WeaponInterface.h"
|
||||||
|
|
||||||
|
TScriptInterface<IGCS_CombatTeamAgentInterface> UGCS_CombatFunctionLibrary::GetCombatTeamAgentInterface(AActor* Actor)
|
||||||
|
{
|
||||||
|
if (IsValid(Actor))
|
||||||
|
{
|
||||||
|
if (Actor->GetClass()->ImplementsInterface(UGCS_CombatTeamAgentInterface::StaticClass()))
|
||||||
|
{
|
||||||
|
return Actor;
|
||||||
|
}
|
||||||
|
TArray<UActorComponent*> Components = Actor->GetComponentsByInterface(UGCS_CombatTeamAgentInterface::StaticClass());
|
||||||
|
return Components.IsValidIndex(0) ? Components[0] : nullptr;
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UGCS_CombatFunctionLibrary::FindCombatTeamAgentInterface(AActor* Actor, TScriptInterface<IGCS_CombatTeamAgentInterface>& OutInterface)
|
||||||
|
{
|
||||||
|
OutInterface = GetCombatTeamAgentInterface(Actor);
|
||||||
|
return OutInterface != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
TScriptInterface<IGCS_CombatEntityInterface> UGCS_CombatFunctionLibrary::GetCombatEntityInterface(AActor* Actor)
|
||||||
|
{
|
||||||
|
if (IsValid(Actor))
|
||||||
|
{
|
||||||
|
if (Actor->GetClass()->ImplementsInterface(UGCS_CombatEntityInterface::StaticClass()))
|
||||||
|
{
|
||||||
|
return Actor;
|
||||||
|
}
|
||||||
|
TArray<UActorComponent*> Components = Actor->GetComponentsByInterface(UGCS_CombatEntityInterface::StaticClass());
|
||||||
|
return Components.IsValidIndex(0) ? Components[0] : nullptr;
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
UObject* UGCS_CombatFunctionLibrary::GetCombatEntity(AActor* Actor)
|
||||||
|
{
|
||||||
|
if (IsValid(Actor))
|
||||||
|
{
|
||||||
|
if (Actor->GetClass()->ImplementsInterface(UGCS_CombatEntityInterface::StaticClass()))
|
||||||
|
{
|
||||||
|
return Actor;
|
||||||
|
}
|
||||||
|
TArray<UActorComponent*> Components = Actor->GetComponentsByInterface(UGCS_CombatEntityInterface::StaticClass());
|
||||||
|
return Components.IsValidIndex(0) ? Components[0] : nullptr;
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
TScriptInterface<IGCS_WeaponInterface> UGCS_CombatFunctionLibrary::GetWeaponInterface(AActor* Actor)
|
||||||
|
{
|
||||||
|
if (IsValid(Actor))
|
||||||
|
{
|
||||||
|
if (Actor->GetClass()->ImplementsInterface(UGCS_WeaponInterface::StaticClass()))
|
||||||
|
{
|
||||||
|
return Actor;
|
||||||
|
}
|
||||||
|
TArray<UActorComponent*> Components = Actor->GetComponentsByInterface(UGCS_WeaponInterface::StaticClass());
|
||||||
|
return Components.IsValidIndex(0) ? Components[0] : nullptr;
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
USkeletalMeshComponent* UGCS_CombatFunctionLibrary::GetMainCharacterMeshComponent(AActor* Actor, FName OverrideMeshLookupTag)
|
||||||
|
{
|
||||||
|
if (IsValid(Actor))
|
||||||
|
{
|
||||||
|
if (OverrideMeshLookupTag != NAME_None)
|
||||||
|
{
|
||||||
|
TArray<UActorComponent*> Components = Actor->GetComponentsByTag(USkeletalMeshComponent::StaticClass(), OverrideMeshLookupTag);
|
||||||
|
if (Components.IsValidIndex(0))
|
||||||
|
{
|
||||||
|
return Cast<USkeletalMeshComponent>(Components[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (const UGCS_CombatSystemSettings* Settings = UGCS_CombatSystemSettings::Get())
|
||||||
|
{
|
||||||
|
TArray<UActorComponent*> Components = Actor->GetComponentsByTag(USkeletalMeshComponent::StaticClass(), Settings->CharacterMeshLookupTag);
|
||||||
|
if (Components.IsValidIndex(0))
|
||||||
|
{
|
||||||
|
return Cast<USkeletalMeshComponent>(Components[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ACharacter* Char = Cast<ACharacter>(Actor))
|
||||||
|
{
|
||||||
|
return Char->GetMesh();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (USkeletalMeshComponent* Component = Cast<USkeletalMeshComponent>(Actor->GetComponentByClass(USkeletalMeshComponent::StaticClass())))
|
||||||
|
{
|
||||||
|
return Component;
|
||||||
|
}
|
||||||
|
|
||||||
|
UE_LOG(LogGCS, Warning, TEXT("Failed to find main character mesh component on actor class:%s"), *Actor->GetClass()->GetName());
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
UMeshComponent* UGCS_CombatFunctionLibrary::GetMainMeshComponent(AActor* Actor, FName OverrideMeshLookupTag)
|
||||||
|
{
|
||||||
|
if (IsValid(Actor))
|
||||||
|
{
|
||||||
|
if (OverrideMeshLookupTag != NAME_None)
|
||||||
|
{
|
||||||
|
if (UActorComponent* Component = Actor->FindComponentByTag(UMeshComponent::StaticClass(), OverrideMeshLookupTag))
|
||||||
|
{
|
||||||
|
return Cast<UMeshComponent>(Component);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (const UGCS_CombatSystemSettings* Settings = UGCS_CombatSystemSettings::Get())
|
||||||
|
{
|
||||||
|
TArray<UActorComponent*> Components = Actor->GetComponentsByTag(UMeshComponent::StaticClass(), Settings->CharacterMeshLookupTag);
|
||||||
|
if (Components.IsValidIndex(0))
|
||||||
|
{
|
||||||
|
return Cast<UMeshComponent>(Components[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (UMeshComponent* Component = Cast<UMeshComponent>(Actor->GetComponentByClass(UMeshComponent::StaticClass())))
|
||||||
|
{
|
||||||
|
return Component;
|
||||||
|
}
|
||||||
|
|
||||||
|
UE_LOG(LogGCS, Warning, TEXT("Failed to find main mesh component on actor class:%s"), *Actor->GetClass()->GetName());
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
TArray<FName> UGCS_CombatFunctionLibrary::GetSocketNamesWithPrefix(const USceneComponent* Component, FString Prefix, ESearchCase::Type SearchCase)
|
||||||
|
{
|
||||||
|
if (IsValid(Component))
|
||||||
|
{
|
||||||
|
return Component->GetAllSocketNames().FilterByPredicate([&](const FName& SocketName)
|
||||||
|
{
|
||||||
|
return SocketName.ToString().StartsWith(Prefix, SearchCase);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UGCS_CombatFunctionLibrary::FindCombatInterface(AActor* Actor, TScriptInterface<IGCS_CombatEntityInterface>& OutInterface)
|
||||||
|
{
|
||||||
|
OutInterface = GetCombatEntityInterface(Actor);
|
||||||
|
return OutInterface.GetObject() != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UGCS_CombatFunctionLibrary::FindWeaponInterface(AActor* Actor, TScriptInterface<IGCS_WeaponInterface>& OutInterface)
|
||||||
|
{
|
||||||
|
OutInterface = GetWeaponInterface(Actor);
|
||||||
|
return OutInterface.GetObject() != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
FRotator UGCS_CombatFunctionLibrary::CalculateAngleBetweenActors(const AActor* From, const AActor* To)
|
||||||
|
{
|
||||||
|
if (IsValid(From) && IsValid(To))
|
||||||
|
{
|
||||||
|
return UKismetMathLibrary::NormalizedDeltaRotator(UKismetMathLibrary::FindLookAtRotation(From->GetActorLocation(), To->GetActorLocation()), From->GetActorRotation());
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO Warning.
|
||||||
|
return FRotator::ZeroRotator;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UGCS_CombatFunctionLibrary::IsSameCombatTeam(const AActor* A, const AActor* B)
|
||||||
|
{
|
||||||
|
return GetCombatTeamId(A) == GetCombatTeamId(B);
|
||||||
|
}
|
||||||
|
|
||||||
|
FGenericTeamId UGCS_CombatFunctionLibrary::GetCombatTeamId(const AActor* Actor)
|
||||||
|
{
|
||||||
|
return QueryCombatTeamId(Actor, true, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
FGenericTeamId UGCS_CombatFunctionLibrary::QueryCombatTeamId(const AActor* Actor, bool bCombatAgent, bool bGenericAgent)
|
||||||
|
{
|
||||||
|
if (!IsValid(Actor))
|
||||||
|
{
|
||||||
|
return FGenericTeamId::NoTeam;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bCombatAgent)
|
||||||
|
{
|
||||||
|
if (Actor->GetClass()->ImplementsInterface(UGCS_CombatTeamAgentInterface::StaticClass()))
|
||||||
|
{
|
||||||
|
return IGCS_CombatTeamAgentInterface::Execute_GetCombatTeamId(Actor);
|
||||||
|
}
|
||||||
|
|
||||||
|
TArray<UActorComponent*> Components = Actor->GetComponentsByInterface(UGCS_CombatTeamAgentInterface::StaticClass());
|
||||||
|
if (Components.IsValidIndex(0))
|
||||||
|
{
|
||||||
|
return IGCS_CombatTeamAgentInterface::Execute_GetCombatTeamId(Components[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (const APawn* Pawn = Cast<APawn>(Actor))
|
||||||
|
{
|
||||||
|
if (Pawn->GetController() && Pawn->GetController()->GetClass()->ImplementsInterface(UGCS_CombatTeamAgentInterface::StaticClass()))
|
||||||
|
{
|
||||||
|
return IGCS_CombatTeamAgentInterface::Execute_GetCombatTeamId(Pawn->GetController());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bGenericAgent)
|
||||||
|
{
|
||||||
|
if (const IGenericTeamAgentInterface* GenericTeamAgentInterface = Cast<IGenericTeamAgentInterface>(Actor))
|
||||||
|
{
|
||||||
|
return GenericTeamAgentInterface->GetGenericTeamId();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (const APawn* Pawn = Cast<APawn>(Actor))
|
||||||
|
{
|
||||||
|
if (const IGenericTeamAgentInterface* GenericTeamAgentInterface = Cast<IGenericTeamAgentInterface>(Pawn->GetController()))
|
||||||
|
{
|
||||||
|
return GenericTeamAgentInterface->GetGenericTeamId();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return FGenericTeamId::NoTeam;
|
||||||
|
}
|
||||||
|
|
||||||
|
EGCS_Direction UGCS_CombatFunctionLibrary::CalculateDirectionFromAngle(const float Angle)
|
||||||
|
{
|
||||||
|
if (UKismetMathLibrary::InRange_FloatFloat(Angle, -45.0f, 45.0f))
|
||||||
|
{
|
||||||
|
return EGCS_Direction::Forward;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (UKismetMathLibrary::InRange_FloatFloat(Angle, 45.0f, 135.f))
|
||||||
|
{
|
||||||
|
return EGCS_Direction::Right;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (UKismetMathLibrary::InRange_FloatFloat(Angle, -135.f, -45.f))
|
||||||
|
{
|
||||||
|
return EGCS_Direction::Left;
|
||||||
|
}
|
||||||
|
return EGCS_Direction::Backward;
|
||||||
|
}
|
||||||
|
|
||||||
|
TSoftObjectPtr<UAnimMontage> UGCS_CombatFunctionLibrary::SelectMontageByDirection(EGCS_Direction Direction, TArray<TSoftObjectPtr<UAnimMontage>> Montages)
|
||||||
|
{
|
||||||
|
switch (Direction)
|
||||||
|
{
|
||||||
|
case EGCS_Direction::Forward:
|
||||||
|
return Montages.IsValidIndex(0) ? Montages[0] : nullptr;
|
||||||
|
case EGCS_Direction::Backward:
|
||||||
|
return Montages.IsValidIndex(1) ? Montages[1] : nullptr;
|
||||||
|
case EGCS_Direction::Left:
|
||||||
|
return Montages.IsValidIndex(2) ? Montages[2] : nullptr;
|
||||||
|
case EGCS_Direction::Right:
|
||||||
|
return Montages.IsValidIndex(3) ? Montages[3] : nullptr;
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UGCS_CombatFunctionLibrary::AddTaggedValue(TArray<FGCS_TaggedValue>& TaggedValues, FGameplayTag Tag, float ValueToAdd)
|
||||||
|
{
|
||||||
|
bool bFound = false;
|
||||||
|
for (FGCS_TaggedValue& TaggedValue : TaggedValues)
|
||||||
|
{
|
||||||
|
if (TaggedValue.Attribute == Tag)
|
||||||
|
{
|
||||||
|
TaggedValue.Value += ValueToAdd;
|
||||||
|
bFound = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!bFound)
|
||||||
|
{
|
||||||
|
FGCS_TaggedValue Temp;
|
||||||
|
Temp.Attribute = Tag;
|
||||||
|
Temp.Value = ValueToAdd;
|
||||||
|
TaggedValues.Add(Temp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
float UGCS_CombatFunctionLibrary::GetTaggedValue(const TArray<FGCS_TaggedValue> TaggedValues, FGameplayTag Tag)
|
||||||
|
{
|
||||||
|
for (const FGCS_TaggedValue& TaggedValue : TaggedValues)
|
||||||
|
{
|
||||||
|
if (TaggedValue.Attribute == Tag)
|
||||||
|
{
|
||||||
|
return TaggedValue.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
FGameplayTagContainer UGCS_CombatFunctionLibrary::FilterGameplayTagContainer(const FGameplayTagContainer& TagContainer, FGameplayTagContainer OtherContainer)
|
||||||
|
{
|
||||||
|
return TagContainer.Filter(OtherContainer);
|
||||||
|
}
|
||||||
|
|
||||||
|
FGameplayEffectSpecHandle UGCS_CombatFunctionLibrary::AddAttackHandleToGameplayEffectSpec(FGameplayEffectSpecHandle SpecHandle, FDataTableRowHandle AttackHandle)
|
||||||
|
{
|
||||||
|
if (SpecHandle.IsValid() && !AttackHandle.IsNull())
|
||||||
|
{
|
||||||
|
if (FGCS_AttackDefinition* AtkDef = AttackHandle.GetRow<FGCS_AttackDefinition>(TEXT("AddAttackHandleToGameplayEffectSpec")))
|
||||||
|
{
|
||||||
|
AddAttackDefinitionToGameplayEffectSpec(SpecHandle, *AtkDef);
|
||||||
|
}
|
||||||
|
|
||||||
|
FGameplayEffectContextHandle ContextHandle = SpecHandle.Data->GetEffectContext();
|
||||||
|
EffectContextSetAttackDefinitionHandle(ContextHandle, AttackHandle);
|
||||||
|
}
|
||||||
|
return SpecHandle;
|
||||||
|
}
|
||||||
|
|
||||||
|
FGameplayEffectSpecHandle UGCS_CombatFunctionLibrary::AddAttackDefinitionToGameplayEffectSpec(FGameplayEffectSpecHandle SpecHandle, const FGCS_AttackDefinition& AtkDefinition)
|
||||||
|
{
|
||||||
|
if (SpecHandle.IsValid())
|
||||||
|
{
|
||||||
|
SpecHandle.Data->AppendDynamicAssetTags(AtkDefinition.AttackTags);
|
||||||
|
|
||||||
|
// apply set by callers from atk definition.
|
||||||
|
|
||||||
|
for (const TTuple<FGameplayTag, float>& ByCallerMagnitude : AtkDefinition.SetByCallerMagnitudes)
|
||||||
|
{
|
||||||
|
if (ByCallerMagnitude.Key.IsValid() && ByCallerMagnitude.Value > 0)
|
||||||
|
{
|
||||||
|
SpecHandle.Data->SetSetByCallerMagnitude(ByCallerMagnitude.Key, ByCallerMagnitude.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return SpecHandle;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UGCS_CombatFunctionLibrary::AddAttackHandleToGameplayEffectContainerSpec(FGGA_GameplayEffectContainerSpec ContainerSpec, FDataTableRowHandle AttackHandle)
|
||||||
|
{
|
||||||
|
if (!AttackHandle.IsNull())
|
||||||
|
{
|
||||||
|
if (FGCS_AttackDefinition* AtkDef = AttackHandle.GetRow<FGCS_AttackDefinition>(TEXT("AddAttackHandleToGameplayEffectSpec")))
|
||||||
|
{
|
||||||
|
for (const FGameplayEffectSpecHandle& SpecHandle : ContainerSpec.TargetGameplayEffectSpecs)
|
||||||
|
{
|
||||||
|
AddAttackDefinitionToGameplayEffectSpec(SpecHandle, *AtkDef);
|
||||||
|
FGameplayEffectContextHandle ContextHandle = SpecHandle.Data->GetEffectContext();
|
||||||
|
EffectContextSetAttackDefinitionHandle(ContextHandle, AttackHandle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UGCS_CombatFunctionLibrary::EffectContextSetAttackDefinitionHandle(FGameplayEffectContextHandle EffectContext, FDataTableRowHandle Handle)
|
||||||
|
{
|
||||||
|
if (FGCS_ContextPayload_Combat* Payload = EffectContextGetMutableCombatPayload(EffectContext))
|
||||||
|
{
|
||||||
|
Payload->AtkDataTable = Handle.DataTable;
|
||||||
|
Payload->AtkRowName = Handle.RowName;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
UE_LOG(LogGCS, Error, TEXT("Can't access GCS_GameplayEffectContext! You need to setup GCS_AbilitySystemGlobals as AbilitySystemGlobalsClassName."))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FGCS_ContextPayload_Combat UGCS_CombatFunctionLibrary::EffectContextGetCombatPayload(FGameplayEffectContextHandle EffectContext)
|
||||||
|
{
|
||||||
|
if (FGGA_GameplayEffectContext* Context = UGGA_GameplayEffectFunctionLibrary::GetEffectContextPtr(EffectContext))
|
||||||
|
{
|
||||||
|
if (FGCS_ContextPayload_Combat* CombatPayload = Context->FindMutablePayloadByType<FGCS_ContextPayload_Combat>())
|
||||||
|
{
|
||||||
|
return *CombatPayload;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return FGCS_ContextPayload_Combat();
|
||||||
|
}
|
||||||
|
|
||||||
|
FGCS_ContextPayload_Combat* UGCS_CombatFunctionLibrary::EffectContextGetMutableCombatPayload(const FGameplayEffectContextHandle& EffectContext)
|
||||||
|
{
|
||||||
|
if (FGGA_GameplayEffectContext* Context = UGGA_GameplayEffectFunctionLibrary::GetEffectContextPtr(EffectContext))
|
||||||
|
{
|
||||||
|
if (FGCS_ContextPayload_Combat* CombatPayload = Context->FindOrAddMutablePayloadPtr<FGCS_ContextPayload_Combat>())
|
||||||
|
{
|
||||||
|
return CombatPayload;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UGCS_CombatFunctionLibrary::EffectContextAddTagToCombatPayload(FGameplayEffectContextHandle EffectContext, FGameplayTag TagToAdd)
|
||||||
|
{
|
||||||
|
if (TagToAdd.IsValid())
|
||||||
|
{
|
||||||
|
if (FGCS_ContextPayload_Combat* Payload = EffectContextGetMutableCombatPayload(EffectContext))
|
||||||
|
{
|
||||||
|
Payload->DynamicTags.AddTagFast(TagToAdd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UGCS_CombatFunctionLibrary::EffectContextSetTaggedValueToCombatPayload(FGameplayEffectContextHandle EffectContext, FGameplayTag Tag, float NewValue)
|
||||||
|
{
|
||||||
|
if (FGCS_ContextPayload_Combat* Payload = EffectContextGetMutableCombatPayload(EffectContext))
|
||||||
|
{
|
||||||
|
Payload->SetTaggedValue(Tag, NewValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
float UGCS_CombatFunctionLibrary::EffectContextGetTaggedValueFromCombatPayload(FGameplayEffectContextHandle EffectContext, FGameplayTag Tag)
|
||||||
|
{
|
||||||
|
if (FGCS_ContextPayload_Combat* Payload = EffectContextGetMutableCombatPayload(EffectContext))
|
||||||
|
{
|
||||||
|
return Payload->GetTaggedValue(Tag);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UGCS_CombatFunctionLibrary::EffectContextGetDynamicTagsFromCombatPayload(FGameplayEffectContextHandle EffectContext, FGameplayTagContainer& OutTags)
|
||||||
|
{
|
||||||
|
if (FGCS_ContextPayload_Combat* Payload = EffectContextGetMutableCombatPayload(EffectContext))
|
||||||
|
{
|
||||||
|
OutTags.Reset();
|
||||||
|
OutTags = Payload->DynamicTags;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FDataTableRowHandle UGCS_CombatFunctionLibrary::EffectContextGetAttackDefinitionHandle(FGameplayEffectContextHandle EffectContext)
|
||||||
|
{
|
||||||
|
if (FGCS_ContextPayload_Combat* Payload = EffectContextGetMutableCombatPayload(EffectContext))
|
||||||
|
{
|
||||||
|
FDataTableRowHandle Handle;
|
||||||
|
Handle.DataTable = Payload->AtkDataTable;
|
||||||
|
Handle.RowName = Payload->AtkRowName;
|
||||||
|
return Handle;
|
||||||
|
}
|
||||||
|
return FDataTableRowHandle();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UGCS_CombatFunctionLibrary::EffectContextGetIsPredictingContext(FGameplayEffectContextHandle EffectContext)
|
||||||
|
{
|
||||||
|
if (FGCS_ContextPayload_Combat* Payload = EffectContextGetMutableCombatPayload(EffectContext))
|
||||||
|
{
|
||||||
|
return Payload->bIsPredictingContext;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
FGCS_AttackDefinition UGCS_CombatFunctionLibrary::EffectContextGetAttackDefinition(FGameplayEffectContextHandle EffectContext)
|
||||||
|
{
|
||||||
|
FDataTableRowHandle Handle = EffectContextGetAttackDefinitionHandle(EffectContext);
|
||||||
|
if (FGCS_AttackDefinition* Def = Handle.GetRow<FGCS_AttackDefinition>(TEXT("EffectContextGetAttackDefinition")))
|
||||||
|
{
|
||||||
|
return *Def;
|
||||||
|
}
|
||||||
|
return FGCS_AttackDefinition();
|
||||||
|
}
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||||
|
|
||||||
|
|
||||||
|
#include "Weapon/GCS_AttachmentRelationshipMapping.h"
|
||||||
|
#include "Animation/Skeleton.h"
|
||||||
|
#include "Engine/StaticMesh.h"
|
||||||
|
#include "Engine/SkeletalMesh.h"
|
||||||
|
#include "Components/SkeletalMeshComponent.h"
|
||||||
|
#include "UObject/ObjectSaveContext.h"
|
||||||
|
|
||||||
|
bool UGCS_AttachmentRelationshipMapping::FindRelationshipForMesh(const USkeletalMeshComponent* InSkeletalMeshComponent, const UStaticMesh* InStaticMesh, const USkeletalMesh* InSkeletalMesh,
|
||||||
|
FName InSocketName, FGCS_AttachmentRelationship& OutRelationship) const
|
||||||
|
{
|
||||||
|
bool bFoundMatchingSkeleton = false;
|
||||||
|
if (bUseNameMatching)
|
||||||
|
{
|
||||||
|
if (!CompatibleSkeletonNames.IsEmpty() && InSkeletalMeshComponent && InSkeletalMeshComponent->GetSkeletalMeshAsset())
|
||||||
|
{
|
||||||
|
for (const FString& SkeletonName : CompatibleSkeletonNames)
|
||||||
|
{
|
||||||
|
if (SkeletonName.IsEmpty())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (USkeleton* Skeleton = InSkeletalMeshComponent->GetSkeletalMeshAsset()->GetSkeleton())
|
||||||
|
{
|
||||||
|
if (Skeleton->GetName() == SkeletonName)
|
||||||
|
{
|
||||||
|
bFoundMatchingSkeleton = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!CompatibleSkeletons.IsEmpty() && InSkeletalMeshComponent && InSkeletalMeshComponent->GetSkeletalMeshAsset())
|
||||||
|
{
|
||||||
|
for (TSoftObjectPtr<USkeleton> CompatibleSkeleton : CompatibleSkeletons)
|
||||||
|
{
|
||||||
|
if (CompatibleSkeleton.IsNull())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (!CompatibleSkeleton.IsValid())
|
||||||
|
{
|
||||||
|
CompatibleSkeleton.LoadSynchronous();
|
||||||
|
}
|
||||||
|
if (CompatibleSkeleton == InSkeletalMeshComponent->GetSkeletalMeshAsset()->GetSkeleton())
|
||||||
|
{
|
||||||
|
bFoundMatchingSkeleton = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (!bFoundMatchingSkeleton)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const FGCS_AttachmentRelationship& Rel : Relationships)
|
||||||
|
{
|
||||||
|
if (Rel.SocketName != InSocketName)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsValid(InStaticMesh) && Rel.StaticMesh.IsValid() && InStaticMesh == Rel.StaticMesh.Get())
|
||||||
|
{
|
||||||
|
OutRelationship = Rel;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsValid(InSkeletalMesh) && Rel.SkeletalMesh.IsValid() && InSkeletalMesh == Rel.SkeletalMesh.Get())
|
||||||
|
{
|
||||||
|
OutRelationship = Rel;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if WITH_EDITOR
|
||||||
|
|
||||||
|
void UGCS_AttachmentRelationshipMapping::PreSave(FObjectPreSaveContext SaveContext)
|
||||||
|
{
|
||||||
|
for (FGCS_AttachmentRelationship& Relationship : Relationships)
|
||||||
|
{
|
||||||
|
if (Relationship.SocketName == NAME_None)
|
||||||
|
{
|
||||||
|
Relationship.EditorFriendlyName = "Invalid setup";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Relationship.EditorFriendlyName = FString::Format(TEXT("Adjust SM({0})/SKM({1}) for Socket({2})"),
|
||||||
|
{
|
||||||
|
Relationship.StaticMesh.IsNull() ? TEXT("None") : Relationship.StaticMesh.LoadSynchronous()->GetName(),
|
||||||
|
Relationship.SkeletalMesh.IsNull() ? TEXT("None") : Relationship.SkeletalMesh.LoadSynchronous()->GetName(),
|
||||||
|
Relationship.SocketName.ToString()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Super::PreSave(SaveContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
EDataValidationResult UGCS_AttachmentRelationshipMapping::IsDataValid(class FDataValidationContext& Context) const
|
||||||
|
{
|
||||||
|
return Super::IsDataValid(Context);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,188 @@
|
|||||||
|
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||||
|
|
||||||
|
|
||||||
|
#include "Weapon/GCS_WeaponActor.h"
|
||||||
|
#include "Components/PrimitiveComponent.h"
|
||||||
|
#include "GameFramework/Pawn.h"
|
||||||
|
#include "GCS_LogChannels.h"
|
||||||
|
#include "Collision/GCS_TraceSystemComponent.h"
|
||||||
|
#include "Misc/DataValidation.h"
|
||||||
|
#include "Net/UnrealNetwork.h"
|
||||||
|
#include "Net/Core/PushModel/PushModel.h"
|
||||||
|
|
||||||
|
AGCS_WeaponActor::AGCS_WeaponActor(const FObjectInitializer& ObjectInitializer)
|
||||||
|
{
|
||||||
|
PrimaryActorTick.bCanEverTick = true;
|
||||||
|
bReplicates = true;
|
||||||
|
bWeaponActive = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
APawn* AGCS_WeaponActor::GetWeaponOwner_Implementation() const
|
||||||
|
{
|
||||||
|
return Cast<APawn>(GetOwner());
|
||||||
|
}
|
||||||
|
|
||||||
|
const FGameplayTagContainer AGCS_WeaponActor::GetWeaponTags_Implementation() const
|
||||||
|
{
|
||||||
|
return WeaponTags;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AGCS_WeaponActor::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
|
||||||
|
{
|
||||||
|
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
|
||||||
|
|
||||||
|
FDoRepLifetimeParams Parameters;
|
||||||
|
Parameters.bIsPushBased = true;
|
||||||
|
|
||||||
|
DOREPLIFETIME_WITH_PARAMS_FAST(ThisClass, bWeaponActive, Parameters)
|
||||||
|
}
|
||||||
|
|
||||||
|
void AGCS_WeaponActor::SetWeaponActive_Implementation(bool bNewActive)
|
||||||
|
{
|
||||||
|
if (GetOwner()->HasAuthority())
|
||||||
|
{
|
||||||
|
const bool prev = bWeaponActive;
|
||||||
|
bWeaponActive = bNewActive;
|
||||||
|
MARK_PROPERTY_DIRTY_FROM_NAME(ThisClass, bWeaponActive, this);
|
||||||
|
OnWeaponActiveStateChanged(prev);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AGCS_WeaponActor::IsWeaponActive_Implementation() const
|
||||||
|
{
|
||||||
|
return bWeaponActive;
|
||||||
|
}
|
||||||
|
|
||||||
|
UPrimitiveComponent* AGCS_WeaponActor::GetPrimitiveComponent_Implementation() const
|
||||||
|
{
|
||||||
|
if (WeaponMeshTagName.IsValid())
|
||||||
|
{
|
||||||
|
UPrimitiveComponent* Primitive = Cast<UPrimitiveComponent>(GetOwner()->FindComponentByTag(UPrimitiveComponent::StaticClass(), WeaponMeshTagName));
|
||||||
|
if (!IsValid(Primitive))
|
||||||
|
{
|
||||||
|
GCS_CLOG(Warning, "failed to find weapon mesh via tag (%s) as weapon primitive component. weapon owner:%s",
|
||||||
|
*WeaponMeshTagName.ToString(),
|
||||||
|
*GetOwner()->GetName());
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return Primitive;
|
||||||
|
}
|
||||||
|
GCS_CLOG(Warning, "no weapon primitive component provided. weapon owner:%s", *GetOwner()->GetName());
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AGCS_WeaponActor::GetOwnedGameplayTags(FGameplayTagContainer& TagContainer) const
|
||||||
|
{
|
||||||
|
TagContainer = Execute_GetWeaponTags(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AGCS_WeaponActor::OnWeaponActiveStateChanged_Implementation(bool Prev)
|
||||||
|
{
|
||||||
|
OnWeaponActiveStateChangedEvent.Broadcast(bWeaponActive);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AGCS_WeaponActor::BeginPlay()
|
||||||
|
{
|
||||||
|
Super::BeginPlay();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AGCS_WeaponActor::EndPlay(const EEndPlayReason::Type EndPlayReason)
|
||||||
|
{
|
||||||
|
if (Execute_IsWeaponActive(this))
|
||||||
|
{
|
||||||
|
bWeaponActive = false;
|
||||||
|
RefreshTraceInstance();
|
||||||
|
}
|
||||||
|
Super::EndPlay(EndPlayReason);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AGCS_WeaponActor::RefreshTraceInstance_Implementation()
|
||||||
|
{
|
||||||
|
APawn* Pawn = Execute_GetWeaponOwner(this);
|
||||||
|
if (!IsValid(Pawn))
|
||||||
|
{
|
||||||
|
GCS_CLOG(Warning, "mising weapon owner!")
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
UGCS_TraceSystemComponent* TSC = UGCS_TraceSystemComponent::GetTraceSystemComponent(Pawn);
|
||||||
|
if (!IsValid(TSC))
|
||||||
|
{
|
||||||
|
GCS_CLOG(Warning, "missing trace system on weapon owner(%s)!", *GetNameSafe(Pawn))
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (bWeaponActive)
|
||||||
|
{
|
||||||
|
TraceHandles.Reset();
|
||||||
|
for (const FGCS_TraceDefinition& TraceDefinition : TraceDefinitions)
|
||||||
|
{
|
||||||
|
UPrimitiveComponent* Primitive = GetSourceComponentForTrace(TraceDefinition.TraceTag);
|
||||||
|
if (Primitive == nullptr)
|
||||||
|
{
|
||||||
|
GCS_CLOG(Error, "No SourceComponent provided for trace:%s,check your GetSourceComponentForTrace implementation.", *TraceDefinition.TraceTag.ToString());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
FGCS_TraceHandle Handle = TSC->AddTrace(TraceDefinition, Primitive, GetSourceObjectForTrace());
|
||||||
|
if (Handle.IsValidHandle())
|
||||||
|
{
|
||||||
|
TraceHandles.Add(Handle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TSC->OnTraceHitEvent.AddDynamic(this, &ThisClass::OnAnyTraceHit);
|
||||||
|
TSC->OnTraceStateChangedEvent.AddDynamic(this, &ThisClass::OnAnyTraceStateChanged);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
TSC->OnTraceHitEvent.RemoveDynamic(this, &ThisClass::OnAnyTraceHit);
|
||||||
|
TSC->OnTraceStateChangedEvent.RemoveDynamic(this, &ThisClass::OnAnyTraceStateChanged);
|
||||||
|
for (const FGCS_TraceHandle& TraceHandle : TraceHandles)
|
||||||
|
{
|
||||||
|
TSC->RemoveTrace(TraceHandle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
UObject* AGCS_WeaponActor::GetSourceObjectForTrace_Implementation()
|
||||||
|
{
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
UPrimitiveComponent* AGCS_WeaponActor::GetSourceComponentForTrace_Implementation(const FGameplayTag& TraceTag) const
|
||||||
|
{
|
||||||
|
// Default Using weapon primitive as source component for trace
|
||||||
|
if (UPrimitiveComponent* PrimitiveComponent = Execute_GetPrimitiveComponent(this))
|
||||||
|
{
|
||||||
|
return PrimitiveComponent;
|
||||||
|
}
|
||||||
|
|
||||||
|
GCS_CLOG(Error, "Weapon(%s) didn't return return valid primitive component to be used as SourceComponent for traces, try implement this function to get properly SourceComponent For Trace.",
|
||||||
|
*GetNameSafe(this))
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AGCS_WeaponActor::OnAnyTraceHit_Implementation(const FGCS_TraceHandle& TraceHandle, const FHitResult& HitResult)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void AGCS_WeaponActor::OnAnyTraceStateChanged_Implementation(const FGCS_TraceHandle& TraceHandle, bool NewState)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void AGCS_WeaponActor::Tick(float DeltaSeconds)
|
||||||
|
{
|
||||||
|
Super::Tick(DeltaSeconds);
|
||||||
|
}
|
||||||
|
|
||||||
|
#if WITH_EDITOR
|
||||||
|
EDataValidationResult AGCS_WeaponActor::IsDataValid(class FDataValidationContext& Context) const
|
||||||
|
{
|
||||||
|
for (int32 i = 0; i < TraceDefinitions.Num(); i++)
|
||||||
|
{
|
||||||
|
if (!TraceDefinitions[i].IsValidDefinition())
|
||||||
|
{
|
||||||
|
Context.AddWarning(FText::FromString(FString::Format(TEXT("Found invalid trace definition at index({0})"), {i})));
|
||||||
|
return EDataValidationResult::Invalid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Super::IsDataValid(Context);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||||
|
|
||||||
|
|
||||||
|
#include "Weapon/GCS_WeaponInterface.h"
|
||||||
|
#include "GameFramework/Actor.h"
|
||||||
|
#include "GameFramework/Pawn.h"
|
||||||
|
|
||||||
|
// Add default functionality here for any IGCS_WeaponInterface functions that are not pure virtual.
|
||||||
|
UPrimitiveComponent* IGCS_WeaponInterface::GetPrimitiveComponent_Implementation() const
|
||||||
|
{
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "GGA_GameplayAbility.h"
|
||||||
|
#include "GCS_CombatAbility.generated.h"
|
||||||
|
|
||||||
|
class IGCS_CombatEntityInterface;
|
||||||
|
class UGCS_CombatSystemComponent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base combat ability.
|
||||||
|
* 基础战斗能力。
|
||||||
|
*/
|
||||||
|
UCLASS(Abstract)
|
||||||
|
class GENERICCOMBATSYSTEM_API UGCS_CombatAbility : public UGGA_GameplayAbility
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
protected:
|
||||||
|
UFUNCTION(BlueprintCallable, BlueprintPure, Category="GCS|Ability")
|
||||||
|
UGCS_CombatSystemComponent* GetCombatSystemFromActorInfo() const;
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable, BlueprintPure, Category="GCS|Ability")
|
||||||
|
UObject* GetCombatEntityFromActorInfo() const;
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable, BlueprintPure, Category="GCS|Ability")
|
||||||
|
TScriptInterface<IGCS_CombatEntityInterface> GetCombatEntityInterfaceFromActorInfo() const;
|
||||||
|
};
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "GCS_CombatAbility.h"
|
||||||
|
#include "Combo/GCS_ComboDefinition.h"
|
||||||
|
#include "GCS_ComboAbility.generated.h"
|
||||||
|
|
||||||
|
class UGCS_CombatSystemComponent;
|
||||||
|
class UAS_Combat;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This combo ability acted as manager of sub abilities.
|
||||||
|
*/
|
||||||
|
UCLASS(Abstract, HideCategories=(Cooldowns,Input,GampelayEffects))
|
||||||
|
class GENERICCOMBATSYSTEM_API UGCS_ComboAbility : public UGCS_CombatAbility
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
UGCS_ComboAbility();
|
||||||
|
|
||||||
|
virtual void PreActivate(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo,
|
||||||
|
FOnGameplayAbilityEnded::FDelegate* OnGameplayAbilityEndedDelegate, const FGameplayEventData* TriggerEventData = nullptr) override;
|
||||||
|
virtual void ActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo,
|
||||||
|
const FGameplayEventData* TriggerEventData) override;
|
||||||
|
virtual bool CanActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayTagContainer* SourceTags = nullptr,
|
||||||
|
const FGameplayTagContainer* TargetTags = nullptr, FGameplayTagContainer* OptionalRelevantTags = nullptr) const override;
|
||||||
|
virtual void OnGiveAbility(const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilitySpec& Spec) override;
|
||||||
|
virtual void OnRemoveAbility(const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilitySpec& Spec) override;
|
||||||
|
|
||||||
|
virtual void EndAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, bool bReplicateEndAbility,
|
||||||
|
bool bWasCancelled) override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = "Combat Ability")
|
||||||
|
bool AllowAdvanceCombo() const;
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = "Combat Ability")
|
||||||
|
void StartCombo(const FGameplayEventData& ComboEvent);
|
||||||
|
virtual void StartCombo_Implementation(const FGameplayEventData& ComboEvent);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Advance combo with ComboEventData as context.
|
||||||
|
* @param ComboEventData The data used as combo context. 游戏事件数据作为连击上下文。
|
||||||
|
*/
|
||||||
|
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = "Combat Ability")
|
||||||
|
void AdvanceCombo(const FGameplayEventData& ComboEventData);
|
||||||
|
virtual void AdvanceCombo_Implementation(const FGameplayEventData& ComboEventData);
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = "Combat Ability")
|
||||||
|
void ResetCombo();
|
||||||
|
|
||||||
|
UFUNCTION()
|
||||||
|
virtual void HandleAbilityEnd(const FAbilityEndedData& AbilityEndedData);
|
||||||
|
|
||||||
|
virtual bool SelectComboDefinition(const FGameplayEventData& ComboEventData, int32 CurrentStep, FGCS_ComboDefinition& OutDefinition);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is where you can use the extension filed within your combo definition to apply additional rules.
|
||||||
|
* 这里你可以使用连击定义中的自定义字段来添加额外的选择规则。
|
||||||
|
* @param ComboEvent The combo event data to provide as context.连击事件数据,用作上下文参考。
|
||||||
|
* @param CurrentStep The current combo step of combat system. 当前的连击步骤。
|
||||||
|
* @param ComboDefinition The combo definition you are checking. 正在检查的连击定义。
|
||||||
|
*/
|
||||||
|
UFUNCTION(BlueprintNativeEvent, Category = "Combat Ability")
|
||||||
|
bool CanSelectedComboDefinition(const FGameplayEventData& ComboEvent, int32 CurrentStep, const FGCS_ComboDefinition& ComboDefinition) const;
|
||||||
|
|
||||||
|
virtual void HandleComboExecution(const FGameplayEventData& ComboEventData);
|
||||||
|
|
||||||
|
// virtual void GiveSubAbilities(const FGameplayAbilitySpec& CurrentSpec);
|
||||||
|
// virtual void RemoveSubAbilities();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
bool bCurrentAbilityEnded = false;
|
||||||
|
|
||||||
|
//The ability current combo step was executing.
|
||||||
|
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Combo")
|
||||||
|
FGameplayAbilitySpecHandle CurrentAbility;
|
||||||
|
|
||||||
|
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Combo")
|
||||||
|
TSubclassOf<UGameplayAbility> CurrentAbilityClass;
|
||||||
|
|
||||||
|
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Combo")
|
||||||
|
FGameplayAbilitySpecHandle NextAbility;
|
||||||
|
|
||||||
|
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Combo")
|
||||||
|
TSubclassOf<UGameplayAbility> NextAbilityAbilityClass;
|
||||||
|
|
||||||
|
int32 DesiredComboStep{INDEX_NONE};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Granted potential combo abilities.
|
||||||
|
* 赋予的潜在ComboAbilities.
|
||||||
|
*/
|
||||||
|
// UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Combo")
|
||||||
|
// TArray<FGameplayAbilitySpecHandle> AvailableAbilities;
|
||||||
|
|
||||||
|
FDelegateHandle AbilityEndedDelegateHandle;
|
||||||
|
|
||||||
|
#if WITH_EDITOR
|
||||||
|
virtual EDataValidationResult IsDataValid(class FDataValidationContext& Context) const override;
|
||||||
|
#endif
|
||||||
|
};
|
||||||
@@ -0,0 +1,124 @@
|
|||||||
|
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "AttributeSet.h"
|
||||||
|
#include "AbilitySystemComponent.h"
|
||||||
|
#include "NativeGameplayTags.h"
|
||||||
|
|
||||||
|
#include "AS_Poise.generated.h"
|
||||||
|
|
||||||
|
namespace AS_Poise
|
||||||
|
{
|
||||||
|
|
||||||
|
GENERICCOMBATSYSTEM_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(Poise)
|
||||||
|
|
||||||
|
GENERICCOMBATSYSTEM_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(MaxPoise)
|
||||||
|
|
||||||
|
GENERICCOMBATSYSTEM_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(PoiseRecover)
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#define ATTRIBUTE_ACCESSORS(ClassName, PropertyName) \
|
||||||
|
GAMEPLAYATTRIBUTE_PROPERTY_GETTER(ClassName, PropertyName) \
|
||||||
|
GAMEPLAYATTRIBUTE_VALUE_GETTER(PropertyName) \
|
||||||
|
GAMEPLAYATTRIBUTE_VALUE_SETTER(PropertyName) \
|
||||||
|
GAMEPLAYATTRIBUTE_VALUE_INITTER(PropertyName)
|
||||||
|
|
||||||
|
UCLASS()
|
||||||
|
class GENERICCOMBATSYSTEM_API UAS_Poise : public UAttributeSet
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
UAS_Poise();
|
||||||
|
|
||||||
|
virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
|
||||||
|
|
||||||
|
virtual void PreAttributeChange(const FGameplayAttribute& Attribute, float& NewValue) override;
|
||||||
|
|
||||||
|
virtual void PostAttributeChange(const FGameplayAttribute& Attribute, float OldValue, float NewValue) override;
|
||||||
|
|
||||||
|
virtual bool PreGameplayEffectExecute(struct FGameplayEffectModCallbackData& Data) override;
|
||||||
|
|
||||||
|
virtual void PostGameplayEffectExecute(const FGameplayEffectModCallbackData& Data) override;
|
||||||
|
|
||||||
|
// Current Poise value of an actor.(actor的当前抗打击值)
|
||||||
|
UPROPERTY(BlueprintReadOnly, ReplicatedUsing = OnRep_Poise, Category = "Attribute|PoiseSet", Meta = (AllowPrivateAccess = true))
|
||||||
|
FGameplayAttributeData Poise{ 3 };
|
||||||
|
ATTRIBUTE_ACCESSORS(ThisClass, Poise)
|
||||||
|
|
||||||
|
// Max Poise value of an actor.(actor的最大抗打击值)
|
||||||
|
UPROPERTY(BlueprintReadOnly, ReplicatedUsing = OnRep_MaxPoise, Category = "Attribute|PoiseSet", Meta = (AllowPrivateAccess = true))
|
||||||
|
FGameplayAttributeData MaxPoise{ 3 };
|
||||||
|
ATTRIBUTE_ACCESSORS(ThisClass, MaxPoise)
|
||||||
|
|
||||||
|
// How many Poise to recover per second.(每秒恢复抗打击值)
|
||||||
|
UPROPERTY(BlueprintReadOnly, ReplicatedUsing = OnRep_PoiseRecover, Category = "Attribute|PoiseSet", Meta = (AllowPrivateAccess = true))
|
||||||
|
FGameplayAttributeData PoiseRecover{ 1 };
|
||||||
|
ATTRIBUTE_ACCESSORS(ThisClass, PoiseRecover)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable,BlueprintPure,meta=(DisplayName="GetPoiseAttribute"), Category = "Attribute|PoiseSet")
|
||||||
|
static FGameplayAttribute Bp_GetPoiseAttribute();
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintPure,meta=(DisplayName="GetPoise"), Category = "Attribute|PoiseSet")
|
||||||
|
float Bp_GetPoise() const;
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable,meta=(DisplayName="SetPoise"), Category = "Attribute|PoiseSet")
|
||||||
|
void Bp_SetPoise(float NewValue);
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable,meta=(DisplayName="InitPoise"), Category = "Attribute|PoiseSet")
|
||||||
|
void Bp_InitPoise(float NewValue);
|
||||||
|
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable,BlueprintPure,meta=(DisplayName="GetMaxPoiseAttribute"), Category = "Attribute|PoiseSet")
|
||||||
|
static FGameplayAttribute Bp_GetMaxPoiseAttribute();
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintPure,meta=(DisplayName="GetMaxPoise"), Category = "Attribute|PoiseSet")
|
||||||
|
float Bp_GetMaxPoise() const;
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable,meta=(DisplayName="SetMaxPoise"), Category = "Attribute|PoiseSet")
|
||||||
|
void Bp_SetMaxPoise(float NewValue);
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable,meta=(DisplayName="InitMaxPoise"), Category = "Attribute|PoiseSet")
|
||||||
|
void Bp_InitMaxPoise(float NewValue);
|
||||||
|
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable,BlueprintPure,meta=(DisplayName="GetPoiseRecoverAttribute"), Category = "Attribute|PoiseSet")
|
||||||
|
static FGameplayAttribute Bp_GetPoiseRecoverAttribute();
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintPure,meta=(DisplayName="GetPoiseRecover"), Category = "Attribute|PoiseSet")
|
||||||
|
float Bp_GetPoiseRecover() const;
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable,meta=(DisplayName="SetPoiseRecover"), Category = "Attribute|PoiseSet")
|
||||||
|
void Bp_SetPoiseRecover(float NewValue);
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable,meta=(DisplayName="InitPoiseRecover"), Category = "Attribute|PoiseSet")
|
||||||
|
void Bp_InitPoiseRecover(float NewValue);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
protected:
|
||||||
|
|
||||||
|
/** Helper function to proportionally adjust the value of an attribute when it's associated max attribute changes. (i.e. When MaxHealth increases, Health increases by an amount that maintains the same percentage as before) */
|
||||||
|
virtual void AdjustAttributeForMaxChange(FGameplayAttributeData& AffectedAttribute, const FGameplayAttributeData& MaxAttribute, float NewMaxValue, const FGameplayAttribute& AffectedAttributeProperty);
|
||||||
|
|
||||||
|
UFUNCTION()
|
||||||
|
virtual void OnRep_Poise(const FGameplayAttributeData& OldValue);
|
||||||
|
|
||||||
|
UFUNCTION()
|
||||||
|
virtual void OnRep_MaxPoise(const FGameplayAttributeData& OldValue);
|
||||||
|
|
||||||
|
UFUNCTION()
|
||||||
|
virtual void OnRep_PoiseRecover(const FGameplayAttributeData& OldValue);
|
||||||
|
|
||||||
|
};
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "GGA_AbilitySystemGlobals.h"
|
||||||
|
#include "DEPRECATED_GCS_AbilitySystemGlobals.generated.h"
|
||||||
|
|
||||||
|
UCLASS(Deprecated, meta=(DeprecationMessage="GCS_AbilitySystemGlobals is deprecated. Please use GGA_AbilitySystemGlobals instead."))
|
||||||
|
class GENERICCOMBATSYSTEM_API UDEPRECATED_GCS_AbilitySystemGlobals : public UGGA_AbilitySystemGlobals
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
virtual FGameplayEffectContext* AllocGameplayEffectContext() const override;
|
||||||
|
};
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "GameplayEffectComponent.h"
|
||||||
|
#include "GCS_GEComponent_PredictivelyExecute.generated.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This component will predictively execute instant GE which treated as infinite one.
|
||||||
|
* @attention Internally will mark this effect context as local predicting context, so you can conditional apply logic in subsequent codes.
|
||||||
|
*/
|
||||||
|
UCLASS()
|
||||||
|
class GENERICCOMBATSYSTEM_API UGCS_GEComponent_PredictivelyExecute : public UGameplayEffectComponent
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
virtual void OnGameplayEffectApplied(FActiveGameplayEffectsContainer& ActiveGEContainer, FGameplayEffectSpec& GESpec, FPredictionKey& PredictionKey) const override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
UPROPERTY(EditDefaultsOnly, Category = GCS)
|
||||||
|
bool bPredictGameplayCues{false};
|
||||||
|
};
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "GCS_CombatStructLibrary.h"
|
||||||
|
#include "UObject/Object.h"
|
||||||
|
#include "GCS_GameplayEffectContext.generated.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Combat related data which was carried and pass around within gameplay effect context, as one of the gameplay effect context payload.
|
||||||
|
* 战斗相关数据,在游戏效果上下文中被携带和传递,作为游戏效果上下文数据之一。
|
||||||
|
*/
|
||||||
|
USTRUCT(BlueprintType)
|
||||||
|
struct GENERICCOMBATSYSTEM_API FGCS_ContextPayload_Combat
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
|
||||||
|
void SetTaggedValue(const FGameplayTag& Tag, float NewValue);
|
||||||
|
|
||||||
|
float GetTaggedValue(const FGameplayTag& Tag) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicate the effect spec owning this context was Predictively executed.
|
||||||
|
* 表示拥有此上下文的效果实例是以客户端预测方式执行的。
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GCS")
|
||||||
|
bool bIsPredictingContext{false};
|
||||||
|
|
||||||
|
UPROPERTY()
|
||||||
|
FPredictionKey PredictionKey;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attack definition data table.
|
||||||
|
* 攻击定义数据表。
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GCS", meta=(RequiredAssetDataTags = "RowStructure=/Script/GenericCombatSystem.GCS_AttackDefinition"))
|
||||||
|
TObjectPtr<const UDataTable> AtkDataTable{nullptr};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Row name in the attack definition table.
|
||||||
|
* 攻击定义表中的行名。
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GCS")
|
||||||
|
FName AtkRowName{NAME_None};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bullet definition data table.
|
||||||
|
* 子弹定义数据表。
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GCS", meta=(RequiredAssetDataTags = "RowStructure=/Script/GenericCombatSystem.GCS_BulletDefinition"))
|
||||||
|
TObjectPtr<const UDataTable> BulletDataTable{nullptr};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Row name in the bullet definition table.
|
||||||
|
* 子弹定义表中的行名。
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GCS")
|
||||||
|
FName BulletRowName{NAME_None};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The tags added during gameplay effect apply(coming from MMC,or Execution).
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GCS")
|
||||||
|
FGameplayTagContainer DynamicTags;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Array of tagged values associated with the combat process.
|
||||||
|
* 与战斗过程关联的标记值数组。
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GCS")
|
||||||
|
TArray<FGCS_TaggedValue> TaggedValues;
|
||||||
|
};
|
||||||
@@ -0,0 +1,116 @@
|
|||||||
|
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "Abilities/Tasks/AbilityTask.h"
|
||||||
|
#include "Collision/GCS_TraceStructLibrary.h"
|
||||||
|
#include "Components/SkeletalMeshComponent.h"
|
||||||
|
#include "GCS_AbilityTask_CollisionTrace.generated.h"
|
||||||
|
|
||||||
|
class UGCS_AttackRequest_Melee;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ability task for handling collision traces in combat.
|
||||||
|
* 处理战斗中碰撞检测的能力任务。
|
||||||
|
*/
|
||||||
|
UCLASS()
|
||||||
|
class GENERICCOMBATSYSTEM_API UGCS_AbilityTask_CollisionTrace : public UAbilityTask
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
UGCS_AbilityTask_CollisionTrace();
|
||||||
|
/**
|
||||||
|
* Creates and activates a collision trace task.
|
||||||
|
* 创建并激活碰撞检测任务。
|
||||||
|
* @param OwningAbility The owning gameplay ability. 所属游戏能力。
|
||||||
|
* @param TaskInstanceName The name of the task instance. 任务实例名称。
|
||||||
|
* @param bAdjustVisibilityBasedAnimTickOption Whether to adjust visibility-based animation ticking. 是否调整基于可见性的动画tick。
|
||||||
|
* @return The created task. 创建的任务。
|
||||||
|
*/
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "GCS|AbilityTasks", meta = (HidePin = "OwningAbility", DefaultToSelf = "OwningAbility", BlueprintInternalUseOnly = "TRUE"))
|
||||||
|
static UGCS_AbilityTask_CollisionTrace* HandleCollisionTraces(UGameplayAbility* OwningAbility, FName TaskInstanceName, bool bAdjustVisibilityBasedAnimTickOption = false);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Activates the task.
|
||||||
|
* 激活任务。
|
||||||
|
*/
|
||||||
|
virtual void Activate() override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the task is destroyed.
|
||||||
|
* 任务销毁时调用。
|
||||||
|
* @param bInOwnerFinished Whether the owner finished the task. 拥有者是否完成了任务。
|
||||||
|
*/
|
||||||
|
virtual void OnDestroy(bool bInOwnerFinished) override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a melee attack request to the task.
|
||||||
|
* 向任务添加近战攻击请求。
|
||||||
|
* @param Request The melee attack request. 近战攻击请求。
|
||||||
|
* @param SourceObject Optional source object. 可选的源对象。
|
||||||
|
*/
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "GCS|AbilityTasks")
|
||||||
|
void AddMeleeRequest(const UGCS_AttackRequest_Melee* Request, UObject* SourceObject);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a melee attack request from the task.
|
||||||
|
* 从任务移除近战攻击请求。
|
||||||
|
* @param Request The melee attack request. 近战攻击请求。
|
||||||
|
*/
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "GCS|AbilityTasks")
|
||||||
|
void RemoveMeleeRequest(const UGCS_AttackRequest_Melee* Request);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delegate for trace instance hit events.
|
||||||
|
* 碰撞检测实例命中事件的委托。
|
||||||
|
*/
|
||||||
|
DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FGCS_OnTraceInstanceHitSignature, const UGCS_AttackRequest_Melee*, MeleeRequest, const FGCS_TraceHandle&, TraceHandle,
|
||||||
|
const FHitResult&,
|
||||||
|
HitResult);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fired when a trace instance detects targets.
|
||||||
|
* 当碰撞检测实例检测到目标时触发。
|
||||||
|
*/
|
||||||
|
UPROPERTY(BlueprintAssignable)
|
||||||
|
FGCS_OnTraceInstanceHitSignature OnTargetsFound;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
/**
|
||||||
|
* Handles trace instance hit events.
|
||||||
|
* 处理碰撞检测实例命中事件。
|
||||||
|
* @param TraceHandle The trace instance. 碰撞检测实例。
|
||||||
|
* @param HitResult The hit result. 命中结果。
|
||||||
|
*/
|
||||||
|
UFUNCTION()
|
||||||
|
void TraceHitCallback(const FGCS_TraceHandle& TraceHandle, const FHitResult& HitResult);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map of melee requests to their associated trace instances.
|
||||||
|
* 近战请求及其关联碰撞检测实例的映射。
|
||||||
|
*/
|
||||||
|
TMap<TObjectPtr<const UGCS_AttackRequest_Melee>, TArray<FGCS_TraceHandle>> MeleeRequests;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether to adjust visibility-based animation ticking.
|
||||||
|
* 是否调整基于可见性的动画tick。
|
||||||
|
*/
|
||||||
|
UPROPERTY()
|
||||||
|
bool bAdjustAnimTickOption{false};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the animation tick option was adjusted.
|
||||||
|
* 是否已调整动画tick选项。
|
||||||
|
*/
|
||||||
|
UPROPERTY()
|
||||||
|
bool bAdjustedAnimTickOption{false};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The previous animation tick option.
|
||||||
|
* 之前的动画tick选项。
|
||||||
|
*/
|
||||||
|
UPROPERTY()
|
||||||
|
EVisibilityBasedAnimTickOption PrevAnimTickOption{EVisibilityBasedAnimTickOption::AlwaysTickPose};
|
||||||
|
};
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "GameplayTagContainer.h"
|
||||||
|
#include "GCS_BulletStructLibrary.h"
|
||||||
|
#include "Net/Serialization/FastArraySerializer.h"
|
||||||
|
#include "UObject/Object.h"
|
||||||
|
#include "GCS_BulletContainer.generated.h"
|
||||||
|
|
||||||
|
|
||||||
|
struct FGCS_BulletContainer;
|
||||||
|
class UGCS_BulletSystemComponent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Structure representing an equipment entry in the container.
|
||||||
|
* 表示容器中装备条目的结构体。
|
||||||
|
* @note WIP
|
||||||
|
*/
|
||||||
|
USTRUCT(BlueprintType)
|
||||||
|
struct GENERICCOMBATSYSTEM_API FGCS_BulletEntry : public FFastArraySerializerItem
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
friend FGCS_BulletContainer;
|
||||||
|
|
||||||
|
//The request id of this entry.
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GCS")
|
||||||
|
FGuid Id;
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GCS")
|
||||||
|
FGCS_BulletSpawnParameters SpawnParameters;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Container for a list of applied equipment.
|
||||||
|
* 存储已应用装备列表的容器。
|
||||||
|
*/
|
||||||
|
USTRUCT()
|
||||||
|
struct GENERICCOMBATSYSTEM_API FGCS_BulletContainer : public FFastArraySerializer
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
FGCS_BulletContainer()
|
||||||
|
: OwningComponent(nullptr)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
FGCS_BulletContainer(UGCS_BulletSystemComponent* InComponent)
|
||||||
|
: OwningComponent(InComponent)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void PreReplicatedRemove(const TArrayView<int32> RemovedIndices, int32 FinalSize);
|
||||||
|
|
||||||
|
|
||||||
|
void PostReplicatedAdd(const TArrayView<int32> AddedIndices, int32 FinalSize);
|
||||||
|
|
||||||
|
void PostReplicatedChange(const TArrayView<int32> ChangedIndices, int32 FinalSize);
|
||||||
|
//~End of FFastArraySerializer contract
|
||||||
|
|
||||||
|
bool NetDeltaSerialize(FNetDeltaSerializeInfo& DeltaParms)
|
||||||
|
{
|
||||||
|
return FastArrayDeltaSerialize<FGCS_BulletEntry, FGCS_BulletContainer>(Entries, DeltaParms, *this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int32 IndexOfById(const FGuid& Id) const;
|
||||||
|
|
||||||
|
UPROPERTY(VisibleAnywhere, Category="BulletSystem", meta=(ShowOnlyInnerProperties, DisplayName="Bullets"))
|
||||||
|
TArray<FGCS_BulletEntry> Entries;
|
||||||
|
|
||||||
|
UPROPERTY()
|
||||||
|
TObjectPtr<UGCS_BulletSystemComponent> OwningComponent;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct TStructOpsTypeTraits<FGCS_BulletContainer> : TStructOpsTypeTraitsBase2<FGCS_BulletContainer>
|
||||||
|
{
|
||||||
|
enum { WithNetDeltaSerializer = true };
|
||||||
|
};
|
||||||
@@ -0,0 +1,406 @@
|
|||||||
|
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "GCS_BulletStructLibrary.h"
|
||||||
|
#include "GCS_EffectCauserInterface.h"
|
||||||
|
#include "GameFramework/Actor.h"
|
||||||
|
#include "GCS_BulletInstance.generated.h"
|
||||||
|
|
||||||
|
class UGCS_AttackRequest_Bullet;
|
||||||
|
class UGCS_BulletSubsystem;
|
||||||
|
class UProjectileMovementComponent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base class for bullet instances.
|
||||||
|
* 子弹实例的基类。
|
||||||
|
*/
|
||||||
|
UCLASS(Abstract, BlueprintType, NotBlueprintable)
|
||||||
|
class GENERICCOMBATSYSTEM_API AGCS_BulletInstance : public AActor, public IGCS_EffectCauserInterface
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
friend UGCS_BulletSubsystem;
|
||||||
|
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* Default constructor.
|
||||||
|
* 默认构造函数。
|
||||||
|
*/
|
||||||
|
AGCS_BulletInstance();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets lifetime replicated properties.
|
||||||
|
* 获取生命周期复制属性。
|
||||||
|
* @param OutLifetimeProps The lifetime properties. 生命周期属性。
|
||||||
|
*/
|
||||||
|
virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the projectile movement component.
|
||||||
|
* 获取子弹的运动组件。
|
||||||
|
* @return The projectile movement component. 运动组件。
|
||||||
|
*/
|
||||||
|
UFUNCTION(BlueprintCallable, BlueprintPure, Category="GCS|Bullet")
|
||||||
|
UProjectileMovementComponent* GetProjectileMovementComponent() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the bullet definition handle.
|
||||||
|
* 设置子弹定义句柄。
|
||||||
|
* @param NewHandle The new definition handle. 新定义句柄。
|
||||||
|
*/
|
||||||
|
UFUNCTION(BlueprintCallable, Category="GCS|Bullet")
|
||||||
|
void SetDefinitionHandle(UPARAM(meta=(RowType="/Script/GenericCombatSystem.GCS_BulletDefinition")) FDataTableRowHandle NewHandle);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the bullet's unique ID.
|
||||||
|
* 设置子弹的唯一ID。
|
||||||
|
* @param NewId The new ID. 新ID。
|
||||||
|
*/
|
||||||
|
void SetBulletId(const FGuid& NewId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the bullet's unique ID.
|
||||||
|
* 获取子弹的唯一ID。
|
||||||
|
* @return The bullet ID. 子弹ID。
|
||||||
|
*/
|
||||||
|
UFUNCTION(BlueprintCallable, BlueprintPure, Category="GCS|Bullet")
|
||||||
|
FGuid GetBulletId() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the parent bullet ID for bullet chains.
|
||||||
|
* 设置子弹链的父子弹ID。
|
||||||
|
* @param NewParentId The parent bullet ID. 父子弹ID。
|
||||||
|
*/
|
||||||
|
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category="GCS|Bullet")
|
||||||
|
void SetParentBulletId(FGuid NewParentId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the parent bullet ID.
|
||||||
|
* 获取父子弹ID。
|
||||||
|
* @return The parent bullet ID. 父子弹ID。
|
||||||
|
*/
|
||||||
|
UFUNCTION(BlueprintCallable, BlueprintPure, Category="GCS|Bullet")
|
||||||
|
FGuid GetParentBulletId() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Launches the bullet.
|
||||||
|
* 发射子弹。
|
||||||
|
*/
|
||||||
|
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category="GCS|Bullet")
|
||||||
|
void LaunchBullet();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the hit result for the bullet.
|
||||||
|
* 设置子弹的命中结果。
|
||||||
|
* @param NewHitResult The hit result. 命中结果。
|
||||||
|
*/
|
||||||
|
UFUNCTION(BlueprintCallable, Category="GCS|Bullet", meta=(DisplayName="Set Bullet HitResult"))
|
||||||
|
void SetHitResult(const FHitResult& NewHitResult);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the latest hit result.
|
||||||
|
* 获取最新的命中结果。
|
||||||
|
* @return The hit result. 命中结果。
|
||||||
|
*/
|
||||||
|
UFUNCTION(BlueprintCallable, BlueprintPure, Category="GCS|Bullet", meta=(DisplayName="Get Bullet HitResult"))
|
||||||
|
const FHitResult& GetHitResult() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the bullet has gameplay authority.
|
||||||
|
* 检查子弹是否具有游戏权限。
|
||||||
|
* @return True if the bullet has authority. 如果子弹具有权限返回true。
|
||||||
|
*/
|
||||||
|
UFUNCTION(BlueprintCallable, BlueprintPure, Category="GCS|Bullet")
|
||||||
|
bool HasGameplayAuthority() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called after network initialization.
|
||||||
|
* 网络初始化后调用。
|
||||||
|
*/
|
||||||
|
virtual void PostNetInit() override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called after receiving network data.
|
||||||
|
* 接收网络数据后调用。
|
||||||
|
*/
|
||||||
|
virtual void PostNetReceive() override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles locally predicted bullet instances.
|
||||||
|
* 处理本地预测的子弹实例。
|
||||||
|
* @param PredictedBullet The predicted bullet instance. 预测的子弹实例。
|
||||||
|
*/
|
||||||
|
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category="GCS|Bullet")
|
||||||
|
void FoundLocalPredictedBullet(AGCS_BulletInstance* PredictedBullet);
|
||||||
|
|
||||||
|
// Effect causer interface
|
||||||
|
/**
|
||||||
|
* Gets the gameplay effect spec handle.
|
||||||
|
* 获取游戏效果规格句柄。
|
||||||
|
* @param OutHandle The effect spec handle (output). 效果规格句柄(输出)。
|
||||||
|
* @return True if successful. 如果成功返回true。
|
||||||
|
*/
|
||||||
|
virtual bool GetEffectSpecHandle_Implementation(FGameplayEffectSpecHandle& OutHandle) override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the gameplay effect container.
|
||||||
|
* 获取游戏效果容器。
|
||||||
|
* @return The effect container. 效果容器。
|
||||||
|
*/
|
||||||
|
virtual FGGA_GameplayEffectContainer GetEffectContainer_Implementation() const override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the effect container level override.
|
||||||
|
* 获取效果容器级别覆盖。
|
||||||
|
* @return The level override. 级别覆盖。
|
||||||
|
*/
|
||||||
|
virtual int32 GetEffectContainerLevelOverride_Implementation() const override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the effect container spec.
|
||||||
|
* 设置效果容器规格。
|
||||||
|
* @param InEffectContainerSpec The effect container spec. 效果容器规格。
|
||||||
|
*/
|
||||||
|
virtual void SetEffectContainerSpec_Implementation(const FGGA_GameplayEffectContainerSpec& InEffectContainerSpec) override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the effect container spec.
|
||||||
|
* 获取效果容器规格。
|
||||||
|
* @return The effect container spec. 效果容器规格。
|
||||||
|
*/
|
||||||
|
virtual FGGA_GameplayEffectContainerSpec GetEffectContainerSpec_Implementation() const override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the effect class.
|
||||||
|
* 获取效果类。
|
||||||
|
* @return The effect class. 效果类。
|
||||||
|
*/
|
||||||
|
virtual TSubclassOf<UGameplayEffect> GetEffectClass_Implementation() const override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the effect level.
|
||||||
|
* 获取效果级别。
|
||||||
|
* @return The effect level. 效果级别。
|
||||||
|
*/
|
||||||
|
virtual int32 GetEffectLevel_Implementation() const override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the effect spec.
|
||||||
|
* 设置效果规格。
|
||||||
|
* @param InEffectSpec The effect spec. 效果规格。
|
||||||
|
*/
|
||||||
|
virtual void SetEffectSpec_Implementation(FGameplayEffectSpecHandle& InEffectSpec) override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the bullet's shape component.
|
||||||
|
* 获取子弹的形状组件。
|
||||||
|
* @return The shape component. 形状组件。
|
||||||
|
*/
|
||||||
|
UFUNCTION(BlueprintCallable, BlueprintPure, BlueprintNativeEvent, Category="GCS|Bullet")
|
||||||
|
UShapeComponent* GetBulletShape() const;
|
||||||
|
virtual UShapeComponent* GetBulletShape_Implementation() const;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
/**
|
||||||
|
* Called when the game starts or when spawned.
|
||||||
|
* 游戏开始或生成时调用。
|
||||||
|
*/
|
||||||
|
virtual void BeginPlay() override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the game ends or the bullet is destroyed.
|
||||||
|
* 游戏结束或子弹销毁时调用。
|
||||||
|
* @param EndPlayReason The reason for ending. 结束原因。
|
||||||
|
*/
|
||||||
|
virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the bullet is taken from the pool with a valid definition.
|
||||||
|
* 子弹从池中取出且具有有效定义时调用。
|
||||||
|
*/
|
||||||
|
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category="GCS|Bullet")
|
||||||
|
void OnBulletBeginPlay();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the bullet is returned to the pool and deactivated.
|
||||||
|
* 子弹返回池中并停用时调用。
|
||||||
|
*/
|
||||||
|
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category="GCS|Bullet")
|
||||||
|
void OnBulletEndPlay();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Records the initial location and rotation of the bullet.
|
||||||
|
* 记录子弹的初始位置和旋转。
|
||||||
|
*/
|
||||||
|
UFUNCTION(BlueprintCallable, Category="GCS|Bullet")
|
||||||
|
void SetupInitialLocationAndRotation();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refreshes the bullet's travel states.
|
||||||
|
* 刷新子弹的移动状态。
|
||||||
|
*/
|
||||||
|
UFUNCTION(BlueprintCallable, Category="GCS|Bullet")
|
||||||
|
virtual void RefreshTravelStates();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the hit result should be penetrated.
|
||||||
|
* 检查命中结果是否应穿透。
|
||||||
|
* @param InHitResult The hit result. 命中结果。
|
||||||
|
* @return True if the hit should be penetrated. 如果命中应穿透返回true。
|
||||||
|
*/
|
||||||
|
UFUNCTION(BlueprintCallable, BlueprintPure, Category="GCS|Bullet")
|
||||||
|
virtual bool ShouldPenetrateHitResult(const FHitResult& InHitResult) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if a bullet chain should be generated.
|
||||||
|
* 检查是否应生成子弹链。
|
||||||
|
* @return True if a bullet chain should be generated. 如果应生成子弹链返回true。
|
||||||
|
*/
|
||||||
|
UFUNCTION(BlueprintCallable, BlueprintPure, BlueprintNativeEvent, Category="GCS|Bullet")
|
||||||
|
bool ShouldGenerateBullet();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles bullet chain logic on hit.
|
||||||
|
* 处理命中时的子弹链逻辑。
|
||||||
|
*/
|
||||||
|
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category="GCS|Bullet")
|
||||||
|
void HandleBulletHitChains();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies gameplay effects to the hit result.
|
||||||
|
* 对命中结果应用游戏效果。
|
||||||
|
* @param HitResult The hit result. 命中结果。
|
||||||
|
*/
|
||||||
|
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category="GCS|Bullet")
|
||||||
|
void ApplyGameplayEffects(FHitResult HitResult);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the bullet ID is replicated.
|
||||||
|
* 子弹ID复制时调用。
|
||||||
|
* @param Prev The previous bullet ID. 之前的子弹ID。
|
||||||
|
*/
|
||||||
|
UFUNCTION()
|
||||||
|
virtual void OnRep_BulletId(FGuid Prev);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the bullet definition is replicated.
|
||||||
|
* 子弹定义复制时调用。
|
||||||
|
*/
|
||||||
|
UFUNCTION()
|
||||||
|
void OnRep_BulletDefinition();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unique ID of the bullet.
|
||||||
|
* 子弹的唯一ID。
|
||||||
|
*/
|
||||||
|
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, ReplicatedUsing=OnRep_BulletId, Category="GCS|BulletState")
|
||||||
|
FGuid BulletId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the bullet is locally predicted.
|
||||||
|
* 子弹是否为本地预测。
|
||||||
|
*/
|
||||||
|
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="GCS|BulletState")
|
||||||
|
bool bIsLocalPredicting{false};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the bullet was spawned on the server.
|
||||||
|
* 子弹是否在服务器上生成。
|
||||||
|
*/
|
||||||
|
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="GCS|BulletState")
|
||||||
|
bool bServerInitiated{false};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parent bullet ID for bullet chains.
|
||||||
|
* 子弹链的父子弹ID。
|
||||||
|
*/
|
||||||
|
UPROPERTY(VisibleAnywhere, Category="GCS|BulletState")
|
||||||
|
FGuid ParentBulletId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The associated attack request.
|
||||||
|
* 关联的攻击请求。
|
||||||
|
*/
|
||||||
|
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="GCS|BulletState")
|
||||||
|
TObjectPtr<const UGCS_AttackRequest_Bullet> Request;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle to the bullet definition.
|
||||||
|
* 子弹定义的句柄。
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GCS|Bullet", ReplicatedUsing=OnRep_BulletDefinition, meta=(ExposeOnSpawn))
|
||||||
|
FDataTableRowHandle DefinitionHandle;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loaded bullet definition.
|
||||||
|
* 加载的子弹定义。
|
||||||
|
*/
|
||||||
|
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="GCS|BulletState")
|
||||||
|
FGCS_BulletDefinition Definition;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The projectile movement component.
|
||||||
|
* 子弹的运动组件。
|
||||||
|
*/
|
||||||
|
UPROPERTY(VisibleAnywhere, Category="GCS|Bullet")
|
||||||
|
TObjectPtr<UProjectileMovementComponent> ProjectileMovement;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The gameplay effect spec carried by the bullet.
|
||||||
|
* 子弹携带的游戏效果规格。
|
||||||
|
*/
|
||||||
|
UPROPERTY(VisibleAnywhere, Category="GCS|BulletState")
|
||||||
|
FGameplayEffectSpecHandle EffectSpecHandle;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The gameplay effect container spec carried by the bullet.
|
||||||
|
* 子弹携带的游戏效果容器规格。
|
||||||
|
*/
|
||||||
|
UPROPERTY(VisibleAnywhere, Category = "GCS|BulletState")
|
||||||
|
FGGA_GameplayEffectContainerSpec EffectContainerSpec;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Index for bullet chains.
|
||||||
|
* 子弹链的索引。
|
||||||
|
*/
|
||||||
|
UPROPERTY()
|
||||||
|
int32 ChainIndex{0};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initial location of the bullet.
|
||||||
|
* 子弹的初始位置。
|
||||||
|
*/
|
||||||
|
UPROPERTY(BlueprintReadWrite, EditDefaultsOnly, Category="GCS|BulletState")
|
||||||
|
FVector InitialActorLocation;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initial rotation of the bullet.
|
||||||
|
* 子弹的初始旋转。
|
||||||
|
*/
|
||||||
|
UPROPERTY(BlueprintReadWrite, EditDefaultsOnly, Category="GCS|BulletState")
|
||||||
|
FRotator InitialActorRotation;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The latest hit result.
|
||||||
|
* 最新的命中结果。
|
||||||
|
*/
|
||||||
|
UPROPERTY(VisibleAnywhere, Category="GCS|BulletState")
|
||||||
|
FHitResult LastHitResult;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Distance traveled by the bullet.
|
||||||
|
* 子弹的移动距离。
|
||||||
|
*/
|
||||||
|
UPROPERTY(BlueprintReadWrite, VisibleAnywhere, Category="GCS|BulletState")
|
||||||
|
double TraveledDistance{0.0f};
|
||||||
|
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* Called every frame.
|
||||||
|
* 每帧调用。
|
||||||
|
* @param DeltaTime Time since last frame. 上一帧以来的时间。
|
||||||
|
*/
|
||||||
|
virtual void Tick(float DeltaTime) override;
|
||||||
|
};
|
||||||
@@ -0,0 +1,351 @@
|
|||||||
|
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "GCS_CombatStructLibrary.h"
|
||||||
|
#include "Runtime/Launch/Resources/Version.h"
|
||||||
|
#if ENGINE_MINOR_VERSION < 5
|
||||||
|
#include "InstancedStruct.h"
|
||||||
|
#else
|
||||||
|
#include "StructUtils/InstancedStruct.h"
|
||||||
|
#endif
|
||||||
|
#include "Collision/GCS_TraceStructLibrary.h"
|
||||||
|
#include "UObject/Object.h"
|
||||||
|
#include "GCS_BulletStructLibrary.generated.h"
|
||||||
|
|
||||||
|
class UGCS_AttackRequest_Bullet;
|
||||||
|
class UNiagaraSystem;
|
||||||
|
class AGCS_BulletInstance;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base struct allow you to extend the bullet definition's fields using C++.
|
||||||
|
* 基础结构体,允许你通过C++拓展子弹定义的字段。
|
||||||
|
*/
|
||||||
|
USTRUCT(BlueprintType, meta=(Hidden))
|
||||||
|
struct GENERICCOMBATSYSTEM_API FGCS_BulletDefinitionExtension
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data structure defining bullet properties and behavior.
|
||||||
|
* 定义子弹属性和行为的数据结构。
|
||||||
|
*/
|
||||||
|
USTRUCT(BlueprintType, meta=(DisplayName="GCS Bullet Definition"))
|
||||||
|
struct GENERICCOMBATSYSTEM_API FGCS_BulletDefinition : public FTableRowBase
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The bullet actor class.
|
||||||
|
* 子弹Actor类。
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Common", meta=(AllowAbstract="false"))
|
||||||
|
TSoftClassPtr<AGCS_BulletInstance> BulletActorClass;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Duration for which the bullet exists (-1 for infinite).
|
||||||
|
* 子弹存在的持续时间(-1表示无限)。
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Common")
|
||||||
|
float Duration{3.0};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Number of bullets fired at once.
|
||||||
|
* 一次性发射的子弹数量。
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Launch Configuration", meta=(ClampMin=1, UIMin=1))
|
||||||
|
int32 BulletCount{1};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Yaw angle for bullet launch.
|
||||||
|
* 子弹发射的水平角。
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Launch Configuration")
|
||||||
|
float LaunchAngle{0.0f};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Yaw angle interval between bullets.
|
||||||
|
* 子弹之间的水平角间隔。
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Launch Configuration")
|
||||||
|
float LaunchAngleInterval{10.0f};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pitch angle for bullet launch.
|
||||||
|
* 子弹发射的仰角。
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Launch Configuration")
|
||||||
|
float LaunchElevationAngle{0.0f};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Distance at which bullet attenuation begins.
|
||||||
|
* 子弹开始衰减的距离。
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Movement", meta=(Units="cm"))
|
||||||
|
float AttenuationRange{800.0f};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gravity scale within the attenuation range.
|
||||||
|
* 衰减范围内的重力系数。
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Movement")
|
||||||
|
float GravityScaleInRange{1.0f};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gravity scale outside the attenuation range.
|
||||||
|
* 衰减范围外的重力系数。
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Movement")
|
||||||
|
float GravityScaleOutRage{1.0f};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initial hit radius for the bullet.
|
||||||
|
* 子弹的初始命中半径。
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Movement", meta=(Units="cm"))
|
||||||
|
float InitialHitRadius{20.0f};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Final hit radius for the bullet (-1 to use initial radius).
|
||||||
|
* 子弹的最终命中半径(-1使用初始半径)。
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Movement", meta=(Units="cm"))
|
||||||
|
float FinalHitRadius{-1.0f};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initial speed of the bullet.
|
||||||
|
* 子弹的初始速度。
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Movement", meta=(Units="cm"))
|
||||||
|
float InitialSpeed{1500.0f};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Minimum speed of the bullet.
|
||||||
|
* 子弹的最小速度。
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Movement", meta=(Units="cm"))
|
||||||
|
float MinSpeed{1500.0f};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maximum speed of the bullet.
|
||||||
|
* 子弹的最大速度。
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Movement", meta=(Units="cm"))
|
||||||
|
float MaxSpeed{1500.0f};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle to the attack definition for the bullet.
|
||||||
|
* 子弹的攻击定义句柄。
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Attack", meta=(RowType="/Script/GenericCombatSystem.GCS_AttackDefinition"))
|
||||||
|
FDataTableRowHandle AttackDefinition;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Trace definitions for hit detection.
|
||||||
|
* 用于命中检测的碰撞检测定义。
|
||||||
|
* @note Overrides trace definitions in the bullet instance class.
|
||||||
|
* @注意 覆盖子弹实例类中的碰撞检测定义。
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Trace")
|
||||||
|
TArray<FGCS_TraceDefinition> TraceDefinitions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Visual effect for the bullet projectile (Niagara).
|
||||||
|
* 子弹的视觉效果(Niagara)。
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="VFX")
|
||||||
|
TSoftObjectPtr<UNiagaraSystem> ProjectileFX;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Visual effect for the bullet projectile (Cascade).
|
||||||
|
* 子弹的视觉效果(Cascade)。
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="VFX")
|
||||||
|
TSoftObjectPtr<UParticleSystem> ProjectileFX_Cascade;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Visual effect for bullet impact (Niagara).
|
||||||
|
* 子弹命中的视觉效果(Niagara)。
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="VFX")
|
||||||
|
TSoftObjectPtr<UNiagaraSystem> ImpactFX;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Visual effect for bullet impact (Cascade).
|
||||||
|
* 子弹命中的视觉效果(Cascade)。
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="VFX")
|
||||||
|
TSoftObjectPtr<UParticleSystem> ImpactFX_Cascade;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sound effect for bullet impact.
|
||||||
|
* 子弹命中的音效。
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="SFX")
|
||||||
|
TSoftObjectPtr<USoundBase> ImpactSFX;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sound effect attached to the bullet projectile.
|
||||||
|
* 附着在子弹上的音效。
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="SFX")
|
||||||
|
TSoftObjectPtr<USoundBase> ProjectileSFX;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sound effect played once when the bullet spawns.
|
||||||
|
* 子弹生成时播放一次的音效。
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="SFX")
|
||||||
|
TSoftObjectPtr<USoundBase> SpawnSFX;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the bullet penetrates characters/pawns.
|
||||||
|
* 子弹是否穿透角色/Pawn。
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Penetration")
|
||||||
|
bool bPenetrateCharacter{false};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the bullet penetrates map geometry.
|
||||||
|
* 子弹是否穿透地图几何体。
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Penetration")
|
||||||
|
bool bPenetrateMap{false};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle to the bullet definition to spawn on hit/expiration.
|
||||||
|
* 命中或失效时生成的子弹定义句柄。
|
||||||
|
* @note Cannot be the same as this bullet.
|
||||||
|
* @注意 不能与此子弹相同。
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Hit Configuration", meta=(RowType="/Script/GenericCombatSystem.GCS_BulletDefinition"))
|
||||||
|
FDataTableRowHandle HitBulletDefinition;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Condition for launching bullet chains.
|
||||||
|
* 子弹链的发射条件。
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Hit Configuration", meta=(Categories="GGF.Combat.Bullet.LaunchCond"))
|
||||||
|
FGameplayTag LaunchCondition{FGameplayTag::EmptyTag};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Native Instanced struct for extending the bullet definition.
|
||||||
|
* 实例化结构体用于扩充子弹定义的字段。
|
||||||
|
* @attention For C++ users only. 仅针对C++用户。
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Extension")
|
||||||
|
TInstancedStruct<FGCS_BulletDefinitionExtension> NativeExtension;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Blueprint Instanced struct for extending the bullet definition.
|
||||||
|
* 实例化结构体用于扩充子弹定义的字段。
|
||||||
|
* @attention For blueprint users only. 仅针对蓝图用户。
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Extension")
|
||||||
|
FInstancedStruct Extension;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom user settings for extending the bullet definition.
|
||||||
|
* 扩展子弹定义的自定义用户设置。
|
||||||
|
*/
|
||||||
|
UE_DEPRECATED(1.5, "Using extension field to add custom fields!")
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Deprecated", meta=(ForceInlineRow, BaseStruct = "/Script/GenericCombatSystem.GCS_UserSetting"))
|
||||||
|
TMap<FGameplayTag, FInstancedStruct> UserSettings;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shares the hit history to sub bullet.(prevent repeat hit for whole bullet chains.)
|
||||||
|
*/
|
||||||
|
// UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Hit Configuration",meta=(Categories="GGF.Combat.Bullet.LaunchCond"))
|
||||||
|
// bool bUseSharedHitList{true};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The amount of time between a bullet hits something and when it explodes.
|
||||||
|
*/
|
||||||
|
// UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Hit Configuration", meta=(Units="s", ClampMin=0))
|
||||||
|
// float ExplosionDelay{0.0};
|
||||||
|
|
||||||
|
// Emitter will be added in next version.
|
||||||
|
|
||||||
|
// UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Emitter", meta=(RowType="/Script/GenericCombatSystem.GCS_BulletDefinition"))
|
||||||
|
// FDataTableRowHandle EmitterBulletDefinition;
|
||||||
|
//
|
||||||
|
// UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Emitter")
|
||||||
|
// float EmitterInitialWaitTime{0};
|
||||||
|
//
|
||||||
|
// UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Emitter")
|
||||||
|
// float EmitterMinShootInterval{0};
|
||||||
|
//
|
||||||
|
// UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Emitter")
|
||||||
|
// float EmitterMaxShootInterval{0};
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parameters for spawning bullets.
|
||||||
|
* 子弹生成参数。
|
||||||
|
*/
|
||||||
|
USTRUCT(BlueprintType)
|
||||||
|
struct GENERICCOMBATSYSTEM_API FGCS_BulletSpawnParameters
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The owner of the bullet.
|
||||||
|
* 子弹的拥有者。
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GCS")
|
||||||
|
TObjectPtr<AActor> Owner{nullptr};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle to the bullet definition.
|
||||||
|
* 子弹定义的句柄。
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GCS", meta=(RowType="/Script/GenericCombatSystem.GCS_BulletDefinition"))
|
||||||
|
FDataTableRowHandle DefinitionHandle;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transform for spawning the bullet.
|
||||||
|
* 子弹生成时的变换。
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GCS")
|
||||||
|
FTransform SpawnTransform{FTransform::Identity};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The associated attack request.
|
||||||
|
* 关联的攻击请求。
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GCS")
|
||||||
|
TObjectPtr<const UGCS_AttackRequest_Bullet> Request{nullptr};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the bullet is locally predicted.
|
||||||
|
* 子弹是否为本地预测。
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GCS")
|
||||||
|
bool bIsLocalPredicting{false};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IDs for locally predicted bullets.
|
||||||
|
* 本地预测子弹的ID。
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GCS", meta=(DisplayName="Local Predicting Bullet Ids"))
|
||||||
|
TArray<FGuid> OverrideBulletIds;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ID of the parent bullet (for bullet chains).
|
||||||
|
* 父子弹的ID(用于子弹链)。
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GCS")
|
||||||
|
FGuid ParentId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a debug string representation.
|
||||||
|
* 返回调试字符串表示。
|
||||||
|
* @return The debug string. 调试字符串。
|
||||||
|
*/
|
||||||
|
FString ToDebugString() const;
|
||||||
|
};
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "GCS_BulletStructLibrary.h"
|
||||||
|
#include "Subsystems/WorldSubsystem.h"
|
||||||
|
#include "GCS_BulletSubsystem.generated.h"
|
||||||
|
|
||||||
|
class UGCS_AttackRequest_Bullet;
|
||||||
|
class AGCS_BulletInstance;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subsystem for managing bullet spawning and lifecycle.
|
||||||
|
* 管理子弹生成和生命周期的子系统。
|
||||||
|
*/
|
||||||
|
UCLASS()
|
||||||
|
class GENERICCOMBATSYSTEM_API UGCS_BulletSubsystem : public UWorldSubsystem
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
static UGCS_BulletSubsystem* Get(const UWorld* World);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Spawns bullets based on the provided parameters.
|
||||||
|
* 根据提供的参数生成子弹。
|
||||||
|
* @param SpawnParameters The spawn parameters. 生成参数。
|
||||||
|
* @return The spawned bullet instances. 生成的子弹实例。
|
||||||
|
*/
|
||||||
|
UFUNCTION(BlueprintCallable, Category="GCS|Bullet")
|
||||||
|
virtual TArray<AGCS_BulletInstance*> SpawnBullets(const FGCS_BulletSpawnParameters& SpawnParameters);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the IDs of the provided bullet instances.
|
||||||
|
* 获取提供的子弹实例的ID。
|
||||||
|
* @param Instances The bullet instances. 子弹实例。
|
||||||
|
* @return The IDs of the bullets. 子弹的ID。
|
||||||
|
*/
|
||||||
|
UFUNCTION(BlueprintCallable, BlueprintPure, Category="GCS|Bullet")
|
||||||
|
virtual TArray<FGuid> GetIdsFromBullets(TArray<AGCS_BulletInstance*> Instances);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets or creates bullet instances based on parameters and definition.
|
||||||
|
* 根据参数和定义获取或创建子弹实例。
|
||||||
|
* @param SpawnParameters The spawn parameters. 生成参数。
|
||||||
|
* @param Definition The bullet definition. 子弹定义。
|
||||||
|
* @return The bullet instances. 子弹实例。
|
||||||
|
*/
|
||||||
|
TArray<AGCS_BulletInstance*> GetOrCreateBulletInstances(const FGCS_BulletSpawnParameters& SpawnParameters, const FGCS_BulletDefinition& Definition);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves a bullet instance from the pool.
|
||||||
|
* 从池中获取子弹实例。
|
||||||
|
* @param BulletClass The class of the bullet. 子弹的类。
|
||||||
|
* @return The bullet instance. 子弹实例。
|
||||||
|
*/
|
||||||
|
AGCS_BulletInstance* TakeBulletFromPool(TSubclassOf<AGCS_BulletInstance> BulletClass);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Destroys a bullet by its ID.
|
||||||
|
* 根据ID销毁子弹。
|
||||||
|
* @param BulletId The bullet ID. 子弹ID。
|
||||||
|
*/
|
||||||
|
UFUNCTION(BlueprintCallable, Category="GCS|Bullet")
|
||||||
|
virtual void DestroyBullet(FGuid BulletId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a single bullet instance.
|
||||||
|
* 创建单个子弹实例。
|
||||||
|
* @param SpawnParameters The spawn parameters. 生成参数。
|
||||||
|
* @param Definition The bullet definition. 子弹定义。
|
||||||
|
* @return The created bullet instance. 创建的子弹实例。
|
||||||
|
*/
|
||||||
|
AGCS_BulletInstance* CreateBulletInstance(const FGCS_BulletSpawnParameters& SpawnParameters, const FGCS_BulletDefinition& Definition);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads a bullet definition from a handle.
|
||||||
|
* 从句柄加载子弹定义。
|
||||||
|
* @param Handle The bullet definition handle. 子弹定义句柄。
|
||||||
|
* @param OutDefinition The loaded definition (output). 加载的定义(输出)。
|
||||||
|
* @return True if the definition was loaded successfully. 如果定义加载成功返回true。
|
||||||
|
*/
|
||||||
|
UFUNCTION(BlueprintCallable, BlueprintPure=false, Category="GCS|Bullet", meta=(ExpandBoolAsExecs="ReturnValue"))
|
||||||
|
virtual bool LoadBulletDefinition(const FDataTableRowHandle& Handle, FGCS_BulletDefinition& OutDefinition);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Active bullet instances mapped by their IDs.
|
||||||
|
* 按ID映射的激活子弹实例。
|
||||||
|
*/
|
||||||
|
UPROPERTY()
|
||||||
|
TMap<FGuid, TObjectPtr<AGCS_BulletInstance>> BulletInstances;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pool of bullet instances for reuse.
|
||||||
|
* 用于重用的子弹实例池。
|
||||||
|
*/
|
||||||
|
UPROPERTY()
|
||||||
|
TArray<TObjectPtr<AGCS_BulletInstance>> BulletPools;
|
||||||
|
};
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "GCS_BulletStructLibrary.h"
|
||||||
|
#include "Components/ActorComponent.h"
|
||||||
|
#include "GCS_BulletSystemComponent.generated.h"
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @note WIP
|
||||||
|
*/
|
||||||
|
UCLASS(Abstract, ClassGroup=(GCS), meta=(BlueprintSpawnableComponent))
|
||||||
|
class GENERICCOMBATSYSTEM_API UGCS_BulletSystemComponent : public UActorComponent
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
// Sets default values for this component's properties
|
||||||
|
UGCS_BulletSystemComponent();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
// Called when the game starts
|
||||||
|
virtual void BeginPlay() override;
|
||||||
|
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* Gets the bullet system component from an actor.
|
||||||
|
* 从Actor获取子弹系统组件。
|
||||||
|
* @param Actor The actor to query. 要查询的Actor。
|
||||||
|
* @return The bullet system component. 子弹系统组件。
|
||||||
|
*/
|
||||||
|
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "GCS|BulletSystem", Meta = (DefaultToSelf="Actor"))
|
||||||
|
static UGCS_BulletSystemComponent* GetBulletSystemComponent(const AActor* Actor);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds the bullet system component on an actor.
|
||||||
|
* 在Actor上查找子弹系统组件。
|
||||||
|
* @param Actor The actor to query. 要查询的Actor。
|
||||||
|
* @param Component The found component (output). 找到的组件(输出)。
|
||||||
|
* @return True if found. 如果找到返回true。
|
||||||
|
*/
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "GCS|BulletSystem", Meta = (DefaultToSelf="Actor", ExpandBoolAsExecs="ReturnValue"))
|
||||||
|
static bool FindBulletSystemComponent(const AActor* Actor, UGCS_BulletSystemComponent*& Component);
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "GCS|BulletSystem")
|
||||||
|
void SpawnBullet(const FGCS_BulletSpawnParameters& SpawnParameters);
|
||||||
|
|
||||||
|
// virtual TArray<AGCS_BulletInstance*> SpawnBulletInternal(const FGCS_BulletSpawnParameters& SpawnParameters);
|
||||||
|
};
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "GCS_BulletInstance.h"
|
||||||
|
#include "GCS_SphereBulletInstance.generated.h"
|
||||||
|
|
||||||
|
class USphereComponent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bullet instance with a spherical collision shape.
|
||||||
|
* 具有球形碰撞形状的子弹实例。
|
||||||
|
*/
|
||||||
|
UCLASS(Abstract, Blueprintable)
|
||||||
|
class GENERICCOMBATSYSTEM_API AGCS_SphereBulletInstance : public AGCS_BulletInstance
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* Default constructor.
|
||||||
|
* 默认构造函数。
|
||||||
|
*/
|
||||||
|
AGCS_SphereBulletInstance();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the bullet's shape component.
|
||||||
|
* 获取子弹的形状组件。
|
||||||
|
* @return The sphere component. 球形组件。
|
||||||
|
*/
|
||||||
|
virtual UShapeComponent* GetBulletShape_Implementation() const override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
/**
|
||||||
|
* Called when the game starts or when spawned.
|
||||||
|
* 游戏开始或生成时调用。
|
||||||
|
*/
|
||||||
|
virtual void BeginPlay() override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The sphere component for collision detection.
|
||||||
|
* 用于碰撞检测的球形组件。
|
||||||
|
*/
|
||||||
|
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="GCS")
|
||||||
|
TObjectPtr<USphereComponent> Sphere;
|
||||||
|
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* Called every frame.
|
||||||
|
* 每帧调用。
|
||||||
|
* @param DeltaTime Time since last frame. 上一帧以来的时间。
|
||||||
|
*/
|
||||||
|
virtual void Tick(float DeltaTime) override;
|
||||||
|
};
|
||||||
@@ -0,0 +1,205 @@
|
|||||||
|
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "GameplayTagContainer.h"
|
||||||
|
#include "GCS_TraceDelegates.h"
|
||||||
|
#include "GCS_ActorOwnedObject.h"
|
||||||
|
#include "GCS_TraceSystemComponent.h"
|
||||||
|
#include "Abilities/GameplayAbilityTypes.h"
|
||||||
|
#include "DEPRECATED_GCS_CollisionTraceInstance.generated.h"
|
||||||
|
|
||||||
|
class UPrimitiveComponent;
|
||||||
|
class UGCS_AttackRequest_Base;
|
||||||
|
class UTargetingPreset;
|
||||||
|
class UGCS_AttackRequest_Melee;
|
||||||
|
class UGCS_TraceSystemComponent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Object for managing collision trace instances.
|
||||||
|
* 管理碰撞检测实例的对象。
|
||||||
|
*/
|
||||||
|
UCLASS(Blueprintable, AutoExpandCategories = ("GCS"), Deprecated, meta=(DeprecationMessage="CollisionTraceInstance is nolonger required since GCS 1.5!"))
|
||||||
|
class GENERICCOMBATSYSTEM_API UDEPRECATED_GCS_CollisionTraceInstance : public UGCS_ActorOwnedObject
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
friend UGCS_TraceSystemComponent;
|
||||||
|
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* Gameplay tag for the trace instance.
|
||||||
|
* 碰撞检测实例的游戏标签。
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GCS|Trace Settings", meta=(ExposeOnSpawn))
|
||||||
|
FGameplayTag TraceGameplayTag;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The primitive component used for tracing.
|
||||||
|
* 用于追踪的原始组件。
|
||||||
|
*/
|
||||||
|
UPROPERTY(BlueprintReadWrite, Category = "GCS|Trace Settings", meta=(ExposeOnSpawn))
|
||||||
|
TObjectPtr<UPrimitiveComponent> TracePrimitiveComponent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Socket names on the primitive component for tracing.
|
||||||
|
* 原始组件上用于追踪的插槽名称。
|
||||||
|
*/
|
||||||
|
UPROPERTY(BlueprintReadWrite, Category = "GCS|Trace Settings", meta=(ExposeOnSpawn))
|
||||||
|
TArray<FName> TracePrimitiveComponentSocketNames;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Targeting preset for fetching target actors.
|
||||||
|
* 用于获取目标Actor的目标预设。
|
||||||
|
*/
|
||||||
|
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="GCS|Trace Settings", meta=(ExposeOnSpawn))
|
||||||
|
TObjectPtr<UTargetingPreset> TargetingPreset;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The actor that created this trace instance.
|
||||||
|
* 创建此碰撞检测实例的Actor。
|
||||||
|
*/
|
||||||
|
UPROPERTY(BlueprintReadOnly, Category = "GCS|Trace Settings")
|
||||||
|
TObjectPtr<AActor> TraceOwner = nullptr;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Associated information for this trace.
|
||||||
|
* 此碰撞检测的关联信息。
|
||||||
|
*/
|
||||||
|
UPROPERTY(VisibleAnywhere, Transient, Category = "GCS|Trace State")
|
||||||
|
FGameplayEventData TraceInformation;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delegate for trace hit events.
|
||||||
|
* 碰撞检测命中事件的委托。
|
||||||
|
*/
|
||||||
|
UPROPERTY(BlueprintAssignable, BlueprintCallable)
|
||||||
|
FGCS_OnTraceHitSignature OnHit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delegate for trace state change events.
|
||||||
|
* 碰撞检测状态更改事件的委托。
|
||||||
|
*/
|
||||||
|
UPROPERTY(BlueprintAssignable, BlueprintCallable)
|
||||||
|
FGCS_OnTraceStateChangedSignature OnTraceStateChangedEvent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The active duration of the trace instance.
|
||||||
|
* 碰撞检测实例的激活时间。
|
||||||
|
*/
|
||||||
|
UPROPERTY(BlueprintReadOnly, Category="GCS|Trace State")
|
||||||
|
float ActiveTime{0.0f};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Broadcasts a hit event.
|
||||||
|
* 广播命中事件。
|
||||||
|
* @param HitResult The hit result. 命中结果。
|
||||||
|
*/
|
||||||
|
UFUNCTION(BlueprintCallable, Category="GCS|Trace")
|
||||||
|
void BroadcastHit(const FHitResult& HitResult);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Broadcasts a state change event.
|
||||||
|
* 广播状态更改事件。
|
||||||
|
* @param bNewState The new state. 新状态。
|
||||||
|
*/
|
||||||
|
void BroadcastStateChanged(bool bNewState);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
/**
|
||||||
|
* Initializes the trace instance.
|
||||||
|
* 初始化碰撞检测实例。
|
||||||
|
*/
|
||||||
|
UFUNCTION(BlueprintNativeEvent, Category = "GCS|Trace", meta=(BlueprintProtected))
|
||||||
|
void OnTraceBeginPlay();
|
||||||
|
virtual void OnTraceBeginPlay_Implementation();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cleans up the trace instance.
|
||||||
|
* 清理碰撞检测实例。
|
||||||
|
* @note The instance is returned to the cache pool instead of being destroyed.
|
||||||
|
* @注意 实例被返回到缓存池而不是销毁。
|
||||||
|
*/
|
||||||
|
UFUNCTION(BlueprintNativeEvent, Category = "GCS|Trace", meta=(BlueprintProtected))
|
||||||
|
void OnTraceEndPlay();
|
||||||
|
virtual void OnTraceEndPlay_Implementation();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles trace ticking.
|
||||||
|
* 处理碰撞检测的tick。
|
||||||
|
* @param DeltaSeconds Time since last frame. 上一帧以来的时间。
|
||||||
|
*/
|
||||||
|
UFUNCTION(BlueprintNativeEvent, Category = "GCS|Trace", meta=(BlueprintProtected))
|
||||||
|
void OnTraceTick(float DeltaSeconds);
|
||||||
|
virtual void OnTraceTick_Implementation(float DeltaSeconds);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles trace state changes.
|
||||||
|
* 处理碰撞检测状态更改。
|
||||||
|
* @param bNewState The new state. 新状态。
|
||||||
|
*/
|
||||||
|
UFUNCTION(BlueprintNativeEvent, Category = "GCS|Trace", meta=(BlueprintProtected))
|
||||||
|
void OnTraceStateChanged(bool bNewState);
|
||||||
|
virtual void OnTraceStateChanged_Implementation(bool bNewState);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Actors hit during the active duration.
|
||||||
|
* 激活期间命中的Actor。
|
||||||
|
*/
|
||||||
|
UPROPERTY(BlueprintReadWrite, EditDefaultsOnly, Category="GCS|Trace State", AdvancedDisplay)
|
||||||
|
TArray<TObjectPtr<AActor>> HitActors;
|
||||||
|
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* Sets the trace mesh information.
|
||||||
|
* 设置碰撞检测网格信息。
|
||||||
|
* @param NewPrimitiveComponent The new primitive component. 新原始组件。
|
||||||
|
* @param PrimitiveComponentSocketNames The socket names. 插槽名称。
|
||||||
|
*/
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "GCS|Trace")
|
||||||
|
void SetTraceMeshInfo(UPrimitiveComponent* NewPrimitiveComponent, TArray<FName> PrimitiveComponentSocketNames);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if an actor can be hit.
|
||||||
|
* 检查是否可以命中Actor。
|
||||||
|
* @param ActorToCheck The actor to check. 要检查的Actor。
|
||||||
|
* @return True if the actor can be hit. 如果可以命中返回true。
|
||||||
|
*/
|
||||||
|
UFUNCTION(BlueprintCallable, BlueprintPure, BlueprintNativeEvent, Category="GCS|Trace")
|
||||||
|
bool CanHitActor(const AActor* ActorToCheck) const;
|
||||||
|
bool CanHitActor_Implementation(const AActor* ActorToCheck) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the source actor for the trace.
|
||||||
|
* 获取碰撞检测的源Actor。
|
||||||
|
* @return The source actor (e.g., weapon, bullet, or trace owner). 源Actor(例如武器、子弹或TraceOwner)。
|
||||||
|
*/
|
||||||
|
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "GCS|Trace")
|
||||||
|
AActor* GetTraceSourceActor() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggles the trace state.
|
||||||
|
* 切换碰撞检测状态。
|
||||||
|
* @param bNewState The new state (active traces tick and attempt to hit). 新状态(激活的追踪会tick并尝试命中)。
|
||||||
|
*/
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "GCS|Trace")
|
||||||
|
void ToggleTraceState(bool bNewState);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if the trace is active.
|
||||||
|
* 表示碰撞检测是否激活。
|
||||||
|
*/
|
||||||
|
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "GCS|Trace")
|
||||||
|
bool bTraceActive{false};
|
||||||
|
|
||||||
|
protected:
|
||||||
|
/**
|
||||||
|
* Handles trace hit events.
|
||||||
|
* 处理碰撞检测命中事件。
|
||||||
|
* @param HitResult The hit result. 命中结果。
|
||||||
|
*/
|
||||||
|
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = "GCS|Trace", meta=(BlueprintProtected))
|
||||||
|
void OnTraceHit(const FHitResult& HitResult);
|
||||||
|
virtual void OnTraceHit_Implementation(const FHitResult& HitResult);
|
||||||
|
};
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "GCS_TraceStructLibrary.h"
|
||||||
|
#include "Engine/CancellableAsyncAction.h"
|
||||||
|
#include "GCS_AsyncAction_CollisionTrace.generated.h"
|
||||||
|
|
||||||
|
class UGCS_TraceSystemComponent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Async action for setting up and listening to collision trace hits.
|
||||||
|
* 设置并监听碰撞检测命中的异步动作。
|
||||||
|
*/
|
||||||
|
UCLASS()
|
||||||
|
class GENERICCOMBATSYSTEM_API UGCS_AsyncAction_CollisionTrace : public UCancellableAsyncAction
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* Creates and activates trace instances from definitions and listens for hits.
|
||||||
|
* 从定义创建并激活碰撞检测实例并监听命中。
|
||||||
|
* @param TraceSystem The collision trace system component. 碰撞检测系统组件。
|
||||||
|
* @param TraceDefinitions The traces definitions will be created and added to collision trace system. 要新建的碰撞实例的定义。
|
||||||
|
* @param PrimitiveComponent The primitive component for tracing. 用于追踪的原始组件。
|
||||||
|
* @param OptionalSourceObject The optional source object. 可选的源对象.
|
||||||
|
* @return The async action instance. 异步动作实例。
|
||||||
|
*/
|
||||||
|
UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category="GUIS", meta = (WorldContext = "WorldContextObject", BlueprintInternalUseOnly = "true"))
|
||||||
|
static UGCS_AsyncAction_CollisionTrace* SetupAndListenForCollisionTraceHit(UGCS_TraceSystemComponent* TraceSystem, const TArray<FGCS_TraceDefinition>& TraceDefinitions,
|
||||||
|
UPrimitiveComponent* PrimitiveComponent, UObject* OptionalSourceObject = nullptr);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Activates the async action.
|
||||||
|
* 激活异步动作。
|
||||||
|
*/
|
||||||
|
virtual void Activate() override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cancels the async action.
|
||||||
|
* 取消异步动作。
|
||||||
|
*/
|
||||||
|
virtual void Cancel() override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delegate for collision trace hit events.
|
||||||
|
* 碰撞检测命中事件的委托。
|
||||||
|
*/
|
||||||
|
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FCollisionTraceSignature, const FGCS_TraceHandle, Handle, const FHitResult&, HitResult);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called before trace instances are activated.
|
||||||
|
* 在激活碰撞检测实例前调用。
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Called before trace instances are activated.
|
||||||
|
* 在激活碰撞检测实例前调用。
|
||||||
|
*/
|
||||||
|
UPROPERTY(BlueprintAssignable)
|
||||||
|
FCollisionTraceSignature BeforeActive;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fired when a trace instance hits something.
|
||||||
|
* 当碰撞检测实例命中某物时触发。
|
||||||
|
*/
|
||||||
|
UPROPERTY(BlueprintAssignable)
|
||||||
|
FCollisionTraceSignature OnHit;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
/**
|
||||||
|
* Handles trace instance hit events.
|
||||||
|
* 处理碰撞检测实例命中事件。
|
||||||
|
* @param TraceHandle The trace instance. 碰撞检测实例。
|
||||||
|
* @param HitResult The hit result. 命中结果。
|
||||||
|
*/
|
||||||
|
UFUNCTION()
|
||||||
|
void TraceHitCallback(const FGCS_TraceHandle& TraceHandle, const FHitResult& HitResult);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The collision system component.
|
||||||
|
* 碰撞系统组件。
|
||||||
|
*/
|
||||||
|
UPROPERTY()
|
||||||
|
TWeakObjectPtr<UGCS_TraceSystemComponent> TraceSystem;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The primitive component for tracing.
|
||||||
|
* 用于追踪的原始组件。
|
||||||
|
*/
|
||||||
|
UPROPERTY()
|
||||||
|
TWeakObjectPtr<UPrimitiveComponent> SourceComponent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The optional source object.
|
||||||
|
* 可选的源对象。
|
||||||
|
*/
|
||||||
|
UPROPERTY()
|
||||||
|
TWeakObjectPtr<UObject> SourceObject;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The trace definitions to create.
|
||||||
|
* 要创建的碰撞检测定义。
|
||||||
|
*/
|
||||||
|
UPROPERTY()
|
||||||
|
TArray<FGCS_TraceDefinition> TraceDefinitions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The monitored trace instances.
|
||||||
|
* 监听的碰撞检测实例。
|
||||||
|
*/
|
||||||
|
UPROPERTY()
|
||||||
|
TArray<FGCS_TraceHandle> TraceHandles;
|
||||||
|
};
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "GCS_TraceStructLibrary.h"
|
||||||
|
#include "UObject/Object.h"
|
||||||
|
#include "GCS_TraceDelegates.generated.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delegate for trace instance hit events.
|
||||||
|
* 碰撞检测实例命中事件的委托。
|
||||||
|
*/
|
||||||
|
UDELEGATE()
|
||||||
|
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FGCS_OnTraceHitSignature, const FGCS_TraceHandle&, TraceHandle, const FHitResult&, HitResult);
|
||||||
|
|
||||||
|
UDELEGATE()
|
||||||
|
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FGCS_OnTraceStateChangedSignature, const FGCS_TraceHandle&, TraceHandle, bool, bNewState);
|
||||||
|
|
||||||
|
UDELEGATE()
|
||||||
|
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FGCS_OnTraceStartedSignature, const FGCS_TraceHandle&, TraceHandle);
|
||||||
|
|
||||||
|
UDELEGATE()
|
||||||
|
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FGCS_OnTraceStoppedSignature, const FGCS_TraceHandle&, TraceHandle);
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "UObject/Object.h"
|
||||||
|
#include "GCS_TraceEnumLibrary.generated.h"
|
||||||
|
|
||||||
|
|
||||||
|
UENUM()
|
||||||
|
enum class EGCS_CollisionShapeType : uint8
|
||||||
|
{
|
||||||
|
Sphere,
|
||||||
|
Box,
|
||||||
|
Capsule
|
||||||
|
};
|
||||||
|
|
||||||
|
UENUM()
|
||||||
|
enum class EGCS_TraceSweepType : uint8
|
||||||
|
{
|
||||||
|
ByChannel,
|
||||||
|
ByObject,
|
||||||
|
ByProfile
|
||||||
|
};
|
||||||
|
|
||||||
|
UENUM()
|
||||||
|
enum class EGCS_TraceTickType : uint8
|
||||||
|
{
|
||||||
|
Default,
|
||||||
|
FixedFrameRate,
|
||||||
|
DistanceBased
|
||||||
|
};
|
||||||
|
|
||||||
|
UENUM()
|
||||||
|
enum class EGCS_TraceExecutionState : uint8
|
||||||
|
{
|
||||||
|
InProgress,
|
||||||
|
Stopped,
|
||||||
|
PendingStop
|
||||||
|
};
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "GCS_TraceStructLibrary.h"
|
||||||
|
#include "Kismet/BlueprintFunctionLibrary.h"
|
||||||
|
#include "GCS_TraceFunctionLibrary.generated.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Blueprint function library for trace system utilities.
|
||||||
|
* 碰撞检测系统工具的蓝图函数库。
|
||||||
|
*/
|
||||||
|
UCLASS()
|
||||||
|
class GENERICCOMBATSYSTEM_API UGCS_TraceFunctionLibrary : public UBlueprintFunctionLibrary
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* Filter definitions with trace tag matches TagToMatch.
|
||||||
|
* 筛选与指定标签匹配的碰撞检测定义。
|
||||||
|
* @param Definitions The definitions to filter. 要筛选的定义。
|
||||||
|
* @param TagToMatch The tag to check. 要检查的标签。
|
||||||
|
* @return Matching definitions. 匹配的定义。
|
||||||
|
*/
|
||||||
|
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "GCS|TraceSystem|Utilities", Meta = (DefaultToSelf="Actor"))
|
||||||
|
static TArray<FGCS_TraceDefinition> FilterTraceDefinitionsByTag(const TArray<FGCS_TraceDefinition>& Definitions, const FGameplayTag& TagToMatch);
|
||||||
|
};
|
||||||
@@ -0,0 +1,469 @@
|
|||||||
|
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "GameplayTagContainer.h"
|
||||||
|
#include "CollisionShape.h"
|
||||||
|
#include "CollisionQueryParams.h"
|
||||||
|
#include "Collision/GCS_TraceEnumLibrary.h"
|
||||||
|
#include "Engine/DataTable.h"
|
||||||
|
#include "StructUtils/InstancedStruct.h"
|
||||||
|
#include "UObject/Object.h"
|
||||||
|
#include "GCS_TraceStructLibrary.generated.h"
|
||||||
|
|
||||||
|
class UTargetingPreset;
|
||||||
|
class UGCS_TraceSystemComponent;
|
||||||
|
|
||||||
|
USTRUCT(BlueprintType)
|
||||||
|
struct FGCS_TraceSweepSetting
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The type of sweep to perform for collision detection.
|
||||||
|
* 执行碰撞检测的扫描类型。
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "GCS")
|
||||||
|
EGCS_TraceSweepType SweepType = EGCS_TraceSweepType::ByChannel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The collision channel to use for channel-based sweeping.
|
||||||
|
* 用于基于通道扫描的碰撞通道。
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "GCS",
|
||||||
|
meta = (EditCondition = "SweepType == EGCS_TraceSweepType::ByChannel", EditConditionHides))
|
||||||
|
TEnumAsByte<ECollisionChannel> TraceChannel = ECC_Visibility;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The object types to use for object-based sweeping.
|
||||||
|
* 用于基于对象扫描的对象类型。
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "GCS",
|
||||||
|
meta = (EditCondition = "SweepType == EGCS_TraceSweepType::ByObject", EditConditionHides))
|
||||||
|
TArray<TEnumAsByte<ECollisionChannel>> ObjectTypes;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The collision profile name to use for profile-based sweeping.
|
||||||
|
* 用于基于配置扫描的碰撞配置名称。
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "GCS",
|
||||||
|
meta = (EditCondition = "SweepType == EGCS_TraceSweepType::ByProfile", EditConditionHides))
|
||||||
|
FName ProfileName = NAME_None;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether to trace against complex collision.
|
||||||
|
* 是否对复杂碰撞进行追踪。
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "GCS")
|
||||||
|
bool bTraceComplex = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build collision shape by static parameters
|
||||||
|
* 通过静态参数构建碰撞形状。
|
||||||
|
* 在SourceComponent空间内的静态形状。
|
||||||
|
*/
|
||||||
|
USTRUCT(meta = (Hidden, DisplayName = "Collision Shape"))
|
||||||
|
struct GENERICCOMBATSYSTEM_API FGCS_CollisionShape
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
virtual ~FGCS_CollisionShape() = default;
|
||||||
|
|
||||||
|
virtual bool InitializeShape(const UPrimitiveComponent* SourceComponent);
|
||||||
|
virtual FTransform GetTransform(const UPrimitiveComponent* SourceComponent, const float& Time) const;
|
||||||
|
virtual FCollisionShape GetDynamicCollisionShape(const UPrimitiveComponent* SourceComponent, const float& Time) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build collision shape by static parameters
|
||||||
|
* 通过静态参数构建碰撞形状。
|
||||||
|
* 在SourceComponent空间内的静态形状。
|
||||||
|
*/
|
||||||
|
USTRUCT(meta = (DisplayName = "Collision Shape (Static)"))
|
||||||
|
struct GENERICCOMBATSYSTEM_API FGCS_CollisionShape_Static : public FGCS_CollisionShape
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The type of collision shape to use.
|
||||||
|
* 要使用的碰撞形状类型。
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditAnywhere, Category = "GCS")
|
||||||
|
EGCS_CollisionShapeType ShapeType = EGCS_CollisionShapeType::Sphere;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The orientation/rotation of the collision shape.
|
||||||
|
* 碰撞形状的方向/旋转。
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditAnywhere, Category = "GCS",
|
||||||
|
meta = (EditCondition = "ShapeType != EGCS_CollisionShapeType::Sphere", EditConditionHides))
|
||||||
|
FRotator Orientation = FRotator::ZeroRotator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The position offset of the collision shape.
|
||||||
|
* 碰撞形状的位置偏移。
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditAnywhere, Category = "GCS")
|
||||||
|
FVector Offset = FVector::ZeroVector;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The radius of the sphere or capsule.
|
||||||
|
* 球体或胶囊体的半径。
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditAnywhere, Category = "GCS",
|
||||||
|
meta = (EditCondition = "ShapeType != EGCS_CollisionShapeType::Box", EditConditionHides))
|
||||||
|
float Radius = 10.f;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The half height of the capsule.
|
||||||
|
* 胶囊体的半高。
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditAnywhere, Category = "GCS",
|
||||||
|
meta = (EditCondition = "ShapeType == EGCS_CollisionShapeType::Capsule", EditConditionHides))
|
||||||
|
float HalfHeight = 10.f;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The half size of the box.
|
||||||
|
* 盒子的半尺寸。
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditAnywhere, Category = "GCS",
|
||||||
|
meta = (EditCondition = "ShapeType == EGCS_CollisionShapeType::Box", EditConditionHides))
|
||||||
|
FVector HalfSize = FVector(10.f, 10.f, 10.f);
|
||||||
|
|
||||||
|
virtual bool InitializeShape(const UPrimitiveComponent* SourceComponent) override;
|
||||||
|
virtual FTransform GetTransform(const UPrimitiveComponent* SourceComponent, const float& Time) const override;
|
||||||
|
virtual FCollisionShape GetDynamicCollisionShape(const UPrimitiveComponent* SourceComponent, const float& Time) const override;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build dynamic collision shape by binding to shape component(Box/Sphere/Capsule).
|
||||||
|
* 通过绑定Shape组件(Box/Sphere/Capsule)构建动态碰撞形状。
|
||||||
|
* 在SourceComponent空间内的,与SourceComponent进行匹配的形状。
|
||||||
|
*/
|
||||||
|
USTRUCT(meta = (DisplayName = "Collision Shape (Shape Based)"))
|
||||||
|
struct GENERICCOMBATSYSTEM_API FGCS_CollisionShape_ShapeBased : public FGCS_CollisionShape
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The type of collision shape to use.
|
||||||
|
* 要使用的碰撞形状类型。
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditAnywhere, Category = "GCS")
|
||||||
|
EGCS_CollisionShapeType ShapeType = EGCS_CollisionShapeType::Sphere;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The orientation/rotation of the collision shape.
|
||||||
|
* 碰撞形状的方向/旋转。
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditAnywhere, Category = "GCS",
|
||||||
|
meta = (EditCondition = "ShapeType != EGCS_CollisionShapeType::Sphere", EditConditionHides))
|
||||||
|
FRotator Orientation = FRotator::ZeroRotator;
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, Category = "GCS")
|
||||||
|
FVector Offset = FVector::ZeroVector;
|
||||||
|
|
||||||
|
UPROPERTY()
|
||||||
|
float Radius = 10.f;
|
||||||
|
|
||||||
|
UPROPERTY()
|
||||||
|
float HalfHeight = 10.f;
|
||||||
|
|
||||||
|
UPROPERTY()
|
||||||
|
FVector HalfSize = FVector(10.f, 10.f, 10.f);
|
||||||
|
|
||||||
|
virtual bool InitializeShape(const UPrimitiveComponent* SourceComponent) override;
|
||||||
|
virtual FTransform GetTransform(const UPrimitiveComponent* SourceComponent, const float& Time) const override;
|
||||||
|
virtual FCollisionShape GetDynamicCollisionShape(const UPrimitiveComponent* SourceComponent, const float& Time) const override;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 在SourceComponent空间内,跟随SourceComponent的某Socket/Bone运动的形状。
|
||||||
|
*/
|
||||||
|
USTRUCT(meta = (DisplayName = "Collision Shape (Attached)"))
|
||||||
|
struct GENERICCOMBATSYSTEM_API FGCS_CollisionShape_Attached : public FGCS_CollisionShape_Static
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Name of the socket or bone the shape will be attached to.
|
||||||
|
* 形状将附加到的插槽或骨骼的名称。
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditAnywhere, Category="GCS")
|
||||||
|
FName SocketOrBoneName;
|
||||||
|
|
||||||
|
virtual bool InitializeShape(const UPrimitiveComponent* SourceComponent) override;
|
||||||
|
virtual FTransform GetTransform(const UPrimitiveComponent* SourceComponent, const float& Time) const override;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build dynamic capsule collision shape by mesh's sockets.
|
||||||
|
* 通过网格的指定Sockets构建动态碰撞形状。
|
||||||
|
*/
|
||||||
|
USTRUCT(meta = (DisplayName = "Collision Shape (Socket Based)"))
|
||||||
|
struct GENERICCOMBATSYSTEM_API FGCS_CollisionShape_SocketBased : public FGCS_CollisionShape
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The name of the starting socket on the mesh.
|
||||||
|
* 网格上起始插槽的名称。
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditAnywhere, Category = "GCS")
|
||||||
|
FName MeshSocketStart = TEXT("TrailStart");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The name of the ending socket on the mesh.
|
||||||
|
* 网格上结束插槽的名称。
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditAnywhere, Category = "GCS")
|
||||||
|
FName MeshSocketEnd = TEXT("TrailEnd");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Additional length offset for the socket-based shape.
|
||||||
|
* 基于插槽的形状的额外长度偏移。
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditAnywhere, Category = "GCS")
|
||||||
|
float MeshSocketLengthOffset = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The radius of the capsule.
|
||||||
|
* 胶囊体的半径。
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditAnywhere, Category = "GCS")
|
||||||
|
float Radius = 10.f;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The orientation/rotation of the collision shape.
|
||||||
|
* 碰撞形状的方向/旋转。
|
||||||
|
*/
|
||||||
|
UPROPERTY()
|
||||||
|
FRotator Orientation = FRotator::ZeroRotator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The position offset of the collision shape.
|
||||||
|
* 碰撞形状的位置偏移。
|
||||||
|
*/
|
||||||
|
UPROPERTY()
|
||||||
|
FVector Offset = FVector::ZeroVector;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The half height of the capsule.
|
||||||
|
* 胶囊体的半高。
|
||||||
|
*/
|
||||||
|
UPROPERTY()
|
||||||
|
float HalfHeight = 10.f;
|
||||||
|
|
||||||
|
virtual FTransform GetTransform(const UPrimitiveComponent* SourceComponent, const float& Time) const override;
|
||||||
|
virtual bool InitializeShape(const UPrimitiveComponent* SourceComponent) override;
|
||||||
|
virtual FCollisionShape GetDynamicCollisionShape(const UPrimitiveComponent* SourceComponent, const float& Time) const override;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Structure for defining collision trace instances.
|
||||||
|
* 定义碰撞检测实例的结构。
|
||||||
|
*/
|
||||||
|
USTRUCT(BlueprintType)
|
||||||
|
struct GENERICCOMBATSYSTEM_API FGCS_TraceDefinition : public FTableRowBase
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
FGCS_TraceDefinition();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tag used as Trace identifier.
|
||||||
|
* 此Trace的Tag标识。
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Trace Settings", meta=(Categories="GGF.Combat.Trace"))
|
||||||
|
FGameplayTag TraceTag;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines the shape used for detecting hit results.
|
||||||
|
* 定义用于获取命中结果的形状。
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditAnywhere, Category = "Trace Settings", meta=(ExcludeBaseStruct, BaseStruct = "/Script/GenericCombatSystem.GCS_CollisionShape"))
|
||||||
|
FInstancedStruct CollisionShape{FInstancedStruct::Make(FGCS_CollisionShape_Static())};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Settings for the sweep operation.
|
||||||
|
* 扫描操作的设置。
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Trace Settings")
|
||||||
|
FGCS_TraceSweepSetting SweepSetting;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The type of tick policy to use for this trace.
|
||||||
|
* 此Trace使用的tick策略类型。
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Trace Settings")
|
||||||
|
EGCS_TraceTickType TraceTickType{EGCS_TraceTickType::FixedFrameRate};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The fixed frame rate for ticking when using FixedFrameRate policy.
|
||||||
|
* 使用固定帧率策略时的固定帧率。
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Trace Settings",
|
||||||
|
meta = (EditCondition = "TraceTickType == EGCS_TraceTickType::FixedFrameRate", EditConditionHides))
|
||||||
|
int32 FixedTickFrameRate = 30;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The distance threshold for ticking when using DistanceBased policy.
|
||||||
|
* 使用基于距离策略时的距离阈值。
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Trace Settings",
|
||||||
|
meta = (EditCondition = "TraceTickType == EGCS_TraceTickType::DistanceBased", EditConditionHides))
|
||||||
|
int32 DistanceTickThreshold = 30;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The angle threshold for ticking when using DistanceBased policy (in degrees).
|
||||||
|
* 使用基于距离策略时的角度阈值(度)。
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Trace Settings",
|
||||||
|
meta = (EditCondition = "TraceTickType == EGCS_TraceTickType::DistanceBased", EditConditionHides))
|
||||||
|
float AngleTickThreshold = 15.0f; // Degrees
|
||||||
|
|
||||||
|
bool IsValidDefinition() const;
|
||||||
|
|
||||||
|
FString ToString() const;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple struct used for trace query.
|
||||||
|
*/
|
||||||
|
USTRUCT(BlueprintType)
|
||||||
|
struct GENERICCOMBATSYSTEM_API FGCS_TraceHandle
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The gameplay tag identifying this trace.
|
||||||
|
* 标识此Trace的游戏标签。
|
||||||
|
*/
|
||||||
|
UPROPERTY(BlueprintReadOnly, Category = "GCS")
|
||||||
|
FGameplayTag TraceTag;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The unique GUID for this trace instance.
|
||||||
|
* 此Trace实例的唯一GUID。
|
||||||
|
*/
|
||||||
|
UPROPERTY(BlueprintReadOnly, Category = "GCS")
|
||||||
|
FGuid Guid;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The source object that created this trace.
|
||||||
|
* 创建此Trace的源对象。
|
||||||
|
*/
|
||||||
|
UPROPERTY(BlueprintReadOnly, Category = "GCS")
|
||||||
|
TWeakObjectPtr<UObject> SourceObject;
|
||||||
|
|
||||||
|
bool IsValidHandle() const;
|
||||||
|
|
||||||
|
// Equality operator
|
||||||
|
friend bool operator==(const FGCS_TraceHandle& A, const FGCS_TraceHandle& B)
|
||||||
|
{
|
||||||
|
return A.TraceTag == B.TraceTag &&
|
||||||
|
A.Guid == B.Guid &&
|
||||||
|
A.SourceObject == B.SourceObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inequality operator
|
||||||
|
friend bool operator!=(const FGCS_TraceHandle& A, const FGCS_TraceHandle& B)
|
||||||
|
{
|
||||||
|
return !(A == B);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hash function for TMap/TSet
|
||||||
|
friend uint32 GetTypeHash(const FGCS_TraceHandle& Handle)
|
||||||
|
{
|
||||||
|
uint32 Hash = GetTypeHash(Handle.TraceTag);
|
||||||
|
Hash = HashCombine(Hash, GetTypeHash(Handle.Guid));
|
||||||
|
Hash = HashCombine(Hash, GetTypeHash(Handle.SourceObject));
|
||||||
|
return Hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
FString ToDebugString() const
|
||||||
|
{
|
||||||
|
FString TagName = TraceTag.ToString();
|
||||||
|
if (TraceTag.IsValid())
|
||||||
|
{
|
||||||
|
TArray<FString> TagNames;
|
||||||
|
TraceTag.ToString().ParseIntoArray(TagNames,TEXT("."));
|
||||||
|
if (TagNames.Num() > 0)
|
||||||
|
{
|
||||||
|
TagName = TagNames.Last();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return FString::Printf(TEXT("%s;%s"), *TagName, SourceObject.IsValid() ? *GetNameSafe(SourceObject.Get()) : TEXT("None"));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 补帧数据.
|
||||||
|
struct FGCS_TraceSubTick
|
||||||
|
{
|
||||||
|
FTransform StartTransform;
|
||||||
|
FTransform EndTransform;
|
||||||
|
FTransform AverageTransform;
|
||||||
|
};
|
||||||
|
|
||||||
|
USTRUCT(BlueprintType)
|
||||||
|
struct FGCS_TraceState
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
void ChangeExecutionState(bool bNewTraceState, bool bStopImmediate = true);
|
||||||
|
void UpdatePreviousTransform(const FTransform& Transform);
|
||||||
|
|
||||||
|
// Get the world space transform of this trace.
|
||||||
|
FTransform GetCurrentTransform() const;
|
||||||
|
|
||||||
|
// Begin Runtime references
|
||||||
|
UPROPERTY()
|
||||||
|
UWorld* World{nullptr};
|
||||||
|
UPROPERTY()
|
||||||
|
TObjectPtr<UPrimitiveComponent> SourceComponent{nullptr};
|
||||||
|
UPROPERTY()
|
||||||
|
TObjectPtr<UGCS_TraceSystemComponent> OwningSystem{nullptr};
|
||||||
|
// End Runtime references
|
||||||
|
|
||||||
|
// Begin Static Data
|
||||||
|
FGCS_TraceSweepSetting SweepSetting;
|
||||||
|
// End Static Data
|
||||||
|
|
||||||
|
// Begin runtime data
|
||||||
|
FGCS_TraceHandle Handle;
|
||||||
|
|
||||||
|
bool IsPendingRemoval = false;
|
||||||
|
|
||||||
|
// The modified dynamic shape.
|
||||||
|
FInstancedStruct Shape;
|
||||||
|
|
||||||
|
TArray<FTransform, TFixedAllocator<2>> TransformsOverTime;
|
||||||
|
|
||||||
|
EGCS_TraceExecutionState ExecutionState = EGCS_TraceExecutionState::Stopped;
|
||||||
|
|
||||||
|
FCollisionShape CollisionShapeOverTime;
|
||||||
|
TArray<FGCS_TraceSubTick> SubTicks;
|
||||||
|
|
||||||
|
EGCS_TraceTickType TickPolicy;
|
||||||
|
float TickInterval = 1 / 30;
|
||||||
|
float AngleThreshold = 15.0f; // Degrees
|
||||||
|
bool bShouldTickThisFrame = false;
|
||||||
|
|
||||||
|
float TimeSinceLastTick = 0;
|
||||||
|
// how long this state active?
|
||||||
|
|
||||||
|
//
|
||||||
|
float TimeSinceActive = 0;
|
||||||
|
int32 TotalTickNumDuringExecution = 0;
|
||||||
|
TArray<TObjectPtr<AActor>> HitActors;
|
||||||
|
|
||||||
|
FCollisionQueryParams CollisionParams;
|
||||||
|
FCollisionResponseParams ResponseParams;
|
||||||
|
FCollisionObjectQueryParams ObjectQueryParams;
|
||||||
|
|
||||||
|
// End runtime data
|
||||||
|
};
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "GCS_TraceStructLibrary.h"
|
||||||
|
#include "DrawDebugHelpers.h"
|
||||||
|
#include "WorldCollision.h"
|
||||||
|
#include "Kismet/KismetSystemLibrary.h"
|
||||||
|
#include "Subsystems/WorldSubsystem.h"
|
||||||
|
#include "GCS_TraceSubsystem.generated.h"
|
||||||
|
|
||||||
|
UCLASS()
|
||||||
|
class GENERICCOMBATSYSTEM_API UGCS_TraceSubsystem : public UTickableWorldSubsystem
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
virtual void Initialize(FSubsystemCollectionBase& Collection) override;
|
||||||
|
|
||||||
|
virtual void Tick(float DeltaTime) override;
|
||||||
|
virtual bool IsTickable() const override { return true; }
|
||||||
|
virtual TStatId GetStatId() const override { return TStatId(); }
|
||||||
|
|
||||||
|
FCriticalSection CriticalSection;
|
||||||
|
bool IsValidStateIdx(int32 StateIdx) const;
|
||||||
|
FGCS_TraceState& GetTraceStateAt(int Index);
|
||||||
|
|
||||||
|
int32 AddTraceState();
|
||||||
|
void RemoveTraceState(int Idx, FGuid Guid);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
TArray<FGCS_TraceState> TraceStates;
|
||||||
|
bool RemovalLock = false;
|
||||||
|
uint32 TickIdx = 0;
|
||||||
|
void RemoveTraceStateAt(int Idx, FGuid Guid);
|
||||||
|
|
||||||
|
void PreTraceTick(const float DeltaTime);
|
||||||
|
void PostTraceTick();
|
||||||
|
|
||||||
|
// 计算所有用于碰撞检测的必须数据。
|
||||||
|
virtual void PrepareSubTicks(const float DeltaTime);
|
||||||
|
virtual void PerformSubTicks(const float DeltaTime);
|
||||||
|
|
||||||
|
static void PerformAsyncTrace(const FTransform& StartTransform, const FTransform& EndTransform, const FTransform& AverageTransform, UWorld* World,
|
||||||
|
const FGCS_TraceSweepSetting& TraceSettings,
|
||||||
|
const FCollisionShape& CollisionShape, const FCollisionQueryParams& CollisionParams,
|
||||||
|
const FCollisionResponseParams& CollisionResponseParams,
|
||||||
|
const FCollisionObjectQueryParams& ObjectQueryParams, const FTraceDelegate* InDelegate = nullptr);
|
||||||
|
|
||||||
|
virtual void HandleTraceResults(const FTraceHandle& InTraceHandle, FTraceDatum& InTraceDatum, int32 TraceStateIdx, uint32 InTickIdx, float InShapeTime);
|
||||||
|
|
||||||
|
#if ENABLE_DRAW_DEBUG
|
||||||
|
static void DrawDebug(const FVector& Start, const FVector& End, const FQuat& Rot, TArray<FHitResult> Hits, const FCollisionShape& CollisionShape, const UWorld* World,
|
||||||
|
const EDrawDebugTrace::Type DrawDebugType, float DrawDebugTime, const FLinearColor& DrawDebugColor, const FLinearColor& DrawDebugHitColor);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// 新增:用于批量移除的Pending列表,避免边遍历边修改
|
||||||
|
TArray<TPair<int32, FGuid>> PendingRemovals;
|
||||||
|
};
|
||||||
@@ -0,0 +1,312 @@
|
|||||||
|
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "Components/ActorComponent.h"
|
||||||
|
#include "GameplayTagContainer.h"
|
||||||
|
#include "GCS_TraceDelegates.h"
|
||||||
|
#include "GCS_TraceStructLibrary.h"
|
||||||
|
#include "GCS_TraceSystemComponent.generated.h"
|
||||||
|
|
||||||
|
class UGCS_TraceSubsystem;
|
||||||
|
class UPrimitiveComponent;
|
||||||
|
|
||||||
|
|
||||||
|
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FGCS_OnTraceSystemDestroyedSignature);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component for managing collision trace instances.
|
||||||
|
* 管理碰撞检测实例的组件。
|
||||||
|
*/
|
||||||
|
UCLASS(ClassGroup=(GCS), meta=(BlueprintSpawnableComponent, PrioritizeCategories="GCS"), Blueprintable, HideCategories=(Sockets, Navigation, Tags, ComponentTick, ComponentReplication,
|
||||||
|
Cooking, AssetUserData, Replication))
|
||||||
|
class GENERICCOMBATSYSTEM_API UGCS_TraceSystemComponent : public UActorComponent
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
friend UGCS_TraceSubsystem;
|
||||||
|
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* Default constructor.
|
||||||
|
* 默认构造函数。
|
||||||
|
*/
|
||||||
|
UGCS_TraceSystemComponent(const FObjectInitializer& ObjectInitializer);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the collision trace system component from an actor.
|
||||||
|
* 从Actor获取碰撞检测系统组件。
|
||||||
|
* @param Actor The actor to query. 要查询的Actor。
|
||||||
|
* @return The collision trace system component. 碰撞检测系统组件。
|
||||||
|
*/
|
||||||
|
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "GCS|TraceSystem", Meta = (DefaultToSelf="Actor"))
|
||||||
|
static UGCS_TraceSystemComponent* GetTraceSystemComponent(const AActor* Actor);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds the collision trace system component on an actor.
|
||||||
|
* 在Actor上查找碰撞检测系统组件。
|
||||||
|
* @param Actor The actor to query. 要查询的Actor。
|
||||||
|
* @param Component The found component (output). 找到的组件(输出)。
|
||||||
|
* @return True if found. 如果找到返回true。
|
||||||
|
*/
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "GCS|TraceSystem", Meta = (DefaultToSelf="Actor", ExpandBoolAsExecs="ReturnValue"))
|
||||||
|
static bool FindTraceSystemComponent(const AActor* Actor, UGCS_TraceSystemComponent*& Component);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the collision trace system.
|
||||||
|
* 初始化碰撞检测系统。
|
||||||
|
*/
|
||||||
|
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = "GCS|TraceSystem", meta=(DisplayName="Initialize Trace System"))
|
||||||
|
void OnInitialize();
|
||||||
|
virtual void OnInitialize_Implementation();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deinitializes the collision trace system.
|
||||||
|
* 取消初始化碰撞检测系统。
|
||||||
|
*/
|
||||||
|
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = "GCS|TraceSystem", meta=(DisplayName="Deinitialize Trace System"))
|
||||||
|
void OnDeinitialize();
|
||||||
|
virtual void OnDeinitialize_Implementation();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates trace instances from definitions.
|
||||||
|
* 从定义创建碰撞检测实例。
|
||||||
|
* @param Definitions The trace definitions. 碰撞检测定义。
|
||||||
|
* @param SourceComponent The primitive component for tracing. 用于追踪的原始组件。
|
||||||
|
* @param SourceObject The source object. 源对象。
|
||||||
|
* @return The created trace instances. 创建的碰撞检测实例。
|
||||||
|
*/
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "GCS|TraceSystem")
|
||||||
|
TArray<FGCS_TraceHandle> AddTracesByDefinitions(const TArray<FGCS_TraceDefinition>& Definitions, UPrimitiveComponent* SourceComponent, UObject* SourceObject);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates trace instances from data table row handles.
|
||||||
|
* 从数据表行句柄创建碰撞检测实例。
|
||||||
|
* @param DefinitionHandles The data table row handles for trace definitions. 碰撞检测定义的数据表行句柄。
|
||||||
|
* @param SourceComponent The primitive component for tracing. 用于追踪的原始组件。
|
||||||
|
* @param SourceObject The source object. 源对象。
|
||||||
|
* @return The created trace instances. 创建的碰撞检测实例。
|
||||||
|
*/
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "GCS|TraceSystem")
|
||||||
|
TArray<FGCS_TraceHandle> AddTracesByDefinitionHandles(UPARAM(meta=(RowType="/Script/GenericCombatSystem.GCS_CollisionTraceDefinition"))
|
||||||
|
const TArray<FDataTableRowHandle>& DefinitionHandles, UPrimitiveComponent* SourceComponent, UObject* SourceObject);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a single trace instance from a definition.
|
||||||
|
* 从定义创建单个碰撞检测实例。
|
||||||
|
* @param Definition The trace definition. 碰撞检测定义。
|
||||||
|
* @param SourceComponent The primitive component for tracing. 用于追踪的原始组件。
|
||||||
|
* @param SourceObject The source object. 源对象。
|
||||||
|
* @return The created trace instance. 创建的碰撞检测实例。
|
||||||
|
*/
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "GCS|TraceSystem")
|
||||||
|
FGCS_TraceHandle AddTraceByDefinition(const FGCS_TraceDefinition& Definition, UPrimitiveComponent* SourceComponent, UObject* SourceObject);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts traces by tags and source object.
|
||||||
|
* 通过标签和源对象启动碰撞检测。
|
||||||
|
* @param TraceTags The gameplay tags to match. 要匹配的游戏标签。
|
||||||
|
* @param SourceObject The source object. 源对象。
|
||||||
|
* @return The started trace instances. 启动的碰撞检测实例。
|
||||||
|
*/
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "GCS|TraceSystem")
|
||||||
|
TArray<FGCS_TraceHandle> StartTracesByTagsAndSource(const FGameplayTagContainer& TraceTags, const UObject* SourceObject);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts multiple traces by their handles.
|
||||||
|
* 通过句柄启动多个碰撞检测。
|
||||||
|
* @param TraceHandles The trace handles to start. 要启动的碰撞检测句柄。
|
||||||
|
*/
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "GCS|TraceSystem")
|
||||||
|
void StartTracesByHandles(const TArray<FGCS_TraceHandle>& TraceHandles);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts a single trace by its handle.
|
||||||
|
* 通过句柄启动单个碰撞检测。
|
||||||
|
* @param TraceHandle The trace handle to start. 要启动的碰撞检测句柄。
|
||||||
|
*/
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "GCS|TraceSystem")
|
||||||
|
void StartTraceByHandle(const FGCS_TraceHandle& TraceHandle);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stops multiple traces by their handles.
|
||||||
|
* 通过句柄停止多个碰撞检测。
|
||||||
|
* @param TraceHandles The trace handles to stop. 要停止的碰撞检测句柄。
|
||||||
|
*/
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "GCS|TraceSystem")
|
||||||
|
void StopTracesByHandles(const TArray<FGCS_TraceHandle>& TraceHandles);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stops a single trace by its handle.
|
||||||
|
* 通过句柄停止单个碰撞检测。
|
||||||
|
* @param TraceHandle The trace handle to stop. 要停止的碰撞检测句柄。
|
||||||
|
*/
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "GCS|TraceSystem")
|
||||||
|
void StopTraceByHandle(const FGCS_TraceHandle& TraceHandle);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a trace by its handle.
|
||||||
|
* 通过句柄移除碰撞检测。
|
||||||
|
* @param TraceHandle The trace handle to remove. 要移除的碰撞检测句柄。
|
||||||
|
*/
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "GCS|TraceSystem")
|
||||||
|
void RemoveTraceByHandle(const FGCS_TraceHandle& TraceHandle);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears all active and cached trace instances.
|
||||||
|
* 清除所有激活和缓存的碰撞检测实例。
|
||||||
|
* @note OnTraceEndPlay event is not fired. OnTraceEndPlay事件不会触发。
|
||||||
|
*/
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "GCS|TraceSystem")
|
||||||
|
void RemoveAllTraces();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets all trace handles with the specified tag.
|
||||||
|
* 获取具有指定标签的所有碰撞检测句柄。
|
||||||
|
* @param TraceToFind The gameplay tag to search for. 要搜索的游戏标签。
|
||||||
|
* @return Array of trace handles with the specified tag. 具有指定标签的碰撞检测句柄数组。
|
||||||
|
*/
|
||||||
|
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "GCS|TraceSystem")
|
||||||
|
TArray<FGCS_TraceHandle> GetTraceHandlesByTag(FGameplayTag TraceToFind) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets all trace handles created by the specified source object.
|
||||||
|
* 获取由指定源对象创建的所有碰撞检测句柄。
|
||||||
|
* @param SourceObject The source object to search for. 要搜索的源对象。
|
||||||
|
* @return Array of trace handles from the specified source. 来自指定源的碰撞检测句柄数组。
|
||||||
|
*/
|
||||||
|
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "GCS|TraceSystem")
|
||||||
|
TArray<FGCS_TraceHandle> GetTraceHandlesBySource(const UObject* SourceObject) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets all trace handles with the specified tags and source object.
|
||||||
|
* 获取具有指定标签和源对象的所有碰撞检测句柄。
|
||||||
|
* @param TraceTags The gameplay tags to match. 要匹配的游戏标签。
|
||||||
|
* @param SourceObject The source object to match. 要匹配的源对象。
|
||||||
|
* @return Array of trace handles matching the criteria. 匹配条件的碰撞检测句柄数组。
|
||||||
|
*/
|
||||||
|
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "GCS|TraceSystem")
|
||||||
|
TArray<FGCS_TraceHandle> GetTraceHandlesByTagsAndSource(const FGameplayTagContainer& TraceTags, const UObject* SourceObject) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the source actor for the specified trace handle.
|
||||||
|
* 获取指定碰撞检测句柄的源Actor。
|
||||||
|
* @param TraceHandle The trace handle to query. 要查询的碰撞检测句柄。
|
||||||
|
* @return The source actor, or nullptr if not found. 源Actor,如果未找到则返回nullptr。
|
||||||
|
*/
|
||||||
|
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "GCS|TraceSystem")
|
||||||
|
AActor* GetTraceSourceActor(const FGCS_TraceHandle& TraceHandle) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the source component for the specified trace handle.
|
||||||
|
* 获取指定碰撞检测句柄的源组件。
|
||||||
|
* @param TraceHandle The trace handle to query. 要查询的碰撞检测句柄。
|
||||||
|
* @return The source component, or nullptr if not found. 源组件,如果未找到则返回nullptr。
|
||||||
|
*/
|
||||||
|
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "GCS|TraceSystem")
|
||||||
|
UPrimitiveComponent* GetTraceSourceComponent(const FGCS_TraceHandle& TraceHandle) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the specified trace is currently active.
|
||||||
|
* 检查指定的碰撞检测当前是否激活。
|
||||||
|
* @param TraceHandle The trace handle to check. 要检查的碰撞检测句柄。
|
||||||
|
* @return True if the trace is active. 如果碰撞检测激活则返回true。
|
||||||
|
*/
|
||||||
|
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "GCS|TraceSystem")
|
||||||
|
bool IsTraceActive(const FGCS_TraceHandle& TraceHandle) const;
|
||||||
|
|
||||||
|
virtual TArray<FGCS_TraceHandle> StartTraces(const FGameplayTagContainer& TraceTags, const UObject* SourceObject);
|
||||||
|
|
||||||
|
virtual TArray<FGCS_TraceHandle> AddTraces(const TArray<FGCS_TraceDefinition>& Definitions, UPrimitiveComponent* SourceComponent, UObject* SourceObject);
|
||||||
|
virtual TArray<FGCS_TraceHandle> AddTraces(const TArray<FDataTableRowHandle>& DefinitionHandles, UPrimitiveComponent* SourceComponent, UObject* SourceObject);
|
||||||
|
virtual FGCS_TraceHandle AddTrace(const FGCS_TraceDefinition& TraceDefinition, UPrimitiveComponent* SourceComponent, UObject* SourceObject);
|
||||||
|
virtual FGCS_TraceHandle AddTrace(const FDataTableRowHandle& TraceDefinitionHandle, UPrimitiveComponent* SourceComponent, UObject* SourceObject);
|
||||||
|
|
||||||
|
virtual void StartTraces(const TArray<FGCS_TraceHandle>& TraceHandles);
|
||||||
|
|
||||||
|
virtual void StartTrace(const FGCS_TraceHandle& TraceHandle);
|
||||||
|
|
||||||
|
virtual void StopTraces(const TArray<FGCS_TraceHandle>& TraceHandles);
|
||||||
|
|
||||||
|
virtual void StopTrace(const FGCS_TraceHandle& TraceHandle);
|
||||||
|
|
||||||
|
virtual void RemoveTraces(const TArray<FGCS_TraceHandle>& TraceHandles);
|
||||||
|
virtual void RemoveTrace(const FGCS_TraceHandle& TraceHandle);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event fired when a trace hits something.
|
||||||
|
* 当碰撞检测命中某物时触发的事件。
|
||||||
|
*/
|
||||||
|
UPROPERTY(BlueprintAssignable, Category = "GCS|TraceSystem")
|
||||||
|
FGCS_OnTraceHitSignature OnTraceHitEvent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event fired when a trace state changes.
|
||||||
|
* 当碰撞检测状态改变时触发的事件。
|
||||||
|
*/
|
||||||
|
UPROPERTY(BlueprintAssignable, Category = "GCS|TraceSystem")
|
||||||
|
FGCS_OnTraceStateChangedSignature OnTraceStateChangedEvent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event fired when a trace starts.
|
||||||
|
* 当碰撞检测开始时触发的事件。
|
||||||
|
*/
|
||||||
|
UPROPERTY(BlueprintAssignable, Category = "GCS|TraceSystem")
|
||||||
|
FGCS_OnTraceStartedSignature OnTraceStartedEvent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event fired when a trace stops.
|
||||||
|
* 当碰撞检测停止时触发的事件。
|
||||||
|
*/
|
||||||
|
UPROPERTY(BlueprintAssignable, Category = "GCS|TraceSystem")
|
||||||
|
FGCS_OnTraceStoppedSignature OnTraceStoppedEvent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event fired when the trace system is destroyed.
|
||||||
|
* 当碰撞检测系统被销毁时触发的事件。
|
||||||
|
*/
|
||||||
|
FGCS_OnTraceSystemDestroyedSignature OnDestroyedEvent;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual void OnTraceHitDetected(const FGCS_TraceHandle& TraceHandle, const TArray<FHitResult>& HitResults, const float DeltaTime, const uint32 TickIdx);
|
||||||
|
|
||||||
|
virtual void OnTraceStateChanged(const FGCS_TraceHandle& TraceHandle, bool bNewState);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the game starts.
|
||||||
|
* 游戏开始时调用。
|
||||||
|
*/
|
||||||
|
virtual void BeginPlay() override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the game ends.
|
||||||
|
* 游戏结束时调用。
|
||||||
|
* @param EndPlayReason The reason for ending. 结束原因。
|
||||||
|
*/
|
||||||
|
virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
|
||||||
|
|
||||||
|
virtual void OnComponentDestroyed(bool bDestroyingHierarchy) override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether to auto-initialize on BeginPlay and deinitialize on EndPlay.
|
||||||
|
* 是否在BeginPlay时自动初始化,在EndPlay时自动取消初始化。
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditAnywhere, Category = "GCS|TraceSystem")
|
||||||
|
bool bAutoInitialize{true};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default trace definitions created on BeginPlay.
|
||||||
|
* 在BeginPlay时创建的默认碰撞检测定义。
|
||||||
|
* @note SourceComponent will be the actor's main mesh. 源组件会是Actor的主要mesh。
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "GCS|TraceSystem", meta=(TitleProperty="TraceTag"))
|
||||||
|
TArray<FGCS_TraceDefinition> TraceDefinitions;
|
||||||
|
|
||||||
|
FCriticalSection TraceDoneScopeLock;
|
||||||
|
|
||||||
|
TMap<FGCS_TraceHandle, int32> HandleToStateIdx;
|
||||||
|
TMultiMap<FGameplayTag, FGCS_TraceHandle> TagToHandles;
|
||||||
|
bool bInitialized = false;
|
||||||
|
};
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "GCS_CombatStructLibrary.h"
|
||||||
|
#include "Engine/DataAsset.h"
|
||||||
|
#include "GCS_AbilityActionSetSettings.generated.h"
|
||||||
|
|
||||||
|
class UGCS_LayeredMontageSelectionSet;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data asset for defining ability action sets.
|
||||||
|
* 定义能力动作集的数据资产。
|
||||||
|
*/
|
||||||
|
UCLASS(BlueprintType, Const, meta=(DisplayName="GCS Ability Action Set"))
|
||||||
|
class GENERICCOMBATSYSTEM_API UGCS_AbilityActionSetSettings : public UDataAsset
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* Selects the best ability actions based on tags.
|
||||||
|
* 根据标签选择最佳能力动作。
|
||||||
|
* @param SourceTags Tags for the source. 来源标签。
|
||||||
|
* @param TargetTags Tags for the target. 目标标签。
|
||||||
|
* @param AbilityTags Tags for the ability. 能力标签。
|
||||||
|
* @param Actions The matched ability actions (output). 匹配的能力动作(输出)。
|
||||||
|
* @return True if selection is successful. 如果选择成功返回true。
|
||||||
|
*/
|
||||||
|
UFUNCTION(BlueprintCallable, BlueprintPure=false, Category = "GCS", meta=(AutoCreateRefTerm="TargetTags"))
|
||||||
|
bool SelectBestAbilityActions(const FGameplayTagContainer& SourceTags, const FGameplayTagContainer& TargetTags, const FGameplayTagContainer& AbilityTags, TArray<FGCS_AbilityAction>& Actions) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Array of ability action sets.
|
||||||
|
* 能力动作集数组。
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditAnywhere, Category="GCS", meta=(TitleProperty="AbilityTag"))
|
||||||
|
TArray<FGCS_AbilityActionSet> ActionSets;
|
||||||
|
|
||||||
|
#if WITH_EDITORONLY_DATA
|
||||||
|
/**
|
||||||
|
* Called before saving in the editor.
|
||||||
|
* 编辑器中保存前调用。
|
||||||
|
* @param SaveContext The save context. 保存上下文。
|
||||||
|
*/
|
||||||
|
virtual void PreSave(FObjectPreSaveContext SaveContext) override;
|
||||||
|
#endif
|
||||||
|
};
|
||||||
@@ -0,0 +1,142 @@
|
|||||||
|
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "GameplayEffect.h"
|
||||||
|
#include "Engine/DataTable.h"
|
||||||
|
#include "GameplayTagContainer.h"
|
||||||
|
#include "GGA_AbilitySystemStructLibrary.h"
|
||||||
|
#include "Runtime/Launch/Resources/Version.h"
|
||||||
|
#if ENGINE_MINOR_VERSION < 5
|
||||||
|
#include "InstancedStruct.h"
|
||||||
|
#else
|
||||||
|
#include "StructUtils/InstancedStruct.h"
|
||||||
|
#endif
|
||||||
|
#include "GCS_AttackDefinition.generated.h"
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base struct allow you to extend the attack definition's fields using C++.
|
||||||
|
* 基础结构体,允许你通过C++拓展攻击定义的字段。
|
||||||
|
*/
|
||||||
|
USTRUCT(BlueprintType, meta=(Hidden))
|
||||||
|
struct GENERICCOMBATSYSTEM_API FGCS_AttackDefinitionExtension
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Structure defining an attack's properties.
|
||||||
|
* 定义攻击属性的结构。
|
||||||
|
*/
|
||||||
|
USTRUCT(BlueprintType, meta=(DisplayName="GCS Attack Definition"))
|
||||||
|
struct GENERICCOMBATSYSTEM_API FGCS_AttackDefinition : public FTableRowBase
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tags describing the attack (e.g., Melee/Ranged, Slash/Strike).
|
||||||
|
* 描述攻击的标签(例如近战/远程、劈砍/打击)。
|
||||||
|
* @note Added as dynamic AssetTags to the gameplay effect spec.
|
||||||
|
* @注意 作为动态AssetTags添加到游戏效果规格。
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Common")
|
||||||
|
FGameplayTagContainer AttackTags;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SetByCaller tag-to-float mappings for gameplay effect specs.
|
||||||
|
* 用于游戏效果规格的SetByCaller标签到浮点映射。
|
||||||
|
* @note Usage is flexible (e.g., damage correction factors).
|
||||||
|
* @注意 使用灵活(例如伤害修正系数)。
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Common", meta=(ForceInlineRow))
|
||||||
|
TMap<FGameplayTag, float> SetByCallerMagnitudes;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gameplay effect to apply to the hit target.
|
||||||
|
* 应用于命中目标的游戏效果。
|
||||||
|
* @note Modified during attack request processing.
|
||||||
|
* @注意 在攻击请求处理期间修改。
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Gameplay Effects")
|
||||||
|
TSoftClassPtr<UGameplayEffect> TargetEffectClass;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Level of the target gameplay effect.
|
||||||
|
* 目标游戏效果的等级。
|
||||||
|
* @note If < 1, uses the ability's level.
|
||||||
|
* @注意 如果<1,使用能力的等级。
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Gameplay Effects")
|
||||||
|
int32 TargetEffectClassLevel{1};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Effect container to apply to the target.
|
||||||
|
* 应用于目标的效果容器。
|
||||||
|
* @note Used for instant targeting; ability level determines effect level.
|
||||||
|
* @注意 用于即时目标;能力等级决定效果等级。
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Gameplay Effects", meta=(ForceInlineRow))
|
||||||
|
FGGA_GameplayEffectContainer TargetEffectContainer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gameplay cues to trigger on the target upon hit.
|
||||||
|
* 命中目标时触发的游戏反馈。
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Gameplay Cues", meta=(Categories="GameplayCue"))
|
||||||
|
TArray<FGameplayTag> TargetGameplayCues;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Knockback distance applied to the target.
|
||||||
|
* 应用于目标的击退距离。
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "HitReaction", meta=(Units="cm"))
|
||||||
|
float KnockbackDistance{100};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Multiplier for knockback effect.
|
||||||
|
* 击退效果的倍增器。
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "HitReaction", meta=(ClampMin=1))
|
||||||
|
float KnockbackMultiplier{1};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Duration of animation stall on hit (disabled if <= 0).
|
||||||
|
* 命中时动画停滞的持续时间(<=0时禁用)。
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Feedback", meta=(ClampMin=0, Units="s"))
|
||||||
|
float HitStallingDuration{0};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Play rate factor for hit animation.
|
||||||
|
* 命中动画的播放速率因子。
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Feedback", meta=(ClampMin=0.1, ClampMax=0.9))
|
||||||
|
float HitPlayRateFactor{0.1};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Native Instanced struct for extending the attack definition.
|
||||||
|
* 实例化结构体用于扩充攻击定义的字段。
|
||||||
|
* @attention For C++ users only. 仅针对C++用户。
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Extension")
|
||||||
|
TInstancedStruct<FGCS_AttackDefinitionExtension> NativeExtension;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Blueprint Instanced struct for extending the attack definition.
|
||||||
|
* 实例化结构体用于扩充攻击定义的字段。
|
||||||
|
* @attention For blueprint users only. 仅针对蓝图用户。
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Extension")
|
||||||
|
FInstancedStruct Extension;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* User-defined settings for the attack.
|
||||||
|
* 攻击的用户定义设置。
|
||||||
|
*/
|
||||||
|
UE_DEPRECATED(1.5, "Using extension field to add custom fields!")
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Deprecated", meta=(ForceInlineRow, BaseStruct = "/Script/GenericCombatSystem.GCS_UserSetting"))
|
||||||
|
TMap<FGameplayTag, FInstancedStruct> UserSettings;
|
||||||
|
};
|
||||||
@@ -0,0 +1,224 @@
|
|||||||
|
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "GameplayTagContainer.h"
|
||||||
|
#include "Bullet/GCS_BulletStructLibrary.h"
|
||||||
|
#include "GCS_AttackDefinition.h"
|
||||||
|
#include "UObject/Object.h"
|
||||||
|
#include "GCS_AttackRequest.generated.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base class for all attack request types.
|
||||||
|
* 所有攻击请求类型的基类。
|
||||||
|
*/
|
||||||
|
UCLASS(Abstract, Blueprintable, BlueprintType, Const, DefaultToInstanced, EditInlineNew, meta=(DisplayName="GCS Attack Request"))
|
||||||
|
class UGCS_AttackRequest_Base : public UObject
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* Gets the attack definition handle.
|
||||||
|
* 获取攻击定义句柄。
|
||||||
|
* @return The attack definition handle. 攻击定义句柄。
|
||||||
|
*/
|
||||||
|
UFUNCTION(BlueprintCallable, BlueprintPure, BlueprintNativeEvent, Category="GCS|Attack")
|
||||||
|
FDataTableRowHandle GetAttackDefinitionHandle() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the attack definition.
|
||||||
|
* 获取攻击定义。
|
||||||
|
* @return The attack definition. 攻击定义。
|
||||||
|
*/
|
||||||
|
UFUNCTION(BlueprintCallable, BlueprintPure, Category="GCS|Attack")
|
||||||
|
FGCS_AttackDefinition GetAttackDefinition() const;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attack request for melee attacks.
|
||||||
|
* 近战攻击请求。
|
||||||
|
*/
|
||||||
|
UCLASS(meta=(DisplayName="GCS Attack Request (Melee)"))
|
||||||
|
class GENERICCOMBATSYSTEM_API UGCS_AttackRequest_Melee : public UGCS_AttackRequest_Base
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* Gets the attack definition handle.
|
||||||
|
* 获取攻击定义句柄。
|
||||||
|
* @return The attack definition handle. 攻击定义句柄。
|
||||||
|
*/
|
||||||
|
virtual FDataTableRowHandle GetAttackDefinitionHandle_Implementation() const override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tags for traces activated during the notify state.
|
||||||
|
* 在通知状态期间激活的追踪标签。
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Attack")
|
||||||
|
FGameplayTagContainer TracesToControl;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
/**
|
||||||
|
* Handle to the attack definition.
|
||||||
|
* 攻击定义的句柄。
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Attack", meta=(RowType="/Script/GenericCombatSystem.GCS_AttackDefinition"))
|
||||||
|
FDataTableRowHandle AttackDefinitionHandle;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enum for ability targeting source types.
|
||||||
|
* 能力目标来源类型的枚举。
|
||||||
|
*/
|
||||||
|
UENUM(BlueprintType)
|
||||||
|
enum class EGCS_AbilityTargetingSourceType : uint8
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* From the player's camera towards camera focus.
|
||||||
|
* 从玩家相机朝向相机焦点。
|
||||||
|
*/
|
||||||
|
CameraTowardsFocus,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* From the pawn's location/socket, in the pawn's orientation.
|
||||||
|
* 从Pawn的位置/插槽,沿Pawn的朝向。
|
||||||
|
*/
|
||||||
|
PawnForward,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* From the pawn's location/socket, oriented towards camera focus.
|
||||||
|
* 从Pawn的位置/插槽,朝向相机焦点。
|
||||||
|
*/
|
||||||
|
PawnTowardsFocus,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* From the weapon's location/socket, in the pawn's orientation.
|
||||||
|
* 从武器的位置/插槽,沿Pawn的朝向。
|
||||||
|
*/
|
||||||
|
WeaponForward,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* From the weapon's location/socket, towards camera focus.
|
||||||
|
* 从武器的位置/插槽,朝向相机焦点。
|
||||||
|
*/
|
||||||
|
WeaponTowardsFocus,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom targeting, requires overriding GetTargetingTransform.
|
||||||
|
* 自定义目标,需重写GetTargetingTransform。
|
||||||
|
*/
|
||||||
|
Custom
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attack request for firing bullets.
|
||||||
|
* 发射子弹的攻击请求。
|
||||||
|
*/
|
||||||
|
UCLASS(Blueprintable, BlueprintType, Const, EditInlineNew, meta=(DisplayName="GCS Attack Request (Bullet)"))
|
||||||
|
class GENERICCOMBATSYSTEM_API UGCS_AttackRequest_Bullet : public UGCS_AttackRequest_Base
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* Gets the bullet definition.
|
||||||
|
* 获取子弹定义。
|
||||||
|
* @return The bullet definition. 子弹定义。
|
||||||
|
*/
|
||||||
|
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "GCS|Attack")
|
||||||
|
FGCS_BulletDefinition GetBulletDefinition() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the attack definition handle.
|
||||||
|
* 获取攻击定义句柄。
|
||||||
|
* @return The attack definition handle. 攻击定义句柄。
|
||||||
|
*/
|
||||||
|
virtual FDataTableRowHandle GetAttackDefinitionHandle_Implementation() const override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the targeting transform for the attack.
|
||||||
|
* 获取攻击的目标变换。
|
||||||
|
* @param SourcePawn The source pawn. 来源Pawn。
|
||||||
|
* @param Source The targeting source type. 目标来源类型。
|
||||||
|
* @return The targeting transform. 目标变换。
|
||||||
|
*/
|
||||||
|
UFUNCTION(BlueprintCallable, BlueprintPure, BlueprintNativeEvent, Category = "GCS|Attack")
|
||||||
|
FTransform GetTargetingTransform(APawn* SourcePawn, EGCS_AbilityTargetingSourceType Source) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the weapon targeting source location.
|
||||||
|
* 获取武器目标来源位置。
|
||||||
|
* @param SourcePawn The source pawn. 来源Pawn。
|
||||||
|
* @return The weapon source location. 武器来源位置。
|
||||||
|
*/
|
||||||
|
UFUNCTION(BlueprintCallable, BlueprintPure, BlueprintNativeEvent, Category = "GCS|Attack")
|
||||||
|
FVector GetWeaponTargetingSourceLocation(APawn* SourcePawn) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the pawn targeting source location.
|
||||||
|
* 获取Pawn目标来源位置。
|
||||||
|
* @param SourcePawn The source pawn. 来源Pawn。
|
||||||
|
* @return The pawn source location. Pawn来源位置。
|
||||||
|
*/
|
||||||
|
UFUNCTION(BlueprintCallable, BlueprintPure, BlueprintNativeEvent, Category = "GCS|Attack")
|
||||||
|
FVector GetPawnTargetingSourceLocation(APawn* SourcePawn) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle to the bullet definition.
|
||||||
|
* 子弹定义的句柄。
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Parameters, meta=(RowType="/Script/GenericCombatSystem.GCS_BulletDefinition"))
|
||||||
|
FDataTableRowHandle BulletDefinitionHandle;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Type of targeting source for the attack.
|
||||||
|
* 攻击的目标来源类型。
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (ExposeOnSpawn = true), Category = Parameters)
|
||||||
|
EGCS_AbilityTargetingSourceType TargetingSourceType{EGCS_AbilityTargetingSourceType::PawnForward};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tag name for looking up the source component.
|
||||||
|
* 用于查找来源组件的标签名称。
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (ExposeOnSpawn = true), Category = Parameters, meta=(EditCondition="TargetingSourceType != EGCS_AbilityTargetingSourceType::CameraTowardsFocus"))
|
||||||
|
FName SourceComponentLookupTagName{NAME_None};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Source socket name, falls back to source location if not found.
|
||||||
|
* 来源插槽名称,如果未找到则回退到来源位置。
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (ExposeOnSpawn = true), Category = Parameters, meta=(EditCondition="TargetingSourceType != EGCS_AbilityTargetingSourceType::CameraTowardsFocus"))
|
||||||
|
FName SourceSocketName{NAME_None};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Weapon socket name, falls back to source location if not found.
|
||||||
|
* 武器插槽名称,如果未找到则回退到来源位置。
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (ExposeOnSpawn = true), Category = Parameters, meta=(EditCondition="TargetingSourceType != EGCS_AbilityTargetingSourceType::CameraTowardsFocus"))
|
||||||
|
FName SourceWeaponSocketName{NAME_None};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Additional offset to the source location.
|
||||||
|
* 来源位置的附加偏移。
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (ExposeOnSpawn = true), Category = Parameters)
|
||||||
|
FVector LocationOffset{FVector::Zero()};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether targeting is required for the attack.
|
||||||
|
* 攻击是否需要目标。
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Parameters)
|
||||||
|
bool bRequireTargeting{false};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Targeting preset for the attack.
|
||||||
|
* 攻击的目标预设。
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Parameters)
|
||||||
|
TObjectPtr<UTargetingPreset> TargetingPreset;
|
||||||
|
};
|
||||||
@@ -0,0 +1,205 @@
|
|||||||
|
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "GameplayEffectTypes.h"
|
||||||
|
#include "GCS_CombatStructLibrary.h"
|
||||||
|
#include "Net/Serialization/FastArraySerializer.h"
|
||||||
|
#include "UObject/Object.h"
|
||||||
|
#include "GCS_AttackResult.generated.h"
|
||||||
|
|
||||||
|
class UGCS_AttackRequest_Melee;
|
||||||
|
class UGCS_CombatFlow;
|
||||||
|
class UGCS_CombatSystemComponent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Structure representing the result of a processed attack.
|
||||||
|
* 表示已处理攻击结果的结构。
|
||||||
|
*/
|
||||||
|
USTRUCT(BlueprintType)
|
||||||
|
struct GENERICCOMBATSYSTEM_API FGCS_AttackResult : public FFastArraySerializerItem
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
|
||||||
|
void PostReplicatedAdd(const struct FGCS_AttackResultContainer& InArray);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deprecated.
|
||||||
|
* 已经弃用。
|
||||||
|
*/
|
||||||
|
UE_DEPRECATED(1.5, "Use TaggedValues within FGCS_ContextPayload_Combat")
|
||||||
|
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="GCS", NotReplicated, meta=(DeprecatedProperty, DeprecationMessage="Use TaggedValues within FGCS_ContextPayload_Combat!"))
|
||||||
|
TArray<FGCS_TaggedValue> TaggedValues;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Optional object related to the attack.
|
||||||
|
* 与攻击相关的可选对象。
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GCS")
|
||||||
|
TObjectPtr<const UObject> OptionalObject;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Context handle for the gameplay effect.
|
||||||
|
* 游戏效果的上下文句柄。
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GCS")
|
||||||
|
FGameplayEffectContextHandle EffectContextHandle;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Aggregated source tags for the attack.
|
||||||
|
* 攻击的聚合来源标签。
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GCS")
|
||||||
|
FGameplayTagContainer AggregatedSourceTags;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Aggregated target tags for the attack.
|
||||||
|
* 攻击的聚合目标标签。
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GCS")
|
||||||
|
FGameplayTagContainer AggregatedTargetTags;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the attack result has been consumed.
|
||||||
|
* 攻击结果是否已被消耗。
|
||||||
|
*/
|
||||||
|
UPROPERTY(NotReplicated)
|
||||||
|
bool bConsumed{false};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates this attack result was found in existing processed array with same prediction key.
|
||||||
|
* 表示此攻击结果在已处理攻击结果列表中有相同的预测Key。
|
||||||
|
*/
|
||||||
|
UPROPERTY(NotReplicated)
|
||||||
|
bool bWasPredicated{false};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates this attack result was replicated via fast array serializer.
|
||||||
|
* 表示此攻击结果是经由fast array serializer同步而来。
|
||||||
|
*/
|
||||||
|
UPROPERTY(NotReplicated)
|
||||||
|
bool bWasReplicated{false};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Container for storing combat results with network serialization.
|
||||||
|
* 用于存储战斗结果的容器,支持网络序列化。
|
||||||
|
*/
|
||||||
|
USTRUCT(BlueprintType)
|
||||||
|
struct GENERICCOMBATSYSTEM_API FGCS_AttackResultContainer : public FFastArraySerializer
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default constructor.
|
||||||
|
* 默认构造函数。
|
||||||
|
*/
|
||||||
|
FGCS_AttackResultContainer();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor with combat flow and max size.
|
||||||
|
* 带有战斗流程和最大尺寸的构造函数。
|
||||||
|
* @param InCombatFlow The combat flow instance. 战斗流程实例。
|
||||||
|
* @param InMaxSize The maximum size of the container. 容器最大尺寸。
|
||||||
|
*/
|
||||||
|
FGCS_AttackResultContainer(UGCS_CombatFlow* InCombatFlow, int32 InMaxSize);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor with combat system component and max size.
|
||||||
|
* 带有战斗系统组件和最大尺寸的构造函数。
|
||||||
|
* @param InCombatSystemComponent The combat system component. 战斗系统组件。
|
||||||
|
* @param InMaxSize The maximum size of the container. 容器最大尺寸。
|
||||||
|
*/
|
||||||
|
FGCS_AttackResultContainer(UGCS_CombatSystemComponent* InCombatSystemComponent, int32 InMaxSize);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the owning combat system component.
|
||||||
|
* 设置所属战斗系统组件。
|
||||||
|
* @param InCombatSystemComponent The combat system component. 战斗系统组件。
|
||||||
|
*/
|
||||||
|
void SetOwningCombatSystem(UGCS_CombatSystemComponent* InCombatSystemComponent) { CombatSystemComponent = InCombatSystemComponent; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the combat flow.
|
||||||
|
* 设置战斗流程。
|
||||||
|
* @param InCombatFlow The combat flow instance. 战斗流程实例。
|
||||||
|
*/
|
||||||
|
void SetCombatFlow(UGCS_CombatFlow* InCombatFlow) { CombatFlow = InCombatFlow; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a new attack result entry to the container.
|
||||||
|
* 向容器添加新的攻击结果条目。
|
||||||
|
* @param NewEntry The attack result to add. 要添加的攻击结果。
|
||||||
|
*/
|
||||||
|
void AddEntry(FGCS_AttackResult& NewEntry);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles post-replication addition of entries.
|
||||||
|
* 处理条目添加后的复制。
|
||||||
|
* @param AddedIndices The indices of added entries. 添加的条目索引。
|
||||||
|
* @param FinalSize The final size of the container. 容器最终尺寸。
|
||||||
|
*/
|
||||||
|
void PostReplicatedAdd(const TArrayView<int32> AddedIndices, int32 FinalSize);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles post-replication changes to entries.
|
||||||
|
* 处理条目更改后的复制。
|
||||||
|
* @param ChangedIndices The indices of changed entries. 更改的条目索引。
|
||||||
|
* @param FinalSize The final size of the container. 容器最终尺寸。
|
||||||
|
*/
|
||||||
|
void PostReplicatedChange(const TArrayView<int32>& ChangedIndices, int32 FinalSize);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Serializes the container for network replication.
|
||||||
|
* 为网络复制序列化容器。
|
||||||
|
* @param DeltaParms The network serialization parameters. 网络序列化参数。
|
||||||
|
* @return True if serialization is successful. 如果序列化成功返回true。
|
||||||
|
*/
|
||||||
|
bool NetDeltaSerialize(FNetDeltaSerializeInfo& DeltaParms)
|
||||||
|
{
|
||||||
|
return FastArrayDeltaSerialize<FGCS_AttackResult, FGCS_AttackResultContainer>(Results, DeltaParms, *this);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HasPredictedResultWithPredictedKey(FPredictionKey PredictionKey) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
/**
|
||||||
|
* Reference to the combat flow instance.
|
||||||
|
* 战斗流程实例的引用。
|
||||||
|
*/
|
||||||
|
UPROPERTY()
|
||||||
|
TObjectPtr<UGCS_CombatFlow> CombatFlow;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reference to the combat system component.
|
||||||
|
* 战斗系统组件的引用。
|
||||||
|
*/
|
||||||
|
UPROPERTY()
|
||||||
|
TObjectPtr<UGCS_CombatSystemComponent> CombatSystemComponent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of attack results.
|
||||||
|
* 攻击结果列表。
|
||||||
|
*/
|
||||||
|
UPROPERTY()
|
||||||
|
TArray<FGCS_AttackResult> Results;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maximum size of the container.
|
||||||
|
* 容器最大尺寸。
|
||||||
|
*/
|
||||||
|
UPROPERTY()
|
||||||
|
int32 MaxSize;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct TStructOpsTypeTraits<FGCS_AttackResultContainer> : TStructOpsTypeTraitsBase2<FGCS_AttackResultContainer>
|
||||||
|
{
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
WithNetDeltaSerializer = true,
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -0,0 +1,287 @@
|
|||||||
|
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "GCS_AttackResult.h"
|
||||||
|
#include "UObject/Object.h"
|
||||||
|
#include "GCS_AttackResultProcessor.generated.h"
|
||||||
|
|
||||||
|
|
||||||
|
UENUM()
|
||||||
|
enum class EGCS_AttackResultProcessorPolicy
|
||||||
|
{
|
||||||
|
//execute when non-predicting cross all server and clients.
|
||||||
|
Default,
|
||||||
|
//execute in predicting client first,then server and other clients.same as default is not predicting.
|
||||||
|
LocalPredicted,
|
||||||
|
//execute only on server side.
|
||||||
|
ServerOnly
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base class for processing attack results.
|
||||||
|
* 处理攻击结果的基类。
|
||||||
|
*/
|
||||||
|
UCLASS(EditInlineNew, DefaultToInstanced, BlueprintType, Blueprintable, Abstract, Const)
|
||||||
|
class GENERICCOMBATSYSTEM_API UGCS_AttackResultProcessor : public UObject
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* Processes an incoming attack result.
|
||||||
|
* 处理传入的攻击结果。
|
||||||
|
* @param AttackResult The attack result to process. 要处理的攻击结果。
|
||||||
|
*/
|
||||||
|
UFUNCTION(BlueprintCallable, Category="GCS")
|
||||||
|
virtual bool ProcessIncomingAttackResult(const FGCS_AttackResult& AttackResult);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the world context for the processor.
|
||||||
|
* 获取处理器的世界上下文。
|
||||||
|
* @return The world context. 世界上下文。
|
||||||
|
*/
|
||||||
|
virtual UWorld* GetWorld() const override;
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable, BlueprintPure, BlueprintNativeEvent, Category="GCS")
|
||||||
|
EGCS_AttackResultProcessorPolicy GetExecutePolicy() const;
|
||||||
|
|
||||||
|
#if WITH_EDITOR
|
||||||
|
bool GetEditorEnableState() const { return bEditorDebugEnabled; };
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
protected:
|
||||||
|
/**
|
||||||
|
* Indicate how this processor will be executed cross network.
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="GCS")
|
||||||
|
EGCS_AttackResultProcessorPolicy ExecutePolicy{EGCS_AttackResultProcessorPolicy::Default};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the incoming attack result.
|
||||||
|
* 处理传入的攻击结果。
|
||||||
|
* @param AttackResult The attack result to handle. 要处理的攻击结果。
|
||||||
|
*/
|
||||||
|
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category="GCS")
|
||||||
|
void HandleIncomingAttackResult(const FGCS_AttackResult& AttackResult) const;
|
||||||
|
virtual void HandleIncomingAttackResult_Implementation(const FGCS_AttackResult& AttackResult) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the owning actor.
|
||||||
|
* 获取所属演员。
|
||||||
|
* @return The owning actor. 所属演员。
|
||||||
|
*/
|
||||||
|
UFUNCTION(BlueprintCallable, BlueprintPure, Category="GCS")
|
||||||
|
AActor* GetOwningActor() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the owning ability system component.
|
||||||
|
* 获取所属能力系统组件。
|
||||||
|
* @return The ability system component. 能力系统组件。
|
||||||
|
*/
|
||||||
|
UFUNCTION(BlueprintCallable, BlueprintPure, Category="GCS")
|
||||||
|
UAbilitySystemComponent* GetOwningAbilitySystemComponent() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the editor-friendly name for the processor.
|
||||||
|
* 获取处理器的编辑器友好名称。
|
||||||
|
* @return The editor-friendly name. 编辑器友好名称。
|
||||||
|
*/
|
||||||
|
UFUNCTION(BlueprintNativeEvent, Category="GCS")
|
||||||
|
FString GetEditorFriendlyName() const;
|
||||||
|
virtual FString GetEditorFriendlyName_Implementation() const;
|
||||||
|
|
||||||
|
#if WITH_EDITORONLY_DATA
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allowing toggle on/off this processor for debugging purpose.
|
||||||
|
* 允许你开关此处理器,用于调试。
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditAnywhere, Category="GCS")
|
||||||
|
bool bEditorDebugEnabled{true};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Editor-friendly name for the processor.
|
||||||
|
* 处理器的编辑器友好名称。
|
||||||
|
*/
|
||||||
|
UPROPERTY(VisibleAnywhere, Category=AlwaysHidden)
|
||||||
|
FString EditorFriendlyName;
|
||||||
|
|
||||||
|
// UPROPERTY(EditAnywhere, Category="GCS")
|
||||||
|
// bool bPrintDebugString{false};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called before saving in the editor.
|
||||||
|
* 编辑器中保存前调用。
|
||||||
|
* @param SaveContext The save context. 保存上下文。
|
||||||
|
*/
|
||||||
|
virtual void PreSave(FObjectPreSaveContext SaveContext) override;
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attack result processor with tag requirements.
|
||||||
|
* 具有标签要求的攻击结果处理器。
|
||||||
|
*/
|
||||||
|
UCLASS(Abstract)
|
||||||
|
class UGCS_AttackResultProcessor_WithTagRequirement : public UGCS_AttackResultProcessor
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* Processes an incoming attack result with tag requirements.
|
||||||
|
* 处理具有标签要求的传入攻击结果。
|
||||||
|
* @param AttackResult The attack result to process. 要处理的攻击结果。
|
||||||
|
*/
|
||||||
|
virtual bool ProcessIncomingAttackResult(const FGCS_AttackResult& AttackResult) override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
/**
|
||||||
|
* Source tag query for filtering attack results.
|
||||||
|
* 用于过滤攻击结果的来源标签查询。
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="GCS")
|
||||||
|
FGameplayTagQuery SourceTagQuery;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Target tag query for filtering attack results.
|
||||||
|
* 用于过滤攻击结果的目标标签查询。
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="GCS")
|
||||||
|
FGameplayTagQuery TargetTagQuery;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the description of the source tag query.
|
||||||
|
* 获取来源标签查询的描述。
|
||||||
|
* @return The source tag query description. 来源标签查询描述。
|
||||||
|
*/
|
||||||
|
UFUNCTION(BlueprintCallable, BlueprintPure, Category="GCS")
|
||||||
|
FString GetSourceTagQueryDesc() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the description of the target tag query.
|
||||||
|
* 获取目标标签查询的描述。
|
||||||
|
* @return The target tag query description. 目标标签查询描述。
|
||||||
|
*/
|
||||||
|
UFUNCTION(BlueprintCallable, BlueprintPure, Category="GCS")
|
||||||
|
FString GetTargetTagQueryDesc() const;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processor for handling death-related attack results.
|
||||||
|
* 处理与死亡相关的攻击结果的处理器。
|
||||||
|
*/
|
||||||
|
UCLASS()
|
||||||
|
class UGCS_AttackResultProcessor_Death : public UGCS_AttackResultProcessor
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* Handles death-related attack results.
|
||||||
|
* 处理与死亡相关的攻击结果。
|
||||||
|
* @param AttackResult The attack result to handle. 要处理的攻击结果。
|
||||||
|
*/
|
||||||
|
virtual void HandleIncomingAttackResult_Implementation(const FGCS_AttackResult& AttackResult) const override;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processor for converting attack results to gameplay events.
|
||||||
|
* 将攻击结果转换为游戏事件的处理器。
|
||||||
|
* @note Only executes for server pawn or local controller pawn. The dynamic tags added to effect context will be merged as Instigator Tags.
|
||||||
|
* @注意 仅对服务器Pawn或本地控制器Pawn执行。 添加到Effect Context的动态标签会被合并为Instigator Tags。
|
||||||
|
*/
|
||||||
|
UCLASS()
|
||||||
|
class UGCS_AttackResultProcessor_GameplayEvent : public UGCS_AttackResultProcessor_WithTagRequirement
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
protected:
|
||||||
|
/**
|
||||||
|
* Handles attack results by converting to gameplay events.
|
||||||
|
* 通过转换为游戏事件处理攻击结果。
|
||||||
|
* @param AttackResult The attack result to handle. 要处理的攻击结果。
|
||||||
|
*/
|
||||||
|
virtual void HandleIncomingAttackResult_Implementation(const FGCS_AttackResult& AttackResult) const override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the editor-friendly name for the processor.
|
||||||
|
* 获取处理器的编辑器友好名称。
|
||||||
|
* @return The editor-friendly name. 编辑器友好名称。
|
||||||
|
*/
|
||||||
|
virtual FString GetEditorFriendlyName_Implementation() const override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether to send the event to the attacker.
|
||||||
|
* 是否将事件发送给攻击者。
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="GCS")
|
||||||
|
bool bSendToAttacker{false};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gameplay tags to trigger as events.
|
||||||
|
* 作为事件触发的游戏标签。
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="GCS")
|
||||||
|
TArray<FGameplayTag> EventTriggers;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processor for triggering gameplay cues from attack results.
|
||||||
|
* 从攻击结果触发游戏反馈的处理器。
|
||||||
|
* @note Cues do not replicate as attack results are replicated.
|
||||||
|
* @注意 反馈不复制,因为攻击结果已复制。
|
||||||
|
*/
|
||||||
|
UCLASS()
|
||||||
|
class UGCS_AttackResultProcessor_GameplayCue : public UGCS_AttackResultProcessor_WithTagRequirement
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
protected:
|
||||||
|
/**
|
||||||
|
* Handles attack results by triggering gameplay cues.
|
||||||
|
* 通过触发游戏反馈处理攻击结果。
|
||||||
|
* @param AttackResult The attack result to handle. 要处理的攻击结果。
|
||||||
|
*/
|
||||||
|
virtual void HandleIncomingAttackResult_Implementation(const FGCS_AttackResult& AttackResult) const override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the editor-friendly name for the processor.
|
||||||
|
* 获取处理器的编辑器友好名称。
|
||||||
|
* @return The editor-friendly name. 编辑器友好名称。
|
||||||
|
*/
|
||||||
|
virtual FString GetEditorFriendlyName_Implementation() const override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modifies gameplay cue parameters before execution.
|
||||||
|
* 在执行前修改游戏反馈参数。
|
||||||
|
* @param ParametersToModify The parameters to modify. 要修改的参数。
|
||||||
|
*/
|
||||||
|
UFUNCTION(BlueprintImplementableEvent, Category="GCS")
|
||||||
|
void ModifyGameplayCueParametersBeforeExecute(UPARAM(ref)
|
||||||
|
FGameplayCueParameters& ParametersToModify) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gameplay cues to trigger.
|
||||||
|
* 要触发的游戏反馈。
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="GCS", meta=(Categories="GameplayCue"))
|
||||||
|
TArray<FGameplayTag> GameplayCues;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tag for finding raw magnitude in TaggedValues.
|
||||||
|
* 在TaggedValues中查找原始幅度的标签。
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="GCS")
|
||||||
|
FGameplayTag RawMagnitudeTag;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tag for finding normalized magnitude in TaggedValues.
|
||||||
|
* 在TaggedValues中查找归一化幅度的标签。
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="GCS")
|
||||||
|
FGameplayTag NormalizedMagnitudeTag;
|
||||||
|
};
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user