跳过正文

CoopGame05-游戏网络

··2977 字·6 分钟
coopgame - 这篇文章属于一个选集。
§ 5: 本文

CoopGame05-游戏网络
#

跟随B站up主“技术宅阿棍儿”的教程制作的笔记。教程链接


变成网络游戏
#

  1. 创建继承CoopGameGameModeBaseC++类的蓝图类BP_GameMode
  2. 在世界场景设置指定游戏模式重载为BP_GameMode,指定默认Pawn为自己的角色蓝图类BP_SCharacter
  3. 修改游戏的启动模式,网络模式:以聆听服务器运行,玩家数量:2

从服务器发枪
#

  1. 让服务器角色才能生成武器,判断是否为权威角色,运行结果为Server端角色有武器,Chient端角色无武器
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
void ASCharacter::BeginPlay(){
...
    if (GetLocalRole() == ROLE_Authority)//武器只在服务器生成
	    {
	    	 FActorSpawnParameters SpawnParameters;//生成参数
		 SpawnParameters.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;//总是生成
		CurrentWeapon = GetWorld()->SpawnActor<ASWeapon>(StartWeaponClass, FVector::ZeroVector, FRotator::ZeroRotator,
		SpawnParameters);//生成武器Actor并拿到实例
		if (CurrentWeapon)
		{
			CurrentWeapon->SetOwner(this);//设置Actor拥有者为当前角色
			CurrentWeapon->AttachToComponent(GetMesh(), FAttachmentTransformRules::SnapToTargetNotIncludingScale,
			WeaponAttachSocketName);//将生成的Actor实例附到网格体组件的骨骼插槽名上
		}
	}
...
}
  1. 使Server端角色的武器复制给Client端角色,运行结果为Server端和Chient端的角色都有武器
    • 注意BP_SWeapon蓝图类中的复制属性是否默认勾选上。
1
2
3
4
5
6
ASWeapon::ASWeapon()
{
...
	SetReplicates(true);//复制武器
...
}
  1. 使两端都能开枪。运行结果为Server端和Chient端的角色都可以开枪,但是两边窗口看不到对方开枪

客户端不能开枪的原因:鼠标左键按下调用StartFire()函数,松开调用StopFire()函数,但此时两个函数都必须满足CurrentWeapon这个变量存在,而这个变量是在上面限制武器只在Server端生成的地方赋值的,所以CurrentWeapon为空,客户端无法调用武器的Fire()函数。

1.在SCheracter.h的CurrentWeapon变量添加宏参数Replicated,用于复制。

1
2
UPROPERTY(Replicated)
class ASWeapon* CurrentWeapon; //声明武器类

2.添加网络同步函数

SCharacter.h

//重写同步函数
virtual void GetLifetimeReplicatedProps( TArray< class FLifetimeProperty > & OutLifetimeProps ) const override;

SCharacter.cpp

1
2
3
4
5
void ASCharacter::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
	Super::GetLifetimeReplicatedProps(OutLifetimeProps);
	DOREPLIFETIME(ASCharacter,CurrentWeapon);//同步条件
}

从服务器开枪
#

1.让Clinet端的角色不仅在本地开枪,也在Server端上开枪。运行结果:Chient端的角色开枪时所有特效在两端窗口都能看到

