斯坦福CS193U-虚幻4C++课程学习笔记(18) Multiplayer & Replication

Replication

客户端与服务端同步数据和procedure call的过程

Client-Server Model

image.png
Note: 数据在服务器和客户端之间传输,无法在客户端之间直传

  • 服务器负责管理游戏状态 // 行为树只在服务器运行
  • 客户端服务器接收数据(Packets)并更新状态

Actor Replication

Actor将采用以下两种方式更新:

  • Property updates - 自动把更新的变量从服务器发送到客户端
    • 例子: 玩家血量降低,数据从服务器发往所有的客户端
  • RPCs (Remote Procedure Calls) - 其他客户端执行了某个函数
    • 例子: 玩家左键攻击,从客户端发RPC到服务器

Property Replication

变量从服务器同步到客户端
image.png

RPC

  • Server RPC - 客户端Call,请求服务器执行函数
    • image.png
  • Client RPC - 被服务器Call,去某个特定客户端上执行
    • image.png
  • NetMulticast RPC - 被服务器Call,所有客户端上执行
    • image.png

Steps to Implement Multiplayer

  • 游玩现有的游戏并观察客户端服务器之间game state的不同
    • image.png
  • 将Actor或Component标记为Replicated
  • 将需要同步的变量标记为Replicated
  • 在代码间添加RPCs

Tips

  • Function Specifier
    • Reliable - 保证最终到达. 请求会被重发直到收到ack
    • Unreliable - 不保证到达
  • Variable Specifier
    • Replicated - 会被ClientRPC同步
    • ReplicatedUsing = “OnRep_” - RepNotify当变量发生改变时的回调函数
      • 使用该通知时还需在服务器手动叫一次RepNotify

项目代码

GitHub: https://github.com/yufeige4/ActionRoguelike

  • 将交互组件修复为支持多人游戏
// GInteractionComponent.h
UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
class ACTIONROGUELIKE_API UGInteractionComponent : public UActorComponent
{
	GENERATED_BODY()

protected:

	UPROPERTY(EditDefaultsOnly, Category = "Trace")
	float TraceDistance;

	UPROPERTY(EditDefaultsOnly, Category = "Trace")
	float TraceRadius;

	UPROPERTY(EditDefaultsOnly, Category = "Trace")
	TEnumAsByte<ECollisionChannel> CollisionChannel;
	
	UPROPERTY(VisibleDefaultsOnly, BlueprintReadOnly, Category = "Interact")
	AActor* FocusedActor;

	UPROPERTY(EditDefaultsOnly, Category = "UI")
	TSubclassOf<UGUserWidget_World> HintWidgetClass;

	UPROPERTY()
	UGUserWidget_World* HintWidgetInstance;

public:	
	// Sets default values for this component's properties
	UGInteractionComponent();
	
	void PrimaryInteract();

protected:
	
	UFUNCTION(Server, Unreliable)
	void ServerInteract(AActor* InFocusedActor);
	
	// Called when the game starts
	virtual void BeginPlay() override;

	void FindBestInteractable();

public:	
	// Called every frame
	virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;
		
};
// GInteractionComponent.cpp
static TAutoConsoleVariable<bool> CVarInteractionDrawDebug(TEXT("ARPG.InteractDrawDebug"),false,TEXT("toggle whether draw debug info for InteractionComp"),ECVF_Cheat);
// Sets default values for this component's properties
UGInteractionComponent::UGInteractionComponent()
{
	// Set this component to be initialized when the game starts, and to be ticked every frame.  You can turn these features
	// off to improve performance if you don't need them.
	PrimaryComponentTick.bCanEverTick = true;

	TraceDistance = 500.0f;
	TraceRadius = 30.0f;
	CollisionChannel = ECC_WorldDynamic;
	// ...
}


// Called when the game starts
void UGInteractionComponent::BeginPlay()
{
	Super::BeginPlay();
	 
	// ...
	
}


