教程的链接:https://www.bilibili.com/video/BV1nU4y1X7iQ
总结一下这次的任务点:
- 用PlayerState来做一个Credit系统,需要在我们的ui内显示我们的分数
- 更新血药对象,每次使用血药都会扣除相应的分数
- 新增一个金币对象,每次和一个金币对象交互就会增加一定的分数
- 在地图内随机生成金币和血药
- 金币和血药需要从同一个父类中继承下来
整个分数系统和我们的血量其实差不多,无非就是增加减少,检查剩余的分数够不够我们用来和血药交互的。还是老规矩,直接上代码
SPlayerState.h
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/PlayerState.h"
#include "Delegates/DelegateCombinations.h"
#include "SPlayerState.generated.h"
//蓝图宏
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnCreditChange, float, NewCredit);
UCLASS()
class ACTIONROGUELIKE_API ASPlayerState : public APlayerState
{
GENERATED_BODY()
ASPlayerState();
//蓝图节点
UPROPERTY(BlueprintAssignable)
FOnCreditChange OnCreditChange;
private:
float Credit;
private:
float CreditMax;
public:
UFUNCTION(BlueprintCallable,Category="Credit")
const float GetCredit() {
return Credit;
}
public:
UFUNCTION(BlueprintCallable, Category = "Credit")
void AddCredit(float DeltaCredit);
};
SPlayerState.cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "SPlayerState.h"
ASPlayerState::ASPlayerState()
{
CreditMax = 100;
Credit = 0;
}
void ASPlayerState::AddCredit(float DeltaCredit)
{
//我给分数加了个上下限,这个功能可加可不加
Credit = FMath::Clamp(Credit + DeltaCredit, 0, CreditMax);
//每次更改都会在日志里头打印出来
UE_LOG(LogTemp, Warning, TEXT("Credit:%f"),Credit);
//蓝图节点广播
OnCreditChange.Broadcast(Credit);
}
各位应该看得出来,我创建的这个类只有得分这一项,其实看APlayerState.h里头你也能看到一个和分数一样的东西,就是Score。这个类在这里的作用单纯是存储我们的Credit用的,然后提供get set方法而已。同时我也在代码内写了一个广播,那么我在蓝图编辑器内是这样写的。
然后就是我们的父类设计,我这里把它命名为SBaseCreditUseActor,因为我能找到的共同点就这点了。在这个类里面我使用了Interface,也就是说我们需要对着这些物品摁e才会触发事件。
SBaseCreditUseActor.h
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "SGameplayInterface.h"
#include "SPlayerState.h"
#include "SBaseCreditUseActor.generated.h"
UCLASS()
class ACTIONROGUELIKE_API ASBaseCreditUseActor : public AActor , public ISGameplayInterface
{
GENERATED_BODY()
//接口实现
void Interact_Implementation(APawn* InstigatorPawn) override;
UPROPERTY(EditAnywhere,Category="Component")
class UStaticMeshComponent* MeshComp;
public:
//用来判断能否执行实现函数
UFUNCTION(BlueprintCallable,Category="Credit")
bool CanImplement(ASPlayerState* ASP);
//实现函数
UFUNCTION(BlueprintCallable,Category="Implementation")
virtual void Implementation(ASPlayerState* ASP , APawn* InstigatorPawn);
public:
//CreditNeed的get set方法
UFUNCTION()
void SetCreditNeed(float Creditneed);
const float GetCreditNeed() {
return CreditNeed;
}
private:
float CreditNeed;
public:
// Sets default values for this actor's properties
ASBaseCreditUseActor();
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
public:
// Called every frame
virtual void Tick(float DeltaTime) override;
};
SBaseCreditUseActor.cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "SBaseCreditUseActor.h"
//接口实现函数
void ASBaseCreditUseActor::Interact_Implementation(APawn* InstigatorPawn)
{
if (!CanImplement(InstigatorPawn->GetPlayerState<ASPlayerState>()))
{
return;
}
Implementation(InstigatorPawn->GetPlayerState<ASPlayerState>(),InstigatorPawn);
}
//判断能否执行实现函数的函数
bool ASBaseCreditUseActor::CanImplement(ASPlayerState*ASP)
{
return ASP->GetCredit() >= CreditNeed;
}
//实现函数,这里我没写是因为这里的函数每个子类都不相同,所以我们这里的函数都会在子类里面重写
void ASBaseCreditUseActor::Implementation(ASPlayerState* ASP, APawn* InstigatorPawn)
{
}
//set方法
void ASBaseCreditUseActor::SetCreditNeed(float Creditneed)
{
CreditNeed = Creditneed;
}
// Sets default values
ASBaseCreditUseActor::ASBaseCreditUseActor()
{
// 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;
MeshComp = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Mesh Component"));
RootComponent = MeshComp;
//CreditNeed默认值
CreditNeed = 25;
}
// Called when the game starts or when spawned
void ASBaseCreditUseActor::BeginPlay()
{
Super::BeginPlay();
}
// Called every frame
void ASBaseCreditUseActor::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
}
这一串代码其实就是我在之前的血瓶的代码的基础上抽象加上一点优化得到的代码,能适应大部分的情况,不过少数的子类仍然需要重写大量的父类函数。
首先是稍微简单一点的金币
SCoin.h
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "SBaseCreditUseActor.h"
#include "SCoin.generated.h"
/**
*
*/
UCLASS()
class ACTIONROGUELIKE_API ASCoin : public ASBaseCreditUseActor
{
GENERATED_BODY()
ASCoin();
virtual void Implementation(ASPlayerState* ASP, APawn* InstigatorPawn);
};
SCoin.cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "SCoin.h"
ASCoin::ASCoin()
{
SetCreditNeed(0.0f);
}
void ASCoin::Implementation(ASPlayerState* ASP, APawn* InstigatorPawn)
{
ASP->AddCredit(10);
GetWorld()->DestroyActor(this);
}
这个类基本上没有做太多的改动
在之后就是改动比较大的血瓶类
SHealthPotion.h
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "SBaseCreditUseActor.h"
#include "SHealthPotion.generated.h"
/**
*
*/
UCLASS()
class ACTIONROGUELIKE_API ASHealthPotion : public ASBaseCreditUseActor
{
GENERATED_BODY()
void Interact_Implementation(APawn* InstigatorPawn) override;
virtual void Implementation(ASPlayerState* ASP, APawn* InstigatorPawn) override;
UPROPERTY(EditDefaultsOnly,Category="Health")
float HealthDelta;
ASHealthPotion();
};
SHealthPotion.cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "SHealthPotion.h"
#include "SAttributeComponent.h"
void ASHealthPotion::Interact_Implementation(APawn* InstigatorPawn)
{
USAttributeComponent* AttributeComp = USAttributeComponent::GetArrtibutes(InstigatorPawn);
//主要改动是if的判断,我需要除了credit的扣除检测之外还要检测玩家血量,不能说满血也能扣除分数进行补血
if (!CanImplement(InstigatorPawn->GetPlayerState<ASPlayerState>())||AttributeComp->GetHealth()==100)
{
return;
}
Implementation(InstigatorPawn->GetPlayerState<ASPlayerState>(), InstigatorPawn);
}
void ASHealthPotion::Implementation(ASPlayerState* ASP, APawn* InstigatorPawn)
{
USAttributeComponent* AttributeComp = USAttributeComponent::GetArrtibutes(InstigatorPawn);
AttributeComp->ApplyHealthChange(this, HealthDelta);
ASP->AddCredit(-GetCreditNeed());
GetWorld()->DestroyActor(this);
}
ASHealthPotion::ASHealthPotion()
{
SetCreditNeed(25);
HealthDelta = 20.0f;
}
写完以上这些之后,就剩下随机生成的问题了,还记得之前的机器人生成的那个函数吗?我们用相同的方法来做。
SGameModeBase.h
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/GameModeBase.h"
#include "EnvironmentQuery/EnvQueryTypes.h"
#include "../../../../../UE_5.1/Engine/Source/Runtime/AIModule/Classes/EnvironmentQuery/EnvQueryInstanceBlueprintWrapper.h"
#include "SGameModeBase.generated.h"
/**
*
*/
UCLASS()
class ACTIONROGUELIKE_API ASGameModeBase : public AGameModeBase
{
GENERATED_BODY()
protected:
UPROPERTY(EditDefaultsOnly,Category="AI")
class UEnvQuery* SpawnBotQuery;
UPROPERTY(EditDefaultsOnly, Category = "AI")
class UEnvQuery* SpawnHealthQuery;
UPROPERTY(EditDefaultsOnly,Category="AI")
TSubclassOf<AActor> MinionClass;
UPROPERTY(EditDefaultsOnly, Category = "AI")
TSubclassOf<AActor> HealthClass;
UPROPERTY(EditDefaultsOnly,Category="AI")
class UCurveFloat* DifficultyCurve;
FTimerHandle TimerHandle_SpawnBots;
UPROPERTY(EditDefaultsOnly,Category = "AI")
float SpawnTimerInterval;
UFUNCTION()
void SpawnBotTimerElapsed();
UFUNCTION()
void SpawnHealth();
UFUNCTION()
void OnQueryCompleted(class UEnvQueryInstanceBlueprintWrapper* QueryInstance, EEnvQueryStatus::Type QueryStatus);
UFUNCTION()
void OnQueryEnd(class UEnvQueryInstanceBlueprintWrapper* QueryInstance, EEnvQueryStatus::Type QueryStatus);
public:
ASGameModeBase();
public:
virtual void StartPlay() override;
UFUNCTION(Exec)
void KillAll();
UFUNCTION()
void RespawnPlayerElapsed(AController* Controller);
public:
virtual void OnActorKilled(AActor* VictimActor, AActor* Killer);
};
SGameModeBase.cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "SGameModeBase.h"
#include "EnvironmentQuery/EnvQueryManager.h"
#include "EnvironmentQuery/EnvQueryTypes.h"
#include "EnvironmentQuery/EnvQueryInstanceBlueprintWrapper.h"
#include "../Public/AI/SAICharacter.h"
#include "../Public/SAttributeComponent.h"
#include "../../../../../UE_5.1/Engine/Source/Runtime/Engine/Public/EngineUtils.h"
#include "SCharacter.h"
#include "SPlayerState.h"
#include "SHealthPotion.h"
static TAutoConsoleVariable<bool> CVarSpawnBots(TEXT("su.SpawnBots"),true,TEXT("Enable Spawing Bots via timer."), ECVF_Cheat);
void ASGameModeBase::SpawnBotTimerElapsed()
{
//看到下面这个函数没,就是我们插入的函数(其实是我偷懒,懒得再写一个timer了)
SpawnHealth();
if (!CVarSpawnBots.GetValueOnGameThread())
{
return;
}
int32 NrOfAliveBots = 0;
for (TActorIterator<ASAICharacter>It(GetWorld()); It; ++It)
{
ASAICharacter* Bot = *It;
USAttributeComponent* AttributeComp = USAttributeComponent::GetArrtibutes(Bot);
if (ensure(AttributeComp) && AttributeComp->IsAlive())
{
NrOfAliveBots++;
}
}
float MaxBotCount = 10.0f;
if (DifficultyCurve)
{
MaxBotCount = DifficultyCurve->GetFloatValue(GetWorld()->TimeSeconds);
}
if (NrOfAliveBots >= MaxBotCount)
{
return;
}
UEnvQueryInstanceBlueprintWrapper* QuetyInstance = UEnvQueryManager::RunEQSQuery(this,SpawnBotQuery,this,EEnvQueryRunMode::RandomBest5Pct,nullptr);
if (ensure(QuetyInstance))
{
FScriptDelegate QueryCompleted;
QueryCompleted.BindUFunction(this, STATIC_FUNCTION_FNAME(TEXT("&ASGameModeBase::OnQueryCompleted")));
QuetyInstance->GetOnQueryFinishedEvent().Add(QueryCompleted);
}
}
void ASGameModeBase::SpawnHealth()
{
int32 HealthExist = 0;
for (TActorIterator<ASHealthPotion>It(GetWorld()); It; ++It)
{
HealthExist++;
}
float MaxHealth = 5;
if (MaxHealth<=HealthExist)
{
return;
}
UEnvQueryInstanceBlueprintWrapper* QueryInstance = UEnvQueryManager::RunEQSQuery(this, SpawnHealthQuery, this, EEnvQueryRunMode::RandomBest25Pct, nullptr);
if (ensure(QueryInstance))
{
FScriptDelegate QueryStop;
QueryStop.BindUFunction(this, STATIC_FUNCTION_FNAME(TEXT("&ASGameModeBase::OnQueryEnd")));
QueryInstance->GetOnQueryFinishedEvent().Add(QueryStop);
}
}
void ASGameModeBase::OnQueryCompleted(UEnvQueryInstanceBlueprintWrapper* QueryInstance, EEnvQueryStatus::Type QueryStatus)
{
if (QueryStatus != EEnvQueryStatus::Success)
{
UE_LOG(LogTemp,Warning,TEXT("Spawn EQS Failed!!!"))
return;
}
TArray<FVector> Locations = QueryInstance->GetResultsAsLocations();
if (Locations.IsValidIndex(0))
{
GetWorld()->SpawnActor<AActor>(MinionClass,Locations[0],FRotator::ZeroRotator);
}
}
void ASGameModeBase::OnQueryEnd(class UEnvQueryInstanceBlueprintWrapper* QueryInstance, EEnvQueryStatus::Type QueryStatus)
{
if (QueryStatus != EEnvQueryStatus::Success)
{
UE_LOG(LogTemp, Warning, TEXT("Spawn EQS Failed!!!"))
return;
}
TArray<FVector> Locations = QueryInstance->GetResultsAsLocations();
if (Locations.IsValidIndex(0))
{
GetWorld()->SpawnActor<AActor>(HealthClass, Locations[0], FRotator::ZeroRotator);
}
}
ASGameModeBase::ASGameModeBase()
{
SpawnTimerInterval = 2.0f;
}
void ASGameModeBase::StartPlay()
{
Super::StartPlay();
GetWorldTimerManager().SetTimer(TimerHandle_SpawnBots, this, &ASGameModeBase::SpawnBotTimerElapsed, SpawnTimerInterval, true);
}
void ASGameModeBase::KillAll()
{
for (TActorIterator<ASAICharacter>It(GetWorld()); It; ++It)
{
ASAICharacter* Bot = *It;
USAttributeComponent* AttributeComp = USAttributeComponent::GetArrtibutes(Bot);
if (ensure(AttributeComp) && AttributeComp->IsAlive())
{
AttributeComp->Kill(this);
}
}
}
void ASGameModeBase::RespawnPlayerElapsed(AController* Controller)
{
if (ensure(Controller))
{
Controller->UnPossess();
RestartPlayer(Controller);
//重生会扣50点Credit
ASPlayerState* ASP = Cast<ASPlayerState>(Controller->PlayerState);
if (ASP)
{
ASP->AddCredit(-50.0f);
}
}
}
//玩家被杀后复活的逻辑
void ASGameModeBase::OnActorKilled(AActor* VictimActor, AActor* Killer)
{
ASCharacter* Player = Cast<ASCharacter>(VictimActor);
if (Player)
{
FTimerHandle TimerHandle_RespawnDelay;
FTimerDelegate Delegate;
Delegate.BindUFunction(this,"RespawnPlayerElapsed",Player->GetController());
float RespawnDelay = 2.0f;
GetWorldTimerManager().SetTimer(TimerHandle_RespawnDelay,Delegate, RespawnDelay,false);
}
}
仿照之前写的那个机器人生成的方法,同时我们把生成的函数直接插入到我们timer结束就会执行的那个函数里头(这是个偷懒的方法),有点投机取巧不过很方便。