RPG LiteNetLib
Contents
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'.
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;
}