Kill mechanic problem

The idea is that the player presses the kill button (Q) and the closest player dies. But my cast:
1.
Roles:
Server - victim;
Client - killer.
Action: Client removes its widgets, Client sees server dying animation, No one becomes a spectator, Server doesn’t react to this.
1.
Roles:
Server - killer;
Client - victim.
Action: Server removes its widgets, Server sees itself dying, Server becomes a spectator, Client doesn’t react to this.


Function used to get the nearest player (Works fine)


Blueprint used in Character BP for kill request


Detecting death

Variables default values:
health: 1
dead: False

I don’t understand what’s the problem here and why the heath variable changes for the killer and not for the victim.

PS. I’m not talking about replication, it’s a completely different story. I’m now mostly interested in killing mechanisms.

Hmmmm…there’s a lot to pick apart here so I’ll just start asking questions in hopes that I can better understand what’s going on and what you’re after…

Let’s start with the “GetNearestPlayer” function. Is that actually working correctly? I don’t see anything that stops it from getting itself. You’re doing a “get all actors of class” but there doesn’t seem to be a mechanism whereby the calling actor can filter itself out…unless of course the killer and victim are in fact different classes. In which case the initial cast you’re doing in the for loop is acting as the filter. Next, the “GetPlayerCharacter” call would always grab the local player controller’s character as the player index is 0 and you appear to be running a client/server model. For starters I would just print “BestPlayer” as is, don’t worry about the name just send it straight to “Print String”…do the same for the calling actor and make sure that in fact those are different.

Also, you’re not going to achieve the right outcome by ignoring replication. That will be the key to getting the outcome you want…and just so I’m clear, the outcome you want is the following

server - victim
client - killer
outcome - server removes all widgets, plays dying animation, becomes spectator

server - killer
client - victim
outcome - clien removes all widgets, plays dying animation, becomes spectator

…correct?

1 Like

“GetNearestPlayer” filters itself by comparing the distance to the player to 0.

The killer and the victim are the same Character Blueprint Class (BP_AUCharacter_2)
As for the “GetPlayerCharacter”, I’ll change that later, that’s not very important for me now, the nickname is not gonna be shown right after death.

Yes, the replication is in fact important here but I meant that my goal, for now, is to at least make it work locally for the victim. And yep, the outcome you wrote is right.

“GetNearestPlayer” filters itself by comparing the distance to the player to 0.

I noticed that, however, it’s not entirely explicit enough. Imagine the killer is on the move while he presses the “kill” key. His location at that instance in time will be passed into the function. Meanwhile the get all actors of class will take some time (albeit very small) to execute and each run through the for loop will take some time (again, small but not nothing) to make it’s way through the loop. So by time you call “GetActorLocation” for yourself in the loop, you could have moved enough such that the distance is not exactly 0.0. It would be worth investing a little time here up front to make this more explicit and guarantee that GetNearestPlayer is in fact returning something other than itself. At the very least, print out the actor returned and make sure it’s what you expect.

As for the “GetPlayerCharacter”, I’ll change that later, that’s not very important for me now…

Fair enough…while debugging though, at least make sure you’re printing out something meaningful so that you can verify functionality. You don’t want to waste your time only to later realize that a function up stream wasn’t doing exactly what you thought.

I meant that my goal, for now, is to at least make it work locally for the victim

You won’t be able to do that without proper replication. You can only hope to get the functionality working for the killer, and even then only server side. Tackling multiplayer workflow is a different beast which requires you to think about the implementation from a replication perspective up front, not later, as this will guide every decision for the most part. Let’s take the “Tick” functionality for instance…you have a call to “Possess”. The server icon in the top right corner indicates that it only runs on the server…this means it has no meaning on the client. Next the “Remove All Widgets” function has a monitor symbol…that means that it runs for the local player only. There is no context to it, it simply removes any and all widgets on the local screen. That is why, under either scenario you provided, the widgets for the killer get removed.

It seems to me like there is some “running before walking” going on here. Even if you got this to work in some capacity as it is setup now, you will have no chance moving forward if you don’t first get a handle on replication to some extent. It is the foundation of any multiplayer experience you will make.

1 Like

