Peer to Peer Networking Example Using the Lidgren.Network Framework

Brief topic switch: Peer to peer networking. I had wanted to get this example in a simple movement game in IceCream but found I needed to start with a console app first. So here it is. I’ve cleverly named it NetConsoles, and the purpose is to teach myself how to link multiple nodes in a peer to peer fashion using the Lidgren.Network framework. While I had some initial issues, it wasn’t all that hard.

Nearly every example I could find using Lidgren.Network was a client/server architecture, so I had to do quite a bit of experimenting to get peer to peer going. Finding sample code for it was completely fruitless. But I did see a post indicating it is possible and relatively easy, so I pressed on. It turns out that the base object of NetClient and NetServer is NetPeer, and it’s all you need to connect p2p (I’m now tired of typing it out).

Two things stand out from this experience: the first is that I tried to create a semi-contained and reusable networking object. It’s far from robust, but I think it will be a sufficient base to carry into my next networking endeavor. The second is that I don’t typically need to do much threading, but it was necessary for this application. The reason is that we need to be constantly polling for incoming network data while also waiting for the user to type commands (like to send a test message). Console.WriteLine is blocking, so my main networking object is written so that when Listen() is called, it spins off a thread that continuously asks Lidgren.Network, “do we have messages to process?” If yes, parse and handle. While basic, I enjoyed the exercise because threads are not in my day to day toolbox (but maybe they should be!).

You can download all the code here, which includes binaries, in case people have trouble building: NetConsoles

Note that the folder structure expects the lidgren-network-gen3 folder to be just one-level above the solution folder. I’d rather include the code library than just a DLL to make debugging and stepping through easier. While learning, it was very handle to be able to visually scan the internal workings of the library rather than be stuck with just IntelliSense (even though IntelliSense is very nice).

Some highlights of the code include:

Construction of the NetPeerConfiguration object (only the first two NetIncomingMessageType are required for basic functionality, but I wanted to see the other 2 in action).

Config = new NetPeerConfiguration("test_console");
Config.EnableMessageType(NetIncomingMessageType.DiscoveryRequest);
Config.EnableMessageType(NetIncomingMessageType.DiscoveryResponse);
Config.EnableMessageType(NetIncomingMessageType.ConnectionApproval);
Config.EnableMessageType(NetIncomingMessageType.UnconnectedData);
Config.LocalAddress = NetUtility.Resolve("localhost");

The initialization of the NetPeer object (extremely simple) and starting up the listener thread:

Peer = new NetPeer(Config);
Peer.Start();
Console.WriteLine("listening on " + Config.Port.ToString());
 
NetWorker = new MyNetWorker(Peer, this);
NetThread = new Thread(NetWorker.ProcessNet);
NetThread.Start();

Sending a basic string message (the MessageType enum is my own, not part of the framework):

public void SendMessage(string message) {
    if (Peer.Connections == null || Peer.Connections.Count == 0) {
        Console.WriteLine("No connections to send to.");
        return;
    }
    NetOutgoingMessage msg = Peer.CreateMessage();
    msg.Write((int)MessageType.StringMessage);
    msg.Write(message);
    Peer.SendMessage(msg, Peer.Connections, NetDeliveryMethod.ReliableOrdered, 0);
}

Sending peer info to all connected peers (by passing Peer.Connections to Peer.SendMessage):

public void SendPeerInfo(IPAddress ip, int port) {
    Console.WriteLine(string.Format("Broadcasting {0}:{1} to all (count: {2})", ip.ToString(),
        port.ToString(), Peer.ConnectionsCount));
    NetOutgoingMessage msg = Peer.CreateMessage();
    msg.Write((int)MessageType.PeerInformation);
    byte[] addressBytes = ip.GetAddressBytes();
    msg.Write(addressBytes.Length);
    msg.Write(addressBytes);
    msg.Write(port);
    Peer.SendMessage(msg, Peer.Connections, NetDeliveryMethod.ReliableOrdered, 0);
}

And finally–huge code dump–the listen thread class:

public class MyNetWorker
{
    NetPeer peer = null;
    NetManager netManager = null;
    public volatile bool shouldQuit = false;
 
    public MyNetWorker(NetPeer inPeer, NetManager inNetManager) {
        peer = inPeer;
        netManager = inNetManager;
    }
 
