血量文本变化和血条变化应该是一起完成的,widget的更改以及动画效果都在蓝图编辑器内完成,C++主要是写了个自定义事件,然后在蓝图内调用自定义事件。我个人的感觉这个是第一次开始尝试结合蓝图与C++,各位可以自己感受下,效率确实会高很多,同时游戏的响应速度也会比纯蓝图要快那么一点。
SAttributeComponent.h
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "SAttributeComponent.generated.h"
//增加的宏定义
DECLARE_DYNAMIC_MULTICAST_DELEGATE_FourParams(FOnHealthChange, AActor*,InstigatorActor,USAttributeComponent*,OwingComp,float,NewHealth,float,Delta );
UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
class ACTIONROGUELIKE_API USAttributeComponent : public UActorComponent
{
GENERATED_BODY()
protected:
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Attributes")
float Health;
public:
// Sets default values for this component's properties
USAttributeComponent();
//将我们自定义的事件声明出来
UPROPERTY(BlueprintAssignable)
FOnHealthChange OnHealthChanged;
public:
UFUNCTION(BlueprintCallable,Category="Attributes")
bool ApplyHealthChange(float Delta);
};
USAttributeComponent.cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "SAttributeComponent.h"
// Sets default values for this component's properties
USAttributeComponent::USAttributeComponent()
{
// Set this component to be initialized when the game starts, and to be ticked every frame. You can turn these features
// off to improve performance if you don't need them.
PrimaryComponentTick.bCanEverTick = true;
// ...
Health = 100.0f;
}
bool USAttributeComponent::ApplyHealthChange(float Delta)
{
Health += Delta;
//多播委托允许您附加多个函数委托,然后通过调用多播委托的"Broadcast()"函数一次性同时执行它们
//Broadcast():将该委托广播给所有绑定的对象,但可能已过期的对象除外。(官方文档的解释)
//个人理解:此处作用是调用蓝图内的OnHealthChanged事件
OnHealthChanged.Broadcast(nullptr,this,Health,Delta);
return true;
}
这之后就是在用户界面的蓝图编辑器内去写一些基本逻辑。
然后就是爆炸桶爆炸更改血量的功能。由于我并不是按照老师的示例一步一步来的,我在老师讲这个课之前把爆炸桶遇到人物就触发爆炸这点视为bug给修改掉了,也就是增加了一个受击的判定,如果是玩家那么不发生爆炸。
所以,我额外增加了一个SphereComp,我的思路是,玩家在爆炸桶的一定范围内,当爆炸桶发生爆炸的时候,玩家才会扣血,所以我用到了SphereComp的OnComponentBeginOverlap()事件,然后自己写了个OnComponentBeginOverlap()的函数,将其绑定到开始重叠的事件后。这一点更加符合我们对于游戏内一般爆炸桶的认知。主要逻辑是相同的,只不过老师是用蓝图实现的,我们这个文档叫C++系列嘛,所以我用C++写了一遍,最后效果是相同的,给各位做参考。
SExplosiveBarrel.h
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "SAttributeComponent.h"
#include "SExplosiveBarrel.generated.h"
UCLASS()
class ACTIONROGUELIKE_API ASExplosiveBarrel : public AActor
{
GENERATED_BODY()
UPROPERTY(VisibleAnywhere)
class UStaticMeshComponent* StaticMeshComp;
UPROPERTY(VisibleAnywhere)
class URadialForceComponent* RadialComp;
//增加的球形碰撞体组件
UPROPERTY(VisibleAnywhere)
class USphereComponent* SphereComp;
//增加一个TArray,考虑到多人游戏的环境
TArray<USAttributeComponent*> CharacterList;
public:
// Sets default values for this actor's properties
ASExplosiveBarrel();
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
public:
// Called every frame
virtual void Tick(float DeltaTime) override;
UFUNCTION()
void OnComponentHit(UPrimitiveComponent*HitComponent, AActor*OtherActor, UPrimitiveComponent* OtherComp, FVector NormalImpulse, FHitResult Hit);
//增加的重叠函数
UFUNCTION()
void OnComponentBeginOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult);
};
SExplosiveBarrel.cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "SExplosiveBarrel.h"
#include "PhysicsEngine/RadialForceComponent.h"
#include "Components/StaticMeshComponent.h"
#include "DrawDebugHelpers.h"
#include "SFatherMagicProjectile.h"
#include "SAttributeComponent.h"
#include "Components/SphereComponent.h"
#include "SCharacter.h"
// Sets default values
ASExplosiveBarrel::ASExplosiveBarrel()
{
// Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;
FScriptDelegate delegator;
delegator.BindUFunction(this, STATIC_FUNCTION_FNAME(TEXT("ASExplosiveBarrel::OnComponentHit")));
//创建一个委托并进行函数绑定
FScriptDelegate OnOverlapBegin;
OnOverlapBegin.BindUFunction(this, STATIC_FUNCTION_FNAME(TEXT("ASExplosiveBarrel::OnComponentBeginOverlap")));
StaticMeshComp = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("StaticMeshComp"));
//StaticMeshComp->OnComponentHit.AddDynamic(this,ASExplosiveBarrel::OnComponentHit());
StaticMeshComp->OnComponentHit.Add(delegator);
StaticMeshComp->SetSimulatePhysics(true);
StaticMeshComp->SetCollisionProfileName("PhysicsActor");
RootComponent = StaticMeshComp;
RadialComp = CreateDefaultSubobject<URadialForceComponent>(TEXT("RadioComp"));
RadialComp->SetupAttachment(StaticMeshComp);
RadialComp->Radius = 500.0f;
RadialComp->ImpulseStrength = -2000.0f;
RadialComp->bImpulseVelChange = true;
RadialComp->bAutoActivate = false;
//球星碰撞体的初始化
SphereComp = CreateDefaultSubobject<USphereComponent>(TEXT("SphereComp"));
SphereComp->SetSphereRadius(500.0f, true);
SphereComp->OnComponentBeginOverlap.Add(OnOverlapBegin);
SphereComp->SetupAttachment(StaticMeshComp);
}
// Called when the game starts or when spawned
void ASExplosiveBarrel::BeginPlay()
{
Super::BeginPlay();
}
// Called every frame
void ASExplosiveBarrel::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
}
//造成伤害的函数,对列表内的每一个玩家造成伤害
void Make_Damage(TArray<USAttributeComponent*> CharacterList)
{
for (int32 i = 0; i < CharacterList.Num(); i++)
{
if (CharacterList[i])
{
CharacterList[i]->ApplyHealthChange(-50.0f);
}
}
}
void ASExplosiveBarrel::OnComponentHit(UPrimitiveComponent* HitComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, FVector NormalImpulse, FHitResult Hit)
{
//做个判断,碰撞的物体只有魔法子弹才能引爆
if (Cast<ASFatherMagicProjectile>(OtherActor))
{
RadialComp->FireImpulse();
UE_LOG(LogTemp, Log, TEXT("OnActorHit in Explosive Barrel"));
UE_LOG(LogTemp, Warning, TEXT("OtherActor: %s, at game time :%f"), *GetNameSafe(OtherActor), GetWorld()->TimeSeconds);
FString CombinedString = FString::Printf(TEXT("Hit at Location:%s"), *Hit.ImpactPoint.ToString());
DrawDebugString(GetWorld(), Hit.ImpactPoint, CombinedString, nullptr, FColor::Green, 2.0f, true, 1.0f);
Make_Damage(CharacterList);
}
}
//当发生重叠的时候,我们就把玩家的人物加到TArray里头,我这里会用一个TArray是考虑到多人环境下,所有的玩家都会收到影响
void ASExplosiveBarrel::OnComponentBeginOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
ASCharacter* Character = Cast<ASCharacter>(OtherActor);
if (Character)
{
UE_LOG(LogTemp, Log, TEXT("Character Added"));
CharacterList.Add(Cast<USAttributeComponent>(Character->AttributeComp));
}
}
写到这里突然意识到没有设置结束重叠的时候要把玩家指针给从TArray中删除了,不过不是什么大问题,多写一个函数,然后直接用Character.Remove()函数移除掉我们想要移除的那个指针就行了。
此处是widget的蓝图结构: