Let’s make a multiplayer game (part 5)

At this point I am going to spend to time to recap on the concepts I covered in the previous articles.

The game is designed to be a single entry point that can either operate in server mode or client mode. This is a design choice and not a requirement.

The game has the following common responsibilities in both server and client mode.

  • Update game objects. Each of our game objects have their own logic which needs to be processed. In this game it is typically the physics logic to allow the objects to move around the screen.
  • Apply game logic. An example of game logic is the processing of collisions between game objects. This behaviour needs to be handled consistently on both the client and server to ensure the responsiveness of the game.
  • Draw game objects. The game obviously needs to render the scene.

When the game is running in client mode it is also responsible for the following.

  • Notify the Server of player actions. When the player performs an action we need to execute it on the client to provide immediate feedback. We also need to inform the server of the action so that it does not override the player state during the next update cycle.
  • Respond to messages from the server. The client needs to respond to server messages to ensure that the client version of the simulation is synchronised with the server version.

When the game is running in server mode it responsible for the following (in addition to the common elements).

  • Respond to messages from the client. The server needs to process messages received from the client. Here the server can override any invalid requests and the action will be reverted on the client.
  • Notify clients of game object changes. The server needs to send out periodic state updates to all the clients. I call this the heartbeat update as it should happen at a regular time interval.
  • Create game objects. The server is responsible for the game state, by not allowing clients to create objects we can be fairly confident of a synchronised simulation.
  • Delete game objects. This is the inverse of creating objects again we do this on the server to ensure consistency.

We encapsulate our game network messages in a message class that implements the following IGameMessage interface.

public interface IGameMessage
{
    GameMessageTypes MessageType { get; }

    void Encode(NetOutgoingMessage om);

    void Decode(NetIncomingMessage im);
}

The MessageType property is used to identify our game messages and should be the first byte sent and received for every game message.

The Encode and Decode methods should mirror each other exactly. This will eliminate a lot of errors that could occur when sending and receiving messages.

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

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

I recommend always sending the NetTime.Now value along with the game message as it is useful in determining the exact latency when the message is processed as well as filtering out of sequence messages.

I think that covers the main ideas I wanted to review. In the next article I will add the PlayerManager class which will allow us to control the player ship.

Advertisements

Let’s make a multiplayer game (part 4)

In this article I will demonstrate how to get some asteroids flying around the screen synchronized on both the client and server.

The Asteroid Class

Each of our asteroids are represented by an Asteroid class which inherits from a general Sprite class. I’m not going to cover the Sprite class in detail but would like to highlight the areas that are relevant for networking.

public double LastUpdateTime { get; set; }

public EntityState PrevDisplayState { get; set; }

public EntityState SimulationState { get; set; }

public EntityState DisplayState { get; set; }

public bool EnableSmoothing { get; set; }

The LastUpdateTime property is used to check when last a particular sprite was updated. I use this to make sure I don’t process old messages out of sequence. You will notice that in this example game I make use of the ReliableUnordered NetDeliveryMethod. This means that the Lidgren library guarantees message delivery but not the sequence in which the messages will be delivered.

The PrevDisplayState property is used to store the last rendered EntityState values. This is used during the smoothing of the drawing which I will cover in more detail in a later article.

The SimulationState property is used to store the current simulation state of the entity. This means the real values used by the simulation i.e. the actual position, velocity and rotation.

The DisplayState property is used to store the current rendered EntityState values. If smoothing is disabled then the DisplayState will equal the SimulationState i.e. we have not made any adjustments to how we render the EntityState between frames.

The EnableSmoothing property is used to determine if we should smooth the rendering of the sprite between frames. The smoothing is done from the PrevDisplayState to the SimulationState.

The EntityState class stores the Position (Vector2), Velocity (Vector2) and Rotation (float) of our sprite.

You can see how the Draw and Update methods makes use of the DisplayState and SimulationState properties respectively.

The Asteroid Manager Class

This game architecture makes use of Manager classes to control objects of the same type. You will see an AsteroidManager, PlayerManager, EnemyManager, ShotManager, CollisionManager, etc.

Each manager class is responsible for loading its own content, creating it’s objects, drawing the objects and updating the objects. Now in the previous articles I mentioned that the server is responsible for the simulation state. In the case of the AsteroidManager the server is the only one that can add or remove asteroids from the simulation. It also has the final say over the real SimulationState of each of the asteroids.

