18uec++多人游戏【服务器为两个角色发枪,并能在线开枪】

打开主角类,生成枪的代码逻辑在游戏开始函数里

所以在生成之前,我们需要判断该对象是否在服务器端(服务器端视角)

void ASCharacter::BeginPlay()
{
	Super::BeginPlay();
	DefaultsFOV = CameraComp->FieldOfView;
	//判断是否在服务器端
	if (Role == ROLE_Authority)
	{
		//设置生成参数,当生成的actor碰到了其他物体,也要生成
		FActorSpawnParameters Parameters;
		Parameters.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
		//生成武器actor(类型、位置、方向、参数),并且其地址赋予到指针上
		CurrentWeapen1 = GetWorld()->SpawnActor<ASWeapen>(StartWeapen, FVector::ZeroVector, FRotator::ZeroRotator, Parameters);
		//设置武器的位置与骨骼的插槽中,并设置主人
		if (CurrentWeapen1)
		{
			CurrentWeapen1->SetOwner(this);
			CurrentWeapen1->AttachToComponent(GetMesh(), FAttachmentTransformRules::SnapToTargetNotIncludingScale, WeaponAttachSoketName);
		}
	}
	
	//受伤自定义事件绑定
	HealthComp->OnHealthChanged.AddDynamic(this, &ASCharacter::OnHealthChanged);

}

编译一下,看看是什么效果

我们发现,左边的有武器,右边的没有武器

 所以我们要让武器可以进行网络复制

打开武器类,在构造函数中进行设置

	//设置可以进行网络复制
	SetReplicates(true);

编译,然后打开枪的蓝图,打上勾

这一次他们都有枪了

 

 现在枪虽然是有了,左边的玩家可以开枪,但是右边的角色不可以开枪

这是因为右边角色,指向武器的指针是空的

 所以我们要让武器指针可以同步,这样就可以同步了

	//目前玩家手中的武器
	UPROPERTY(Replicated)
	class ASWeapen * CurrentWeapen1;

 要实现网络同步,必须还要有一个函数,这个函数定义在actor类里面

 我们将其复制到玩家类里面

	//用于网络同步的函数
	void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;

导入头文件

#include "Net/UnrealNetwork.h"

 定义这个函数

void ASCharacter::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
	Super::GetLifetimeReplicatedProps(OutLifetimeProps);
	//同步给所有的客户端和服务器
	DOREPLIFETIME(ASCharacter, CurrentWeapen1);
}

测试,两个角色都可以打枪了,但是互相看不见。

下面解决这个问题。

==========================================

给武器类添加成员函数serverfire()

	UFUNCTION(Server, Reliable, WithValidation)
		void ServerFire();

然后实现该函数的方式比较特殊

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

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

 然后我们修改一下Fire()函数

