Create a character entity
The simple player entity created in the quick start works as a bare-bones example but it doesn’t slide along surfaces when it collides with them. In Unreal Engine’s gameplay framework, this is typically handled by a subclass of UMovementComponent. These movement components are often tightly coupled to the actor classes to which they’re designed to attach. For example, UPawnMovementComponent must be attached to an APawn, UCharacterMovementComponent must be attached to ACharacter, and so on.
In this guide, we are going to create a player entity based on Unreal’s built-in ACharacter class and UCharacterMovement component, which is a very comprehensive character controller that robustly handles collision, slopes, jumping, crouching, different movement types, and more.
Introducing ASnapNetCharacterEntity
Because Unreal’s built-in character controller is so fully featured, SnapNet provides a built-in subclass of ACharacter, ASnapNetCharacterEntity, which provides a quick way to get minimal networked character movement going using best practices.
Although it’s not required to use ASnapNetCharacterEntity, it performs a number of important actions and optimizations on your behalf. The actual implementation is brief, so it’s strongly recommended to open up SnapNetCharacterEntity.h and SnapNetCharacterEntity.cpp and familiarize yourself with how it wraps Unreal’s functionality. Specifically, it does the following for you:
- Registers a USnapNetEntityComponent
- Configures it to sync the actor’s position and rotation
- Disables the generation of overlap events on the capsule collision component
- Due to SnapNet’s rollback functionality, it isn’t possible for overlap events to be dispatched correctly and the overlaps are more expensive to calculate, so they should be disabled for all components attached to entities.
- Configures the character movement component to run even when it’s not possessed by a player controller
- This is important because the entity created for the server simulation will never be possessed by a player controller but still needs to perform its movement.
- Configures the character movement component to tick after the actor
- In order to synchronize the movement component’s movement mode and velocity using SnapNet, the actor saves these variables in its own networked fields. The networked values are restored to the component at the beginning of the actor tick and then, after the component ticks for the frame, the new values are retrieved from the component and saved back into the networked variables. CharacterMovementUpdated() and Tick() can be overridden by subclasses to save and restore additional fields, respectively, as needed.
- Disables animation and kinematic bone updates on the skeletal mesh component
- It is rare to require processing of the entire animation blueprint and physics asset bodies on every simulation frame and can be computationally expensive due to prediction and resimulation. Usually, this type of data is only needed in response to a specific event e.g., a bullet is fired and a collision trace against the skeletal hitboxes must be performed. To help with this, ASnapNetCharacterEntity provides the RefreshPose() function which will manually refresh this on demand.
Create the entity class
First, we’ll create a subclass of ASnapNetCharacterEntity:
#pragma once
#include "SnapNetCharacterEntity.h"
#include "SimpleCharacterEntity.generated.h"
UCLASS( abstract )
class ASimpleCharacterEntity : public ASnapNetCharacterEntity
{
GENERATED_BODY()
public:
ASimpleCharacterEntity( const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get() );
virtual void Tick( float DeltaSeconds ) override;
};
#include "SimpleCharacterEntity.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "SnapNetEntityComponent.h"
#include "SnapNetSimulation.h"
ASimpleCharacterEntity::ASimpleCharacterEntity( const FObjectInitializer& ObjectInitializer )
: Super( ObjectInitializer )
{
CharacterMovement->bOrientRotationToMovement = true;
}
void ASimpleCharacterEntity::Tick( float DeltaSeconds )
{
Super::Tick( DeltaSeconds );
if ( USnapNetSimulation* Simulation = USnapNetSimulation::Get( this ) )
{
const float MoveForwardAxisValue = Simulation->GetInputAxis( EntityComponent->GetOwnerPlayerIndex(), MoveForwardAxisName );
const float MoveRightAxisValue = Simulation->GetInputAxis( EntityComponent->GetOwnerPlayerIndex(), MoveRightAxisName );
AddMovementInput( FVector::ForwardVector * MoveForwardAxisValue + FVector::RightVector * MoveRightAxisValue );
}
}
Create the renderer class
Unlike the simple player entity created in the quick start, we’re going to create a native class to represent the character entity’s renderer. In it, we’ll hook into the USnapNetEntityRendererComponent’s OnUpdateFromEntity delegate to save the velocity and movement mode from the simulation entity so that we can use it to update the character’s animation blueprint later in the frame.
#pragma once
#include "GameFramework/Pawn.h"
#include "SimpleCharacterRenderer.generated.h"
UCLASS( abstract )
class ASimpleCharacterRenderer : public APawn
{
GENERATED_BODY()
public:
ASimpleCharacterRenderer( const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get() );
static const FName EntityRendererComponentName;
static const FName RootSceneComponentName;
static const FName SkeletalMeshComponentName;
protected:
void OnUpdateFromEntity( const class USnapNetEntityComponent* EntityComponent, float DeltaSeconds, bool bTeleported );
UPROPERTY( Category = "Simple Character Renderer", VisibleAnywhere, BlueprintReadOnly )
class USnapNetEntityRendererComponent* EntityRendererComponent;
UPROPERTY( Category = "Simple Character Renderer", VisibleAnywhere, BlueprintReadOnly, Transient )
bool bIsInAir;
UPROPERTY( Category = "Simple Character Renderer", VisibleAnywhere, BlueprintReadOnly )
class USceneComponent* RootSceneComponent;
UPROPERTY( Category = "Simple Character Renderer", VisibleAnywhere, BlueprintReadOnly )
class USkeletalMeshComponent* SkeletalMeshComponent;
UPROPERTY( Category = "Simple Character Renderer", VisibleAnywhere, BlueprintReadOnly, Transient )
float Speed;
};
#include "SimpleCharacterRenderer.h"
#include "Components/SkeletalMeshComponent.h"
#include "Engine/CollisionProfile.h"
#include "SimpleCharacterEntity.h"
#include "SnapNetEntityComponent.h"
#include "SnapNetEntityRendererComponent.h"
const FName ASimpleCharacterRenderer::EntityRendererComponentName( "SnapNetEntityRendererComponent" );
const FName ASimpleCharacterRenderer::RootSceneComponentName( "RootSceneComponent" );
const FName ASimpleCharacterRenderer::SkeletalMeshComponentName( "SkeletalMeshComponent" );
ASimpleCharacterRenderer::ASimpleCharacterRenderer( const FObjectInitializer& ObjectInitializer /* = FObjectInitializer::Get() */ )
{
RootSceneComponent = CreateDefaultSubobject<USceneComponent>( RootSceneComponentName );
RootComponent = RootSceneComponent;
EntityRendererComponent = CreateDefaultSubobject<USnapNetEntityRendererComponent>( EntityRendererComponentName );
EntityRendererComponent->OnUpdateFromEntity.AddUObject( this, &ASimpleCharacterRenderer::OnUpdateFromEntity );
SkeletalMeshComponent = CreateDefaultSubobject<USkeletalMeshComponent>( SkeletalMeshComponentName );
SkeletalMeshComponent->SetupAttachment( RootSceneComponent );
SkeletalMeshComponent->SetCollisionProfileName( UCollisionProfile::NoCollision_ProfileName );
}
void ASimpleCharacterRenderer::OnUpdateFromEntity( const class USnapNetEntityComponent* EntityComponent, float DeltaSeconds, bool bTeleported )
{
if ( ASimpleCharacterEntity* SimpleCharacterEntity = Cast<ASimpleCharacterEntity>( EntityComponent->GetOwner() ) )
{
bIsInAir = SimpleCharacterEntity->GetMovementMode() == MOVE_Falling;
Speed = SimpleCharacterEntity->GetMovementComponentVelocity().Size();
}
}
With these new files added, regenerate the project files and compile.
Create the entity blueprint
The next step is to create a blueprint for the entity. Open the editor and use the Content Browser to navigate to the Entities folder. With this folder open, click Add/Import → Blueprint Class and select SimpleCharacterEntity as the parent class for the new blueprint.
Name it BP_SimpleCharacterEntity and then double-click on this new blueprint to open it up. In the Components panel, select the Mesh component, then assign SK_Mannequin to the Skeletal Mesh property. In order to align the mesh to face forward and fit inside the collision capsule, set its location to (0, 0, -95) and its rotation to (0, 0, -90).
Note
In this case, assigning the skeletal mesh property is not actually required. This entity blueprint will not be rendered—it’s only used for simulation—and we are not using the mesh’s physics asset for anything in the simulation. However, we do it here to visualize the mesh’s transform relative to the collision capsule. More advanced use cases might use it for performing collision tests during the simulation.Compile and save the blueprint.
Create the renderer blueprint
Now, we’ll create our renderer blueprint to represent our character visually. Back in the Content Browser, navigate to the Entities folder, and create a new blueprint class by clicking Add/Import → Blueprint Class and selecting SimpleCharacterRenderer as the parent class for the new blueprint.
Name it BP_SimpleCharacterEntity_Renderer and then double-click it to open it up. Like in the entity blueprint, select the Skeletal Mesh Component, and assign SK_Mannequin to the Skeletal Mesh Property. It’s important to remember that the transform of this renderer blueprint is going to mirror that of the entity blueprint, so we need to set the mesh’s transform the same way i.e., set its location to (0, 0, -95) and its rotation to (0, 0, -90).
Compile and save the blueprint.
Link the entity with its renderer
Like the simple player entity created in the quick start, we need to link the entity blueprint to its renderer blueprint. To do so, open BP_SimpleCharacterEntity and select its entity component. In the Details panel, expand the SnapNet Entity category, open the Entity Renderer Class dropdown, and select BP_SimpleCharacterEntity_Renderer.
Compile and save the blueprint.
Register the entity with SnapNet
Also like the simple player entity example, we need to register the entity with SnapNet. Go to Edit → Project Settings → SnapNet → Common → Registration → Entities. Click on the + sign to add an entry to the list and then, in the dropdown, select BP_SimpleCharacterEntity.
Configure the server script
In order to spawn the character entity when a player joins, we need to configure the server script to use BP_SimpleCharacterEntity. Use the Content Browser to navigate to the Blueprints folder and double-click on BP_SimpleServerScript to open it. Reassign the Player Entity Class to BP_SimpleCharacterEntity.
Compile and save the blueprint.
Create the animation blueprint
The last thing we need to do is animate the characters appropriately as they run around. The template content imported during the quick start already contains a useful animation blueprint called ThirdPerson_AnimBP. We can’t use it as-is because it’s built for single player but with a couple of quick changes it will work great. In the Content Browser, navigate to Content → Mannequin → Animations, right-click on ThirdPerson_AnimBP, and then select Duplicate. Name it ABP_SimpleCharacter and then double-click on it to open it. In the Event Graph, we can see that two important variables are being set based on the owning character. First, it gets whether or not the character is falling from its owner’s movement component, and then it gets its speed. We’ll set those same variables but we’ll get their values directly from the owning SimpleCharacterRenderer. Replace the existing event graph logic with the following:
Compile and save the animation blueprint.
Configure the animation blueprint
Finally, navigate to the Entities folder and re-open BP_SimpleCharacterEntity_Renderer. In the Components panel, select the Skeletal Mesh Component. In the Details panel, under the Animation category, assign ABP_SimpleCharacter to the Anim Class property.
Compile and save the blueprint.
Test the results
Press Play in the toolbar and you should see the character(s) spawn in. You can run around using W/A/S/D.
And that’s it! You now have movement powered by Unreal’s built-in character controller and networked with SnapNet.
Next steps
Check out other Guides to learn more about what SnapNet can do.