Because we want our simulation to be responsive on the client-side (even with some latency) we need to do some client-side processing of the simulation. What this means is that the client-side instance of the AsteroidManager should be allowed to draw, bounds asteroid against each other and update the asteroid positions.

To separate these two behaviours I have introduced a bool called IsHost to the AsteroidManager class. When IsHost is true our AsteroidManager will perform server functions as well as the functionality common to both the client and the server.

Most of the work is done in the Update method. The purpose of this method is to update each of the individual asteroids and then to perform some game logic such as handle asteroids that have move offscreen, handle collisions between asteroids, ensure the correct number of asteroids exist and finally to send out notifications of the current asteroid EntityStates.

public void Update(GameTime gameTime)
{
    foreach (Asteroid asteroid in this.Asteroids)
    {
        asteroid.Update(gameTime);
        if (this.isHost)
        {
            if (!this.IsOnScreen(asteroid))
            {
                asteroid.SimulationState = this.SelectRandomEntityState();
                this.OnAsteroidStateChanged(asteroid);
            }
        }
    }

    var processedList = new List<Asteroid>();
    foreach (Asteroid a1 in this.Asteroids)
    {
        processedList.Add(a1);
        foreach (Asteroid a2 in this.asteroids.Values.Except(processedList))
        {
            if (a1.IsCircleColliding(a2.Center, a2.CollisionRadius))
            {
                this.BounceAsteroids(a1, a2);
            }
        }
    }

    if (this.isHost)
    {
        for (int i = this.asteroids.Count; i < this.asteroidCount; i++)
        {
            this.AddAsteroid();
        }

        if (this.hearbeatTimer.Stopwatch(200))
        {
            foreach (var asteroid in this.Asteroids)
            {
                this.OnAsteroidStateChanged(asteroid);
            }
        }
    }
}

In the first part of the Update method we enumerate all the asteroids and call their Update method. This will allow the asteroids to move around on the screen due to their positions being updated with the velocity vector.

Here we see the first server specific code where we test if an asteroid has been moved outside the bounds of the screen. When an asteroid is outside the bounds we choose a new random position and velocity and then raise the AsteroidStateChanged event.

Note: I decided that I would make use of events to manage notifications of state changes in my simulation. The server instance of the game will bind to this event and send the appropriate network message to each of the clients. The client instance of the game does not bind to this event so even if it was raised it would never do anything about it. I found this technique to work quite nicely for this game.

The second part of the update method checks for collisions between asteroids and invokes a method to let the asteroids bounce by changing their velocity vectors. Once the colliding asteroids have been updated I once again raise the AsteroidStateChanged event so that the new state update will be sent to the clients.

The last part of the update method is purely server focused. The first thing I do is ensure that there are enough asteroids in the game. This code will never run on the client as it is the server’s responsibility to create asteroids.

The next part makes use of a Timer class to send updated EntityState values for each of the asteroids every 200ms. In an earlier design I was handling the heartbeat update in the game code itself, but it made sense for each manager to handle its own update. This heartbeat is basically our overriding update message to ensure that the client does not deviate too far from the server simulation.

Sending Messages

Our AsteroidManager class is now raising events when the asteroid EntityState changes. The next step is for our Game code to handle the events and send the appropriate network messages.

The following code shows the Initialize method in the game class:

protected override void Initialize()
{
    // TODO: Add your initialization logic here
    this.networkManager.Connect();

    var randomNumberGenerator = new MersenneTwister();

    this.asteroidManager = new AsteroidManager(this.resolutionManager, randomNumberGenerator, this.IsHost);
    if (this.IsHost)
    {
        this.asteroidManager.AsteroidStateChanged += (sender, e)
            => this.networkManager.SendMessage(new UpdateAsteroidStateMessage(e.Asteroid));
    }

    base.Initialize();
}

You will notice that I use the Initialize method to instantiate my Manager objects. I used to have the code in the constructor but decided this felt more like initialization.

Note: You might be wondering why the AsteroidManager isn’t an XNA game object. I will cover this in more detail in another blog post as it is more XNA related than mutliplayer game related.

The interresting part in the above code is the event handler I’m attaching to the AsteroidStateChanged event. Firstly it is only done when the game is running as a Host. Secondly I am calling a method on the INetworkManager interface called SendMessage. The message I’m sending is called the UpdateAsteroidStateMessage.

I like to encapsulate my network communications in message objects. I find this a really elegant way of handling messages with the added benefit of reducing message encoding errors.

Each of my messages inherit from the IGameMessage interface.

