Boxes Full Of Bones

Project_catalan is still a game that doesn’t really know what it wants to be when it grows up, but that hasn’t prevented me from using it as a platform on which to try out new things I’m learning to do. Once I had established the game board as a set of tiles of varying colors (representing terrain types like grass, sand, water, etc.) I became interested in the idea of generating a new version of the board each launch. This is less like sophisticated procedural generation and more like plucking hexagons from the Catan box, but… baby steps.

The method here takes place in three steps. First, a full grid of grass tiles is laid down. Then, starting from one edge, a line of river tiles is placed in a random meandering line, replacing grass as they go. Finally, the tiles on either side of each river tile are replaced with sandy banks. The whole thing is somewhat unwieldy but here’s the river function as an example of what’s being done:

void MakeRivers()
{
 //this could get a little tricky
 //we want to make a river that cuts across the whole grid in a meandering fashion
 //we'll assume at the moment that we only ever want one river
 //first: is this a vertical or a horizontal river?

 int riverOrientation = Random.Range(0, 2);
 if (riverOrientation == 0)
 {
  Debug.Log ("Vertical river");
 }
 else
 {
  Debug.Log ("Horizontal river");
 }

 //now, where does the river start?
 //at a random point along either the top of the left side
 Vector2 riverStartTile;
 int startRow;
 int startColumn;
 if (riverOrientation == 0)
 {
  //start on final row, then randomize column
  startRow = rows - 1;
  startColumn = Random.Range(0, columns);
 }
 else
 {
  //start on final column, then randomize rows
  startColumn = 0;
  startRow = Random.Range(0, rows);
 }

 //now that we have the row/column index, we need to match that up with the index into 
 //tiles[] of the tile actually living in that spot
 //first let's get the world pos of where we think we actually are
 Debug.Log("starting our river at row " + startRow + " , column " + startColumn);
 penPoint = new Vector3(startColumn * width -16f, 0f, startRow * length -16f);
 Debug.Log ("penPoint is " + penPoint);

 //since we're naming the tiles with their row/column numbers, we can use that to find and destory them
 //let's try a straight river first I guess
 int currentRow = startRow;
 int currentColumn = startColumn;

 if(riverOrientation == 0)
 {
  for(int i = startRow; i>= 0 ; i--)
  {
   string tileName = "grassTile_"+i+"_"+currentColumn;
   GameObject tileObject = GameObject.Find(tileName);
   //Debug.Log ("Looking for " + tileName);
   if(!tileObject)
   {
    break;
   }
   Vector3 replacePosition = tileObject.transform.position;
   Quaternion replaceRotation = tileObject.transform.rotation;
   Destroy(tileObject);
   GameObject waterTile = Instantiate(tileTemplate_water, replacePosition, replaceRotation) as GameObject;
   waterTile.transform.parent = tileMapObject.transform;
   waterTile.name = "waterTile_"+i+"_"+currentColumn;

   //bend?
   int bend = Random.Range(0, 5);
   if (bend == 3)
   {
    //ok, we're bending, let's determine how much
    int bendAmt = Random.Range(1, maxRiverBend+1);
    //find bend direction
    int coinFlip = Random.Range(0, 2);

    for(int k = 0; k <= bendAmt; k++)
    {
     //here's where we hop to another column
     //east or west?
     if (coinFlip == 1) //east
     {
      currentColumn++;
     }
     else //west
     {
      currentColumn--;
     }

     //now replace the bend tile 
     GameObject bendGrassTile = GameObject.Find ("grassTile_"+i+"_"+currentColumn);
     if(!bendGrassTile)
     {
      break;
     } 
     Vector3 bendReplacePosition = bendGrassTile.transform.position;
     Quaternion bendReplaceRotation = bendGrassTile.transform.rotation;
     Destroy(bendGrassTile);
     GameObject bendWaterTile = Instantiate(tileTemplate_water, bendReplacePosition, bendReplaceRotation) as GameObject;
     bendWaterTile.transform.parent = tileMapObject.transform;
     bendWaterTile.name = "waterTile_"+i+"_"+currentColumn;
    }
   }
  }
 }
 if(riverOrientation == 1)
 {
  for(int i = startColumn; i < columns; i++)
  {
   string tileName = "grassTile_"+currentRow+"_"+i;
   GameObject tileObject = GameObject.Find(tileName);
   //Debug.Log ("Looking for " + tileName);
   if(!tileObject)
   {
    break;
   }
   Vector3 replacePosition = tileObject.transform.position;
   Quaternion replaceRotation = tileObject.transform.rotation;
   Destroy(tileObject);
   GameObject waterTile = Instantiate(tileTemplate_water, replacePosition, replaceRotation) as GameObject;
   waterTile.transform.parent = tileMapObject.transform;
   waterTile.name = "waterTile_"+currentRow+"_"+i;
 
   //bend?
   int bend = Random.Range(0, 5);
   if (bend == 3)
   {
   
    //ok, we're bending, let's determine how much
    int bendAmt = Random.Range(1, maxRiverBend+1);
    //find bend direction
    int coinFlip = Random.Range(0, 2);

    for(int k = 0; k <= bendAmt; k++)
    {
     //hop to another row
     //north or south?

     if (coinFlip == 1) //north
     {
      currentRow++;
     }
     else //south
     {
      currentRow--;
     }

     //now replace the bend tile 
     GameObject bendGrassTile = GameObject.Find ("grassTile_"+currentRow+"_"+i);
     if(!bendGrassTile)
     {
      break;
     } 
     Vector3 bendReplacePosition = bendGrassTile.transform.position;
     Quaternion bendReplaceRotation = bendGrassTile.transform.rotation;
     Destroy(bendGrassTile);
     GameObject bendWaterTile = Instantiate(tileTemplate_water, bendReplacePosition, bendReplaceRotation) as GameObject;
     bendWaterTile.transform.parent = tileMapObject.transform;
     bendWaterTile.name = "waterTile_"+currentRow+"_"+i;
    }
   }
  }
 }
}

