本文最后更新于212 天前,其中的信息可能已经过时,如有错误请联系作者
[斯坦福CS193u-使用虚幻引擎和C++的游戏开发6]-多人游戏 – 知乎 (zhihu.com)
初步实现网络设置
//SInteractionComponent.h
protected:
UFUNCTION(Server, Reliable)
void ServerInteract(AActor* InFocus);
//SInteractionComponent.cpp
void USInteractionComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
APawn* MyPawn = Cast<APawn>(GetOwner());
if (MyPawn->IsLocallyControlled())
{
FindBestInteractable();
}
}
void USInteractionComponent::PrimaryInteract()
{
ServerInteract(FocusedActor);
}
void USInteractionComponent::ServerInteract_Implementation(AActor* InFocus)
{
if (InFocus == nullptr)
{
GEngine->AddOnScreenDebugMessage(-1, 1.0f, FColor::Red, "No Focus Actor to interact.");
return;
}
APawn* MyPawn = Cast<APawn>(GetOwner());
ISGameplayInterface::Execute_Interact(InFocus, MyPawn);
}
使得箱子实现网络同步
//SItemChest.h
protected:
UPROPERTY(ReplicatedUsing="OnRep_LidOpened", BlueprintReadOnly) // RepNotify
bool bLidOpened;
UFUNCTION()
void OnRep_LidOpened();
// Called when the game starts or when spawned
virtual void BeginPlay() override;
//SItemChest.cpp
ASItemChest::ASItemChest()
{
SetReplicates(true);
}
void ASItemChest::Interact_Implementation(APawn* InstigatorPawn)
{
bLidOpened = !bLidOpened;
OnRep_LidOpened();
}
void ASItemChest::OnRep_LidOpened()
{
float CurrPitch = bLidOpened ? TargetPitch : 0.0f;
LidMesh->SetRelativeRotation(FRotator(CurrPitch, 0, 0));
}
void ASItemChest::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME(ASItemChest, bLidOpened);
}
在箱子的蓝图中使其继承父节点功能
在每tick绘制观察线条
在PlayControlBP中判断是否为本地控制端
设置客户端的Actor Instances同步
//SPowerupActor.cpp
ASPowerupActor::ASPowerupActor()
{
SetReplicates(true);
}
//SProjectileBase.cpp
ASProjectileBase::ASProjectileBase()
{
SetReplicates(true);
}
//SAttributeComponent.h
protected:
UPROPERTY(EditDefaultsOnly,BlueprintReadOnly,Replicated,Category="Attributes")
float Health;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly,Replicated, Category = "Attributes")
float HealthMax;
public:
UFUNCTION(NetMulticast, Reliable) // @FIXME: mark as unreliable once we moved the 'state' our of scharacter
void MulticastHealthChanged(AActor* InstigatorActor, float NewHealth, float Delta);
//SAttributeComponent.cpp
USAttributeComponent::USAttributeComponent()
{
SetIsReplicatedByDefault(true);
}
bool USAttributeComponent::IsActorAlive(AActor* Actor)
{
return false;
}
bool USAttributeComponent::ApplyHealthChange(AActor* InstigatorActor,float Delta)
{
if(!GetOwner()->CanBeDamaged() && Delta < 0.0f)
{
return false;
}
if(Delta < 0.0f)
{
float DamageMultipier = CVarDamageMultiplier.GetValueOnGameThread();
Delta *= DamageMultipier;
}
float OldHealth = Health;
Health = FMath::Clamp(Health+Delta,0.0f,HealthMax);
float ActualDelta = Health - OldHealth;
/*OnHealthChanged.Broadcast(InstigatorActor,this,Health,ActualDelta);*/
if (ActualDelta != 0.0f)
{
MulticastHealthChanged(InstigatorActor, Health, ActualDelta);
}
//Died
if(Delta <0.0f && Health == 0.0f)
{
ASGameModeBase* GM = GetWorld()->GetAuthGameMode<ASGameModeBase>();
if(GM)
{
GM->OnActorKilled(GetOwner(),InstigatorActor);
}
}
return ActualDelta != 0;
}
void USAttributeComponent::MulticastHealthChanged_Implementation(AActor* InstigatorActor, float NewHealth, float Delta)
{
OnHealthChanged.Broadcast(InstigatorActor, this, NewHealth, Delta);
}
void USAttributeComponent::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME(USAttributeComponent, Health);
DOREPLIFETIME(USAttributeComponent, HealthMax);
//DOREPLIFETIME_CONDITION(USAttributeComponent, HealthMax, COND_InitialOnly);
}
完成上述代码后我们的生命值和健康血条就在服务端攻击客户端时同步了
在SCharacter的蓝图中添加Minion的widget
//SWorldUserWidget.h
UPROPERTY(BlueprintReadWrite, Category = "UI", meta = (ExposeOnSpawn=true))
AActor* AttachedActor;
添加上面代码后,我们可以再次修改蓝图
现在我们可以看到角色头顶出现血量条了
设置爆炸桶中的蓝图
添加下列内容,不知道为什么我们的爆炸桶无法实现自动爆炸
设置多播地址
我们不希望我们的爆炸桶在客户端上被销毁,我们希望这一步只在服务端完成
最后按照下面的设置才完成了效果
最后我们在蓝图中实现了爆炸桶的自我销毁,但是可能是因为爆炸桶的爆炸逻辑采用的还是我在C++中写的,所以蓝图中的爆炸效果并没有同步
//SActionComponent.h
protect:
UFUNCTION(Server, Reliable)
void ServerStartAction(AActor* Instigator, FName ActionName);
//SActionComponent.cpp
USActionComponent::USActionComponent()
{
PrimaryComponentTick.bCanEverTick = true;
SetIsReplicatedByDefault(true);
}
bool USActionComponent::StartActionByName(AActor* Instigator, FName ActionName)
{
for(USAction* Action : Actions)
{
if(Action && Action->ActionName == ActionName)
{
if (!Action->CanStart(Instigator))
{
FString FailedMsg = FString::Printf(TEXT("Failed to run: %s"), *ActionName.ToString());
GEngine->AddOnScreenDebugMessage(-1, 2.0f, FColor::Red, FailedMsg);
continue;
}
// Is Client?
if (!GetOwner()->HasAuthority())
{
ServerStartAction(Instigator, ActionName);
}
Action->StartAction(Instigator);
return true;
}
}
return false;
}
void USActionComponent::ServerStartAction_Implementation(AActor* Instigator, FName ActionName)
{
StartActionByName(Instigator, ActionName);
}
what can I say?😩