UE5.1.1 C++从0开始(16.作业5思路分享)

教程介绍了如何用PlayerState建立信用系统,包括在UI中显示分数更新、血药和金币的交互以及地图上的随机生成。血药和金币都从一个父类继承,父类实现了交互接口。代码示例展示了如何添加、检查和广播分数变化,以及如何在蓝图中使用。金币和血药的子类覆盖父类方法以实现特定行为,如血药需要额外检查玩家血量。最后,文章提到了如何在游戏模式中定时生成这些物品。
摘要由CSDN通过智能技术生成

教程的链接:https://www.bilibili.com/video/BV1nU4y1X7iQ

总结一下这次的任务点:

  1. 用PlayerState来做一个Credit系统,需要在我们的ui内显示我们的分数
  2. 更新血药对象,每次使用血药都会扣除相应的分数
  3. 新增一个金币对象,每次和一个金币对象交互就会增加一定的分数
  4. 在地图内随机生成金币和血药
  5. 金币和血药需要从同一个父类中继承下来

整个分数系统和我们的血量其实差不多,无非就是增加减少,检查剩余的分数够不够我们用来和血药交互的。还是老规矩,直接上代码

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结束就会执行的那个函数里头(这是个偷懒的方法),有点投机取巧不过很方便。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
对不起,我的回答中有误。在UE5.1.1中,uperarm_r的旋转坐标是由四个值构成的,分别是Roll、Pitch、Yaw和W。因此,需要将mediapipe的坐标系转换为UE5.1.1的左手坐标系,然后计算uperarm_r的四元数值,最后将四元数值赋给uperarm_r的旋转坐标。 具体的计算方法如下: 1. 将mediapipe的坐标系转换为UE5.1.1的左手坐标系。具体来说,需要将y坐标和z坐标取负,即将(0.546142, 0.315784, -0.148612)转换为(0.546142, -0.315784, 0.148612)。 2. 计算uperarm_r的旋转四元数值。具体来说,可以使用UE5.1.1中的FQuat::FindBetweenVectors函数来计算uperarm_r的方向向量和转换后的坐标的四元数值。具体的计算方法如下: ``` FVector ShoulderPos(0.546142, -0.315784, 0.148612); // 转换后的坐标 FVector UperarmDir(1.0f, 0.0f, 0.0f); // uperarm_r的方向向量 FQuat UperarmQuat = FQuat::FindBetweenVectors(UperarmDir, ShoulderPos.GetSafeNormal()); ``` 3. 将计算得到的uperarm_r的四元数值赋给uperarm_r的旋转坐标。具体来说,需要使用UE5.1.1中的USkeletalMeshComponent::SetBoneRotationByName函数来设置uperarm_r的旋转坐标。具体的代码如下: ``` USkeletalMeshComponent* SkeletalMeshComponent; // 需要替换成实际的SkeletalMeshComponent SkeletalMeshComponent->SetBoneRotationByName(TEXT("uperarm_r"), UperarmQuat, EBoneSpaces::WorldSpace); ``` 需要注意的是,以上计算过程中需要考虑坐标系的转换和旋转角度的顺序等因素,因此具体的计算方法可能会有所不同。建议在实际使用过程中,根据具体的需求和场景进行调整和优化。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

楚江_wog1st

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值