I thought it might be useful to answer one of your direct assertions in hopes that it illustrates why you need to consider this from a replication perspective…

I don’t understand what’s the problem here and why the heath variable changes for the killer and not for the victim.

Assuming that the GetNearestPlayer function is not returning itself, you are not actually changing the health of the killer. Let’s take a look at the scenario you gave

Roles:
Server - killer;
Client - victim.
Action: Server removes its widgets, Server sees itself dying, Server becomes a spectator, client doesn’t react to this.

So what happened here? Well, you have 4 pawns/characters in play. Server side pawn A and pawn B along with client side pawn A and pawn B. You take control of the server side pawn A and press the kill key. It grabs server side pawn B and sets its health to 0. Server side pawn B, on the tick event, recognizes that its health is 0, spawns an actor to play a death animation (why?!)…and then the cracks in the implementation begin to show…you call GetPlayerController. Remember, this is server side pawn B so you’re still on the server which means that GetPlayerController (index 0) is going to return the local player controller. Which in your case is the one controlling server side pawn A. Not what you’re really after which is the client side player controller. You then proceed to tell it to possess the newly spawned pawn…which it does. This is why “server sees itself dying”. It’s not actually dying, you’ve just attached the wrong player controller to it. You then make a call to “Remove All Widgets”…which does exactly what you asked it…it removes all of the widgets on the local screen…which happens to be the screen of server side pawn A not client side pawn B.

All of this gets addressed if you understand and consider replication up front when thinking about the execution flow.

As a test, try this. Call “DestroyActor” directly after setting the health to -1 on the tick event. Do not connect this to the actor spawning, forget that for a minute and leave it disconnected. Once you do that, using the scenario above, as the server press the kill key. I would expect that the other players pawn will get destroyed and you will see this on both server and client…it of course will not work the other way around without proper consideration for replication.

1 Like

As for the replication. What exactly should I use. As I learned:
“Not replicated” is for local changes no matter who’s calling it;
“Multicast” is for everyone except the server;
“Run on Server” is only for server;
“Run on owning Client” is only for the local client,
Right?
And here I run into a problem. Though I read and watched many learning courses, I still don’t understand how replication works. (Especially here with killing)

So my questions for now are:

  1. How to properly set up replication here;
  2. How to sort out killer’s characters at “GetNearestPlayer” (As I’ve never heard of two pawns for each player).

PS I’m not a good blueprinted, I just finished learning python and now I’m creating my first UE project in my life. :sweat_smile:

1 Like

It is definitely a topic that takes some time to wrap your head around and I won’t be able to do it justice here in a few paragraphs. This is something you’ll need to really put some time into to make sure that you grasp the concepts as it’s critical to any multiplayer endeavor with unreal or any other game engine for that matter.

Here are some good resources on the topic.

An Unreal Engine Blog – An Unreal Engine Blog (cedric-neukirchen.net)
Multiplayer in Unreal Engine: How to Understand Network Replication - YouTube

With that being said, let’s focus for a minute on what you have going on and hopefully I can relay the information in a way that’s meaningful. Keep in mind that I’m intentionally keeping this as focused on your issue as possible. You will find there are usually a couple of ways to solve a problem. My intent here is to offer a glimpse into what a typical design patter for something like this might look like.

First, everything you’re attempting to do can be done inside the pawn/character, including the search for the nearest player. From what I can see it looks like you might have put that out to a blueprint function library is that correct? Not that it’s complex by any means but initially start simple. If you find you need the function elsewhere then you can move it to a BFL.

Next, be careful with what you put on tick. All to often you see people adopt design patterns that rely on that over well thought out execution flows. Don’t get me wrong, some times it’s unavoidable. When to use it and not becomes more apparent with more experience. Everything you have on tick at the moment should be put elsewhere.

Now, a bit about your specific problem and how it relates to replication. I’m going to assume for a second that you’re running a listen server where one player is the host and the other is the client. Under this scenario, once play has begun the host version of the game (the server) will have the following:

  1. A Pawn/Character possessed/controlled by Player1 which we’ll refer to as SPawnA (S for server)
  2. A Pawn/Character possessed/controlled by Player2 which we’ll refer to as SPawnB (S for server)

