Development News
Usually, game development studios are a closed entity where information is barely leaked. At Milkstone Studios, we think that an important part of the development lies in the community. Both listening to it and sharing information with it.
Wool development time distribution
While developing Wool, we were measuring the time we spent working on each game part. We think it will be useful to us to help estimate better our future project development times, and the effort needed in each part of it.
As you can see, most of the time spent went to Betatest, Level design and Graphics Art. The only thing unexpected here was the time spent developing each level. Despite having an editor that allowed us to design one complete level from scratch in around 15 minutes, the whole process took around 1.3 hours per level (Not including playtest). That is due to level redesigns, subtle modifcations, difficulty tuning, etc.
Little Racers networking decisions
Here are my decisions about the upcoming online mode of Little Racers.
The multiplayer section will allow players to select whether they want to play on System Link or Xbox Live. After that, the player select screen is shown (this screen will only allow profiles able to play in the selected mode: Signed In for System Link game, and Live profiles for Xbox Live game.
After this screen, there are some menu options available:
- Host public game: Equivalent to championship mode. Each player gets points for his position at race finish, plus one extra point if he manages to get the best lap time. This mode allows the host to set some parameters, such as lap number and car class. Everyone will be able to join the game, even at mid-championship (with the handicap that it implies).
- Host private game: Just like public game, but only friends of the host are allowed to join.
- Quick join: This option does a search for available games and, if one or more is found, it joins to the one that offers the best network conditions. If no game is found, a game with some fixed parameters (10 races, 12 max players, random car class, no handicap) is created.
- Find: This mode shows all created games with some info on the creation parameters, and allows the player to join one of them. I’m not implementing a search function since I don’t think there will be so many simultaneous games to make it necessary. If the online mode renders very successful and the need of a search function arises, we’ll add it.
And that’s the behaviour I’ve chosen to add for the LR online mode. I’d like to know your thoughts and suggestions about it. It’s probably too simple, other games such as Gears Of War 2 have more options and more complex quickjoin behaviour, etc. , but I think that for a game that won’t probably get more than 10 public games at the same time (that could mean around 120 players), it should be enough and there’s no need to make this section more complex.
Developing an editor (Part 1)
While Waaghman is working very hard on the Little Racers patches and updates (yes, he’s working full time on the online mode), I’m coding some tools and editors four our next games.
I already did some tools for another game that we started (and it probably won’t be finished) and it served me as a learning experience. One of the most annoying things of it was the different kind of things formed that part of a level. We had tiles, but also random geometry, player locations and some other things. The code was becoming a real mess that growed on each new iteration.
This time I’m making a generic level editor, and trying to keep the code clean. The idea is simple: a level has a size, and it can contain layers (just like in photoshop). So you can have a layer which represents a tilemap, and another one that shows the player positions, and all of them are independent.
Right now, the basic framework is done, and I’ve got some layers working. Of course, it has been designed to load and save the levels to XML. Most of the layers that are missing are just modifications of the ones I made, so it shouldn’t take long (but if I’ve learned something through the years is that everything takes more time than expected).
I want the editor to be easily customizable, so it can load external layer-types through dlls, so we’ll be able to use the editor on different games without compiling the full thing. It wasn’t a high priority feature, but in C# it was done in less than half an hour.
I have to say that I haven’t had much problems while developing in Windows Forms. Usually, doing this kind of app is something boring, and you end up with lots of little hacks to make the layouts work as expected. But this time, there are tiny ugly things here and there, but everything is much more clean than usual, and I’m pretty proud of it. I’m actually enjoying developing this tool.
When I finish this, there are other more ‘in-depth’ tools that I have to make. But those are really small apps to simplify some specific tasks. I’ll post about them when I have some new material to show. See you later!
Creating 2D geometry from a line strip
During development of an old prototype that never saw the light, I found the need to create some 2D geometry from a set of points that form a line.
After searching for any algorithm that could make what I needed, with no success, I decided to develop my own.
The algorithm is very simple, it just takes a rope and a width and creates a set of vertices tha form a TriangleStrip to be rendered on screen.
/// <summary>
/// Triangulates a line, returning a list of vertices that form a triangle strip
/// </summary>
/// <param name="contour">List of points that form the line</param>
/// <param name="width">Desired with of the geometry</param>
/// <returns>list of vertices that form a triangle strip</returns>
public static List<Vector2> Process(ReadOnlyCollection<Vector2> contour,float width)
{
List<Vector2> result = new List<Vector2>();
Vector2 normal = Vector2.Zero ;
// Generate vertices for each point in the line
for (int i = 0; i < contour.Count; i++)
{
// First and last are special cases, the normal is calculated right away
if (i == contour.Count - 1)
normal = GameMath.Normal(contour[i] - contour[i - 1]);
else if (i == 0)
normal = GameMath.Normal(contour[1] - contour[0]);
else
{
// For the rest of points, determine the normal as the middle angle between the direction of the previous and next segments.
Vector2 delta1 = contour[i] - contour[i - 1];
Vector2 delta2 = contour[i + 1] - contour[i];
if ((delta1.Length()!=0)&&(delta2.Length()!=0))
{
delta1.Normalize();
delta2.Normalize();
normal = GameMath.Normal(delta1 + delta2);
}
}
// Add two vertices to the vertex list
result.Add(contour[i] - normal * width / 2f);
result.Add(contour[i] + normal * width / 2f);
}
return result;
}
/// <summary>
/// Returns one normal of the 2D vector
/// </summary>
/// <param name="vector"></param>
/// <returns></returns>
public static Vector2 Normal(Vector2 line)
{
float x = line.X; float y = line.Y;
Vector2 normal = new Vector2();
if (x != 0)
{
normal.X = -y / x;
normal.Y = 1 / (float)Math.Sqrt(normal.X * normal.X + 1);
normal.Normalize();
}
else if (y != 0)
{
normal.Y = 0;
normal.X = (line.Y<0) ? 1 : -1;
}
else
{
normal.X = 1;
normal.Y = 0;
}
if (x < 0)
{
normal *= -1;
}
return normal;
}
That code is well enough to generate a credible line in almost all situations. As for UVs, what I did was assign the even vertices the value (X,0) and odd vertices (X,1), where X is the distance elapsed since the line start.
Problems with System.Random
Yesterday I ran a profiling tool (CLR Profiler) to see how Little Racers does with heap memory allocation, and found something strange: The log showed that class System.Random allocated around 88Mbytes of RAM during the whole execution.
The reason of this is the way the particle system works: Each particle stores only a seed to generate its random values, and a new Random is generated with that seed each time it needs to be updated (that is, on every frame). So, if we’re showing 50 particles on a frame, we’re calling new Random(seed) 50 times.
With System.Random, there was no possible workaround since it doesn’t have a method available to set the seed without recreating everything. Also, it seems to do some hard work on creation, maybe allocating the N next random numbers, I don’t know.
Anyway, I found a nice replacement called FastRandom: This class has the same interface that System.Random, so they can be easily interchanged. Also, it has a method to set the seed without having to recreate the class.
Anyway, I found a bug in the provided code, related with the methods Next(int) and Next(int,int) , it seems to store a double with the minimum value of an int and then multiply it by the desired range. It didn’t work for me, so I just replaced it with a modulus. Maybe it’s a little slower, but it works perfectly.
Plain XML content in XNA Content Pipeline
During the first stages of Little Racers development, I found the need of parsing XML data during the game load. The XNA Content Pipeline system includes XML Parsing, but it uses it in a very particular way. I won’t go much further in the way XNA parses XML because, to be sincere, I’ve not tried it enough to test wether it’s good or not.
My first impression was that it was too strict (probably due to optimization and type safe issues), moreover during parse I would need access to some data structures that only were available at runtime, so I preferred to store plain XML and parse it during game load.
This can be done by extending the content pipeline and adding a new type. To do this we’ll need to add two new projects to our solution.
XmlSource project
The first project we’ll create is the one which has the class that stores our xml plain text. I’ve called it XmlSource.It will have two classes in total.
XmlSource
This class is a very simple one. It simply has a string with the xml plain text:
public class XmlSource
{
public XmlSource(string xmlCode)
{
this.xmlCode = xmlCode;
}
private string xmlCode;
public string XmlCode { get { return xmlCode; } }
}
XmlSourceReader
This class extends ContentTypeReader and simply creates an XmlSource instance from its binary representation. In our case, the binary representation wil be a simple string, so the read is straightforward.
public class XmlSourceReader : ContentTypeReader<XmlSource>
{
/// <summary>
/// Loads an imported shader.
/// </summary>
protected override XmlSource Read(ContentReader input, XmlSource existingInstance)
{
string xmlData = input.ReadString();
return new XmlSource(xmlData);
}
}
With this, our first project is finished. This project is platform-independant (The Xbox needs to read the binary data) and will have to be referenced from every project that uses the XmlSource class, including the second project we’re making:
XmlSourceImporter
The second project(I called it XmlDocumentImporter) is a project of type “Content Pipeline Extension Library”. This project is referenced from the Content projects, and converts the source text file to binary data that will be saved in the .xnb files.
This project has 2 files:
XmlSourceImporter.cs
This file converts the XML source file into an XmlSource instance, that will be further converted into binary data
[ContentImporter(".xml", DisplayName = "Xml Source Importer")]
class XmlSourceImporter : ContentImporter<XmlSource>
{
public override XmlSource Import(string filename, ContentImporterContext context)
{
string sourceCode = System.IO.File.ReadAllText(filename);
return new XmlSource(sourceCode);
}
}
XmlSourceWriter.cs
This file converts an XmlSource into binary data, as opposed to XmlSourceReader
[ContentTypeWriter]
class XmlSourceWriter : ContentTypeWriter<XmlSource>
{
protected override void Write(ContentWriter output, XmlSource value)
{
/*StringWriter sw=new StringWriter();
value.Save(sw);
string content = sw.ToString();*/
output.Write(value.XmlCode);
}
public override string GetRuntimeType(TargetPlatform targetPlatform)
{
return typeof(XmlDocument).AssemblyQualifiedName;
}
public override string GetRuntimeReader(TargetPlatform targetPlatform)
{
return typeof(XmlDocumentReader).AssemblyQualifiedName;
}
}
Sample of use
Once both projects set up (remember to add reference to XmlSourceImporter from the Content project), we can add .xml files to the content project. Under “Content importer”, we’ll need to select “Xml Source importer”, instead of the default XNA one.
Once done this, the program sould compile ok. Loading XML from the program now is very easy:
XmlSource xs = Content.Load<XmlSource>(AssetName); XmlDocument xd = new XmlDocument(); xd.LoadXml(xs.XmlCode);
I know this can be a little confusing, i’ll upload some binaries if someone asks for them.
PS: Here they are: Plain Xml Content Importer
Follow us
Games
- All #xblig sales seem to have dropped to just 10% their usual value yesterday... Really strange2010/07/30 16:25
- Retro game of the week: Ice breaker by Topo Soft (1990)
http://www.youtube.com/watch?v=VfumQVwZU2o2010/07/26 09:16 - We'll be giving a conference on #euskal encounter 18 in 90 minutes (11:30 local time), so if you're there be sure to come and say hi!2010/07/23 07:36
- Surprisingly the Avatar Ninja costume is sold at more than 3 times the price of the game :p2010/07/19 08:52
- Check out this Avatar Ninja review by @MasterBlud
http://www.youtube.com/watch?v=zZs65H4-fl0 It features an authentic Ninja Gi!2010/07/19 08:18 - Retro Game of the Week: Evolva (PC)
http://www.youtube.com/watch?v=YWzoiGlApxM2010/07/19 06:12 - Avatar Ninja got to Top 3 sales last week! Who could've guessed?2010/07/15 07:33
- There are 11 fanmade videos of Avatar Ninja available on Youtube, wow!2010/07/14 06:04
- Retro game of the week: Raptor - Call of the Shadows (Apogee)
http://www.youtube.com/watch?v=pYa2g9_5Ss42010/07/12 07:35 - MotorHEAT reviewed on TeamKobun.com
http://xboxindies.teamkobun.com/index.php/motorheat/2010/07/12 06:56









