CoopGame08-游戏模式
type: Post
status: Published
date: 2022/10/20
slug: CoopGame08
summary: UE4 C++多人游戏入门
category: Unreal
跟随B站up主“技术宅阿棍儿”的教程制作的笔记。教程链接
1.概要
1.开启(各引擎版本不同)
1.编辑器偏好设置-通用-实验性功能-AI-Environment Query System
2.插件-Environment Query Editor(4.27版本默认开启)2.新建蓝图类EnvQueryContext_BotSpawns继承EnvQueryContext_BlueprintBase环境查询情景蓝图基础。
重载提供Actor集ProvideActorSet函数,获取TargetPoint的所有Actor
3.新建蓝图类EnvQueryContext_AllPlayers继承EnvQueryContext_BlueprintBase。
重载提供Actor集ProvideActorSet函数,获取SCharacter的所有Actor
4.创建环境查询EQS_FindSpawnLocation(右键-人工智能-环境查询)
从根拉出Points:Grid设置
右键添加测试Distance
编写BP_TestGameMode蓝图
场景中添加目标点。
3.增加回合机制
1..创建SGameModeC++类继承GameModeBaseC++类并编写。
SGameMode.h
protected:
FTimerHandle TimerHandle_BotSpawner;
//生成AI数量
int32 NrOfBotsToSpawn;
//关卡数
int32 WaveCount;
//关卡之间的间隔时间
UPROPERTY(EditDefaultsOnly, Category=GameMode)
float TimeBetweenWaves;
//生成新的机器人函数,蓝图实现
UFUNCTION(BlueprintImplementableEvent, Category=GameMode)
void SpawnNewBot();
//生成机器人时间结束
void SpawnBotTimerElapsed();
//开始关卡
void StartWave();
//结束关卡
void EndWave();
public:
ASGameMode();
virtual void StartPlay() override;
//准备下一个关卡
void PrepareForNextWave();
public:
ASGameMode();
virtual void StartPlay() override;
void PrepareForNextWave();//准备下一个关卡
SGameMode.cpp
ASGameMode::ASGameMode()
{
TimeBetweenWaves = 2;
}
void ASGameMode::StartPlay()
{
Super::StartPlay();
PrepareForNextWave();
}
void ASGameMode::StartWave()
{
WaveCount++;
NrOfBotsToSpawn = 2 *WaveCount;
//每秒调用一次,每秒会生成一个AI
GetWorldTimerManager().SetTimer(TimerHandle_BotSpawner,this,&ASGameMode::SpawnBotTimerElapsed,1,true,0);
}
void ASGameMode::EndWave()
{
GetWorldTimerManager().ClearTimer(TimerHandle_BotSpawner);
PrepareForNextWave();
}
void ASGameMode::SpawnBotTimerElapsed()
{
SpawnNewBot();
NrOfBotsToSpawn--;
//生成完就
if (NrOfBotsToSpawn <= 0)
{
EndWave();
}
}
void ASGameMode::PrepareForNextWave()
{
FTimerHandle TimerHandle_NextWaveStart;
GetWorldTimerManager().SetTimer(TimerHandle_NextWaveStart, this, &ASGameMode::StartWave, TimeBetweenWaves, false);
}
修改BP_TestGameMode蓝图类中的BeginPlay节点为SpawnNewBot,运行结果:生成当前关卡的AI后继续生成后续关卡的AI。
2.完善回合机制
SHealthComponent.h
protected:
UPROPERTY(ReplicatedUsing=OnRep_Health,BlueprintReadOnly,Category="HealthComponent")
float Health;
public:
float GetHealth() const;//用来获取Health生命值,Health生命值为protected较合理。
SHealthComponent.cpp
float USHealthComponent::GetHealth() const
{
return Health;
}
SGameMode.h
protected:
FTimerHandle TimerHandle_NextWaveStart;//管理下一关开启时间
void CheckWaveState();
public:
virtual void Tick(float DeltaSeconds) override;
SGameMode.cpp
ASGameMode::ASGameMode()
{
//...
//修改Tick机制
PrimaryActorTick.bCanEverTick = true;
PrimaryActorTick.TickInterval = 1;
}
void ASGameMode::Tick(float DeltaSeconds)
{
Super::Tick(DeltaSeconds);
CheckWaveState();//检查关卡状态
}
void ASGameMode::CheckWaveState()
{
//是否在准备下一关卡,如果是,说明敌人已经消灭光了
bool bIsPreparingForWave = GetWorldTimerManager().IsTimerActive(TimerHandle_NextWaveStart);
//如果是在准备下一关卡,或者还在生成AI,就不需要检测关卡状态了
if (bIsPreparingForWave||NrOfBotsToSpawn>0)return;
//创建布尔变量标记是否有存活的AI
bool bIsAnyBotAlive = false;
//用Pawn迭代器获取所有Pawn,当然也包括玩家Character
for (FConstPawnIterator It = GetWorld()->GetPawnIterator();It;++It)
{
APawn* Pawn = It->Get();
//判断Pawn是否空,和此Pawn是否被玩家控制,被玩家控制的是玩家角色。是要排除的
if (Pawn == nullptr || Pawn->IsPlayerControlled()) continue;
//尝试拿到AIPawn里的生命值组件,判断生命值是否大于0,大于则活着,只要一个活着就为true
USHealthComponent* HealthComp = Cast<USHealthComponent>(Pawn->GetComponentByClass(USHealthComponent::StaticClass()));
if (HealthComp && HealthComp->GetHealth() > 0)
{
bIsAnyBotAlive = true;
break;
}
}
//如果没有活着的了就准备下一关卡
if (!bIsAnyBotAlive) PrepareForNextWave();
}
void ASGameMode::EndWave()
{
GetWorldTimerManager().ClearTimer(TimerHandle_BotSpawner);
//PrepareForNextWave();不需要在这准备关卡,而是等到判断所有AI死了再准备
}
void ASGameMode::PrepareForNextWave()
{
//FTimerHandle TimerHandle_NextWaveStart;提到头文件
GetWorldTimerManager().SetTimer(TimerHandle_NextWaveStart, this, &ASGameMode::StartWave, TimeBetweenWaves, false);
}
确认BP_TestGameMode蓝图类中的Tick间隔是否为Cpp中设置的1秒,同时删除场景中其他测试的角色,只留出生点,运行结果:在当前关卡的AI都死亡后才生成下一关卡的AI。
3.实现GameOver状态
SGameMode.h
protected:
void CheckAnyPlayerAlive(); //检查玩家存活
void GameOver();
SGameMode.cpp
void ASGameMode::Tick(float DeltaSeconds)
{
Super::Tick(DeltaSeconds);
CheckWaveState();
CheckAnyPlayerAlive();
}
void ASGameMode::CheckAnyPlayerAlive()
{
//如果玩家存活,这个for是一直被Tick调用,一直检测
//使用玩家控制器迭代器获取所有玩家控制器,再又控制器获得玩家Pawn,判断Pawn中的健康组件中的生命值判断玩家是否存活
for (FConstPlayerControllerIterator It_PC = GetWorld()->GetPlayerControllerIterator(); It_PC; ++It_PC)
{
APlayerController* PC = It_PC->Get();
APawn* Pawn = PC->GetPawn();
if (PC && Pawn)
{
USHealthComponent* HealthComp = Cast<USHealthComponent>(
Pawn->GetComponentByClass(USHealthComponent::StaticClass()));
//ensure判空提供更多信息
if (ensure(HealthComp) && HealthComp->GetHealth() > 0)return;
}
}
GameOver();
}
void ASGameMode::GameOver()
{
EndWave();//会结束一个TimerHandle_BotSpawner
UE_LOG(LogTemp,Log,TEXT("游戏结束,玩家死了"));
}
运行结果:当玩家死后,日志打印游戏结束。
4.使用GameState类同步游戏状态
1.创建SGameState类,继承GameStateBase类的C++类。
SGameState.h
UENUM(BlueprintType)
enum class EWaveState:uint8
{
//等待开始
WaitingToStart,
//开始生成AI
WaveInProgress,
//AI生成完毕,等待通关
WaitingToComplete,
//通关
WaveComplete,
//游戏失败
GameOver,
};
UCLASS()
class MULTIPLESHOOT_CFGO_API ASGameState : public AGameStateBase
{
GENERATED_BODY()
protected:
//网络同步游戏状态,当前状态改变时会调用这个函数。
UFUNCTION()
void OnRep_WaveState(EWaveState OldState);
//状态改变
UFUNCTION(BlueprintImplementableEvent, Category=GameState)
void WaveStatedChanged(EWaveState NewState, EWaveState OldState);
//当前状态
UPROPERTY(BlueprintReadOnly, ReplicatedUsing=OnRep_WaveState, Category=GameState)
EWaveState WaveState;
};
SGameState.cpp
void ASGameState::OnRep_WaveState(EWaveState OldState)
{
WaveStatedChanged(WaveState,OldState);
}
void ASGameState::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
//属性同步条件
DOREPLIFETIME(ASGameState,WaveState);
}
2.修改SGameMode代码,设置每个阶段的游戏状态。
SGameMode.h
public:
//用来设置新的游戏状态
void SetWaveState(EWaveState NewState);
SGameMode.cpp
ASGameMode::ASGameMode()
{
//...
//更换GM的游戏状态
GameStateClass = ASGameState::StaticClass();
}
void ASGameMode::PrepareForNextWave()
{
//...
//进入等待开始状态
SetWaveState(EWaveState::WaitingToStart);
}
void ASGameMode::StartWave()
{
//...
//进入生成AI状态
SetWaveState(EWaveState::WaveInProgress);
}
void ASGameMode::EndWave()
{
//...
//进入生成完毕,等待游戏完成状态,下一步要么AI死光,要么角色死光。在Tike中检查。
SetWaveState(EWaveState::WaitingToComplete);
}
void ASGameMode::CheckWaveState()
{
//...
for (FConstPawnIterator It = GetWorld()->GetPawnIterator(); It; ++It)
{
//...
}
if (!bIsAnyBotAlive)
{
//...
//进入关卡完毕状态,
SetWaveState(EWaveState::WaveComplete);
}
}
void ASGameMode::GameOver()
{
//...
//进入游戏结束状态
SetWaveState(EWaveState::GameOver);
}
void ASGameMode::SetWaveState(EWaveState NewState)
{
ASGameState* GS = GetGameState<ASGameState>();
if (ensureAlways(GS))
{
GS->SetWaveState(NewState);
}
}
3.创建BP_GameState蓝图类,继承SGameState C++类,编写打印状态的蓝图。
4.修改BP_TestGameMode中的游戏状态类为BP_GameState
5.可以选择设置AI掉下平台就摧毁。
6.运行结果:左上角正确显示游戏状态
5.使用PlayerState
类同步玩家状态
1.创建SPlayerState C++类,继承PlayerState C++类,编写添加分数的函数AddScore。
SPlayerState.h
UFUNCTION(BlueprintCallable,Category="PlayerState")
void AddScore(float ScoreDelta);
SPlayerState.cpp
void ASPlayerState::AddScore(float ScoreDelta)
{
float Score = GetScore();
Score += ScoreDelta;
SetScore(Score);
}
2.SGameMode新委托,需要在蓝图中绑定。
SGameMode.h
//添加三参数的委托(死者,造成伤害者,控制器)
DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FOnActorKilled,AActor*,VictimActor,AActor*,KillerActor,AController*,KillerController);
class MULTIPLESHOOT_CFGO_API ASGameMode : public AGameModeBase
{
GENERATED_BODY()
//...
public:
UPROPERTY(BlueprintAssignable,Category="GameMode")
FOnActorKilled OnActorKilled;
}
SGameMode.cpp
ASGameMode::ASGameMode()
{
//...
//更换GM的玩家状态
PlayerStateClass = ASPlayerState::StaticClass();
}
3.修改SHealthComponent类,标记死亡。并调用GM中的委托,广播出去有人死了
SHealthComponent.h
bool bIsDead;
SHealthComponent.cpp
USHealthComponent::USHealthComponent()
{
//...
bIsDead = false;
}
void USHealthComponent::HandleTakeAnyDamage(AActor* DamagedActor, float Damage, const UDamageType* DamageType,
AController* InstigatedBy, AActor* DamageCauser)
{
//if (Damage <= 0)return;
if (Damage <= 0 || bIsDead)return;
//...
bIsDead = Health <= 0;
if (bIsDead)
{
ASGameMode* GM = Cast<ASGameMode>(GetWorld()->GetAuthGameMode());
if (GM)
{
GM->OnActorKilled.Broadcast(GetOwner(),DamageCauser,InstigatedBy);
}
}
}
4.编写BP_TestGameMode蓝图类,首先确保GM中的玩家状态类为SPlayerState
5.玩家重生机制
1.玩家复活是在准备下一个关卡之后复活,修改SGameMode类
SGameMode.h
public:
void RestartPlayers();
SGameMode.cpp
void ASGameMode::PrepareForNextWave()
{
//...
RestartPlayers();
}
void ASGameMode::RestartPlayers()
{
for (FConstPlayerControllerIterator It_PC = GetWorld()->GetPlayerControllerIterator(); It_PC; ++It_PC)
{
APlayerController* PC = It_PC->Get();
//这里要角色死后调用了Destroy()函数才能进去。
if (PC && PC->GetPawn() == nullptr)
{
RestartPlayer(PC);
}
}
}
2.运行结果:在玩家没有全部死亡,并且通关后,会复活已经死亡的角色
5.快速搭建简单场景工具BSP-Tools
略