void ASWeapen::Fire()
{
	//如果不是服务器,就执行ServerFire()
	if (Role < ROLE_Authority)
	{
		ServerFire();
		return;
	}
	//创建一个撞击句柄,用来获取弹道相关信息
	FHitResult Hit;
	//弹道的起点,我们设置为角色眼睛的位置
	AActor * MyOwner = GetOwner();
	if (MyOwner)
	{	//位置
		FVector EyeLocation;
		//方向
		FRotator EyeRotator;
		//得到眼睛的位置和角度
		MyOwner->GetActorEyesViewPoint(EyeLocation, EyeRotator);
		//弹道的终点就是起点+方向*10000
		FVector TraceEnd = EyeLocation + (EyeRotator.Vector() * 1000);
		//弹道特效的结束点
		FVector TraceEndPoint = TraceEnd;
		//设置碰撞通道为可见性通道
		FCollisionQueryParams  QueryParams;
		//让射线忽略玩家和枪
		QueryParams.AddIgnoredActor(MyOwner);
		QueryParams.AddIgnoredActor(this);
		//符合追踪设为true,可以让射击更加精准
		QueryParams.bTraceComplex = true;
		//返回命中目标的表面材质
		QueryParams.bReturnPhysicalMaterial = true;


		//在创建一个单轨迹线来计算弹道
		//LineTraceSingleByChannel击中物体返回true
		if (GetWorld()->LineTraceSingleByChannel(Hit, EyeLocation, TraceEnd, COLLISION_WEAPON, QueryParams))
		{
			//命中对象
			AActor * HitActor = Hit.GetActor();
			//实际的伤害
			float ActualDamage = BaseDamage;
			//得到命中物体表面材质
			EPhysicalSurface SurfaceType = UPhysicalMaterial::DetermineSurfaceType(Hit.PhysMaterial.Get());
			//如果命中的是头部表面材质,伤害变成四倍
			if (SurfaceType == SURFACE_FLESHVULNERABLE)
			{
				ActualDamage *= 4;
			}

			//造成点伤害ApplyPointDamage
			//参数分别为命中对象、基础伤害、射击方向、命中信息(命中句柄)、MyOwner->GetInstigatorController(暂时不了解)
			//this(射击者) 和伤害类型 
			UGameplayStatics::ApplyPointDamage(HitActor, ActualDamage, EyeRotator.Vector(), Hit, MyOwner->GetInstigatorController(), this, DamageType);

			//根据材质的不同,进行不同的处理
			UParticleSystem * SelectedEffect = nullptr;
			switch (SurfaceType)
			{
				//这两种情况是一个效果
			case SURFACE_FLESHDEFAULT:
			case SURFACE_FLESHVULNERABLE:
				SelectedEffect = FleshImpactEffect;
				break;
			default:
				SelectedEffect = DefaultImpactEffect;
				break;

			}

			//生成特效在命中点
			//ImpactEffect:特效 ImpactPoint:打击点 Rotation():打击方向
			if (SelectedEffect)
			{
				UGameplayStatics::SpawnEmitterAtLocation(GetWorld(), SelectedEffect, Hit.ImpactPoint, Hit.ImpactNormal.Rotation());
			}
			//命中的时候,修改弹道特效的终点
			TraceEndPoint = Hit.ImpactPoint;



		}
		//方便debug
		//DrawDebugLine(GetWorld(), EyeLocation, TraceEnd, FColor::Red, false, 1, 0, 1);
		//射击特效
		PlayFireEffects(TraceEndPoint);
		//最后开火的时间
		FireLastTime = GetWorld()->TimeSeconds;
	}

}

编译,测试,服务器端开火,客户端看不到(不管控制哪个角色,都是在服务器端看到开火)

 

 修改一下代码,我们把return去掉

 编译测试

服务器端开火,客户端看不到;客户端开火,同时可以看到

 

 给角色蓝图添加两个节点

===========================================

目前

操作客户端,两端都能看到特效

 操作服务器端,客户端看不到特效

在武器类的头文件中,创建结构体

USTRUCT()
struct FHitScanTrace
{
	GENERATED_BODY()
public:
	//弹道的目的坐标
	UPROPERTY()
	FVector_NetQuantize TraceTo;
	//子弹数目:为了让该结构体内容发生变化,结构体才被不断得被网络复制
	UPROPERTY()
	uint8 BrustCounter;

};

创建该类中的结构体成员变量,和对应的网络复制函数

	//网络射击信息
	UPROPERTY(ReplicatedUsing = OnRep_HitScanTrace)
	FHitScanTrace HitScanTrace;

	//网络复制函数
	UFUNCTION()
	void OnRep_HitScanTrace();

当结构体内容发生改变的时候,就会自动调用网络复制函数

在fire函数里面,更新网络射击信息

		//更新网络射击信息
		if (Role == ROLE_Authority)
		{
			//子弹数量++
			HitScanTrace.BrustCounter++;
			//更新弹道目的坐标
			HitScanTrace.TraceTo = TraceEndPoint;

		}
