Trigger an event

Follow these steps to raise a networked event from the simulation.

While the core of the networked simulation is powered by entities and their synchronized fields, there are certain situations that don’t fit neatly into that model. For example, consider the act of firing a bullet in a first-person shooter. In order to play the corresponding visual and sound effects, there’s likely a number of fields that you’d like to transmit regarding the firing of that weapon such as what weapon was used, the attacker, the victim (if any), from where it was fired, the impact location, the surface type that was hit, etc. These fields don’t necessarily belong on the attacking actor itself, but are critical to triggering effects associated with the firing of the weapon. This is where SnapNet’s events come in.

In this guide, we’ll extend the quick start to trigger a simple Niagara effect above the player when you click the mouse.

Add Niagara as a dependency

In order to use Niagara from our game module, we need to add it as a dependency in our build script. Open up SnapNetQuickStart.Build.cs and add Niagara to PublicDependencyModuleNames. Ours looks like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
using UnrealBuildTool;

public class SnapNetQuickStart : ModuleRules
{
    public SnapNetQuickStart( ReadOnlyTargetRules Target ) : base( Target )
    {
        PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;

        PublicDependencyModuleNames.AddRange( new string[] { "Core", "CoreUObject", "Engine", "InputCore", "Niagara", "SnapNet" } );
    }
}

Create the event class

First, we’ll create a subclass of USnapNetEvent:

#pragma once

#include "SnapNetEvent.h"
#include "SnapNetPropertyEntityIndex.h"
#include "SnapNetPropertyVector.h"

#include "SimpleEvent.generated.h"

class UNiagaraComponent;
class UNiagaraSystem;

UCLASS( abstract )
class USimpleEvent : public USnapNetEvent
{
    GENERATED_BODY()

public:
    USimpleEvent();

    virtual void OnCanceled() override;

    virtual void OnConfirmed() override;

    virtual void OnPredicted() override;

    // The entity above which the FX should be played
    UPROPERTY()
    FSnapNetPropertyEntityIndex Entity;

protected:
    void SpawnFx();

    // Whether the FX have already been spawned for this event
    UPROPERTY( Transient )
    bool bFxSpawned;

    // The FX component spawned at runtime to play the FX system
    UPROPERTY( Transient )
    UNiagaraComponent* FxComponent;

    // The offset at which the FX should be played relative to the entity
    UPROPERTY( Category = "Simple Event", EditDefaultsOnly, BlueprintReadOnly )
    FVector FxOffset;

    // The FX system to play
    UPROPERTY( Category = "Simple Event", EditDefaultsOnly, BlueprintReadOnly )
    UNiagaraSystem* FxSystem;
};
#include "SimpleEvent.h"

#include "NiagaraComponent.h"
#include "NiagaraFunctionLibrary.h"
#include "SnapNetEntityRendererComponent.h"
#include "SnapNetSubsystem.h"

USimpleEvent::USimpleEvent()
    : FxOffset( 0.0f, 0.0f, 100.0f )
{
}

void USimpleEvent::OnCanceled()
{
    if ( FxComponent )
    {
        FxComponent->DestroyComponent();
        FxComponent = nullptr;
    }
}

void USimpleEvent::OnConfirmed()
{
    SpawnFx();
}

void USimpleEvent::OnPredicted()
{
    SpawnFx();
}

void USimpleEvent::SpawnFx()
{
    if ( bFxSpawned )
    {
        return;
    }

    bFxSpawned = true;

    if ( FxSystem != nullptr && Entity.GetValue() >= 0 )
    {
        if ( const USnapNetSubsystem* SnapNetSubsystem = USnapNetSubsystem::Get( this ) )
        {
            if ( const AActor* RendererActor = SnapNetSubsystem->GetEntityRenderer( Entity.GetValue() ) )
            {
                FxComponent = UNiagaraFunctionLibrary::SpawnSystemAttached( FxSystem, RendererActor->GetRootComponent(), NAME_None, FxOffset, FRotator::ZeroRotator, EAttachLocation::KeepRelativeOffset, true );
            }
        }
    }
}

Spawn the event

Now, we modify our SimplePlayerEntity to spawn a new event on click. Since there is already an input action named SetDestination defined as the left mouse click, we’ll use that as a condition to trigger the event. Changes to the existing SimplePlayerEntity code are highlighted below:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#pragma once

#include "GameFramework/Actor.h"

#include "SimplePlayerEntity.generated.h"

class USimpleEvent;
class USnapNetEntityComponent;

UCLASS( abstract )
class ASimplePlayerEntity : public AActor
{
    GENERATED_BODY()

public:
    ASimplePlayerEntity( const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get() );

    virtual void Tick( float DeltaSeconds ) override;