public interface IGameMessage
{
    GameMessageTypes MessageType { get; }

    void Encode(NetOutgoingMessage om);

    void Decode(NetIncomingMessage im);
}

The MessageType property is encoded as the first value in any game message we send over the network. When we receive a game message we can then safely decode the first byte, cast it to the GameMessageType and then know what the rest of the message content should contain. You will see this in action later.

The Encode method is used to encode or write the contents of the message to the NetOutgoingMessage.

The Decode method is used to decode or read the contents from the NetIncomingMessage into the IGameMessage instance.

The implementation of the UpdateAsteroidStateMessage is as follows:

public class UpdateAsteroidStateMessage : IGameMessage
{
    #region Constructors and Destructors

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

    public UpdateAsteroidStateMessage(Asteroid asteroid)
    {
        this.Id = asteroid.Id;
        this.Position = asteroid.SimulationState.Position;
        this.Velocity = asteroid.SimulationState.Velocity;
        this.Rotation = asteroid.SimulationState.Rotation;
        this.MessageTime = NetTime.Now;
    }

    #endregion

    #region Properties

    public long Id { get; set; }

    public double MessageTime { get; set; }

    public Vector2 Position { get; set; }

    public float Rotation { get; set; }

    public Vector2 Velocity { get; set; }

    #endregion

    #region Public Methods

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

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

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

    #endregion
}

The key thing to note is how the Decode and Encode methods are mirrored. This is a very useful technique in managing your game messages and I can highly recommend this pattern.

Simply put, the Encode method writes each of the game message class variables to the NetOutgoingMessage. The sequence is very important.

Note: I do not write the MessageType property. The reason for this is that your typically read this property before you call the Decode method and I want to keep them as similar as possible.

The Decode method then reads the variable types from the NetIncomingMessage class in the same sequence as it was encoded in.

The properties in the class store the values we want to send over the wire. The MessageTime property is interesting as it is not required to update the asteroid EntityState values. The MessageTime property is used to store the NetTime.Now value when the message was sent. This value is used by the client to determine the time delay from when then message was sent to when the message was received. This information is used to perform some client-side prediction on the asteroid position.

The next step is to look at how the server will send messages to the connected clients.

The server version of the SendMessage method on the INetworkManager interface is as follows:

public void SendMessage(IGameMessage gameMessage)
{
    NetOutgoingMessage om = netServer.CreateMessage();
    om.Write((byte)gameMessage.MessageType);
    gameMessage.Encode(om);

    netServer.SendToAll(om, NetDeliveryMethod.ReliableUnordered);
}

The first thing we do is to create a new NetOutGoingMessage by calling the CreateMessage method on the NetServer.

We then write the game message header which is a single byte defined by our MessageType property.

Thereafter we encode the rest of the message properties onto the NetOutgoingMessage.

And finally we call the SendToAll method on the NetServer which will send the message to all the connected clients.

As I mentioned earlier I am using the NetDeliveryMethod.ReliableUnordered to send my game messages.

Receiving Messages

The receiving of messages is handled with the rest of our message processing code. You will notice that I have introduced a new case in the switch statement for messages of type NetIncomingMessageType.Data as follows:

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

Our own game messages will always be of type Data. We can then ready the first byte, cast it to our GameMessageTypes enum and handle the appropriate message type.

I like to encapsulate my message handling code in a method with the corresponding name. In this case it is the HandleUpdateAsteroidStateMessage method.

private void HandleUpdateAsteroidStateMessage(NetIncomingMessage im)
{
    var message = new UpdateAsteroidStateMessage(im);

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

    Asteroid asteroid = this.asteroidManager.GetAsteroid(message.Id) ??
                        this.asteroidManager.AddAsteroid(
                            message.Id, message.Position, message.Velocity, message.Rotation);

    asteroid.EnableSmoothing = true;

    if (asteroid.LastUpdateTime < message.MessageTime)
    {
        asteroid.PrevDisplayState = (EntityState)asteroid.DisplayState.Clone();

        asteroid.SimulationState.Position = message.Position += (message.Velocity * timeDelay);

        asteroid.SimulationState.Velocity = message.Velocity;
        asteroid.SimulationState.Rotation = message.Rotation;

        asteroid.LastUpdateTime = message.MessageTime;
    }
}

The first thing we do is to construct our UpdateAsteroidStateMessage from the NetIncomingMessage which will call our Decode method and populate all the game message properties.

