文章目录

  • 添加新角色(不写了)
  • 创建一个发射技能
  • 创建一个飞弹类
  • 添加击中特效
  • 添加准星UI
  • 获取瞄准目标


添加新角色(不写了)

将原本的机器人蓝图改为BP_PlayerCharacter,以此创建子蓝图
在这里插入图片描述
创建动画蓝图模板(具体就是换资源,我没弄,我重新写了)
在这里插入图片描述

创建一个发射技能

创建一个GA_Shoot
在这里插入图片描述
添加新的标签

	CRUNCH_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(Stats_Crosshair)// 射击CRUNCH_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(Ability_Shoot)CRUNCH_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(Ability_Shoot_Hand_l)CRUNCH_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(Ability_Shoot_Hand_r)//目标更新CRUNCH_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(Target_Updated)
	UE_DEFINE_GAMEPLAY_TAG_COMMENT(Stats_Crosshair, "Stats.Crosshair", "准星")UE_DEFINE_GAMEPLAY_TAG_COMMENT(Ability_Shoot, "Ability.Shoot", "射击技能")UE_DEFINE_GAMEPLAY_TAG_COMMENT(Ability_Shoot_Head_l, "Ability.Shoot.Head_l", "左手开火")UE_DEFINE_GAMEPLAY_TAG_COMMENT(Ability_Shoot_Head_r, "Ability.Shoot.Head_r", "右手开火")UE_DEFINE_GAMEPLAY_TAG_COMMENT(Target_Updated, "Target.Updated", "目标更新")

CGameplayAbility中添加提供子类调用的函数

	// 本地播放Montage动画void PlayMontageLocally(UAnimMontage* MontageToPlay);// 播放完当前分段后停止Montagevoid StopMontageAfterCurrentSection(UAnimMontage* MontageToStop);// 获取拥有者的队伍IDFGenericTeamId GetOwnerTeamId() const;
