Replication for Projectile

Hello been searching around but been unable to find any info on replicating projectile.
Right now i have a projectile thats setup with a collision sphere, a mesh, particle and a UProjectileMovementComponent.

In my weapon class i have a subclass of my projectile class.
Witch i spawn with a RPC call


UFUNCTION(Reliable, Server, WithValidation)

Everything is working as intended but if fired on one client no projectile is shown on the second one.

So am wondering if anyone have some resources on how to do this for Multiplayer?
I have found some stuff on the Wiki but as far as i can see, its not covering multiplayer.

Thanks for your time.

I assume you mean UFUNCTION not UPROPERTY and the projectile already spawns on the server? In that case, add bNetLoadOnClient = true; in your projectile’s constructor and it will be instanced on clients. Also make sure the projectile only gets spawned on the server and never on a client, otherwise you might end up with two projectiles on the instigator client.

greetings,
FTC

Yes of course UFUNCTION :stuck_out_tongue: my bad.
I got both bNetLoadOnClient and bNetLoadOnServer set to true.

bReplicates set to true and ProjectileMovementComponent->UpdateCompnent = RootComponent;

This is basically what I use for spawning projectiles:



void AWeapon::FireProjectileServer_Implementation()
{
	//Only spawn on server
	if (Role < ROLE_Authority)
	{
		return;
	}

	...]
	
	FActorSpawnParameters Params;
	Params.Owner = this;
	Params.Instigator = Instigator;
	AProjectile* NewProjectile = GetWorld()->SpawnActor<AProjectile>(ProjectileClass, GetActorLocation(), GetActorRotation(), Params);
	if (!NewProjectile)
	{
		return;
	}

	...]
	
	NewProjectile->SetReplicates(true);
}


Hope it helps,
FTC

I recently ported the blueprints networking tutorial to C++ as an exercise to learn this stuff. So with that in mind the following may be completely incorrect! I’m still hoping good written documentation.

Basically if I’m the client, I call the RPC ServerAttemptSpawnBomb() otherwise the implementation (server) version. I’m not sure from your post if this is what you’re doing.

I also set any replicates flags in blueprints for my projectile even if I’ve done it in code.

I spawn the projectile just like FTC.

Could also be a network relevancy issue?



void AVASACharacter::DropBomb()
{
	//if (GEngine) { GEngine->AddOnScreenDebugMessage(-1, 10.0, FColor::Red, "DROP BOMB"); }
	if (BombCount > 0 && Role < ROLE_Authority)
	{
		ServerAttemptSpawnBomb();		
	}
	else
	{
		ServerAttemptSpawnBomb_Implementation();
	}
}


@: I wouldn’t recommend spawning a projectile (or bomb) on the client itself. The server will spawn a projectile independently which gets replicated back to all clients and the original client ends up with two projectiles instead of one.

@FTC: I think I didn’t articulate myself very well. ServerAttemptSpawnBomb is a server side function.

If it still looks wrong or there’s a better way please let me know :slight_smile:


UFUNCTION(Server, Reliable, WithValidation)
virtual void ServerAttemptSpawnBomb();

With a ROLE_Authority guard in the implementation.