On the client you will have the following

  1. A Pawn/Character possessed/controlled by Player1 which we’ll refer to as CPawnA (C for client)
  2. A Pawn/Character possessed/controlled by Player2 which we’ll refer to as CPawnB (C for client)

Keep in mind this is the tip of the iceberg as there are of course and gamemode, gamestate, playerstate, etc. that are all available but we’re going to focus on the things that matter to your scenario at the moment so you’re not drinking from the fire hose.

So, while playing on the host/server you are Player1 controlling SPawnA. While playing on the client your are Player2 controlling CPawnB. The general idea is that anything Player1 does with it’s pawn on the host, gets replicated and played back down on the client. Anything Player2 does with it’s pawn on the client, gets replicated and played back on the host. I can not stress enough how simplified a view this is and how much is hidden from you, but let’s move on.

Ok, from here, whenever I consider the execution flow I prefer to think of things from the client’s perspective as this usually pertains to the server as well but not vice versa so let’s start there. So the client (Player2) wants to kill the nearest player. He presses a key which fires a chain of execution in CPawnB. Right now you’re finding the nearest player on the client side and attempting to change it’s health. While this works locally for the client, the server has no idea what has just happened, why? The simple answer is that the client does not have the authority over the game, the server does. So anything you change on the client, unless explicitly replicated upstream, has no effect on the server. Let’s make it have an effect. In order for this to work, you want the “GetNearestPlayer” function to run from the server version of CPawnB (SPawnB). So how do we do that? With a server reliable RPC. Create a custom event in your character graph and set it to “Run On Server” and check the reliable flag. This will tell unreal that this event needs to be run on the server AND we need a guarantee that it is going to fire (reliable). Then, move the BP nodes that you currently have following “InputAction Kill” over to this new event, and hook the inputaction up to a call to your newly created event. So the execution flow looks something like “InputAction Kill” → “NewCustomEvent” and “NewCustomEvent” → “GetNearestPlayer” → etc.

Right so, in the spirit of KISS (Keep It Simple Stupid), let’s stop there and I want you to do that…with a slight twist. Instead of setting the health to 0, simply call “DestroyActor”, making sure you connect up the return from the cast to the “Target” pin…you don’t want to destroy self. This will allow you to test what you’ve done so far. If executed properly you should, from either the server or the client, be able to hit the kill key and it will make the opposing pawn disappear on both the server and the client version of the games.

Let me know how that goes, and we can move on from there. If you run into issues, make sure to post your new nodes/connections. Oh, and one last thing, move that GetNearestCharacter function over to your character BP. It will simplify it slightly in that you can simply check to see if for loop array element (which is a character) equals “self”. That will explicitely exclude the calling character and get rid of the check for 0 distance.

Good luck!!

1 Like


Yep, the victim now disappears for everyone.
As for the links, thanks, I’ll watch them.

Sweet, that’s progress, nice work. That solves the core mechanic of your game. Now, let’s take it one step further. The next piece you had going on was that any hud widgets were removed from the screen of the victim. Again, a couple of ways this can be handled. Let’s go the easiest route first to see what that looks like.

Actors have an event called Destroyed which fires when an actor is marked for destruction. Let’s try to use that in order to remove the widgets. In the event graph of your character, add the “Event Destroyed”. Since you are destroying a replicated actor from the server, this destruction propagates to the clients. This means that “Event Destroyed” will fire on both the server version, and client version of the victim. That’s important to keep in mind as we only care to remove the widgets from the screen that is controlling that particular pawn. The way you check for this is through the node “IsLocallyControlled”. So after event destroyed, check whether or not the actor is locally controlled…if it is then go ahead and remove the widgets.

Give this a shot and let me know how that works. I don’t have the editor up in front of me so I can’t test that the “Event Destroyed” actually executes on both but if memory serves me, it should. Otherwise, there’s another plan of attack.


I’ve tried to use the “Is Locally Controlled” node, but it’s always False. (For “Get All Widgets Of Class”, I know, that’s not right to use but I have no idea how to get the right widget.)

Ha, of course, that would have been too easy. I guess it would make sense that by this point the pawn has no idea whether or not it’s locally controlled. Ok, let’s circle the wagons and approach this with a slight bit more sophistication…we’re going to use repnotify. Go ahead and disconnect Event Destroyed.

