CoopGame05-游戏网络
type: Post
status: Published
date: 2022/10/05
slug: CoopGame05
summary: UE4 C++多人游戏入门
category: Unreal
跟随B站up主“技术宅阿棍儿”的教程制作的笔记。教程链接
变成网络游戏
- 创建继承
CoopGameGameModeBase
C++类的蓝图类BP_GameMode
、 - 在世界场景设置指定游戏模式重载为
BP_GameMode
,指定默认Pawn为自己的角色蓝图类BP_SCharacter
。 - 修改游戏的启动模式,网络模式:
以聆听服务器运行
,玩家数量:2
从服务器发枪
- 让服务器角色才能生成武器,判断是否为权威角色,
运行结果为Server端角色有武器,Chient端角色无武器
。
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实例附到网格体组件的骨骼插槽名上
}
}
...
}
使Server端角色的武器复制给Client端角色,
运行结果为Server端和Chient端的角色都有武器
。- 注意
BP_SWeapon
蓝图类中的复制属性
是否默认勾选上。
- 注意
ASWeapon::ASWeapon()
{
...
SetReplicates(true);//复制武器
...
}
- 使两端都能开枪。
运行结果为Server端和Chient端的角色都可以开枪,但是两边窗口看不到对方开枪
。
客户端不能开枪的原因:鼠标左键按下调用StartFire()函数,松开调用StopFire()函数,但此时两个函数都必须满足CurrentWeapon这个变量存在,而这个变量是在上面限制武器只在Server端生成的地方赋值的,所以CurrentWeapon为空,客户端无法调用武器的Fire()函数。
1.在SCheracter.h的CurrentWeapon变量添加宏参数Replicated,用于复制。
UPROPERTY(Replicated)
class ASWeapon* CurrentWeapon; //声明武器类
2.添加网络同步函数
SCharacter.h
//重写同步函数
virtual void GetLifetimeReplicatedProps( TArray< class FLifetimeProperty > & OutLifetimeProps ) const override;
SCharacter.cpp
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
//一个包含击中信息的结构体,物理表面和弹道终点
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);
}