void UGInteractionComponent::FindBestInteractable()
{
	FCollisionObjectQueryParams ObjectQueryParams;
	ObjectQueryParams.AddObjectTypesToQuery(CollisionChannel);
	
	FVector End;
	
	AActor* MyOwner = GetOwner();
	FVector CameraLocation;
	FRotator CameraRotation;
	
	AGCharacter* MyCharacter = Cast<AGCharacter>(MyOwner);
	MyCharacter->GetCameraViewPoint(CameraLocation,CameraRotation);

	End = CameraLocation + (CameraRotation.Vector()*TraceDistance);
	FCollisionShape Shape;
	Shape.SetSphere(TraceRadius);
	TArray<FHitResult> Hits;
	bool bBlockingHit = GetWorld()->SweepMultiByObjectType(Hits,CameraLocation,End,FQuat::Identity,ObjectQueryParams,Shape);
	// Debug color
	FColor LineColor = bBlockingHit ? FColor::Green : FColor::Red;
	bool bDebugDraw = CVarInteractionDrawDebug.GetValueOnGameThread();

	// 清空之前保存的Focus并重新搜索
	FocusedActor = nullptr;
	
	for(FHitResult Hit : Hits)
	{
		AActor* HitActor = Hit.GetActor();
		if(HitActor!=nullptr)
		{
			// 如果该Actor实现了这个接口
			if(HitActor->Implements<UGGameplayInterface>())
			{
				FocusedActor = HitActor;
				break;
			}
		}
		// Debug Purpose
		if(bDebugDraw)
		{
			DrawDebugSphere(GetWorld(),Hit.ImpactPoint,TraceRadius,32,LineColor,false,2.0f);
		}
	}

	if(FocusedActor)
	{
		// 若当前未实例化过并且widget类被指定
		if(!HintWidgetInstance && ensure(HintWidgetClass))
		{
			HintWidgetInstance = CreateWidget<UGUserWidget_World>(GetWorld(),HintWidgetClass);
		}
		if(HintWidgetInstance)
		{
			// attach到相应的actor上, 如果未在视口中则添加到视口
			HintWidgetInstance->AttachedActor = FocusedActor;
			if(!HintWidgetInstance->IsInViewport())
			{
				HintWidgetInstance->AddToViewport();
			}
		}
	}else
	{
		// 交互物超出范围, 提示消失
		if(HintWidgetInstance && HintWidgetInstance->IsInViewport())
		{
			HintWidgetInstance->RemoveFromParent();
		}
	}
	// Debug purpose
	if(bDebugDraw)
	{
		DrawDebugLine(GetWorld(),CameraLocation,End,LineColor,false,2.0f,0,2.0f);
	}
}

// Called every fame
void UGInteractionComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
	Super::TickComponent(DeltaTime, TickType, ThisTickFunction);

	auto MyPawn = Cast<APawn>(GetOwner());
	if(MyPawn->IsLocallyControlled())
	{
		FindBestInteractable();
	}
	
}

void UGInteractionComponent::PrimaryInteract()
{
	ServerInteract(FocusedActor);
}

void UGInteractionComponent::ServerInteract_Implementation(AActor* InFocusedActor)
{
	// refactored, move tracing logic into tick
	if(InFocusedActor==nullptr)
	{
		if(CVarInteractionDrawDebug.GetValueOnGameThread())
		{
			GEngine->AddOnScreenDebugMessage(-1,1.0f,FColor::Red,"No FocusedActor to Interact");
		}
		return;
	}
	APawn* MyPawn = Cast<APawn>(GetOwner());
	// 调用接口
	IGGameplayInterface::Execute_Interact(InFocusedActor,MyPawn);
}
  • 箱子实现了简单的服务器客户端同步
// AGItemChest.h
UCLASS()
class ACTIONROGUELIKE_API AGItemChest : public AActor, public IGGameplayInterface
{
	GENERATED_BODY()

protected:

	UPROPERTY(VisibleAnywhere)
	UStaticMeshComponent* BaseMesh;
	
	UPROPERTY(VisibleAnywhere,BlueprintReadOnly)
	UStaticMeshComponent* LidMesh;

