CoopGame08-游戏模式

type: Post
status: Published
date: 2022/10/20
slug: CoopGame08
summary: UE4 C++多人游戏入门
category: Unreal

跟随B站up主“技术宅阿棍儿”的教程制作的笔记。教程链接

1.概要

  • 用GameMode生成回合制的敌人
  • 击杀奖励
  • 处理游戏状态,例GameOver
  • 玩家重生
  • 搭建场景

    2.EQS(场景查询系统)

1.开启(各引擎版本不同)

1.编辑器偏好设置-通用-实验性功能-AI-Environment Query System
2.插件-Environment Query Editor(4.27版本默认开启)

2.新建蓝图类EnvQueryContext_BotSpawns继承EnvQueryContext_BlueprintBase环境查询情景蓝图基础。

重载提供Actor集ProvideActorSet函数,获取TargetPoint的所有Actor

CoopGame08-游戏模式

3.新建蓝图类EnvQueryContext_AllPlayers继承EnvQueryContext_BlueprintBase。

重载提供Actor集ProvideActorSet函数,获取SCharacter的所有Actor

CoopGame08-游戏模式

4.创建环境查询EQS_FindSpawnLocation(右键-人工智能-环境查询)

从根拉出Points:Grid设置

CoopGame08-游戏模式

右键添加测试Distance

CoopGame08-游戏模式

编写BP_TestGameMode蓝图

CoopGame08-游戏模式

场景中添加目标点。

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++类,编写打印状态的蓝图。

CoopGame08-游戏模式

4.修改BP_TestGameMode中的游戏状态类为BP_GameState

5.可以选择设置AI掉下平台就摧毁。

CoopGame08-游戏模式

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

CoopGame08-游戏模式

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

文章目录