Ok, so select your health variable and, under the details panel set “Replication” to RepNotify. What this is going to do is that anytime the value changes on the server it will let all clients know the change has occurred and give them an opportunity to do something about it. When you set this to RepNotify it will automatically create a function for you called “OnRep_(variable name)”. In this function is where you can lay down logic regarding what to do when the value changes. An important piece to point out here is that the change has to occur on the server, otherwise it won’t propagate. So, instead of calling DestroyActor, let’s change that to setting the health to 0.

Right, so this OnRep function is going to fire on both the server version of the pawn and the client version. As you just learned, the server is the one that should be destroying the actor so the destruction propagates. With that in mind, we’re going to introduce another key node “HasAuthority”. This is a slightly involved topic so I’ll leave that to you to research. For our purposes at the moment just know that this checks to make sure the actor is the authoritative version (so the server version in our case). The very first check in the OnRep function should check “HasAuthority”. If yes, then this is the one that should be calling DestoryActor…if no, then this is the one that will remove the widgets. As for the widget removal you should just be able to use “RemoveAllWidgets” like you did in the initial post. Don’t forget to put the widget removal behind an IsLocallyControlled check.

Ok so thinking through this just a bit. There is the potential that the destroy actor could propagate before the client has the chance to get the change notification and remove the widgets. On top of that, you do want to play some sort of death animation. Let’s make an attempt to head off both of these issues. In your pawn graph go ahead and add a custom event “HandleDeath”. Move the OnRep functionality I just mentioned above, to this event. Then call “HandleDeath” directly in the OnRep function. The reason we’re moving it is because we’re going to add a Delay node and since these are latent they can’t be inside of functions. So, for the HasAuthority=true path, go ahead and add a Delay node with 1 second followed by DestroyActor. This delay is in place at the moment to mimic the death playing animation. For the non-authority path just leave it with the remove widgets, no delay.

Let me know how that goes.

1 Like

I left something out that isn’t important for this case but might be in the future for you. Go ahead and in the OnRep function, check that the health is at 0 before calling the death handler.

Another side…while messing with something else I found that there’s a FindNearestActor node for BP already…of course you want to remove yourself from the actor list before sending in but this might be cleaner.

Should it look something like this?


Not quite, but you do have all of the pieces.

  1. The very first thing you should be doing in OnRep is checking if the health is 0. If it is then call HandleDeath
  2. Move everything else in OnRep out to HandleDeath
  3. The first check in HandleDeath should be the authority check
  4. If authority then Delay 1 second and call destroyactor (leave out the animation for the moment)
  5. If not authority then check if it’s locally controlled and if it is then remove all widgets

Send updated pics when that is going.

image

So now the server kills the client perfectly but if the client is the killer, the server, just as you said about the “Remove All Widgets” node, doesn’t remove them.

Alright, almost there. I see what’s missing in the logic for the server side widget removal. Let’s walk through the chain of events when the client is the killer. Using the terminology we laid out previously…

The client is controlling CPawnB. When the kill key is pressed it calls “KillReplica” which moves us over to SPawnB which is on the server (because we have the function set to run on the server). SPawnB proceeds to find the nearest player (in our simple case that’s SPawnA) and sets its health to 0. Setting the health to 0, because the variable is set to repnotify, causes the change to propagate to connected clients. In turn OnRep fires on both the server and client version so SPawnA and CPawnA in our scenario. OnRep checks the health and sees that it’s at 0 and calls handledeath.

Now, here’s the key. HandleDeath is being called on both SPawnA and CPawnA. The first node it hits is SwitchHasAuthority. Since SPawnA is running on the server it is the authoritative version so it executes the “Authority” path. CPawnA is not the authoritative version so it runs the “Remote” branch. On the remote branch it checks to see if CPawnA is locally controlled. Remember that CPawnA is on the client. The client is controller CPawnB so CPawnA is NOT locally controlled and doesn’t ask the client to remove its widgets…which is the behavior that we want. The missing piece is in the “Authority” branch. I’ll let you attempt to work that out. Remember that the server is controlling SPawnA and thus has the widgets you want to get rid of.

Let me know how it goes.

1 Like