伤害系统
给敌人创建血条
- 首先添加一个UI界面用来显示敌人血条
- 设置背景图像为黑色半透明
- 填充颜色
- 给敌人类添加两种状态表示血量与最大血量,添加一个
UWidegtComponet组件与UProgressBar组件
cpp
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Enemy Stats")
float Health;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Enemy Stats")
float MaxHealth;
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Enemy Stats")
class UWidgetComponent* HealthBarWidgetComponent;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Enemy Stats")
class UProgressBar* HealthBar;
MaxHealth = 100.f;
Health = MaxHealth;
设置WidgetComponent组件
- 头文件:
- #include "Components/WidgetComponent.h":使用WidgetComponent就要使用
- #include "Blueprint/UserWidget.h":获取到User内部对象时需要使用
- #include "Components/ProgressBar.h":获取调用ProgressBar需要使用
- 创建UWidgetComponent组件
cpp
HealthBarWidgetComponent = CreateDefaultSubobject<UWidgetComponent>(TEXT("HealthBarWidgetComponent"));
HealthBarWidgetComponent->SetupAttachment(GetRootComponent());
HealthBarWidgetComponent->SetWidgetSpace(EWidgetSpace::Screen);
HealthBarWidgetComponent->SetDrawSize(FVector2D(125.f, 10.f));
HealthBarWidgetComponent->SetWorldLocation(FVector(0.f, 0.f, 50.f));
- 获取到HealBar小组件
cpp
// Called when the game starts or when spawned
void ABaseEnemy::BeginPlay()
{
Super::BeginPlay();
ChaseVolume->OnComponentBeginOverlap.AddDynamic(this, &ABaseEnemy::OnChaseVolumeOverlapBegin);
ChaseVolume->OnComponentEndOverlap.AddDynamic(this, &ABaseEnemy::OnChaseVolumeOverlapEnd);
AttackVolume->OnComponentBeginOverlap.AddDynamic(this, &ABaseEnemy::OnAttackVolumeOverlapBegin);
AttackVolume->OnComponentEndOverlap.AddDynamic(this, &ABaseEnemy::OnAttackVolumeOverlapEnd);
//获取到HealBar小组件
HealthBar = Cast<UProgressBar>(HealthBarWidgetComponent->GetUserWidgetObject()->GetWidgetFromName("HealthBar"));
HealthBar->SetPercent(Health / MaxHealth);
//拿到Controller
AIController = Cast<AAIController>(GetController());
}
- 运行结果
血条进入敌人追逐区显示
- 一开始敌人血条是不显示的,只有主角进入了敌人追逐区才开始显示血条,出了追逐区也不显示
cpp
//获取到HealBar小组件
HealthBar = Cast<UProgressBar>(HealthBarWidgetComponent->GetUserWidgetObject()->GetWidgetFromName("HealthBar"));
HealthBar->SetPercent(Health / MaxHealth);
HealthBar->SetVisibility(ESlateVisibility::Hidden);//一开始不显示血条
void ABaseEnemy::OnChaseVolumeOverlapBegin(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
if (OtherActor)
{
AMainPlayer* Player = Cast<AMainPlayer>(OtherActor);
if (Player)
{
//主角进入追逐范围显示血条
HealthBar->SetVisibility(ESlateVisibility::Visible);
MoveToTarget(Player);
}
}
}
void ABaseEnemy::OnChaseVolumeOverlapEnd(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
{
if (OtherActor)
{
AMainPlayer* Player = Cast<AMainPlayer>(OtherActor);
if (Player)
{
if (AIController)
{
//主角出追逐范围不显示血条
HealthBar->SetVisibility(ESlateVisibility::Hidden);
//停止移动
AIController->StopMovement();
}
}
}
}
触发伤害思路
- 我们可以在武器上加一个碰撞器,当这个碰撞器碰撞到敌人的时候就开启伤害,如果没有就躲避的伤害
- 给每把剑的骨骼添加一个Socket
触发伤害需求
- 因为要使用UE中内置的直接伤害附加的功能,类似爆炸物那一节,所以我们要在武器类中新建一个盒子碰撞触发器,新建一个伤害值,新建一个伤害类型,新建一个伤害发起者
cpp
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Weapon|Attack")
class UBoxComponent* AttackCollision;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Weapon|Attack")
float Damage;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Weapon|Attack")
TSubclassOf<UDamageType> DamageTyClass;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Weapon|Attack")
class AController* WeaponOwner;
- 新建伤害开始触发事件与结束事件,两个动态切换碰撞函数,我只需要在挥剑的那个瞬间去切换需要在动画的notify中去切换的所以需要添加反射
cpp
UFUNCTION()
void OnAttackCollisionOverlapBegin(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult);
UFUNCTION()
void OnAttackCollisionOverlapEnd(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex);
//动态切换碰撞
UFUNCTION(BlueprintCallable)
void ActiveAttackCollision();
UFUNCTION(BlueprintCallable)
void DeactiveAttackCollision();
- 初始化,创建BoxComponent需要头文件:#include "Components/BoxComponent.h"
cpp
AttackCollision = CreateDefaultSubobject<UBoxComponent>(TEXT("AttackCollision"));
AttackCollision->SetupAttachment(DisplayMesh, "WeaponSocket");//将这个碰撞点附加到武器插槽上
DeactiveAttackCollision();//关闭碰撞
Damage = 25.f;
//绑定
void AWeaponItem::BeginPlay()
{
Super::BeginPlay();
AttackCollision->OnComponentBeginOverlap.AddDynamic(this, &AWeaponItem::OnAttackCollisionOverlapBegin);
AttackCollision->OnComponentEndOverlap.AddDynamic(this, &AWeaponItem::OnAttackCollisionOverlapEnd);
}
void AWeaponItem::OnAttackCollisionOverlapBegin(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
}
void AWeaponItem::OnAttackCollisionOverlapEnd(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
{
}
//设置碰撞类型
void AWeaponItem::ActiveAttackCollision()
{
AttackCollision->SetCollisionEnabled(ECollisionEnabled::QueryOnly);
AttackCollision->SetCollisionObjectType(ECollisionChannel::ECC_WorldDynamic);
AttackCollision->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Ignore);
AttackCollision->SetCollisionResponseToChannel(ECollisionChannel::ECC_Pawn, ECollisionResponse::ECR_Overlap);
}
void AWeaponItem::DeactiveAttackCollision()
{
AttackCollision->SetCollisionEnabled(ECollisionEnabled::NoCollision);
}
WeaponItem.cpp
cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "WeaponItem.h"
#include "Components/SkeletalMeshComponent.h"
#include "Characters/Player/MainPlayer.h"
#include "Engine/SkeletalMeshSocket.h"
#include "Kismet/GameplayStatics.h"
#include "Sound/SoundCue.h"
#include "Particles/ParticleSystemComponent.h"
#include "UObject/ConstructorHelpers.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "Components/BoxComponent.h"
AWeaponItem::AWeaponItem()
{
//销毁
if (DisplayMesh)
{
DisplayMesh->DestroyComponent();
//UE_LOG(LogTemp, Warning, TEXT("delete succeed"));
}
else
{
//UE_LOG(LogTemp, Warning, TEXT("fail to delete"));
}
//因为TEXT具有唯一性,我们不知道什么时候销毁原UStaticMeshComponent* DisplayMesh;所以这里TEXT进行一下区分
DisplayMesh=CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("DisplaySkeletalMesh"));
DisplayMesh->SetupAttachment(GetRootComponent());
ActiveDisplayMeshCollision();//设置碰撞
AttackCollision = CreateDefaultSubobject<UBoxComponent>(TEXT("AttackCollision"));
AttackCollision->SetupAttachment(DisplayMesh, "WeaponSocket");//将这个碰撞点附加到武器插槽上
DeactiveAttackCollision();
static ConstructorHelpers::FObjectFinder<USoundCue> SoundCueAsset(TEXT("SoundCue'/Game/Assets/Audios/Blade_Cue.Blade_Cue'"));
if (SoundCueAsset.Succeeded())
{
OnEquipSound = SoundCueAsset.Object;
}
//拾取武器后粒子效果默认关闭
bOnEquipParticle = false;
//默认状态武器是可拾取的
WeaponState = EWeaponState::EWS_CanPickUp;
Damage = 25.f;
}
void AWeaponItem::BeginPlay()
{
Super::BeginPlay();
AttackCollision->OnComponentBeginOverlap.AddDynamic(this, &AWeaponItem::OnAttackCollisionOverlapBegin);
AttackCollision->OnComponentEndOverlap.AddDynamic(this, &AWeaponItem::OnAttackCollisionOverlapEnd);
}
void AWeaponItem::OnOverlapBegin(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
Super::OnOverlapBegin(OverlappedComponent, OtherActor, OtherComp, OtherBodyIndex, bFromSweep, SweepResult);
if (OtherActor && WeaponState == EWeaponState::EWS_CanPickUp)
{
AMainPlayer* Player = Cast<AMainPlayer>(OtherActor);
if (Player)
{
//告诉角色正在重叠的武器是当前武器
Player->OverlapWeapon = this;
}
}
}
void AWeaponItem::OnOverlapEnd(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
{
Super::OnOverlapEnd(OverlappedComponent, OtherActor, OtherComp, OtherBodyIndex);
if (OtherActor)
{
AMainPlayer* Player = Cast<AMainPlayer>(OtherActor);
//判断一开始是否拾起的武器是当前武器
if (Player && Player->OverlapWeapon == this)
{
//告诉角色离开了武器触发器
Player->OverlapWeapon = nullptr;
}
}
}
void AWeaponItem::Equip(AMainPlayer* Player)
{
if (Player && !Player->GetMovementComponent()->IsFalling())
{
//已装备武器
WeaponState = EWeaponState::EWS_Equip;
DeactiveDisplayMeshCollision();//关闭碰撞
//获取Player的Socket
const USkeletalMeshSocket* RightHandSocker = Player->GetMesh()->GetSocketByName(TEXT("RightHandSocket"));
if (RightHandSocker)
{
//让武器附属到Socket上
RightHandSocker->AttachActor(this, Player->GetMesh());
Player->bIsWeapon = true;
Player->EquipWeapon = this;
Player->OverlapWeapon = nullptr;
bRotate = false;//武器旋转关闭
if (OnEquipSound)
{
//播放音乐
UGameplayStatics::PlaySound2D(this, OnEquipSound);
}
//if (!bOnEquipParticle)
//{
// //关闭粒子组件
// ParticleEffectsComponent->Deactivate();
//
//}
}
}
}
void AWeaponItem::UnEuip(AMainPlayer* Player)
{
if (Player && !Player->GetMovementComponent()->IsFalling() && !Player->bIsAttacking)
{
WeaponState = EWeaponState::EWS_CanPickUp;
ActiveDisplayMeshCollision();//开启碰撞
Player->bIsWeapon = false;
Player->EquipWeapon = nullptr;
if (Player->OverlapWeapon == nullptr)
{
Player->OverlapWeapon = this;
}
//分离当前WeaponItem Socket
DetachFromActor(FDetachmentTransformRules::KeepWorldTransform);
SetActorRotation(FRotator(0.f));
SetActorScale3D(FVector(1.f));
bRotate = true;
}
}
void AWeaponItem::ActiveDisplayMeshCollision()
{
DisplayMesh->SetCollisionEnabled(ECollisionEnabled::PhysicsOnly);
DisplayMesh->SetCollisionObjectType(ECollisionChannel::ECC_WorldStatic);
DisplayMesh->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Ignore);
DisplayMesh->SetCollisionResponseToChannel(ECollisionChannel::ECC_Pawn, ECollisionResponse::ECR_Block);
}
void AWeaponItem::DeactiveDisplayMeshCollision()
{
DisplayMesh->SetCollisionEnabled(ECollisionEnabled::NoCollision);
}
void AWeaponItem::OnAttackCollisionOverlapBegin(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
}
void AWeaponItem::OnAttackCollisionOverlapEnd(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
{
}
void AWeaponItem::ActiveAttackCollision()
{
AttackCollision->SetCollisionEnabled(ECollisionEnabled::QueryOnly);
AttackCollision->SetCollisionObjectType(ECollisionChannel::ECC_WorldDynamic);
AttackCollision->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Ignore);
AttackCollision->SetCollisionResponseToChannel(ECollisionChannel::ECC_Pawn, ECollisionResponse::ECR_Overlap);
}
void AWeaponItem::DeactiveAttackCollision()
{
AttackCollision->SetCollisionEnabled(ECollisionEnabled::NoCollision);
}
在蒙太奇中添加攻击通知激活伤害碰撞
- 在主角的蒙太奇中添加四个通知,跟当时添加攻击结束通知差不多
- 然后在动画蓝图中进行调用函数
- 将三把剑的盒子碰撞检测大小调整一下
给敌人添加触发器
- 基本与上面给主角添加伤害需求的思路一致
- 先给敌人添加两个骨骼
触发伤害需求
- 再来就是编辑敌人类的变量需求与基本逻辑了,两个触发盒子因为是两只腿的攻击,新建伤害值,伤害类型
cpp
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Attack")
class UBoxComponent* LeftAttackCollision;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Attack")
UBoxComponent* RightAttackCollision;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Attack")
float Damage;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Attack")
TSubclassOf<UDamageType> DamageTyClass;
- 不用说,现在是四个伤害触发事件的新建,四个动态切换碰撞的函数
cpp
UFUNCTION()
void OnLeftAttackCollisionOverlapBegin(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult);
UFUNCTION()
void OnLeftAttackCollisionOverlapEnd(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex);
UFUNCTION()
void OnRightAttackCollisionOverlapBegin(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult);
UFUNCTION()
void OnRightAttackCollisionOverlapEnd(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex);
//动态切换碰撞
UFUNCTION(BlueprintCallable)
void ActiveLeftAttackCollision();
UFUNCTION(BlueprintCallable)
void DeactiveLeftAttackCollision();
UFUNCTION(BlueprintCallable)
void ActiveRightAttackCollision();
UFUNCTION(BlueprintCallable)
void DeactiveRightAttackCollision();
- 现在就可以开始编写,初始化了
cpp
LeftAttackCollision = CreateDefaultSubobject<UBoxComponent>(TEXT("LeftAttackCollision"));
LeftAttackCollision->SetupAttachment(GetMesh(), "LeftAttackSocket");
DeactiveLeftAttackCollision();
RightAttackCollision = CreateDefaultSubobject<UBoxComponent>(TEXT("RightAttackCollision"));
RightAttackCollision->SetupAttachment(GetMesh(), "RightAttackSocket");
DeactiveRightAttackCollision();
//赋值
Damage = 10.f;
//绑定
LeftAttackCollision->OnComponentBeginOverlap.AddDynamic(this, &ABaseEnemy::OnLeftAttackCollisionOverlapBegin);
LeftAttackCollision->OnComponentEndOverlap.AddDynamic(this, &ABaseEnemy::OnLeftAttackCollisionOverlapEnd);
RightAttackCollision->OnComponentBeginOverlap.AddDynamic(this, &ABaseEnemy::OnRightAttackCollisionOverlapBegin);
RightAttackCollision->OnComponentEndOverlap.AddDynamic(this, &ABaseEnemy::OnRightAttackCollisionOverlapEnd);
void ABaseEnemy::ActiveLeftAttackCollision()
{
LeftAttackCollision->SetCollisionEnabled(ECollisionEnabled::QueryOnly);
LeftAttackCollision->SetCollisionObjectType(ECollisionChannel::ECC_WorldDynamic);
LeftAttackCollision->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Ignore);
LeftAttackCollision->SetCollisionResponseToChannel(ECollisionChannel::ECC_Pawn, ECollisionResponse::ECR_Overlap);
}
void ABaseEnemy::DeactiveLeftAttackCollision()
{
LeftAttackCollision->SetCollisionEnabled(ECollisionEnabled::NoCollision);
}
void ABaseEnemy::ActiveRightAttackCollision()
{
RightAttackCollision->SetCollisionEnabled(ECollisionEnabled::QueryOnly);
RightAttackCollision->SetCollisionObjectType(ECollisionChannel::ECC_WorldDynamic);
RightAttackCollision->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Ignore);
RightAttackCollision->SetCollisionResponseToChannel(ECollisionChannel::ECC_Pawn, ECollisionResponse::ECR_Overlap);
}
void ABaseEnemy::DeactiveRightAttackCollision()
{
RightAttackCollision->SetCollisionEnabled(ECollisionEnabled::NoCollision);
}
BaseEnemy.cpp
cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "BaseEnemy.h"
#include "Components/SphereComponent.h"
#include "Components/SkeletalMeshComponent.h"
#include "Components/CapsuleComponent.h"
#include "AIController.h"
#include "Characters/Player/MainPlayer.h"
#include "Animation/AnimInstance.h"
#include "Kismet/KismetMathLibrary.h"
#include "Kismet/GameplayStatics.h"
#include "Components/WidgetComponent.h"
#include "Blueprint/UserWidget.h"
#include "Components/ProgressBar.h"
#include "Components/BoxComponent.h"
// Sets default values
ABaseEnemy::ABaseEnemy()
{
// Set this character to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;
ChaseVolume = CreateDefaultSubobject<USphereComponent>(TEXT("ChaseVolume"));
ChaseVolume->SetupAttachment(GetRootComponent());
ChaseVolume->InitSphereRadius(800.f);
ChaseVolume->SetCollisionObjectType(ECollisionChannel::ECC_WorldDynamic);
ChaseVolume->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Ignore);
ChaseVolume->SetCollisionResponseToChannel(ECollisionChannel::ECC_Pawn, ECollisionResponse::ECR_Overlap);
AttackVolume = CreateDefaultSubobject<USphereComponent>(TEXT("AttackVolume"));
AttackVolume->SetupAttachment(GetRootComponent());
AttackVolume->InitSphereRadius(100.f);
AttackVolume->SetCollisionObjectType(ECollisionChannel::ECC_WorldDynamic);
AttackVolume->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Ignore);
AttackVolume->SetCollisionResponseToChannel(ECollisionChannel::ECC_Pawn, ECollisionResponse::ECR_Overlap);
HealthBarWidgetComponent = CreateDefaultSubobject<UWidgetComponent>(TEXT("HealthBarWidgetComponent"));
HealthBarWidgetComponent->SetupAttachment(GetRootComponent());
HealthBarWidgetComponent->SetWidgetSpace(EWidgetSpace::Screen);
HealthBarWidgetComponent->SetDrawSize(FVector2D(125.f, 10.f));
HealthBarWidgetComponent->SetWorldLocation(FVector(0.f, 0.f, 50.f));
LeftAttackCollision = CreateDefaultSubobject<UBoxComponent>(TEXT("LeftAttackCollision"));
LeftAttackCollision->SetupAttachment(GetMesh(), "LeftAttackSocket");
DeactiveLeftAttackCollision();
RightAttackCollision = CreateDefaultSubobject<UBoxComponent>(TEXT("RightAttackCollision"));
RightAttackCollision->SetupAttachment(GetMesh(), "RightAttackSocket");
DeactiveRightAttackCollision();
//避免摄像机被敌人给阻挡
GetMesh()->SetCollisionResponseToChannel(ECollisionChannel::ECC_Camera, ECollisionResponse::ECR_Ignore);
GetCapsuleComponent()->SetCollisionResponseToChannel(ECollisionChannel::ECC_Camera, ECollisionResponse::ECR_Ignore);
//设置持有属性
AutoPossessAI = EAutoPossessAI::PlacedInWorldOrSpawned;
//初始化默认移动状态
EnemyMovementStatus = EEnemyMovementStatus::EEMS_Idle;
InterpSpeed = 15.f;
bInterpToPlayer = false;
MaxHealth = 100.f;
Health = MaxHealth;
Damage = 10.f;
}
// Called when the game starts or when spawned
void ABaseEnemy::BeginPlay()
{
Super::BeginPlay();
ChaseVolume->OnComponentBeginOverlap.AddDynamic(this, &ABaseEnemy::OnChaseVolumeOverlapBegin);
ChaseVolume->OnComponentEndOverlap.AddDynamic(this, &ABaseEnemy::OnChaseVolumeOverlapEnd);
AttackVolume->OnComponentBeginOverlap.AddDynamic(this, &ABaseEnemy::OnAttackVolumeOverlapBegin);
AttackVolume->OnComponentEndOverlap.AddDynamic(this, &ABaseEnemy::OnAttackVolumeOverlapEnd);
LeftAttackCollision->OnComponentBeginOverlap.AddDynamic(this, &ABaseEnemy::OnLeftAttackCollisionOverlapBegin);
LeftAttackCollision->OnComponentEndOverlap.AddDynamic(this, &ABaseEnemy::OnLeftAttackCollisionOverlapEnd);
RightAttackCollision->OnComponentBeginOverlap.AddDynamic(this, &ABaseEnemy::OnRightAttackCollisionOverlapBegin);
RightAttackCollision->OnComponentEndOverlap.AddDynamic(this, &ABaseEnemy::OnRightAttackCollisionOverlapEnd);
//获取到HealBar小组件
HealthBar = Cast<UProgressBar>(HealthBarWidgetComponent->GetUserWidgetObject()->GetWidgetFromName("HealthBar"));
HealthBar->SetPercent(Health / MaxHealth);
HealthBar->SetVisibility(ESlateVisibility::Hidden);//一开始不显示血条
//拿到Controller
AIController = Cast<AAIController>(GetController());
}
// Called every frame
void ABaseEnemy::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
if (bInterpToPlayer)
{
FRotator LookYaw(0.f, UKismetMathLibrary::FindLookAtRotation(GetActorLocation(), UGameplayStatics::GetPlayerPawn(this, 0)->GetActorLocation()).Yaw, 0.f);
FRotator InterpRotation = FMath::RInterpTo(GetActorRotation(), LookYaw, DeltaTime, InterpSpeed);
SetActorRotation(InterpRotation);
}
}
// Called to bind functionality to input
void ABaseEnemy::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);
}
void ABaseEnemy::OnChaseVolumeOverlapBegin(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
if (OtherActor)
{
AMainPlayer* Player = Cast<AMainPlayer>(OtherActor);
if (Player)
{
//主角进入追逐范围显示血条
HealthBar->SetVisibility(ESlateVisibility::Visible);
MoveToTarget(Player);
}
}
}
void ABaseEnemy::OnChaseVolumeOverlapEnd(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
{
if (OtherActor)
{
AMainPlayer* Player = Cast<AMainPlayer>(OtherActor);
if (Player)
{
if (AIController)
{
//主角出追逐范围不显示血条
HealthBar->SetVisibility(ESlateVisibility::Hidden);
//停止移动
AIController->StopMovement();
}
}
}
}
void ABaseEnemy::OnAttackVolumeOverlapBegin(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
if (OtherActor)
{
AMainPlayer* Player = Cast<AMainPlayer>(OtherActor);
if (Player)
{
Player->UpdataAttackTarget();
bAttackVolumeOverlap = true;
AttackBegin();
}
}
}
void ABaseEnemy::OnAttackVolumeOverlapEnd(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
{
if (OtherActor)
{
AMainPlayer* Player = Cast<AMainPlayer>(OtherActor);
if (Player)
{
bAttackVolumeOverlap = false;
if (EnemyMovementStatus!=EEnemyMovementStatus::EEMS_Attacking)
{
MoveToTarget(Player);
}
}
}
}
void ABaseEnemy::MoveToTarget(AMainPlayer* Player)
{
EnemyMovementStatus = EEnemyMovementStatus::EEMS_MoveToTarget;
if (AIController)
{
FAIMoveRequest MoveRequest;
MoveRequest.SetGoalActor(Player);//设置移动请求目标
MoveRequest.SetAcceptanceRadius(10.f); //设置移动半径
FNavPathSharedPtr NavPath;//会返回路径
AIController->MoveTo(MoveRequest, &NavPath);
}
}
void ABaseEnemy::AttackBegin()
{
//攻击中关闭移动
if (AIController)
{
AIController->StopMovement();
}
if (EnemyMovementStatus != EEnemyMovementStatus::EEMS_Attacking)
{
EnemyMovementStatus = EEnemyMovementStatus::EEMS_Attacking;
bInterpToPlayer = true;
UAnimInstance* AnimInstance = GetMesh()->GetAnimInstance();
if (AnimInstance && AttackMontage)
{
float PlayRate = FMath::RandRange(0.9f, 1.1f);
FString SectionName = FString::FromInt(FMath::RandRange(1, 3));
AnimInstance->Montage_Play(AttackMontage, PlayRate);
AnimInstance->Montage_JumpToSection(FName(*SectionName), AttackMontage);
}
}
}
void ABaseEnemy::AttackEnd()
{
EnemyMovementStatus = EEnemyMovementStatus::EEMS_Idle;
bInterpToPlayer = false;
if (bAttackVolumeOverlap)
{
AttackBegin();
}
}
void ABaseEnemy::OnLeftAttackCollisionOverlapBegin(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
}
void ABaseEnemy::OnLeftAttackCollisionOverlapEnd(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
{
}
void ABaseEnemy::OnRightAttackCollisionOverlapBegin(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
}
void ABaseEnemy::OnRightAttackCollisionOverlapEnd(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
{
}
void ABaseEnemy::ActiveLeftAttackCollision()
{
LeftAttackCollision->SetCollisionEnabled(ECollisionEnabled::QueryOnly);
LeftAttackCollision->SetCollisionObjectType(ECollisionChannel::ECC_WorldDynamic);
LeftAttackCollision->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Ignore);
LeftAttackCollision->SetCollisionResponseToChannel(ECollisionChannel::ECC_Pawn, ECollisionResponse::ECR_Overlap);
}
void ABaseEnemy::DeactiveLeftAttackCollision()
{
LeftAttackCollision->SetCollisionEnabled(ECollisionEnabled::NoCollision);
}
void ABaseEnemy::ActiveRightAttackCollision()
{
RightAttackCollision->SetCollisionEnabled(ECollisionEnabled::QueryOnly);
RightAttackCollision->SetCollisionObjectType(ECollisionChannel::ECC_WorldDynamic);
RightAttackCollision->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Ignore);
RightAttackCollision->SetCollisionResponseToChannel(ECollisionChannel::ECC_Pawn, ECollisionResponse::ECR_Overlap);
}
void ABaseEnemy::DeactiveRightAttackCollision()
{
RightAttackCollision->SetCollisionEnabled(ECollisionEnabled::NoCollision);
}
在蒙太奇中添加攻击通知激活伤害碰撞
- 首先修改攻击触发器的大小
- 在敌人的蒙太奇中添加通知,跟上面主角添加通知差不多
- 编辑动画蓝图即可
设置击中敌人时的特效、音效与伤害
- 给MainPlayer.h与BaseEnemy.h中添加一个粒子系统与声音组件
cpp
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Hit Effect")
class UParticleSystem* HitPaticles;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Hit Effect")
class USoundCue* HitSound;
- 在WeaponItem.cpp的攻击开始重叠事件中去添加声音特效与伤害,这个就和之前装备武器与爆炸物传递伤害逻辑差不多
- 正好复习一下,获取插槽的时候必须加const,因为获取插槽的函数返回的是一个const值
cpp
void AWeaponItem::OnAttackCollisionOverlapBegin(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
if (OtherActor)
{
ABaseEnemy* BaseEnemy = Cast<ABaseEnemy>(OtherActor);
if (BaseEnemy)
{
if (BaseEnemy->HitPaticles)
{
//获取WeaponSocket插槽
const USkeletalMeshSocket* WeaponScoket = ((USkeletalMeshComponent*)DisplayMesh)->GetSocketByName("WeaponSocket");
if (WeaponScoket)
{
FVector SocketLocation = WeaponScoket->GetSocketLocation((USkeletalMeshComponent*)DisplayMesh);
UGameplayStatics::SpawnEmitterAtLocation(this, BaseEnemy->HitPaticles, SocketLocation, FRotator(0.f), true);
}
if (BaseEnemy->HitSound)
{
UGameplayStatics::PlaySound2D(this, BaseEnemy->HitSound);
}
if (DamageTyClass)
{
UGameplayStatics::ApplyDamage(BaseEnemy, Damage, WeaponOwner, this, DamageTyClass);
}
}
}
}
}
- 将攻击类型添加到每把剑上,人物添加上粒子与音效
设置击中玩家时的特效、音效与伤害
- 基本与上面一样,在BaseEnemy.cpp的左右攻击事件中添加粒子音效与伤害
cpp
void ABaseEnemy::OnLeftAttackCollisionOverlapBegin(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
if (OtherActor)
{
AMainPlayer* Player = Cast<AMainPlayer>(OtherActor);
if (Player)
{
if (Player->HitPaticles)
{
const USkeletalMeshSocket* AttackScoket = GetMesh()->GetSocketByName("LeftAttackSocket");
if (AttackScoket)
{
FVector SocketLocation = AttackScoket->GetSocketLocation(GetMesh());
UGameplayStatics::SpawnEmitterAtLocation(this, Player->HitPaticles, SocketLocation, FRotator(0.f), true);
}
if (Player->HitSound)
{
UGameplayStatics::PlaySound2D(this, Player->HitSound);
}
if (DamageTyClass)
{
UGameplayStatics::ApplyDamage(Player, Damage, AIController, this, DamageTyClass);
}
}
}
}
}
void ABaseEnemy::OnLeftAttackCollisionOverlapEnd(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
{
}
void ABaseEnemy::OnRightAttackCollisionOverlapBegin(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
if (OtherActor)
{
AMainPlayer* Player = Cast<AMainPlayer>(OtherActor);
if (Player)
{
if (Player->HitPaticles)
{
const USkeletalMeshSocket* AttackScoket = GetMesh()->GetSocketByName("RightAttackSocket");
if (AttackScoket)
{
FVector SocketLocation = AttackScoket->GetSocketLocation(GetMesh());
UGameplayStatics::SpawnEmitterAtLocation(this, Player->HitPaticles, SocketLocation, FRotator(0.f), true);
}
if (Player->HitSound)
{
UGameplayStatics::PlaySound2D(this, Player->HitSound);
}
if (DamageTyClass)
{
UGameplayStatics::ApplyDamage(Player, Damage, AIController, this, DamageTyClass);
}
}
}
}
}
- 将攻击类型添加到敌人上,添加上粒子与音效
敌人受伤死亡分析
- 敌人与主角类中添加死亡函数
- 在敌人类中重写TakeDamage方法接收伤害,注意的是因为MainPlayer的血量UI是写在UI绑定事件蓝图里面在,而敌人血量UI是在C++中编码跟随,所以最后返回写完逻辑返回血量前,要更新一下血条
cpp
//重写TakeDamage方法
float TakeDamage(float Damage, struct FDamageEvent const& DamageEvent, AController* EventInstigator, AActor* DamageCauser) override;
void Die();
- 伤害逻辑
cpp
float ABaseEnemy::TakeDamage(float Damage, FDamageEvent const& DamageEvent, AController* EventInstigator, AActor* DamageCauser)
{
if (Health - Damage <= 0.f)
{
Health = FMath::Clamp(Health - Damage, 0.f, MaxHealth);
Die();
}
else
{
Health -= Damage;
}
HealthBar->SetPercent(Health / MaxHealth);//更新UI血条
return Health;
}
按布尔类型播放死亡动画
- 思路:我们还是可以使用notify来通知调用死亡动画,首先在MainPlayer与BaseEnemy中新建一个用于信息通知的函数
cpp
UFUNCTION(BlueprintCallable)
void DeathEnd();
- 然后编辑MainPlayer死亡函数的逻辑,首先主角状态设置为死亡,判断是否持剑,持剑就关闭武器所有碰撞
cpp
void AMainPlayer::Die()
{
SetMovementStatus(EPlayerMovementStatus::EPMS_Dead);
if (EquipWeapon)
{
EquipWeapon->DeactiveAttackCollision();
EquipWeapon->DeactiveDisplayMeshCollision();
}
}
- 编辑BaseEnemy死亡函数逻辑,首先设置状态为死亡 ,关闭所有的碰撞,因为敌人已死亡更新主角攻击目标
cpp
void ABaseEnemy::Die()
{
EnemyMovementStatus = EEnemyMovementStatus::EEMS_Dead;
DeactiveLeftAttackCollision();
DeactiveRightAttackCollision();
ChaseVolume->SetCollisionEnabled(ECollisionEnabled::NoCollision);
AttackVolume->SetCollisionEnabled(ECollisionEnabled::NoCollision);
GetCapsuleComponent()->SetCollisionEnabled(ECollisionEnabled::NoCollision);
//敌人死亡,主角要更新攻击目标
Cast<AMainPlayer>(UGameplayStatics::GetPlayerPawn(this, 0))->UpdataAttackTarget();
}
- 编辑他们的动画蓝图,添加Blend Poses by bool播放死亡动画,Enemy状态为死亡时播放死亡动画,否则就正常播放其他动画,记得将死亡的循环播放动画关闭
限制玩家死亡后的一切活动状态
- 思路:我们在MainPlayer与BaseEnemy中新建一个专门用来判断玩家是否存活状态的函数,如果玩家不存活就停止一切活动
cpp
FORCEINLINE bool IsAlive() { return MovementStatus != EPlayerMovementStatus::EPMS_Dead; }
- 敌人的状态中还得检测一下玩家是否死亡
cpp
FORCEINLINE bool IsAlive() { return EnemyMovementStatus != EEnemyMovementStatus::EEMS_Dead; }
bool HasValidTarget();
//------------------------------------------------------------------------------------------------
bool ABaseEnemy::HasValidTarget()
{
return Cast<AMainPlayer>(UGameplayStatics::GetPlayerPawn(this, 0))->MovementStatus != EPlayerMovementStatus::EPMS_Dead;
}
MainPlayer.cpp
- 然后在MainPlayer中添加判断禁止死亡后的一切活动,也就是都加上IsAlive判断
cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "MainPlayer.h"
#include "GameFramework/SpringArmComponent.h"
#include "Camera/CameraComponent.h"
#include "Components/CapsuleComponent.h"
#include "Components/InputComponent.h"
#include "GameFramework/PlayerController.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "GamePlay/WeaponItem.h"
#include "Animation/AnimInstance.h"
#include "Characters/Enemy/BaseEnemy.h"
#include "Kismet/KismetMathLibrary.h"
// Sets default values
AMainPlayer::AMainPlayer()
{
// Set this character to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;
SpringArm = CreateDefaultSubobject<USpringArmComponent>(TEXT("SpringArm"));
SpringArm->SetupAttachment(GetRootComponent());
//设置SPringArm无碰撞臂长
SpringArm->TargetArmLength = 600.f;
SpringArm->bUsePawnControlRotation = true;//硬编码SpringArm继承controlller旋转为真
FollowCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("FollowCamera"));
FollowCamera->SetupAttachment(SpringArm, NAME_None);
FollowCamera->bUsePawnControlRotation = false;//硬编码FollowCamera继承controlller旋转为假
//设置胶囊体的默认宽高
GetCapsuleComponent()->SetCapsuleSize(35.f, 100.f);
//对Character的Pawn进行硬编码
bUseControllerRotationPitch = false;
bUseControllerRotationYaw = false;
bUseControllerRotationRoll = false;
//硬编码orient Rotation to Movement,给个默认转向速率
GetCharacterMovement()->bOrientRotationToMovement = true;
GetCharacterMovement()->RotationRate = FRotator(0.f, 500.f, 0.f);
//设置跳跃初始值与在空中的坠落时横向运动控制量
GetCharacterMovement()->JumpZVelocity = 400.f;
GetCharacterMovement()->AirControl = 0.15f;
//给键盘控制转向的速率变量赋初值
BaseTurnRate = 21.f;
BaseLookUpRate = 21.f;
//初始化角色状态
MaxHealth = 100.f;
Health = MaxHealth;
MaxStamina = 200.f;
Stamina = MaxStamina;
StaminaConsumeRate = 20.f;
ExhaustedStamina = 0.167f;
Coins = 0;
RunningSpeed = 600.f;
SprintSpeed = 900.f;
MovementStatus = EPlayerMovementStatus::EPMS_Normal;
StaminaStatus = EPlayerStaminaStatus::EPSS_Normal;
//默认没有按下shift
bLeftShiftDown = false;
InterpSpeed = 15.f;
bInterpToEnemy = false;
}
// Called when the game starts or when spawned
void AMainPlayer::BeginPlay()
{
Super::BeginPlay();
}
// Called every frame
void AMainPlayer::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
//不存活就不执行其他活动了
if (!IsAlive())
{
return;
}
switch (StaminaStatus)
{
case EPlayerStaminaStatus::EPSS_Normal:
//当Shift按下
if (bLeftShiftDown)
{
if (Stamina - StaminaConsumeRate * DeltaTime <= MaxStamina * ExhaustedStamina)
{
StaminaStatus = EPlayerStaminaStatus::EPSS_Exhausted;
}
//无论是不是精疲力尽状态都要减去当前帧冲刺消耗的耐力
Stamina -= StaminaConsumeRate * DeltaTime;
SetMovementStatus(EPlayerMovementStatus::EPMS_Sprinting);
}
else
{
//当Shift没有按下,恢复耐力
Stamina = FMath::Clamp(Stamina + StaminaConsumeRate * DeltaTime, 0.f, MaxStamina);
SetMovementStatus(EPlayerMovementStatus::EPMS_Normal);
}
break;
case EPlayerStaminaStatus::EPSS_Exhausted:
if (bLeftShiftDown)
{
//如果耐力已经为0
if (Stamina - StaminaConsumeRate * DeltaTime <= 0.f)
{
//么我们需要内部编码把shift抬起,此时StaminaStatus状态转换为ExhaustedRecovering状态,然后设置移动状态为Normal
LeftShiftUp();
StaminaStatus = EPlayerStaminaStatus::EPSS_ExhaustedRecovering;
SetMovementStatus(EPlayerMovementStatus::EPMS_Normal);
}
else
{
Stamina -= StaminaConsumeRate * DeltaTime;
}
}
else
{
StaminaStatus = EPlayerStaminaStatus::EPSS_ExhaustedRecovering;
Stamina = FMath::Clamp(Stamina + StaminaConsumeRate * DeltaTime, 0.f, MaxStamina);
SetMovementStatus(EPlayerMovementStatus::EPMS_Normal);
}
break;
case EPlayerStaminaStatus::EPSS_ExhaustedRecovering:
//当恢复大于疲劳区时,StaminaStatus状态为Normal
if (Stamina + StaminaConsumeRate * DeltaTime >= MaxStamina * ExhaustedStamina)
{
StaminaStatus = EPlayerStaminaStatus::EPSS_Normal;
}
//这状态值肯定是加定了
Stamina += StaminaConsumeRate * DeltaTime;
//抬起shift
LeftShiftUp();
SetMovementStatus(EPlayerMovementStatus::EPMS_Normal);
break;
default:
break;
}
//进行转向插值
if (bInterpToEnemy && AttackTarget)
{
//只需要AttackTarget的Yaw转向
FRotator LookAtYaw(0.f, UKismetMathLibrary::FindLookAtRotation(GetActorLocation(), AttackTarget->GetActorLocation()).Yaw, 0.f);
FRotator InterpRotation = FMath::RInterpTo(GetActorRotation(), LookAtYaw, DeltaTime, InterpSpeed);
SetActorRotation(InterpRotation);
}
}
// Called to bind functionality to input
void AMainPlayer::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);
//检查PlayerInputComponent指针,check函数只能在这使用
check(PlayerInputComponent);
//绑定跳跃轴映射事件
PlayerInputComponent->BindAction("Jump", IE_Pressed, this, &AMainPlayer::Jump);//按下空格
PlayerInputComponent->BindAction("Jump", IE_Released, this, &ACharacter::StopJumping);//抬起空格
PlayerInputComponent->BindAction("Sprint", IE_Pressed, this, &AMainPlayer::LeftShiftDown);//按下shift
PlayerInputComponent->BindAction("Sprint", IE_Released, this, &AMainPlayer::LeftShiftUp);//抬起shift
//拾取剑
PlayerInputComponent->BindAction("Interact", IE_Pressed, this, &AMainPlayer::InteractKeyDown);//按下F
//攻击
PlayerInputComponent->BindAction("Attack", IE_Pressed, this, &AMainPlayer::AttackKeyDown);
PlayerInputComponent->BindAction("Attack", IE_Released, this, &AMainPlayer::AttackKeyUp);
//绑定移动轴映射事件
PlayerInputComponent->BindAxis("MoveForward", this, &AMainPlayer::MoveForward);
PlayerInputComponent->BindAxis("MoveRight", this, &AMainPlayer::MoveRight);
//绑定Controller控制器去管理视角旋转
PlayerInputComponent->BindAxis("Turn", this, &AMainPlayer::Turn);
PlayerInputComponent->BindAxis("LookUp", this, &AMainPlayer::LookUp);
//绑定键盘鼠标轴映射事件
PlayerInputComponent->BindAxis("TurnRate", this, &AMainPlayer::TurnRate);
PlayerInputComponent->BindAxis("LookUpRate", this, &AMainPlayer::LookUpRate);
}
void AMainPlayer::Jump()
{
//继承父类的方法
if (IsAlive())
{
Super::Jump();
}
}
void AMainPlayer::MoveForward(float value)
{
if (Controller != nullptr && value != 0.f && !(bIsAttacking) && IsAlive())
{
//获取到Control旋转
FRotator Rotation = Controller->GetControlRotation();
//转向只关注水平Yaw方向,因此置0防止影响
FRotator YowRotation = FRotator(0.0f, Rotation.Yaw, 0.0f);
//获取相机(鼠标控制器的朝向),并且朝这个轴的方向移动
FVector Direction = FRotationMatrix(YowRotation).GetUnitAxis(EAxis::X);
AddMovementInput(Direction, value);
}
}
void AMainPlayer::MoveRight(float value)
{
if (Controller != nullptr && value != 0.f && !(bIsAttacking) && IsAlive())
{
//获取到Controller旋转
FRotator Rotation = Controller->GetControlRotation();
//转向只关注水平Yaw方向,因此置0防止影响
FRotator YowRotation = FRotator(0.0f, Rotation.Yaw, 0.0f);
//获取相机(鼠标控制器的朝向),并且朝这个轴的方向移动
FVector Direction = FRotationMatrix(YowRotation).GetUnitAxis(EAxis::Y);
AddMovementInput(Direction, value);
}
}
void AMainPlayer::Turn(float Value)
{
if (Value != 0.f && IsAlive())
{
AddControllerYawInput(Value);
}
}
void AMainPlayer::LookUp(float Value)
{
//UE_LOG(LogTemp, Warning, TEXT("%f"), GetControlRotation().Pitch);
if (IsAlive())
{
//控制视角
if (GetControlRotation().Pitch < 270.f && GetControlRotation().Pitch >180.f && Value > 0.f)
{
return;
}
else if (GetControlRotation().Pitch < 180.f && GetControlRotation().Pitch >45.f && Value < 0.f)
{
return;
}
AddControllerPitchInput(Value);
}
}
void AMainPlayer::TurnRate(float Rate)
{
//要乘以一个DeltaTime这样就可以避免高帧底帧差值问题
float Value = Rate * BaseTurnRate * GetWorld()->GetDeltaSeconds();
if (Value != 0.f && IsAlive())
{
AddControllerYawInput(Value);
}
}
void AMainPlayer::LookUpRate(float Rate)
{
//要乘以一个DeltaTime这样就可以避免高帧底帧差值问题
if (IsAlive())
{
float Value = Rate * BaseLookUpRate * GetWorld()->GetDeltaSeconds();
//控制视角
if (GetControlRotation().Pitch < 270.f && GetControlRotation().Pitch >180.f && Value > 0.f)
{
return;
}
else if (GetControlRotation().Pitch < 180.f && GetControlRotation().Pitch >45.f && Value < 0.f)
{
return;
}
AddControllerPitchInput(Value);
}
}
void AMainPlayer::AddHealth(float value)
{
Health = FMath::Clamp(Health + value, 0.f, MaxHealth);
}
void AMainPlayer::AddStamina(float value)
{
Stamina = FMath::Clamp(Stamina + value, 0.f, MaxStamina);
}
void AMainPlayer::AddCoin(float value)
{
Coins += value;
}
float AMainPlayer::TakeDamage(float Damage, FDamageEvent const& DamageEvent, AController* EventInstigator, AActor* DamageCauser)
{
if (Health - Damage <= 0.f)
{
Health = FMath::Clamp(Health - Damage, 0.f, MaxHealth);
//TODO Die();
}
else
{
Health -= Damage;
}
return Health;
}
void AMainPlayer::SetMovementStatus(EPlayerMovementStatus Status)
{
if (IsAlive())
{
MovementStatus = Status;
//切换状态的时候改变移动速度
switch (MovementStatus)
{
case EPlayerMovementStatus::EPMS_Sprinting:
GetCharacterMovement()->MaxWalkSpeed = SprintSpeed;
break;
default:
GetCharacterMovement()->MaxWalkSpeed = RunningSpeed;
break;
}
}
}
void AMainPlayer::InteractKeyDown()
{
if (OverlapWeapon && IsAlive())
{
if (EquipWeapon)
{
//交换武器
EquipWeapon->UnEuip(this);
OverlapWeapon->Equip(this);
}
else
{
//装备武器
OverlapWeapon->Equip(this);
}
}
else
{
if (EquipWeapon)
{
//卸载武器
EquipWeapon->UnEuip(this);
}
}
}
void AMainPlayer::AttackKeyDown()
{
if (IsAlive())
{
bAttackKeyDown = true;
if (bIsWeapon)
{
AttackBegin();
}
}
}
void AMainPlayer::AttackBegin()
{
if (!bIsAttacking && IsAlive())
{
bIsAttacking = true;
bInterpToEnemy = true;
//拿到动画
UAnimInstance* AnimInstance = GetMesh()->GetAnimInstance();
if (AnimInstance && AttackMontage)
{
float PlayRate = FMath::RandRange(1.25f, 1.75f);
FString SectionName = FString::FromInt(FMath::RandRange(1, 2));
//指定片段播放
AnimInstance->Montage_Play(AttackMontage, PlayRate);
AnimInstance->Montage_JumpToSection(FName(*SectionName), AttackMontage);
}
}
}
void AMainPlayer::AttackEnd()
{
bIsAttacking = false;
bInterpToEnemy = false;
//形成闭环
if (bAttackKeyDown && IsAlive())
{
AttackKeyDown();
}
}
void AMainPlayer::UpdataAttackTarget()
{
TArray<AActor*> OVerlappingActors;
GetOverlappingActors(OVerlappingActors,EnemyFilter);
//判断列表里面是否为空,为空就无攻击目标
if (OVerlappingActors.Num() == 0)
{
AttackTarget = nullptr;
return;
}
ABaseEnemy* ClosestDistance = nullptr;
float MinDistance = 1000.f;
FVector Loation = GetActorLocation();
for (auto Actor : OVerlappingActors)
{
ABaseEnemy* Enemy = Cast<ABaseEnemy>(Actor);
if (Enemy && Enemy->EnemyMovementStatus != EEnemyMovementStatus::EEMS_Dead)
{
float DistanceToActor = (Enemy->GetActorLocation() - Loation).Size();//记录当前位置Enemy距离MainPlayer位置
if (DistanceToActor < MinDistance)
{
MinDistance = DistanceToActor;
ClosestDistance = Enemy;
}
}
}
AttackTarget = ClosestDistance;
}
void AMainPlayer::Die()
{
SetMovementStatus(EPlayerMovementStatus::EPMS_Dead);
if (EquipWeapon)
{
EquipWeapon->DeactiveAttackCollision();
EquipWeapon->DeactiveDisplayMeshCollision();
}
}
void AMainPlayer::DeathEnd()
{
}
限制敌人死亡后的一切活动状态
- 基本跟限制玩家差不多,只不过有些位置得多一个玩家是否存活的判断
BaseEnemy.cpp
cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "BaseEnemy.h"
#include "Components/SphereComponent.h"
#include "Components/SkeletalMeshComponent.h"
#include "Components/CapsuleComponent.h"
#include "AIController.h"
#include "Characters/Player/MainPlayer.h"
#include "Animation/AnimInstance.h"
#include "Kismet/KismetMathLibrary.h"
#include "Kismet/GameplayStatics.h"
#include "Components/WidgetComponent.h"
#include "Blueprint/UserWidget.h"
#include "Components/ProgressBar.h"
#include "Components/BoxComponent.h"
#include "Sound/SoundCue.h"
#include "Engine/SkeletalMeshSocket.h"
#include "Characters/Player/MainPlayer.h"
// Sets default values
ABaseEnemy::ABaseEnemy()
{
// Set this character to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;
ChaseVolume = CreateDefaultSubobject<USphereComponent>(TEXT("ChaseVolume"));
ChaseVolume->SetupAttachment(GetRootComponent());
ChaseVolume->InitSphereRadius(800.f);
ChaseVolume->SetCollisionObjectType(ECollisionChannel::ECC_WorldDynamic);
ChaseVolume->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Ignore);
ChaseVolume->SetCollisionResponseToChannel(ECollisionChannel::ECC_Pawn, ECollisionResponse::ECR_Overlap);
AttackVolume = CreateDefaultSubobject<USphereComponent>(TEXT("AttackVolume"));
AttackVolume->SetupAttachment(GetRootComponent());
AttackVolume->InitSphereRadius(100.f);
AttackVolume->SetCollisionObjectType(ECollisionChannel::ECC_WorldDynamic);
AttackVolume->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Ignore);
AttackVolume->SetCollisionResponseToChannel(ECollisionChannel::ECC_Pawn, ECollisionResponse::ECR_Overlap);
HealthBarWidgetComponent = CreateDefaultSubobject<UWidgetComponent>(TEXT("HealthBarWidgetComponent"));
HealthBarWidgetComponent->SetupAttachment(GetRootComponent());
HealthBarWidgetComponent->SetWidgetSpace(EWidgetSpace::Screen);
HealthBarWidgetComponent->SetDrawSize(FVector2D(125.f, 10.f));
HealthBarWidgetComponent->SetWorldLocation(FVector(0.f, 0.f, 50.f));
LeftAttackCollision = CreateDefaultSubobject<UBoxComponent>(TEXT("LeftAttackCollision"));
LeftAttackCollision->SetupAttachment(GetMesh(), "LeftAttackSocket");
DeactiveLeftAttackCollision();
RightAttackCollision = CreateDefaultSubobject<UBoxComponent>(TEXT("RightAttackCollision"));
RightAttackCollision->SetupAttachment(GetMesh(), "RightAttackSocket");
DeactiveRightAttackCollision();
//避免摄像机被敌人给阻挡
GetMesh()->SetCollisionResponseToChannel(ECollisionChannel::ECC_Camera, ECollisionResponse::ECR_Ignore);
GetCapsuleComponent()->SetCollisionResponseToChannel(ECollisionChannel::ECC_Camera, ECollisionResponse::ECR_Ignore);
//设置持有属性
AutoPossessAI = EAutoPossessAI::PlacedInWorldOrSpawned;
//初始化默认移动状态
EnemyMovementStatus = EEnemyMovementStatus::EEMS_Idle;
InterpSpeed = 15.f;
bInterpToPlayer = false;
MaxHealth = 100.f;
Health = MaxHealth;
Damage = 5.f;
}
// Called when the game starts or when spawned
void ABaseEnemy::BeginPlay()
{
Super::BeginPlay();
ChaseVolume->OnComponentBeginOverlap.AddDynamic(this, &ABaseEnemy::OnChaseVolumeOverlapBegin);
ChaseVolume->OnComponentEndOverlap.AddDynamic(this, &ABaseEnemy::OnChaseVolumeOverlapEnd);
AttackVolume->OnComponentBeginOverlap.AddDynamic(this, &ABaseEnemy::OnAttackVolumeOverlapBegin);
AttackVolume->OnComponentEndOverlap.AddDynamic(this, &ABaseEnemy::OnAttackVolumeOverlapEnd);
LeftAttackCollision->OnComponentBeginOverlap.AddDynamic(this, &ABaseEnemy::OnLeftAttackCollisionOverlapBegin);
LeftAttackCollision->OnComponentEndOverlap.AddDynamic(this, &ABaseEnemy::OnLeftAttackCollisionOverlapEnd);
RightAttackCollision->OnComponentBeginOverlap.AddDynamic(this, &ABaseEnemy::OnRightAttackCollisionOverlapBegin);
RightAttackCollision->OnComponentEndOverlap.AddDynamic(this, &ABaseEnemy::OnRightAttackCollisionOverlapEnd);
//获取到HealBar小组件
HealthBar = Cast<UProgressBar>(HealthBarWidgetComponent->GetUserWidgetObject()->GetWidgetFromName("HealthBar"));
HealthBar->SetPercent(Health / MaxHealth);
HealthBar->SetVisibility(ESlateVisibility::Hidden);//一开始不显示血条
//拿到Controller
AIController = Cast<AAIController>(GetController());
}
// Called every frame
void ABaseEnemy::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
if (bInterpToPlayer && HasValidTarget() && IsAlive())
{
FRotator LookYaw(0.f, UKismetMathLibrary::FindLookAtRotation(GetActorLocation(), UGameplayStatics::GetPlayerPawn(this, 0)->GetActorLocation()).Yaw, 0.f);
FRotator InterpRotation = FMath::RInterpTo(GetActorRotation(), LookYaw, DeltaTime, InterpSpeed);
SetActorRotation(InterpRotation);
}
}
// Called to bind functionality to input
void ABaseEnemy::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);
}
void ABaseEnemy::OnChaseVolumeOverlapBegin(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
if (OtherActor && IsAlive())
{
AMainPlayer* Player = Cast<AMainPlayer>(OtherActor);
if (Player)
{
//主角进入追逐范围显示血条
HealthBar->SetVisibility(ESlateVisibility::Visible);
MoveToTarget(Player);
}
}
}
void ABaseEnemy::OnChaseVolumeOverlapEnd(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
{
if (OtherActor && IsAlive())
{
AMainPlayer* Player = Cast<AMainPlayer>(OtherActor);
if (Player)
{
if (AIController)
{
//主角出追逐范围不显示血条
HealthBar->SetVisibility(ESlateVisibility::Hidden);
//停止移动
AIController->StopMovement();
}
}
}
}
void ABaseEnemy::OnAttackVolumeOverlapBegin(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
if (OtherActor && IsAlive())
{
AMainPlayer* Player = Cast<AMainPlayer>(OtherActor);
if (Player)
{
Player->UpdataAttackTarget();
bAttackVolumeOverlap = true;
AttackBegin();
}
}
}
void ABaseEnemy::OnAttackVolumeOverlapEnd(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
{
if (OtherActor && IsAlive())
{
AMainPlayer* Player = Cast<AMainPlayer>(OtherActor);
if (Player)
{
bAttackVolumeOverlap = false;
if (EnemyMovementStatus!=EEnemyMovementStatus::EEMS_Attacking)
{
MoveToTarget(Player);
}
}
}
}
void ABaseEnemy::MoveToTarget(AMainPlayer* Player)
{
if (IsAlive())
{
EnemyMovementStatus = EEnemyMovementStatus::EEMS_MoveToTarget;
if (AIController)
{
FAIMoveRequest MoveRequest;
MoveRequest.SetGoalActor(Player);//设置移动请求目标
MoveRequest.SetAcceptanceRadius(10.f); //设置移动半径
FNavPathSharedPtr NavPath;//会返回路径
AIController->MoveTo(MoveRequest, &NavPath);
}
}
}
void ABaseEnemy::AttackBegin()
{
if (HasValidTarget() && IsAlive())
{
//攻击中关闭移动
if (AIController)
{
AIController->StopMovement();
}
if (EnemyMovementStatus != EEnemyMovementStatus::EEMS_Attacking)
{
EnemyMovementStatus = EEnemyMovementStatus::EEMS_Attacking;
bInterpToPlayer = true;
UAnimInstance* AnimInstance = GetMesh()->GetAnimInstance();
if (AnimInstance && AttackMontage)
{
float PlayRate = FMath::RandRange(0.9f, 1.1f);
FString SectionName = FString::FromInt(FMath::RandRange(1, 3));
AnimInstance->Montage_Play(AttackMontage, PlayRate);
AnimInstance->Montage_JumpToSection(FName(*SectionName), AttackMontage);
}
}
}
}
void ABaseEnemy::AttackEnd()
{
bInterpToPlayer = false;
if (HasValidTarget() && IsAlive())
{
EnemyMovementStatus = EEnemyMovementStatus::EEMS_Idle;
if (bAttackVolumeOverlap && HasValidTarget() && IsAlive())
{
AttackBegin();
}
}
}
void ABaseEnemy::OnLeftAttackCollisionOverlapBegin(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
if (OtherActor && IsAlive())
{
AMainPlayer* Player = Cast<AMainPlayer>(OtherActor);
if (Player)
{
if (Player->HitPaticles)
{
const USkeletalMeshSocket* AttackScoket = GetMesh()->GetSocketByName("LeftAttackSocket");
if (AttackScoket)
{
FVector SocketLocation = AttackScoket->GetSocketLocation(GetMesh());
UGameplayStatics::SpawnEmitterAtLocation(this, Player->HitPaticles, SocketLocation, FRotator(0.f), true);
}
if (Player->HitSound)
{
UGameplayStatics::PlaySound2D(this, Player->HitSound);
}
if (DamageTyClass)
{
UGameplayStatics::ApplyDamage(Player, Damage, AIController, this, DamageTyClass);
}
}
}
}
}
void ABaseEnemy::OnLeftAttackCollisionOverlapEnd(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
{
}
void ABaseEnemy::OnRightAttackCollisionOverlapBegin(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
if (OtherActor && IsAlive())
{
AMainPlayer* Player = Cast<AMainPlayer>(OtherActor);
if (Player)
{
if (Player->HitPaticles)
{
const USkeletalMeshSocket* AttackScoket = GetMesh()->GetSocketByName("RightAttackSocket");
if (AttackScoket)
{
FVector SocketLocation = AttackScoket->GetSocketLocation(GetMesh());
UGameplayStatics::SpawnEmitterAtLocation(this, Player->HitPaticles, SocketLocation, FRotator(0.f), true);
}
if (Player->HitSound)
{
UGameplayStatics::PlaySound2D(this, Player->HitSound);
}
if (DamageTyClass)
{
UGameplayStatics::ApplyDamage(Player, Damage/2, AIController, this, DamageTyClass);
}
}
}
}
}
void ABaseEnemy::OnRightAttackCollisionOverlapEnd(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
{
}
void ABaseEnemy::ActiveLeftAttackCollision()
{
LeftAttackCollision->SetCollisionEnabled(ECollisionEnabled::QueryOnly);
LeftAttackCollision->SetCollisionObjectType(ECollisionChannel::ECC_WorldDynamic);
LeftAttackCollision->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Ignore);
LeftAttackCollision->SetCollisionResponseToChannel(ECollisionChannel::ECC_Pawn, ECollisionResponse::ECR_Overlap);
}
void ABaseEnemy::DeactiveLeftAttackCollision()
{
LeftAttackCollision->SetCollisionEnabled(ECollisionEnabled::NoCollision);
}
void ABaseEnemy::ActiveRightAttackCollision()
{
RightAttackCollision->SetCollisionEnabled(ECollisionEnabled::QueryOnly);
RightAttackCollision->SetCollisionObjectType(ECollisionChannel::ECC_WorldDynamic);
RightAttackCollision->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Ignore);
RightAttackCollision->SetCollisionResponseToChannel(ECollisionChannel::ECC_Pawn, ECollisionResponse::ECR_Overlap);
}
void ABaseEnemy::DeactiveRightAttackCollision()
{
RightAttackCollision->SetCollisionEnabled(ECollisionEnabled::NoCollision);
}
float ABaseEnemy::TakeDamage(float Damage, FDamageEvent const& DamageEvent, AController* EventInstigator, AActor* DamageCauser)
{
if (Health - Damage <= 0.f)
{
Health = FMath::Clamp(Health - Damage, 0.f, MaxHealth);
Die();
}
else
{
Health -= Damage;
}
HealthBar->SetPercent(Health / MaxHealth);//更新UI血条
return Health;
}
void ABaseEnemy::Die()
{
EnemyMovementStatus = EEnemyMovementStatus::EEMS_Dead;
DeactiveLeftAttackCollision();
DeactiveRightAttackCollision();
ChaseVolume->SetCollisionEnabled(ECollisionEnabled::NoCollision);
AttackVolume->SetCollisionEnabled(ECollisionEnabled::NoCollision);
GetCapsuleComponent()->SetCollisionEnabled(ECollisionEnabled::NoCollision);
//敌人死亡,主角要更新攻击目标
Cast<AMainPlayer>(UGameplayStatics::GetPlayerPawn(this, 0))->UpdataAttackTarget();
}
void ABaseEnemy::DeathEnd()
{
}
bool ABaseEnemy::HasValidTarget()
{
return Cast<AMainPlayer>(UGameplayStatics::GetPlayerPawn(this, 0))->MovementStatus != EPlayerMovementStatus::EPMS_Dead;
}
主角死亡与敌人死亡后延时销毁
- 首先设置敌人血条在敌人死亡后就隐藏
cpp
void ABaseEnemy::Die()
{
EnemyMovementStatus = EEnemyMovementStatus::EEMS_Dead;
HealthBar->SetVisibility(ESlateVisibility::Hidden);
DeactiveLeftAttackCollision();
DeactiveRightAttackCollision();
ChaseVolume->SetCollisionEnabled(ECollisionEnabled::NoCollision);
AttackVolume->SetCollisionEnabled(ECollisionEnabled::NoCollision);
GetCapsuleComponent()->SetCollisionEnabled(ECollisionEnabled::NoCollision);
//敌人死亡,主角要更新攻击目标
Cast<AMainPlayer>(UGameplayStatics::GetPlayerPawn(this, 0))->UpdataAttackTarget();
}
- 然后去主角的死亡动画中添加DeathEnd的通知,并在动画蓝图中调用
- 编写DeathEnd死亡逻辑,首先关闭动画,添加定时器,玩家死后一秒重启游戏
cpp
void AMainPlayer::DeathEnd()
{
GetMesh()->bPauseAnims = true;
GetMesh()->bNoSkeletonUpdate = true;
FTimerHandle DeathTimerHandle;
auto Lambda = []()
{
//TODO RestartLevel();
};
GetWorldTimerManager().SetTimer(DeathTimerHandle, FTimerDelegate::CreateLambda(Lambda), 1.0, false);
}
- 敌人这边也是一样添加通知动画蓝图中调用,然后编写死亡消失逻辑
- 死亡一秒后销毁
cpp
void ABaseEnemy::DeathEnd()
{
GetMesh()->bPauseAnims = true;
GetMesh()->bNoSkeletonUpdate = true;
FTimerHandle DeathTimerHandle;
auto Lambda = [this]()
{
Destroy();
};
GetWorldTimerManager().SetTimer(DeathTimerHandle, FTimerDelegate::CreateLambda(Lambda), 1.0, false);
}
玩家死亡重新开始游戏
- 将上面遗留的RestartLevel函数进行新建然后编写,用UGameplayStatics中的方法进行重新开始游戏
cpp
void AMainPlayer::DeathEnd()
{
GetMesh()->bPauseAnims = true;
GetMesh()->bNoSkeletonUpdate = true;
FTimerHandle DeathTimerHandle;
auto Lambda = [this]()
{
RestartLevel();
};
GetWorldTimerManager().SetTimer(DeathTimerHandle, FTimerDelegate::CreateLambda(Lambda), 1.0, false);
}
void AMainPlayer::RestartLevel()
{
FString LevelName = UGameplayStatics::GetCurrentLevelName(this);
UGameplayStatics::OpenLevel(this, FName(*LevelName));
}