Click Hanger – Procedurally generating a mountain

Today we’re proud to show our work on the latest game for the Française Des Jeux (France’s national lottery game)! Made with Isobar and our friends at James Bang.

Click Hanger, the first non lottery game of the FDJ, is a mobile climbing game available on Android and iOS.

Read more on its development process below!

Generation

Seeds:
To generate a different mountain at each game but yet be able to play the same one again if necessary we implemented a seed system (a seed is a string used by different random Generator to give diverse yet predicable value, giving the same seed to a random Generator will give the same ‘random’ result).

First we give a main seed to the generator (hand typed or random value), from this seed we generate a list of lesser important seeds, one per type of generated value.
We use two type of of generator to get random value:

The first one is the System.Random, we instantiate a list of those with for each one a specific seed given, the System.Random object will give us a new random double between 0 and 1 each time we call ‘NextDouble()’

        public static float nextRandom(randomIndex randomIndex)
        {
            return (float)Instance.randoms[(int)randomIndex].NextDouble();
        }

The second one is Open simplex noise, this one is more interesting as from a seed it generate an multi dimential field of values (we used a 2D field). The values given by Open Simplex noise are between -1 and 1.

RandomSeed.cs66→69

        public static float noiseValue(int noiseIndex, float posX, float posY)
        {
            return (float)Instance.noise[noiseIndex].Evaluate(posX, posY) ;
        }

The consequence to that is than asking 2 “near” values in the field will give 2 different yet similar result.
Ex:
(x=2, y=2) => 0.54
(x=1.9, y=2) => 0.49
(x=1.8, y=1.9) => 0.47

These “noise” are generally used to represent random elevation map as their continuous yet random values help generation of moderately rough terrain.

Noise value from black (minimal value) to white (maximum value)
noise as map elevation

 

 

 

 

 

 

Mountain:
Now than we have an efficient way of getting random yet interesting values we can work on building the mountain. For this we decided we will be working block by block: the designer will make a series of block placed in a manner they will add themselves together and create a wall witch will be the side of the mountain we can see.

Generation, the only visible part in game is the right side of the mountain, giving the impression than we are on the edge of the mountain.

The generation follow this scheme:

First we generate a ‘mountain’ object, this mountain contain a list of ‘steps’ (horizontal rows of blocks), the mountain generate the first 15 steps and then will generate more while we climb.

here we can see the differences steps and the mountain changing of direction following the noise values
the mountain as seen in game (the hero stay on the right side of the mountain to see the background)

 

 

 

 

 

 

 

 

Each of these steps contain one block in witch the player will be able to climb. Each step appear behind the last one a little higher and may be more to the left or more to the right, this give the impression than we are climbing from one step to another and as we go forward the mountain look more pyramidal than a simple wall. Each steps heights and position is controlled by a main Open Simplex Noise to get coherent values and next by a pure random to add a little of variety, each block is also individually affected by openSimplexNoise and Random using his position in the step as a reference.

In addition to the positions of steps another addition to the generation is the use of different blocks, small props and biomes:
-Each mountains have one or more biomes.
-Each biomes have multiples blocks and props element (vegetation, small rocks ect …).

When the generation start the mountain uses blocks from the Biome 1 and put vegetation and other random element from this biomes on top of the blocks.
While we climb we may eventually see a biome change (less and less of blocks from Biome 1 are appearing while more and more of blocks from biome 2 are appearing).
The level designer can give information such as “the Biome one Start at 0% of the mountain and end at 10% while the second start at 15% and end at 25%”.
This would result in the mountain having blocks and elements from the Biome 1 during it’s first 10% percent, a mix of elements from biome1&2 inbetween 10% and 15% and then elements only from biomes 2.

Each mountain start with a ground prefab
Not all mountain have squared blocks, the program enable special orientation and blocks for it’s generation
here we can see in game a ongoing transition from grassy blocks to block without any vegetation

 

 

 

 

 

 

 

 

Level Editor Tool

The level data that defines mountain checkpoints and all the variables required to define difficulty and chances for symbols and pickups was edited via an online interface for quick iteration and sharing with our client.

It was created with Unity so that the level data classes would be shared with the game as well as the AnimationCurve used to evaluate the progress of variables as the altitude increased.

In fact, the editor is a simple UI using Vectrosity to edit AnimationCurve values, export everything in json. In the iteration process of Game and Level designers working on the mountains, they were able to modify the mountain data visually and reload a scene simply to see changes.

The file format we went for is simply .json , and since classes are shared between editor and game, exporting and importing is relatively easy. We just had to use JSON.NET’s serialize/deserialize callbacks to handle the serialization and deserialization of the animation curves which allowed simple to complex curve editing. Effectively, these are difficulty curves for the whole mountain.

A mountain is built of several “modules” which are repeatable sections of mountains, defining checkpoint frequency, chances for secondary currency or pickups to appear, and indidividual difficulty curves for symbols to be drawn.

The editor could be run in unity, on the web, and as a standalone file which simply communicated with a php file handling the synchronisation of the level data file. At launch, unless the online version is more recent, the app starts with its own cached version of the level data files.

Gesture Recognizer
The gesture recognizer is based on the $P Point-Cloud algorithm. Some years ago we made a Unity version which is the one used in this game!

