Simple XNA Music Manager

Image

XNA provides an easy way to play music via the MediaPlayer class, but it doesn’t make it easy to set up a playlist for your game. Below is a minimal example of how to manage the soundtrack of your game:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Media;
 
namespace Example
{
    class Music
    {
        IList<Song> songs;
 
        Random random;
 
        int index;
 
        public IList<Song> Songs
        {
            get { return songs; }
            set { songs = value; }
        }
 
        public Music()
            : base()
        {
            songs = new List<Song>();
 
            random = new Random();
 
            index = 0;
 
            MediaPlayer.IsVisualizationEnabled = true;
 
            MediaPlayer.ActiveSongChanged += new EventHandler<EventArgs>(MediaPlayer_ActiveSongChanged);
            MediaPlayer.MediaStateChanged += new EventHandler<EventArgs>(MediaPlayer_MediaStateChanged);
        }
 
        public void Play()
        {
            if (MediaPlayer.GameHasControl)
            {
                MediaPlayer.IsRepeating = false;
 
                if (MediaPlayer.State == MediaState.Stopped)
                {
                    if (songs.Count > 0)
                    {
                        index %= songs.Count;
 
                        MediaPlayer.Play(songs[index]);
 
                        index++;
                    }
                }
            }
        }
 
        public void Stop()
        {
            songs.Clear();
 
            index = 0;
 
            if (MediaPlayer.GameHasControl)
            {
                MediaPlayer.Stop();
            }
        }
 
        public void Shuffle()
        {
            Shuffle(songs, random);
        }
 
        void MediaPlayer_ActiveSongChanged(object sender, EventArgs e)
        {
 
        }
 
        void MediaPlayer_MediaStateChanged(object sender, EventArgs e)
        {
            Play();
        }
 
        static void Shuffle<T>(IList<T> list, Random random)
        {
            int n = list.Count;
 
            while (n > 1)
            {
                n--;
 
                int k = random.Next(n + 1);
 
                T value = list[k];
 
                list[k] = list[n];
                list[n] = value;
            }
        }
    }
}

And to get it working:

// Create an instance of the music manager
Music music = new Music();
 
// Add three tracks to the playlist
music.Songs.Add(contentManager.Load<Song>(@"Music/01"));
music.Songs.Add(contentManager.Load<Song>(@"Music/02"));
music.Songs.Add(contentManager.Load<Song>(@"Music/03"));
 
// Shuffle the playlist
music.Shuffle();
 
// Play the music!
music.Play();

Innovative Learning Week: Yann Seznec & GameJam

As promised, we have a couple of events during Innovative Learning Week which we think you’ll enjoy. Firstly, there’s a talk by Yann Seznec of Lucky Frame happening between 15:00 and 16:00 on Friday 22nd of February in Room G.07 of the Informatics Forum. He’s an entertaining speaker, and will be talking about what’s involved in running an independent games company. You can find more details about it in the Innovative Learning Week guide.

Pugs Luv Beats - An iPad rhythm game by Lucky Frame

Pugs Luv Beats, an iPad rhythm game by Lucky Frame

In addition, we have our 5th GameJam! This will be taking place in Room 5.08 of Appleton Tower on the 21st and 22nd February, starting at at 10:00. The format will be as usual. We will pick a theme from the hat, and then teams will spend two days building their game. Teams may be up to 4; feel free to turn up on your own or as a team.

All disciplines are welcome - artists, designers, digital artists, programmers, sound designers, writers etc.

Regarding rules, use any language, framework, or platform you like, provided you are not simply completing an existing game. Any platform means any platform, even board games! To enter the judging, your game be set up to play by the community by 8pm on the second day. There is a £50 prize (£50!) for the community’s favourite game.

If you are new to making games, we recommend that you use GameMaker. There is a free version available for both Windows and Mac. If you run through the first tutorial before attending then you can get straight into making your game. As usual we will be running around helping people with minor art tasks, GameMaker, XNA, and Java + Slick. The source code from some of the previous entries can be found on our site.

Any questions, drop us an email.

Scottish Game Jam 2013

Scottish Game Jam

The Scottish Game Jam (part of the Global Game Jam) took place this weekend, and along with a few other GameDevSoc members Nick and I went along to the Edinburgh site to take part. It was certainly pleasant to only be making a game rather than organising it as well for a change, and I don’t envy the work Luke Dicken put in to co-ordinating the Edinburgh venue. Despite the fact that not everyone who booked a place actually turned up there were still a reasonable number of people there, making up 5 teams, and a good atmosphere. Plus, since they could actually hire the room, they were allowed it overnight as well!

I think most teams were a little disconcerted by the theme initially – silence fell for the announcement to be broadcast, and everyone listened to a heartbeat sound, waiting for the theme to be revealed. The sound continued to play, and it slowly dawned that that was the theme, not a prelude to it! Nick and I were certainly thrown for a while, and tossed around ideas for a sonar hunting game or a resource management hospital sim, before settling on the hospital sim, which we promptly scrapped on Saturday to instead produce The Old Man and The Ducks. Unfortunately technical difficulties with our falling apart legacy laptops (and subsequent need of my scanner) meant we had to retreat to our flat for most of the work.

