In Planet Jumpers' third phase of gameplay, you fly (or drift) through an endless universe. The goal, to get further than anyone else has ever been from the starting point. As of writing, that records belongs to a gas planet roughly 18 million miles away named… (checks notes)… uh… that’s not that important right now… More about the leaderboard in another post. This post is to explain how the universe generation works!

This post will be more interesting if you play Planet Jumpers and experience the “Launch” phase of the game for yourself!

Here’s a zoomed out view of a new round with freshly generated galaxies! That tiny planet in the middle? That’s the starting planet that everyone starts on each time. pj_universe.PNG

So how does the game build this? Well let’s start at the smallest piece, a single planet! Every planet is generated with four primary attributes.

  • Gravity
  • Radius
  • Biome
  • Atmosphere Toxicity

These different attributes give the planet a different look in space!

planet_comps.png

As you can see in the image above, these properties allow planets to have a wide variety of appearances! The player can even actively avoid toxic planets by paying attention to the slight color changes.

All of these properties are generated at random, with position being calculated during galaxy creation. Here’s the code for planet creation.

func generatePlanet():
	return {
		radius = rand_range(16,64),
		gravity = rand_range(20, 80),
		biome = PlanetBiome.values()[randi()%PlanetBiome.values().size()],
		atmosphereToxicity = rand_range(1,MAX_ATMO_TOXIC)
	}

So what is galaxy creation, and how does it work? Well, here is a simple galaxy, freshly generated.

GALAXY.PNG

Every galaxy has a star in the center of it. When a galaxy is created, it picks a random number of planets between 1 and 10. The first planet is placed 150 units away from the center, but the direction is picked at random. After it places this first random planet, it then picks a random number between 2 and 5, and multiplies it by the first planet’s radius. This is then added to the first planet’s distance to get the distance for the next planet. The direction in which the planet is placed, is again generated at random.

This is then repeated for each planet that is placed. The planets are limited to be at most 500 units away from center, causing planets to sometimes stack up at the edge (Not intentional on my part, something that I wish I had more time to tune!). Below is a diagram explaining the placements.

galaxy_planet_dg.png

Here is the code that generates a galaxy.

func generateGalaxy(x,y,planets = 10, maxPlanetDistance = 500):
	var sun = sunScene.instance()
	sun.global_position = Vector2(x,y)
	sun.planetsOrbiting = planets
	$Planets.add_child(sun)
		
	
	var lastPlanetDistance = 150
	for i in range(planets):
		var randomDegree = randi()%360
		var randomRad = deg2rad(randomDegree)
		var planetVector = Vector2(cos(randomRad), sin(randomRad))		
		var newPlanet = newPlanetNode(x,y)			
		var planetDistance = lastPlanetDistance + rand_range(2*newPlanet.planetRadius, 5*newPlanet.planetRadius)
		if planetDistance > maxPlanetDistance:
			planetDistance = maxPlanetDistance
		
		newPlanet.global_position += planetVector.normalized()*planetDistance
		$Planets.add_child(newPlanet)
		lastPlanetDistance = planetDistance

So now that we see how planets are placed within galaxies, how do galaxies get placed? Well galaxies use a very similar approach. When you first launch, the game generates what I’ve called a “space area”. This area consists of x number of rings of galaxies. Due to performance limitations, the game currently only generates 1 ring of galaxies at a time. These rings are generated with a center on the current player position. Every ring is 1000 units apart. Each ring has a specific number of galaxies within it, the calculation for this is below.

var galaxyCount = 4 + floor(2 * distance / ringDistance)

Since I originally wanted multiple rings to generate at a time, the calculation takes into account the current ring’s distance from the player. Since there is only ever 1 ring at a time, the current version of the game always generates 8 galaxies in one ring.

Once the galaxy counts are calculated, it picks a random degree to start with (0-360). It then takes 360, divides it by the number of galaxies, and that becomes the degree of difference between each galaxy in the ring. This makes it so that the ring has evenly spaced galaxies placed around it.

Here is the code for generating an area of space.

func generateSpace(centralPosition):
	var ringsToGenerate = 1
	var ringDistance = 1000
	var maxGenerationDistance = ringDistance*ringsToGenerate
	var distance = ringDistance
	while distance <= maxGenerationDistance:
		var angleDeg = randi()%360
		var galaxyCount = 4 + floor(2 * distance / ringDistance)
		for i in range(galaxyCount):
			angleDeg += 360/galaxyCount*i
			var angleRad = deg2rad(angleDeg)
			var position = centralPosition + (Vector2(cos(angleRad), sin(angleRad)) * distance)
			generateGalaxy(position.x, position.y, randi()%10+1)
		distance += ringDistance

Below is a diagram showing roughly how galaxies are placed around a ring.

generation_deg.png

The final question is, how does the game handle when you go outside of the galaxy ring? The answer to that is somewhat disappointing I’m afraid. Due to time constraints, I wasn’t able spend time figuring out a way to map areas that have previously had planets generated. I also didn’t want to risk the player getting lag from too many planets. So my solution was to calculate a “play area”.

A “play area” is simply a bounding box, calculated by looking at the furthest planets in each direction.

Here is a quick illustration of what a “play area” would look like.

playbox.png

When the player steps outside of this box (plus some margin), all planets that are currently in play are destroyed and a brand new galaxy ring is generated around the player. This is unfortunately crude, and causes both stutter during the game and noticeable flashes of planets at times. However, it does ultimately give the feeling of a never ending universe.

To sum things up, the random generation is relatively simple! Planets are placed at random distances on a random angle from the center of galaxies, and galaxies are placed along a radius from the player’s position. With this we can get what feels like random planet placement in all directions. Generating a game each time really does feel unique, and despite a few hiccups and shortcuts I am happy with the result!