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类型,可以根据需要在项目设置碰撞设置中添加。
bTraceComplexTrue代表开启复杂碰撞检测(消耗性能),false代表简单碰撞。
ActorsToIgnore需要忽略的Actor对象
DrawDebugTypedebug类型,上图之所以会有射线显示就是因为这个参数。
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++实现近战攻击,精准检测与碰撞检测的主要内容,如果未能解决你的问题,请参考以下文章

UE4最简化碰撞检测

pygame.mask原理及使用pygame.mask实现精准碰撞检测

pygame.mask原理及使用pygame.mask实现精准碰撞检测

pygame.mask原理及使用pygame.mask实现精准碰撞检测

UE4 C++实现发出伤害与接收伤害

UE4 C++设置攻击间隔面向敌人攻击