Download

Come Learn Blueprint Multiplayer with me! (aka Tom's a Glutton for Punishment)

Update: Blueprint Multiplayer Movement replication is officially good to go! Here’s a shortcut to the tutorial post:
https://forums.unrealengine.com/show…ll=1#post19427

Hello intrepid UE4 Blueprinters!
Welcome to what is assuredly a fiasco in the making! :smiley:

I’m trying to make a Blueprint-only multiplayer game. I’m going to start VERY small and try and build from there. I want to share with everybody exactly how, from the ground-up to make an MP game with blueprints. Epic have stated that they want Blueprints to have full network functionality, so here’s my personal test-bed for the state of BP MP. I want to share with everybody my journey and get help along the way from those that know multiplayer, replication and anything else better than I.

About me
Ive been using UDK/UE3 for years as a tech artist and eventually UnrealScripter. I was invited to the the UE4 beta last year and have spent a fair bit of time in it learning materials, lighting and blueprints. I have a pretty good grasp of all of it, but I’ve never really tackled multiplayer and replication (it’s so scary!). Now I’ve made a fun game prototype (Drone Wars Extreme! *the name changes daily) and I want to make it multiplayer, but don’t want to have to learn C++ to do it. Why? because the game runs great without ANY C++ and I feel that Blueprints are fully capable of creating great games without C++. (Here’s a little clip from the other day: https://www.youtube.com/watch?v=eeHsqCM2QyI)

Where I’m at now
I’ve started over for the sake of learning, scrapping all that fancy copter action and going with just a simple Pawn, etc.
I’ve created a Blueprint for each of the classes and assigned them accordingly to the gametype:

Great. You can see I added a big, green arrow to my Pawn (I can see where it is in the world, etc.) There’s nothing other than the Static Mesh arrow in there.

I’ve also made a simple level that has a few PlayerStarts in it:

First problem!
I’m running my multiplayer game from PIE using the fancy Number of Clients setting and a dedicated server (Worst case scenario, ya know?) and immediately, I have a problem. My two players are starting at the same playerstart!

I don’t see any obvious functionality to determine what PlayerStart I should be assigning my players. Also, I’m not savvy enough with networking in UE to even know what class should be handling that sort of thing.

Can anybody help? Thanks for tuning in! As soon as I figure out an answer, I’ll post it here, then on to to the next problem, etc until there’s a real, living MP game!

Files
Oh, here’s the project files. I’ll try and update them as we figure all this out:
https://dl.dropboxusercontent.com/u/…_20140404a.zip

This is a cool project. I’m going to bump since that’s currently all I can do to help.

+1

Great stuff, looking forward to watching your progress!

WOW, that video look’s amazing, great job. I’m glad that multiplayer will be supported in Blueprints.

Good luck Tom!
Your progress will help me greatly. Also wanted to note that over in another thread Billy Bramer from Epic, when I asked about blueprints stuff for networking 2 days ago, said the following;

So looking forward to that. I think it will help us both.

PS: Copter game is coming along great, looks fun. I first saw you showing it off on the ol’ UDK forums.

GameMode.cpp.
Start by making sure ShouldSpawnAtStartSpot() returns false, then create a custom ChoosePlayerStart().

The default behaviour is to place spawning players at the first PIE player start it can find or, failing that, just the last player start found.

You should be able to get what you need by checking out those functions in “ShooterGame\Private\Online\ShooterGameMode.cpp”.

Have fun! :slight_smile:

Thanks for the insight! I was trolling around the ShooterGame example, looking for where that was handled. There doesn’t seem to be anything in Blueprints that is analogous. I’ll try and make a workaround in the Game Mode blueprint.

I think I have a working solution for problem #1!

Using the knowledge that the Gametype was chosing the playerstart for the pawns in ShooterGame, I tried to access the functions being used in C++. No luck. I did however discover that I cannot use the variable names defined in GameType.h (specifically the PlayerStarts array!) But I could use the function names. While it’s probably a bad idea, I’ve done it anyways :stuck_out_tongue:

**Here’s how it works. **
When each player logs in, they spawn a pawn. In the Construction Script, I have the BP_MP_Pawn ask the BP_MP_Game GameType to find a PlayerStart actor in the level whose PlayerStartTag variable isn’t set to ‘Taken’ (the only variable accessible for PlayerStarts though Blueprints, and seems to be replicated.*).

This is accomplished by casting the gametype to BP_MP_Game and running the ChoosePlayerStart function. This function iterates through the PlayerStart actors in the level and compares the value of PlayerStartTag to ‘Taken’. If it’s not, it breaks the loop, then sets the tag to ‘Taken’ (so it will skip over it next time), then returns the PlayerStart actor reference to the Pawn that called the function! The Pawn simply takes that data and sets its location and rotation to match the PlayerStart.

Choose Player Start function (in BP_MP_Game Blueprint)

BP_MP_Pawn Construction Script

Here’s proof it worked!

Them pawns be lookin’ at eachother!

**Next up: **
Movin’ dem Pawns! Stay Tuned!

*I could create my own brand of PlayerStart actors using blueprints, as there isn’t anything particularly special about the class for this examples particular needs, but that’s over complicating things… for now…

Awww yiss

Okay, I think I may have learned something about replication. Gasp.

I got my pawns rotating around. And the SEE EACH-OTHER rotating. Seriously.

The secret sauce is… telling the server the actor has rotated! :o

How did I get here?
First off, I had learned that you can’t replicate functions in Blueprints. Only variables and Events. With that in mind, I set off trying a bunch of stuff.

What didn’t work (Don’t do this!)
My first attempt was pretty poor and didn’t work… kinda… Yeah kinda didn’t work. That means it didn’t work but not entirely! What I had done was set up a Custom Event in my Pawn to send the pitch and yaw inputs from the PC to the Pawn. I was calling that event each Tick from the PC and sending the pitch and yaw as floats. Setting the event to Multicast only seemed to rotate each player locally (as expected, it warns to run it only on the server!), setting it to run on the Server was the opposite: I only saw my pawn rotate on the other clients! Progress, indeed.

Then, I decided I should have the server send the rotation back to the local pawn. I set up another event (LocalRotate) and set it to OwningClient. This ran after each time the previous event finished and simply took the rotation of the pawn and sent it back to the client. IT WORKED!! But this was complicated AND the Pawn was rotating like it was ratcheted. This is an effect of compressing rotators for net play in UE. It’s something very common for games to do as it’s purely cosmetic and can take up a LOT of bandwidth.

**Here’s how to NOT do it. Again DON’T DO IT THIS WAY (Even tho it kinda works, but sucks)

And, how it looks in motion:

https://www.youtube.com/watch?v=4X28-JZlIac

(How to do it right in next post)

**How to ACTUALLY do it. **
Rotate the PC

http://i.imgur.com/x4bvjSX.png

Rotate the Pawn based on the PC’s rotation

http://i.imgur.com/GVpIpPy.png

Each Tick, send the server the pawn’s rotation.

http://i.imgur.com/oGAIb6S.png

And, how it looks:
https://youtube.com/watch?v=sHKP5xiUFws
Smoooooth!

Next up: Movement!
Actually, sleep as the 2-year old is sick and will be screaming at me all night :frowning: At-Least I figured out some replication in Blueprints today!

Source Files:
DOWNLOAD HERE

great tread! good luck with your development.

Good work! At last somebody’s talking about replication ,that’s why I abandoned my project in UDK!

Nice thread, but I wonder is it even necessary to update rotation on every tick?
I was also checking the spawning and sync movement as well, guess if I found something( I want to have a volume to spawn players in instead of fixed playerStart locations.)

What I think is(since I haven’t figure out how to do the random spawning part), when your input updates the Pawn, in Pawn blueprint where it sets rotation, make the rotation change replicatable by setting Replicate/ReplicateNotify(which creates an function).
And in the Pawn Blueprint defaults->Replication, there should already have a “Replicate movement”, and this should replicate all update regarding transforms of a pawn.

I thought the same thing about replicating the movement! But it simply doesn’t. Atleast not the rotation. Funny thing is, if you disable that checkbox, this method doesn’t work.
As far as updating every tick being intensive, yeah I could probably put some sort of check in and only update if there’s actually been a change in rotation. I might give the repnotify a chance, it’s possible way of doing that check easily.

As for the random spawn, I might go about it by making a blueprint with a volume and having it create X number of PlayerStart actors in the construction script by sending random line traces down, etc. that way you could visualize the possible start locations. Then, rather than sequentially finding a start location, you could get a random int.

Thanks for the input!

I’ve played around your level, and come to conclusion that Pawn and DefaultPawn are classes that does not handle those replication check boxes.
If you switch BP_MP_Pawn’s parent class to character, the movement would replicate properly without any additional event to drive update.
I think player controller is for when you have something extra, like the mini game example in Input examples.
(And this would somehow break your script, maybe some graph needs to be recompiled.)
Edit: by break I mean I can only see one arrow spawned in client 1 window. But when still using your input script,
I can turn client4 with mouse that sees movement from client 1(also its arrow). all other 3 clients stucked at their location, no arrow to be seen.
I use gamepad to control client1, and somehow keyboard and mouse assigned to client4 on start play.

Also, I think we both misunderstand what PlayerController class is about.
If you check the input example from ContentExamples, you will see that the player controller class has nothing in its graph.
In the UFO Pawn, it uses GetInputAxisMoveForward/Right/Up for movement and GetInputAxisLookUp/Turn for pitch and yaw.
Which use the input to drive velocity, and drive camera where and align pawn rotation to camera’s view direction.
Third person obviously use a lerp and velocity direction to drive actor’s direction.

I think if we ever need to build a multiplayer game based on just Pawn, Epic has to either make a insertable movement component that already handles replications,
or expose some class functions to allow us wiring them.(Have to check those source codes for sure.)
In the mean time, I think your approach plus some custom variable with RepNotify should get the ball rolling.

Thanks for checking that out. I was thinking that might be case as the Pawn class doesn’t have a Movement component. Saved me the trouble of checking it out! what’s strange is how unchecking it breaks the custom replication I have set up. Something seems amiss there.

Controller is really a way to separate logic from the Pawn. Take UT for example. you’d want to have basic abilities no matter if you were a walking character or one in a Goliath. So, for me all I’m doing is rotating the controller and using that to drive the rotation of the Pawn. You’re right however, if you’re not going to be switching Pawns, you only need to put the logic in the Pawn.

However, take my Quad Copter game. While you’d never switch Pawns in gameplay, I’m planning on having several classes of Copters (Light, Heavy, Balanced, etc.) and I’d want to have almost all my logic in the Controller so that I could have whatever Pawn I wanted and not have to redo the input and control code for each one I make (And keep it updated).

True to your case, however(yes there is always a however:D), if you think about your game, which closely mimic the UFO example, shouldn’t it be easier to have one generic flying blueprint that has all the logic, expose control parameters(like thruster strength, weight, mesh, etc) and leave the specific( ie animation and material etc) to your types of new blueprint vehicle.

It certainly is a hack in my opinion to let user directly access keyboard and other controller events, when you can already create custom events in project settings’ input bindings.
This way you can always have character/vehicle separation where W in character binds to MoveForward, and in vehicle binds to Acceleration, and you just use those events in your Pawn blueprint.

Also, I think it kinda make sense to disable replication for default Pawn class, where they are mostly used for either debugging or spectating purpose(which don’t replicate their movement to server/other client anyway.)
So when you inherit Pawn, there should be a movement component that we can use, and that has to wait for Epic.( Right now only have projectile and rotating movement component that you can add to a bluepriint.)

This looks good, will follow to see the progress looking to do some thing on my own like this. Keep it up!

Update, it seems that Pawn might actually do replicate, just that it’s own static mesh isn’t replicated by default.(And somehow StaticMesh replicate isn’t exposed in component)
Check this question and you can see there is a bStaticMeshReplicateMovement flag available when you blueprint a StaticMeshActor.(For something like physic enabled box/barrel/etc)

Warning, CODE block ahead, it should still be pretty easy to understand with any kind of scripting experience. Many keywords should be really familiar if you played Blueprint for a while
Also, here is how DefaultPawn setup it’s control, as I predicted, you add those input event binding in Pawn blueprint.(binds to a string you setup in editor)
These should be your basic setup if you create a new Pawn blueprint. (not from Custom Classes)



void ADefaultPawn::MoveRight(float Val)
{
   if (Val != 0.f)
   {
      if (Controller)
      {
         FRotator const ControlSpaceRot = Controller->GetControlRotation();

         // transform to world space and add it
         AddMovementInput( FRotationMatrix(ControlSpaceRot).GetScaledAxis( EAxis::Y ), Val );
      }
   }
}

void ADefaultPawn::MoveForward(float Val)
{
   if (Val != 0.f)
   {
      if (Controller)
      {
         FRotator const ControlSpaceRot = Controller->GetControlRotation();

         // transform to world space and add it
         AddMovementInput( FRotationMatrix(ControlSpaceRot).GetScaledAxis( EAxis::X ), Val );
      }
   }
}

void ADefaultPawn::MoveUp_World(float Val)
{
   if (Val != 0.f)
   {
      AddMovementInput(FVector::UpVector, Val);
   }
}

void ADefaultPawn::TurnAtRate(float Rate)
{
   // calculate delta for this frame from the rate information
   AddControllerYawInput(Rate * BaseTurnRate * GetWorld()->GetDeltaSeconds());
}


And rotation related are inherited from Pawn, note that even though it gets player controller to add rotation values,
but this is entirely inside Pawn.cpp. So in blueprint sense it should be in the event graph(ie, you might have different turn rate limit for different pawn)



void APawn::AddMovementInput(FVector WorldDirection, float ScaleValue)
{
	UPawnMovementComponent* MovementComponent = GetMovementComponent();
	if (MovementComponent)
	{
		MovementComponent->AddInputVector(WorldDirection * ScaleValue);
	}
}

void APawn::AddControllerPitchInput(float Val)
{
	if (Controller && Controller->IsLocalPlayerController())
	{
		APlayerController* const PC = CastChecked<APlayerController>(Controller);
		PC->AddPitchInput(Val);
	}
}

void APawn::AddControllerYawInput(float Val)
{
	if (Controller && Controller->IsLocalPlayerController())
	{
		APlayerController* const PC = CastChecked<APlayerController>(Controller);
		PC->AddYawInput(Val);
	}
}

void APawn::AddControllerRollInput(float Val)
{
	if (Controller && Controller->IsLocalPlayerController())
	{
		APlayerController* const PC = CastChecked<APlayerController>(Controller);
		PC->AddRollInput(Val);
	}
}


And here is the binding part(note these won’t be exposed in editor, thus we should probably avoid inherit DefaultPawn and use it as sort of debug Pawn. although you can also disable default binding and roll your own and get free movement component.)



void InitializeDefaultPawnInputBindings()
{
	static bool bBindingsAdded = false;
	if (!bBindingsAdded)
	{
		bBindingsAdded = true;

		UPlayerInput::AddEngineDefinedAxisMapping(FInputAxisKeyMapping("DefaultPawn_MoveForward", EKeys::W, 1.f));
		UPlayerInput::AddEngineDefinedAxisMapping(FInputAxisKeyMapping("DefaultPawn_MoveForward", EKeys::S, -1.f));
		UPlayerInput::AddEngineDefinedAxisMapping(FInputAxisKeyMapping("DefaultPawn_MoveForward", EKeys::Up, 1.f));
		UPlayerInput::AddEngineDefinedAxisMapping(FInputAxisKeyMapping("DefaultPawn_MoveForward", EKeys::Down, -1.f));
		UPlayerInput::AddEngineDefinedAxisMapping(FInputAxisKeyMapping("DefaultPawn_MoveForward", EKeys::Gamepad_LeftY, 1.f));

		UPlayerInput::AddEngineDefinedAxisMapping(FInputAxisKeyMapping("DefaultPawn_MoveRight", EKeys::A, -1.f));
		UPlayerInput::AddEngineDefinedAxisMapping(FInputAxisKeyMapping("DefaultPawn_MoveRight", EKeys::D, 1.f));
		UPlayerInput::AddEngineDefinedAxisMapping(FInputAxisKeyMapping("DefaultPawn_MoveRight", EKeys::Gamepad_LeftX, 1.f));

		UPlayerInput::AddEngineDefinedAxisMapping(FInputAxisKeyMapping("DefaultPawn_MoveUp", EKeys::Gamepad_LeftThumbstick, 1.f));
		UPlayerInput::AddEngineDefinedAxisMapping(FInputAxisKeyMapping("DefaultPawn_MoveUp", EKeys::Gamepad_RightThumbstick, -1.f));
		UPlayerInput::AddEngineDefinedAxisMapping(FInputAxisKeyMapping("DefaultPawn_MoveUp", EKeys::Gamepad_FaceButton_Bottom, 1.f));
		UPlayerInput::AddEngineDefinedAxisMapping(FInputAxisKeyMapping("DefaultPawn_MoveUp", EKeys::LeftControl, -1.f));
		UPlayerInput::AddEngineDefinedAxisMapping(FInputAxisKeyMapping("DefaultPawn_MoveUp", EKeys::SpaceBar, 1.f));
		UPlayerInput::AddEngineDefinedAxisMapping(FInputAxisKeyMapping("DefaultPawn_MoveUp", EKeys::C, -1.f));
		UPlayerInput::AddEngineDefinedAxisMapping(FInputAxisKeyMapping("DefaultPawn_MoveUp", EKeys::E, 1.f));
		UPlayerInput::AddEngineDefinedAxisMapping(FInputAxisKeyMapping("DefaultPawn_MoveUp", EKeys::Q, -1.f));

		UPlayerInput::AddEngineDefinedAxisMapping(FInputAxisKeyMapping("DefaultPawn_TurnRate", EKeys::Gamepad_RightX, 1.f));
		UPlayerInput::AddEngineDefinedAxisMapping(FInputAxisKeyMapping("DefaultPawn_TurnRate", EKeys::Left, -1.f));
		UPlayerInput::AddEngineDefinedAxisMapping(FInputAxisKeyMapping("DefaultPawn_TurnRate", EKeys::Right, 1.f));
		UPlayerInput::AddEngineDefinedAxisMapping(FInputAxisKeyMapping("DefaultPawn_Turn", EKeys::MouseX, 1.f));
		
		UPlayerInput::AddEngineDefinedAxisMapping(FInputAxisKeyMapping("DefaultPawn_LookUp", EKeys::Gamepad_RightY, 1.f));
		UPlayerInput::AddEngineDefinedAxisMapping(FInputAxisKeyMapping("DefaultPawn_LookUp", EKeys::MouseY, -1.f));
	}
}

void ADefaultPawn::SetupPlayerInputComponent(UInputComponent* InputComponent)
{
	check(InputComponent);

	if (bAddDefaultMovementBindings)
	{
		InitializeDefaultPawnInputBindings();

		InputComponent->BindAxis("DefaultPawn_MoveForward", this, &ADefaultPawn::MoveForward);
		InputComponent->BindAxis("DefaultPawn_MoveRight", this, &ADefaultPawn::MoveRight);
		InputComponent->BindAxis("DefaultPawn_MoveUp", this, &ADefaultPawn::MoveUp_World);
		InputComponent->BindAxis("DefaultPawn_Turn", this, &ADefaultPawn::AddControllerYawInput);
		InputComponent->BindAxis("DefaultPawn_TurnRate", this, &ADefaultPawn::TurnAtRate);
		InputComponent->BindAxis("DefaultPawn_LookUp", this, &ADefaultPawn::AddControllerPitchInput);
	}
}


So maybe there is a way to make a new Pawn class replicated properly.
I will keep you guys posted.

OMG! 1 word -OMG!Whata hieroglyph! I’m kidding:)
I know you wanted to help=) but the title says “come and learn BLUEPRINT multiplayer with me” and we want to learn BLUEPRINT multiplayer:)