Samstag, 22. November 2014

Generating a Battle Space - Procedural Map Generation Using the Dungeon Building Algorithm

For the boarding battles in Infinity Raider I implemented a variation of the Dungeon-Building Algotihm. The basic idea of the Dungeon-Building Algorithm is to place a room in the center of the map (everything else is a wall) and then randomly pick walls connected with floor tiles and place random features (rooms, corridors etc.). In my implementation I placed the start room at the top of the map ("the bridge") and only used half the width of the map, so it can later be mirrored along the vertical axis. The only features used are rooms, larger rooms are created if rooms happen to lie next to each other. This combining of rooms only happens, because the implementation ignores the walls of rooms when checking for overlap. The hardest part to figure out probably is the detection of the floor tile and that a new feature needs to be build into the opposite direction. The methods "MirrorMap", "GenerateUnits" and "ParseMap" are ommited in the code listing. "MirrorMap" mirrors the map along the vertical axis. "GenerateUnits" randomly places units for the player and for the AI. "ParseMap" creates the needed GameObjects from the bool array.

using UnityEngine;
using System.Collections.Generic;

public static class MapGenerator {

private class Room
{
  public int width;
  public int height;
  public Vector2 position;

  public Room(int w, int h, Vector2 pos)
  {
    width = w;
    height = h;
    position = pos;
  }
}

public static GameObject[,] GenerateSpaceShip()
{
  int width = 30;
  int height = 30;

  bool[,] layout = new bool[width,height];

  Room bridge = new Room (5, 5, 
                          new Vector2 (width / 2, height - 3));
  AddRoomToMap (bridge, layout);

  int tries = 10000;
  for (int i = 0; i < tries; i++)
  {
    //pick random wall
    int x = Random.Range(0,width/2);
    int y = Random.Range(0,height);

    if(!layout[x,y])
    {
      bool left = false;
      bool right = false;
      bool top = false;
      bool bottom = false;
      //check for floor in neighbourhood
      if(x -1 >= 0)
      {
        right = layout[x-1,y];
      }
      if(x + 1 < width/2)
      {
        left = layout[x+1,y];
      }
      if(y - 1 >= 0)
      {
        top = layout[x,y-1];
      }
      if(y + 1 < height)
      {
        bottom = layout[x,y+1];
      }

      Vector2 roomPos = Vector2.one;
      int roomWidth = Random.Range(3,6);
      int roomHeight = Random.Range(3,6);

      if(left)
      {
        roomPos = new Vector2(x-1 - roomWidth/2,y);
      }
      else if(right)
      {
        roomPos = new Vector2(x + 1 + roomWidth/2,y);
      }
      else if(bottom)
      {
 roomPos = new Vector2(x, y-1 - roomHeight/2);
      }
      else if(top)
      {
        roomPos = new Vector2(x, y+ 1 + roomHeight/2);
      }

      if(!roomPos.Equals(Vector2.one))
      {
 Room nextRoom = new Room(roomWidth,roomHeight,roomPos);
 if(TestRoomPlacement(nextRoom,layout))
 {
   layout[x,y] = true;
   AddRoomToMap(nextRoom,layout);
 }
      }
    }
  }
  MirrorMap (layout);
  GenerateUnits (layout,10);
  return ParseMap(layout);
}

private static void AddRoomToMap(Room r, bool[,] map)
{
  Vector2 pos = r.position;
  for (int x =  (int)pos.x - r.width / 2;
           x <= (int)pos.x + r.width /2; x++) 
  {
    for(int y = (int)pos.y - r.height / 2;
            y <= (int)pos.y + r.height/2; y++)
    {
      map[x,y] = true;
    }
  }
}

private static bool TestRoomPlacement(Room r, bool[,]map)
{
  Vector2 pos = r.position;
  int width = map.GetLength (0);
  int height = map.GetLength (1);
  for (int x =  (int)pos.x - r.width / 2;
           x <= (int)pos.x + r.width /2; x++) 
  {
    for(int y = (int)pos.y - r.height / 2;
            y <= (int)pos.y + r.height/2; y++)
    {
      if(x < 0 || x >= width/2 || y < 0 || y >= height || map[x,y])
      {
        return false;
      }
    }
  }
  return true;
}

}
Where to go from here? It might be a good idea to replace the bool array with an int array and then post process the map with a cellular automaton (or something else) to place enemies, objects, decorations etc. Also the rooms should be stylized in some way, so that the player knows what kind of environment it is supposed to be. Thanks for reading and happy experimenting with your own space ships (or dungeons or ...)

Procedural Sprites - Space Ships in Infinity Raider

I'm pretty bad at drawing so I tried to let the computer do the drawing of the space ships for Infinity Raider. The basic idea was to split up the space ships into multiple components. Each component would be drawn onto a seperate layer and in the end everything would be combined into a single sprite that could be used by the game. The code in this blogpost is not optimized in any way, use it at your on risk:) In the code shown below the "ship" texture is the texture holding the completed texture, while "cockpit", "weapons", "body" and "engine" hold the different parts. "OutlineTexture", "ClearTexture" and "CopyTexture" are helper functions. "ClearTexture" sets every pixel to transparent black (0f,0f,0f,0f). Unity's default for new created textures is a semitransparent white. "CopyTexture" copies opaque pixels from a source texture to a target texture. This is used to paint the different layers on top of each other. "OutlineTexture" simply draws a black outline around the opaque pixels of a texture.

