Instant Replay System in Multiplayer using Dynamic Duplicated Levels


I wanna use the replay system to implement a killcam feature for like multiplayer deathmatch type of game and need stay connected to the server in order to spawn again for the next round.
In the memory streamer section of the documentation it says this can be done by using the dynamic duplicated levels. I tried enabling the Experimental_ShouldPreDuplicateMap method to create a level duplication at runtime what seems to work. I use the memory streamer and at PlayReplay I add the option “LevelPrefixOverride=1” in order to use the demo driver from the dynamic duplicated levels collection. Otherwise it plays the replay in the same level and disconnects from the server. However every time the game either crashes or doesn’t load the map correctly and breaks the PlayerController so I’m not able to move. The connection to the server seems to be fine, as I can still see other replicated players moving around on the broken map.
The map I’m testing on is just a simple persistent level. I tried splitting it up into a Level Stream but doesn’t seem to make any diference.

Doesn anyone else have this issue or got the replay system to work in a multiplayer environment without losing the connection to the server?

I think that’s because you’re using an experimental feature (which should never be used in production).

Have you tried this: DemoNetDriver - Using Memory Streamer - UE4 AnswerHub? There is no mention of “Experimental_ShouldPreDuplicateMap” there, and it seems pretty straight forward.

I agree, it seems like the whole Dynamic Duplicated Level - System is experimental.
The problem with the solution in the forum is that it will replay in the same level, what ends up in a connection lost to the server. Thats not what I want. I saw the PlayReplay() has a param “WorldOverride” that seems to manage in what world the replay happens but I could not get it to work. Not sure what the expected way is to “duplicate” the current world.

Well, I can think of an alternative method that actually implements disconnection into the system; it’s not ideal, but it should work. I don’t know if it’s even practical, but on paper, it should work.

When the player dies, play the replay (which will obviously disconnect from the server). After the replay is done, reconnect to the same server.

Keep a list of all players in the game, as well as their gameplay variables (score, team, etc.). When a player disconnects to play a replay, don’t remove them from the list, scoreboard, etc. (i.e. treat it as though they never left). When the player rejoins, give them back their gameplay variables, and resume the game like normal.

There’s many problems, like: How to make it seamless? Will disconnecting and reconnecting be fast enough? Will it be stable? Idk any of this, but it can be tested.

Another idea (actually think this may be better):
I once made a replay system in blueprints. Every frame, it copied the transforms of all actors and skeletal meshes (all bones) and saved it to disk. Then, to play it, you would play those transforms back in order. This was very inefficient, but I think I know how you can make it efficient for your needs.

You can have the server keep track of all transforms of every gameplay-specific object (characters, boxes, doors, etc.), and whenever a player watches a replay, stream those transforms to the player. This is efficient because the transform data is stored in only one place (the server), and every player uses the same data (from the server). Also, when you think about it, streaming a replay is almost no different than streaming actual players (i.e. instead of streaming live transforms, we’re streaming recorded ones).

The benefit of this idea is that you stay connected to the server the whole time (which is what you want).

The only problem there is, I need to duplicate the map as I don’t want to merge live gameplay with recorded gameplay and so far I had no success on duplicating the UWorld at runtime. Otherwise I’m not sure if I can stop getting updates from the live game when I stream the replay and seemless travel back to live gameplay without update problems…

So here’s what I’ve got so far:

It doesn’t use demos, it’s all just blueprints (I know this is in c++ thread). It’s using the second idea I posted: the server keeps track of the transforms of the character, and when the client requests to see the replay, the server sends a copy of the replay to the client, and the client plays the replay. Currently, it’s only playing on the player’s character since I put it in the character blueprint, so I’m gonna move it in it’s own “replay manager” actor.

I realized you actually only need to track the gameplay variables and not the bone transforms. The animation of the skeletal mesh be handled by the animation blueprint (same as how the player character works).

Also, notice in the video how the last replay only plays on the client, not the server. The server doesn’t actually see the replay, so the server character can still be controlled by the player as though the replay isn’t even happening. On the client, when the replay stops, the character snaps right back to where it is on the server. This shows that you may not have to duplicate the level for the replay.

