RPG LiteNetLib

From TRCCompSci - AQA Computer Science
Jump to: navigation, search

Requirements

You must follow the previous tutorial first: Example LiteNetLib.

From this you will get a working client and server connection, you should be able to start one instance of the game as the client and one instance of the game as the server.

Making it into a game

We are going to make it so one running instance of the game is the server, and the other is the client. Each instance of the game will control a different character, and will repeatedly update its current position and send its position to the other instance.

Getting Squared.Tiled

This example will use a Tiled map for all of the visuals. You will need to create a new class in your project (left click 'Project' and select 'New Class').

Copy the code from this document: Square.Tiled Class

Or from GitHub: GitHub TRCCompSci tiled-xna

Paste it over the whole code pre generated for you new class.

Add Using References

You will need to add references to the following:

using System.IO;
using Squared.Tiled;

Creating a Map

I have created this map in Tiled. The 'dungeon' layer contains the floor and walls of my dungeon, the 'collision' layer contains the box looking tiles to identify the bounds of my room. the 'objects' layer contains an object for 'player1' and 'player2'. Netrpgmap.png

Extra Variables

Add the following extra variables:

        Map map;
        Layer collision;
        Vector2 viewportPosition;
        Squared.Tiled.Object sprite;
        string playerobject, other;

LoadContent

Now to load the map, layers and textures. We also assign an instance to sprite, this will be the character controlled by this player. You may need to change the names for the map, collision layer, and texture to load, it will depend on your naming.

            map = Map.Load(Path.Combine(Content.RootDirectory, "SimpleRPG.tmx"), Content);
            collision = map.Layers["Collision"];
            map.ObjectGroups["objects"].Objects["player1"].Texture = Content.Load<Texture2D>("hero");
            map.ObjectGroups["objects"].Objects["player2"].Texture = Content.Load<Texture2D>("hero");
            sprite = new Squared.Tiled.Object();

I have also changed the timer interval:

atimer.Interval = 25;

Timer Event Changes

In the previous tutorial we added a timer to send messages every n seconds. We will change this code to send our current position instead. I have changed the 'writer.Put' to add the X of sprite, and then another line to put the Y of sprite:

        private void Atimer_Elapsed(object sender, ElapsedEventArgs e)
        {
            atimer.Enabled = false;
            count++;
            NetDataWriter writer = new NetDataWriter();               
            writer.Put(sprite.X);// Add the X of the playing character
            writer.Put(sprite.Y);// Add the Y of the playing character

            if (client)
                Client.SendToAll(writer, DeliveryMethod.ReliableOrdered);
            else if (server)
                Server.SendToAll(writer, DeliveryMethod.ReliableOrdered);
        }

Update Method

Depending on which instance of the game is the client, and which is the server, we need to set which character to control. So after the exit code enter this:

            if (playerobject == "player1")
                sprite = map.ObjectGroups["objects"].Objects["player1"];
            else if (playerobject == "player2")
                sprite = map.ObjectGroups["objects"].Objects["player2"];

Client Code Changes

You should already have working client code, so we just need to make a few changes:

            if (Keyboard.GetState().IsKeyDown(Keys.C))
            {
                if (!server && !client)
                {
                    client = true;
                    EventBasedNetListener listener = new EventBasedNetListener();
                    Client = new NetManager(listener);

                    Client.Start();
                    Client.Connect("127.0.0.1" /* host ip or name */, 9050 /* port */, "SomeConnectionKey" /* text key or NetDataWriter */);

                    playerobject = "player2"; //set player character
                    other = "player1"; // set other character

                    listener.NetworkReceiveEvent += (fromPeer, dataReader, deliveryMethod) =>
                    {
                        map.ObjectGroups["objects"].Objects[other].X = dataReader.GetInt(); // when we receive a message it will can an int for the X
                        map.ObjectGroups["objects"].Objects[other].Y = dataReader.GetInt(); // and an int for the Y of the other character
                        dataReader.Recycle();
                    };
                }
            }

Server Code Changes

