深圳画册设计网站,南昌莱布网络科技有限公司,无为建设局网站,可以自己建设购物网站伤害系统
给敌人创建血条
首先添加一个UI界面用来显示敌人血条设置背景图像为黑色半透明 填充颜色 给敌人类添加两种状态表示血量与最大血量#xff0c;添加一个UWidegtComponet组件与UProgressBar组件 UPROPERTY(EditAnywhere, BlueprintReadWrite, Category Enemy …伤害系统
给敌人创建血条
首先添加一个UI界面用来显示敌人血条设置背景图像为黑色半透明 填充颜色 给敌人类添加两种状态表示血量与最大血量添加一个UWidegtComponet组件与UProgressBar组件 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组件 HealthBarWidgetComponent CreateDefaultSubobjectUWidgetComponent(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小组件
// 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 CastUProgressBar(HealthBarWidgetComponent-GetUserWidgetObject()-GetWidgetFromName(HealthBar));HealthBar-SetPercent(Health / MaxHealth);//拿到ControllerAIController CastAAIController(GetController());
}运行结果
血条进入敌人追逐区显示
一开始敌人血条是不显示的只有主角进入了敌人追逐区才开始显示血条出了追逐区也不显示 //获取到HealBar小组件HealthBar CastUProgressBar(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 CastAMainPlayer(OtherActor);if (Player){//主角进入追逐范围显示血条HealthBar-SetVisibility(ESlateVisibility::Visible);MoveToTarget(Player);}}
}void ABaseEnemy::OnChaseVolumeOverlapEnd(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
{if (OtherActor){AMainPlayer* Player CastAMainPlayer(OtherActor);if (Player){if (AIController){//主角出追逐范围不显示血条HealthBar-SetVisibility(ESlateVisibility::Hidden);//停止移动AIController-StopMovement();}}}
}触发伤害思路
我们可以在武器上加一个碰撞器当这个碰撞器碰撞到敌人的时候就开启伤害如果没有就躲避的伤害给每把剑的骨骼添加一个Socket
触发伤害需求
因为要使用UE中内置的直接伤害附加的功能类似爆炸物那一节所以我们要在武器类中新建一个盒子碰撞触发器新建一个伤害值新建一个伤害类型新建一个伤害发起者 UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category Weapon|Attack)class UBoxComponent* AttackCollision;UPROPERTY(EditAnywhere, BlueprintReadWrite, Category Weapon|Attack)float Damage;UPROPERTY(EditAnywhere, BlueprintReadWrite, Category Weapon|Attack)TSubclassOfUDamageType DamageTyClass;UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category Weapon|Attack)class AController* WeaponOwner;新建伤害开始触发事件与结束事件两个动态切换碰撞函数我只需要在挥剑的那个瞬间去切换需要在动画的notify中去切换的所以需要添加反射 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” AttackCollision CreateDefaultSubobjectUBoxComponent(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
// 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进行一下区分DisplayMeshCreateDefaultSubobjectUSkeletalMeshComponent(TEXT(DisplaySkeletalMesh));DisplayMesh-SetupAttachment(GetRootComponent());ActiveDisplayMeshCollision();//设置碰撞AttackCollision CreateDefaultSubobjectUBoxComponent(TEXT(AttackCollision));AttackCollision-SetupAttachment(DisplayMesh, WeaponSocket);//将这个碰撞点附加到武器插槽上DeactiveAttackCollision();static ConstructorHelpers::FObjectFinderUSoundCue 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 CastAMainPlayer(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 CastAMainPlayer(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的Socketconst 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 SocketDetachFromActor(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);
}在蒙太奇中添加攻击通知激活伤害碰撞
在主角的蒙太奇中添加四个通知跟当时添加攻击结束通知差不多 然后在动画蓝图中进行调用函数 将三把剑的盒子碰撞检测大小调整一下
给敌人添加触发器
基本与上面给主角添加伤害需求的思路一致先给敌人添加两个骨骼
触发伤害需求
再来就是编辑敌人类的变量需求与基本逻辑了两个触发盒子因为是两只腿的攻击新建伤害值伤害类型 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)TSubclassOfUDamageType DamageTyClass;不用说现在是四个伤害触发事件的新建四个动态切换碰撞的函数 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();现在就可以开始编写初始化了 LeftAttackCollision CreateDefaultSubobjectUBoxComponent(TEXT(LeftAttackCollision));LeftAttackCollision-SetupAttachment(GetMesh(), LeftAttackSocket);DeactiveLeftAttackCollision();RightAttackCollision CreateDefaultSubobjectUBoxComponent(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
// 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 dont need it.PrimaryActorTick.bCanEverTick true;ChaseVolume CreateDefaultSubobjectUSphereComponent(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 CreateDefaultSubobjectUSphereComponent(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 CreateDefaultSubobjectUWidgetComponent(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 CreateDefaultSubobjectUBoxComponent(TEXT(LeftAttackCollision));LeftAttackCollision-SetupAttachment(GetMesh(), LeftAttackSocket);DeactiveLeftAttackCollision();RightAttackCollision CreateDefaultSubobjectUBoxComponent(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 CastUProgressBar(HealthBarWidgetComponent-GetUserWidgetObject()-GetWidgetFromName(HealthBar));HealthBar-SetPercent(Health / MaxHealth);HealthBar-SetVisibility(ESlateVisibility::Hidden);//一开始不显示血条//拿到ControllerAIController CastAAIController(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 CastAMainPlayer(OtherActor);if (Player){//主角进入追逐范围显示血条HealthBar-SetVisibility(ESlateVisibility::Visible);MoveToTarget(Player);}}
}void ABaseEnemy::OnChaseVolumeOverlapEnd(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
{if (OtherActor){AMainPlayer* Player CastAMainPlayer(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 CastAMainPlayer(OtherActor);if (Player){Player-UpdataAttackTarget();bAttackVolumeOverlap true;AttackBegin();}}
}void ABaseEnemy::OnAttackVolumeOverlapEnd(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
{if (OtherActor){AMainPlayer* Player CastAMainPlayer(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中添加一个粒子系统与声音组件 UPROPERTY(EditAnywhere, BlueprintReadWrite, Category Hit Effect)class UParticleSystem* HitPaticles;UPROPERTY(EditAnywhere, BlueprintReadWrite, Category Hit Effect)class USoundCue* HitSound;在WeaponItem.cpp的攻击开始重叠事件中去添加声音特效与伤害这个就和之前装备武器与爆炸物传递伤害逻辑差不多正好复习一下获取插槽的时候必须加const因为获取插槽的函数返回的是一个const值
void AWeaponItem::OnAttackCollisionOverlapBegin(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult SweepResult)
{if (OtherActor){ABaseEnemy* BaseEnemy CastABaseEnemy(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的左右攻击事件中添加粒子音效与伤害
void ABaseEnemy::OnLeftAttackCollisionOverlapBegin(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult SweepResult)
{if (OtherActor){AMainPlayer* Player CastAMainPlayer(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 CastAMainPlayer(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中编码跟随所以最后返回写完逻辑返回血量前要更新一下血条 //重写TakeDamage方法float TakeDamage(float Damage, struct FDamageEvent const DamageEvent, AController* EventInstigator, AActor* DamageCauser) override;void Die();伤害逻辑
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中新建一个用于信息通知的函数
UFUNCTION(BlueprintCallable)
void DeathEnd();然后编辑MainPlayer死亡函数的逻辑首先主角状态设置为死亡判断是否持剑持剑就关闭武器所有碰撞
void AMainPlayer::Die()
{SetMovementStatus(EPlayerMovementStatus::EPMS_Dead);if (EquipWeapon){EquipWeapon-DeactiveAttackCollision();EquipWeapon-DeactiveDisplayMeshCollision();}
}编辑BaseEnemy死亡函数逻辑首先设置状态为死亡 关闭所有的碰撞因为敌人已死亡更新主角攻击目标
void ABaseEnemy::Die()
{EnemyMovementStatus EEnemyMovementStatus::EEMS_Dead;DeactiveLeftAttackCollision();DeactiveRightAttackCollision();ChaseVolume-SetCollisionEnabled(ECollisionEnabled::NoCollision);AttackVolume-SetCollisionEnabled(ECollisionEnabled::NoCollision);GetCapsuleComponent()-SetCollisionEnabled(ECollisionEnabled::NoCollision);//敌人死亡主角要更新攻击目标CastAMainPlayer(UGameplayStatics::GetPlayerPawn(this, 0))-UpdataAttackTarget();
}编辑他们的动画蓝图添加Blend Poses by bool播放死亡动画Enemy状态为死亡时播放死亡动画否则就正常播放其他动画记得将死亡的循环播放动画关闭
限制玩家死亡后的一切活动状态
思路我们在MainPlayer与BaseEnemy中新建一个专门用来判断玩家是否存活状态的函数如果玩家不存活就停止一切活动
FORCEINLINE bool IsAlive() { return MovementStatus ! EPlayerMovementStatus::EPMS_Dead; }
敌人的状态中还得检测一下玩家是否死亡
FORCEINLINE bool IsAlive() { return EnemyMovementStatus ! EEnemyMovementStatus::EEMS_Dead; }
bool HasValidTarget();//------------------------------------------------------------------------------------------------
bool ABaseEnemy::HasValidTarget()
{return CastAMainPlayer(UGameplayStatics::GetPlayerPawn(this, 0))-MovementStatus ! EPlayerMovementStatus::EPMS_Dead;
}MainPlayer.cpp
然后在MainPlayer中添加判断禁止死亡后的一切活动也就是都加上IsAlive判断
// 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 dont need it.PrimaryActorTick.bCanEverTick true;SpringArm CreateDefaultSubobjectUSpringArmComponent(TEXT(SpringArm));SpringArm-SetupAttachment(GetRootComponent());//设置SPringArm无碰撞臂长SpringArm-TargetArmLength 600.f;SpringArm-bUsePawnControlRotation true;//硬编码SpringArm继承controlller旋转为真FollowCamera CreateDefaultSubobjectUCameraComponent(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;//默认没有按下shiftbLeftShiftDown 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){//如果耐力已经为0if (Stamina - StaminaConsumeRate * DeltaTime 0.f){//么我们需要内部编码把shift抬起此时StaminaStatus状态转换为ExhaustedRecovering状态然后设置移动状态为NormalLeftShiftUp();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状态为Normalif (Stamina StaminaConsumeRate * DeltaTime MaxStamina * ExhaustedStamina){StaminaStatus EPlayerStaminaStatus::EPSS_Normal;}//这状态值肯定是加定了Stamina StaminaConsumeRate * DeltaTime;//抬起shiftLeftShiftUp();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);//按下shiftPlayerInputComponent-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()
{TArrayAActor* 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 CastABaseEnemy(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
// 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 dont need it.PrimaryActorTick.bCanEverTick true;ChaseVolume CreateDefaultSubobjectUSphereComponent(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 CreateDefaultSubobjectUSphereComponent(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 CreateDefaultSubobjectUWidgetComponent(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 CreateDefaultSubobjectUBoxComponent(TEXT(LeftAttackCollision));LeftAttackCollision-SetupAttachment(GetMesh(), LeftAttackSocket);DeactiveLeftAttackCollision();RightAttackCollision CreateDefaultSubobjectUBoxComponent(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 CastUProgressBar(HealthBarWidgetComponent-GetUserWidgetObject()-GetWidgetFromName(HealthBar));HealthBar-SetPercent(Health / MaxHealth);HealthBar-SetVisibility(ESlateVisibility::Hidden);//一开始不显示血条//拿到ControllerAIController CastAAIController(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 CastAMainPlayer(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 CastAMainPlayer(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 CastAMainPlayer(OtherActor);if (Player){Player-UpdataAttackTarget();bAttackVolumeOverlap true;AttackBegin();}}
}void ABaseEnemy::OnAttackVolumeOverlapEnd(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
{if (OtherActor IsAlive()){AMainPlayer* Player CastAMainPlayer(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 CastAMainPlayer(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 CastAMainPlayer(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);//敌人死亡主角要更新攻击目标CastAMainPlayer(UGameplayStatics::GetPlayerPawn(this, 0))-UpdataAttackTarget();
}void ABaseEnemy::DeathEnd()
{
}bool ABaseEnemy::HasValidTarget()
{return CastAMainPlayer(UGameplayStatics::GetPlayerPawn(this, 0))-MovementStatus ! EPlayerMovementStatus::EPMS_Dead;
}主角死亡与敌人死亡后延时销毁
首先设置敌人血条在敌人死亡后就隐藏
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);//敌人死亡主角要更新攻击目标CastAMainPlayer(UGameplayStatics::GetPlayerPawn(this, 0))-UpdataAttackTarget();
}然后去主角的死亡动画中添加DeathEnd的通知并在动画蓝图中调用 编写DeathEnd死亡逻辑首先关闭动画添加定时器玩家死后一秒重启游戏
void AMainPlayer::DeathEnd()
{GetMesh()-bPauseAnims true;GetMesh()-bNoSkeletonUpdate true;FTimerHandle DeathTimerHandle;auto Lambda [](){//TODO RestartLevel();};GetWorldTimerManager().SetTimer(DeathTimerHandle, FTimerDelegate::CreateLambda(Lambda), 1.0, false);
}敌人这边也是一样添加通知动画蓝图中调用然后编写死亡消失逻辑 死亡一秒后销毁
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中的方法进行重新开始游戏
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));
}