Instant Replays

Learn how to configure, save, and play instant replays

Configuration

There are two SnapNet project settings relevant to instant replays.

Saved Instant Replays

The first is located under the Common category, called Saved Instant Replays, and is the number of instant replays the server can save for later playback. This is useful for features like Play of the Game where you want to save a clip of gameplay when it happens but don’t want to play it immediately.

Instant Replay Max Seconds

The second is located under the advanced section of the Server category, called Instant Replay Max Seconds, and is the number of seconds of history the server will save to support playing instant replays. Higher values allow you to capture longer instant replays at the expense of more server memory.

Saving Instant Replays

To save an instant replay for future playback, get a pointer to the USnapNetServer and call SaveInstantReplay(). Because this must be done on the server, this is typically triggered from the running server script instance.

enum class ESavedReplaySlot : uint8
{
    PlayOfTheRound,
    PlayOfTheMatch
};

void UExampleServerScript::SaveInstantReplay( ESavedReplaySlot Slot, int32 DurationMs )
{
    if ( USnapNetServer* Server = USnapNetServer::Get( this ) )
    {
        const int32 EndTime = Server->GetSimulation()->GetTimeMilliseconds();
        const int32 StartTime = EndTime - DurationMs;
        Server->SaveInstantReplay( static_cast<int32>( Slot ), StartTime, CurrentTime );
    }
}

Playing Instant Replays

There are two ways to play back an instant replay.

Playing a saved instant replay

The first is to play back an instant replay that had previously been saved as described above. This can be done by getting a pointer to the USnapNetServer and calling PlaySavedInstantReplay().

enum class ESavedReplaySlot : uint8
{
    PlayOfTheRound,
    PlayOfTheMatch
};

void UExampleServerScript::StartPlayOfTheRound( ESavedReplaySlot Slot )
{
    if ( USnapNetServer* Server = USnapNetServer::Get( this ) )
    {
        const USnapNetSettings* SnapNetSettings = GetDefault<USnapNetSettings>();
        for ( int32 ClientIndex = 0 ; ClientIndex < SnapNetSettings->MaxClients ; ++ClientIndex )
        {
            if ( Server->IsClientConnected( ClientIndex ) )
            {
                const int32 EndTime = Server->GetSimulation()->GetTimeMilliseconds();
                const int32 StartTime = EndTime - DurationMs;
                Server->PlaySavedInstantReplay( static_cast<int32>( Slot ), ClientIndex, PlayOfTheGamePlayerIndex, PlayOfTheGameContextEntityIndex );
            }
        }
    }
}

The above code plays a saved instant replay for all connected clients.

PlayOfTheGamePlayerIndex would refer to the player’s point of view from which the replay should be spectated. If no player in particular should be spectated, this can be -1.

PlayOfTheGameContextEntityIndex refers to an additional entity that was present at the time which clients can use at their discretion based on context. In a shooter, this might be the entity which actually inflicted the damage or an entity representing a desired camera transform. It’s up to the game to decide if and when a context entity should be provided and what it should be used for. If none is desired, -1 can be passed in instead.

Playing a live instant replay

Another option is to play an instant replay of events that just occurred recently. This has two advantages. The first is that, since the history is still in the server’s instant replay buffer, you don’t need to save it first. The second is that, because the instant replay is relative to the current time, you can specify an end time in the future. Consider the following example of playing a killcam:

void UExampleServerScript::OnPlayerKilled( int32 VictimPlayerIndex, int32 AttackerPlayerIndex )
{
    USnapNetServer* Server = USnapNetServer::Get( this );
    int32 ClientIndex = Server->GetClientIndex( VictimPlayerIndex );
    if ( ClientIndex >= 0 && AttackerPlayerIndex >= 0 )
    {
        const int32 DeathTime = Server->GetSimulation()->GetTimeMilliseconds();
        Server->StartInstantReplay( ClientIndex, AttackerPlayerIndex, -1, DeathTime - 3000, DeathTime + 3000 );
    }
}

The above code plays an instant replay for the victim starting 3 seconds before their death and ending 3 seconds after from the perspective of the attacking player.

Checking Replay Status

There are a number of functions on USnapNetClient to get state related to instant replay playback. These are particularly useful for showing the correct UI elements or camera perspectives.

enum class ESavedReplaySlot : uint8
{
    PlayOfTheRound,
    PlayOfTheMatch
};

void UExampleUserWidget::NativeTick( const FGeometry& MyGeometry, float InDeltaTime )
{
    Super::NativeTick( MyGeometry, InDeltaTime );

    if ( USnapNetClient* Client = USnapNetClient::Get( this ) )
    {
        if ( Client->IsPlayingInstantReplay() )
        {
            const int32 InstantReplaySlotIndex = Client->GetInstantReplaySlotIndex();
            if ( InstantReplaySlotIndex == static_cast<int32>( ESavedReplaySlot::PlayOfTheRound ) )
            {
                const int32 InstantReplayContextEntityIndex = Client->GetInstantReplayContextEntityIndex();
                UE_LOG( LogTemp, Log, TEXT( "Playing the Play of the Round with context entity %d" ), InstantReplayContextEntityIndex );
            }
        }
    }
}