玩家在靠近地上无主的武器一定范围内,武器上方会弹出一个提示,告知玩家按某个按钮后拾取武器。离开一定范围后提示消失。同时我们希望这个弹出的按钮只会在本地显示,也就是说一个玩家能否看到提示和其它玩家是无关的。

我们需要一个UserWidget控件和一个Weapon武器类,在UserWidget类中自己加一下文本提示即可,然后在Weapon.h中添加控件组件的声明。
1 2
| UPROPERTY(VisibleAnywhere, Category = "Weapon Properties") class UWidgetComponent* PickupWidget;
|

之后我们在Weapon武器蓝图中设置空间为屏幕,添加上我们之前创建好的控件类,指定其大小,并勾上以所需大小绘制。这样就设置好了显示的格式。
武器类中添加了一个球形碰撞体,就是通过这个球形碰撞体积和玩家的碰撞来判断玩家进入和离开可拾取武器的范围的。
1 2 3
| private: UPROPERTY(VisibleAnywhere, Category = "Weapon Properties") class USphereComponent* AreaSphere;
|
之后通过两个函数来处理玩家进入和离开的逻辑。
1 2 3 4 5
| protected: UFUNCTION() virtual void OnSphereOverlap(UPrimitiveComponent * OverlappedComponent, AActor * OtherActor, UPrimitiveComponent * OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult); UFUNCTION() void OnSphereEndOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex);
|
参数挺多,一个管玩家进,一个管玩家出,下面是实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| void AWeapon::OnSphereOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult) { ABlasterCharacter* BlasterCharacter = Cast<ABlasterCharacter>(OtherActor); if (BlasterCharacter) { BlasterCharacter->SetOverlappingWeapon(this); } }
void AWeapon::OnSphereEndOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex) { ABlasterCharacter* BlasterCharacter = Cast<ABlasterCharacter>(OtherActor); if (BlasterCharacter) { ShowPickupWidget(false); BlasterCharacter->SetOverlappingWeapon(nullptr); } }
|
这里的SetOverlappingWeapon函数用来传入武器给角色,就是告诉角色你已经进入了可拾取武器的范围并把武器自身的指针传给了角色。接下来回到角色类的.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13
| void ABlasterCharacter::SetOverlappingWeapon(AWeapon* Weapon) { OverlappingWeapon = Weapon; if(IsLocallyControlled()) { if (OverlappingWeapon) { OverlappingWeapon->ShowPickupWidget(true); } } }
|
OverlappingWeapon是在角色类中一个开启了属性复制的变量,因此当这个变量发生变化的时候,可以通过一个RepNotify回调去其他客户端上执行相应的逻辑
1 2 3 4 5
| void ABlasterCharacter::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const { Super::GetLifetimeReplicatedProps(OutLifetimeProps); DOREPLIFETIME_CONDITION(ABlasterCharacter, OverlappingWeapon, COND_OwnerOnly); }
|
此处开启条件复制,仅进入武器范围内的角色在服务端的Authority或(如果是服务端玩家进入范围) 和自主代理的那个客户端(某个客户端玩家进入范围)会被更新值。为什么客户端玩家进入范围,服务端也会被更新值?因为服务端永远是Authority的,换句话说服务端也是Owner。
知道了这些之后,我们就可以理解下面关于OverlappingWeapon的RepNotify回调了,它只会在服务端和 或某一个客户端上更新:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| UFUNCTION() void OnRep_OverlappingWeapon(AWeapon* LastWeapon);
void ABlasterCharacter::OnRep_OverlappingWeapon(AWeapon* LastWeapon) { if (OverlappingWeapon) { OverlappingWeapon->ShowPickupWidget(true); } if(LastWeapon) { LastWeapon->ShowPickupWidget(false); } }
|
你问搞这么麻烦干什么,一个提示而已,只在本地执行不就好了,为什么非得搞这么麻烦的同步。其实可以测试一下,如果只写玩家进入离开时的代码而不写同步的内容的话,对于客户端玩家而言确实没什么问题,但是如果是服务端玩家进入范围触发提示后,那么所有玩家的屏幕上都会被打开那个提示….
最后补充一下Weapon类中关于球形碰撞体积的设置,此处已省略其他无关代码:
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
| AWeapon::AWeapon() { PrimaryActorTick.bCanEverTick = false; bReplicates = true; SetReplicateMovement(true); AreaSphere = CreateDefaultSubobject<USphereComponent>(TEXT("AreaSphere")); AreaSphere->SetupAttachment(RootComponent); AreaSphere->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Ignore); AreaSphere->SetCollisionEnabled(ECollisionEnabled::NoCollision); AreaSphere->SetSphereRadius(72.f); PickupWidget = CreateDefaultSubobject<UWidgetComponent>(TEXT("PickupWidget")); PickupWidget->SetupAttachment(RootComponent); }
void AWeapon::BeginPlay() { Super::BeginPlay();
if(HasAuthority()) { AreaSphere->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics); AreaSphere->SetCollisionResponseToChannel(ECollisionChannel::ECC_Pawn, ECollisionResponse::ECR_Overlap); AreaSphere->OnComponentBeginOverlap.AddDynamic(this, &AWeapon::OnSphereOverlap); AreaSphere->OnComponentEndOverlap.AddDynamic(this, &AWeapon::OnSphereEndOverlap); } if (PickupWidget) { PickupWidget->SetVisibility(false); } }
|
在这里绑定了球形碰撞体积在被覆盖时候的处理函数。
那么到这里就大功告成了。