void AVASACharacter::ServerAttemptSpawnBomb_Implementation()
{

	if (BombCount > 0 && Role == ROLE_Authority)
	{
                   ....
        }


Ah ok, then it is correct, I was confused by the



	else
	{
		ServerAttemptSpawnBomb_Implementation();
	}


Whenever I have a function that I needed to be called on Server and Client I do something like this:



void DoSomething();

UFUNCTION(Server, ...)
void DoSomethingServer();


and implement them like this:



void AMyActor::DoSomething()
{
	if(Role < ROLE_Authority)
	{
		DoSomethingServer();
	}

	[Do other stuff]
}

void AMyActor::DoSomethingServer_Implementation()
{
	DoSomething();
}


Granted, it’s a bit cumbersome to always write two functions, but I think it is pretty clean and you do not need to call the _Implementation function manually. You can also make the RPC call private or protected and only expose the client side function to hide the server call.

greetings,
FTC

Hello guys thanks for all the great tips.
Am not having any luck, if you want to have a look is the snip of the code.

But atleast now am having the behavior you whould expect, the projectile is only showing up on the server/ host now.


// Weapon Class

AMyProjectWeapon::AMyProjectWeapon(const class FPostConstructInitializeProperties& PCIP)
	: Super(PCIP)
{
	bReplicates = true;
	bWeaponIsFireing = false;
	ProjectileClass = AMyProjectProjectile::StaticClass();

	// Default Spawn Info for Weapon
	SpawnInfo.bNoCollisionFail = true;
	SpawnInfo.Owner = NULL;
	SpawnInfo.Instigator = NULL;
	SpawnInfo.bDeferConstruction = false;
	bReplicateInstigator = true;
	bNetUseOwnerRelevancy = true;

	WeaponMes1P = PCIP.CreateDefaultSubobject<USkeletalMeshComponent>(this, TEXT("Weapon Mesh"));
	if (WeaponMes1P)
	{
// 		WeaponMes1P->SetVisibility(true);
// 		WeaponMes1P->SetSimulatePhysics(true);
// 		WeaponMes1P->SetEnableGravity(true);

		WeaponMes1P->bOwnerNoSee = false;
		WeaponMes1P->bCastDynamicShadow = true;
		WeaponMes1P->CastShadow = true;
		WeaponMes1P->BodyInstance.SetObjectType(ECC_WorldDynamic);
		WeaponMes1P->BodyInstance.SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics);
		WeaponMes1P->BodyInstance.SetResponseToAllChannels(ECR_Ignore);
		WeaponMes1P->BodyInstance.SetResponseToChannel(ECC_WorldStatic, ECR_Block);
		WeaponMes1P->BodyInstance.SetResponseToChannel(ECC_WorldDynamic, ECR_Block);
		WeaponMes1P->BodyInstance.SetResponseToChannel(ECC_Pawn, ECR_Block);
		WeaponMes1P->SetHiddenInGame(false);

		RootComponent = WeaponMes1P;
	}

	PrimaryActorTick.bCanEverTick = true;
	SetActorTickEnabled(true);
}


	UFUNCTION()
		virtual bool AttachWeaponToPawn(APawn* WeaponOwner, FString SkeletalCompName);

bool AMyProjectWeapon::AttachWeaponToPawn(APawn* WeaponOwner, FString SkeletalCompName)
{
	OwningPawn = WeaponOwner; // Sets the owner of this weapon.
	SetOwner(OwningPawn);
	USkeletalMeshComponent* ArmMesh = GetPawnSkeletalComp(SkeletalCompName);

	if (ArmMesh) // Check to see if our rig Skeletal Mesh Component is good.
	{
		AttachRootComponentTo(ArmMesh, FName(TEXT("WeaponPoint")), EAttachLocation::SnapToTarget); // Attach the root component of our Weapon actor to the ArmMesh at the location of the socket.

		SpawnInfo.Owner = WeaponOwner;
		SpawnInfo.Instigator = WeaponOwner;

		return true; // Note: We can only assume it is ok, since epic did not provide a return value.
	}
	else
	{
		return false;
	}
}

	UFUNCTION()
		void OnFireStart(FVector Location, FRotator Rotation);

void AMyProjectWeapon::OnFireStart(FVector Location, FRotator Rotation)
{
	bWeaponIsFireing = true;

	// TO DO: Needs timer.
	if (!WeaponSettings.bAutomaticFire /* && WeaponSettings.FireingRate > TimeSinceFired */ )
	{
		FireProjectile();
	}
}

	UFUNCTION()
		virtual void OnFireEnd();

void AMyProjectWeapon::OnFireEnd()
{
	bWeaponIsFireing = false;
}

	UFUNCTION()
		virtual void FireProjectile();