This was the only work I did on this over the winter, as project petrov was monopolizing most of my time (see previous post). I returned to it late last month, as it seemed like a perfect test bed for my latest preoccupation: 3D modeling and animation.

I had done some hands-on work with Maya and 3DS Max at various studios, mostly in the context of testing, and I managed to find my way around at a basic level, but these programs are a little too expensive for home use, so I approached Blender, and bounced off, hard. My first crack at it was probably about a year ago, and every so often I would return, a tutorial in hand, and try again, only to end up sliding face first down the learning curve like Wile E. Coyote. In a sense I had been spoiled, as Maya is quite friendly and sensible in terms of its user interface. Blender, by contrast, is full of obscure keyboard combinations, context-sensitive menus, and many different modes the main editor can be in, all of which respond differently to those commands and menus. Ultimately it’s like any other piece of complex software: if you bang your head against it repeatedly, you’ll eventually realize that there are one or two small techniques you’ve absorbed and become comfortable with. You can then use those as a beachhead and campsite from which to explore more daunting areas. Eventually you may earn enough hard-fought victories to ascend the mightiest peaks, breathe that rarefied air, and proudly don the mantle of “beginner”.

Jokes aside, 3D modeling (not to mention rigging, skinning, and the rest) is a discipline much like programming, in that it’s easy to fool yourself into thinking you know something about it, when you really don’t. A forum post I read somewhere described the gap between a bit of functional programming knowledge and the ability to contribute meaningfully in a programming role at a serious studio as being somewhat like the gap between being able to make yourself dinner and being able to work as a sous-chef at a busy high-end restaurant. I think this is spot on, it applies to many game studio roles, and it’s a good reminder not to get cocky. There is always much more to learn.

My goal in this instance was to add another prototyping tool to my kit. Many of the game ideas I jot down in notebooks require animated 3D models, that is to say, little people moving around. If I couldn’t at least rough in this sort of thing, there was no hope. So, I affixed my pitons to the face of Mt. Blender and started climbing.

This is boxman, a Blender model composed of a bunch of cubes joined together. Boneheadedly simple, not much to look at, and in this form totally useless except as a mannequin. I could import him into Unity, but he would just stand there motionless.

This is boxman’s armature, made of a series of “bones” that control his movements. The model is made up of vertices, points in 3D space, that together define the shapes of the boxes. Rigging and skinning a model means that each vertex must be assigned to a bone, so that when the bone moves, its assigned set of vertices are moved along with the bone, in a process called mesh deformation. Since boxman only has a couple dozen vertices, this is about the simplest case you could hope for. Making a realistic-looking character model move realistically involves assigning thousands and thousands of vertices and carefully “weighting” them to control how much deformation the bones can do, to say nothing of constructing the actual animations, transitioning seamlessly between them, and making them responsive to player input. I have a tremendous amount of respect and admiration for the people who do this work for AAA games. Have a look at something like the combat moves in Shadow Of Mordor and it’s quite clear that that this is as painstaking and complex a combination of skill and art as anything a programmer might face.