The next step is to calculate the time delay since the message was sent and the current NetTime. The MessageTime we sent along with our game message comes in handy to do this.

Using the id from the game message I test to see if the asteroid already exists (if it does I retrieve it) and whether I need to create a new asteroid. Once the asteroid has been created or retrieved we can proceed to update it’s EntityState values.

The next check is to ensure that old messages i.e. messages received out of order are discarded.

Ignore the EnableSmoothing and PrevDisplayState code for now as I will cover this in a later article.

The important part here is the updating of the asteroid Position. You will notice that I am updating the asteroid position with the message position PLUS the velocity multiplied by the timeDelay. This is how I implement client-side prediction to make sure that our client is not running behind the server simulation.

You will notice that when you run the code that even with a simulated delay of 200ms the two simulations are synchronized.

Example output from Client and Server

Let’s make a multiplayer game (part 3)

Unified Client/Server Game Code

For this article series I want to have a single version of the game logic that can be applied to both the client and the server. For now I am going to make use of inversion of control via the INetworkManager interface to achieve the different networking behavior for the client and server. (This is by no means the only way, I just chose this approach because I prefer it in this case.)

The INetworkManager interface has the following members which basically wrap calls to the NetServer and NetClient classes that I will explain later.

public interface INetworkManager : IDisposable
{
    void Connect();

    void Disconnect();

    NetIncomingMessage ReadMessage();

    void Recycle(NetIncomingMessage im);

    NetOutgoingMessage CreateMessage();
}

Using the above interface my ExampleGame constructor is as follows:

public ExampleGame(INetworkManager networkManager)
{
    graphics = new GraphicsDeviceManager(this);
    Content.RootDirectory = "Content";

    this.networkManager = networkManager;
}

The client and server console applications can therefore instantiate the ExampleGame class as follows:

static void Main(string[] args)
{
    using (var game = new ExampleGame(new ServerNetworkManager()))
    {
        game.Run();
    }
}

static void Main(string[] args)
{
    using (var game = new ExampleGame(new ClientNetworkManager()))
    {
        game.Run();
    }
}

Setting up the Server

The code required to set up a server is contained in the ServerNetworkManager class. The interesting part is the Connect method which is as follows.

public void Connect()
{
    var config = new NetPeerConfiguration("Asteroid")
    {
        Port = Convert.ToInt32("14242"),
        //SimulatedMinimumLatency = 0.2f,
        //SimulatedLoss = 0.1f
    };
    config.EnableMessageType(NetIncomingMessageType.WarningMessage);
    config.EnableMessageType(NetIncomingMessageType.VerboseDebugMessage);
    config.EnableMessageType(NetIncomingMessageType.ErrorMessage);
    config.EnableMessageType(NetIncomingMessageType.Error);
    config.EnableMessageType(NetIncomingMessageType.DebugMessage);
    config.EnableMessageType(NetIncomingMessageType.ConnectionApproval);

    netServer = new NetServer(config);
    netServer.Start();
}

The first thing we need to do is set up a NetPeerConfiguration. For the server the only thing required is the port that our server will be listening on. Depending on your usage scenario you can let the user customize the port. In this case we will use a hardcoded value.

The next set of config.EnableMessageType method calls are used to tell the NetServer what type of messages to expect. I have exposed all the debug, warning and error messages as well as the ConnectionApproval message type.

The ConnectionApproval message type allows you to control if a client is allowed to connect. This is useful if you want to limit the number of connections or do some kind of authentication.

Once we’ve set up the config information we can instantiate our NetServer and call the start method. The server is now up and running and listening on the configured port.

You will notice some commented lines where I instantiate the NetPeerConfiguration class. These lines allow us to simulate latency and losses over the network. This is very handy for testing purposes to ensure that your game will be able to handle the real-world issues of internet gameplay.

Setting up the Client

The code required to set up a client is contained in the ClientNetworkManager class. Once again the interesting stuff happens in the Connect method.

public void Connect()
{
    var config = new NetPeerConfiguration("Asteroid")
    {
        //SimulatedMinimumLatency = 0.2f,
        //SimulatedLoss = 0.1f
    };

    config.EnableMessageType(NetIncomingMessageType.WarningMessage);
    config.EnableMessageType(NetIncomingMessageType.VerboseDebugMessage);
    config.EnableMessageType(NetIncomingMessageType.ErrorMessage);
    config.EnableMessageType(NetIncomingMessageType.Error);
    config.EnableMessageType(NetIncomingMessageType.DebugMessage);
    config.EnableMessageType(NetIncomingMessageType.ConnectionApproval);

    this.netClient = new NetClient(config);
    this.netClient.Start();

    this.netClient.Connect(new IPEndPoint(NetUtility.Resolve("127.0.0.1"), Convert.ToInt32("14242")));
}