	UPROPERTY(ReplicatedUsing = "OnRep_LidOpened") // RepNotify
	bool bLidOpened;
	
public:

	UPROPERTY(EditAnywhere)
	float TargetRoll;
	
	void Interact_Implementation(APawn* InstigatorPawn);

	// Sets default values for this actor's properties
	AGItemChest();

protected:
	// Called when the game starts or when spawned
	virtual void BeginPlay() override;

	UFUNCTION() // Need to mark UFUNCTION for Unreal
	void OnRep_LidOpened();

public:	
	// Called every frame
	virtual void Tick(float DeltaTime) override;

};
// AGItemChest.cpp
AGItemChest::AGItemChest()
{
 	// Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
	PrimaryActorTick.bCanEverTick = true;

	BaseMesh = CreateDefaultSubobject<UStaticMeshComponent>("BaseMesh");
	RootComponent = BaseMesh;

	LidMesh = CreateDefaultSubobject<UStaticMeshComponent>("LidMesh");
	LidMesh->SetupAttachment(BaseMesh);

	TargetRoll = -110.0f;

	SetReplicates(true);
}

// Called when the game starts or when spawned
void AGItemChest::BeginPlay()
{
	Super::BeginPlay();
}

void AGItemChest::OnRep_LidOpened()
{
	float CurrPitch = bLidOpened ? TargetRoll : 0.0f;
	LidMesh->SetRelativeRotation(FRotator(0,0,CurrPitch));
}

// Called every frame
void AGItemChest::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);
}

void AGItemChest::Interact_Implementation(APawn* InstigatorPawn)
{
	bLidOpened = !bLidOpened;
	OnRep_LidOpened();
}

// key function generated by UHT for replication rules
void AGItemChest::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
	Super::GetLifetimeReplicatedProps(OutLifetimeProps);
	// 所有客户端同步bLidOpened变量
	DOREPLIFETIME(AGItemChest,bLidOpened);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
学习虚幻引擎主要分为以下几个步骤: 1. 熟悉虚幻引擎的基本概念和架构:虚幻引擎是一个基于C++的游戏引擎,它提供了完整的游戏开发工具链和编辑器,包括场景编辑器、材质编辑器、蓝图编辑器等。在开始学习虚幻引擎之前,需要先了解它的基本概念和架构,包括游戏对象、关卡、材质、蓝图等。 2. 学习虚幻引擎的编程语言和API:虚幻引擎主要使用C++作为开发语言,并提供了丰富的API和工具库,包括游戏框架、渲染引擎、物理引擎等。在学习虚幻引擎的编程语言和API时,需要掌握C++语言的基础知识,并了解虚幻引擎的API和工具库的使用方法。 3. 实践虚幻引擎的开发项目:虚幻引擎提供了丰富的示例项目和文档,可以通过实践来巩固所学知识。可以选择一个适合自己的开发项目,比如一个简单的游戏或者一个模拟器,通过实践来掌握虚幻引擎的开发流程和技术。 以下是一些学习虚幻引擎的资源推荐: 1. 虚幻引擎官方网站:https://www.unrealengine.com/,官方网站提供了虚幻引擎的下载、文档、示例项目等资源。 2. 学习虚幻引擎C++编程:https://docs.unrealengine.com/en-US/Programming/index.html,官方文档,详细介绍了虚幻引擎C++编程和API使用。 3. 虚幻引擎社区:https://forums.unrealengine.com/,虚幻引擎社区是一个非常活跃的开发者社区,可以在这里获取最新的开发资讯、文档、工具和插件等资源。 4. Unreal C++ Survival Guide:https://gitlab.com/UnrealEngineEdu/UnrealC- -SurvivalGuide,这是一份针对虚幻引擎C++编程的学习指南,提供了大量的示例代码和实践项目。 5. Unreal Engine 4 Mastery: Create Multiplayer Games with C++:https://www.udemy.com/course/unrealengine-cpp/,这是一门虚幻引擎C++编程的在线视频课程,通过实践项目和代码演示,教授了虚幻引擎C++编程技术和多人游戏开发。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值