none
How the weapon talks

How the weapon talks


To be honest, I've never written a complicated multi-player game before. I figured that writing weapon mechanics was as easy as invoking one or two methods. It is that easy in singleplayer. However, to get a fully working and correct function weapon in multiplayer, it turns out that we need about seven or eigth different methods for a single weapon functionality! This small article will explain on why this logic is so relatively complicated, and how the different methods co-operate.

Let's pretend that we want to implement the kicking functionality of the weapon. If you aim at the ball and 'kick', you essentially make the ball move away from you. Now let's not get into the details on how and when the ball actually gets kicked, but let's take a bird's eye view from above. The following list denotes the methods that are needed for the weapon class to support kicking:

  1. Player::Kick

  2. Kick

  3. Server_Kick_Implementation

  4. Server_Kick_Validate

  5. DoKick

  6. OnKick_RPC

  7. OnKick


Player::Kick


In many cases, the first things to be done are fairly trivial. This is true as well for this method. Whenever the player presses his 'kick' button, this method gets called and initiates the kicking process. There's nothing too difficult about that!

void ASupraPawn::Kick()
{
    GetWeapon()->Kick();
}

MayKick


Before we even start considering kicking, we need to assert that certain conditions hold. In other words, the player wants to kick, but can he really kick; is he allowed? For example, you may only kick when you don't have the ball and are not yet charging a shot. This little method will be used in the proceeding methods.

bool AWeapon::MayKick()
{
    return !HasBall() && !IsCharging();
}

Kick


This is were the fun starts. In the usual singleplayer game, you'd check if the player `MayKick`, and if so, you invoke the `Kick` method. Done! Except, for multiplayer games it works a tad bit different. The first thing that has to be done, is to check whether the caller of this method is actually the server or not. If we are a client, and thus not the server, we first check if we `MayKick`. We do this in order to save some strain on the server. The server will be the decisive authority to check whether kick is allowed, but if we already know client-side that we cannot kick, there is no need to bugger the server about our unnecessary request. If we may kick, we tell the server that we want to kick; we invoke `Server_Kick`. If we are the server, we may immediately start kicking by invoking `DoKick` if `MayKick` has given us the thumbs up. We are the authority after all!

bool AWeapon::Kick()
{
    if (MayKick())
    {
        if (Role < ROLE_Authority)
            Server_Kick();
        else
            DoKick();

        return true;
    }

    return false;
}

Server_Kick_Implementation


In case we were not the client, we invoked this method instead of directly invoking `DoKick`. This method gets executed on the server. So as a server, some client thinks he was allowed to kick and wants to, so the only thing we have to do is verify as the server, that the player is indeed allowed to kick. If he was, we simply invoke `DoKick`.

void AWeapon::Server_Kick_Implementation()
{
    if (MayKick())
        DoKick();
}

Server_Kick_Validate


The request to kick that has been send to the server however, has gone through the series of tubes called the internet. This implies that the message could have magically gone lost for whatever godforsaken reason. Therefore, this method has been made what is called a 'Reliable' method. This basically asserts that, while it may take ages, the request will *always* reach the server. The `Validate` function then serves as an additional check to 'validate' any arguments, or payload data the request could have carried with it. In this case we did not pass any additional information, so there's nothing to compare and 'validate', hence we simply return `true`.

bool AWeapon::Server_Kick_Validate()
{
    return true;
}

DoKick


Holy balls, can we finally go to actually kicking the ball!? Yes! And when have performed our kick, we will invoke `OnKick_RPC` as an event to notify the clients we have actually kicked the ball! Wait, what? RPC?

void AWeapon::DoKick()
{
    // Here we have performed the ball kicking. We're done now!
    OnKick_RPC();
}

OnKick_RPC


Yeah, I'm afraid we have to take a little detour here. Remember that it's actually the *server* performing the kick, and not the client? In order for that we have to invoke what is called a networked 'Multicast' function. If the server calls this function, it will be also called on all clients. If we put our event method logic in here, it will be called for everybody when somebody has kicked the ball. Excellent!

void AWeapon::OnKick_RPC_Implementation()
{
    OnKick();
}

OnKick


Wait, why are we taking this detour!? Why are we not just putting the logic directly in the RPC function? Well, that's where UE4 comes in. We don't want people to try and override OnKick_RPC; it is not virtual. We want a clear API, that when somebody decides to inherit from our weapon class and add additional logic, he can simply override `OnKick`. That's why we take this little detour. Finally, UE4 loves to be inconsistent. Namely, `UAudioComponent->Play()` gets replicated, but `UParticleSystem->ActivateSystem()` does not. Therefore, we only want the server to play the sound file, and thus have to place a check. This (finally) concludes the implementation of kicking.

void AWeapon::OnKick()
{
    this->KickEffect->ActivateSystem();

    if (Role == ROLE_Authority)
        this->Audio["Kick"]->Play();
}

Conclusion


This concludes a short description on the server-client mechanism behind certain weapon functionality. We took the kick attack as an example, but you can easily see that his extends for other functionality as well, like charging, passing and deflecting. As you can see, there's quite a lot of boilerplate code necessary in order to have a good and correctly working design.