That looks great :slightly_smiling_face:
Do you track the transform/replication variables per Actor Tick?
How would you handle Actors that dynamically spawn during recording/playback? I think the server would need to track spawning/destroying Actor aswell and needs to add that to the replay data the client receives. That sounds a bit tricky. To get rid of not merging live gameplay and clientside replay I could disable replication for all Actors and re-enable when the replay is done.

Yeah (EDIT: I don’t do it every tick; I do it at a rate of 30 FPS (configurable); this is mainly to reduce the size of the replay data). Currently it only tracks the location and rotation of the character, as well as if it’s in the air (which is what happens when the dots turn red). The idea is to record only the gameplay variables that are necessary for the replay (i.e. have cosmetic effects).

It actually wouldn’t be difficult at all (I think). Spawned actors are replicated, so when an actor spawns on the server, it spawns on the clients. The replay would then play the same as any other actor.
For things like grenades, you could just have the mesh always loaded but hidden, and when the player throws the grenade, unhide it.

Also, the client never receives any replay data until it requests it. When the client requests the replay, the server sends it in an array to the client, then the client plays the data from that array. Basically, the server only records the replay and the client only plays the replay; the client never records and the server never plays; however, for non-gameplay things (like chatter), you could just record it on the client.

Actually, I found out that won’t work. Setting an actor’s replication can only be called from the server, not the client, which means the replication is set for all clients, not just one (here are the two nodes: Set Replicates, Set Replicate Movement). So if you disable replication for all actors, it will disable replication of those actors for all clients. But it’s not a big deal. From what I got from the memory streamer docs, the gameplay still updates during the replay, so it would be the same here.

There’s multiple ways to switch from live to replay and back:

  • Play the replay directly on the live actors (which is how I do it in the example). When playing the replay, the recorded movement overwrites the live movement. Since it only plays on the client, the server and other clients don’t see the replay happening. In fact, if I move the character while the replay is playing, the client still sees the replay, but the server and other clients see the character’s live movements.
  • Have two separate sets of actors: one for live and one for replay. If a replay is playing, hide the live actors and show the replay actors. You only need to duplicate gameplay actors, not the entire level.
  • Duplicate the level using Load Level Instance, which allows streaming the same level (tested it already, works). I think this is worse than the second idea since the entire level is duplicated vs. just the gameplay actors.

The last two may only be necessary if changes to actors can’t be undone (e.g. destroying a box). However, if you track the state of the actor (box_fixed, box_destroyed), you wouldn’t have to duplicate it.

I found this: WasActorRecentlyRendered | Unreal Engine Documentation. You can use it to only replay actors that the player’s camera saw.

I’m wondering if the client should do the recording instead. I think the more actors there are that have to be recorded, the more data there will be that needs to be sent back (since I send them all as one array at one time). Considering the killcam will always be around the player (from the view of the enemy), the player will already have everything it needs to do the replay. So really, we may only need to send what the enemy saw; actually, we may only need to use the replay data from the enemy (i.e. use only replay data from the client that killed the player).

Thanks for sharing your approach in such a detail. I think that will actually work until Epic fixes the replay system. However I implemented my killcam feature now using ffmpeg and generate a mp4 file from the cameras RenderTarget. That worked very well and also fits my requirement a bit better as I don’t really need a full replay system but rather just a way to capture a video from a cameras perspective for a certain period of time.

1 Like

Lol, I was just thinking that, too. Good to know you found a solution!

I would’ve posted the nodes for my example, but it was too messy and things weren’t done efficiently. But in a nutshell, it was basically:

  • Server records transform of actor every 1/30th of a second (30 fps) into a circular buffer
  • Client requests replay using a “run on server” event
  • Server sends replay (array of transforms) to client using a “run on client” event
  • Client plays replay by setting the transform of the character

I think it can be done way more efficiently, but if you decide later to implement a full replay system, that’s a starting point.

1 Like