Character customisation
In the game, you’re able to choose your gender and appearance. Characters are available to buy and you can swap heads, bodies or legs and create any combo you like, if a full set doesn’t suit you.

To make this happen, we tried a lot of things with the animator, unfortunately cutting and binding back together individual skinned meshes part of the same fbx was hard. We’ve actually tried doing this in code but once a skinned mesh – for example the head – of a seperate character was replacing the initial head, even if all properties like the bone (Transform) it should be connected to was correct, it would no longer animate.

This was however possible by hand in the editor, still not at runtime with code.

It was like Unity was doing some processing in the background, unfortunately we didn’t figure out what it was doing (in hindsight, it’s possible that it was processing the mesh instances and resetting some vertex data). So the easy solution we went for is that the animator exported rigged and animated objects that contained all possible variants of the parts of the characters. As we had only 6 full sets for each gender, it was fairly easy to then activate/deactivate child objects and combine several characters into one… and trigger a particle explosion 🙂

Some Asset store plugins appear to be able to merge skinned meshes together and not lose the animation, unfortunately it was too risky for us to take the time and test these solutions. This would have probably made for a lighter solution as not all possible meshes would need to be loaded at once.

Unity In-App Purchase
Unity released codeless IAP a great way to manage IAP in your game without having to write a lot of code. Top notch feature is being able to specify them in Unity and then export directly on Apple and Google project store. No more dumb copy/paste! It’s pretty neat to see Unity offering this kind of plugin.

Firebase
The backend of this application is powered by Firebase. Firebase, made by Google, became a must have for mobile application nowadays. It includes: analytics, user authentication (via email, Facebook, Twitter, Google…), a powerful NoSQL database, storage, push notifications, remote config, crash reporting… well, a must have!

Most of the games data are stored in the Firebase database: items with pricing and effects’ strength, avatar informations, mountains levels… This structure changed often during the game development, so having a NoSQL database enabled us to be really agile and reading it as a simple json was easy (a Firebase database is finally just a json). When launching the app, we just check if the number version evolves and so parsing data and save it inside a Unity PlayerPrefs. Having it saved as a plain typed structure saved us lot of time when making request thanks to Linq. So this game data part is read only, and we also had users saved data. Here are the database permissions, everything can be read by everyone but the node “users” can only be written if the uid node is the authenticated user, pretty easy isn’t it?

{
  "rules": {
    ".read": true,
    //".write": true
    "users": {
      "$uid": {
        ".write": "$uid === auth.uid"
      }
    }
  }
}

The game uses iCloud and Google Play Saved Games services, to save users data on the same devices OS. We could use the Firebase database to do the same (with the bonus to save user progression accross OSs) via a Facebook Connect, but clients didn’t want to force a Facebook Connect, so we used OSs native services. In the game we have weekly tournaments on special maps with a ticket system (you can play max 3 time in 18 hours) and custom leaderboards emphasizing your friends result. Those score are saved in Firebase, and here is the Firebase server logic (yes you have Cloud functions!!) for providing tickets:

exports.tickets = functions.database.ref('/users/{userId}')
	.onWrite(event => {

		const maxTickets = 3;
		const promises = [];

		// Exit when the data is deleted.
		if (!event.data.exists())
			return;

		// On user creation add tickets and prepare time_counter
		if (!event.data.previous.exists()) {

			promises.push(event.data.ref.child('tickets').set(maxTickets));
			promises.push(event.data.ref.child('time_counter').set(0));

			return Promise.all(promises);
		}

		const previous = event.data.previous.val();
		const original = event.data.val();

		const hour = 3600 * 1000; // sec * ms

		if (original.time == previous.time || previous.tickets == maxTickets || previous.time_counter == undefined || previous.tickets == undefined)
			return;

		let time_counter = previous.time_counter + original.time - previous.time;

		if (time_counter > 18 * hour) {
			
			promises.push(event.data.ref.child('tickets').set(maxTickets));
			promises.push(event.data.ref.child('time_counter').set(0));

		} else if (time_counter > 6 * hour) {

			let addTicket = 0;

			while (time_counter > 6 * hour) {

				time_counter -= 6 * hour;
				++addTicket;
			}

			promises.push(event.data.ref.child('tickets').set(previous.tickets + 1));
			promises.push(event.data.ref.child('time_counter').set(time_counter));

		} else
			promises.push(event.data.ref.child('time_counter').set(time_counter));

		return Promise.all(promises);
	});

Writing to Firebase is mostly asynchronous. Even if Firebase has cloud methods it can be tricky to run some logic with server data, here is some code for saving a quickest time on a level: if “time” already exist on server side, we compare it. Writed that way, we’re sure there won’t be any issues with concurrent writing.

reference.Child("users").Child(newUser.UserId).Child("mountains/" + mountainId).RunTransaction(mutableData => {

	if (time > 1 && (!mutableData.HasChild("time") || (mutableData.HasChild("time") && (double)mutableData.Child("time").Value < time)))
		mutableData.Child("time").Value = time;

	return TransactionResult.Success(mutableData);
});

Note for self, for an other project may be have a look on PlayFab which seems really complete and more game oriented.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.