void ASWeapen::Fire()
{
	//如果不是服务器,就执行ServerFire()
	if (Role < ROLE_Authority)
	{
		ServerFire();
		
	}
	//创建一个撞击句柄,用来获取弹道相关信息
	FHitResult Hit;
	//弹道的起点,我们设置为角色眼睛的位置
	AActor * MyOwner = GetOwner();
	if (MyOwner)
	{	//位置
		FVector EyeLocation;
		//方向
		FRotator EyeRotator;
		//得到眼睛的位置和角度
		MyOwner->GetActorEyesViewPoint(EyeLocation, EyeRotator);
		//弹道的终点就是起点+方向*10000
		FVector TraceEnd = EyeLocation + (EyeRotator.Vector() * 1000);
		//弹道特效的结束点
		FVector TraceEndPoint = TraceEnd;
		//设置碰撞通道为可见性通道
		FCollisionQueryParams  QueryParams;
		//让射线忽略玩家和枪
		QueryParams.AddIgnoredActor(MyOwner);
		QueryParams.AddIgnoredActor(this);
		//符合追踪设为true,可以让射击更加精准
		QueryParams.bTraceComplex = true;
		//返回命中目标的表面材质
		QueryParams.bReturnPhysicalMaterial = true;


		//在创建一个单轨迹线来计算弹道
		//LineTraceSingleByChannel击中物体返回true
		if (GetWorld()->LineTraceSingleByChannel(Hit, EyeLocation, TraceEnd, COLLISION_WEAPON, QueryParams))
		{
			//命中对象
			AActor * HitActor = Hit.GetActor();
			//实际的伤害
			float ActualDamage = BaseDamage;
			//得到命中物体表面材质
			EPhysicalSurface SurfaceType = UPhysicalMaterial::DetermineSurfaceType(Hit.PhysMaterial.Get());
			//如果命中的是头部表面材质,伤害变成四倍
			if (SurfaceType == SURFACE_FLESHVULNERABLE)
			{
				ActualDamage *= 4;
			}

			//造成点伤害ApplyPointDamage
			//参数分别为命中对象、基础伤害、射击方向、命中信息(命中句柄)、MyOwner->GetInstigatorController(暂时不了解)
			//this(射击者) 和伤害类型 
			UGameplayStatics::ApplyPointDamage(HitActor, ActualDamage, EyeRotator.Vector(), Hit, MyOwner->GetInstigatorController(), this, DamageType);

			//根据材质的不同,进行不同的处理
			UParticleSystem * SelectedEffect = nullptr;
			switch (SurfaceType)
			{
				//这两种情况是一个效果
			case SURFACE_FLESHDEFAULT:
			case SURFACE_FLESHVULNERABLE:
				SelectedEffect = FleshImpactEffect;
				break;
			default:
				SelectedEffect = DefaultImpactEffect;
				break;

			}

			//生成特效在命中点
			//ImpactEffect:特效 ImpactPoint:打击点 Rotation():打击方向
			if (SelectedEffect)
			{
				UGameplayStatics::SpawnEmitterAtLocation(GetWorld(), SelectedEffect, Hit.ImpactPoint, Hit.ImpactNormal.Rotation());
			}
			//命中的时候,修改弹道特效的终点
			TraceEndPoint = Hit.ImpactPoint;



		}
		//方便debug
		//DrawDebugLine(GetWorld(), EyeLocation, TraceEnd, FColor::Red, false, 1, 0, 1);
		//射击特效
		PlayFireEffects(TraceEndPoint);
		//更新网络射击信息
		if (Role == ROLE_Authority)
		{
			//子弹数量++
			HitScanTrace.BrustCounter++;
			//更新弹道目的坐标
			HitScanTrace.TraceTo = TraceEndPoint;

		}
		//最后开火的时间
		FireLastTime = GetWorld()->TimeSeconds;
	}

}

定义网络复制函数,让其执行射击特效

void ASWeapen::OnRep_HitScanTrace()
{
	//调用射击特效
	PlayFireEffects(HitScanTrace.TraceTo);
}

此时,网络射击信息结构体变量还没有实现网络的复制共享,我们让其共享。

创建复制成员变量的函数

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

定义这个函数

