官方文档--多人游戏编程快速入门指南

569 阅读3分钟

0.UPROPERTY中的BlueprintPure,即工具类纯函数,无需执行引脚,只需输入输出。

1.想要复制某个属性时,使用Replicated,或使用ReplicatedUsing=函数名+UFUNCTION() void OnRep_属性名()的方法搭配,可实现变量复制/客户端成功接收复制数据时触发通知的效果。

image.png

2.标记复制的变量需要在GetLifetimeReplicatedProps函数中配置才可生效

void AThirdPersonMPCharacter::GetLifetimeReplicatedProps(TArray <FLifetimeProperty> & OutLifetimeProps) const
{
    Super::GetLifetimeReplicatedProps(OutLifetimeProps);

    //复制当前生命值。
    DOREPLIFETIME(AThirdPersonMPCharacter, CurrentHealth);
}

3.变量更新时对服务端与客户端提供不同的行为(该函数在OnRep_变量名中调用)


void AThirdPersonMPCharacter::OnHealthUpdate()
{
    //客户端特定的功能
    if (IsLocallyControlled())
    {
        FString healthMessage = FString::Printf(TEXT("You now have %f health remaining."), CurrentHealth);
        GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Blue, healthMessage);

        if (CurrentHealth <= 0)
        {
            FString deathMessage = FString::Printf(TEXT("You have been killed."));
            GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Red, deathMessage);
        }
    }

    //服务器特定的功能
    if (GetLocalRole() == ROLE_Authority)
    {
        FString healthMessage = FString::Printf(TEXT("%s now has %f health remaining."), *GetFName().ToString(), CurrentHealth);
        GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Blue, healthMessage);
    }

    //在所有机器上都执行的函数。 
    /*  
        因任何因伤害或死亡而产生的特殊功能都应放在这里。 
    */
}

4.仅在服务器端可以更改变量的值,更改后顺便调用OnHealthUpdate函数(因为服务器端复制数据之后不会调用OnRep_系列函数,需要在更新变量时手动调用操作的函数)

void AThirdPersonMPCharacter::SetCurrentHealth(float healthValue)
{
    if (GetLocalRole() == ROLE_Authority)
    {
        CurrentHealth = FMath::Clamp(healthValue, 0.f, MaxHealth);
        OnHealthUpdate();
    }
}

5.TakeDamage整体流程:

  • 外部Actor或函数对角色调用 CauseDamage,而角色又调用其 TakeDamage 函数。
  • TakeDamage 调用 SetCurrentHealth 以在服务器上更改玩家的当前生命值。
  • SetCurrentHealth 在服务器上调用 OnHealthUpdate,导致执行功能,响应玩家生命值的更改。
  • CurrentHealth 复制到所有已连接的客户端的角色副本。
  • 各个客户端从服务器收到 CurrentHealth 的新值时,会调用 OnRep_CurrentHealth
  • OnRep_CurrentHealth 调用 OnHealthUpdate,确保各个客户端以相同方式响应 CurrentHealth 的新值。

6.用代码将某个Actor设置为复制:当 bReplicates 设为 True,只要Actor的权威副本存在于服务器上,就会尝试将该Actor复制到所有已连接的客户端。

image.png

7.子弹的初始化API,碰撞预设的设置: SphereComponent->SetCollisionProfileName(TEXT("BlockAllDynamic")); FobjectFinder<>按路径查找资产,若成功(finder.Succeeded()返回true),则将结果(finder.Object)设置给StaticMeth。

image.png

image.png

8.撞击函数/撞击函数绑定碰撞组件/销毁子弹产生爆炸效果

void AThirdPersonMPProjectile::Destroyed()
{
    FVector spawnLocation = GetActorLocation();
    UGameplayStatics::SpawnEmitterAtLocation(this, ExplosionEffect, spawnLocation, FRotator::ZeroRotator, true, EPSCPoolMethod::AutoRelease);
}
void AThirdPersonMPProjectile::OnProjectileImpact(UPrimitiveComponent* HitComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, FVector NormalImpulse, const FHitResult& Hit)
{   
    if ( OtherActor )
    {
        UGameplayStatics::ApplyPointDamage(OtherActor, Damage, NormalImpulse, Hit, GetInstigator()->Controller, this, DamageType);
    }

    Destroy();
}

if (GetLocalRole() == ROLE_Authority)
{
    SphereComponent->OnComponentHit.AddDynamic(this, &AThirdPersonMPProjectile::OnProjectileImpact);
}

9.生成投射物的RPC函数

image.png

10.Fire函数+定时器处理+FActorSpawnParameters的配置

void AThirdPersonMPCharacter::StartFire()
{
    if (!bIsFiringWeapon)
    {
        bIsFiringWeapon = true;
        UWorld* World = GetWorld();
        World->GetTimerManager().SetTimer(FiringTimer, this, &AThirdPersonMPCharacter::StopFire, FireRate, false);
        HandleFire();
    }
}

void AThirdPersonMPCharacter::StopFire()
{
    bIsFiringWeapon = false;
}

void AThirdPersonMPCharacter::HandleFire_Implementation()
{
    FVector spawnLocation = GetActorLocation() + ( GetControlRotation().Vector()  * 100.0f ) + (GetActorUpVector() * 50.0f);
    FRotator spawnRotation = GetControlRotation();

    FActorSpawnParameters spawnParameters;
    spawnParameters.Instigator = GetInstigator();
    spawnParameters.Owner = this;

    AThirdPersonMPProjectile* spawnedProjectile = GetWorld()->SpawnActor<AThirdPersonMPProjectile>(spawnLocation, spawnRotation, spawnParameters);
}

StartFire 是玩家在本地机器上调用的函数,用于初始化发射流程,它基于以下条件限制用户调用 HandleFire 的频率:

  • 若用户正在发射投射物,则不可发射。这是用 bFiringWeapon 指派的,在调用 StartFire 时,bFiringWeapon 设为 true
  • 调用 StopFire 时,bFiringWeapon 仅可设为 false
  • 时长为 FireRate 的定时器结束时,会调用 StopFire

这意味着用户发射投射物时,必须等待数秒(等于 FireRate),之后方可继续发射。无论 StartFire 绑定到何种输入,这种情况始终一致。例如,若用户将"Fire"命令绑定到滚轮或类似的不当输入,或用户反复狂按按钮,此函数仍会按可接受的时间间隔执行,不会使 HandleFire 调用导致用户的可靠函数队列溢出。

因为 HandleFire 是服务器RPC,其在CPP文件中的实现必须在函数名前面添加前缀 _Implementation

本指南中的实施使用角色的控制旋转获取摄像机的朝向,然后生成面朝该方向的投射物,以便玩家瞄准。接下来,投射物的投射物移动组件使其朝该方向移动。