    public void ProcessNet() {
        // read messages
        while (!shouldQuit) {
            Thread.Sleep(1);
            if (peer == null)
                continue;
            NetIncomingMessage msg;
            while ((msg = peer.ReadMessage()) != null) {
                switch (msg.MessageType) {
                    case NetIncomingMessageType.DiscoveryRequest:
                        Console.WriteLine("ReceivePeersData DiscoveryRequest");
                        peer.SendDiscoveryResponse(null, msg.SenderEndpoint);
                        break;
                    case NetIncomingMessageType.DiscoveryResponse:
                        // just connect to first server discovered
                        Console.WriteLine("ReceivePeersData DiscoveryResponse CONNECT");
                        peer.Connect(msg.SenderEndpoint);
                        break;
                    case NetIncomingMessageType.ConnectionApproval:
                        Console.WriteLine("ReceivePeersData ConnectionApproval");
                        msg.SenderConnection.Approve();
                        //broadcast this to all connected clients
                        //msg.SenderEndpoint.Address, msg.SenderEndpoint.Port
                        netManager.SendPeerInfo(msg.SenderEndpoint.Address, msg.SenderEndpoint.Port);
                        break;
                    case NetIncomingMessageType.Data:
                        //another client sent us data
                        Console.WriteLine("BEGIN ReceivePeersData Data");
                        MessageType mType = (MessageType)msg.ReadInt32();
                        if (mType == MessageType.StringMessage) {
                            Console.WriteLine("Message received: " + msg.ReadString());
                        }
                        else if (mType == MessageType.PeerInformation) {
                            Console.WriteLine("Data::PeerInfo BEGIN");
                            int byteLenth = msg.ReadInt32();
                            byte[] addressBytes = msg.ReadBytes(byteLenth);
                            IPAddress ip = new IPAddress(addressBytes);
                            int port = msg.ReadInt32();
                            //connect
                            IPEndPoint endPoint = new IPEndPoint(ip, port);
                            Console.WriteLine("Data::PeerInfo::Detecting if we're connected");
                            if (peer.GetConnection(endPoint) == null) {//are we already connected?
                                //Don't try to connect to ourself!
                                if (peer.Configuration.LocalAddress.GetHashCode() != endPoint.Address.GetHashCode()
                                        || peer.Configuration.Port.GetHashCode() != endPoint.Port.GetHashCode()) {
                                    Console.WriteLine(string.Format("Data::PeerInfo::Initiate new connection to: {0}:{1}",
                                        endPoint.Address.ToString(), endPoint.Port.ToString()));
                                    peer.Connect(endPoint);
                                }
                            }
                            Console.WriteLine("Data::PeerInfo END");
                        }
                        Console.WriteLine("END ReceivePeersData Data");
                        break;
                    case NetIncomingMessageType.UnconnectedData:
                        string orphanData = msg.ReadString();
                        Console.WriteLine("UnconnectedData: " + orphanData);
                        break;
                    default:
                        Console.WriteLine("ReceivePeersData Unknown type: " + msg.MessageType.ToString());
                        try {
                            Console.WriteLine(msg.ReadString());
                        }
                        catch {
                            Console.WriteLine("Couldn't parse unknown to string.");
                        }
                        break;
                }
            }
        }
        Console.WriteLine("Exiting net thread!");
    }
}

Whew! It’s a lot, but p2p is pretty cool! Good luck!

Moving the Camera in IceCream

I know this sounds silly, so it’s mostly just a note to myself. The proper way to move the camera in IceCream is:

scene.ActiveCameras[0].Position = new Vector2(300300);

I know this may seem obvious, but looking at the various properties, this was not the first thing I tried.

Also, if you’re relying on the camera BoundingRect for anything (like bounds-checking), IceCream by default does not recompute the BoundingRect when the camera is moved. I had to patch that one in, too. I’m really starting to get a feel for the IceCream code base, and I like what I see when I’m digging around.

IceCream Parallax Scrolling

On my mission to create the perfect side-scroller action game, parallax scrolling is a key component. After diving into the IceCream code base, I thought I was going to have to create my own. I started on the feature (fortunately it’s a relatively small thing) when I discovered that IceCream has a built-in sprite property called AutoScroll. Setting this to true and the AutoScrollSpeed > 0 causes the sprite to “scroll” just like a background, making a copy of itself and everything necessary to cover the entire camera width.

Wow, how cool!

There are a few bugs with it, though. One, the AutoScroll properties are configurable in MilkShake but don’t save to the scene XML, or load. So I had to add that in (quick). It also doesn’t work if you want to move the image from right to left, only vertical or left to right. This is a bit of an oversight, as no one wants to play a game that scrolls from left to right. The character/level should move left to right, but the background should move right to left. Basically, I just need to add support for AutoScrollSpeed being negative.