public static Sprite GenerateShip()
{
  //Create the different ship parts as seperate layers,
  //then bake them into single texture
  Texture2D cockpit = GenerateCockpit ();
  cockpit = OutlineTexture (cockpit);
  Texture2D weapons = GenerateWeapons ();
  weapons = OutlineTexture (weapons);
  Texture2D body = GenerateBody ();
  body = OutlineTexture (body);
  Texture2D engine = GenerateEngine ();
  engine = OutlineTexture (engine);

  Texture2D ship = new Texture2D (width,height);
  ship = ClearTexture (ship);
  ship = CopyTexture (ship, body);
  ship = CopyTexture (ship, engine);
  ship = CopyTexture (ship, cockpit);
  ship = CopyTexture (ship, weapons);
  return Sprite.Create (ship, new Rect (0,0,width,height),
                        new Vector2 (0.5f, 0.5f));  
}
The "body" is maybe the most interesting part, as it can be one of several different shapes. The other parts only vary in color. The following code block shows the code for the body and the diamond as one example for a shape. "MirrorTexture" is another helper function used to mirror textures along the vertical axis.

private static Texture2D GenerateBody()
{
  Texture2D body = new Texture2D (width, height);
  body = ClearTexture (body);

  //generate a random shape
  int shape = Random.Range (1, 4);
  switch (shape)
  {
    case 1:{body = GenerateDiamond(body);break;}
    case 2:{body = GenerateTriangle(body);break;}
    case 3:{body = GenerateCircle(body);break;}
    default:{break;}
  }

  return body;
}

private static Texture2D GenerateDiamond(Texture2D input)
{
  Color bodyColor = new Color (Random.Range (0f, 1f),
                               Random.Range (0f, 1f),
                               Random.Range (0f, 1f), 1f);
  Vector2 seed = new Vector2 (width / 2 - 1, height / 2 - 1);
  int maxDistance = 31;
  for (int x = 0; x < width / 2; x++)
  {
    for(int y = 0; y < height; y++)
    {
      if(Mathf.Abs(x - seed.x)+Mathf.Abs(y-seed.y) <= maxDistance)
      {
        input.SetPixel(x,y,bodyColor);
      }
    }
  }
  input = MirrorTexture (input);
  return input;
}
The space ships created this way look really simple, but are still better than my hand drawings:) If you decide to create your own sprite generator (for space ships or whatever else you need) there's a lot of things that could be improved / played with. It would be more efficient to use a Color array instead of texture objects and to only create a texture when your sprite is complete. Also you could experiment with different sizes and offsets for the different layers. Another thing that could be done is adding fine details and lighting. Thanks fors reading and happy experimenting.

Sonntag, 16. November 2014

Infinity Raider - Post Mortem

I "shipped" my first game today: Infinity Raider. It's a game about space pirates which I created for the procjam 2014. Besides of being the first shipped game it was also my first game jam. There are even a few more "firsts" like working with the new Unity UI, adding sound effects made with Bfxr or procedurally making sprites (and those sprites look even better than sprites I paint by hand).

The optional theme of the jam was "infinity" and one of the first things that came to mind was "space" followed by "space pirates". As to be exspected of a beginner, I overscoped my project. The gameplay is divided into organizing your ship/crew, battling other spaceships and boarding said ships after successful battles.

The part of organizing ship and crew was greatly cut out, the only thing remaining is the option to buy ship parts (which isn't really necessary considering how powerful you are compared to your adversaries).

The space battles were greatly inspired by the G-Castle battles in Mugen Souls (Z) and a certain story about space pirates. Basically you try to disarm the opposing ship either using weapons or electronic warfare. If I had more time I could have experimented with different weapons, maneuvers and a more complex damage model, which for example could allow to target specific ship systems. The sprites for the space ships are procedurally generated. The first version looked similar to aliens from the original NES metroid while the second version (which is part of the release) atleast somewhat resembles spaceships.

The boarding battles are classical turnbased strategy and can be won either by defeating all enemies or by conquering the bridge (the room in the top center). The maps are procedural generated using a variation of the dungeon building algorithm. After fighting with the new Unity UI for a while, I decided to scrap the equipment feature. Originally different kinds of equipment should have been available, to increase tactical options. Also the "Artificial Intelligence" doens't really deserve the "Intelligence", as the AI only reacts when enemies come close enough. In a worst case scenario the map is created in a way, where the bridge can be captured without fighting a single enemy, removing any challange from the game.

All in all I'm happy with my results. During the jam there were moments where I wasn't sure, if I could deliver a game before the end of the deadline. There's still a lot to do, if I should return to working on Infinity Raider, e.g. tying the different gameplay elements together and adding more meaning to the procedural generation parts. If you see a ship you should be able to tell if it's a warship ("Retreat right now!") or if it's a trade ship ("Tonight we gonna party!"). Also the enemies encountered, rooms created and the size of the boarding maps should reflect the kind of ship your boarding.

Thanks for reading and happy plundering!