We need to make the same changes as above, firstly to set the player and other characters and to the 'NetworkReceiveEvent' for the server. I also commented out the 'peer.Send()' line.

            if (Keyboard.GetState().IsKeyDown(Keys.S))
            {
                if (!server && !client)
                {
                    server = true;
                    EventBasedNetListener listener = new EventBasedNetListener();
                    Server = new NetManager(listener);
                    Server.Start(9050 /* port */);

                    playerobject = "player1"; //set player character
                    other = "player2"; // set other character

                    listener.ConnectionRequestEvent += request =>
                    {
                        if (Server.PeersCount < 10 /* max connections */)
                            request.AcceptIfKey("SomeConnectionKey");
                        else
                            request.Reject();
                    };

                    listener.PeerConnectedEvent += peer =>
                    {
                        Console.WriteLine("We got connection: {0}", peer.EndPoint); // Show peer ip
                        NetDataWriter writer = new NetDataWriter();                 // Create writer class
                        writer.Put("Hello client!");                                // Put some string
                        //peer.Send(writer, DeliveryMethod.ReliableOrdered);             // Send with reliability
                    };

                    listener.NetworkReceiveEvent += (fromPeer, dataReader, deliveryMethod) =>
                    {
                        map.ObjectGroups["objects"].Objects[other].X = dataReader.GetInt();
                        map.ObjectGroups["objects"].Objects[other].Y = dataReader.GetInt();
                        dataReader.Recycle();
                    };
                }
            }

Polling If Statement

I have simplified the if statement which contained the 'PollEvents' lines. I removed the 'Thread.Sleep' because this made the motion jerky:

            if (server && !atimer.Enabled)
            {
                atimer.Enabled = true;
                Server.PollEvents();
            }
            else if (client && !atimer.Enabled)
            {
                Client.PollEvents();
                atimer.Enabled = true;
            }

Movement Code

Add the following if statement to read movement from the keys, move the sprite and set the viewport position. this will only run when the server or client have been started:

            if (client || server)
            {
                KeyboardState keypress = Keyboard.GetState();

                int scrollx = 0, scrolly = 0;
                if (keypress.IsKeyDown(Keys.Left))
                    scrollx = -1;
                else if (keypress.IsKeyDown(Keys.Right))
                    scrollx = 1;
                else if (keypress.IsKeyDown(Keys.Up))
                    scrolly = -1;
                else if (keypress.IsKeyDown(Keys.Down))
                    scrolly = 1;

                sprite.X += (scrollx * 2);
                sprite.Y += (scrolly * 2);

                viewportPosition = new Vector2(sprite.X - (graphics.PreferredBackBufferWidth / 2), sprite.Y - (graphics.PreferredBackBufferHeight / 2));
            }

Draw Method

Add the following to draw the map if the server or client is started:

            spriteBatch.Begin();
            if (server ||client)
                map.Draw(spriteBatch, new Rectangle(0, 0, GraphicsDevice.Viewport.Width, GraphicsDevice.Viewport.Height), viewportPosition);
            spriteBatch.End();

Check for Bounds

I used the info on this tutorial:

To create the following CheckBounds method:

public bool CheckBounds()
        {
            bool check = false;
            int tilepixel = 16;

            Rectangle playrec = new Rectangle(
                map.ObjectGroups["objects"].Objects[playerobject].X,
                map.ObjectGroups["objects"].Objects[playerobject].Y,
                map.ObjectGroups["objects"].Objects[playerobject].Width,
                map.ObjectGroups["objects"].Objects[playerobject].Height
                );

            for (int x = 0; x < map.Width; x++)
            {
                for (int y = 0; y < map.Height; y++)
                {
                    if (collision.GetTile(x, y) != 0)
                    {
                        Rectangle tile = new Rectangle(
                            (int)x * tilepixel,
                            (int)y * tilepixel,
                            tilepixel,
                            tilepixel
                            );

                        if (playrec.Intersects(tile))
                            check = true;
                    }
                }
            }

            return check;
        }

now change the movement code, and add this after the 'Keyboard.GetState':

            //store current position to move back too if collision
            int tempx = map.ObjectGroups["objects"].Objects[playerobject].X;
            int tempy = map.ObjectGroups["objects"].Objects[playerobject].Y;

and add this before we set the viewport position:

            //now we have moved checkbounds
            if (CheckBounds())
            {
                map.ObjectGroups["objects"].Objects[playerobject].X = tempx;
                map.ObjectGroups["objects"].Objects[playerobject].Y = tempy;
            }