void ASWeapen::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
	Super::GetLifetimeReplicatedProps(OutLifetimeProps);
	//同步给所有的客户端和服务器(DOREPLIFETIME_CONDITION不用同步给自己)
	DOREPLIFETIME_CONDITION(ASWeapen, HitScanTrace,COND_SkipOwner);
}

编译,测试

发现服务器端射击的时候,客户端可以看到弹道的特效,但看不到击中特效

 ============================================

现在解决一下这个问题

为网络射击结构体添加表面材质成员变量

	//表面材质
	UPROPERTY()
	TEnumAsByte<EPhysicalSurface> SurfaceType;

在fire函数里面为其赋值

		//更新网络射击信息
		if (Role == ROLE_Authority)
		{
			//子弹数量++
			HitScanTrace.BrustCounter++;
			//更新弹道目的坐标
			HitScanTrace.TraceTo = TraceEndPoint;
			//更新击中物体的材质
			HitScanTrace.SurfaceType = SurfaceType;

		}

我们为击中特效的生成做成一个函数

	//击中特效
	void PlayImpactEffects(EPhysicalSurface SurfaceType , FVector ImpactPoint);

定义这个函数

void ASWeapen::PlayImpactEffects(EPhysicalSurface SurfaceType , FVector ImpactPoint)
{

	//根据材质的不同,进行不同的处理
	UParticleSystem * SelectedEffect = nullptr;
	switch (SurfaceType)
	{
		//这两种情况是一个效果
	case SURFACE_FLESHDEFAULT:
	case SURFACE_FLESHVULNERABLE:
		SelectedEffect = FleshImpactEffect;
		break;
	default:
		SelectedEffect = DefaultImpactEffect;
		break;

	}

	//生成特效在命中点
	//ImpactEffect:特效 ImpactPoint:打击点 Rotation():打击方向
	if (SelectedEffect)
	{
		//计算枪口位置
		FVector MuzzleLocation = MeshComponent->GetSocketLocation("MuzzleSocket");
		//射击方向向量 = 打击点-枪口
		FVector ShotDirection = ImpactPoint - MuzzleLocation;
		UGameplayStatics::SpawnEmitterAtLocation(GetWorld(), SelectedEffect, ImpactPoint, ShotDirection.Rotation());
	}
}

完善一下复制函数内容函数

void ASWeapen::OnRep_HitScanTrace()
{
	//调用射击特效
	PlayFireEffects(HitScanTrace.TraceTo);
	//生成命中特效
	PlayImpactEffects(HitScanTrace.SurfaceType, HitScanTrace.TraceTo);
}

完善fire函数