void AMyProjectWeapon::FireProjectile()
{
	if (Role < ROLE_Authority)
	{
		ServerFireProjectile();
		return;
	}

	UWorld* VGWorld = GetWorld();
	if (VGWorld)
	{
		const USkeletalMeshSocket* MuzzleSocket = WeaponMes1P->GetSocketByName(FName(TEXT("MuzzleFlashSocket"))); // Get the muzzle socket
		if (MuzzleSocket)
		{
			CurrentMuzzleLoc = MuzzleSocket->GetSocketLocation(WeaponMes1P); // Location
			CurrentMuzzleRot = MuzzleSocket->GetSocketTransform(WeaponMes1P).Rotator(); // Rotation
		}

		if (bWeaponIsFireing)
		{
			AMyProjectProjectile* Projectile = VGWorld->SpawnActor<AMyProjectProjectile>(ProjectileClass, CurrentMuzzleLoc, CurrentMuzzleRot, SpawnInfo); // Spawn this projectile, 
			if (Projectile)
			{
				Projectile->SetReplicates(true);
			}
		}
	}
}

bool AMyProjectWeapon::ServerFireProjectile_Validate()
{
	return true;
}

	UFUNCTION(reliable, server, WithValidation)
		virtual void ServerFireProjectile()

void AMyProjectWeapon::ServerFireProjectile_Implementation()
{
	FireProjectile();
}

// Projectile Base Class ////////


AMyProjectProjectile::AMyProjectProjectile(const class FPostConstructInitializeProperties& PCIP)
	: Super(PCIP)
{
	//Replication
	bReplicates = true;
	ProjectileRadius = 10.f;
	bReplicateMovement = true;

	CollisionSphere = PCIP.CreateDefaultSubobject<class USphereComponent>(this, TEXT("CollisionSphere"));
	if (CollisionSphere)
	{
		CollisionSphere->SetSphereRadius(ProjectileRadius);
		CollisionSphere->BodyInstance.SetCollisionProfileName("Projectile");			// Collision profiles are defined in DefaultEngine.
		CollisionSphere->AlwaysLoadOnClient = true;
		CollisionSphere->AlwaysLoadOnServer = true;
		CollisionSphere->bTraceComplexOnMove = true;
		RootComponent = CollisionSphere;
	}

	ProjectileMesh = PCIP.CreateDefaultSubobject<UStaticMeshComponent>(this, TEXT("ProjectileMesh"));
	if (ProjectileMesh)
	{
		ProjectileMesh->AttachTo(CollisionSphere);
	}

	ProjectileParticle = PCIP.CreateDefaultSubobject<UParticleSystemComponent>(this, TEXT("ProjectileParticle"));
	if (ProjectileParticle)
	{
		ProjectileParticle->AttachTo(ProjectileMesh);
	}

	ProjectileMovementComp = PCIP.CreateDefaultSubobject<UProjectileMovementComponent>(this, TEXT("ProjectileMovementComponent"));
	if (ProjectileMovementComp)
	{
		ProjectileMovementComp->UpdatedComponent = CollisionSphere;
		ProjectileMovementComp->InitialSpeed = 3000.f;
		ProjectileMovementComp->MaxSpeed = 3000.f;
		ProjectileMovementComp->bRotationFollowsVelocity = true;
		ProjectileMovementComp->bShouldBounce = true;
	}

}

// Player Class \ Character

	UFUNCTION()
		virtual void OnFireStart();

void AMyProjectPlayerClass::OnFireStart()
{
	FVector ProjectileLocation; 
	FRotator ProjectileRotation;

	if (Controller)
	{
		AMyProjectPlayerController* PC = Cast<AMyProjectPlayerController>(Controller);
		if (PC)
		{
			PC->PlayerCameraManager->GetCameraViewPoint(ProjectileLocation, ProjectileRotation); 

			if (CurrentWeapon)
				CurrentWeapon->OnFireStart(ProjectileLocation, ProjectileRotation); // TO DO: Remove he Vectors and change function parameters
		}
	}
}

