Archive for the ‘Engineering’ Category

Tools for generating tabletop cards

I’ve been working on a board game for about a year now. The code name is Calvinball because I make so many rule changes in the middle of a game. It’s a worker placement and resource management game. This post is about my latest attempt to improve the tools I use to print the cards for that game.

I’m pretty comfortable in Visio so I started out with a big Visio document with all the cards in it. This was annoying because I had a zillion instances of everything and if I wanted to change the way one of the icons looked I would have to go retouch all the cards. So I did some scripting in Visio to automate some of that. Unfortunately that made each element complex enough that Visio runs out of memory and starts corrupting things. It’s also not nearly procedural enough for my tastes and made me to a bunch of redundant dragging and clicking.

When I was trying to bring up a second card game (a coop wilderness exploration thing) I tried something completely different. Using Django and Python I generated the cards in HTML and then printed them from the browser. This kinda worked, but HTML is really not meant to have tight registration on its printed output so getting the front and back of cards to line up was constantly a problem. There were also strange scale and background color problems depending on what options I forgot to pick in the print dialog. So this approach wasn’t very satisfying either.

This weekend I started working on a new technique, and so far that’s going pretty well. I build card templates as SVG files in Inkscape. Then I load those with a Python script, process them a bit and dump out a card-specific SVG file. Then I use more Python to combine blocks of card files into pages. Then I turn those pages into PDFs and use PDFtk to make one big PDF with everything in it. At that point a PDF reader (I use FoxIt) can print everything in one batch.

The card templates are pretty straightforward. Here is one of them:

All the information required to render the cards is in one big list of dictionaries. When I get the rest of the cards switched over I’ll make this representation more powerful:

	cards = [
			"params" : {
				"cardtitle" : "Construction Zone",
				"carddescription" : "Players take one victory point when they build a building.",
			"back" : "back_goal",
			"front" : "front_goal",
			"params" : {
				"cardtitle" : "Research Center",
				"cost" : "RRR",
			"back" : "back_goal",
			"front" : "front_goal_donate",
			"params" : {
				"cardtitle" : "Breeding Stock",
				"carddescription" : "Players take one victory point when they opt to not gain a colonist and instead donate it to the galactic core.",
			"back" : "back_goal",
			"front" : "front_goal",
			"params" : {
				"cardtitle" : "One for All",
				"carddescription" : "Players take one victory point when they make a donation.",
			"back" : "back_goal",
			"front" : "front_goal_allforone",

The code to combine the two is too long to paste inline, but the whole python script to do all of this is here. It walks through every entry in the cards list loading the front and back templates for each card. Then it tries to find elements for each entry in the “params” dictionary and replace the text of that element. You may have some difficulty getting this to run on any computer other than mind. Install lxml and svg_stack into Python, and put Inkscape and PDFtk in your path and you should be most of the way there.

One tricky bit is that the node layout for flowing text is different from a single line of text. This function finds the node with the text in it in each case:

def FindNode( root, name ):
	el = root.findall( ".//n:text[@id='" + name + "']/n:tspan", xmlNamespaces)
	if( el ) :
		return el[0]

	el = root.findall( ".//n:flowRoot[@id='" + name + "']/n:flowPara", xmlNamespaces )
	if( el ):
		return el[0]

	return None

It’s also worth noting that flowRoot doesn’t seem to work anywhere but Inkscape. Apparently it was in the final spec for some version of SVG that nobody supports. I use it for descriptions on some cards where I need word wrapping.

The other kind of parameterization that’s currently supported is recoloring or hiding the resource icons to match the cost string on the card definition. That just finds paths under a group with a special ID and sets their style to something specific to that resource type:

resourceStyles = {
	"F" : "fill: #16CD22; stroke: #000000; stroke-width: 0.5;",
	"E" : "fill: #D5FF2D; stroke: #000000; stroke-width: 0.5;",
	"M" : "fill: #404040; stroke: #000000; stroke-width: 0.5;",
	"R" : "fill: #FF69F7; stroke: #000000; stroke-width: 0.5;",
	"C" : "fill: #0000F7; stroke: #000000; stroke-width: 0.5;",
hiddenResourceStyle = "opacity:0"

def SubstCost( root, cost ):
	for n in range( 0, 15 ):
		style = hiddenResourceStyle
		if( n < len( cost ) ):
			style = resourceStyles[ cost[n] ]

		el = root.findall( ".//n:g[@id='cost" + str(n) + "']/n:path", xmlNamespaces )
		for path in el:
			path.set( "style", style )

The card SVG files are all dumped into a directory that contains all the script's output. For my game I write card back and fronts for every card. Most of the cards in my game are double-sided so having a shared back between multiple cards is actually kinda rare. You might be able to do something less spammy. Here are all the files generated by my initial test case:

From there the script builds "pages" where a page is a list of svg filenames. With standard 2.5" x 3.5" cards you can fit 8 cards on an 8.5" x 11.00" page, so every 8 cards forms a page. The pages alternate between fronts and backs with the fronts shown top to bottom and the backs shown bottom to top (to match the duplex feature on my printer.) If there aren't an even multiple of 8 cards blank cards are added to the pages so that each page is exactly 7" x 10". See lines 140-170 in the script for the code that does this pagination.

Using the lists of SVG filenames the script uses a Python module called svg_stack to combine them into bigger SVG files. svg_stack has horizontal and vertical box layouts, but it's pretty easy to combine those two to get a 2x4 grid to fit 8 cards per page. those cards are then written out to an SVG file that looks like this:

From there it's a quick trip through the Inkscape command line to converting that 7" x 10" SVG into a 7" x 10" PDF:
inkscape -f output\page0.svg -A output\page0.pdf

And once I have a bunch of those files I can cat them all together with pdftk:
pdftk output\*.pdf cat output output\allcards.pdf

Then you can print the whole thing in a PDF reader. In FoxIt I have to make sure to turn off scaling and auto-center on an 8.5"x11.0" page when I print:

Hopefully that brain dump is useful to somebody! I sure wish I'd had something like this to read yesterday morning.

Using heightmaps in the Terrain component in Unity 4.2

This weekend I spent some time playing around with procedural heightmap generation in Unity. Since the documentation for these APIs is pretty sparse, I wanted to post about all the traps I fell into and maybe save other people some time.

Here’s the code I ended up with. ChunkId.chunkSize is a Vector2 that contains {100, 100}. Perlin is a noise generator from this LibNoise port to Unity. This is a method on a custom component I added to each of my Terrain GameObjects.

public void UpdateHeight( Perlin noise, float height )
Terrain terrain = gameObject.GetComponent();
TerrainData terData = terrain.terrainData;
Vector3 pos = gameObject.transform.localPosition;

float[,] rHeight = new float[terData.heightmapWidth, terData.heightmapHeight];

float xStart = (float)id.x * ChunkId.chunkSize.x;
float yStart = (float)id.y * ChunkId.chunkSize.y;
float xStep = ChunkId.chunkSize.x / (float)(terData.heightmapWidth - 1 );
float yStep = ChunkId.chunkSize.y / (float)(terData.heightmapHeight - 1);
for( int x = 0; x< terData.heightmapWidth; x++ )
for( int y=0; y< terData.heightmapHeight; y++ )
float xCurrent = xStart + (float)x * xStep;
float yCurrent = yStart + (float)y * yStep;
double v = noise.GetValue( xCurrent, yCurrent, 0 );
rHeight[ y, x ] = (float)(v + 1.0) * 0.5f;

terData.SetHeights( 0, 0, rHeight );

When I ran the initial version of this code, I ended up with gaps all over the place wherever two terrain chunks met. These are all the things I had to learn to fix those gaps:

Trap #1: Heightmap data is duplicated on the edges

This should have been obvious. It was also an easy fix. That's why there is a -1 in this code:

float xStep = ChunkId.chunkSize.x / (float)(terData.heightmapWidth - 1 );
float yStep = ChunkId.chunkSize.y / (float)(terData.heightmapHeight - 1);

Trap #2: Height values must be in the range 0 to 1
The values you put into your heightmap aren't actually "height" values even though they're floats. They are the parameter that will let the terrain system scale the height between 0 and the "Terrain Height" parameter in the editor. Because the noise generator produces roughly -1 to 1 values, I had to scale them like this:

rHeight[ y, x ] = (float)(v + 1.0) * 0.5f;

Values above 1 or below 0 are clamped to 1 or 0 respectively so if you pass them in you will end up with some flattened sections of terrain.

Trap #3: Height arrays are Column-Major

You might have noticed something else that's funny about the line of code I just listed:

rHeight[ y, x ] = (float)(v + 1.0) * 0.5f;

For most of the time I was fighting with this I had the x and y reversed because I assumed that the 2D array expected by SetHeights was row-major just like everything else in the universe. This is the biggest documentation hole I ran into and what cost me the most time.

Trap #4: The "top" neighbor is in the positive Z direction

In order to make the LODs and normals on the transitions between terrain chunks work Unity keeps track of which terrains border which other terrains. You tell the engine about these relationships via the SetNeighbors call. For no particularly good reason I was expecting that left was -x, right was +x, top was -z and bottom was +z. Turns out that I had two of those backwards:

  • Left: -x
  • Right: +x
  • Top: +z
  • Bottom: -z

Simulating Locator Beacons

Recently I’ve been thinking a lot about how a locator system that satisfies my own requirements could be put together. My current approach is a system of beacons in fixed positions that communicate with each other and broadcast to receivers via radio. This is basically the same system described in Rainbow’s End

Both receivers and beacons use transmit time to compute distance. Beacons use those distances (and the knowledge that they are all actually in fixed positions) to build an accurate mesh of their relative positions to each other. Those relative positions are fixed to absolute positions by including beacons with very precise known positions in the network.

I wanted to see if I could figure out how to actually find beacon positions relatively and then absolutely based on those few known beacons, so I built a simulator. The beacons use a mass and spring system to push and pull each other around into usually the right positions. This has the advantage of working without any central authority computing beacon positions. If its neighbors are trustworthy, each beacon can “move” itself around until it finds a stable location. This simulation is in 2D, but there’s nothing preventing it from working just as well in 3D.

The simulator is in Javascript, so I’ve just embedded it below. You can find some instructions on how to use it if you scroll down. Comments appreciated!

Select a beacon type from the UI and click anywhere in the frame to add a beacon at that location. Or click Random to add a new beacon.

Units are in pixels, except for the mass and spring weight values which are in Foozles and Smurfs respectively.

You can also use these key equivalents:
k: select Known
u: select Unknown
g: select GPS
r: place a beacon of the selected type at a random location

50 things I never need to hear at another conference

  1. Korea is the future. They are five years ahead of us and where Korea goes, the rest of the world will follow.  (I have been hearing this for at least five years. )
  2. Free to play with micro transactions is the one true business model.
  3. Client downloads are death.
  4. We must look beyond the core gamer audience and embrace more casual players.
  5. Women are 50% of the audience.
  6. Don’t trust the client, it is in the hands of the enemy.
  7. You game is a service.
  8. MMOs are hard. No, they’re really really hard. Seriously. You can’t possibly imagine how hard they are.
  9. Runescape is the second biggest MMO and is the one you should really watch.
  10. Club Penguin is huge and is the one you should really watch.
  11. Lineage is huge in Asia and is the one you should really watch. (These days it’s actually more likely to be ZT Online or some other game in China.)
  12. Flash is the best platform to build your MMO on.
  13. Web games are cheesy and no core gamer will ever play them.
  14. Rudy’s has the best BBQ in Austin.  No, County Line is better.  Are you kidding me?  It’s obviously The Salt Lick.
  15. The game industry is bigger than Hollywood.
  16. Triple-A MMOs are a dead end. WoW is impossible to compete with.
  17. Game X is going to take the top spot from WoW.
  18. Games cost so much to make now that the industry is about to collapse under its own weight.
  19. MMOs are just like MUDs and you should all learn the lessons MUDs learned X years ago.  (To be fair, I don’t think I’ve actually heard this one in a few years.)
  20. All of these things happened in UO. Why won’t you people learn from UO?
  21. The community around your game is incredibly important and you should take care of them.
  22. Your players have no idea what they want. Don’t believe anything they say.
  23. Forums are very important.
  24. Don’t believe anything you read on forums.
  25. Launch is just the beginning. The real work comes after launch.
  26. Metrics, metrics, metrics.  Record everything!
  27. Don’t record too much with your metrics. Too much data is just as useless as too little data.
  28. Some people spend CRAZY amounts of money via micro-transactions
  29. MMOs on consoles are the Next Big Thing.
  30. Casual games are going to save the PC market
  31. MMOs are going to save the PC market
  32. My background in economics tells me…
  33. WoW is a wonderful thing for the industry because of the way they expanded the market.
  34. WoW has set expectations so high that you can’t make an MMO for less than X million dollars. (Where X>=30)
  35. Person X is a jerk. Let me tell you this funny story about…
  36. Company Y is so clueless that they will never put out a successful game
  37. Fantasy is where it’s at! MMOs just don’t work as well in other genres.
  38. Fantasy has been done. Players want us to move on to other genres.
  39. There’s so much money to be made in Asia! Just make sure you internationalize your game first.
  40. Gamers in Asia demand click to move so they can smoke while they play.
  41. Players are going to trade stuff for real money no matter what you do. You might as well embrace it.
  42. RMT causes huge amounts of fraud.
  43. Gold spam is impossible to stop.
  44. Our startup is the next big thing in MMOs.  Just look at this giant pile of money we raised!
  45. Game development is all about iteration. Waterfall doesn’t work.
  46. There’s this guy named Richard Bartle who proposed dividing players into four types…
  47. You can’t use scripting languages in games. They’re way too slow.
  48. Writing all your code in C++ is stupid.
  49. Launch early, launch often.
  50. You only get to launch once.
This year it was obvious to me that I’ve hit the Austin GDC level cap. Fortunately that means I have moved on to the conference elder game and learn far more interesting things speaking and engaging in deep hallway conversations.
What about you?  What things are you sick of hearing in conference presentations?

Useless magnetic sensors

After getting roll and pitch working through the Kalman filter, this week I wanted to move on to yaw. Too bad the magnetic sensors in the SparkFun IMU don’t actually work:

While I was recording those values the IMU rotated a full 360 degrees and was even turned upside down.  MagZ should have inverted when it turned upside down, at least.  I guess there is enough stuff going on inside the IMU that it mostly detects itself.

I tried using just the gyros to track yaw by dead reckoning, but they drift enough that the fish are turned 90 degrees after about ten seconds. I’ll have to wait to track yaw until I can get a magnetic compas that works or have vision-based tracking working well enough to use it to compensate for the drift.