You will notice that I don’t specify a client port in the configuration. You can do so if you want, but it’s not required.

The same message types as the server are enabled on the client and instead of a NetServer we instantiate a NetClient and call the Start method.

To make the connection to the server the client needs to connect to it. I have hardcoded the IP address and port in this case. In a real game you would allow the user to specify this information or provide some kind of server browser.

Calling the Connect method starts the handshaking process between the client and the server. Since we’ve enabled the ConnectionApproval message type we need to handle this message to complete the connection.

Processing Network Messages

Now that we have our client and server connection code we need to update the game to make use of it. I make the INetworkManager.Connect() call in the Initialize() method as follows:

protected override void Initialize()
{
    // TODO: Add your initialization logic here
    this.networkManager.Connect();

    base.Initialize();
}

The next step is to make sure our game handles the network messages. I typically process the network messages in the Update method as follows:

protected override void Update(GameTime gameTime)
{
    // Allows the game to exit
    if (Keyboard.GetState(PlayerIndex.One).IsKeyDown(Keys.Escape))
        this.Exit();

    // TODO: Add your update logic here
    this.ProcessNetworkMessages();

    base.Update(gameTime);
}

private void ProcessNetworkMessages()
{
    NetIncomingMessage im;

    while ((im = this.networkManager.ReadMessage()) != null)
    {
        switch (im.MessageType)
        {
            case NetIncomingMessageType.VerboseDebugMessage:
            case NetIncomingMessageType.DebugMessage:
            case NetIncomingMessageType.WarningMessage:
            case NetIncomingMessageType.ErrorMessage:
                Console.WriteLine(im.ReadString());
                break;
            case NetIncomingMessageType.StatusChanged:
                switch ((NetConnectionStatus)im.ReadByte())
                {
                    case NetConnectionStatus.Connected:
                        Console.WriteLine("{0} Connected", im.SenderEndpoint);
                        break;
                    case NetConnectionStatus.Disconnected:
                        Console.WriteLine("{0} Disconnected", im.SenderEndpoint);
                        break;
                    case NetConnectionStatus.RespondedAwaitingApproval:
                        im.SenderConnection.Approve();
                        break;
                }
                break;
        }

        this.networkManager.Recycle(im);
    }
}

The above ProcessNetworkMessages() method is a typical example of how you would process network messages. The above code will continue to process messages until there are no more messages.

Each message will have a type that we can test for and then handle appropriately. You will notice that the types of messages I am checking for matches the types I enabled when I configured both the NetServer and NetClient instances.

When we’re done with a message we recycle it. You don’t have to recycle it, but according to the Lidgren documentation it is more efficient to do so.

The interesting case in the above example is the RespondedAwaitingApproval status type. This is where we can either Approve or Deny a connection attempt. Since we enabled the ConnectionApproval message type we have to call the im.SenderConnection.Approve() method to complete the connection process.

When you run the code you should see the following output in the client and server console windows.

image

The source code for this article series can be found on google code.

In the next article I will demonstrate how to get some asteroids flying around the screen synchronized on both the client and server.

Let’s make a multiplayer game (part 2)

Game Overview

Asteroid Belt Assault, the game I’m using as foundation for this series is basically a combination of Galaga and Asteroids. The player controls a spaceship that is flying through an asteroid belt. The asteroids themselves fly across the game screen and can bounce off each other. In addition to the asteroids the player is faced with waves of enemies that follow a pre-determined path and fire shots at the player. The player can move around the screen and fire his cannon at the enemies and asteroids.

Networking Architecture

There are various network topology choices available when making a multiplayer game. In this article series I will make use of a client-server architecture. I find that thinking about game networking in terms of a dedicated client and server helps me to better understand (and explain) what happens when.

In a pure client-server model the server typically handles all the game logic with a client being a dumb terminal only concerned with rendering the game state. This would be the easiest to program, but unfortunately in reality this model is not suitable due to latency and bandwidth constraints.

In order to mask the effects of network latency we need to do some processing of game logic on the client. There is therefore a lot of similarity between the game logic on the client and server, in fact they could essentially be the same code. So why do we need a server then?