Edit: I got it working :).
I will post the code/ a tutorial on this when i get some time, am sure someone else will find it helpfull.

Awesome! What was it in the end?

A combination of wrong spawn parameters and some member variables needed to be replicated.
e.g: bWeaponIsFireing

BTW is it so that when a member is declared as replicated with the UPROPERTY() macro.
Is this only one way Server->Client(s) and any change that happens on the Client wil NOT replicate to the server.

And needs a RPC call for that to work, or is it some other way?
Seems like a lot of trafic will be necesery to do any changes that the server to be aware/ will become aware of.

No, in UE4 the server is authoritive and only its world is the valid one. All clients only try to simulate what is happening on the server as best as they can. Exceptions are only made for certain actors (e.g. the local pawn and controller) and the only way they have to get the server to change something is by using RPCs.

Server functions only go one way from calling client to server, **Client ** functions only go one way from server to the owning client of an actor and there are also **NetMulticast ** RPCs which go from the server to all clients. For everything else there is property replication.

If you want special things to happen once a property changes you can use ReplicatedUsing=FunctionCallback to call FunctionCallback everytime the property changes. Furthermore you can add one parameter to the callback function with the same type the replicated property has. The parameter will then always have the pre-replication value of your property.

You might want to take a look at this:
http://udn.epicgames.com/Three/ReplicationHome.html
Granted, it is for UDK and UnrealScript but the concepts are basically the same in UE4 and the documentation is pretty complete (contrary to UE4’s at this time :wink: )
greetings,
FTC

Hello FTC thanks for that :).
I think am getting it, only one question.

When you use ReplicateUsing=FunctionCallback its stile only called locally on that client,
and the change had to occur on the server for the method to be called?

I guess that’s two :stuck_out_tongue:

Replication callbacks will only be called on clients and only if the value they get replicated from the server differs from their local value.

For example the UT3 Weapon system had a counter that got increased with every shot and clients would play shot effects whenever it changed. It worked like this:
The owner client shot the weapon, increased the counter, played the effects and called the server shoot function. The server then increased the counter on its side the same way and played the effects as well (if it wasn’t dedicated). After that the server replicated the new counter value to all clients. All clients except the owner client now executed the replication callback function to play the weapon effects on their side. The reason the owner client does not is because its counter value already matches the new value replicated by the server.

I haven’t actually analyzed the Shooter Game of UE4, but my guess is that it works somewhat similar at least. Apart from that, the method described above still works, I use it for my weapon replication. I hope that clarifies how ReplicateUsing works.

greetings,
FTC

Has anyone encountered issues with choppy replicated projectiles? I believe I’m replicating my projectiles correctly (spawning them on the server with authority, apply velocity on the server, net load on client, replicate movement), but they appear choppy in-game, so I’m doing something wrong but it’s yet unclear to me.

I’ve posted an answer hub question in case anyone has any insights: Replicated projectile movement components are choppy - World Creation - Epic Developer Community Forums

I’ve encountered this issue, particularly with fast-moving projectiles. I’m thinking of a new way - replicate the projectile, and replicate its position (only initially, so clients have a start position), and firing velocity, so clients can move the projectiles at said velocity. Turn off collision for the bullets on client as well, so there’s less CPU load. Definitely not as good as having 0 computation for bullets, but it’s better than choppy bullets.

I like that idea. I like it so much that I implemented it, and I think it works pretty well. I didn’t implement any rewind/replay, but since the ProjectileMovement components are so predictable I haven’t noticed any deviations (yet).

I added an answer to my question if you’re interested in the details, or have any thoughts for improving the implementation: Replicated projectile movement components are choppy - World Creation - Epic Developer Community Forums.

Thanks again, I appreciate the help!