Here’s boxman getting his walk on via Unity’s Mechanim animation system, which uses a simple keyframe animation interface like you’d find in Maya or even Flash. Of course, you can animate in Blender, then import those animations in Unity and hope you can make them work, but I was quite relieved to discover I didn’t have to. Unity lets you move the bones around in Animation mode using the same transform and rotate widgets you’d use on any other object, so it was a joy to be back in familiar territory. Boxman’s walk cycle is as rudimentary as the rest of him, but the goal here was just to get the basic parts in motion. Pretty can wait. Mechanim also contains a handy state machine interface with variable-based transitions that can be called from script. That is to say, I can give the model a walking state and an idle state, which will flip back and forth depending on if the boolean “isWalking” is true. Then I can make that bool true or false from the movement control script, based on whether the player is touching a key.

Keys bring up the topic of character control, a subject that most projects wrestle with. The original proto-prototype of catalan (known as benko at the time) had a control scheme where you simply clicked the mouse on the point where you wanted to travel, as I had been playing a bunch of Path of Exile at the time and basically wanted to rip off some of the feel of playing that game (no luck whatsoever on that front). I quickly ran into problems because the shooting mechanic involved clicking on monsters, so a slightly misplaced click would send you strolling blithely toward the enemy instead of engaging it, which got frustrating fast. The next iteration was straight-up WASD movement with mouse aim, which was great when the player was an orange cylinder because it didn’t matter which way it was facing. To my horror, the box model totally broke this movement scheme.

It’s an interesting and fundamental point of game design that when you introduce anything from the real world, the player expects (quite reasonably) that it will behave like that thing does in the real world. Players will forgive a lack of verisimilitude when controlling cubes or spheres or even spaceships (sometimes), but gosh darn it we all know how people move. The problem with my controls came about when I implemented the player model rotating based on mouse aim. Having the rotation disconnected from movement was no problem for a cylinder, but for a person, it just felt weird that the right arrow was sometimes rightward movement, and sometimes forward or backward movement, depending on the model rotation. Now, this scheme is perfectly valid, and has a long and storied history in the “twin-stick shooter” genre, going all the way back to Robotron 2084, but in most successful cases the movement is sold to the player as realistic in various ways, the most ingenious being to disconnect the upper half of the body from the lower, and orient the legs to the movement vector and the arms to the aim vector. Elegant, attractive, and still a bit over my head technically. I could see upgrading catalan to that scheme sometime in the future, but for now I needed something simpler.

I had a really cute second idea to make the right and left
arrows move the player laterally to the facing direction, and even learned just enough about cross product math to make it work, but it turned out to also be a
really dumb idea. When the player was facing away from the camera, the arrows worked
beautifully; when the player faced the camera their motions were flipped, which was an
even queasier sensation than before.
In the end, I opted for a dirt simple approach: the forward
and backward arrows (or W and S) work, the side arrows do nothing, the mouse
controls aim and rotation. This still doesn’t feel great, but it’s workable. I
punched up the visuals with some handmade, randomly placed trees and some
simple textures instead of solid colors. I animated the water and even replaced
the “purple refrigerator” enemies with some kind of bear giant thing that
toddles around amusingly.
The whole thing is still a far cry from being fun, there’s nothing much to do and it still lacks any kind of theme or character, but this is really the way I like building things. A lot of designers start with a high concept idea or story and then build the mechanics up underneath that, but I always start with a mental image of a small onscreen interaction. A video game is just a person in front of a screen with a manipulation device (at least until the VR revolution swallows us all whole). If the basic act of interacting with what’s on the screen isn’t engaging second to second, then the best story in the world won’t be any use. Refine the gameplay loop, again and again, make that your primary focus, and don’t worry about whether the player is supposed to be a pirate, a cowpoke, an astronaut, or a QA contractor with dreams of glory. Make the fun, and the rest will come out in the wash.
Coming Up
  • Oh, hello Unreal 4. Hello Source 2. Is this an embarrassment of riches or what?
  • A radar for catalan
  • More prototypes, and maybe I’ll finish those design docs I keep been talking about