Still, we had it finished by 6:03 on Sunday morning, at which point we decided it would probably be best to get a few hours sleep before heading back down to the site for the demonstrations. We were glad to see that all the teams finished their games, and everyone was in high spirits for possibly the best bit of the Jam – seeing what everyone else had made of the theme!

Tobias, Marat, Orfeas and Danai created Meternal, an audio-based game were you play a baby’s spirit travelling through a maze, in search of a mother. The player finds their way by listening to which direction her voice calls you from.

Jib and Sangseo created Immunity, a co-operative game in which you each control a white blood cell which has to eat appearing viruses to preserve your ever dwindling number of red blood cells.

Both teams’ creations impressed the three judges, who awarded them each joint second place for the site – an excellent achievement on their parts, and a good showing from GameDevSoc!

Whether or not there will be an Edinburgh site again next year is currently a matter of debate at the moment. We are certainly keen for them to do it again, and have offered to help pitch in with the organisation. I think one of the reasons there were fewer people was a lack of advertising (even we didn’t find out about it till quite late on), so I’d urge you to let them know there isn’t a lack of interest!

If you didn’t make it to the Scottish Game Jam for whatever reason, don’t worry – GameDevSoc will be holding our second semester GameJam in Innovative Learning week on the 21st and 22nd of February!

Broad-phase Collision Detection with Spatial Hashing

Suppose that you have a number of entities in your game – tanks, bullets, mushrooms, etc. You’d like to find out if some of those entities overlap. Are any bullets hitting any tanks?

Generally speaking, we are trying to generate a list of all the pairs of entities that are colliding. The list should contain no duplicates.. and take into account the fact that collision relation is a symmetric one.

A naive solution works as follows:

for (int i = 0; i &lt; entityList.size() - 1; i++) {
 
    a = entityList.get(i);
 
    for (int j = i + 1; j &lt; entityList.size(); j++) {
 
        b = entityList.get(j);
 
        if (a.isColliding(b)) {
 
            collisionSet.add(new Collision(a, b));
        }
    }
}
 
// TODO: Do something with the collisionSet

This work great for a small game, but it’s not scalable. If there are n entities, then n^2 collision checks are required. What’s needed is a broad-phase collision detection. This is pretty much just a heuristic; it returns a set containing all entities that will probably collide – but might not – faster than O(n^2). This reduces the number of pairs we need to test fully, giving a much faster implementation overall. Of course it’s important that the system doesn’t give any false negatives, or a collision might get skipped!

There are lots of methods out there, but a particular approach I’ve had success with is spatial hashing. First, the map is divided into a grid structure. Next, we take each entity and find the grid squares that it overlaps. The coordinates of each overlapping grid square are used as input for a specialized hash function, which maps it to a bucket index between 0 and k. A reference to the entity is stored in each bucket it hashes to.

Now each entity is stored in one or more buckets, and – crucially – collisions may only occur between entities that share a bucket. To find the collision set, we simply run the naive solution on each bucket and collate the results. Increasing k – having a larger number of buckets – reduces the number of collision checks required, at the expense of memory usage. The right value will depend on your game.

This is probably best explained by example, so here’s a simple implementation in Java + Slick. It manages a steady 60 fps on a DICE machine, so it can’t be too bad!

Discuss this article

The New Term

We were rather lax in updating the website with details of our various events last term I know, mostly because we figured that letting you all know via the mailing list was probably enough. However it was recently pointed out to me that, especially given the low cost, we should probably advertise our events on all our outlets, so this term I’ll be making more of an effort to do that.

In that vein, our first event of this term is a joint pub quiz we’re holding with GameSoc this Thursday (17th January) in Teviot Debating Hall. It starts at 7:00, and is free to members and non-members alike, so feel free to bring friends! Like all our events you can either turn up in a team, or we’ll find you one on the night, and there will be a fabulous and unspecified prize for the winners. If our joint quiz last year is anything to go on it should be a lot of fun, so we hope to see you there :)

The other date to pencil into your diary is our customary semester GameJam, which is going to take place on the Thursday and Friday of Innovative Learning Week (18-22nd February), when we’ll also be hosting a talk from Yann Seznec of Edinburgh based game company Lucky Frame, but more details on all that a bit closer to the time.

Some Useful Collision Code

Collisions between objects are essential in games. Finding if a collision has occurred if relatively straight forward. What’s difficult is finding a collision response.

For Ganymede we have to deal with two types of objects: units, which are represented by circles and structures, which are represented by rectangles. Units may collide with units, and units may collide with structures. Thus, we have to deal with two types of collision: circle and circle and circle and rectangle. In each case we need to find the smallest vector to move one object by so that it no longer overlaps with the other. More discussion can be found here: http://www.metanetsoftware.com/technique.html.

I’ve made a simple helper class to deal with these types of calculations. It makes use of classes from the Java Slick framework. Eclipse should be able to fix the formatting. I hope it’s useful!

