3
\$\begingroup\$

I have a 2d game that generates an "infinite" map of tiles based on a "noise" height map. Each time a new "chunk" of 16x16 tiles is loaded, I have code that randomly chooses a "creature" to spawn into the game. Whether or not a creature will spawn on the tile is random, and which creature type is weighted/random.

However, I'd like to take this to the next level and design a creature that can spawn in groups.

I have a few choices:

  • Spawn additional entities in a random set of surrounding tiles. Multi-chunk groups are impossible unless I write code to base spawns off of what already exists next to it.
  • Create a new noise/height-map value that I then use to spawn entities with.

There are pros/cons to each, but I'm hoping someone with more experience can suggest which is better, or a third choice.

\$\endgroup\$
1
\$\begingroup\$

(Sorry, I don't know java, so I'll give my suggestion as if it was C++)

If the enemies that spawn in groups are logically related (ex. swarm of bees or something), you could make a swarm or herd or whatever class: Class Swarm<EnemyType>.

The swarm entity would basically just be a container that then itself spawns a certain number of whatever enemy it is related to (ex. bees) in it's constructor: new Swarm<BeeEntity>. You could even have it randomly choose how many to spawn

I'm not sure if this fits with your current implementation, but I see a few side benefits to it:

1) You could more easily implement group behavior as the individual enemies are collectively grouped by the swarm class. (For example, have the enemies in the group generally stick together, or act differently/more aggressive if they are the last one left).

2) The swarm is a single object, so you wouldn't need to make many changes to your height map.

\$\endgroup\$
1
\$\begingroup\$

In reference to the second choice of basing the groups off of the things next to them, I've done similar in path finding code, although the two may seem pretty dissimilar at first glance. It's a long answer, but it probably suites what you want nicely...

You may want the enemies to come from neighboring tiles. Picking neighboring tiles is probably something you've already precomputed if you do pathfinding, as it's computationally expensive to compute it whenever you need to build a new path.

The idea I have is to pick one random tile in which a Swarm is to begin, then use the neighboring tiles list or functionality you have to pick a neighboring tile. If it's uninhabited, then you can spawn the next one, and keep going until you have boatloads of bees, goblins, or Scientologists.

To do this, I can offer psuedocode. First, if you don't have neighboring tiles, sometime during initialization, you'd want something like this...

public class Tile
{
     // Tile stuff like location...
     private List<Tile> neighbors = new List<Tile>();

     public List<Tile> getNeighbors()
     {
          return this.neighbors;
     }
}

Now, in your level object, you'd want an initialization step that does basically the following, assuming Tile objects are stored in an array of width (width) and height (height), although I know you want to dynamically create the field, so you'd probably have 16 for both values, and create neighbors listings for each. You may also want to go back and connect the previous set of 16x16 to this one too..

public void createNeighbors()
{
     int i = 0;
     int loc = 0;
     int bounds = 0;

     for(i = 0; i < this.width * this.height; i++)
     {
          // Get the tile above...
          // Note that in normal person math, bounds cancels, but in integer math,
          // it actually gives you the start of a row of tiles...
          loc = i - this.width;
          bounds = (i / this.width) * this.width;
          if(loc >= bounds)
               this.tiles[i].neighbors.add(this.tiles[loc]);

          // Get the right tile...
          loc = i + 1;
          bounds = (i + (this.width - (i mod this.width));
          if(loc < bounds)
               this.tiles[i].neighbors.add(this.tiles[loc]);

          // Get the bottom tile...
          loc = i + this.width;
          bounds = this.width * this.height;
          if(loc < bounds)
                this.tiles[i].neighbors.add(this.tiles[loc]);

          // Get the left tile...
          loc = i - 1;
          bounds = (i - (i mod this.width));
          if(loc >= bounds)
                this.tiles[i].neighbors.add(this.tiles[loc]);
     }
}

The point to that bit of code is to precompute the neighbors so you can make an algorithm that searches the neighbors to spawn the next guy in a swarm. Let's say that you want to give a function the number of enemies, and a center tile, and it returns a list of tiles in which each enemy should spawn. That looks a lot like this...

public List<Tile> spawnGroupTiles(Tile start, int number)
{
     List<Tile> group = new List<Tile>();
     Tile current = start;

     if(number <= 0)
           return group;

     for(int i = 0; i < number; i++)
     {
           if(this.isOccupied(current) == false && group.contains(current) == false)
                  group.add(current);
           else
           {
                  Tile next = getNext(current, group, index);

                  if(next == null)
                          return group;

                  group.add(next);
           }

           current = group.last();
     }

     return group;
}

protected Tile getNext(Tile current, List<Tile> group, int index)
{
      List<Tile> neighbors = current.getNeighbors();
      boolean isFound = false;
      int i = 0;

      while(i < neighbors.size() && isFound == false)
      {
           Tile next = neighbors.get(i);

            if(this.isOccupied(next) == false)
                  isFound == true;
            else
                  i++;
      }

      if(isFound)
           return this.tiles[i];

      index--;

      if(index < 0)
          return null;

      return getNext(group.get(index), group, index);
}

This looks complicated, but basically, we're building a search out for unoccupied Tiles. If we can't find one next to current, then we go back one index in our list and try its neighbors, and so on, until we either find an unoccupied tile, or we've exhausted every connected searchable space. It's recursive, and a bit ugly, and I bet there is an error or two, but it should build a rough cluster of spawned enemies once all that is fixed.

For extra credit, it would be nice if it picked randomly from the neighbor list, so that the groups would not build first up, then left, then down, then right, however, I'll leave that one in your able hands.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.