The third nitpick isn’t a bug, but it doesn’t support a linked background. So if my background is chopped into multiple images to save time/memory when rendering back-to-back, they’ll have to be combined into a single material in IceCream. This isn’t the end of the world and could be patched in if it became an issue (via LinkPoints property or something). Not low-hanging fruit in terms of performance.

I don’t understand why this engine never caught on or wasn’t pushed more by the developers. It’s very powerful and extremely easy to use. It’s the perfect combination of GUI + code, not to mention all the core engine components are already written for you.

Update: I realized later that the reason AutoScroll isn’t saved to XML by MilkShake is probably because it’s more common to set it up via code after the scene loads and the scene has truly started.

It also brings up two other points to investigate: is AutoScroll meant to scroll w/ the camera automatically? And, can the AutoScroll functionality work in a scenario that does not use a constant speed? E.g., in an action sort of game where the player can freely move left (“back” in the level), or right (“forward in the level”), at varying speeds.

Reusable IceCream Utilities and Components

Since the IceCream library seems to be somewhat abandoned (though they graciously committed an XNA4-compatible version) I’ve created my own private repository for my own changes and fixes. If I continue to add-on, I’ll contact the original authors and see if they want the changes to be official or what, but for now I’m keeping it private so the authors don’t feel like I’m trying to take credit for their great work. (It really is great stuff.)

Anyway, one of the first things I did was add a Utility directory and drop in IceTimer, which is a rip of timer code in Advanced 2D Game Development (a great book!, though written for C++ programmers). I’ve used it in previous game demos with great success. Here’s the code for IceCream:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
/// <summary>
/// To use, instantiate new IceTimer(). Then call Stopwatch() with ms intervals. It will auto-reset, so
/// just call/monitor return value of Stopwatch().
/// </summary>
public class IceTimer
{
	public IceTimer() {
		timer_start = TimeGetTime();
		Reset();
	}
 
	private long TimeGetTime() {
		return DateTime.Now.Ticks / 10000; //convert ticks to milliseconds. 10,000 ticks in 1 millisecond.
	}
 
	public long GetTimer() {
		return (long)(TimeGetTime());
	}
 
	public long GetStartTimeMillis() {
		return (long)(TimeGetTime() - timer_start);
	}
 
	public void Sleep(int ms) {
		long start = GetTimer();
		while (start + ms > GetTimer()) { }
	}
 
	public void Reset() {
		stopwatch_start = GetTimer();
	}
 
	public bool Stopwatch(int ms) {
		if (TimeGetTime() > stopwatch_start + ms) {
			Reset();
			return true;
		}
		else
			return false;
	}
 
	long timer_start;
	long stopwatch_start;
}

All you have to do is instantiate it and then call something like:

1
if (myTimer.Stopwatch(1000)) { /* this code will fire once every second */ }

Next up, a couple highly reusable components. In fact, I was surprised their functionality wasn’t already built-in to the IceCream framework. Maybe it is and I missed it? Anyway, here’s a component to destroy a scene item after a user-provided number of seconds (useful for, say, bullets flying off-screen that you don’t want to zoom into infinity).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
[IceComponentAttribute("LifeComponent")]
public class LifeComponent : IceComponent
{
	[IceComponentProperty("Life in Seconds")]
	public int LifeInSeconds { get; set; }
 
	public LifeComponent() {
	}
	public override void CopyValuesTo(object target) {
		base.CopyValuesTo(target);
		if (target is LifeComponent) {
			LifeComponent targetCom = target as LifeComponent;
			targetCom.LifeInSeconds = this.LifeInSeconds;
		}
	}
	public override void OnRegister() {
		Enabled = true;
		LifeTimer = new IceTimer();
	}
 
	public override void Update(float elapsedTime) {
		if (LifeTimer.Stopwatch(LifeInSeconds * 1000))
			this.Owner.MarkForDelete = true;
	}
 
	private IceTimer LifeTimer;
}

And here’s another for moving a SceneItem given an X and Y velocity:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
[IceComponentAttribute("VelocityComponent")]
public class VelocityComponent : IceComponent
{
	[IceComponentProperty("Velocity Vector")]
	public Vector2 Velocity { get; set; }
 
	public VelocityComponent() {
	}
	public override void OnRegister() {
		Enabled = true;
	}
 
	public override void CopyValuesTo(object target) {
		base.CopyValuesTo(target);
		if (target is VelocityComponent) {
			VelocityComponent targetCom = target as VelocityComponent;
			targetCom.Velocity = this.Velocity;
		}
	}
 