package nlr.collisions;
 
import org.newdawn.slick.geom.Circle;
import org.newdawn.slick.geom.Rectangle;
import org.newdawn.slick.geom.Vector2f;
 
public strictfp final class CollisionChecker {
 
	public static Vector2f collisionDepth(Circle a, Circle b) {
 
		Vector2f seperation = new Vector2f(a.getCenterX(), a.getCenterY()).sub(new Vector2f(b.getCenterX(), b.getCenterY()));
 
		float requiredDistance = a.getRadius() + b.getRadius();
 
		if (seperation.lengthSquared() &gt; requiredDistance * requiredDistance) {
			return new Vector2f(0, 0);
		}
		else {
			return seperation.copy().normalise().scale(requiredDistance - seperation.length());
		}
	}
 
	public static Vector2f collisionDepth(Rectangle a, Rectangle b) {
 
		float seperationX = a.getCenterX() - b.getCenterX();
		float distanceX = Math.abs(seperationX);
		float requiredDistanceX = (a.getWidth() + b.getWidth()) / 2;
 
		if (distanceX &lt; requiredDistanceX) {
 
			float seperationY = a.getCenterY() - b.getCenterY();
			float distanceY = Math.abs(seperationY);
			float requiredDistanceY = (a.getHeight() + b.getHeight()) / 2;
 
			if (distanceY &lt; requiredDistanceY) {
 
				if (distanceX &gt; distanceY) {
					return new Vector2f(seperationX, 0).normalise().scale(requiredDistanceX - distanceX);
				}
				else {
					return new Vector2f(0, seperationY).normalise().scale(requiredDistanceY - distanceY);
				}
			}
		}
 
		return new Vector2f(0, 0);
	}
 
	public static Vector2f collisionDepth(Circle a, Rectangle b) {
 
		if (a.getCenterX() &lt; b.getX()) {
			if (a.getCenterY() &lt; b.getY()) {
				// Top left
				return CollisionChecker.collisionDepth(a, new Vector2f(b.getX(), b.getY()));
			}
			else if (a.getCenterY() &gt; b.getY() + b.getHeight()) {
				// Bottom left
				return CollisionChecker.collisionDepth(a, new Vector2f(b.getX(), b.getY() + b.getHeight()));
			}
			else {
				// Middle left
				float seperation = b.getX() - a.getCenterX();
 
				if (seperation &gt; a.getRadius()) {
					return new Vector2f(0, 0);
				}
				else {
					return new Vector2f(seperation - a.getRadius(), 0);
				}
			}
		}
		else if (a.getCenterX() &gt; b.getX() + b.getWidth()) {
			if (a.getCenterY() &lt; b.getY()) {
				// Top right
				return CollisionChecker.collisionDepth(a, new Vector2f(b.getX() + b.getWidth(), b.getY()));
			}
			else if (a.getCenterY() &gt; b.getY() + b.getHeight()) {
				// Bottom right
				return CollisionChecker.collisionDepth(a, new Vector2f(b.getX() + b.getWidth(), b.getY() + b.getHeight()));
			}
			else {
				// Middle right
				float seperation = b.getX() + b.getWidth() - a.getCenterX();
				float distance = Math.abs(seperation);
 
				if (distance &gt; a.radius) {
					return new Vector2f(0, 0);
				}
				else {
					return new Vector2f(seperation, 0).normalise().scale(distance - a.getRadius());
				}
			}
		}
		else {
			if (a.getCenterY() &lt; b.getY()) {
				// Top center
				float seperation = b.getY() - a.getCenterY();
 
				if (seperation &gt; a.getRadius()) {
					return new Vector2f(0, 0);
				}
				else {
					return new Vector2f(0, seperation - a.getRadius());
				}
			}
			else if (a.getCenterY() &gt; b.getY() + b.getHeight()) {
				// Bottom center
				float seperation = b.getY() + b.getHeight() - a.getCenterY();
				float distance = Math.abs(seperation);
 
				if (distance &gt; a.radius) {
					return new Vector2f(0, 0);
				}
				else {
					return new Vector2f(0, seperation).normalise().scale(distance - a.getRadius());
				}
			}
			else {
				// Middle center
				return CollisionChecker.collisionDepth(
						new Rectangle(a.getX(),a.getY(), a.getRadius() * 2, a.getRadius() * 2), 
						b);
			}
		}
	}
 
	public static Vector2f collisionDepth(Circle a, Vector2f b) {
 
		Vector2f seperation = new Vector2f(a.getCenterX(), a.getCenterY()).sub(new Vector2f(b.getX(), b.getY()));
 
		if (seperation.lengthSquared() &gt; a.getRadius() * a.getRadius()) {
			return new Vector2f(0, 0);
		}
		else {
			return seperation.copy().normalise().scale(a.getRadius() - seperation.length());
		}
	}
}

Discuss this article here: http://gamedevsoc.eusa.ed.ac.uk/forum/viewtopic.php?f=13&t=463