前言

今天完成了教程的 42 - 48 小节,很遗憾没有完成作者一整天的提交,因为动画部分实在是太繁琐了。动画部分打算之后再专门总结一下。今天的教程中依然涉及了网络同步方面的内容,因此还是主要记录这方面的知识点。

Crouch

ACharacter 中已经内置了 Crouch 的功能,并且已经设置了自动改变碰撞体以及网络同步,十分方便。想要使用该函数,需要在角色的类中开启如下设置,UnCrouch 功能同理。

1
GetCharacterMovement()->NavAgentProps.bCanCrouch = true;  // 设置了此项才能够蹲下

继续探究 Replicate 与 RPC 的关系

为了在客户端与服务器端都同步 “瞄准” 这一动作,需要继续设置网络同步,让我们来思考一下应该如何设计:首先,瞄准这一动作肯定需要改变动画,因此在 AnimInstance 里声明一个新的布尔变量不可避免,并且为了同步,需要把这个变量声明为 Replicated

通过之前的文章我们知道:Replication 一般只能从服务器端同步到客户端,这意味着 服务器端操控的角色 的动作能够同步到其他客户端,但按下按键这一行为如果由客户端发起,即使 bAiming 被标记为了 Replicated,该信息也无法传递给 服务器端的客户端角色,因此会出现客户端进入瞄准姿态,但是服务器端的对应角色并没有进入这个姿态的现象。

为了由客户端传递信息到服务器端,需要使用 ServerRPC,即写一个 ServerSetAiming 函数,在 SetAiming 中调用,这样一来当客户端调用 SerAiming 进行瞄准时,会自动发送信息到服务器端,使得服务器端的角色进行相同的动作;同时,服务器端操控的角色 动作本身已经由 Replicated 同步到了客户端;这样一来双向的信息传递就不会出现问题。

1
2
3
// CombatComponents.h
UPROPERTY(Replicated)
bool bAiming;
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
// CombatComponents.cpp
void UCombatComponent::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);

DOREPLIFETIME(UCombatComponent, EquippedWeapon);
DOREPLIFETIME(UCombatComponent, bAiming);
}

void UCombatComponent::SetAiming(bool bIsAiming)
{
bAiming = bIsAiming;
ServerSetAiming(bIsAiming);
if (Character)
{
Character->GetCharacterMovement()->MaxWalkSpeed = bIsAiming ? AimWalkSpeed : BaseWalkSpeed;
}
}

void UCombatComponent::ServerSetAiming_Implementation(bool bIsAiming)
{
bAiming = bIsAiming;
if (Character)
{
Character->GetCharacterMovement()->MaxWalkSpeed = bIsAiming ? AimWalkSpeed : BaseWalkSpeed;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// BlasterCharacter.cpp
void ABlasterCharacter::AimButtonPressed()
{
if (Combat)
{
Combat->SetAiming(true);
}
}

void ABlasterCharacter::AimButtonReleased()
{
if (Combat)
{
Combat->SetAiming(false);
}
}

为了验证这一系列的逻辑,可以分别进行实验。

Test1

注释掉下面三行,这样逻辑就变成了:bAiming 属性本身就不会复制,并且客户端不会向服务器端发送 RPC。因此,所有的动作都只有本地才能看到。

1
2
3
4
5
// UPROPERTY(Replicated)

// DOREPLIFETIME(UCombatComponent, bAiming);

// ServerSetAiming(bIsAiming);

服务器端瞄准

仅有服务器端可以看到效果。

客户端瞄准

仅有客户端可以看到效果。

运行的结果确实符合预期。

Test2

bAiming 设置为复制,但是依然不调用 ServerRPC,这样一来服务器端操控角色的信息能够正确同步到客户端,因此在客户端也能看到服务器端操控角色的瞄准动作,但客户端的瞄准动作只有本地可以看到,不会同步到服务器。

1
2
3
4
5
UPROPERTY(Replicated)

DOREPLIFETIME(UCombatComponent, bAiming);

// ServerSetAiming(bIsAiming);

服务器端瞄准

服务器与客户端均可以看见效果

客户端瞄准

仅有客户端可以看见效果

运行的结果也符合预期。

其他

排列组合下来可以做很多实验,这里就不一一赘述了。但值得注意的是,在上面的逻辑中,如果是客户端调用 SetAimimg,会先将本地的 bAiming 状态改变,再调用 ServerRPC 使得服务器上的状态改变,但实际上,由于设置了 Replicate,因此可以直接调用 ServerRPC 再将状态同步到本地,这样依然可以达到效果。

但是,这就涉及到了一个问题:由于瞄准是玩家按下按键就期望立刻得到反馈的事件,如果不在 SetAimimg 中设置客户端本地的 bAiming 而是等到发送 ServerRPC 再同步给客户端,这样在网络延迟很高的情况下,可能会出现玩家按下按键却没有反馈的情况,这显然是不符合游戏设计的逻辑的。

因此,对于这种需要及时反馈的事件,还是应该先改变玩家本地的状态,再调用 ServerRPC 改变服务器的状态,这样一来即使出现了客户端与服务器端状态不同步的情况也能通过 Replication 来补救。