Click here to Skip to main content
15,861,168 members
Articles / Artificial Intelligence

Writing a Multiplayer Game (in WPF)

Rate me:
Please Sign up or sign in to vote.
4.93/5 (131 votes)
16 Mar 2012CPOL25 min read 210.9K   17.1K   246   93
This article will explain some concepts of game development and how to apply and adapt them for multiplayer development.

Introduction

This article will explain some concepts of game development and how to apply and adapt them for multiplayer development.

Image 1

Background

When I started to program, I used AMOS BASIC. It was oriented for games and it was the reason I liked programming. Professionally, I never had the opportunity to create games but a friend who studies to create games asked me if I had something to show. I had nothing, but I decided to create a basic racing game with multi-player support to make it appealing.

Image 2

Some Principles

Even if it is possible to do event-oriented games, that's not usual. While normal programs wait for something to happen to then respond, games are constantly checking states (keys) and are doing their animation, even if the player isn't doing anything.

Well, that works very well for single-player games but multiplayer games have a lot of other problems. One client can have a fast connection while another one can have a slow one. The game itself can't wait for the slow clients and should keep going even when someone may be missing frames.

To solve the slow connection problem, there are some techniques. One of them is to allow such a player to see his game running and calculate that everything continues as expected, and at sometime correct the things that didn't go as expected. Even if this usually creates the illusion of a never stopping game, at other times, it creates the problem of, for example, shooting someone to then discover no damage was done, or even to look as if that someone runs in one direction and sometimes appears at a different place.

I decided for another approach. Simpler, not so good at the internet, but works very well locally. The server only processes everything with the "states" it knows about the clients. The clients are responsible for sending their states immediately and to draw what the server tells them to draw. They don't try to guess. If I want to move forward, even if I can, my client will only show such a movement when the server tells that the move was done.

On slow connections, if you press to move forward, it can take some time to show you what happened and, if you stop moving forward, the character will still move forward on the server until it receives such a state change. That's why it is not very good for users with a huge lag (but I think there is no right technique for huge lags).

The Technology

XNA has the Update/Draw principle, but it is local. Even if I can keep drawing the same frame over and over again, I am not sure if XNA does that or if it tries to discover what should happen when the server is not responding. But the real reason I am not using XNA is because it does not work in my home computer. So I am using WPF, but I am only using the OnRender method to draw all my components, so don't expect to find WPF templates or controls in this article.

To make communication fast, I decided that I should only send the changed values from the server to the client and from the client to the server. If there are no value changes, I don't need to send a TCP/IP packet. But how can I accomplish that?

The solution is similar to WPF's Dependency Properties, but home made. I decided that each property should have an ID (integer value) and that their values will be stored in a Dictionary. At each change, the value in that Dictionary is changed and also a copy is put at a change dictionary. At each frame end, the changes are stored at each participant modifications dictionary and, if a participant is not busy at the moment, the changes are sent. The first version of the IDs should be manually set and the properties should be implemented always with the same pattern, but I finished up with auto-generated (at runtime) properties.

In the final version, properties should be kept as abstract, like:

C#
public abstract int FrameIndex { get; set; }

And, at run-time, the framework I did will implement it. It will look like:

C#
public int FrameIndex
{
  get
  {
    return RemoteGameProperty.GetPropertyValueById<int>(1, this);
  }
  set
  {
    RemoteGameProperty.SetPropertyValueById(1, this, value);
  }
}

I didn't do that for performance reasons, I did that to make the code look clean and less error prone. The bolded and italic 1 is the PropertyId, which should never be repeated. The auto-generation also fills another structure of what properties exist, which are then sent to the client to guarantee that both use the same indexes for the properties and also adds some more validations, avoiding a client from changing server properties.

The Most Basic Game Structure

At first, as soon as a client is connected, the server sends it its ID (used to identify which objects are owned by it, even if they are only created at the server side), sends it the existing properties with their corresponding IDs, and sends the actual status of the game (all created components and their property values).

Then one thread keeps waiting for server changes and sends them when available, and another thread keeps waiting for client changes, applying (and validating them) and waiting for more.

The game itself is in another thread, and from time to time (in this game, 40 times per second) runs the "Update".

The Update

The client/server approach of the game has some advantages. As long as the server is able to process the next frame at acceptable speeds, the game will never have delays (at least not from the server point of view) and there is no need to make complicated code that multiplies the direction the character should go by the time passed. The time can be fixed. If the server suffers a real slowdown, it is acceptable to let the game run slower. If the client is slow, then it will receive less notifications, so it will be losing frames naturally.

So, instead of doing:

C#
x += 40*timeDiff.TotalSeconds;

If your game is 40 frames per second, you can simply do:

C#
x++;

But, again, the update concept is problematic. If you want to make a character go right, then down, then left, then up, you can't simply write:

C#
public override void Update()
{
  while(true)
  {
    for (int x=0; x<40; x++)
    {
      Position = new Point(x, 0);
    }

    for(int y=0; y<40; y++)
    {
      Position = new Point(40, y);
    }

    for (int x=40; x>0; x--)
    {
      Position = new Point(x, 40);
    }

    for(int y=40; y>0; y--)
    {
      Position = new Point(0, y);
    }
  }
}

You should do something like:

C#
int state;
int x;
int y;
public override void Update()
{
  switch(state)
  {
    case 0:
     x++;
     if (x == 40)
       state++;

     break;

    case 1:
     y++;
     if (y == 40)
       state++;

     break;

    case 2:
     x--;
     if (x == 0)
       state++;

     break;

    case 3:
     y--;
     if (y == 0)
       state = 0;

     break;
  }

  Position = new Point(x, y);
}

Even if both do the same, I really believe the first one is easier to understand and to program, specially if we do inner loops. But there is no language resource for that... well, at least not directly.

IEnumerator and IEnumerable - A New Way of Doing Animations

In C# 4.0, the compiler is able to create the State Machine for us. To be honest, I wanted such a resource to be more generic, as I don't want to generate values, I only want the states to be kept and updated. So, considering the limitations, I decided that all my animations are IEnumerator<bool>, but I don't consider the results of such an enumeration.

That way, the animation can be done very similar to what I expected in the first presentation, with only a tiny modification:

C#
public IEnumerator<bool> Animation()
{
  while(true)
  {
    for (int x=0; x<40; x++)
    {
      Position = new Point(x, 0);
      yield return true;
      // false can be used, as the result is ignored. The yield return
      // must be seen as "wait for the next frame".
    }

    for(int y=0; y<40; y++)
    {
      Position = new Point(40, y);
      yield return true;
    }

    for (int x=39; x>0; x--)
    {
      Position = new Point(x, 40);
      yield return true;
    }

    for(int y=39; y>0; y--)
    {
      Position = new Point(0, y);
      yield return true;
    }
  }
}

This is easier, isn't it?

I really wanted it to be public Animation Animation() instead of public IEnumerator<bool>, but it is not possible at this moment. If C# allows something better in the future, like returning Animation or IIterator instead of IEnumerator<bool>, I will change the framework.

Don't Like Compiler Generated State Machine but Want Sequential Animation?

Another thing I will love to see is the option mark a "go back point" and at some point call a method that saves the actual stack and goes back there. I recently published an article about that here in CodeProject.

But as always, we need to find alternatives. Mine was to use Threads and synchronization to solve the problem. Surely it uses a lot of resources (a thread and two autoreset events) but it works and, as the threads are kept in wait state, they don't consume CPU time.

There are other drawbacks, like locks. In fact, a lock owned by the thread that calls Update is not owned by the thread that runs the method, even if the first thread is waiting for it. So, any held locks may cause a dead lock. But I think I solved all dead-locks in my framework.

As threads, they can run in parallel, and I take advantage of that. So, which one is better? Compiler generated state machine or ThreadedAnimations/ThreadedEnumerators? Well, that depends. ThreadedAnimations are more portable, use the most natural code and can scale. But, when the animation is short the synchronization code takes more time than the animation itself. Either way, it is working fine and fast on my old 32-bit computer, so I don't think it is a real problem.

With that ThreadedAnimation, the animation code could look like this:

C#
public void Animation(Action finishFrame)
{
    while(true)
    {
        for (int x=0; x<40; x++)
        {
            Position = new Point(x, 0);
            finishFrame();
        }
        for(int y=0; y<40; y++)
        {
            Position = new Point(40, y);
            finishFrame();
        }
        for (int x=39; x>0; x--)
        {
            Position = new Point(x, 40);
            finishFrame();
        }
        for(int y=39; y>0; y--)
        {
            Position = new Point(0, y);
            finishFrame();
        }
    }
}

Again, I didn't do this for performance reasons. I did it because it is easier to port this way, and end-up with something that scales better, so if the animation logic is really complicated, it can have some advantages.

The Game Itself

Until this moment, I was talking about the technology and the framework I built. I will not explain the inner workings of the framework in too many details as my real purpose is to present how to create the game. This same framework can be used for 2D or 3D games, as its purpose is only automatic communication of property values and requests between both.

I decided that the game must be server-centered. Surely I could've made some animations or put the start screen as client-side only, but I really wanted a server centered game.

The structure of this game is:

  • RemoteGameSample.Common: This is the DLL that has the common objects. The game components with properties known to both client and server or objects that are serialized from one to the other must be here.
  • RemotaGameSample.Server: This is where the game logic really runs.
  • RemoteGameSample: This is the game that players start and use to play. It is only responsible for sending requests to the server, and sub-classing the common objects to show them in screen.

First Test of the Game