void UCGameplayAbility::PlayMontageLocally(UAnimMontage* MontageToPlay)
{UAnimInstance* OwnerAnimInst = GetOwnerAnimInstance();if (OwnerAnimInst && !OwnerAnimInst->Montage_IsPlaying(MontageToPlay)){OwnerAnimInst->Montage_Play(MontageToPlay);}
}void UCGameplayAbility::StopMontageAfterCurrentSection(UAnimMontage* MontageToStop)
{UAnimInstance* OwnerAnimInst = GetOwnerAnimInstance();if (OwnerAnimInst){FName CurrentSectionName = OwnerAnimInst->Montage_GetCurrentSection(MontageToStop);OwnerAnimInst->Montage_SetNextSection(CurrentSectionName, NAME_None, MontageToStop);}
}FGenericTeamId UCGameplayAbility::GetOwnerTeamId() const
{IGenericTeamAgentInterface* OwnerTeamInterface = Cast<IGenericTeamAgentInterface>(GetAvatarActorFromActorInfo());if (OwnerTeamInterface){return OwnerTeamInterface->GetGenericTeamId();}return FGenericTeamId::NoTeam;
}
#pragma once#include "CoreMinimal.h"
#include "GAS/Core/CGameplayAbility.h"
#include "GA_Shoot.generated.h"/*** */
UCLASS()
class CRUNCH_API UGA_Shoot : public UCGameplayAbility
{GENERATED_BODY()public:// 构造函数UGA_Shoot();// 激活能力(开始射击)virtual void ActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, const FGameplayEventData* TriggerEventData) override;// 输入释放(停止射击)virtual void InputReleased(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo) override;// 结束能力(能力生命周期结束)virtual void EndAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, bool bReplicateEndAbility, bool bWasCancelled) override;private:// 射击动画UPROPERTY(EditDefaultsOnly, Category = "Anim")TObjectPtr<UAnimMontage> ShootMontage;// 开始射击事件处理UFUNCTION()void StartShooting(FGameplayEventData Payload);// 停止射击事件处理UFUNCTION()void StopShooting(FGameplayEventData Payload);// 发射子弹UFUNCTION()void ShootProjectile(FGameplayEventData Payload);
};
#include "GA_Shoot.h"#include "Abilities/Tasks/AbilityTask_PlayMontageAndWait.h"
#include "Abilities/Tasks/AbilityTask_WaitGameplayEvent.h"UGA_Shoot::UGA_Shoot()
{// 技能激活时添加瞄准以及准星ActivationOwnedTags.AddTag(TGameplayTags::Stats_Aim);ActivationOwnedTags.AddTag(TGameplayTags::Stats_Crosshair);
}void UGA_Shoot::ActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo,const FGameplayAbilityActivationInfo ActivationInfo, const FGameplayEventData* TriggerEventData)
{// 提交消耗以及cdif (!K2_CommitAbility()){K2_EndAbility();return;}UE_LOG(LogTemp, Warning, TEXT("激活射击技能"));// 仅在服务器或有预测权限时绑定事件if (HasAuthorityOrPredictionKey(ActorInfo, &ActivationInfo)){// 绑定开始射击事件UAbilityTask_WaitGameplayEvent* WaitStartShootingEvent = UAbilityTask_WaitGameplayEvent::WaitGameplayEvent(this, TGameplayTags::Ability_BasicAttack_Pressed);WaitStartShootingEvent->EventReceived.AddDynamic(this, &UGA_Shoot::StartShooting);WaitStartShootingEvent->ReadyForActivation();// 绑定停止射击事件UAbilityTask_WaitGameplayEvent* WaitStopShootingEvent = UAbilityTask_WaitGameplayEvent::WaitGameplayEvent(this, TGameplayTags::Ability_BasicAttack_Released);WaitStopShootingEvent->EventReceived.AddDynamic(this, &UGA_Shoot::StopShooting);WaitStopShootingEvent->ReadyForActivation();// 绑定发射子弹事件UAbilityTask_WaitGameplayEvent* WaitShootProjectileEvent = UAbilityTask_WaitGameplayEvent::WaitGameplayEvent(this, TGameplayTags::Ability_Shoot, nullptr, false, false);WaitShootProjectileEvent->EventReceived.AddDynamic(this, &UGA_Shoot::ShootProjectile);WaitShootProjectileEvent->ReadyForActivation();}
}void UGA_Shoot::InputReleased(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo,const FGameplayAbilityActivationInfo ActivationInfo)
{UE_LOG(LogTemp, Warning, TEXT("停止射击技能"));K2_EndAbility();
}void UGA_Shoot::EndAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo,const FGameplayAbilityActivationInfo ActivationInfo, bool bReplicateEndAbility, bool bWasCancelled)
{// 停止射击StopShooting(FGameplayEventData());Super::EndAbility(Handle, ActorInfo, ActivationInfo, bReplicateEndAbility, bWasCancelled);
}void UGA_Shoot::StartShooting(FGameplayEventData Payload)
{UE_LOG(LogTemp, Warning, TEXT("开始射击技能"));// 仅在服务器有权限时播放动画if (HasAuthority(&CurrentActivationInfo)){// 服务器播放射击动画UAbilityTask_PlayMontageAndWait* PlayShootMontage = UAbilityTask_PlayMontageAndWait::CreatePlayMontageAndWaitProxy(this, NAME_None, ShootMontage);PlayShootMontage->ReadyForActivation();}else{// 客户端本地播放射击动画PlayMontageLocally(ShootMontage);}
}void UGA_Shoot::StopShooting(FGameplayEventData Payload)
{if (ShootMontage){UE_LOG(LogTemp, Warning, TEXT("停止射击技能"));// 停止蒙太奇动画StopMontageAfterCurrentSection(ShootMontage);}
}void UGA_Shoot::ShootProjectile(FGameplayEventData Payload)
{
}

添加一个新的输入
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

创建蓝图版本的射击技能,把瞄准射击技能添加到角色中
在这里插入图片描述

创建一个飞弹类

ProjectileActor
在这里插入图片描述

#pragma once#include "CoreMinimal.h"
#include "GenericTeamAgentInterface.h"
#include "GameplayEffectTypes.h"
#include "GameFramework/Actor.h"
#include "GAS/Core/CGameplayAbilityTypes.h"
#include "ProjectileActor.generated.h"/*** 投射物Actor类* 负责处理子弹/弹道的移动、命中、效果应用等逻辑*/
UCLASS()
class CRUNCH_API AProjectileActor : public AActor, public IGenericTeamAgentInterface
{GENERATED_BODY()public:// Sets default values for this actor's propertiesAProjectileActor();// 发射子弹,初始化速度、距离、目标、队伍ID和命中效果void ShootProjectile(float InSpeed,float InMaxDistance,const AActor* InTarget,FGenericTeamId InTeamId,FGameplayEffectSpecHandle InHitEffectHandle,FGenericDamageEffectDef InDamageEffectDef);// 网络属性同步virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;// 设置队伍IDvirtual void SetGenericTeamId(const FGenericTeamId& NewTeamID);// 获取队伍IDvirtual FGenericTeamId GetGenericTeamId() const { return TeamId; }// 处理与其他Actor的重叠(命中检测)virtual void NotifyActorBeginOverlap(AActor* OtherActor) override;
private:// 命中时触发的GameplayCue标签UPROPERTY(EditDefaultsOnly, Category = "Gameplay Cue")FGameplayTag HitGameplayCueTag;// 伤害效果定义UPROPERTY(EditDefaultsOnly, Category = "DamageEffect")FGenericDamageEffectDef DamageEffectDef;// 投射物所属队伍ID(同步属性)UPROPERTY(Replicated)FGenericTeamId TeamId;// 投射物移动方向(同步属性)UPROPERTY(Replicated)FVector MoveDir;// 投射物速度(同步属性)UPROPERTY(Replicated)float ProjectileSpeed;// 当前目标ActorUPROPERTY()const AActor* Target;// 命中时应用的效果句柄FGameplayEffectSpecHandle HitEffectSpecHandle;// 发射定时器句柄FTimerHandle ShootTimerHandle;
protected:// Called when the game starts or when spawnedvirtual void BeginPlay() override;public:// Called every framevirtual void Tick(float DeltaTime) override;
private:// 达到最大射程时的处理void TravelMaxDistanceReached();// 发送本地GameplayCue(用于特效、音效等)void SendLocalGameplayCue(AActor* CueTargetActor, const FHitResult& HitResult);
};
#include "ProjectileActor.h"#include "AbilitySystemBlueprintLibrary.h"
#include "AbilitySystemComponent.h"
#include "AbilitySystemGlobals.h"
#include "GameplayCueManager.h"
#include "Net/UnrealNetwork.h"// Sets default values
AProjectileActor::AProjectileActor()
{// 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;// 创建根组件USceneComponent* RootComp = CreateDefaultSubobject<USceneComponent>("Root Comp");SetRootComponent(RootComp);// 启用网络同步bReplicates = true;
}void AProjectileActor::ShootProjectile(float InSpeed, float InMaxDistance, const AActor* InTarget,FGenericTeamId InTeamId, FGameplayEffectSpecHandle InHitEffectHandle, FGenericDamageEffectDef InDamageEffectDef)
{// 设置目标以及速度Target = InTarget;ProjectileSpeed = InSpeed;// 方向FRotator OwnerViewRot = GetActorRotation();// 队伍设置SetGenericTeamId(InTeamId);// 获取拥有者视角(如有需要可用于弹道修正)if (GetOwner()){FVector OwnerViewLoc;GetOwner()->GetActorEyesViewPoint(OwnerViewLoc, OwnerViewRot);}// 设置移动方向MoveDir = OwnerViewRot.Vector();HitEffectSpecHandle = InHitEffectHandle;if (InDamageEffectDef.DamageEffect){// 伤害效果定义DamageEffectDef = InDamageEffectDef;}// 设置最大飞行时间,到达后自动销毁float TravelMaxTime = InMaxDistance / InSpeed;GetWorldTimerManager().SetTimer(ShootTimerHandle, this, &AProjectileActor::TravelMaxDistanceReached, TravelMaxTime);
}void AProjectileActor::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{Super::GetLifetimeReplicatedProps(OutLifetimeProps);DOREPLIFETIME(AProjectileActor, MoveDir);DOREPLIFETIME(AProjectileActor, TeamId);DOREPLIFETIME(AProjectileActor, ProjectileSpeed);
}void AProjectileActor::SetGenericTeamId(const FGenericTeamId& NewTeamID)
{TeamId = NewTeamID;
}void AProjectileActor::NotifyActorBeginOverlap(AActor* OtherActor)
{// 忽略自身和拥有者if (!OtherActor || OtherActor == GetOwner())return;// 只对敌方目标生效if (GetTeamAttitudeTowards(*OtherActor) != ETeamAttitude::Hostile)return;// 获取目标的能力系统组件UAbilitySystemComponent* OtherASC = UAbilitySystemBlueprintLibrary::GetAbilitySystemComponent(OtherActor);if (IsValid(OtherASC)){// 服务器应用命中效果if (HasAuthority() && HitEffectSpecHandle.IsValid()){for (const auto& TypePair : DamageEffectDef.DamageTypeDefinitions){// 创建效果Spec句柄,指定效果类、能力等级和上下文FGameplayEffectSpecHandle EffectSpecHandle = HitEffectSpecHandle;float TotalModifier = TypePair.Value.BaseDamage.GetValueAtLevel(HitEffectSpecHandle.Data->GetLevel());for (const auto& Modifier : TypePair.Value.AttributeDamageModifiers){UAbilitySystemBlueprintLibrary::AssignTagSetByCallerMagnitude(EffectSpecHandle, Modifier.Key, Modifier.Value);}UAbilitySystemBlueprintLibrary::AssignTagSetByCallerMagnitude(EffectSpecHandle, TypePair.Key, TotalModifier);// 在目标上应用游戏效果规范OtherASC->ApplyGameplayEffectSpecToSelf(*EffectSpecHandle.Data.Get());}// OtherASC->ApplyGameplayEffectSpecToSelf(*HitEffectSpecHandle.Data.Get());GetWorldTimerManager().ClearTimer(ShootTimerHandle);}// 构造命中结果FHitResult HitResult;HitResult.ImpactPoint = GetActorLocation();HitResult.ImpactNormal = GetActorForwardVector();// 发送本地GameplayCue(用于特效、音效等)SendLocalGameplayCue(OtherActor, HitResult);// TODO:不销毁的话可以做成子弹对象池// 销毁投射物Destroy();}
}void AProjectileActor::TravelMaxDistanceReached()
{Destroy();
}void AProjectileActor::SendLocalGameplayCue(AActor* CueTargetActor, const FHitResult& HitResult)
{FGameplayCueParameters CueParams;CueParams.Location = HitResult.ImpactPoint;CueParams.Normal = HitResult.ImpactNormal;UAbilitySystemGlobals::Get().GetGameplayCueManager()->HandleGameplayCue(CueTargetActor, HitGameplayCueTag, EGameplayCueEvent::Executed, CueParams);
}// Called when the game starts or when spawned
void AProjectileActor::BeginPlay()
{Super::BeginPlay();}// Called every frame
void AProjectileActor::Tick(float DeltaTime)
{Super::Tick(DeltaTime);// 服务器端:如果有目标则实时调整方向if (HasAuthority()){if (Target){MoveDir = (Target->GetActorLocation() - GetActorLocation()).GetSafeNormal();}}// 按当前方向和速度移动SetActorLocation(GetActorLocation() + MoveDir * ProjectileSpeed * DeltaTime);
}

给射击技能添加代码,射出飞弹

	// 命中弹道时应用的效果UPROPERTY(EditDefaultsOnly, Category = "Shoot")FGenericDamageEffectDef ProjectileHitEffect;// 子弹速度UPROPERTY(EditDefaultsOnly, Category = "Shoot")float ShootProjectileSpeed = 2000.f;// 子弹射程UPROPERTY(EditDefaultsOnly, Category = "Shoot")float ShootProjectileRange = 3000.f;// 子弹Actor类UPROPERTY(EditDefaultsOnly, Category = "Shoot")TSubclassOf<class AProjectileActor> ProjectileClass;
void UGA_Shoot::ShootProjectile(FGameplayEventData Payload)
{UE_LOG(LogTemp, Warning, TEXT("发射子弹"))if (K2_HasAuthority()){// 获取拥有者ActorAActor* OwnerAvatarActor = GetAvatarActorFromActorInfo();FActorSpawnParameters SpawnParams;SpawnParams.Owner = OwnerAvatarActor;SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;// 默认发射位置为角色位置FVector SocketLocation = GetAvatarActorFromActorInfo()->GetActorLocation();// 获取角色的骨骼USkeletalMeshComponent* MeshComp = GetOwningComponentFromActorInfo();if (MeshComp){// 如果事件标签包含Socket名,则用Socket位置TArray<FName> OutNames;UGameplayTagsManager::Get().SplitGameplayTagFName(Payload.EventTag, OutNames);if (OutNames.Num() != 0){FName SocketName = OutNames.Last();SocketLocation = MeshComp->GetSocketLocation(SocketName);}}// 生成子弹AProjectileActor* ProjectileActor = GetWorld()->SpawnActor<AProjectileActor>(ProjectileClass, SocketLocation, OwnerAvatarActor->GetActorRotation(), SpawnParams);if (ProjectileActor){const UAbilitySystemComponent* ASC = GetAbilitySystemComponentFromActorInfo();AActor* AvatarActor				   = GetAvatarActorFromActorInfo();// 创建效果上下文, 设置能力 、源对象 和 施加者FGameplayEffectContextHandle EffectContextHandle = MakeEffectContext(GetCurrentAbilitySpecHandle(), GetCurrentActorInfo());// 将施法者Actor添加到上下文源对象中(用于追踪效果来源)EffectContextHandle.AddSourceObject(AvatarActor);EffectContextHandle.SetAbility(this);EffectContextHandle.AddSourceObject(AvatarActor);EffectContextHandle.AddInstigator(AvatarActor, AvatarActor);ProjectileActor->ShootProjectile(ShootProjectileSpeed,ShootProjectileRange,nullptr,GetOwnerTeamId(),ASC->MakeOutgoingSpec(ProjectileHitEffect.DamageEffect, GetAbilityLevel(CurrentSpecHandle, CurrentActorInfo), EffectContextHandle),ProjectileHitEffect);// ProjectileActor->ShootProjectile(// 	ShootProjectileSpeed,// 	ShootProjectileRange,// 	nullptr,// 	GetOwnerTeamId(),// 	MakeOutgoingGameplayEffectSpec(ProjectileHitEffect.DamageEffect, GetAbilityLevel(CurrentSpecHandle, CurrentActorInfo)),// 	ProjectileHitEffect// );}}
}

通过标签的最后一段,用于搜索骨骼位置
在这里插入图片描述

添加击中特效

创建GC
在这里插入图片描述
父类设置为HIt
在这里插入图片描述
在这里插入图片描述

添加准星UI

CrosshairWidget
在这里插入图片描述

#pragma once#include "CoreMinimal.h"
#include "Blueprint/UserWidget.h"
#include "GameplayTagContainer.h"
#include "CrosshairWidget.generated.h"class UImage;
class UCanvasPanelSlot;
/*** 准星控件* 用于显示和动态更新准星状态(如是否有目标、准星颜色等)*/
UCLASS()
class CRUNCH_API UCrosshairWidget : public UUserWidget
{GENERATED_BODY()
public:// 构建时回调,初始化控件virtual void NativeConstruct() override;// 每帧回调,处理准星位置和状态更新virtual void NativeTick(const FGeometry& MyGeometry, float InDeltaTime) override;private:// 有目标时准星颜色UPROPERTY(EditDefaultsOnly, Category = "View")FLinearColor HasTargetColor = FLinearColor::Red;// 无目标时准星颜色UPROPERTY(EditDefaultsOnly, Category = "View")FLinearColor NoTargetColor = FLinearColor::White;// 准星图片控件UPROPERTY(meta=(BindWidget))TObjectPtr<UImage> CrosshairImage;// 准星标签变化回调(用于响应目标锁定等)void CrosshairTagUpdated(const FGameplayTag Tag, int32 NewCount);// 准星在画布上的Slot(用于定位)UPROPERTY()TObjectPtr<UCanvasPanelSlot> CrosshairCanvasPanelSlot;// 缓存的玩家控制器UPROPERTY()TObjectPtr<APlayerController> CachedPlayerController;// 更新准星位置void UpdateCrosshairPosition();// 当前瞄准目标UPROPERTY()TObjectPtr<const AActor> AimTarget;// 目标更新回调(用于响应目标变化事件)void TargetUpdated(const struct FGameplayEventData* EventData);
};
#include "CrosshairWidget.h"#include "AbilitySystemBlueprintLibrary.h"
#include "AbilitySystemComponent.h"
#include "Components/CanvasPanelSlot.h"
#include "Components/Image.h"
#include "GAS/Core/TGameplayTags.h"
#include "Blueprint/WidgetLayoutLibrary.h"void UCrosshairWidget::NativeConstruct()
{Super::NativeConstruct();// 隐藏准星CrosshairImage->SetVisibility(ESlateVisibility::Hidden);UAbilitySystemComponent* OwnerAbilitySystemComponent = UAbilitySystemBlueprintLibrary::GetAbilitySystemComponent(GetOwningPlayerPawn());if (OwnerAbilitySystemComponent){// 监听准星标签的变换OwnerAbilitySystemComponent->RegisterGameplayTagEvent(TGameplayTags::Stats_Crosshair).AddUObject(this, &UCrosshairWidget::CrosshairTagUpdated);// 监听目标更新OwnerAbilitySystemComponent->GenericGameplayEventCallbacks.Add(TGameplayTags::Target_Updated).AddUObject(this, &UCrosshairWidget::TargetUpdated);}// 缓存玩家控制器CachedPlayerController = GetOwningPlayer();// 获取画布面板插槽用于定位CrosshairCanvasPanelSlot = Cast<UCanvasPanelSlot>(Slot);if (!CrosshairCanvasPanelSlot){UE_LOG(LogTemp, Error, TEXT("十字准星控件必须放在Canvas Panel中才能正确定位"));}
}void UCrosshairWidget::NativeTick(const FGeometry& MyGeometry, float InDeltaTime)
{Super::NativeTick(MyGeometry, InDeltaTime);// 当准星可见时更新位置if (CrosshairImage->GetVisibility() == ESlateVisibility::Visible){UpdateCrosshairPosition();}
}void UCrosshairWidget::CrosshairTagUpdated(const FGameplayTag Tag, int32 NewCount)
{// 根据标签计数显示/隐藏准星CrosshairImage->SetVisibility(NewCount > 0 ? ESlateVisibility::Visible : ESlateVisibility::Hidden);
}void UCrosshairWidget::UpdateCrosshairPosition()
{if (!CachedPlayerController || ! CrosshairCanvasPanelSlot) return;// 获取视口缩放比例float ViewportScale = UWidgetLayoutLibrary::GetViewportScale(this);// 获取玩家控制器的视口大小int32 SizeX, SizeY;CachedPlayerController->GetViewportSize(SizeX, SizeY);// 如果没有目标居中设置if (!AimTarget){FVector2D ViewportSize = FVector2D{static_cast<float>(SizeX), static_cast<float>(SizeY)};CrosshairCanvasPanelSlot->SetPosition(ViewportSize / 2.f / ViewportScale);return;}// 有目标时追踪目标位置FVector2D TargetScreenPosition;CachedPlayerController->ProjectWorldLocationToScreen(AimTarget->GetActorLocation(), TargetScreenPosition);if (TargetScreenPosition.X > 0 && TargetScreenPosition.X < SizeX && TargetScreenPosition.Y > 0 && TargetScreenPosition.Y < SizeY){CrosshairCanvasPanelSlot->SetPosition(TargetScreenPosition / ViewportScale);}}void UCrosshairWidget::TargetUpdated(const struct FGameplayEventData* EventData)
{// 更新当前目标AimTarget = EventData->Target;// 根据目标状态设置准星颜色CrosshairImage->SetColorAndOpacity(AimTarget ? HasTargetColor : NoTargetColor);
}

到GameplayWidget中添加准星UI

	// 准星UIUPROPERTY(meta=(BindWidget))TObjectPtr<UCrosshairWidget> CrosshairWidget;

创建一个材质制作准星,设置为用户见面以及半透明
在这里插入图片描述
在这里插入图片描述

float2 coord = uv - float2(0.5, 0.5);
float retVal = 0;float xDist = abs(coord.x);
float yDist = abs(coord.y);if(xDist < thickness/2 || yDist < thickness/2)
{retVal = 1;
}if(xDist < centerGap && yDist < centerGap)
{retVal = 0;
}if(length(coord) < thickness/2)
{retVal = 1;
}return retVal;

创建一个准星蓝图,把材质放进去
在这里插入图片描述
在这里插入图片描述

获取瞄准目标

到玩家角色ACPlayerCharacter中重载获取视觉的位置以及旋转

	// 获取角色视觉的位置和旋转virtual void GetActorEyesViewPoint(FVector& OutLocation, FRotator& OutRotation) const  override;
void ACPlayerCharacter::GetActorEyesViewPoint(FVector& OutLocation, FRotator& OutRotation) const
{// 获取视角摄像机的位置和旋转OutLocation = ViewCamera->GetComponentLocation();OutRotation = GetBaseAimRotation();
}

在能力基类中添加获取瞄准目标的函数

    // 获取瞄准目标(按距离和阵营过滤)AActor* GetAimTarget(float AimDistance, ETeamAttitude::Type TeamAttitude) const;// 判断目标是否为指定阵营bool IsActorTeamAttitudeIs(const AActor* OtherActor, ETeamAttitude::Type TeamAttitude) const;// 发送本地Gameplay事件void SendLocalGameplayEvent(const FGameplayTag& EventTag, const FGameplayEventData& EventData);
AActor* UCGameplayAbility::GetAimTarget(float AimDistance, ETeamAttitude::Type TeamAttitude) const
{// 获取当前执行能力的角色AActor* OwnerAvatarActor = GetAvatarActorFromActorInfo();if (OwnerAvatarActor){// 获取角色的视觉位置和视角方向FVector Location;FRotator Rotation;OwnerAvatarActor->GetActorEyesViewPoint(Location, Rotation);// 计算瞄准射线的终点FVector AimEnd = Location + Rotation.Vector() * AimDistance;// 设置碰撞查询参数FCollisionQueryParams CollisionQueryParams;CollisionQueryParams.AddIgnoredActor(OwnerAvatarActor); // 忽略自身// 设置碰撞对象查询参数(只查询Pawn类型对象)FCollisionObjectQueryParams CollisionObjectQueryParams;CollisionObjectQueryParams.AddObjectTypesToQuery(ECC_Pawn); // 仅检测Pawn对象// 调试模式:绘制瞄准线if (ShouldDrawDebug()){DrawDebugLine(GetWorld(), Location, AimEnd, FColor::Red, false, 2.f, 0U, 3.f);}// 射线检测TArray<FHitResult> HitResults;if (GetWorld()->LineTraceMultiByObjectType(HitResults,Location,AimEnd,CollisionObjectQueryParams,CollisionQueryParams)){// 遍历命中结果for (FHitResult& HitResult : HitResults){// 寻找指定阵容的Actorif (IsActorTeamAttitudeIs(HitResult.GetActor(), TeamAttitude)){// 返回命中的Actorreturn HitResult.GetActor();}}}}return nullptr;
}bool UCGameplayAbility::IsActorTeamAttitudeIs(const AActor* OtherActor, ETeamAttitude::Type TeamAttitude) const
{if (!OtherActor)return false;IGenericTeamAgentInterface* OwnerTeamAgentInterface = Cast<IGenericTeamAgentInterface>(GetAvatarActorFromActorInfo());if (OwnerTeamAgentInterface){return OwnerTeamAgentInterface->GetTeamAttitudeTowards(*OtherActor) == TeamAttitude;}return false;
}void UCGameplayAbility::SendLocalGameplayEvent(const FGameplayTag& EventTag, const FGameplayEventData& EventData)
{UAbilitySystemComponent* OwnerASC = GetAbilitySystemComponentFromActorInfo();if (OwnerASC){OwnerASC->HandleGameplayEvent(EventTag, &EventData);}
}

CAbilitySystemStatics添加判断是否死亡

	// 判断Actor是否死亡static bool IsActorDead(const AActor* ActorToCheck);// 判断Actor是否拥有指定标签static bool ActorHasTag(const AActor* ActorToCheck, const FGameplayTag& Tag);
bool UCAbilitySystemStatics::IsActorDead(const AActor* ActorToCheck)
{return ActorHasTag(ActorToCheck, TGameplayTags::Stats_Dead);
}bool UCAbilitySystemStatics::IsHero(const AActor* ActorToCheck)
{return ActorHasTag(ActorToCheck, TGameplayTags::Role_Hero);
}
bool UCAbilitySystemStatics::ActorHasTag(const AActor* ActorToCheck, const FGameplayTag& Tag)
{const IAbilitySystemInterface* ActorISA = Cast<IAbilitySystemInterface>(ActorToCheck);if (ActorISA){UAbilitySystemComponent* ActorASC = ActorISA->GetAbilitySystemComponent();if (ActorASC){return ActorASC->HasMatchingGameplayTag(Tag);}}return false;
}

到发射技能中添加索敌

	// 获取有效瞄准目标AActor* GetAimTargetIfValid() const;// 当前瞄准目标UPROPERTY()TObjectPtr<AActor> AimTarget;// 目标的能力系统组件UPROPERTY()UAbilitySystemComponent* AimTargetAbilitySystemComponent;// 检查瞄准目标的定时器句柄FTimerHandle AimTargetCheckTimerHandle;// 查找瞄准目标void FindAimTarget();// 检查瞄准目标的时间间隔UPROPERTY(EditDefaultsOnly, Category = "Target")float AimTargetCheckTimeInterval = 0.1f;// 启动瞄准目标检测定时器void StartAimTargetCheckTimer();// 停止瞄准目标检测定时器void StopAimTargetCheckTimer();// 是否有有效目标bool HasValidTarget() const;// 目标是否在射程内bool IsTargetInRange() const;// 目标死亡标签更新回调void TargetDeadTagUpdated(const FGameplayTag Tag, int32 NewCount);
void UGA_Shoot::EndAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo,const FGameplayAbilityActivationInfo ActivationInfo, bool bReplicateEndAbility, bool bWasCancelled)
{// 移除目标死亡标签监听if (AimTargetAbilitySystemComponent){AimTargetAbilitySystemComponent->RegisterGameplayTagEvent(TGameplayTags::Stats_Dead).RemoveAll(this);AimTargetAbilitySystemComponent = nullptr;}// 通知目标已更新SendLocalGameplayEvent(TGameplayTags::Target_Updated, FGameplayEventData());// 停止射击StopShooting(FGameplayEventData());Super::EndAbility(Handle, ActorInfo, ActivationInfo, bReplicateEndAbility, bWasCancelled);
}void UGA_Shoot::StartShooting(FGameplayEventData Payload)
{UE_LOG(LogTemp, Warning, TEXT("开始射击技能"));// 仅在服务器有权限时播放动画if (HasAuthority(&CurrentActivationInfo)){// 服务器播放射击动画UAbilityTask_PlayMontageAndWait* PlayShootMontage = UAbilityTask_PlayMontageAndWait::CreatePlayMontageAndWaitProxy(this, NAME_None, ShootMontage);PlayShootMontage->ReadyForActivation();}else{// 客户端本地播放射击动画PlayMontageLocally(ShootMontage);}// 查找瞄准目标并启动检测定时器FindAimTarget();StartAimTargetCheckTimer();
}void UGA_Shoot::ShootProjectile(FGameplayEventData Payload)
{// 仅在服务器有权限时发射子弹if (K2_HasAuthority()){// 获取拥有者ActorAActor* OwnerAvatarActor = GetAvatarActorFromActorInfo();FActorSpawnParameters SpawnParams;SpawnParams.Owner = OwnerAvatarActor;SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;// 默认发射位置为角色位置FVector SocketLocation = GetAvatarActorFromActorInfo()->GetActorLocation();UE_LOG(LogTemp, Warning, TEXT("发射位置1:%s"), *SocketLocation.ToString())// 获取角色的骨骼USkeletalMeshComponent* MeshComp = GetOwningComponentFromActorInfo();if (MeshComp){// 如果事件标签包含Socket名,则用Socket位置TArray<FName> OutNames;UGameplayTagsManager::Get().SplitGameplayTagFName(Payload.EventTag, OutNames);if (OutNames.Num() != 0){FName SocketName = OutNames.Last();//UE_LOG(LogTemp, Warning, TEXT("SocketName:%s"), *SocketName.ToString())SocketLocation = MeshComp->GetSocketLocation(SocketName);UE_LOG(LogTemp, Warning, TEXT("发射位置2:%s"), *SocketLocation.ToString())}}// 生成子弹AProjectileActor* ProjectileActor = GetWorld()->SpawnActor<AProjectileActor>(ProjectileClass, SocketLocation, OwnerAvatarActor->GetActorRotation(), SpawnParams);if (ProjectileActor){const UAbilitySystemComponent* ASC = GetAbilitySystemComponentFromActorInfo();AActor* AvatarActor				   = GetAvatarActorFromActorInfo();// 创建效果上下文, 设置能力 、源对象 和 施加者FGameplayEffectContextHandle EffectContextHandle = MakeEffectContext(GetCurrentAbilitySpecHandle(), GetCurrentActorInfo());// 将施法者Actor添加到上下文源对象中(用于追踪效果来源)EffectContextHandle.AddSourceObject(AvatarActor);EffectContextHandle.SetAbility(this);EffectContextHandle.AddSourceObject(AvatarActor);EffectContextHandle.AddInstigator(AvatarActor, AvatarActor);ProjectileActor->ShootProjectile(ShootProjectileSpeed,ShootProjectileRange,GetAimTargetIfValid(),GetOwnerTeamId(),ASC->MakeOutgoingSpec(ProjectileHitEffect.DamageEffect, GetAbilityLevel(CurrentSpecHandle, CurrentActorInfo), EffectContextHandle),ProjectileHitEffect);}}
}AActor* UGA_Shoot::GetAimTargetIfValid() const
{if (HasValidTarget())return AimTarget;return nullptr;
}void UGA_Shoot::FindAimTarget()
{// 已经有有效目标直接退出if (HasValidTarget())return;// 没有有效目标,曾经获取过目标ASC移除死亡监听if (AimTargetAbilitySystemComponent){AimTargetAbilitySystemComponent->RegisterGameplayTagEvent(TGameplayTags::Stats_Dead).RemoveAll(this);AimTargetAbilitySystemComponent = nullptr;}// 查找射程内的敌方目标AimTarget = GetAimTarget(ShootProjectileRange, ETeamAttitude::Hostile);if (AimTarget){AimTargetAbilitySystemComponent = UAbilitySystemBlueprintLibrary::GetAbilitySystemComponent(AimTarget);if (AimTargetAbilitySystemComponent){// 监听目标死亡标签变化AimTargetAbilitySystemComponent->RegisterGameplayTagEvent(TGameplayTags::Stats_Dead).AddUObject(this, &UGA_Shoot::TargetDeadTagUpdated);}}// 通知目标已更新FGameplayEventData EventData;EventData.Target = AimTarget;SendLocalGameplayEvent(TGameplayTags::Target_Updated, EventData);
}bool UGA_Shoot::HasValidTarget() const
{// 目标不存在if (!AimTarget)return false;// 角色死亡if (UCAbilitySystemStatics::IsActorDead(AimTarget))return false;// 目标不在范围内if (!IsTargetInRange())return false;return true;
}void UGA_Shoot::StartAimTargetCheckTimer()
{UWorld* World = GetWorld();if (World){World->GetTimerManager().SetTimer(AimTargetCheckTimerHandle, this, &UGA_Shoot::FindAimTarget, AimTargetCheckTimeInterval, true);}
}void UGA_Shoot::StopAimTargetCheckTimer()
{UWorld* World = GetWorld();if (World){World->GetTimerManager().ClearTimer(AimTargetCheckTimerHandle);}
}bool UGA_Shoot::IsTargetInRange() const
{if (!AimTarget) return false;// 获取目标距离float Distance = FVector::Distance(GetAvatarActorFromActorInfo()->GetActorLocation(), AimTarget->GetActorLocation());return Distance <= ShootProjectileRange;
}void UGA_Shoot::TargetDeadTagUpdated(const FGameplayTag Tag, int32 NewCount)
{if (NewCount > 0){FindAimTarget();}
}
#pragma once#include "CoreMinimal.h"
#include "GAS/Core/CGameplayAbility.h"
#include "GA_Shoot.generated.h"class AProjectileActor;
/*** */
UCLASS()
class CRUNCH_API UGA_Shoot : public UCGameplayAbility
{GENERATED_BODY()public:// 构造函数UGA_Shoot();// 激活能力(开始射击)virtual void ActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, const FGameplayEventData* TriggerEventData) override;// 输入释放(停止射击)virtual void InputReleased(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo) override;// 结束能力(能力生命周期结束)virtual void EndAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, bool bReplicateEndAbility, bool bWasCancelled) override;private:// 命中弹道时应用的效果UPROPERTY(EditDefaultsOnly, Category = "Shoot")FGenericDamageEffectDef ProjectileHitEffect;// 子弹速度UPROPERTY(EditDefaultsOnly, Category = "Shoot")float ShootProjectileSpeed = 2000.f;// 子弹射程UPROPERTY(EditDefaultsOnly, Category = "Shoot")float ShootProjectileRange = 3000.f;// 子弹Actor类UPROPERTY(EditDefaultsOnly, Category = "Shoot")TSubclassOf<AProjectileActor> ProjectileClass;// 射击动画UPROPERTY(EditDefaultsOnly, Category = "Anim")TObjectPtr<UAnimMontage> ShootMontage;// 开始射击事件处理UFUNCTION()void StartShooting(FGameplayEventData Payload);// 停止射击事件处理UFUNCTION()void StopShooting(FGameplayEventData Payload);// 发射子弹UFUNCTION()void ShootProjectile(FGameplayEventData Payload);// 获取有效瞄准目标AActor* GetAimTargetIfValid() const;// 当前瞄准目标UPROPERTY()TObjectPtr<AActor> AimTarget;// 目标的能力系统组件UPROPERTY()UAbilitySystemComponent* AimTargetAbilitySystemComponent;// 检查瞄准目标的定时器句柄FTimerHandle AimTargetCheckTimerHandle;// 查找瞄准目标void FindAimTarget();// 检查瞄准目标的时间间隔UPROPERTY(EditDefaultsOnly, Category = "Target")float AimTargetCheckTimeInterval = 0.1f;// 启动瞄准目标检测定时器void StartAimTargetCheckTimer();// 停止瞄准目标检测定时器void StopAimTargetCheckTimer();// 是否有有效目标bool HasValidTarget() const;// 目标是否在射程内bool IsTargetInRange() const;// 目标死亡标签更新回调void TargetDeadTagUpdated(const FGameplayTag Tag, int32 NewCount);
};
#include "GA_Shoot.h"#include "AbilitySystemBlueprintLibrary.h"
#include "AbilitySystemComponent.h"
#include "Abilities/Tasks/AbilityTask_PlayMontageAndWait.h"
#include "Abilities/Tasks/AbilityTask_WaitGameplayEvent.h"
#include "Actor/ProjectileActor.h"
#include "Abilities/Tasks/AbilityTask_NetworkSyncPoint.h"
#include "GAS/Core/CAbilitySystemStatics.h"
#include "GameplayTagsManager.h"UGA_Shoot::UGA_Shoot(): AimTargetAbilitySystemComponent(nullptr)
{// 技能激活时添加瞄准以及准星ActivationOwnedTags.AddTag(TGameplayTags::Stats_Aim);ActivationOwnedTags.AddTag(TGameplayTags::Stats_Crosshair);
}void UGA_Shoot::ActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo,const FGameplayAbilityActivationInfo ActivationInfo, const FGameplayEventData* TriggerEventData)
{// 提交消耗以及cdif (!K2_CommitAbility()){K2_EndAbility();return;}UE_LOG(LogTemp, Warning, TEXT("激活射击技能"));// 仅在服务器或有预测权限时绑定事件if (HasAuthorityOrPredictionKey(ActorInfo, &ActivationInfo)){// 绑定开始射击事件UAbilityTask_WaitGameplayEvent* WaitStartShootingEvent = UAbilityTask_WaitGameplayEvent::WaitGameplayEvent(this, TGameplayTags::Ability_BasicAttack_Pressed);WaitStartShootingEvent->EventReceived.AddDynamic(this, &UGA_Shoot::StartShooting);WaitStartShootingEvent->ReadyForActivation();// 绑定停止射击事件UAbilityTask_WaitGameplayEvent* WaitStopShootingEvent = UAbilityTask_WaitGameplayEvent::WaitGameplayEvent(this, TGameplayTags::Ability_BasicAttack_Released);WaitStopShootingEvent->EventReceived.AddDynamic(this, &UGA_Shoot::StopShooting);WaitStopShootingEvent->ReadyForActivation();// 绑定发射子弹事件UAbilityTask_WaitGameplayEvent* WaitShootProjectileEvent = UAbilityTask_WaitGameplayEvent::WaitGameplayEvent(this, TGameplayTags::Ability_Shoot, nullptr, false, false);WaitShootProjectileEvent->EventReceived.AddDynamic(this, &UGA_Shoot::ShootProjectile);WaitShootProjectileEvent->ReadyForActivation();}
}void UGA_Shoot::InputReleased(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo,const FGameplayAbilityActivationInfo ActivationInfo)
{UE_LOG(LogTemp, Warning, TEXT("停止射击技能"));K2_EndAbility();
}void UGA_Shoot::EndAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo,const FGameplayAbilityActivationInfo ActivationInfo, bool bReplicateEndAbility, bool bWasCancelled)
{// 移除目标死亡标签监听if (AimTargetAbilitySystemComponent){AimTargetAbilitySystemComponent->RegisterGameplayTagEvent(TGameplayTags::Stats_Dead).RemoveAll(this);AimTargetAbilitySystemComponent = nullptr;}// 通知目标已更新SendLocalGameplayEvent(TGameplayTags::Target_Updated, FGameplayEventData());// 停止射击StopShooting(FGameplayEventData());Super::EndAbility(Handle, ActorInfo, ActivationInfo, bReplicateEndAbility, bWasCancelled);
}void UGA_Shoot::StartShooting(FGameplayEventData Payload)
{UE_LOG(LogTemp, Warning, TEXT("开始射击技能"));// 仅在服务器有权限时播放动画if (HasAuthority(&CurrentActivationInfo)){// 服务器播放射击动画UAbilityTask_PlayMontageAndWait* PlayShootMontage = UAbilityTask_PlayMontageAndWait::CreatePlayMontageAndWaitProxy(this, NAME_None, ShootMontage);PlayShootMontage->ReadyForActivation();}else{// 客户端本地播放射击动画PlayMontageLocally(ShootMontage);}// 查找瞄准目标并启动检测定时器FindAimTarget();StartAimTargetCheckTimer();
}void UGA_Shoot::StopShooting(FGameplayEventData Payload)
{if (ShootMontage){UE_LOG(LogTemp, Warning, TEXT("停止射击技能"));// 停止蒙太奇动画StopMontageAfterCurrentSection(ShootMontage);}	StopAimTargetCheckTimer();
}void UGA_Shoot::ShootProjectile(FGameplayEventData Payload)
{UE_LOG(LogTemp, Warning, TEXT("发射子弹"))// 仅在服务器有权限时发射子弹if (K2_HasAuthority()){// 获取拥有者ActorAActor* OwnerAvatarActor = GetAvatarActorFromActorInfo();FActorSpawnParameters SpawnParams;SpawnParams.Owner = OwnerAvatarActor;SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;// 默认发射位置为角色位置FVector SocketLocation = GetAvatarActorFromActorInfo()->GetActorLocation();UE_LOG(LogTemp, Warning, TEXT("发射位置1:%s"), *SocketLocation.ToString())// 获取角色的骨骼USkeletalMeshComponent* MeshComp = GetOwningComponentFromActorInfo();if (MeshComp){// 如果事件标签包含Socket名,则用Socket位置TArray<FName> OutNames;UGameplayTagsManager::Get().SplitGameplayTagFName(Payload.EventTag, OutNames);if (OutNames.Num() != 0){FName SocketName = OutNames.Last();//UE_LOG(LogTemp, Warning, TEXT("SocketName:%s"), *SocketName.ToString())SocketLocation = MeshComp->GetSocketLocation(SocketName);UE_LOG(LogTemp, Warning, TEXT("发射位置2:%s"), *SocketLocation.ToString())}}// 生成子弹AProjectileActor* ProjectileActor = GetWorld()->SpawnActor<AProjectileActor>(ProjectileClass, SocketLocation, OwnerAvatarActor->GetActorRotation(), SpawnParams);if (ProjectileActor){const UAbilitySystemComponent* ASC = GetAbilitySystemComponentFromActorInfo();AActor* AvatarActor				   = GetAvatarActorFromActorInfo();// 创建效果上下文, 设置能力 、源对象 和 施加者FGameplayEffectContextHandle EffectContextHandle = MakeEffectContext(GetCurrentAbilitySpecHandle(), GetCurrentActorInfo());// 将施法者Actor添加到上下文源对象中(用于追踪效果来源)EffectContextHandle.AddSourceObject(AvatarActor);EffectContextHandle.SetAbility(this);EffectContextHandle.AddSourceObject(AvatarActor);EffectContextHandle.AddInstigator(AvatarActor, AvatarActor);ProjectileActor->ShootProjectile(ShootProjectileSpeed,ShootProjectileRange,GetAimTargetIfValid(),GetOwnerTeamId(),ASC->MakeOutgoingSpec(ProjectileHitEffect.DamageEffect, GetAbilityLevel(CurrentSpecHandle, CurrentActorInfo), EffectContextHandle),ProjectileHitEffect);}}
}AActor* UGA_Shoot::GetAimTargetIfValid() const
{if (HasValidTarget())return AimTarget;return nullptr;
}void UGA_Shoot::FindAimTarget()
{// 已经有有效目标直接退出if (HasValidTarget())return;// 没有有效目标,曾经获取过目标ASC移除死亡监听if (AimTargetAbilitySystemComponent){AimTargetAbilitySystemComponent->RegisterGameplayTagEvent(TGameplayTags::Stats_Dead).RemoveAll(this);AimTargetAbilitySystemComponent = nullptr;}// 查找射程内的敌方目标AimTarget = GetAimTarget(ShootProjectileRange, ETeamAttitude::Hostile);if (AimTarget){AimTargetAbilitySystemComponent = UAbilitySystemBlueprintLibrary::GetAbilitySystemComponent(AimTarget);if (AimTargetAbilitySystemComponent){// 监听目标死亡标签变化AimTargetAbilitySystemComponent->RegisterGameplayTagEvent(TGameplayTags::Stats_Dead).AddUObject(this, &UGA_Shoot::TargetDeadTagUpdated);}}// 通知目标已更新FGameplayEventData EventData;EventData.Target = AimTarget;SendLocalGameplayEvent(TGameplayTags::Target_Updated, EventData);
}bool UGA_Shoot::HasValidTarget() const
{// 目标不存在if (!AimTarget)return false;// 角色死亡if (UCAbilitySystemStatics::IsActorDead(AimTarget))return false;// 目标不在范围内if (!IsTargetInRange())return false;return true;
}void UGA_Shoot::StartAimTargetCheckTimer()
{UWorld* World = GetWorld();if (World){World->GetTimerManager().SetTimer(AimTargetCheckTimerHandle, this, &UGA_Shoot::FindAimTarget, AimTargetCheckTimeInterval, true);}
}void UGA_Shoot::StopAimTargetCheckTimer()
{UWorld* World = GetWorld();if (World){World->GetTimerManager().ClearTimer(AimTargetCheckTimerHandle);}
}bool UGA_Shoot::IsTargetInRange() const
{if (!AimTarget) return false;// 获取目标距离float Distance = FVector::Distance(GetAvatarActorFromActorInfo()->GetActorLocation(), AimTarget->GetActorLocation());return Distance <= ShootProjectileRange;
}void UGA_Shoot::TargetDeadTagUpdated(const FGameplayTag Tag, int32 NewCount)
{if (NewCount > 0){FindAimTarget();}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/pingmian/92515.shtml
繁体地址,请注明出处:http://hk.pswp.cn/pingmian/92515.shtml
英文地址,请注明出处:http://en.pswp.cn/pingmian/92515.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

解决渲染抖动与滚动锚点定位不准确问题的方法与经验分享

场景描述&#xff1a;React 虚拟列表&#xff08;Virtualized List&#xff09;是当我们在处理大列表时&#xff0c;为了提升性能而采用的一种技术。然而在实现过程中&#xff0c;可能会遇到渲染抖动问题以及滚动锚点定位不准确的问题。  解决方案&#xff1a;React虚拟列表实…

OpenAI 时隔多年再开源!GPT-OSS 120B/20B 发布,支持本地部署,消费级 GPU 即可运行

OpenAI 近期做出了一项令人瞩目的战略转变&#xff1a;宣布推出两款开放权重&#xff08;Open Weight&#xff09; 语言模型 GPT-OSS-120B 和 GPT-OSS-20B。这不仅是其自 GPT-2 之后首次开源模型&#xff0c;更关键的是&#xff0c;这两款模型特别针对消费级硬件进行了深度优化…

MySQL高可用方案之MySQL Group Replication高可用架构搭建完全指南

MySQL Group Replication高可用架构搭建完全指南 前言 在当今互联网应用中,数据库高可用性已成为系统设计的核心需求。MySQL作为最流行的开源关系型数据库之一,其高可用解决方案备受关注。MySQL Group Replication是MySQL官方推出的原生高可用解决方案,它基于Paxos协议实现…

网站SSL证书到期如何更换?简单完整操作指南

----------------------------------------------------------------------------------------------- 这是我在我的网站中截取的文章&#xff0c;有更多的文章欢迎来访问我自己的博客网站rn.berlinlian.cn&#xff0c;这里还有很多有关计算机的知识&#xff0c;欢迎进行留言或…

Spring Boot 开发三板斧:POM 依赖、注解与配置管理

引言 Spring Boot 是一个功能强大且广受欢迎的框架&#xff0c;用于快速构建基于 Spring 的应用。它通过简化配置和自动化管理&#xff0c;帮助开发者专注于业务逻辑的实现。然而&#xff0c;要想高效地开发 Spring Boot 应用&#xff0c;掌握以下三个关键点至关重要&#xff1…

kubernetes安装搭建

个人博客站—运维鹿:http://www.kervin24.top/ CSDN博客—做个超努力的小奚&#xff1a; https://blog.csdn.net/qq_52914969?typeblog 一、kubernetes介绍 Kubernetes本质是一组服务器集群&#xff0c;它可以在集群的每个节点上运行特定的程序&#xff0c;来对节点中的容…

MySQL高可用方案之MySQL InnoDB Cluster高可用架构实战指南:从零搭建到生产部署

MySQL InnoDB Cluster高可用架构实战指南:从零搭建到生产部署 一、引言:为什么选择MySQL InnoDB Cluster 在当今数据驱动的商业环境中,数据库高可用性已成为企业IT基础设施的核心需求。MySQL作为全球最受欢迎的开源关系型数据库,其高可用解决方案备受关注。而MySQL InnoD…

祝融号无线电工作频段

前面深入查证了旅行者1号的无线电工作频段&#xff1a; 旅行者1号无线电工作频段-CSDN博客 下面尝试查证我国祝融号无线电工作频段。 一、百度百科 来自百度百科&#xff1a; 我注意到一条关键信息&#xff1a; 这说明祝融号在国际上是有合作的&#xff0c;而不是我们国家单…

Kafka生产者相关原理

前言前面已经介绍了Kafka的架构知识并引出了Kafka的相关专业名称进行解释这次分享一下Kafka对生产者发送消息进行处理的运行机制和原理生产者发送消息两种方式同步发送消息程序中线程执行完消息发送操作之后会等待Kafka的消息回应ack默认等待30秒没有回应就会抛出异常等待时间和…

Python 获取对象信息的所有方法

在 Python 里&#xff0c;我们经常需要检查一个对象的类型、属性、方法&#xff0c;甚至它的源码。这对调试、学习和动态编程特别有用。今天我们就来聊聊获取对象信息的常见方法&#xff0c;按由浅入深的顺序来学习。 参考文章&#xff1a;Python 获取对象信息 | 简单一点学习…

vuhub Beelzebub靶场攻略

靶场下载&#xff1a; 下载地址&#xff1a;https://download.vulnhub.com/beelzebub/Beelzebub.zip 靶场攻略&#xff1a; 主机发现&#xff1a; nmap 192.168.163.1/24 端口扫描&#xff1a; nmap -p-65535 -A 192.168.163.152 发现没有额外端口。 页面扫描&#xff1…

开启单片机

前言&#xff1a;为未来拼搏的第n天&#xff0c;从单片机开始。为什么要学习单片机呢&#xff0c;单片机的工作涉及范围及其广如&#xff1a;消费电子&#xff0c;游戏机音响&#xff1b;工业控制&#xff1a;机器人控制&#xff1b;医疗设备&#xff0c;通信设备&#xff0c;物…

人工智能系列(8)如何实现无监督学习聚类(使用竞争学习)?

案例&#xff1a;鸢尾花数据集的聚类一.聚类简介神经网络能够从输入数据中自动提取有意义的特征&#xff0c;而竞争学习规则使得单层神经网络能够根据相似度将输入样本进行聚类&#xff0c;每个聚类由一个输出神经元代表并作为该类别的“原型”&#xff0c;从而实现对输入模式的…

Windows安装mamba全流程(全网最稳定最成功)

windows系统下安装mamba会遇到各种各样的问题。博主试了好几天&#xff0c;把能踩的坑都踩了&#xff0c;总结出了在windows下安装mamba的一套方法&#xff0c;已经给实验室的windows服务器都装上了。只要跟着我的流程走下来&#xff0c;大概率不会出问题&#xff0c;如果遇到其…

Autosar Dem配置-最大存储的DTC信息个数配置-基于ETAS软件

文章目录 前言 Autosar Dem相关配置 ETAS工具中的配置 生成文件分析 测试验证 总结 前言 诊断DTC开发中,会有故障快照和扩展数据的存储需求,但由于控制器的可用存储空间有限,所以无法存储所有DTC的信息,这时就需要限制存储的数量,本文介绍该参数在ETAS软件中的配置。 Au…

【MySQL】EXISTS 与 NOT EXISTS 深度解析:从原理到实战的完整指南

在复杂的业务查询中&#xff0c;我们常常需要判断“是否存在满足某条件的记录”或“找出不满足某些条件的记录”。这时&#xff0c;EXISTS 和 NOT EXISTS 子查询便成为强大的工具。它们不仅逻辑清晰、语义明确&#xff0c;而且在某些场景下性能远超 IN 或 JOIN。然而&#xff0…

面对信号在时频平面打结,VNCMD分割算法深度解密

“ 信号迷宫中的破壁者&#xff1a;VNCMD如何分解纠缠的时空密码&#xff1f;——从鲸歌到机械故障&#xff0c;宽带信号分解新纪元。”01—痛点直击&#xff1a;为什么传统方法集体失效&#xff1f;2017年&#xff0c;上海交大团队提出了一项突破性研究&#xff1a;变分非线性…

CSS优先级、HTTP响应状态码

CSS优先级 优先级&#xff1a;看CSS的来源、样式引入方式、选择器、源码顺序。 行内样式/内联样式&#xff1a;直接在HTML元素的style属性中编写CSS样式。这种方式适用于少量样式的情况&#xff0c;但不推荐在大规模开发中使用&#xff0c;因为它会使HTML文件变得冗长和难以维…

项目一系列-第2章 Git版本控制

第2章 Git版本控制 2.1 Git概述 Git是什么&#xff1f;Git是一个分布式版本控制工具&#xff0c;于管理开发过程中的文件。 Git有哪些作用&#xff1f; 远程备份&#xff1a;Git可以将本地代码备份到远程服务器&#xff0c;防止数据丢失。多人协作&#xff1a;Git运行多个开发者…

Java异常:认识异常、异常的作用、自定义异常

目录1.什么是异常&#xff1f;1&#xff09;运行时异常2&#xff09;编译时异常2.异常的作用1&#xff09;Java 异常在定位 BUG 中的核心作用2&#xff09;Java 异常作为方法内部特殊返回值的作用3&#xff09;自定义异常1.什么是异常&#xff1f; Error:代表的系统级别错误(属…