原理:客户端窗口里的客户端角色开枪请求服务端,让服务端里的客户端角色开火,然后服务端里的客户端角色开火后复制给客户端里的客户端角色,最后服务端里的客户端角色和客户端里的客户端角色都能开火。`

SWeapon.h

//Server RPC的大致原理:如果当前的端是Server(Authority),那么就是直接在自身执行。如果没有Authority,就是Client发送请求到Server上,然后在Server上执行这些函数。
UFUNCTION(Server,Reliable,WithValidation)//UE自带的同步机制Reliable
void ServerFire();

SWeapon.cpp

void ASWeapon::ServerFire_Implementation()
{
	Fire();
}

bool ASWeapon::ServerFire_Validate()
{
	return true;
}

void ASWeapon::Fire()
{

    //客户端上的客户端角色x调用Fire()函数时会调用ServerFire(),请求其对应的服务端里的那个客户端角色x调用Fire()函数。
    if (GetLocalRole()<ROLE_Authority)ServerFire();
}

#让客户端开枪(同步枪口特效,弹道特效,击中特效)

现状:服务端窗口里的服务端角色和客户端角色都能开枪,客户端窗口上只有客户端角色能开枪,不能看到服务端角色开枪,我们要让客户端窗口里的服务端角色拿到复制过来的特效,模拟开枪。

1.同步开枪特效的属性,让服务器下发,因为之前整理的特效代码和教程不一致,这里以我自己整理的为准。运行结果:Server端的角色开枪时击中特效在两端端窗口都能看到

修改CooGame.Build.cs

PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore" });
PrivateDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "NavigationSystem", "PhysicsCore", "DeveloperSettings" });

SWeapon.h

 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
//一个包含击中信息的结构体,物理表面和弹道终点
USTRUCT()
struct FHitScanTrace
{
	GENERATED_BODY()

public:
	UPROPERTY()
	FHitResult Hit;

	UPROPERTY()
	FVector_NetQuantize TraceTo;

};

class COOPGAMEPLUS_API ASWeapon : public AActor
{
	GENERATED_BODY()

public:
    //声明结构体变量和绑定调用时复制的方法,作用:当服务器中的结构体变量改变,就让客户端中的角色调用OnRep_HitScanTrace()函数?
	UPROPERTY(ReplicatedUsing=OnRep_HitScanTrace)
	FHitScanTrace HitScanTrace;
	UFUNCTION()
	void OnRep_HitScanTrace();
	//重写同步函数
	virtual void GetLifetimeReplicatedProps( TArray< class FLifetimeProperty > & OutLifetimeProps ) const override;
}

SWeapon.cpp

ASWeapon::ASWeapon()
{
//...
	//网络参数,改善延迟
	NetUpdateFrequency = 66;
	MinNetUpdateFrequency = 33;
//...
}

void ASWeapon::Fire()
{
    //...
	AActor* WeaponOwner = GetOwner();
	if (WeaponOwner)
	{
        //...
        //如果是服务端上的角色,就把弹道终点和打击信息赋值到结构体。
    	if (GetLocalRole() == ROLE_Authority)
    		{
    			HitScanTrace.TraceTo = TraceEnd;
    			HitScanTrace.Hit = Hit;
    		}
    }
}

void ASWeapon::OnRep_HitScanTrace()
{
	//当结构体的值改变时,播放特效复制到客户端中的角色
	PlayFireEffects(HitScanTrace.TraceTo);
	PlayImpactEffects(HitScanTrace.Hit);
}

//同步函数
void ASWeapon::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
	Super::GetLifetimeReplicatedProps(OutLifetimeProps);
	DOREPLIFETIME_CONDITION(ASWeapon,HitScanTrace,COND_SkipOwner);//结构体的同步条件:跳过拥有者(既服务端角色)
}

#死亡同步 1.给生命值组件添加同步,给生命值的宏添加复制参数,设置生命值组件是否复制为true,设置只能由服务器角色绑定伤害事件,同时角色类中的bDried也得被复制到客户端上。运行结果:两端击杀的死亡动画已同步

SHealthComponent.h

UPROPERTY(Replicated,BlueprintReadOnly,Category="HealthComponent")
float Health;

//重写同步函数
	virtual void GetLifetimeReplicatedProps( TArray< class FLifetimeProperty > & OutLifetimeProps ) const override;

SHealthComponent.cpp

USHealthComponent::USHealthComponent()
{
	SetIsReplicated(true);
}

void USHealthComponent::BeginPlay()
{
	Super::BeginPlay();
    //只有服务器上的角色才能绑定伤害事件
	if (GetOwnerRole() == ROLE_Authority)
	{
		AActor* MyOwner = GetOwner();
		//当Actor收到伤害时调用OnTakeAnyDamage绑定的HandleTakeAnyDamage函数
		if (MyOwner) MyOwner->OnTakeAnyDamage.AddDynamic(this,&USHealthComponent::HandleTakeAnyDamage);
	}
}

//重写同步函数,设置同步条件
void USHealthComponent::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
	Super::GetLifetimeReplicatedProps(OutLifetimeProps);
	DOREPLIFETIME(USHealthComponent,Health);//Health属性同步条件
}

SCharacter.h

UPROPERTY(Replicated,EditDefaultsOnly, BlueprintReadOnly, Category="Player")
 bool bDied;

SCharacter.cpp

void ASCharacter::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
	Super::GetLifetimeReplicatedProps(OutLifetimeProps);
//...
	DOREPLIFETIME(ASCharacter,bDied);//同步条件
}

#挑战:爆炸油桶的网络同步 1.修改必要的同步属性和视觉效果。运行结果:两端油桶爆炸和移动已同步

SExplosiveBarrel.h

    UPROPERTY(ReplicatedUsing = OnRep_Exploded)
	bool bExploded;

	UFUNCTION()
	void Onrep_Exploded();

	virtual void GetLifetimeReplicatedProps( TArray< class FLifetimeProperty > & OutLifetimeProps ) const override;

SExplosiveBarrel.cpp

ASExplosiveBarrel::ASExplosiveBarrel()
{
//...
	SetReplicates(true);
	SetReplicateMovement(true);
//...
}

void ASExplosiveBarrel::OnHealthChanged(USHealthComponent* OwningHealthComp, float Health, float HealthDelta,
	const UDamageType* DamageType, AController* InstigatedBy, AActor* DamageCauser)
{
	if (bExploded)return;
	if (Health<=0)
	{
		bExploded = true;
		MeshComp->AddImpulse(ExplosionImpulse * FVector::UpVector,NAME_None,true);//添加向上的力,通过组件自己的同步属性同步
        //UGameplayStatics::SpawnEmitterAtLocation(GetWorld(),ExplosionEffect,GetActorLocation());//生成爆炸特效
	  //MeshComp->SetMaterial(0,ExplodedMaterial);//改变爆炸后的材质

		OnRep_Exploded();//将生成爆炸特效和修改材质放入同步函数并调用。
		RadialForceComp->FireImpulse();//半径力发射,如果是物体,可以通过勾选物体自己的移动同步属性来同步
	}
}

void ASExplosiveBarrel::OnRep_Exploded()
{
	UGameplayStatics::SpawnEmitterAtLocation(GetWorld(),ExplosionEffect,GetActorLocation());//生成爆炸特效
	MeshComp->SetMaterial(0,ExplodedMaterial);//改变爆炸后的材质
}

void ASExplosiveBarrel::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
	Super::GetLifetimeReplicatedProps(OutLifetimeProps);
	DOREPLIFETIME(ASExplosiveBarrel,bExploded);
}

```https://s3.zmingu.com/images
coopgame - 这篇文章属于一个选集。
§ 5: 本文