    static const FName EntityComponentName;
    static const FName MoveForwardAxisName;
    static const FName MoveRightAxisName;
    static const FName SetDestinationActionName;

protected:
    UPROPERTY( Category = "Simple Player Entity", VisibleAnywhere, BlueprintReadOnly )
    USnapNetEntityComponent* EntityComponent;

    UPROPERTY( Category = "Simple Player Entity", EditDefaultsOnly, BlueprintReadOnly )
    TSubclassOf<USimpleEvent> SimpleEventClass;

    UPROPERTY( Category = "Simple Player Entity", EditDefaultsOnly, BlueprintReadOnly )
    float Speed;
};
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include "SimplePlayerEntity.h"

#include "SimpleEvent.h"
#include "SnapNetEntityComponent.h"
#include "SnapNetSimulation.h"

const FName ASimplePlayerEntity::EntityComponentName( "SnapNetEntityComponent" );
const FName ASimplePlayerEntity::MoveForwardAxisName( "MoveForward" );
const FName ASimplePlayerEntity::MoveRightAxisName( "MoveRight" );
const FName ASimplePlayerEntity::SetDestinationActionName( "SetDestination" );

ASimplePlayerEntity::ASimplePlayerEntity( const FObjectInitializer& ObjectInitializer /* = FObjectInitializer::Get() */ )
    : Speed( 400.0f )
{
    EntityComponent = CreateDefaultSubobject<USnapNetEntityComponent>( EntityComponentName );
    EntityComponent->SetRequiresOwnerInput( true );
    EntityComponent->SetTransformSyncMode( ESnapNetTransformSyncMode::PositionAndRotation );

    PrimaryActorTick.bCanEverTick = true;
}

void ASimplePlayerEntity::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 );
        const FVector MovementDelta = ( FVector::ForwardVector * MoveForwardAxisValue + FVector::RightVector * MoveRightAxisValue ) * Speed * DeltaSeconds;

        AddActorWorldOffset( MovementDelta, true );

        if ( Simulation->WasInputActionPressed( EntityComponent->GetOwnerPlayerIndex(), SetDestinationActionName ) )
        {
            if ( USimpleEvent* SimpleEvent = Simulation->SpawnEvent<USimpleEvent>( SimpleEventClass, EntityComponent ) )
            {
                SimpleEvent->Entity.SetValue( EntityComponent->GetEntityIndex() );
            }
        }
    }
}

With those changes made, compile and run the editor.

Synchronize input action

First, as mentioned above, we’ll be using the SetDestination input action that’s already defined to detect when players click the left mouse button. In order to make this input action available for use within your network simulation, you must add it to your project’s SnapNet settings. To do so, navigate to Edit → Project Settings → Plugins → SnapNet and expand the Common → Input section. Under Input Actions, click on the + sign and enter SetDestination.

SnapNet input settings screenshot

Note that this SetDestination label matches the one defined in Project Settings → Engine → Input.

Create the event blueprint

The next step is to create a blueprint for the event. Open the editor and use the Content Browser to create a new folder named Events. With this folder open, click Add/Import → Blueprint Class and select SimpleEvent as the parent class for the new blueprint.

Create SimpleEvent blueprint screenshot

Name it BP_SimpleEvent. Now double-click on this new blueprint to open it up and, in the Details panel, assign the Fx System property to RadialBurst. If you don’t see RadialBurst in the dropdown, click View Options in the bottom-right of the dropdown and ensure that Show Engine Content and Show Plugin Content are both selected.

Screenshot of BP_SimpleEvent in blueprint editor

Compile and save the blueprint.

Now we need to tell our player entity which event class to spawn when we click. In the Content Browser, navigate to the Entities folder and double-click on BP_SimplePlayerEntity to open it. In the Class Defaults, assign Simple Event Class to the BP_SimpleEvent class you just created.

Screenshot of BP_SimplePlayerEntity in blueprint editor

Compile and save the blueprint.

Register the event with SnapNet

Like entities, any events that you want to spawn during a SnapNet session must be registered with the SnapNet Subsystem prior to session start. We’ll do this in the project’s SnapNet settings. Go to Edit → Project Settings → SnapNet → Common → Registration → Events. Click on the + sign to add an entry to the list and then, in the dropdown, select BP_SimpleEvent.

Screenshot showing registration of BP_SimpleEvent in the SnapNet project settings

Play

Press Play in the toolbar and click your left mouse button while the viewport is active. You should see the Niagara FX play above the sphere you’re controlling.

Screenshot of particle effect playing during gameplay

And that’s it! You now have fully-functional networked effects powered by SnapNet.

Next steps

Check out other Guides to learn more about what SnapNet can do.