项目仓库地址:https://github.com/Backfire935/BlasterAgain
之前做丢弃武器功能的时候遇到了点同步上的问题,这里纪录一下。
首先还是输入绑定,没啥好说的
1 2
| 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(); } else { ServerDropButtonPressed(); } } }
|
老样子,首先判断下Combat组件是否存在,之后如果是Server端按下了丢弃按钮,直接去调Combat->DropWeapon()函数处理丢弃武器的业务逻辑。如果是Client按下了丢弃按钮,则执行ServerRPC,ServerDropButtonPressed()在服务端执行,然后由服务端通过WeaponState的属性复制后的RepNotify回调来在客户端执行丢弃的逻辑。这样所有的玩家才都能看到武器被之前拥有武器的玩家正常的丢弃,
1 2 3 4 5 6 7 8
| void ABlasterCharacter::ServerDropButtonPressed_Implementation() { 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函数自动复制的,详见其实现:

然后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() { 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; } }
|
这样子玩家就可以在所有的客户端和服务单上正常执行丢弃武器的操作了,配合拾取武器的同步就可以正常的拾取丢弃再拾取了。

(补充)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网络细节真的多…..