Image 3

The first test of the game was not really a game, it was a simple connect, be in the game map, and move. There were no collisions, everyone started at the same position and there was not a start race/end race. I was only checking for movement.

The first PlayerCar animation, which runs on server side, looked like this:

C#
public IEnumerator<bool> ControlledByPlayerAnimation()
{
  while(true)
  {
    int angle = Angle;

    var movement = Movement;

    double x = movement.X;
    double y = movement.Y;
    if (x < 0)
    {
      angle -= 4;

      if(angle < 0)
        angle += 360;

      Angle = angle;
    }
    else
    if (x > 0)
    {
      angle += 4;
      if (angle >= 360)
        angle -= 360;

      Angle = angle;
    }

    if (y < 0)
    {
      if (_speed >= 0)
      {
        int diff = (int)Math.Sqrt(1000-_speed);

        _speed += diff;
        if (_speed > 1000)
          _speed = 1000;
      }
      else
        _speed  = _speed * 90 / 100;
    }
    else
    if (y > 0)
    {
      if (_speed <= 0)
      {
        int diff = (int)Math.Sqrt(200-_speed);

        _speed -= diff;
        if (_speed < -200)
          _speed = -200;
      }
      else
        _speed  = _speed * 90 / 100;
    }
    else
    {
      if (_speed != 0)
      {
        int diff = (_speed/100);
        if (diff == 0)
        {
          if (_speed < 0)
            diff = -1;
          else
            diff = 1;
        }

        _speed -= diff;
      }
    }

    if (_speed != 0)
    {
      var oldPosition = Position;
      x = oldPosition.X + Math.Sin(angle * 2 * Math.PI / 360) * _speed / 100;
      y = oldPosition.Y - Math.Cos(angle * 2 * Math.PI / 360) * _speed / 100;

      Position = new Point(x, y);
    }

    yield return true;
  }
}

The server reads the Movement property (which is changed from client side) and decides to rotate (x different from 0), or decides to accelerate/decelerate, or even move backwards (y different from zero).

The angle property must be kept between 0 and 359. So, if it is less than 0, a value of 360 is added, while if it is 360 or greater, its value is reduced by 360. For the speed calculation, I wanted the car to start accelerating faster, that's why that code is complicated.

But either way, the server could change the angle of the car or its position, without any validation. The client is only responsible for setting the Movement property and is not allowed to set the angle or the position.

The reason for that is in the declaration, at CommonPlayerCar:

C#
using System.Windows;
using Pfz.RemoteGaming;

namespace RemoteGameSample.Common
{
  public abstract class CommonPlayerCar:
    RemoteGameComponent
  {
    [ClientGameProperty]
    public abstract Point Movement { get; set; }

    [VolatileGameProperty]
    public abstract Point Position { get; set; }

    [VolatileGameProperty]
    public abstract int Angle { get; set; }

    public abstract int CarIndex { get; set; }
  }
}

The properties that are changeable from the client-side must be marked with the [ClientGameProperty] attribute. All others are considered server properties. The server properties marked with [VolatileGameProperty] are sent to the client on everyframe, even if they didn't change, but use UDP when possible, making it faster.

Position and Angle are the ones that are part of the animation, while CarIndex determines which car images must be used to present the car.

What You See Is Not What the Server Sees

Image 4

Image 5

Image 6

After making the car move, I wanted to make the car collide (with the map). But to do that, I didn't want to use Artificial Intelligence to make the game recognize the maps and understand where it should collide, I simple created a "collision map".