	public override void Update(float elapsedTime) {
		this.Owner.PositionX += Velocity.X;
		this.Owner.PositionY += Velocity.Y;
	}
}

Let me know if you use these! So far I think most of my objects will be having these components.

IceCream XNA4 Tutorial

The IceCream Pong tutorial is still working in the latest version, and because the official source is no longer available, I’ve uploaded a final working version of it here: IceCream Pong Sample Code  It should open and build in Visual Studio 2010 as long as your directory structure alongside the IceCream project code is as follows:

Pong IceCream Tutorial Directory Structure Layout

To clarify, that’s a root folder that contains both the base IceCream directory from their SVN, and a folder called PongIC that contains the solution file and all child directories of the linked archive.

The “game” is obviously very simplistic, but it does give a great overview of how to use the Milkshake GUI if you decide to build it yourself.

IceCream for XNA 4 Available via SVN!

I’ve been in touch with Loïc, one of the author’s (the primary?) of IceCream, the 2d framework and toolset for XNA. He just committed to SVN the codebase compatible with XNA 4! You can grab it from their repository URL: http://svn.assembla.com/svn/IceCream (that URL has both the XNA 3.1 and 4 code).

Not all of it is done, but I think it’s enough for me to get going. I tried updating the Visual Studio templates for VS2010 and XNA 4, but the format has changed for multi-project solutions and templates are just not something I’m very familiar with. I actually got it about 90% of the way there, but it’s not something I would feel comfortable releasing (I don’t release things at 90%). I had spent too much time already as I had issues with my Export Template menu item, and I was more eager to get in and play with code. Maybe I’ll try to complete it sometime this week.

I’m also having difficulty building the XBox 360 and Windows Phone 7 versions of the library because of the reliance on XML, but they’re not critical platforms for me. PC is all I need for now. (X360 and WP7 run reduced versions of the .NET framework where the standard XML routines are not included).

Here’s hoping to more IceCream updates soon! Thanks, Loïc!

PSA: If the ‘Export Template…’ File Menu Option Isn’t Available in Visual Studio 2010

PSA: If the ‘Export Template…’ File menu option isn’t available in Visual Studio 2010, it’s probably because you imported your settings from a previous version of Visual Studio (I did because I use custom text coloring). To get this option back, you can either:

Reset your settings. This is a bummer because all of your coloring goes back to default too, though you could just re-import them after you’ve done your export work.

Or, even better:

  1. Go to Tools, then “Customize…”, then click the “Commands” tab.
  2. At the top of the dialog is a radio button called Menu bar with a dropdown next to it. Select “File” from that dropdown.
  3. Click the “Add Command” button.
  4. In the dialog that appears, from the Categories list, select File.
  5. Scroll through the commands list to find “Export Template…” and select it.
  6. Click Ok and use the Move Up and Move Down buttons to move it down near all the other save commands (I put mine last of the save’s).
And you’re done! I suggest re-exporting your settings so you have a fresh copy for the future.

XNA 2d Framework

I’ll just come out and say it: I really enjoy 2d games. I love it when game companies license classic IP, like from the SNES era, and revamp it with HD graphics and modern gameplay, while keeping it 2d (or 2.5d). When going from 2d to 3d, it often changes the gameplay so significantly that it’s no longer the same game, it’s just the same brand.

Consequently, I plan to focus on 2d games for the foreseeable future. All my current ideas are 2d, and the people who appreciate the sort of gameplay 2d offers are my peeps.

I already have a game started with all my own code, but as I build one reusable component after another, I can’t help but think these same objects have been written hundreds of times before me. For example: sprite sheets, animation, a “SceneItem” base class, etc. I found that there aren’t any maintained 2d frameworks for XNA right now. There are plenty of 2d engines that want to do everything for you, and some 3d engines. But nothing (that I could find) that gives a developer full source and says, “here’s a great base, now go forth, programmer!”

What to do? I’m not a game programming expert, but I’m an experienced enough developer to know that reinventing the code wheel is not a good way to spend time (DRY principle, except at a higher level). So I started looking around and found IceCream. IceCream is a 2d XNA framework with its own GUI even, called MilkShake. I haven’t been able to actually run it yet though, because it’s targeting the XNA 3.1 framework (downloading now). Building the source targeting XNA 4 leads to many (many) errors, that I’m thinking about fixing. If I can get it working in a few evenings and get all that usable code, it’ll be worth it. I’ve just emailed the author to find out what license the code has been released in (I think MS-PL) before I spend time on it.