void ASWeapen::Fire()
{
	//如果不是服务器,就执行ServerFire(),服务器端就有响应
	if (Role < ROLE_Authority)
	{
		ServerFire();
		
	}
	//创建一个撞击句柄,用来获取弹道相关信息
	FHitResult Hit;
	//弹道的起点,我们设置为角色眼睛的位置
	AActor * MyOwner = GetOwner();
	//击中物体的材质
	EPhysicalSurface SurfaceType = EPhysicalSurface::SurfaceType_Default;

	if (MyOwner)
	{	//位置
		FVector EyeLocation;
		//方向
		FRotator EyeRotator;
		//得到眼睛的位置和角度
		MyOwner->GetActorEyesViewPoint(EyeLocation, EyeRotator);
		//弹道的终点就是起点+方向*10000
		FVector TraceEnd = EyeLocation + (EyeRotator.Vector() * 1000);
		//弹道特效的结束点
		FVector TraceEndPoint = TraceEnd;
		//设置碰撞通道为可见性通道
		FCollisionQueryParams  QueryParams;
		//让射线忽略玩家和枪
		QueryParams.AddIgnoredActor(MyOwner);
		QueryParams.AddIgnoredActor(this);
		//符合追踪设为true,可以让射击更加精准
		QueryParams.bTraceComplex = true;
		//返回命中目标的表面材质
		QueryParams.bReturnPhysicalMaterial = true;


		//在创建一个单轨迹线来计算弹道
		//LineTraceSingleByChannel击中物体返回true
		if (GetWorld()->LineTraceSingleByChannel(Hit, EyeLocation, TraceEnd, COLLISION_WEAPON, QueryParams))
		{
			//命中对象
			AActor * HitActor = Hit.GetActor();
			//实际的伤害
			float ActualDamage = BaseDamage;
			//得到命中物体表面材质
			EPhysicalSurface SurfaceType = UPhysicalMaterial::DetermineSurfaceType(Hit.PhysMaterial.Get());
			//如果命中的是头部表面材质,伤害变成四倍
			if (SurfaceType == SURFACE_FLESHVULNERABLE)
			{
				ActualDamage *= 4;
			}

			//造成点伤害ApplyPointDamage
			//参数分别为命中对象、基础伤害、射击方向、命中信息(命中句柄)、MyOwner->GetInstigatorController(暂时不了解)
			//this(射击者) 和伤害类型 
			UGameplayStatics::ApplyPointDamage(HitActor, ActualDamage, EyeRotator.Vector(), Hit, MyOwner->GetInstigatorController(), this, DamageType);
			//生成命中特效
			PlayImpactEffects(SurfaceType, Hit.ImpactPoint);

			//命中的时候,修改弹道特效的终点
			TraceEndPoint = Hit.ImpactPoint;

		}
		//方便debug
		//DrawDebugLine(GetWorld(), EyeLocation, TraceEnd, FColor::Red, false, 1, 0, 1);
		//射击特效
		PlayFireEffects(TraceEndPoint);
		//更新网络射击信息
		if (Role == ROLE_Authority)
		{
			//子弹数量++
			HitScanTrace.BrustCounter++;
			//更新弹道目的坐标
			HitScanTrace.TraceTo = TraceEndPoint;
			//更新击中物体的材质
			HitScanTrace.SurfaceType = SurfaceType;

		}
		//最后开火的时间
		FireLastTime = GetWorld()->TimeSeconds;
	}

}

编译,测试,两边特效同步了 

============================

现在是死亡不同步

打开生命值组件

首先在游戏开始函数里判断当前是否是服务器,如果是服务器,才绑定受伤

// Called when the game starts
void USHealthComponent::BeginPlay()
{
	Super::BeginPlay();
	//这就意味着客户端不会响应受伤事件
	if (GetOwnerRole() == ROLE_Authority)
	{
		AActor * Owner = GetOwner();
		//将该函数绑定在角色的受伤事件上
		if (Owner)
		{
			Owner->OnTakeAnyDamage.AddDynamic(this, &USHealthComponent::HandleTakeAnyDamage);
		}
	}

	Health = DefaultHealth;
	
	
}

在构造函数中设为可网路复制

USHealthComponent::USHealthComponent()
{
	// 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;

	DefaultHealth = 100;
	//网络复制
	SetIsReplicated(true);
}

将其中的生命值成员变量声明为网路可复制

	//当前生命值
	UPROPERTY(Replicated, BlueprintReadOnly, Category = "HealthComponent")
	float Health;

并定义和声明相关同步函数

		//用于网络同步的函数
	void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
#include "Net/UnrealNetwork.h"
void USHealthComponent::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
	Super::GetLifetimeReplicatedProps(OutLifetimeProps);
	//同步给所有的客户端和服务器
	DOREPLIFETIME(USHealthComponent, Health);
}

打开角色类,将该变量设为网络可复制

	//是否死亡
	UPROPERTY(Replicated, BlueprintReadOnly, Category = "Player")
	bool bDied;

网络同步函数中,也进行同步一下

void ASCharacter::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
	Super::GetLifetimeReplicatedProps(OutLifetimeProps);
	//同步给所有的客户端和服务器
	DOREPLIFETIME(ASCharacter, CurrentWeapen1);
	DOREPLIFETIME(ASCharacter, bDied);
}

测试

同时倒地

 

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

无情的阅读机器

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值