To the server, the map it processes is only the collision map. It is the client that presents us a real map (which can be further divided into Background and Foreground... that's why cars can pass under trees, for example).

The code to add the collision checks and reduce speed checks is something like this:

C#
if (_speed != 0)
{
  var oldPosition = Position;
  x = oldPosition.X + Math.Sin(angle * 2 * Math.PI / 360) * _speed / 100;
  y = oldPosition.Y - Math.Cos(angle * 2 * Math.PI / 360) * _speed / 100;

  int ix = (int)x;
  int iy = (int)y;
  bool canMove = ix >= 0 && iy >= 0 && ix < _lockedBitmap.Width &&
            iy < _lockedBitmap.Height;

  if (canMove)
  {
    Argb color = _lockedBitmap[ix, iy];

    canMove = color != new Argb(255, 255, 255);
    if (canMove)
    {
      if (color.Red == color.Green && color.Green == color.Blue)
      {
        if (color.Red > 10)
          _speed = _speed * 90 / 100; // reduce speed by 10%
      }
    }
  }

  if (canMove)
    Position = new Point(x, y);
  else
  {
    if (_speed > 500)
      _ShowMessage("Big Hit!", -2, 0, new Argb(255, 0, 0));

    _speed = 0;
  }
}

This new code considers that out of the map bounds are always impassable (the declaration of the canMove variable). Then, if it is not already impassable, it checks for the color at the server side. It will be impassable if the color is white [Argb(255, 255, 255)] and it will reduce the speed if the color is gray but not black.

Considering it can move, the new Position is set. And, if it can't move but oldSpeed is greater than 500, a message like this is shown:

Image 7

This message is animated, and the code looks like this:

C#
private void _ShowMessage(string message, int xMovement, int yMovement, Argb color)
{
  Room._animations.Add(_ShowMessageAnimation(message, xMovement, yMovement, color));
}

private IEnumerator<bool> _ShowMessageAnimation(string message,
        int xMovement, int yMovement, Argb color)
{
  using(var messageComponent = Owner.CreateComponent<CommonMessage>())
  {
    messageComponent.Text = message;
    messageComponent.Color = color;

    for(int i=11; i<=40; i++)
    {
      messageComponent.Position = Position;
      messageComponent.Size = i;
      yield return true;
    }

    for(int i=1; i<=25; i++)
    {
      var position = Position;
      messageComponent.Position =
        new Point(position.X + (xMovement*i), position.Y + (yMovement*i));
      int alpha = 255-i*10;
      messageComponent.Color =
        new Argb((byte)alpha, color.Red, color.Green, color.Blue);
      yield return true;
    }
  }
}

In fact, the ShowMessage method adds an animation to the room. The message goes from size 11 to 40, then for 25 frames, its opacity is reduced while the message moves in some direction. That same ShowMessage is used to give other messages that will be presented later.

What Else?

Up to this moment, we are already making the car move, lose speed, and collide. It is not colliding with other cars, there are no Start Race/End Race screens, and I have not presented how the client works.

So, before continuing with the game itself (the server side), I will present a little about the client.

Connecting to the Server

The actual code uses the configuration file to decide the host to connect to. The code is in MainWindow.xaml.cs and looks like this:

C#
_game = new RemoteGameClient();

string host = ConfigurationManager.AppSettings["Host"];
_game.Changed += game_Changed;
_game.Start(host, 578);

As you can see, the port is fixed. That's easy to change but I didn't needed such configuration, only the host was changing in my tests. After calling Start, the game will start receiving server info, creating components, changing its properties, and calling the Changed event.

The Changed events tells which components have been added or removed, but there is no way to know what properties have been changed at the client side. It is really required to redraw everything when the Changed event is invoked, but that's how games usually work.

Now the Changed has a dictionary with all components that have been changed and, inside such dictionary, which properties have been changed. Also, to support the WPF binding structure, I created the ObservableRemoteGameComponent, which implements INotifyPropertyChanged. Check it at the end of the article.

I wanted to render everything in the MainWindow directly but the OnRender of Window is never called. So I created a GameControl component, which is responsible for drawing all IDrawable components.

As you may expect, the components (like the cars, the messages, and even the Background) must implement IDrawable. They must also inherit from the common components so the framework knows how to create them.

The Message class (which presents animated messages like "Big Hit!", "Finished Lap 1", and some others) looks like this:

C#
using System.Windows;
using System.Windows.Media;
using RemoteGameSample.Common;

namespace RemoteGameSample
{
  public abstract class Message:
    CommonMessage,
    IDrawable
  {
    public void Draw()
    {
      var dc = GameControl._drawingContext;

      var argbColor = Color;
      var color = System.Windows.Media.Color.FromArgb(argbColor.Alpha,
                  argbColor.Red, argbColor.Green, argbColor.Blue);
      var brush = new SolidColorBrush(color);

      var text = FormattedTextCreator.Create(Text, Size, brush);

      var point = Position;
      var newPoint = new Point(point.X+PlayerCar._leftModifier-text.Width/2,
                         point.Y+PlayerCar._topModifier-text.Height/2);
      dc.DrawText(text, newPoint);
    }
  }
}

The Message class itself does not know it is animated. But at each server change, the message is redrawn with the new property values (like new Color and new Position) and that's how it gets animated. The server is the one that's changing those values.

If you want to look at the other classes, feel free. Even the PlayerCar, which is the most complex class, is easy to understand. It only registers to the key events, changes the Movement value, and draws. Download the source code and check it out.

Now, I will go back to the server, because that is where everything happens.

Going Back to the Server

Rooms

The first version of the game didn't have the concept of rooms, but it is a very important one.

First, rooms allows players to join different maps. Also, the maps have a limit of four players at once, so creating a new room allows more players to play in the same map but in separate games. And, for this game, it was essential to make the first screen.

Image 8

Even with this simple visual (and this is the one that is drawn using WPF components Label and ListBox), this first screen runs in a separate room at the server side. The participant must always be bound to a room and will always receive all the public changes from that room and the private changes directed to him. So, to avoid receiving public changes of a game when one was not chosen, a new room is created as soon the participant connects.

To understand that, let's see the initialization of the server itself:

C#
using System;
using System.Net;
using Pfz.RemoteGaming;
using RemoteGameSample.Common;

namespace RemoteGameSample.Server
{
  class Program
  {
    static void Main(string[] args)
    {
      using(var listener =
        new RemoteGameListener(IPAddress.Any, 578, typeof(CommonMessage).Assembly))
      {
        listener.ClientConnected += new
          EventHandler<RemoteGameConnectedEventArgs>(listener_ClientConnected);
        listener.Start();
        Console.ReadLine();
      }
    }

    static void listener_ClientConnected(object sender,
                RemoteGameConnectedEventArgs e)
    {
      var participant = new ServerParticipant();
      e.Participant = participant;
      var room = new StartRoom();
      room.Start(participant);
    }
  }
}

This is the main unit of the server. It starts listening on port 578, telling that the common assembly is the one where the CommonMessage is found.

That's necessary so server-side only components are sent to the client using the base-type found at the common assembly. Then, the ClientConnected event is set and the listener starts.

The ClientConnected handler creates a new Participant (which can be seen as the Game Connection, to differentiate from the direct TCP/IP connection), creates a StartRoom, and tells that room to Start.

Such a room, while starting, sets the Participant's Room to itself, creates the "StartScreen" and sets MapTitles and MapNames to that start screen and then really Starts "animating", even if this room does not have any animations at this moment:

C#
public void Start(RemoteGameParticipant participant)
{
  _participant = participant;
  participant.Room = this;
  _startScreen = _participant.CreateComponent<CommonStartScreen>();
  _startScreen.MapTitles = Map.Titles;
  _startScreen.MapNames = Map.Names;

  base.Start(1000/40, _Animate());
}

So, this is the initial screen where the player selects a map. As soon as the map is selected, the Participant's Room is set to the appropriate RaceRoom, be it an already existing one or a newly created one.

The Race Start and Race Over

Initially, a race started directly as soon as the participant was connected. In fact, the PlayerCar animation could be changed to wait for the game start, but I decided that the PlayerCar animations should not know the status of the race itself.

In fact, the RaceRoom needs to wait for four players or to a request to start:

Image 9

Then I decided that there should be a countdown of three seconds:

Image 10

And finally, the game should start. Even if the PlayerCar class was added to the room immediately, its animation is only invoked when the race starts.

A Room has only one animation but such an animation can manage inner animations. And that's exactly what happens, as can be seen in the following code:

C#
private IEnumerator<bool> _Animate()
{
  // Wait for 4 players.
  using(RegisterAction<StartRequest>(() => _started = true))
    using(var startScreen = CreateComponent<CommonWaitingScreen>())
      while(_carIndex < 4 && !_started)
        yield return true;

  _started = true;
  // Tells that the game is started,
  // but to really start there is a 3 seconds countdown.

  using(var timeComponent = CreateComponent<CommonTimeToStart>())
  {
    for(int secondsToStart=3; secondsToStart>0; secondsToStart--)
    {
      timeComponent.Second = secondsToStart;

      for(int frameIndex=0; frameIndex<40; frameIndex++)
      {
        timeComponent.Size = (40 - frameIndex)*2;
        yield return true;
      }
    }
  }

  using(RegisterAction<PauseRequest>(_Pause))
  {
    // here is were the race really happens.
    var animationWithCollisions = _AnimateWithCollisions(true);
    while(animationWithCollisions.MoveNext())
    {
      yield return true;

      while(_pause != null)
        yield return true;
    }
  }

  var animationWithCollisions2 = _AnimateWithCollisions(false);
  using(var resultScreen = CreateComponent>CommonResultScreen<())
  {
    InitializeResultScreen(resultScreen);

    while(animationWithCollisions2.MoveNext())
      yield return true;
  }
}

That's the entire "Race Loop". It starts waiting, then it animates the countdown (the timeComponent), then it plays the game and, when the game ends, it plays the same game.

The difference is that the first time the game is pausable while the second time there is a Race Over screen and the game is not pausable.

Image 11

How the Race Ends

Going back a little, I didn't explain how the race ends. To make that work, I created check-points in the server map. If you look at it, it has some green areas.

The green areas with green values which are multiples of ten (10, 20, 30, but not 11, 12, or 13) are considered checkpoint areas. If the player is in the actual checkpoint, nothing happens, if he is in the next checkpoint, then it becomes the actual one, but if he is in another one, a message "You Missed a Checkpoint" appears. After finishing all checkpoints and going back to the first one, a message "Finished Lap 1" appears or, if it is the last lap, a Ranking is set to the player and if he is the last one, the race is over.

All those checks are in the PlayerCar _Move method. The first version of that method was already presented but was directly inside PlayerCar's ControlledByPlayerAnimation, but later I decided to create a separate method for it. I will not present that again as I think the article is already long enough, but you can download and see the source code if you want.

Car Collisions

Image 12

I decided that car collisions should only happen after animating a car. The reason for that is the order in which animations are executed.

To run many animations at once, I must create an AnimationsAnimation. It works as a single animation (which is required to start the game) but is capable of animating many animations per frame. The animations are run in the order they are added and I wanted to avoid the first player moving and checking for collisions and then the second player moving and checking collisions. They must all move, then they must all apply collisions.

So, what really happens is this: There is one animation that first animates the AnimationsAnimation and, before calling yield return, stores all the car info and applies the collision using the original values. So, even if car A receives the collision before (moving from the collision), car B will have its collision check made against the original values.

The code is like this:

C#
private IEnumerator<bool> _AnimateWithCollisions()
{
  while(_animations.Update())
  {
    var cars = GetComponents().OfType<PlayerCar>().ToArray();

    int count = cars.Length;
    var speeds = new int[count];
    var positions = new Point[count];
    for(int i=0; i<count; i++)
    {
      var car = cars[i];
      speeds[i] = car._speed;
      positions[i] = car.Position;
    }

    _CheckAndApplyCollisions(cars, speeds, positions);
    yield return true;
  }
}
private void _CheckAndApplyCollisions(PlayerCar[] cars,
             int[] speeds, Point[] positions)
{
  int count = cars.Length;
  for(int i=0; i<count; i++)
  {
    var car1 = cars[i];
    var speed1 = speeds[i];
    var position1 = positions[i];
    for(int j=i+1; j<count; j++)
    {
      var car2 = cars[j];
      var speed2 = speeds[j];
      var position2 = positions[j];

      PlayerCar._CheckAndApplyCollision(
        car1, speed1, position1, car2, speed2, position2);
      PlayerCar._CheckAndApplyCollision(
        car2, speed2, position2, car1, speed1, position1);
    }
  }
}

Properties Versus Requests

I don't know how many of you may have noticed, but the RaceRoom animation uses the RegisterAction method to process StartGame and Pause. Such "actions" will process client requests.

Image 13

But, what's the difference between a request and a property value? Why is the movement a property and not a request?

Well, properties are better suited to tell "states". A request like MoveForward can process this frame, but at the next frame, should it move again?

Considering that the client may be losing frames, it is not appropriate to wait for the next request. Also, for "security" reasons, it is better to check a state at each frame than to allow each request to do a move. After all, what happens if 20 requests are done at the same frame?

But then, why are requests useful?

Well... imagine that you press and release Pause very fast. When pressing Pause, IsPausePressed is set to true but when releasing the key, IsPausePressed is set to false. If the connection is only a little slow, the value comes back to false before the server is even aware that it was true.

So, when the actions are not states and must be done even if some state changes happen very fast, it is better suited to use Requests. When the value is a state-changer, then properties should be used to tell the actual state.

Also, the Actions to process Requests must be registered, so sometimes Requests can simply be ignored. Well, properties can be ignored also, but you never need to register for the properties as they are always available.

More

Image 14

The implementation of the game has more things. It transfers the maps from the server to the client if there was a change on the files, the server keeps the running maps in memory, and tries to cache the already loaded ones that are no more in use; when loading the foreground, the black color is converted to transparent and things like that, but I don't think such logic must be presented here, as they will change the focus of the article.

If you have any questions on how I did that, ask me or check out the code. I really think that many of these things can be easily understood by debugging the code.

More - Part 2

When revising my code to avoid dead-locks, to add animations based on threads and so on, I also tried SpinLocks available in .NET 4. They were terrible for my tests, but I decided to try my own optimistic locks.

I was really thinking they will have bad performance but I was surprised they out-performed even the conventional lock keyword, and I managed to make them work as reader/writer locks. Those optimistic reader/writer locks are based on the Interlocked keyword and always start considering they will obtain the lock. Only when they don't, if they are some type of reader, they decrease the count and keep trying. But how do they keep trying?

They call Thread.Yield() to wait, making another thread/process run and then check again. If there is no other thread to run, they will check immediately. That makes the CPU go up to 100% when they are waiting, but that's an illusory value, as when they run, they soon ask another thread to run. And, when compared to ReaderWriterLockSlim (which I avoided), they are nearly 10 times faster. Now I am really considering using reader/writer locks with more frequency.

An article? I explained that class and some other thread-synchronization primitives here.

TCP or UDP

Games in general use UDP but my first implementation used TCP for communication. So, which one is better?

Well, when sending the map files, it is surely better to use TCP, as the packets are received in order and are never lost. UDP, on the other hand, is better for always changing states. So, if I lose state 10, there is no problem as state 11 will have all the changes we need.

Considering such losses and the fact that UDP will never wait until some old packet is resent, it is usually "faster". But my implementation only sends the changed properties, so I can't lose packets. But, if I receive them in the wrong order, I can process the last packet and, when the old packet arrives, I can process it and reprocess the actual one.

Well, to make that possible, I created GuaranteedUdpConnection which is capable of resending lost packets but allows you to receive them unordered. So, the property-changes (but not the requests) are now sent using UDP and in my tests, it is much more playable over the internet now.

Future Improvements

Before I'd said I wanted to allow cars to shoot each other and to throw oil. Well, that's already done. The oil looks like a blackhole, but the feature is already there and looks like this:

Image 15

Surely the game needs a lot more. It needs more informative things, like the lap index, how much oil is still available to throw, how many big hits you had, and so on. Also, it needs better configuration, as some people will prefer the race as a race only, without shots or anything like that.

I plan to implement all of these, but I have no idea when I will release such changes. At the moment, I am more focused on finishing the framework, as I plan to write other games and I hope someone else wants to use my framework too.

Also, the actual version uses a more precise (and more CPU intensive) timer. As it keeps calling Thread.Yield while waiting, one CPU goes to 100% but the threads are not really blocked. If in the future I manage to have a precise timer that does not look like consuming all the CPU, I will replace the actual one. But I have no expectations about that, as .NET is at version 4 and there is no built-in solution for such limitations.

License Note

The source code is CPOL, but the maps I got from the internet and I don't know the type of license. If someone wants to draw and send me maps to include here (and even replace the actual ones), I will be glad to include them and mention your names in the article.

Trying the Game

To try the game, you must first run the server and at least one client. To try it over the network, the IP address of the server must be visible to all clients, and the RemoteGame.exe.config file in the client must point to the server IP (actually, it connects to the local [127.0.0.1] server).

Update - ObservableRemoteGameComponent

One of the things that I am not used to (use?) is WPF DataTemplates. I really believe many aspects of the game could be done using WPF DataTemplates instead of drawing with the DrawingContext but at the first moment the game components didn't implement the INotifyPropertyChanged interface and they still don't use DependencyProperties.

To show some more information on the screen, I really wanted to use DataTemplates. Not that I needed them but because I want to integrate the game technology to WPF so others more used to DataTemplates will consider it natural to create games using WPF templating. So, now the RemoteGameComponent has an OnChanged virtual method, which is only invoked on the client for the properties changed by the server, and the ObservableRemoteGameComponent implements the INotifyPropertyChanged and calls the PropertyChanged event for each of the changed properties.

With that, it is now possible to add the ObservableRemoteGameComponent directly to the window and use the WPF data templates to show them. So, to show some information, like this:

Image 16

I used this DataTemplate:

XML
<DataTemplate DataType="{x:Type App:PlayerCar}">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition Width="Auto"/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <Label Content="Angle: " FontSize="20" Foreground="White" FontWeight="Bold"/>
        <Label Content="{Binding Angle}" Grid.Column="1" 
		FontSize="20" Foreground="White"/>
        <Label Content="Lap Number: " Grid.Row="1" FontSize="20" 
		Foreground="White" FontWeight="Bold"/>
        <Label Content="{Binding LapNumber}" Grid.Row="1" Grid.Column="1" 
		FontSize="20" Foreground="White"/>
        <Label Content="Oil Thrown: " Grid.Row="2" FontSize="20" 
		Foreground="White" FontWeight="Bold"/>
        <Label Content="{Binding OilThrown}" Grid.Row="2" Grid.Column="1" 
		FontSize="20" Foreground="White"/>
    </Grid>
</DataTemplate>

Ok... that's not the template. I consider three things annoying in the Grid. First I must set the columns in full objects. Then I must set the rows as it can't simple assume new rows are automatic. Finally, I must set the Grid.Column and Grid.Row in each control that is not at column or row 0.

It is not really part of the article, but I have a WrapGrid that simply considers that the next control is always at the next column or, if there are no more columns, at the next line. So, my template ended up like this:

XML
<DataTemplate DataType="{x:Type App:PlayerCar}">
    <PfzWpf:WrapGrid ColumnWidths="Auto, 60" 
	HorizontalAlignment="Right" VerticalAlignment="Top">
        <Label Content="Angle: " FontSize="20" Foreground="White" FontWeight="Bold"/>
        <Label Content="{Binding Angle}" FontSize="20" Foreground="White"/>
        <Label Content="Lap Number: " FontSize="20" Foreground="White" FontWeight="Bold"/>
        <Label Content="{Binding LapNumber}" FontSize="20" Foreground="White"/>
        <Label Content="Oil Thrown: " FontSize="20" Foreground="White" FontWeight="Bold"/>
        <Label Content="{Binding OilThrown}" FontSize="20" Foreground="White" />
    </PfzWpf:WrapGrid>
</DataTemplate>

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) Microsoft
United States United States
I started to program computers when I was 11 years old, as a hobbyist, programming in AMOS Basic and Blitz Basic for Amiga.
At 12 I had my first try with assembler, but it was too difficult at the time. Then, in the same year, I learned C and, after learning C, I was finally able to learn assembler (for Motorola 680x0).
Not sure, but probably between 12 and 13, I started to learn C++. I always programmed "in an object oriented way", but using function pointers instead of virtual methods.

