Level Creation and Initial Playtesting

So since the last update, the first level of GhostLight, currently being dubbed ‘Purgatory’ has been fleshed out a little, and undergone some very brief playtesting, this little article is going to be a summary of that process, as well as an update to the current state of the game.

So first of all, I’ve actually made the level, using 2D Toolkit’s tilemap it is literally as simple as selecting the tile from your tileset and ‘painting’ it onto the stage. However, some things need to be taken into account; the way 2D Toolkit creates the levels is it lumps all tiles painted on a certain layer together and creates a hitbox through edge colliders. The issue is if you want tiles to overlap, or if you want background/special tiles, then this doesn’t really work. Thankfully there’s a ‘layers’ option, each layer remains separate from each other, so you can have a layer for player hazards, and tag them appropriately using Unity’s tagging system.

levellayers

The above image shows my current workstation, I use a single layer for Hazards (such as the spikes that can be seen in the level), a platform (Foreground) layer, that the player will actually move on, a foreground effects layer that handles overlapping or transition tiles, (The edges of some tiles have transparent areas, so with this I can overlay them over other tiles to provide a nicer transition), and two background layers for similar reasons.

I had a quick playtest of my admittedly short level, and so did my girlfriend, and immediately the first problem with my desired mechanics arose. So originally, I had planned to introduce a sequential jumping mechanic, similar to the 3D Mario games, where pressing the jump button as the player landed would propel them further, and higher. The level was designed around this mechanic, which may have been a bad idea in retrospect, as trying to navigate the level with this mechanic proved very troubling.

It just didn’t feel natural for a 2D game to move that way, it felt like the control listener was too inaccurate to provide a tight window for the double jump that didn’t seem ‘cheap’ or inconsistent. With a little bit of tweaking I instead swapped it out for a more traditional double jump.

However there were more issues arising from what I thought would be a very basic and simple implementation; making long jumps felt very difficult, not in the sense of timing, but due to the nature of how I’d programmed the jumps (and how it detected whether the player was grounded), trying to jump at the edge of a ledge whilst running nearly always failed, the player would always jump just a little too late, taking away the important aspect of control and fairness required in a 2D platformer.

I came up with a solution, inspired from Juicy Beast’s similar implementation in their game “Toto Temple Deluxe!” where is the player has just left the ground and has not yet used their initial jump, then instead of immediately using their double jump, treat them as if they are still grounded. Let them use their first jump despite being just off of the ground, and the results were great! The player felt much more in control and the game felt much more forgiving in this element.

(By the way, Juice Beast has their own Dev Blog, and wrote about their implementation of the above mechanic here! I highly recommend you check it out and read through their series about Toto Temple Deluxe!)

I’d like to go into a bit of technical depth in how I implemented this, showing some of my code, so that anyone who’s not quite sure about how to make something like this can hopefully learn something!

//The following code is within the Control Handler function, that deals with all the basic player controls
//Unity has built in methods to detect certain Inputs.
//If the player has pressed the jump button, the character is grounded or they haven't double jumped, and the counter to allow them to jump hasn't expired
if (Input.GetKeyDown(KeyCode.Space) && (isGrounded || (!hasDoubleJumped || lastOnLandCounter > 0.0f)))
{
        //The timer for when the player was last on land is told to begin by setting the buffer variable to true
	lastOnLandBuffer = true;
	jumpPressedTimer = jumpPressedTimerMax;
        
        //If the land buffer timer has ended, and the player isn't grounded, it's considered a double jump
	if (!isGrounded && lastOnLandCounter <= 0.0f)
	{
		hasDoubleJumped = true;
	}
        
        //If the counter is still running, it's set to zero, and the player is vaulted along the positive y axis by our jump amount
	lastOnLandCounter = 0.0f;
	GetComponent().velocity = new Vector2(GetComponent().velocity.x, jumpHeight);
}

//This is called during the Fixed Update function a set amount of times each second
void LastOnLand()
{
	//A counter to check if the player has just fallen off the land or not, allowing them to jump still
	if (!lastOnLandBuffer && !isGrounded)
	{
		lastOnLandCounter = lastOnLandMax;
		lastOnLandBuffer = true;
	}

	if (lastOnLandCounter > 0.0f) 
        {
		lastOnLandCounter--;
	}
}

If the above just seems like jibberish to you don’t worry about it! Although please let me know how you feel about code examples in the comments to I know whether to include them in future 🙂

Switch-activated doors have also been added but I think this article is long enough, so next time 😉
Hope you all enjoyed!