同步系列5-丢弃武器的同步


项目仓库地址:https://github.com/Backfire935/BlasterAgain

之前做丢弃武器功能的时候遇到了点同步上的问题,这里纪录一下。

首先还是输入绑定,没啥好说的

1
2
//按G丢弃武器
EnhancedInputComponent->BindAction(IA_DropWeapon, ETriggerEvent::Triggered, this, &ABlasterCharacter::InputDropWeapon);

来看InputDropWeapon函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void ABlasterCharacter::InputDropWeapon()
{
if (Combat)
{
if (HasAuthority())
{
Combat->DropWeapon();//如果就在Server上就直接执行
}
else
{
ServerDropButtonPressed();//不然就执行RPC
}
}
}

老样子,首先判断下Combat组件是否存在,之后如果是Server端按下了丢弃按钮,直接去调Combat->DropWeapon()函数处理丢弃武器的业务逻辑。如果是Client按下了丢弃按钮,则执行ServerRPC,ServerDropButtonPressed()在服务端执行,然后由服务端通过WeaponState的属性复制后的RepNotify回调来在客户端执行丢弃的逻辑。这样所有的玩家才都能看到武器被之前拥有武器的玩家正常的丢弃,

1
2
3
4
5
6
7
8
void ABlasterCharacter::ServerDropButtonPressed_Implementation()
{
//反正只在服务端调用,不用写HasAuthority
if (Combat)
{
Combat->DropWeapon();
}
}

又回到了熟悉的Combat->DropWeapon();Combat是啥我在拾取武器的同步中一节有讲:

1
2
3
4
5
6
7
8
9
void UCombatComponent::DropWeapon()
{
if (EquippedWeapon)//如果武器存在
{
EquippedWeapon->Dropped();//如果已经装备了一件武器,就丢掉手上的
EquippedWeapon->SetOwner(nullptr);//将拥有者设置为空
EquippedWeapon = nullptr;
}
}

这里将EquippedWeapon的拥有者设为空,注意SetOnwer函数自动复制的,详见其实现:

image-20230419093006345

然后EquippedWeapon指针置空,之后进入Weapon类的Dropped函数:

1
2
3
4
5
void AWeapon::Dropped()
{
SetWeaponState(EWeaponState::EWS_Dropped);//先将此件武器的状态的设为丢弃状态
ServerDropWeapon();
}

这里一个第一个是设置武器当前的状态,这样一会通过WeaponState的属性赋值就可以调用在客户端上的回调,第二个就是直接在服务端执行丢弃操作了;

1
2
3
4
5
6
7
8
9
10
11
12
13
void AWeapon::ServerDropWeapon_Implementation()//这个用来处理Server端武器的掉落效果,客户端的效果由WeaponState触发的OnRep_WeaponState完成
{
FDetachmentTransformRules DetachRules(EDetachmentRule::KeepWorld, true);//这个变量是解绑组件的规则,参数意思是自动计算相对转换,以便分离组件维护相同的世界转换。
WeaponMesh->DetachFromComponent(DetachRules);//不论组件被附加到什么上面都会拆下来,自动解绑被绑在一起的组件
//开启模拟物理
WeaponMesh->SetSimulatePhysics(true);
WeaponMesh->SetLinearDamping(LinearDamping);
WeaponMesh->SetAngularDamping(AngularDamping);
WeaponMesh->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics); // 启用碰撞检测和物理模拟
//开启重力
WeaponMesh->SetEnableGravity(true);

}

而在SetWeaponState中,我们设置武器新的状态。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void AWeapon::SetWeaponState(EWeaponState State)
{
WeaponState = State;
switch (WeaponState)
{
case EWeaponState::EWS_Initial:
ShowPickupWidget(false);
AreaSphere->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics);
break;
case EWeaponState::EWS_Equipped:
ShowPickupWidget(false);
AreaSphere->SetCollisionEnabled(ECollisionEnabled::NoCollision);
break;
case EWeaponState::EWS_Dropped:
ShowPickupWidget(false);
AreaSphere->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics);
break;
}
}

那么最后一步,RepNotify回调:

1
2
3
4
5
6
private:
UPROPERTY(ReplicatedUsing= OnRep_WeaponState, VisibleAnywhere, Category = "Weapon Properties")
EWeaponState WeaponState;

UFUNCTION()
void OnRep_WeaponState();
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
void AWeapon::OnRep_WeaponState()//处理武器不同状态时客户端武器的物理效果
{
ABlasterCharacter* BlasterCharacter = Cast<ABlasterCharacter>(GetOwner());
const USkeletalMeshSocket* HandSocket = BlasterCharacter ? BlasterCharacter->GetMesh()->GetSocketByName(FName("RightHandSocket")) : nullptr;
switch (WeaponState)
{
case EWeaponState::EWS_Initial:
ShowPickupWidget(false);
break;
case EWeaponState::EWS_Equipped :
//相关逻辑
break;
case EWeaponState::EWS_Dropped :
ShowPickupWidget(false);
FDetachmentTransformRules DetachRules(EDetachmentRule::KeepWorld, true);//这个变量是解绑组件的规则,参数意思是自动计算相对转换,以便分离组件维护相同的世界转换。
WeaponMesh->DetachFromComponent(DetachRules);//不论组件被附加到什么上面都会拆下来,自动解绑被绑在一起的组件
//开启模拟物理
WeaponMesh->SetSimulatePhysics(true);
WeaponMesh->SetLinearDamping(LinearDamping);
WeaponMesh->SetAngularDamping(AngularDamping);
WeaponMesh->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics); // 启用碰撞检测和物理模拟
//开启重力
WeaponMesh->SetEnableGravity(true);
break;
}
}

这样子玩家就可以在所有的客户端和服务单上正常执行丢弃武器的操作了,配合拾取武器的同步就可以正常的拾取丢弃再拾取了。

image-20230419093726007

(补充)2023年4月19日12:15:10

写前面的内容的时候我想到一个问题,就是Combat的EquippedWeapon,关于它的属性设置都是由角色类调Combat中的函数来设置的,而角色类中调用Combat的函数都是只在服务端执行的,而Combat组件并未开启复制,换句话说,按理说EquippedWeapon的属性在修改后只是在服务端修改,在客户端是不生效的,但实际上生效了。比如这段

1
2
3
4
5
6
7
8
9
void UCombatComponent::DropWeapon()
{
if (EquippedWeapon)//如果武器存在
{
EquippedWeapon->Dropped();//如果已经装备了一件武器,就丢掉手上的
EquippedWeapon->SetOwner(nullptr);//将拥有者设置为空
EquippedWeapon = nullptr;
}
}

这段我认为EquippedWeapon = nullptr;是只在服务端发生的,也就是说客户端丢掉武器后指针没有置空,实际可能武器脱离附加在角色身上后仍然能够开火,但这个bug并没有发生,这让我开始寻找。

直到我看到了EquippedWeapon的定义

1
2
3
private:
UPROPERTY(Replicated)
AWeapon* EquippedWeapon;

UE网络细节真的多…..


文章作者: John Doe
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 John Doe !
  目录