UE4 C++实现近战攻击,精准检测与碰撞检测
Posted TanZq_
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了UE4 C++实现近战攻击,精准检测与碰撞检测相关的知识,希望对你有一定的参考价值。
效果展示
走进敌人范围会受到伤害,并触发角色受伤特效,学习,素材有限,所以就放了一个火。
敌人使用的是碰撞重叠+事件通知。
我方攻击采用的是精准识别打击,会触发敌人受伤特效(一个闪,图片中没有体现出来。。)并且弹出输出敌人名字。
绿方块代表检测到的敌人。
秉持着多学多益的思想,所以给大家介绍了两种攻击效果的C++实现。
C++代码实现
Weapon
.h文件
添加一个武器攻击的声音(记得编译之后给这个武器挑选声音。)
UPROPERTY(EditAnywhere, BlueprintReadWrite)
class USoundCue* AttackSound;
AttackTracer
然后创建一个继承AnimNotifyState
的C++文件,命名为:AttackTracer
(命名是随意的,为了之后好理解,这里就给一个参考名字!),有了这个文件就可以在动画蒙太奇中实现射线检测了。
.h文件
以下均为public
TArray<FHitResult> HitResults;
使用一个数组去保存武器的碰撞信息,碰撞是动态的,所以会产生很多碰撞信息,需要使用数组去保存。TArray<AEnemy*> HitEnemies;
两个好处:①避免重复检测到敌人产生多次碰撞特效②如果武器碰撞到了多个敌人,那么就会保存多个敌人的信息。class ARole* Player;
保存持有武器的玩家。class USkeletalMeshComponent* Weapon;
保存玩家武器。TArray<AActor*>ActorsToIgnore;
射线忽略的目标virtual void NotifyTick(USkeletalMeshComponent * MeshComp, UAnimSequenceBase * Animation, float FrameDeltaTime)override;
重写Tick函数,每一帧都要检测碰撞,射线检测就是在这个函数中实现的。virtual void NotifyEnd(USkeletalMeshComponent * MeshComp, UAnimSequenceBase * Animation)override;
收尾清空。virtual void NotifyBegin(USkeletalMeshComponent * MeshComp, UAnimSequenceBase * Animation, float TotalDuration)override;
开始,也就是初始化函数。
.cpp文件
- NotifyBegin
添加头文件:
#include "Role.h"
初始化操作,Cast是很消耗性能的,所以提前加载出来,减少性能消耗。
初始化武器,玩家自身不参与碰撞检测。
Player = Cast<ARole>(MeshComp->GetOwner());
if(Player)
{
Weapon = Player->GetWeapon()->Mesh;
ActorsToIgnore = { MeshComp->GetOwner() };
}
- NotifyTick
添加头文件:
#include "Components/SkeletalMeshComponent.h"
#include "Kismet/KismetSystemLibrary.h"
#include "Kismet/GameplayStatics.h"
#include "GameFramework/Controller.h"
#include "Enemy.h"
#include "Sound/SoundCue.h"
if (!Player) return;
UKismetSystemLibrary::LineTraceMulti(Player->GetWorld(), Weapon->GetSocketLocation("TraceBegin"), Weapon->GetSocketLocation("TraceEnd"), TraceTypeQuery3, false, ActorsToIgnore, EDrawDebugTrace::ForDuration, HitResults, true);
for (int i = 0; i < HitResults.Num(); i++)
{
//获取本次射线击中的敌人
AEnemy* HitEnemy = Cast<AEnemy>(HitResults[i].GetActor());
//查询数组中是否有本次击中的Actor,如果没有则加入数组,防止一次通知内多次击中的情况
if (HitEnemy && !HitEnemies.Contains(HitEnemy))
{
GEngine->AddOnScreenDebugMessage( 0, 1.f, FColor::Red, HitEnemy->GetName());
HitEnemies.Add(HitEnemy);
if (HitEnemy->InteractParticle) { //播放敌人受伤粒子特效
UGameplayStatics::SpawnEmitterAtLocation(HitEnemy, HitEnemy->InteractParticle, HitResults[i].Location);
}
if (HitEnemy->ReactSound) { // 播放敌人受伤音乐
UGameplayStatics::PlaySound2D(HitEnemy, HitEnemy->ReactSound);
}
}
}
LineTraceMulti
线性检测函数,之前在讲背包的时候用过LineTraceSingle
,他们两个的区别是什么呢?
LineTraceSingle
单一检测,检测到一个物品之后就不检测了,而LineTraceMulti
会检测多个物品,这也是碰撞信息需要用数组保存的原因。
参数 | 意义 |
---|---|
WorldContext | 获取世界内容 |
Start | 射线开始位置 |
End | 射线结束位置 |
TraceChannel | 射线通道,ETraceTypeQuery 类型,可以根据需要在项目设置碰撞设置中添加。 |
bTraceComplex | True代表开启复杂碰撞检测(消耗性能),false代表简单碰撞。 |
ActorsToIgnore | 需要忽略的Actor对象 |
DrawDebugType | debug类型,上图之所以会有射线显示就是因为这个参数。 |
OutHit | 跟踪命中的属性 |
bIgnoreSelf | 是否忽略自己。 |
返回值,如果为真则代表有阻塞,假则没有阻塞。
EDrawDebugTrace
中的类型:
None
不会显示检测射线。
ForOneFrame
每一帧都会显示显示,当调用了NotifyEnd
时,就会结束。
ForDuration
放出来之后会显示一段时间,就是我当前使用的。
Persistent
射线一直存在。
在这个项目中就需要自定义一个TraceTypeQuery,
这个为第三个,前两个是系统自带的,
打开到敌人的编辑框里面,按照上图设置即可。
- NotifyEnd
收尾,清空数组操作。
HitEnemies.Empty();
编译之后给武器添加上插槽:
给动画添加通知状态:
打开角色动画蒙太奇,通知区域:
在动画蒙太奇中可以添加通知,也可以直接点击动画进行添加,我之前认为直接在动画之中添加不好多了吗,还在动画蒙太奇中添加多此一举干啥,后面发现我错了,我感觉在动画蒙太奇中添加会更好一点,因为当多个动画蒙太奇使用了这个动画之后,他们的通知就都放到一起去了,不好区分和后期维护,做一个项目就应该要往以后好维护、好拓展的方向写。
在Role角色蒙太奇的事件图表中添加:
Role.h 第三人称角色
.h
- 播放音乐函数
UFUNCTION(BlueprintCallable)
void PlayAttackSound();
- 声明受伤粒子特效
UPROPERTY(EditAnywhere, BlueprintReadOnly)
class UParticleSystem* InteractParticle;
- 声明受伤声音
UPROPERTY(EditAnywhere, BlueprintReadWrite)
class USoundCue* ReactSound;
.cpp文件
- PlayAttackSound
if (EquiqedWeapon && EquiqedWeapon->AttackSound) {
UGameplayStatics::PlaySound2D(this, EquiqedWeapon->AttackSound);
}
播放音乐函数实现。
记得编译后添加资源。
Enemy内容添加
接下来讲第二种方法,使用碰撞检测来触发攻击特效。
在敌人的动画蒙太奇中添加通知:
.h文件
UPROPERTY(EditAnywhere, BlueprintReadOnly)
class UAnimMontage* _AnimMontage; // 声明动画蒙太奇
UPROPERTY(EditAnywhere, BlueprintReadOnly)
class UParticleSystem* InteractParticle; // 敌人受伤粒子特效
UPROPERTY(EditAnywhere, BlueprintReadWrite)
class USoundCue* ReactSound; // 受伤声音
UPROPERTY(VisibleAnywhere, BlueprintReadOnly)
class UBoxComponent* CombatBox; // 攻击检测盒体
// 盒体检测重叠函数,老套路了。
UFUNCTION()
void OnCombatBoxOverlapBegin(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor,
UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult);
UFUNCTION()
void OnCombatBoxOverlapEnd(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor,
UPrimitiveComponent* OtherComp, int32 OtherBodyIndex);
UPROPERTY(EditAnywhere, BlueprintReadWrite)
class USoundCue* AttackSound; // 攻击声音
UFUNCTION(BlueprintCallable)
void EnableCollision(); // 启用碰撞
UFUNCTION(BlueprintCallable)
void DisableCollision(); // 关闭碰撞
bool Attacking; // 是否处于攻击状态
UFUNCTION(BlueprintCallable)
void AttackBegin(); // 攻击开始状态
UFUNCTION(BlueprintCallable)
void AttackEnd(); // 攻击结束状态
.cpp 文件
- 构造函数
初始化操作,盒体是附加在剑上的,所以就放到插槽里面。
CombatBox = CreateDefaultSubobject<UBoxComponent>(TEXT("CombatBox"));
CombatBox->AttachToComponent(GetMesh(), FAttachmentTransformRules::SnapToTargetNotIncludingScale, "WEAPON_R");
AttachToComponent
有四种依附形式:
KeepRelativeTransform
(保持两个物体之间的相对位置不变)
KeepWorldTransform
(保持连个物体在世界的位置不变)
SnapToTargetNotIncludingScale
(保持物体的缩放对齐到目标上)
SnapToTargetIncludingScale
(随目标的缩放)
- BeginPlay
添加碰撞检测与函数绑定,设置碰撞为没有碰撞。
CombatBox->OnComponentBeginOverlap.AddDynamic(this, &AEnemy::OnCombatBoxOverlapBegin);
CombatBox->OnComponentEndOverlap.AddDynamic(this, &AEnemy::OnCombatBoxOverlapEnd);
CombatBox->SetCollisionEnabled(ECollisionEnabled::NoCollision);
- OnCombatBoxOverlapBegin
重叠开始的时候播放音乐、释放特效。
if (OtherActor) {
ARole* _Role = Cast<ARole>(OtherActor);
if (_Role) {
if (_Role->InteractParticle) {
UGameplayStatics::SpawnEmitterAtLocation(this, _Role->InteractParticle, GetMesh()->GetSocketLocation("WEAPON_R"));
}
if (_Role->ReactSound) {
UGameplayStatics::PlaySound2D(this, _Role->ReactSound);
}
}
}
- OnCombatBoxOverlapEnd
{}
- EnableCollision 启用碰撞:
CombatBox->SetCollisionEnabled(ECollisionEnabled::QueryOnly);
- DisableCollision 关闭碰撞:
CombatBox->SetCollisionEnabled(ECollisionEnabled::NoCollision);
之前是使用敌人枚举状态控制敌人移动,现在改为C++内部实现:
- OnAttackSphereOverlapBegin
if (!HittingRole) {
if (OtherActor) {
ARole* _Role = Cast<ARole>(OtherActor);
if (_Role) {
HittingRole = _Role;
MoveStatus = EMoveStatus::MS_Attacking;
//修改部分
AttackBegin();
if (_AIController) {
_AIController->StopMovement();
}
}
}
}
- AttackBegin
攻击开始函数,播放动画,播放声音
UAnimInstance* Instance = GetMesh()->GetAnimInstance();
if (_AnimMontage && Instance && !Instance->Montage_IsPlaying(_AnimMontage)) {
Attacking = true;
Instance->Montage_Play(_AnimMontage);
Instance->Montage_JumpToSection(FName("Attack01"), _AnimMontage);
}
if (AttackSound) {
UGameplayStatics::PlaySound2D(this, AttackSound);
}
- AttackEnd
和之前说的操作差不多。
Attacking = false;
if (HittingRole) {
MoveToTarget();
AttackBegin();
}
else if (TargetRole) {
MoveStatus = EMoveStatus::MS_MoveToTarget;
MoveToTarget();
}
else {
MoveStatus = EMoveStatus::MS_Idle;
}
敌人动画中添加:
流程就是:动画蒙太奇开启碰撞 - 开始碰撞检测 - 碰到角色做出反应 - 动画蒙太奇关闭碰撞 - 关闭碰撞检测。
结束!赶紧试试吧!
以上是关于UE4 C++实现近战攻击,精准检测与碰撞检测的主要内容,如果未能解决你的问题,请参考以下文章
pygame.mask原理及使用pygame.mask实现精准碰撞检测
pygame.mask原理及使用pygame.mask实现精准碰撞检测