At 15 I started to learn Pascal at school and to use Delphi. At 16 I started my first internship (using Delphi). At 18 I started to work professionally using C++ and since then I've developed my programming skills as a professional developer in C++ and C#, generally creating libraries that help other developers do their work easier, faster and with less errors.

Want more info or simply want to contact me?
Take a look at: http://paulozemek.azurewebsites.net/
Or e-mail me at: paulozemek@outlook.com

Codeproject MVP 2012, 2015 & 2016
Microsoft MVP 2013-2014 (in October 2014 I started working at Microsoft, so I can't be a Microsoft MVP anymore).

Comments and Discussions

 
GeneralMy vote of 5 Pin
Sergio Andrés Gutiérrez Rojas17-Mar-12 6:32
Sergio Andrés Gutiérrez Rojas17-Mar-12 6:32 
GeneralRe: My vote of 5 Pin
Paulo Zemek17-Mar-12 11:46
mvaPaulo Zemek17-Mar-12 11:46 
GeneralMy vote of 5 Pin
Xmen Real 15-Mar-12 0:42
professional Xmen Real 15-Mar-12 0:42 
GeneralRe: My vote of 5 Pin
Paulo Zemek16-Mar-12 4:04
mvaPaulo Zemek16-Mar-12 4:04 
QuestionExcellent and exceptional. Pin
kartalyildirim2-Mar-12 6:31
kartalyildirim2-Mar-12 6:31 
AnswerRe: Excellent and exceptional. Pin
Paulo Zemek2-Mar-12 6:42
mvaPaulo Zemek2-Mar-12 6:42 
GeneralMy vote of 5 Pin
Narf the Mouse22-Feb-12 15:19
Narf the Mouse22-Feb-12 15:19 
GeneralRe: My vote of 5 Pin
Paulo Zemek23-Feb-12 5:17
mvaPaulo Zemek23-Feb-12 5:17 
Thanks!
QuestionMy vote of 5 Pin
Gandalf_TheWhite24-Nov-11 2:28
professionalGandalf_TheWhite24-Nov-11 2:28 
AnswerRe: My vote of 5 Pin
Paulo Zemek24-Nov-11 2:29
mvaPaulo Zemek24-Nov-11 2:29 
GeneralMy vote of 5 Pin
ShdNx1-Oct-11 10:47
ShdNx1-Oct-11 10:47 
GeneralRe: My vote of 5 Pin
Paulo Zemek4-Oct-11 6:15
mvaPaulo Zemek4-Oct-11 6:15 
GeneralMy vote of 5 Pin
nachodd30-Sep-11 1:55
nachodd30-Sep-11 1:55 
GeneralRe: My vote of 5 Pin
Paulo Zemek30-Sep-11 2:55
mvaPaulo Zemek30-Sep-11 2:55 
GeneralMy vote of 5 Pin
Unque28-Sep-11 1:40
Unque28-Sep-11 1:40 
GeneralRe: My vote of 5 Pin
Paulo Zemek29-Sep-11 2:54
mvaPaulo Zemek29-Sep-11 2:54 
GeneralMy vote of 5 Pin
tom-englert27-Sep-11 21:17
tom-englert27-Sep-11 21:17 
GeneralRe: My vote of 5 Pin
Paulo Zemek29-Sep-11 2:58
mvaPaulo Zemek29-Sep-11 2:58 
GeneralRe: My vote of 5 Pin
tom-englert29-Sep-11 3:14
tom-englert29-Sep-11 3:14 
GeneralRe: My vote of 5 Pin
Paulo Zemek29-Sep-11 3:15
mvaPaulo Zemek29-Sep-11 3:15 
GeneralMy vote of 5 Pin
magicpapacy26-Sep-11 21:04
magicpapacy26-Sep-11 21:04 
GeneralRe: My vote of 5 Pin
Paulo Zemek27-Sep-11 5:49
mvaPaulo Zemek27-Sep-11 5:49 
GeneralMy vote of 5 Pin
mayur csharp G26-Sep-11 18:40
mayur csharp G26-Sep-11 18:40 
GeneralRe: My vote of 5 Pin
Paulo Zemek27-Sep-11 5:49
mvaPaulo Zemek27-Sep-11 5:49 
GeneralMy vote of 5 Pin
Ștefan-Mihai MOGA22-Sep-11 3:08
professionalȘtefan-Mihai MOGA22-Sep-11 3:08 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.