Let’s make a multiplayer game (part 8)


In the previous article we implemented smoothing of player movements on both the client and server side. In this article we will implement the functionality required to allow the player to fire shots.

As per the previous articles the first class to implement is the Shot class. This class will represent a bullet flying across the screen and inherits from the Sprite base class. The Shot class has two additional properties as shown below.

public long FiredById { get; private set; }

public bool FiredByPlayer { get; private set; }

The FiredById property is used to store the Id of the entity that fired the shot.

The FiredByPlayer property is used to indicate whether the player fired the shot. When we implement enemies this property will be false when an enemy ship fires at the player.

Shots are managed by the ShotManager class, meaning that the ShotManager is responsible for drawing all the shots and updating the movement of the shots. Shots are removed from the ShotManager when they move out of the screen bounds.

public Shot FireShot(Vector2 position, Vector2 velocity, long firedById, bool playerFired)
{
    Shot shot = this.FireShot(
        Interlocked.Increment(ref shotIdCounter), position, velocity * this.shotSpeed, firedById, playerFired);
    this.OnShotFired(shot);
    return shot;
}

The FireShot method is used to fire a shot and takes as input parameter the position from where the shot is fired and the velocity (direction and speed) of the shot. Additional meta data includes the Id of the entity that fired the shot and whether the firing entity is a player. The FireShot method raises an event that is handled by the Game class to notify the client or server (depending who is firing the shot) when a shot is fired.

Sending Messages

As per the previous articles we send messages by handling the events raised by the Manager type classes. In this case we handle the ShotFired event as follows.

this.soundManager = new SoundManager(randomNumberGenerator);
this.shotManager = new ShotManager(this.resolutionManager, this.soundManager);
this.shotManager.ShotFired += (sender, e) => this.networkManager.SendMessage(new ShotFiredMessage(e.Shot));

The ShotFired event is handled for both the client and the server.

The ShotFiredMessage is similar to the message classes defined previously.

public class ShotFiredMessage : IGameMessage
{
    #region Constructors and Destructors

    public ShotFiredMessage(NetIncomingMessage im)
    {
        this.Decode(im);
    }

    public ShotFiredMessage(Shot shot)
    {
        this.Id = shot.Id;
        this.Position = shot.SimulationState.Position;
        this.Velocity = shot.SimulationState.Velocity;
        this.FiredByPlayer = shot.FiredByPlayer;
        this.MessageTime = NetTime.Now;
        this.FiredById = shot.FiredById;
    }

    #endregion

    #region Public Properties

    public long FiredById { get; set; }

    public bool FiredByPlayer { get; set; }

    public long Id { get; set; }

    public double MessageTime { get; set; }

    public GameMessageTypes MessageType
    {
        get
        {
            return GameMessageTypes.ShotFired;
        }
    }

    public Vector2 Position { get; set; }

    public Vector2 Velocity { get; set; }

    #endregion

    #region Public Methods and Operators

    public void Decode(NetIncomingMessage im)
    {
        this.Id = im.ReadInt64();
        this.MessageTime = im.ReadDouble();
        this.Position = im.ReadVector2();
        this.Velocity = im.ReadVector2();
        this.FiredByPlayer = im.ReadBoolean();
        this.FiredById = im.ReadInt64();
    }

    public void Encode(NetOutgoingMessage om)
    {
        om.Write(this.Id);
        om.Write(this.MessageTime);
        om.Write(this.Position);
        om.Write(this.Velocity);
        om.Write(this.FiredByPlayer);
        om.Write(this.FiredById);
    }

    #endregion
}

The above ShotFiredMessage is very similar to the other GameMessages we have defined thus far. In this case we have made provision for the FiredById and FiredByPlayer properties.

Receiving Messages

The ShotFiredMessage is handled using the same pattern as the previous message types. The ProcessNetworkMessages method in the game code is updated as follows:

case NetIncomingMessageType.Data:
    var gameMessageType = (GameMessageTypes)im.ReadByte();
    switch (gameMessageType)
    {
        case GameMessageTypes.UpdateAsteroidState:
            this.HandleUpdateAsteroidStateMessage(im);
            break;
        case GameMessageTypes.UpdatePlayerState:
            this.HandleUpdatePlayerStateMessage(im);
            break;
        case GameMessageTypes.ShotFired:
            this.HandleShotFiredMessage(im);
            break;
    }

    break;

With the HandleShotFired method implemented as follows:

private void HandleShotFiredMessage(NetIncomingMessage im)
{
    var message = new ShotFiredMessage(im);

    var timeDelay = (float)(NetTime.Now – im.SenderConnection.GetLocalTime(message.MessageTime));

    Vector2 adjustedPosition = message.Position + (message.Velocity * timeDelay);

    this.shotManager.FireShot(
        message.Id, adjustedPosition, message.Velocity, message.FiredById, message.FiredByPlayer);
}

As before, we decode the game message into our ShotFiredMessage class. We calculate the time delay and update the position of the Shot taking into account any latency. We then call the FireShot message on the ShotManager class to add the shot to the collection of shots to be managed.

Note: We are using the non event raising override of the FireShot method. This pattern is repeated in all the manager implementations. On each manager we have a version of the method that will raise the “create” or “update” event and a version that will add the object instance but not raise any events. The latter version is used when processing game network messages to update the local state in our manager classes.

By adding a few lines of code to the PlayerManager class we now have the player ship firing bullets on both the client and server.

if (this.inputManager.IsKeyDown(Keys.Space))
{
    this.FireShot();
}

private void FireShot()
{
    if (this.shotTimer.Stopwatch(200))
    {
        this.shotManager.FireShot(
            this.localPlayer.SimulationState.Position + this.gunOffset, new Vector2(0, -1), this.localPlayer.Id, true);
    }
}

The Update method will check if the Spacebar is pressed and call the FireShot method. We have limited the shots to be fired to fire every 200ms. Shots are fired from the SimulatedState position taking into account a GunOffset to set the initial position of the shot.

The video below shows an example of shots being fired by the client player instance and how the shots are displayed on the server-side.

Firing shots (200ms delay)
Advertisements

One thought on “Let’s make a multiplayer game (part 8)

  1. Thanks , I’ve just been searching for info approximately this subject for a long time and yours is the greatest I’ve came upon so far.
    But, what in regards to the conclusion? Are you certain concerning the supply?

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s