The server is responsible for the game state, it needs to coordinate starting a game, ending a game and everything that happens in between. What this means is that the server in this game is responsible for generating our enemies, asteroids and making sure that the whole simulation remains in sync.

Solution Structure

The following screenshot shows the solution structure for our game.

image

The actual game code is contained in the XNA (windows) project called MultiplayerGame with its associated content contained in the MultiplayerGameContent project.

The Client and Server projects are console projects used to start up separate instances of the MultiplayerGame project. The reason for this is to allow us to have both a client and server instance of the game while we’re debugging. To achieve this we set multiple startup projects as follows:

image

From the above solution structure you will notice that I didn’t create a separate client XNA game project and a separate server XNA game project. The reasoning is that the game itself is identical on both the server and the client except for who owns the game state i.e. the server.

In the next article I will cover setting up the connection between the client and the server.

Let’s make a multiplayer game (part 1)

Last year I discovered the indie game called Terraria, which inspired me to try and make a multiplayer game. This proved to be a challenging undertaking due to various factors, with the foremost being the lack of information with regards to multiplayer game programming to be found on the internet.

The purpose of this article series is to document some of my experiences with the hope of it being useful to someone wanting to make a multiplayer game. I will be making use of XNA as rendering platform and the Lidgren networking library.

Please note: I am no XNA or networking expert. What I can promise is that by the end of the series I will have covered the basics and leave you with a working multiplayer game.

The game I am going to make (Asteroid Belt Assault) comes from the book XNA 4.0 Game Development by Example written by Kurt Jaegers. The emphasis will therefore not be on making a game but rather on making a game multiplayer.

Quests #3

I remember a great gaming experience I had with Might and Magic VI. The game started in a small town where everything seemed to have a place and a purpose. I really enjoyed the immersion factor of this area. Never did I get the feeling that I didn’t belong or that my actions were meaningless. Unfortunately, the rest of the game didn’t quite live up to the promise of that first area.

That initial town started me thinking about crafting playing areas where everything had a function. As per my previous posts, I will explore the potential of using a village (and surrounding areas) as the main questing area in the context of a roguelike.

I want the village to seem alive with every actor having a clear role and back story. The village should function like a living organism. It should be able to operate without relying on the actions of the player and should react to environmental events (weather, economic, physical threat) in a acceptable manner.

It is my vision that the player will be able to see the impact of his actions on the livelihood of the village and it’s villagers. If the baker gets sick (special event) the player can choose to go find a cure or do nothing. If the player fails (there might not be a cure) or does nothing, the baker could either die or get well. If the baker dies there might not be a baker in town for a while, which could impact the baked goods supply within the village. There might be other consequences depending on the other relationships impacted by the death of the baker.

It is these indirect consequences that I would like to explore in a game design. I am hoping that direct feedback (through quest rewards) and indirect feedback (through changes in the village) would inspire the player to engage with the game.

For the next few posts, I hope to explore building the indirect feedback loop into the fundamental game design.

Quests #2

Traditionally, roguelikes have been about random generated environments, turn-based game play, tactical combat, character progression and inventory management. A few modern incarnations have had some form of questing system, but not to the level as described in my previous post.

In order to facilitate a meaningful gaming experience I need to find a way to engage the player with the game world. I want the player to feel like he belongs in the world and the he can make a difference to his character’s life and the lives of the other characters within the game world.

As a first implementation, I propose to centre the action around a single village. Each character in the village will have a specific role and purpose. I don’t want any meaningless elements within the game world. Everything should be built according to a plan and a purpose.

The player character will be a member of the village. I want the player to feel like he is a part of the village and has some purpose in the village. I don’t think the player will take a primary role within the village, such as the role of the innkeeper, smith or butcher, but will have some kind of supporting role within the village.

I can achieve the above mentioned supporting role through various means. An obvious way would be to make the player a child of one of the villagers. The player will start his journey by assisting his parents with their particular tasks and then gradually branch out to do greater things.

Another possibility is for the player’s livelihood to be threatened by some external force, causing the player to seek refuge at the village and requiring the player to take steps to get back on his feet. For example, the player could have his farm destroyed by a band of Orcs and would then need to take refuge at the village Inn. From there he has various options available to him. For example, he could assist the villagers with their tasks or leave the village to find the Orcs. It becomes the player’s decision and his decisions will change his experience.

The outcome of the player’s actions would, hopefully, have a lasting impact on the lives of the villagers, the overall future of the village and possibly the player himself.