Before I go on, let me just note ahead of time that replication has already been documented at great length in the wiki I mentioned before, and is quite a heavy subject on its own which is generally only recommended to look at when you already got the hang of how the engine overall works, so if you want to know more details, here you go:
https://unrealwiki.unrealsp.org/index.p ... n_overview
1337GameDev wrote: ↑Sat Nov 21, 2020 12:54 am
Hmm, so my understanding is any Mutator should be instantiated from a simulated function, and the function execution will be replicated on clients?
No.
First and foremost, function execution is not "replicated" at all.
While there is such a thing as "replicated functions" (which you may know as RPCs if you used other engines or technologies overall with this concept), that's a completely different subject and has nothing to do with a "simulated function", so let's not call them "replicated".
Simulation here means that the server and the client have independent instances of the same actor, which are only connected to one another over the network with something like a shared ID, thus any function calls that happen in either side of the network only run on that side of the network alone, independently from one another.
In order to better understand this, the way an instance is replicated from the server to the client, can be somewhat summarized to the client just instantiating the same class and then just remain identified with an ID shared between client and server for that instance (there's more to it of course, but for the sake of simplicity this is the gist of it).
Mutators are generally server-side only actors, since generally a mutator only affects things like inventory replacement, set the map to low gravity, and such, so basic game logic if you will, nothing that the client has anything to do about, so they are not replicated at all in most cases (what ends up being replicated are the effects of those changes, such as the state of the gravity value will be replicated to clients).
However, HUD mutators have to be replicated, because they do run client-side code, and in that case is
not the function that instantiates this mutator that has to be simulated (because we already determined that the mutator is instantiated server-side), but rather the functions that run client-side code have to be simulated instead (such as to register the instantiated mutator as an HUD mutator to be used).
I cannot really go much in depth here without making a super giant post, as at that point I might as well do an in-depth explanation of replication with pictures and such (no time for that now though), so I really advise you to go look in the wiki to see how these things work, as well check the game source code (UnrealScript side), to see how things like mutators work (the code is very easy to follow, and mutators are relatively simple classes).
Also I also advise you to read my previous post again concerning the roles: I understand your confusion, since replication is a subject that may be hard to grasp at first, but read it again and read the wiki, start trying to do some experiments to see things working, and you should start to get the handle of it.
1337GameDev wrote: ↑Sat Nov 21, 2020 12:54 am
In the context of a hud Mutator, or something that doesn't need to be executed on the server (eg: the server doesn't care about the game state in the context of code for a hud), if this is registered / instantiated in a simulated function, it'll be executed on the server too?
Yes and no.
Let's say you do this in your HUD mutator:
Code: Select all
class MyHUDMutator extends Mutator
{
simulated function PostBeginPlay()
{
RegisterHUDMutator();
}
}
Since PostBeginPlay is an engine event, which is automatically called from the engine itself when the actor is instantiated, it will be called on both the client and server, but independently (remember: the "same" instance between client and server are actually separate instances of the same class, but just connected over the network with an ID).
By spawning this mutator server-side, you're essentially saying that the authoritative instance is the one in the server, and an authoritative instance is allowed to execute all functions from that instance, so in this case, yes, the RegisterHUDMutator will be called and executed in the server.
Then, by defining the mutator remote role as a simulated proxy, this means that the corresponding mutator instance in the client is simulated, and a simulated instance is only allowed to execute simulated functions.
Since PostBeginPlay is simulated, it's executed, and since RegisterHUDMutator is also simulated (if you check it), then it's also executed.
So in practice, yeah, both functions are called, independently, on each side of the network (client and server).
But then, when you look at the code of RegisterHUDMutator:
https://www.madrixis.de/undox/Source_en ... r.html#213
you will see that it iterates over all players, but only actually adds the mutator to the HUD mutators linked list if the player HUD instance is itself not None (not null), which is only true for the player corresponding to that client, and is false concerning other players, and is also false server-side for all players, meaning that in practice the HUD mutator is only added to the local player of that client.
1337GameDev wrote: ↑Sat Nov 21, 2020 12:54 am
When would you generally check the role of an actor? Just check if the role is an authority and then execute code? (Because an authority actor state will be replicated to the simulated proxy versions of the actor on the other end of the network)?
If the code is well structured enough, very rarely actually.
Generally, you would only add such a condition in a function which is meant to be executed in both sides of the network (client and server), but somewhere in there you have a tiny little thing which you want to ensure only gets executed in the authoritative instance, like instantiating a specific actor, like so:
Code: Select all
class SimPotato extends Actor
{
simulated function mySimulatedFunction()
{
//do stuff...
if (Role == ROLE_Authority) {
spawn(class'Veggie');
}
//do more stuff...
}
}
since without the condition that actor would be instantiated once in the authoritative side (server), and twice in the simulated side (client): the one explicitly instantiated on the simulated side, and other one instantiated as a result of the replication of the one from the authoritative side.
But generally speaking, you can easily imagine that you could completely prevent that kind of condition by simply creating a non-simulated function to do that, like so:
Code: Select all
class SimPotato extends Actor
{
simulated function mySimulatedFunction()
{
//do stuff...
spawnVeggie();
//do more stuff...
}
function spawnVeggie()
{
spawn(class'Veggie');
}
}
which would result in the exact same behavior, given that
spawnVeggie
is not simulated, thus it will only be executed when the Role is ROLE_Authority in the exact same way.
So it's hard to imagine this, but in the original UT99 source code you can check this condition being used a lot throughout the code, that being because Epic didn't really know to structure their code properly back then.
1337GameDev wrote: ↑Sat Nov 21, 2020 12:54 am
When would you manually set the role? Or is the role just set by the game normally, based on if it's called inside a simulated function?
The Role is something that you don't really set, ever.
What you do set is the RemoteRole, and generally this is done in the default properties block alone and you don't usually change it in the code.
The RemoteRole decides what kind of proxy the instance will act as remotely on the other side of the network, which goes all the way from ROLE_None (do not replicate the instance at all, keep it only in the authority side) to ROLE_AutonomousProxy (generally reserved for the player itself).
Usually, if you want an instance to be replicated, you set the RemoteRole to either ROLE_DumbProxy or ROLE_SimulatedProxy in the default properties, with one of the differences between the two being that DumbProxy does not run any code at all (not even simulated) in that side of the network.
The RemoteRole can be changed later in the code itself, which will modify the behavior of the remote instance accordingly, but it may not work as good as you may believe it to, due to a multitude of other replication factors I didn't cover here (relevance, tear-off replication, net priority, replication conditions of properties, etc), so is generally just best to set the RemoteRole in the default properties, and keep the same one throughout the entire lifetime of the instance.