跳过正文

CoopGame08-游戏模式

··3182 字·7 分钟
coopgame - 这篇文章属于一个选集。
§ 8: 本文

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

Untitled.webp

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

重载提供Actor集Prhttps://s3.zmingu.com/imagesr

Untitled 1.webp

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

从根拉出Points:Grid设置

Untitled 2.webp

右键添加测试Distance

Untitled 3.webp

编写BP_TestGameMode蓝图

Untitled 4.webp

场景中添加目标点。

###3.增加回合机制

1..创建SGameModeC++类继承GameModeBaseC++类并编写。

SGameMode.h

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
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

1
2
3
4
5
protected:
	UPROPERTY(ReplicatedUsing=OnRep_Health,BlueprintReadOnly,Category="HealthComponent")
	float Health;
public:
	float GetHealth() const;//用来获取Health生命值,Health生命值为protected较合理。

SHealthComponent.cpp

1
2
3
4
float USHealthComponent::GetHealth() const
{
	return Health;
}

SGameMode.h

1
2
3
4
5
protected:
	FTimerHandle TimerHandle_NextWaveStart;//管理下一关开启时间
	void CheckWaveState();
public:
	virtual void Tick(float DeltaSeconds) override;

SGameMode.cpp

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
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

1
2
3
4
5
protected:

	void CheckAnyPlayerAlive(); //检查玩家存活

	void GameOver();

SGameMode.cpp

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
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

1
2
3
public:
	//用来设置新的游戏状态
	void SetWaveState(EWaveState NewState);

SGameMode.cpp

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
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++类,编写打印状态的蓝图。

Untitled 5.webp

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

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

Untitled 6.webp

6.运行结果:左上角正确显示游戏状态

###5.使用PlayerState类同步玩家状态

1.创建SPlayerState C++类,继承PlayerState C++类,编写添加分数的函数AddScore。

SPlayerState.h

1
2
    UFUNCTION(BlueprintCallable,Category="PlayerState")
	void AddScore(float ScoreDelta);

SPlayerState.cpp

1
2
3
4
5
6
void ASPlayerState::AddScore(float ScoreDelta)
{
	float Score = GetScore();
	Score += ScoreDelta;
	SetScore(Score);
}

2.SGameMode新委托,需要在蓝图中绑定。

SGameMode.h

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
//添加三参数的委托(死者,造成伤害者,控制器)
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

1
2
3
4
5
6
ASGameMode::ASGameMode()
{
//...
	//更换GM的玩家状态
	PlayerStateClass = ASPlayerState::StaticClass();
}

3.修改SHealthComponent类,标记死亡。并调用GM中的委托,广播出去有人死了

SHealthComponent.h

1
	bool bIsDead;

SHealthComponent.cpp

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
    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

Untitled 7.webp

###5.玩家重生机制

1.玩家复活是在准备下一个关卡之后复活,修改SGameMode类

SGameMode.h

1
2
public:
    void RestartPlayers();

SGameMode.cpp

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
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

coopgame - 这篇文章属于一个选集。
§ 8: 本文