Adventures in Unity – 1.9 Tighten & Tidy

Official_unity_logo

With the inclusion of Obstacles; there isn’t anything else I really wanted to add to this project.

It still needed a lot of work; pulling all the elements together and polishing the presentation – One final Tighten & Tidy.

But I felt I’d done what I set out to achieve at the start.

I’ve included notes for the larger updates and amendments below – & while there’s a lot more that could be done (especially with the code structure) – I think this will be it for project 1.

 


 

Particles – This is a very simple game, and could sometimes look a little flat. I added a small number of collision particles (platform collision, pickup collision, obstacle collision) to hopefully make the game more visually appealing.

particles

 


 

Standardised Colours – For better or worse I’ve used the same colour scheme for all my games since EOL back in 2010.

To standardise the same colours across the game  I created a script called ColourManager. This contains a number of static methods to manage element colours;

GetColour returns a specific colour, used by HighScoreAdd to manage the keyboard key colours.

SetColour used to set the colour of a components renderer. Used to set the colours of platforms, obstacles and pickups

CurrentColour & NextColour used by platforms and titlescreen text to loop through each colour in rotation.

 

colour_range

 


 

Ensure player isn’t killed at game start – Until now, at the game start; a player would sometimes immediately hit the edge of a platform and fall out of the game – Ending the game before it had begun. To fix this I made a very minor edit to the PlatformManager code so that the very first platform is much longer – Giving the player a safe entry to the game before different platform heights are introduced.

 


 

Ensure player managed when not in-game –  On the title screen (and game over screen) the player components ridgedbody element was still being updated – If a player left the title screen running for any length of time, they would be subjected to mysterious platform colour changes & collision sound effects being played seemingly randomly. To resolve this, I now only update the player if in-game (if statement in the players Update method) – Otherwise I reset them to an offscreen position and reset the velocity

     void ResetPlayer()
     {
          rb.transform.position = new Vector3(0, 7.5f, 0); //Set game init position
          rb.velocity = Vector3.zero;
     }

 



 

Set Alternate Game Name – Changed the title screen to say 20 goto 10 presents Bock Run. It’s not a great name, but seemed a little better than Welcome to Test Game (Colours are randomly determined on screen init).

 

GameTitle

 


 

In-Game Scoreboard – I simplified the presentation of the scoreboard by only displaying the players score. Though I wanted the score to be shown behind the player (but in front of thr skybox), so I complicated the back end. Adding a new canvas (called CanvasScore), plugging in the Main Camera changing the render mode to ScreenSpace – Camera

CanvasScore

 


 

SkyBox – I liked the default skybox, but I thought I should at least try something a little different. Ultimately I settled on the Fantasy Skybox FREE by G.E.TeamDev  – Stripping out all the content except the Sunny 01A material and Sunny_01A_Front texture.

Sunny_01A_front

 


 

Player Death Pause – Colliding with an obstacle, results in the player exploding in a cloud of particles – game over. Previously, after creating an instance of explosion particles the game went straight to either the ‘new highscore‘ or ‘game over‘ screen letting the particles play out in the background. While this didn’t look too bad, I wanted to add a slight pause – Long enough to let the player appreciate what had happened – But not so long as to be distracting – I guessed about a second would do the job.

My quick and dirty way to add this, was to create a new game states ‘GameOverPause‘ which would be set only if the game over state was a result of obstacle collision. When setting  ‘GameOverPause‘, a timer is run as a coroutine;

IEnumerator GameOverPauseTimer()
{
     yield return new WaitForSeconds(1);
     HighScoreCheck();
}

Once the timer completes HighScoreCheck() is called and the games next state is detemined – ‘New Highscore’ or ‘Game Over’

 


 

Background scrolls smoothly when transitioning from game over to game start – At the moment, when a new game is started, I reload the scene – start everything from fresh. I wasn’t sure if this was a little jumpy & liked the idea of the background blocks continually scrolling, whatever the game state (game/game over/new highscore, etc…). No reset or reloads – when the game ends, the background keeps scrolling – when the player presses play they fall straight into the scene whatever state it’s in. I ultimately decided not to go this way (Because of ‘Ensure player isn’t killed at game start‘), but the code is still in place & can be activated, either in-code by setting the ‘bool reloadLevel‘ (in the GameStateManager class) to false – Or in-game by pressing ‘R‘.

 


 

Multiple obstacle types – spheres bounce, blocks burst – Expanding obstacles a little – As well as the instant death obstacles, I added new type – using the ObstacleSphere prefab – bounce. Both obstacle types use the same script, the only change is the addition of a enum used to determine obstacle type;

public enum ObstacleType { Cube = 0, Sphere, Pyramid }
public ObstacleType type;

& a little code to handle the different collision responses – Cubes & pyramids use the old/existing – instant death code – While spheres ‘bounce’ the player uncontrollably

void OnCollisionEnter(Collision collision)
{

     if (GameState.Is(GameState.State.InGame) && collision.gameObject.CompareTag("Player")) //Ensure we are checking aginst the player.
     {

          Player player = collision.gameObject.GetComponent(); //Grab the player

          if (type == ObstacleType.Cube || type == ObstacleType.Pyramid)
               player.PlayDie();

          else if (type == ObstacleType.Sphere)
               player.Bounce();

     }

}

Bouncing just increases the players Y velocity;

public void Bounce()
{

     float bounceAmount = 6f;
     rb.velocity = new Vector3(rb.velocity.x, Mathf.Abs(rb.velocity.y) + bounceAmount, rb.velocity.z);
     audioBounce.Play();

}

 


 

Controller support – Support for different input methods was pretty spotty – some screens recognised mouse and keyboard input; others only mice. – I wanted to ensure all screens could be controlled by either keyboard or mouse, as well as gamepad (No touch input yet though).

The setup as it currently stands is a little rough, clearly a first draft which could  benefit from a review/rewrite or two. It’s a static class based around four methods – Two methods Horizontal & Vertical check for player movement requests  & two methods ButtonsDown & Buttons to check for player action requests (jump, select key, button, etc).

Companion axis methods Left/Right – Up/Down – HorizontalOff/VerticalOff use the results of Horizontal  & Vertical to provide reference to the current state (e.g. is the player trying to move left) without needing to rerun Horizontal & Vertical more than once a frame. Used to navigate the the virtual keyboard and main menu buttons.

Companion button methods e.g ButtonADownButtonBDownButtonShoulderLDown are used to reference specific button selections.

Horizontal()  & Vertical() effectivly both work in the same way, just along different axis. They are run every loop; checking for user input using a valid method (left thumbstick, right thumbstick, arrow key, etc) and return a result value; -1 for left/down, 0 for no input  & 1 for right/up.

public static float Horizontal() //Is the player using horizontal movement
{

     lastHorizontal = currentHorizontal;

     if (Input.GetKey(KeyCode.LeftArrow) || ZoneCheck(ZoneRange.Left, Input.GetAxis("Horizontal")) || ZoneCheck(ZoneRange.Left, Input.GetAxis("HorizontalThumbstickR")) || Input.GetAxis("HorizontalDPad") < 0)           currentHorizontal = -1;      else if (Input.GetKey(KeyCode.RightArrow) || ZoneCheck(ZoneRange.Right, Input.GetAxis("Horizontal")) || ZoneCheck(ZoneRange.Right, Input.GetAxis("HorizontalThumbstickR")) || Input.GetAxis("HorizontalDPad") > 0)
          currentHorizontal = 1;

     else if (Input.GetKeyUp(KeyCode.LeftArrow) || Input.GetKeyUp(KeyCode.RightArrow) ||
                    (currentHorizontal != 0 && ZoneCheck(ZoneRange.Centre, Input.GetAxis("Horizontal")) && ZoneCheck(ZoneRange.Centre, Input.GetAxis("HorizontalThumbstickR")) && Input.GetAxis("HorizontalDPad") == 0))
          currentHorizontal = 0;

     return currentHorizontal;

}

Buttons() checks all valid input methods (gamepad buttons, space bar, return key, etc) using the unity function GetButton – To check if the button is currently being pressed -Used for in game jumping.

public static bool Buttons() //Any button pressed
{

     if (Input.GetKeyDown(KeyCode.Return) || Input.GetKeyDown(KeyCode.Space) || 
            Input.GetButton("JoystickButton0") || Input.GetButton("JoystickButton1") || Input.GetButton("JoystickButton2") || Input.GetButton("JoystickButton3"))
          return true;

     return false;

}

ButtonsDown() checks all valid input methods (gamepad buttons, space bar, return key, etc) using the unity function GetButtonDown – To check if the button was pressed in the last frame. Used when the user selects a button from the Main Menu.

public static bool ButtonsDown() //Any button pressed
{

     if (Input.GetKeyDown(KeyCode.Return) || Input.GetKeyDown(KeyCode.Space) || 
            Input.GetButtonDown("JoystickButton0") || Input.GetButtonDown("JoystickButton1") || Input.GetButtonDown("JoystickButton2") || Input.GetButtonDown("JoystickButton3"))
          return true;

     return false;

}

 


 

Split GameController into multiple classes – The GameController script was heavily overloaded – It managed the games states, title screen buttons, gameplay loop, player score, game timer, scroll speed, in-game music. While I suspect it really needs to be completely re-worked, for the moment I spread the functionality over three classes;

GameState – Really just holds & manages an enum used to determine the games current state (title screen, game over screen, in-game, etc) and the games last state.

GameStateManager – Is still rather overloaded since it does a lot more than just manage the GSM – Looks after the title screen buttons, manages highscore add, game over pause – Pretty much everything that isn’t ‘in-game’

InGameManager – Does handle everything in game (platforms, obstacles, player, etc) – It also manages the players score, background scroll speeds, etc.

It’s far from idea & both GameStateManager and InGameManager are still overloaded, but I think it is an improvement.

 


 

Instructions – I added some very simple instructions -To let players know what they can do (e.g. double jump) and how they can do it (keyboard mouse, gamepad). I try to keep instructions as simple as possible – & as free of words as possible – In case the player doesn’t speak English.

GamePad icon made by EpicCoders from http://www.flaticon.com
Mouse icon made by Freepik from http://www.flaticon.com

InstructionsALT

 


 

So, for version 1 at least, I think that’s pretty much everything. It’s far from perfect, but for my first attempt at a Unity game – not a complete failure – I’ve some very basic gameplay, a messy GSM, a working online highscore table and a much greater understanding of Unity.

As usual, a video showing the game, a link to the source code and a link to play the game online (WebGL) can be found below.

Thankyou!

 


 

Play this build (WebGL)

 


 

Grab a copy of the project here

 


 

 


 

Last post: 1.8 Obstacles

Contents page.

 


 

Adventures in Unity – 1.8 Obstacles

Official_unity_logo

Obstacles are static/non-moving  objects which appear on platforms with increasing frequency as the game progresses.

They are there to provide an extra challenge for the player and give a little more variety to the game.

Should a player collide with an obstacle, it’s instant death – game over – & so should be avoided at all costs.

The code itself it pretty straight forward – (Obstacles are created, they scroll left then are destroyed) – it’s a modified/simplified version of the code written to handle pickups.

 


 

For in-game obstacles I initially wanted to use a pyramid (Tetrahedron) shape – Since these are pointy and seem like the sort of thing a player should avoid. Unfortunately Unity doesn’t provide tetrahedrons as part of the basic 3D objects.

My first attempt at a workaround was to use cubes – Rotating them, and placing them halfway into the platform top – They looked okay-ish; but didn’t quite work out.

My second attempt was to use spheres – These looked much better – I nearly stuck with them – I just wasn’t convinced they looked dangerous enough.

Looking for an alternative – I found an article in Morten Nobel’s Blog detailing how to create a tetrahedron in code (rather than importing a model) – I used the code pretty much as-is – only making two very minor tweaks; 1. To make them self-construct when initialised & 2. To auto build a collision mesh.

Since I’m a big fan of procrastination, the code currently has all three obstacles types in place, setup as prefabs.

ObstacleCube – A 3D object Cube with default settings – implemented as as a prefab. A obstacle c# script is attached (used to manage collision response).

ObstacleCube

ObstacleSphere – A 3D object Sphere with default settings – implemented as a prefab. A obstacle c# script is attached (used to manage collision response).

ObstacleSphere

ObstacleTetrahedron – A custom object. Defined as a prefab, it contains the same components as ObstacleCube and ObstacleSphere – Except that the MeshFilter is empty. In addition it includes two extra C# scripts; obstacle – used to manage collision response & TetraHedron – Used to construct the object at runtime.

ObstacleTetra

 


 

ObstacleManager;

ObstacleManager is a C# script which manages all obstacles in game, covering their entire lifespan (initalisation, update & removal).

The public method AddObstacles is used to instantiate new obstacles (called externally by the PlatformManager script).

Once created they are then add to a list (List<GameObject> obstacles). Each frame the positions of all obstacles are updated.

Once an obstacle moves offscreen it is destroyed by ObstacleManager  to free up space.

 


 

AddObstacles;

public void AddObstacles(Vector3 inPosition, float inWidth)
{

     Vector3 obstaclePosition = new Vector3(inPosition.x, inPosition.y, inPosition.z-4.5f);


     float obstacleHalfWidth = obstacle.transform.localScale.x * 0.6f;
     float halfWidth = (inWidth / 2f)-obstacleHalfWidth;
     obstaclePosition.x = Random.Range(obstaclePosition.x - halfWidth, obstaclePosition.x + halfWidth);

     for (int counter=0; counter<9; counter++)
     {
          obstacles.Add(Instantiate(obstacle, obstaclePosition, Quaternion.identity) as GameObject);
          obstacles[obstacles.Count - 1].transform.position = obstaclePosition;
          obstaclePosition.z += 1;
     }

}

AddObstacles is called by the platformManager script. When called it creates a column of obstacles – Even though, only the centre obstacle is need (since the player doesn’t move on the Y axis) – a row is more visually appealing.

It receives two parameter values inPosition & inWidth. inPosition is the source/base position for obstacles it is about to instantiate. This can be used to add new obstacles to the middle of the platform. This is fine, looks good and works as an extra challenge in-game. However to keep things interesting I wanted to position the obstacles randomly along the platforms X axis. For this I use the inWidth variable.

 


 

Update;

Every loop the position of instantiated obstacles is updated, moving them from right to left; destroying any which move out of the game area;

// Update is called once per frame
void Update()
{

     //---

     if (GameController.gameState == GameController.GameState.InGame)
     {

          //---

          ScrollSpeed(); //set movement speed
          UpdatePositions(); //update platforms positions

          //---

     }

//---

}

//---
//---

//set movement speed
void ScrollSpeed()
{

     speed = 0.1f + (GameController.scrollSpeed * 0.004f);

     if (speed > maxSpeed)
          speed = maxSpeed;

}

 


 

Obstacle;

All obstacles have an attached script -all called Obstacle.

It’s is a simple script containing two methods;

Start – Which sets the obstacles colour after initialisation.

OnCollisionEnter – Checks to see if the player has collided with the obstacles – If a collision has occurred – PlayerDie() method is called.

 


 

Start;

Start defines the colour of the obstacle (currently they are all set to black);

void Start()
{

     //---

     Renderer rend = GetComponent(); //Ensure the obstacle's colour is set to black
     Color whichColour = Color.black;
     rend.material.SetColor("_Color", whichColour);
     rend.material.SetColor("_SpecColor", whichColour);
     rend.material.SetColor("_EmissionColor", whichColour);
     rend.material.SetColor("_ReflectColor", whichColour);

     //---

}

 


 

OnCollisionEnter;

OnCollisionEnter checks for collision with the player – Since obstacles are deadly, if a collision is found, I call the player.PlayDie method;

void OnCollisionEnter(Collision collision)
{

     if (collision.gameObject.CompareTag("Player")) //Ensure we are checking aginst the player.
     {

          Player player = collision.gameObject.GetComponent(); //Grab the player
          player.PlayDie();

     }

}

 


 

That’s pretty much it. I may expand obstacles a little in the future – But for now it gives me a framework to work from.

 


 

Play this build (WebGL)

 


 

Grab a copy of the project here

 


 

 


 

Next post: 1.9 Tighten & Tidy

Last post: 1.7d Highscores (Display)

Contents page.

 


 

Adventures in Unity – 1.7e Highscores (Add)

Official_unity_logo

 

Last part for highscores – A method to allow players who have achieved a new high score to enter their names. This probably should have been a simple screen containing two elements an input field and a enter button. However, for whatever reason I decided to make things a little more complex – Adding a virtual keyboard.

The front end for the virtual keyboard is based on the example provided in the Unity Samples: UI package – The back end I put together myself – With the bulk of the code for this section setup for managing user input. It can currently be controlled either by mouse or keyboard – Expanding to allow controller input ‘should’ be fairly straight forward.

The player doesn’t need to use the virtual keyboard to enter their name – They can always click on the input field and type their names directly – So there’s also a little code to ensure that only valid characters are accepted and that the maximum length is not exceeded should this approach be used.

To try and explain how I’ve put this together, I’ve split the description into two sections;

  1. Front End: GUI.
  2. Back End: Code

 


 

1. Front End: GUI

HighscoreAdd

HighscoreAdd – an empty component which groups together all the new highscore UI elements.

HighScoreTextTop

HighscoreAddTitle

A text component, used as the title of the screen

Window – Contains the input field and keyboard keys – The bulk of new highscore components.

Inputfield

HighscoreAddInputField

Displays the players name. Contents can be filled either by using the keyboard keys or by direct input. I set the character limit to 15 so that highscore names fit nicely into the highscore table. Although I only want to allow the player to enter alphanumeric I have the input type set as standard – Managing the input in code- the method InputFieldUpdate is called ‘on value changed‘ which checks the character types, only accepting valid characters.’One Edit End‘ calls a second script method ClickDone – This allows the player to press enter after tying in their name to add the new high score – rather than forcing them to click the done button.

Vertical Group – Is the start of the on-screen keyboard. It’s a component that itself just contains a Vertical Layout Group (script) and has as a child the Grid Component.

Grid – Another component setup primarily to manage the shape of the keyboard, is has two layout components attached Grid Layout Group (script) and LayoutElement (script). As children it has 40 buttons – one for each key.

GridCell-X – Forty of these, one for each key. They are setup in pretty much exactly the same way as all the other buttons in the game. An Animator shrinks the button slightly when clicked, and audioSource & event trigger produce a click sound when the key is selected and a relevant method is called On Click  – ClickAlphanumeric method is called for the alphanumeric keys, with individual methods for the space (clickSpace), delete (ClickDelete), caps (ClickCaps) and enter (ClickDone) keys.

 


 

2. Back End: Code

The backbone of the keyboard is a script called HighscoreAdd. This is primarily a functional script, primarily containing methods for handling key presses and ensuring only acceptable (Alphanumeric) values are used for the highscore name.

If the player clicks on an alphanumeric keyboard key, it calls the ClickAlphanumeric method.

public void ClickAlphaNumeric(string inValue)
{

     if (inputField.text.Length < inputField.characterLimit)
          inputField.text += inValue;

     UpdateIndex(inValue);

}

This method first checks to see if the input has reached maximum allowed characters as set in the input field, if there’s space, it adds the new character. Before calling UpdateIndex – a method which changes the colour of the button selected – To make the keyboard feel a little more interactive.

public void ClickSpace(Text inValue)
{

     if (inputField.text.Length < inputField.characterLimit)
          inputField.text += " ";

     UpdateIndex(inValue.text);

}

ClickDelete removes the last character entered by assigning the players name text with a substring of itself, starting from position 0 and ending at length-1;

public void ClickDelete(Text inValue)
{

     if (inputField.text.Length > 0)
          inputField.text = inputField.text.Substring(0, inputField.text.Length - 1);

     UpdateIndex(inValue.text);

}

ClickCaps switches characters between upper and lower case. When the player adds a letter by clicking a button, it adds the value of the buttons text/label to the players name. So when switching between upper and lower case, this method loops through each alphabetic button in turn and sets its label to an upper or lower case version of itself. This has the added effect of visually displaying the current case. I also change the colour of the caps button to indicate it’s state – currently red for uppercase and blue for lower case.

public void ClickCaps(Text inValue)
{

     useCaps = !useCaps;

     UpdateIndex(inValue.text);

     if (useCaps)
     {
          lastColor = Color.red;

          for (int counter = 0; counter < buttons.Length; counter++)
               buttons[counter].GetComponentInChildren().text = buttons[counter].GetComponentInChildren().text.ToUpper();
     }

     else
     {
          lastColor = Color.blue;

          for (int counter = 0; counter < buttons.Length; counter++)
               buttons[counter].GetComponentInChildren().text = buttons[counter].GetComponentInChildren().text.ToLower();
     }

}

ClickDone is run when either the DONE button is clicked or the player presses the return key while the input field is focused. This function first checks to see if the player has added a name – If there is no value, or only spaces have been added the generic player name ‘A Nony Moose’ is used instead. Once a player name is set, it passes it to the SetHighScore method in the gameController script to be added to the list of highscores.

public void ClickDone(Text inValue)
{

     //---

     GameObject gameControllerObject = GameObject.FindGameObjectWithTag("GameController");

     if (gameControllerObject != null)
     {
          GameController gameController = gameControllerObject.GetComponent();

          if (inputField.text == "" || OnlySpaces(inputField.text))
          inputField.text = "A Nony Moose";

          gameController.SetHighScore(inputField.text);
     }

     //---

}

 


 

UpdateIndex method is called by all Button functions to ensure the game knows which was the last button pressed, and to update the button highlight appropriately (By calling UpdateButtonIndex);

void UpdateIndex(string inValue)
{

     for (int counter = 0; counter < buttons.Length; counter++)
          if (inValue.ToUpper() == buttons[counter].GetComponentInChildren().text.ToUpper())
          {
               index = counter;
               UpdateButtonIndex();
               break;
          }

}

UpdateButtonIndex stores the index value of the last button clicked. It also stores the last colour of the button (its unpressed colour), before changing it’s current colour to show its selection (currently grey) – The reason the last colour is stored, despite the majority of keys being white is because the caps and done keys have their own colours – We need to ensure they are correctly returned to these colours once these keys are correctly deselected;

void UpdateButtonIndex()
{

     if (index < 0)             
          index = 0;         

     if (index >= buttons.Length)
          index = buttons.Length - 1;


     //if (index != lastIndex)
     {

          buttons[lastIndex].image.color = lastColor;
          lastColor = buttons[index].image.color;
          buttons[index].image.color = Color.grey;
          lastIndex = index;
     }

}

InputCheck, called from the update method, runs every loop. It allows the keyboard to be controller via the arrow and enter keys. Arrows to move around the keyboard – Enter to select the key and add the character to the players name. This input method is only valid if the input field isn’t currently selected – This is because the input field runs the ClickDone method if the return key is pressed;

bool lastFocused = false;
public void InputCheck(bool checkReturn)
{

     if (inputField.isFocused)
     {
     }

     else if (Input.GetKeyDown(KeyCode.LeftArrow))
     {

          if (index >= 0 && index % columnLength > 0)
          {
               index--;
               UpdateButtonIndex();
          }
     }

     else if (Input.GetKeyDown(KeyCode.RightArrow))
     {

          if (index == -1 || (index + 1) % columnLength > 0)
          {
               index++;
               UpdateButtonIndex();
          }
     }

     else if (Input.GetKeyDown(KeyCode.UpArrow))
     {
          if (index >= columnLength)
          {
               index -= columnLength;
               UpdateButtonIndex();
          }
     }

     else if (Input.GetKeyDown(KeyCode.DownArrow))
     {
          if (index != -1 && index + 1 <= buttons.Length - columnLength)
          {
               index += columnLength;
               UpdateButtonIndex();
          }
     }

     else if ((Input.GetKeyDown(KeyCode.Return) || Input.GetKeyDown(KeyCode.Space)))
     {

          if (index < 0)
               index = 0;

          if (index >= buttons.Length)
               index = buttons.Length - 1;

          lastIndex = index;

          if (!checkReturn)
          {
               var pointer = new PointerEventData(EventSystem.current);
               ExecuteEvents.Execute(buttons[index].gameObject, pointer, ExecuteEvents.submitHandler);
               buttons[index].GetComponentInChildren().Play();
               }

     }

}

Finally; the InputFieldMethod, run every time a character is added by the player directly into the inputfield (by clicking on the inputfield component and typing using the computers keyboard, rather than the virtual keyboard). It checks to see if the last character added character is valid (alphanumeric or a space) – If it isn’t, it deletes the character, if it is it returns without making any changes;

    
public void InputFieldUpdate()
{

     if (inputField.text.Length > 0)
     {
     
          char chr = inputField.text[inputField.text.Length - 1];
     
          if ((chr < 'a' || chr > 'z') && (chr < 'A' || chr > 'Z') && (chr < '0' || chr > '9') && chr != ' ')
          {
               inputField.text = inputField.text.Substring(0, inputField.text.Length - 1);
          }

     }
        
}

 


 

Play this build (WebGL)

 


 

Grab a copy of the project here

 


 

 


 

Next post: 1.8 Obstacles

Last post: 1.7d Highscores (Display)

Contents page.

 


 

Adventures in Unity – 1.7d Highscores (Display)

Official_unity_logo

 

The backbone of the highscore system is in place – But theres still no method for the program to display highscores.

Neither of the two sources I’d used to far were able to help me here – The unify community code was more involved with the back-end of things & the YouTube guide [Unity Tutorial] Online Highscores 01 (dreamlo) only demonstrated a very basic method for displaying the top ten scores.

I’d intended to show the top fifty scores; which was clearly going to be too many to display all at the same time on a single screen. I figured the game needed the ability to list the highscores, and let the player scroll through them.

I thought this would be pretty easy to implement – unfortunately there’s currently not a lot of elements in Unity’s UI for handling this kind of thing – At least, not without putting in a lot of effort first.

Ultimately I was able to put something together, which I think does the job pretty well, but still isn’t quite as flexible as I’d liked.


 

Highscore display is made up of three main elements;

1. HighScorePanel prefab

HighscorePanel

This is a panel with three text components attached (position, Name, Score). Used to display the details of one highscore entry.

2. ScrollView component

HighscoreDisplay

This contains a list HighScorePanel objects & is used to display the top 50 scores.

3. HighScoreDisplay script

The meat of the highscore display process – This code requests highscore data and uses it to fill the scrollview component with the current data to display.

 


 

 

The HighScoreDisplay script,  run at startup, calls the function refreshHighScores, which every 30 seconds, (by way of the HighscoreController script), requests an updated list of highscore data;

    
IEnumerator refreshHighScores()
{
     while (true)
     {
          highScoreController.DownloadHighscores();
          yield return new WaitForSeconds(30);
     }
}

 

Once the HighscoreController refreshes the highscore list, it passes control back to the HighScoreDisplay script – By calling the OnHighScoresDownloaded function along with a list of all current highscores. OnHighScoresDownloaded uses the highscore data to populate the ScrollView with HighscorePanel prefabs, each of which containing one highscore entry;

 

public void OnHighScoresDownloaded(HighScore[] highScoreList)
{

     ClearList();

     for (int counter = 0; counter < highScoreList.Length; counter++)
     {

          //---

          GameObject go = Instantiate(hsPanel) as GameObject;
          go.transform.SetParent(hsContent.transform);
          go.transform.localScale = go.transform.localScale * canvas.scaleFactor;
          Text[] children = go.GetComponentsInChildren();
          children[0].text = (counter + 1).ToString();
          children[1].text = highScoreList[counter].userName;
          children[2].text = highScoreList[counter].score.ToString();

          //---

          if (counter == HighscoreController.lastHighScore)
          {

               string currentColour = "ff0000";

               children[0].text = "<color=#" + currentColour + ">" + children[0].text + "";
               children[1].text = "<color=#" + currentColour + ">" + children[1].text + "";
               children[2].text = "<color=#" + currentColour + ">" + children[2].text + "";

          }

     }

}

 

OnHighScoresDownloaded loops through each highscore entry, & for each it creates instance of a HighScorePanel prefab; adding the name, score and position in highscore to the prefabs attached Text components.

for (int counter = 0; counter < highScoreList.Length; counter++)
{

     //---

     GameObject go = Instantiate(hsPanel) as GameObject;
     go.transform.SetParent(hsContent.transform);
     go.transform.localScale = go.transform.localScale * canvas.scaleFactor;
     Text[] children = go.GetComponentsInChildren();
     children[0].text = (counter + 1).ToString();
     children[1].text = highScoreList[counter].userName;
     children[2].text = highScoreList[counter].score.ToString();

     //---
}

Setting the prefabs parent to the content component of the HighScorePanel component.
This attaches it to the scroll object allowing it to be displayed,

     go.transform.SetParent(hsContent.transform);

Also scaling the text, so that it will look approximately the same no matter what screen res is used;

     go.transform.localScale = go.transform.localScale * canvas.scaleFactor;

 

& Finally, If a new highscore has been achieved, after the new high score entry added the game displays the highscore table, highlighting the new entry by changing its colour;

 

if (counter == HighscoreController.lastHighScore)
{

     string currentColour = "ff0000";

     children[0].text = "<color=#" + currentColour + ">" + children[0].text + "";
     children[1].text = "<color=#" + currentColour + ">" + children[1].text + "";
     children[2].text = "<color=#" + currentColour + ">" + children[2].text + "";
}

 


 

Now I have a way display highscores list – But no method to allow players to add new highscores to the table.

 


 

Play this build (WebGL)

 


 

Grab a copy of the project here

 


 

 


 

Next post: 1.7e Highscores (Add)

Last post: Adventures in Unity – 1.7c Highscores (In-Game Backend)

Contents page.

 


 

Adventures in Unity – 1.7c Highscores (In-Game Backend)

Official_unity_logo

Once the server side code was in place, I needed methods to download the current high score list and upload new entries in-game.

The basic code for connecting to the server and retrieving scores was provided by the unify community ‘Server Side Highscores’ article.

I added the method Md5Sum (also provided by the unify community) to encode the data before sending to the server.

Expanding on this code using the  YouTube tutorial ‘[Unity Tutorial] Online Highscores 01 (dreamlo)‘ – Using IEnumerator when adding a new highscore or downloading the current highscore list provides a smoother in game experience for the player, since it allows the primary thread to manage the game, while the secondary threads manage the highscore table status.

 


 

The modified code was placed in a c# script called HighScoreController – Which I attached to an empty component also called HighScoreController.

Functionality within the HighScoreController script can be split into two fairly clear sections;

  1. Add New High Score.
  2. Download (format and store) current highscores list.

 


 

1. Add New High Score;

Adapting the method to add new highscores – Using IEnumerator to allow the code to run on a separate thread to the main game;

    
public static void AddNewHighScore(string userName, int score)
{
     instance.StartCoroutine(instance.UploadNewHighscore(userName, score));
}

// remember to use StartCoroutine when calling this function!
IEnumerator UploadNewHighscore(string name, int score)
{
     //This connects to a server side php script that will add the name and score to a MySQL DB.
     // Supply it with a string representing the players name and the players score.
     string hash = Md5Sum(name + score + secretKey);

     string post_url = addScoreURL + "name=" + WWW.EscapeURL(name) + "&score=" + score + "&hash=" + hash;

     // Post the URL to the site and create a download object to get the result.
     WWW hs_post = new WWW(post_url);
     yield return hs_post; // Wait until the download is done

     if (hs_post.error != null)
     {
          print("There was an error posting the high score: " + hs_post.error);
     }

     else
     {
          DownloadHighscores();
     }

}

With an associated method which encrypts highscores for sending (also provided by the Unify Community site);

    
public string Md5Sum(string strToEncrypt)
{

     System.Text.UTF8Encoding ue = new System.Text.UTF8Encoding();
     byte[] bytes = ue.GetBytes(strToEncrypt);

     // encrypt bytes
     System.Security.Cryptography.MD5CryptoServiceProvider md5 = new System.Security.Cryptography.MD5CryptoServiceProvider();
     byte[] hashBytes = md5.ComputeHash(bytes);

     // Convert the encrypted bytes back to a string (base 16)
     string hashString = "";

     for (int i = 0; i < hashBytes.Length; i++)
     {
          hashString += System.Convert.ToString(hashBytes[i], 16).PadLeft(2, '0');
     }

     return hashString.PadLeft(32, '0');

}

 


 

2. Download (format and store) current highscores list;

Expanding on the Unify Community method to download highscores as a string from the server. As with the AddNewHighScore method, DownloadHighScores is also modified from the Unify Community code using IEnuerator to allow threaded functionality;

public void DownloadHighscores()
{
     StartCoroutine("DownloadHighscoresFromDatabase");
}

// Get the scores from the MySQL DB to display in a Text.
// remember to use StartCoroutine when calling this function!
IEnumerator DownloadHighscoresFromDatabase()
{

     WWW hs_get = new WWW(highscoreURL);
     yield return hs_get;

     int counter = 0;
     for (; counter < hs_get.text.Length; counter++)
          if (hs_get.text[counter] == '#')
               break;

     string hs_got = hs_get.text.Substring(0, counter); //Substring(0, counter);

     if (hs_get.error != null)
     {
          print("There was an error getting the high score: " + hs_get.error);
     }
     else
     {
          FormatHighScores(hs_got);
          highScoreDisplay.OnHighScoresDownloaded(highScoresList);
     }

}

 


 

Note: The highscore data is returned by 000webhost as a text string – When returning the data, 000webhost  appends a little extra/unwanted info

<!-- Hosting24 Analytics Code -->
http://stats.hosting24.com/count.php
<!-- End Of Analytics Code -->

This code is used by 000webhost to check site usage – Which is fair enough, since they provide the hosting for free – But is unneeded and unwanted by the game – I definitely dont want it getting mixed up with the highscore table.

My quick and dirty fix to this;

  
int counter = 0;

for (; counter < hs_get.text.Length; counter++)
     if (hs_get.text[counter] == '#')
          break;

string hs_got = hs_get.text.Substring(0, counter); //Substring(0, counter);

On the server, after creating the highscore list, I append a ‘#’ to the end of the string. A character I know isn’t used either in the highscores (it cant be entered when adding a highscore) or in 000webhost’s appended text. I loop through the highscore text, searching for the position of the hash character – Once found, the code removes it and any text which follows.

 


 

After DownloadHighscoresFromDatabase has retrieved the highscore text; it calls FormatHighScores which parses the names and scores from the text and stores them for use in game;

void FormatHighScores(string TextStream)
{
     string[] entries = TextStream.Split(new char[] { '\n' }, System.StringSplitOptions.RemoveEmptyEntries);
     highScoresList = new HighScore[entries.Length];

     for (int counter = 0; counter < entries.Length; counter++)
     {

          string[] entryInfo = entries[counter].Split(new char[] { '|' });

          if (entryInfo.Length == 2)
          {

               //---

               string userName = entryInfo[0];
               int score = int.Parse(entryInfo[1]);
               highScoresList[counter] = new HighScore(userName, score);

               //---

          }

     }

     //---

}

FormatHighScores populates a list of HighScore struct. This is used by the HighscoreDisplay script to populate the highscore table – And by the HighScoreAdd script to check if the new score should be counted as a new entry;

public struct HighScore
{
     public string userName;
     public int score;

     public HighScore(string inUserName, int inScore)
     {
          userName = inUserName;
          score = inScore;
     }
}

 


 

This provides a backbone in-game to manage highscores – But I still need methods to display current high score & add new highscores.

 


 

Play this build (WebGL)

 


 

Grab a copy of the project here

 


 

 


 

Next post: 1.7d Highscores (Display)

Last post: 1.7b Highscores (In-Game Overview)

Contents page.

 


 

Adventures in Unity – 1.7b Highscores (In-Game Overview)

Official_unity_logo

 

The unify community ‘Server Side Highscores’ article provided basic code for connecting to the server, downloading the current high score list and uploading new entries.

But no real framework to manage downloads & uploads, nothing to display highscores or to add new entries to the table.

Prior to using the unify community code, I implementing the dreamlo highscore system – I set this up by following the excellent you tube guide ‘[Unity Tutorial] Online Highscores 01 (dreamlo)‘ put together by Sebastian Lague.

While I wasn’t able to get the dreamlo system to work with WebGL games – I did borrow elements of the framework Sabastian demonstrated when constructing high score code for this game.

The final framework can be split into three basic sections;

  1. Backend high score management.
  2. Display current high scores.
  3. Add new high score.

The code itself is surprisingly extensive, so for this post I’ll try and provide a top level overview for each section & try to explain how they all fit together in a high level – Then, for the next few posts I’ll focus on each section in turn and explain in more detail.

 


 

1. Backend high score management;

HighscoreComponent

HighScore (Component) – Holds both HighscoreController and HighscoreDisplay scripts

HighscoreController (script) – A singleton, this script contains methods to get the current highscore list from the server (used by HighScoreDisplay script), and add new highscore entries to the server (used by HighScoreAdd). Also contains a static array of type HighScore where the current list of highscores are held, and a static int lastHighScore which identifies the index of the last highscore added (set to -1 by default)

HighScore (Strut) – A simple object containing two variables, int score & string name. Used to hold the details of a single high score entry. A static list of these is maintained by the HighscoreController script.

GameController (script) – A currently very overloaded script. The original intend for this script was to manage the game states (title screen, in game, game over , new high score screen, display highscores, etc). However it currently also manages the player’s score, the title screen buttons and manages the various highscore states – It does this primarily through two methods;

CheckHighScore, which at game over, checks to see if the player’s score should be added to the highscore list – If the player has scored high, the script both sets the HighscoreController.lastHighScore int, and sets the game state to display the new high score screen.

A second method managed by GameController;  SetHighScore, called by the AddHighScore script once a new player names has been added, directly requests that the highscorecontroller add the new high score to the highscore list (locally and on the server) by calling highscorecontroller.AddNewHighScore, before passing control to the UpdateHighscore method, which changes the game state to Display the current highscore list.

 


 

2. Display High Scores;

HighscoreDisplay

HighScoreDisplay (script) – Attached to the HighScore component; this uses the HighScore (Strut) list in HighscoreController to populate a list of HighScorePanel (Panel)  when displaying the highscore list to the player. Also, every 30 seconds, it requests the HighscoreController to download an updated list of highscores.

HighscorePanel

HighScorePanel (prefab ) – This is a customised panel UI component. It has three Text UI children – PositionText, NameText ScoreText. The values are set by the HighScoreDisplay (script) and used to display the details of a single high score entry when displaying the highscore table.

HighscoreDisplay

HighScoreDisplay (Component) – An empty component, groups the UI elements used to display the high score screen.

HighScoreTextTop (Component ) – A Text UI element, child of the HighScoreDisplay (Component), used to display the title HIGH SCORE on the high score screen.

Scroll View (Component) – A ScrollView UI element, child of the HighScoreDisplay (Component), Used to hold up to 50 HighScorePanel (Panel) prefabs when displaying the highscore table.

 


 

3. Add High Score;

HighscoreAdd

HighscoreAdd (Script) – Contains a number of methods referenced by the UI components of the New Highscore screen. These methods validate and process user input when adding a player new to a new highscore. Ensuring the players name only contains valid characters, switches text case, ensure the players name doesn’t exceed a maximum length putting in a dummy name should the player not enter anything at all – Finally, it calls the SetHighScore method in the gamecontroller script once the new name has been entered.

HighscoreAdd

HighscoreAdd (Component) – An empty component, groups the UI elements used for the new highscore screen.

HighScoreTextTop (Component) – A Text UI element, child of the HighscoreAdd (Component), used to display the title NEW HIGH SCORE! on the add highscore screen.

Window (Component) – An empty component, child of the HighscoreAdd (Component), groups the UI elements used for the new high score screen.

InputField (Component) – An inputfield UI component, child of the window (component), used to display the players name when adding  new highscore. Players can also click directly onto the component and type their name using the computer’s keyboard.

Vertical Group (Component) – An empty component, child of the window (Component), groups and formats the UI elements used for the keyboard buttons on the new high score screen.

Grid (Component) – An empty component, child of the Vertical Group (Component), groups and formats the UI elements used for the keyboard buttons on the new high score screen.

GridCell-X (Component) – Button UI components, child of the Grid (Component). 40 of these operate as the buttons for a virtual keyboard that can be used to add a player name for a new highscore.

 


 

Play this build (WebGL)

 


 

Grab a copy of the project here

 


 

 


 

Next post: 1.7c Highscores (In-Game Backend)

Last post: 1.7a Highscores (Server Side)

Contents page.

 


 

Adventures in Unity – 1.7a Highscores (Server Side)

 

Official_unity_logo

 

#1 What would I ideally like to achieve by the time I finish working today – Have a working online highscore system (backend), with the ability to display and update high scores (in-game).

#2 What’s the minimum I’d like to achieve by the close of play – Have a working online highscore system (backend).

 


 

To recap – Teaching myself Unity by building a simple game – An infinite runner.

In my opinion, with this style of game, a high score table is vital – I don’t think the game has enough content in and of itself to provide much entertainment beyond the first few plays.

A high score table provides motivation for players to return – In part to beat their own scores – But mainly to make sure nobody else has beaten them.

While a local high score table will add value to the game, I think an online high score table will do a much better job.

Fortunately there is plenty of code out there showing how to implement an online highscore table in Unity – Unfortunately there are far fewer sites showing how to implement an online high score for a WebGL game (my intended final format).

I was going to have to hack something together myself.

 


 

The highscore systems I eventually put together is fairly involved, so I’ve broken it into five separate sections;

  1. Server Side Code
  2. In-Game code structure
  3. Highscore Controller
  4. Display High Scores
  5. Add new high score.

 

This week;

  1. Server Side Code

Exciting stuff! 🙂

 


 

By server side code I’m referring to a location online where the highscores are stored, as well as the server side mechanisms to obtain and update the highscores.

To store the highscore I used the free hosting site 000webhost – They are a hosting provider I’d not used before; but I’d noticed their name being thrown a few times when I was looking to see how to put this together. They provide a free domain, site & mySQL database – All of which were life-savers for me.

The server side code itself, I based the online highscore on the excellent ‘Server Side Highscores‘ sample from the UnifyCommunity website  – This won’t work straight out of the box, especially if you want for WebGL games – But it gave me a nice base to work from.

 


 

Following the instructions provided on the UnifyComunity Server Side Highscores page;

I created a simple mySQL highscore using my 000webhost webspace.

Important note: After creating a mySQL database – It takes a little time to actually get up and running – The website suggests a couple of minutes – For me it was a couple of hours.

Once the mySQL database is up and running, it’s time to upload the server side scripts (I used the PHP scripts).

There are three scripts provided in the UnifyComunity instructions;

  1. crossdomain.xml
  2. addscore.php
  3. display.php

 


 

crossdomain.xml

This is a required file; The Unity Manual explains ‘The Unity webplayer expects a http served policy file named crossdomain.xml to be available on the domain you want to access with the WWW class, (although this is not needed if it is the same domain that is hosting the unity3d file).

The crossdomain.xml code provided looks like this;

<?xml version="1.0"?>
<cross-domain-policy>
<allow-access-from domain="*"/>
</cross-domain-policy>

Though you may need to add ‘secure = “false”‘ parameter;

<?xml version="1.0"?>
<cross-domain-policy>
<allow-access-from domain="*" secure="false" />
</cross-domain-policy>

This file needs to be placed in the root of the site, in the case of 000webhost that’s in the public_html folder

crossdomain location

 


 

addscore.php

This is called by the game when adding a new highscore.

By-and-large I used the PHP as provided;

<?php 
        $db = mysql_connect('mysql_host', 'mysql_user', 'mysql_password') or die('Could not connect: ' . mysql_error()); 
        mysql_select_db('my_database') or die('Could not select database');
 
        // Strings must be escaped to prevent SQL injection attack. 
        $name = mysql_real_escape_string($_GET['name'], $db); 
        $score = mysql_real_escape_string($_GET['score'], $db); 
        $hash = $_GET['hash']; 
 
        $secretKey="mySecretKey"; # Change this value to match the value stored in the client javascript below 

        $real_hash = md5($name . $score . $secretKey); 
        if($real_hash == $hash) { 
            // Send variables for the MySQL database class. 
            $query = "insert into scores values (NULL, '$name', '$score');"; 
            $result = mysql_query($query) or die('Query failed: ' . mysql_error()); 
        } 
?>

Since I only want to store the top 50 scores; I added an extra operation to the end of the query ensuring only the top 50 scores are stored;

 $query = "delete from [high score table] Where id not in (select * from(select id from [high score table] order by score desc limit 50) as temp)"; 
 $result = mysql_query($query) or die('Query failed: ' . mysql_error());

resulting in;

<?php 
        $db = mysql_connect('mysql_host', 'mysql_user', 'mysql_password') or die('Could not connect: ' . mysql_error()); 
        mysql_select_db('my_database') or die('Could not select database');
 
        // Strings must be escaped to prevent SQL injection attack. 
        $name = mysql_real_escape_string($_GET['name'], $db); 
        $score = mysql_real_escape_string($_GET['score'], $db); 
        $hash = $_GET['hash']; 
 
        $secretKey="mySecretKey"; # Change this value to match the value stored in the client javascript below 

        $real_hash = md5($name . $score . $secretKey); 
        if($real_hash == $hash) { 
            // Send variables for the MySQL database class. 
            $query = "insert into scores values (NULL, '$name', '$score');"; 
            $result = mysql_query($query) or die('Query failed: ' . mysql_error());
 
            //---
 
            $query = "delete from [high score table] Where id not in (select * from(select id from [high score table] order by score desc limit 50) as temp)"; 
            $result = mysql_query($query) or die('Query failed: ' . mysql_error());
        } 
?>

 


 

display.php

This script get the highscores from the database so they can be displayed in game.

The code as provided returns the top 5 scores – I just amended

DESC LIMIT 5

added an extra 0 so that it would return the top 50 – Otherwise it’s a cut-&-paste from the website.

Also adding

header('Access-Control-Allow-Origin: *');

 

<?php

     header('Access-Control-Allow-Origin: *');

    // Send variables for the MySQL database class.
    $database = mysql_connect('mysql_host', 'mysql_user', 'mysql_password') or die('Could not connect: ' . mysql_error());
    mysql_select_db('my_database') or die('Could not select database');
 
    $query = "SELECT * FROM `scores` ORDER by `score` DESC LIMIT 50";
    $result = mysql_query($query) or die('Query failed: ' . mysql_error());
 
    $num_results = mysql_num_rows($result);  
 
    for($i = 0; $i < $num_results; $i++)
    {
         $row = mysql_fetch_array($result);
         echo $row['name'] . "\t" . $row['score'] . "\n";
    }

 


 

These scripts just need to be stored on the website, in a location the game can access. Since I’m hoping to use this site to manage the highscores for a number of games, I created a folder for this game – called BLK_RNR – and placed both scripts inside;

updatehighscore scripts location

 


 

So that’s almost everything. If you are building a PC game, these should be all the changes you need to make to setup the backend. However, if you want to create an online high score table for a webGL title – This setup won’t work – You’ll need to make one further edit to get everything up and running. After building a WebGL game using Unity 5, two folders will be created – Build and Release. In the build folder you’ll need find a .htaccess file which you’ll need to edit. The default the .htaccess file should look something like this;

Options +FollowSymLinks
RewriteEngine on

RewriteCond %{HTTP:Accept-encoding} gzip
RewriteCond %{REQUEST_FILENAME}gz -f
RewriteRule ^(.*)\.js$ $1\.jsgz [L]

RewriteCond %{HTTP:Accept-encoding} gzip
RewriteCond %{REQUEST_FILENAME}gz -f
RewriteRule ^(.*)\.data$ $1\.datagz [L]

RewriteCond %{HTTP:Accept-encoding} gzip
RewriteCond %{REQUEST_FILENAME}gz -f
RewriteRule ^(.*)\.mem$ $1\.memgz [L]

RewriteCond %{HTTP:Accept-encoding} gzip
RewriteCond %{REQUEST_FILENAME}gz -f
RewriteRule ^(.*)\.unity3d$ $1\.unity3dgz [L]

AddEncoding gzip .jsgz
AddEncoding gzip .datagz
AddEncoding gzip .memgz
AddEncoding gzip .unity3dgz

I stripped all the  AddEncoding lines from the file.

AddEncoding gzip .jsgz
AddEncoding gzip .datagz
AddEncoding gzip .memgz
AddEncoding gzip .unity3dgz

and added

AddType application/octet-stream .memgz .datagz .unity3dgz
AddType application/javascript .jsgz

<IfModule mod_headers.c>
Header set Access-Control-Allow-Origin "*"
</IfModule>

Resulting in (you should be able to just copy and paste this);

Options +FollowSymLinks
RewriteEngine on

AddType application/octet-stream .memgz .datagz .unity3dgz
AddType application/javascript .jsgz
Header set Access-Control-Allow-Origin "*"

RewriteCond %{HTTP:Accept-encoding} gzip
RewriteCond %{REQUEST_FILENAME}gz -f
RewriteRule ^(.*)\.js$ $1\.jsgz [L]

RewriteCond %{HTTP:Accept-encoding} gzip
RewriteCond %{REQUEST_FILENAME}gz -f
RewriteRule ^(.*)\.data$ $1\.datagz [L]

RewriteCond %{HTTP:Accept-encoding} gzip
RewriteCond %{REQUEST_FILENAME}gz -f
RewriteRule ^(.*)\.mem$ $1\.memgz [L]

RewriteCond %{HTTP:Accept-encoding} gzip
RewriteCond %{REQUEST_FILENAME}gz -f
RewriteRule ^(.*)\.unity3d$ $1\.unity3dgz [L]

With this in place I am able to get a list of current high scores – and add new ones from within a Unity WebGL game.

 


 

Play this build (WebGL)

 


 

Grab a copy of the project here

 


 

 


 

Next post: 1.7b Highscores (In-Game Overview)

Last post: 1.6 PickUps

Contents page.

 


 

Adventures in Unity – 1.6 PickUps

Official_unity_logo

 

#1 What would I ideally like to achieve by the time I finish working today – Add objects, floating above platforms which can be collected/picked-up by the player. Objects should be arranged into custom shapes.

#2 What’s the minimum I’d like to achieve by the close of play – Add objects, floating above platforms which can be collected/picked-up by the player.

 


 

It seemed a good time to add some pickups – Something like coins in a Mario game.

I had a vague idea of how I wanted the final code to work – Each platform would have X number of pickups above it & I liked the idea of being able to lay the pickups out in the shape of an image – Unfortunately, I had absolutely no idea of how to implement it.

The closest code reference I could think of was from the Unity tutorial Creating a basic platformer. In this 2D game a pickup is created and positioned in one of three  locations above a platform (left, right & middle of the platform). The pickup is a prefab (2D sprite) and the three positions are pre-defined transforms (set-up as child objects to the main platform element). When the platform is created, a simple C# script attached to the platform instantiates a pickup referencing one of the transforms (selected at random) for it’s position.

Basic2DPlatformer

That’s fine and groovy – Unfortunately in my game, platforms are generated with random heights – So I can’t use predefined positions for the pickups – & since the platform scale isn’t determined until after it’s instantiation – I can’t instantiate the pickups when the platform is created.

 


 

I played around with a few potential setups – Most of which I could get working after a fashion. However, I wanted to implement this as cleanly as possible, something that at best guess fit in with Unity’s structural practices & would make sense to anyone looking at the project.

This being my first project, I don’t really know how things should be structured – At the moment I’m working from best guesses.

With that in mind I ultimately decided to attach a pickup manager script to the platform prefab. This script would instantiate, manage and ultimate destroy a set of pickups positioned above the platform.

 


 

With a script added, I had to deal with the issue of how to instantiate the pickups. As I mentioned previously, a platforms height & position isn’t determined until after it has been created.

Knowing the width (localscale.x) of the platform allows me to identify how many pickups I can create (along the X axis) and stay within the bound of the platform.

Knowing the height (position.y + (scale.y/2)) of the platform allows me to position the pickups at a uniform position above each platform (along the Y axis).

My fudged work around to this, was to create the pickups in the first call of the Update() method rather than in a Start() or Awake() method. It’s not a particularly clean way to manage this issue, but it works without making too much mess.

All subsequent calls to the update();

  1. Automatically calculates the correct position for each pickup.
  2. Moves each pickup along the X axis at scroll speed (allowing them to move with the platforms) -also adding a little bounce (by stealing code from an older Xbox 360 project) to give the pickups a little personality.
  3. It also checks to see if a pickup has moved off-screen – If it has, the pickup is destroyed.
void Update()
{

    //---

    if (GameController.gameState == GameController.GameState.InGame)
    {
        if (pickups != null)
        {

            //---

            baseValueY = gameObject.transform.position.y + (gameObject.transform.localScale.y / 2) + scaleBound;

            float offset = ((sizeX * 1.0f) * scaleBound) / 2;
            baseValueX = gameObject.transform.position.x - offset;

            //---

            for (int ocounter = 0; ocounter < sizeX; ocounter++)
            {

                for (int icounter = 0; icounter < sizeY; icounter++)
                {

                    if (pickups[ocounter, icounter] != null)
                    {

                        //---

                        float bounce = Bounce(pickups[ocounter, icounter].transform.position);
                        pickups[ocounter, icounter].transform.position = new Vector3((baseValueX + (ocounter * scaleBound)), (baseValueY + (icounter * scaleBound) + bounce), 0);

                        if (pickups[ocounter, icounter].transform.position.x <= farLeft)
                            Destroy(pickups[ocounter, icounter]);

                        //---

                    }

                }
            }

            //---

        }
        else if (pickupInit == false)
        {
            PickUpInit();
        }
    }

    //---

}




 

Now the game automatically positions and manages (create, move, destroy) pickups – I need define a layout. When creating a layout for the pickups I tried two approaches;

The first was to create a rectangular ‘block’ of pickups above each platform.

The ‘PickUpInitBlock()‘ method creates 5 rows of pickups (sizeY = 5;) , each row containing X number of pickups – The number of pickups being determined by the width of the attached platform (sizeX = ((int)gameObject.transform.localScale.x) + 2;)

void PickUpInitBlock()
{

    sizeY = 5;
    sizeX = ((int)gameObject.transform.localScale.x) + 2;

    pickups = new GameObject[sizeX, sizeY];

    for (int ocounter = 0; ocounter < sizeX; ocounter++)
        for (int icounter = 0; icounter < sizeY; icounter++)
        {
            pickups[ocounter, icounter] = Instantiate(pickup, new Vector3(999, 999, 999), Quaternion.identity) as GameObject;
            pickups[ocounter, icounter].transform.Rotate(90, 0, 0);
        }

}

This worked well, but the number of pickups created seemed a little oppressive – Too many pickups and I found the game was less fun to play.

 


 

My next attempt/expansion was to try displaying out the pickups in a shape.

The way I implemented this is a bit of a cheat – It effectively uses the same process as in the ‘PickUpInitBlock()‘ method to create a block of pickups – However this time some pickups are turned ‘off’ from the start, allowing a ‘shape’ to be created.

To do this I needed a method of defining and storing shapes which could be quickly referenced at run-time – Something I can reference when instantiating the pickup objects.

With this in mind, I first needed to define/draw some test shapes to work with – To do this I drew a block of ‘.’ characters in an 8×8 square which operated as my blank canvas;

........
........
........
........
........
........
........
........

I then drew/filled in the shape using ‘*’ characters;

..****..
.******.
**.**.**
**.**.**
********
**.**.**
.*....*.
..****..

Finally, merged each line into a string which I could use for storage/retrieval;

..****...******.**.**.****.**.************.**.**.*....*...****..

After creating a few example shapes this way – I added them a string array;

string[] shapes = new string[7]
{
"****************************************************************", //block
"..****...******.**.**.****.**.************.**.**.*....*...****..", //circle
"...**......**.....****..********..****....****...**..**.**....**", //star
"...**.....****...******.********...**......**......**......**...", //arrowup
"....*.......**......***.****************....***.....**......*...", //arrowleft
"...*......**.....***....****************.***......**.......*....", //arrowright
"...**......**......**......**...********.******...****.....**..." //arrowdown
};

Now, when instantiating the pickups, the game creates an 8×8 array of ‘pickup’ objects – randomly selects one of the shapes from the ‘shapes’ array, then loops through the string char-by-char. If the character returned is a ‘*’ it creates a pickup, otherwise it sets the value to null.

Since the Update() method manages the position of each pickup , this method just needs to place them in the correct position in the ‘PickUp’ array;

void PickUpInitShape()
{

    sizeY = GetSize((int)gameObject.transform.localScale.x);
    sizeX = sizeY;

    string shape = GetShape(sizeY);

    //---

    if (shape == "")
        PickUpInitBlock();

    else
    {

        //---

        pickups = new GameObject[sizeX, sizeY];
        int fullSize = (shape.Length - 1);

        for (int ocounter = 0; ocounter < sizeX; ocounter++)
            for (int icounter = 0; icounter < sizeY; icounter++)
            {
                if (shape[fullSize - ((ocounter * sizeY) + icounter)] == '*')
                {
                    pickups[icounter, ocounter] = Instantiate(pickup, new Vector3(999, 999, 999), Quaternion.identity) as GameObject;
                    pickups[icounter, ocounter].transform.Rotate(90, 0, 0);
                }
                else
                    pickups[icounter, ocounter] = null;
            }

        //---

    }

    //---

}

The problem was how to ensure the shapes stayed within the bounds of a randomly sized platform – For a thin platform an 8×8 block of pickups may be too wide.

I only needing a basic implementation that would work for now, but which could potentially expand on at a later date (possibly add more shapes & sizes, set different colours, possibly rotate them around a central point – that kind of thing).

I’m not completely happy with the current implementation, hopefully future amendments will help tighten things up[ quite a lot.

The current fix was to create another sets of shapes – One set  defined within an 8×8 block the other within a 5×5 block.

string[] shapes5 = new string[7]
{
"*************************", //block
"..*...***.*****.***...*..", //circle
"*.*.*.***.*****.***.*.*.*", //star
"..*...***.*****..*....*..", //arrowup
"..*....**.*****..**...*..",  //arrowleft
"..*...**..*****.**....*..",  //arrowright
"..*....*..*****.***...*.."  //arrowdown
};

In implementation – The code first checks the platforms scale.x as an int and determines it’s available width.

public int GetSize(int inWidth)
{

    if (inWidth >= 8)
        return 8;

    else if (inWidth >= 5)
        return 5;

    else
        return inWidth;

}

If the platform can accommodate the 8×8 pickups it randomly selects an 8×8 pattern to use – If 8×8 is too large, it checks to see if a 5×5 pattern would suit – If that fails, it reverts back to the original code and creates a block of pickups instead.

void PickUpInitShape()
{

    sizeY = GetSize((int)gameObject.transform.localScale.x);
    sizeX = sizeY;

    string shape = GetShape(sizeY);

    //---

    if (shape == "")
        PickUpInitBlock();

    else
    {

        //---

        pickups = new GameObject[sizeX, sizeY];
        int fullSize = (shape.Length - 1);

        for (int ocounter = 0; ocounter < sizeX; ocounter++)
            for (int icounter = 0; icounter < sizeY; icounter++)
            {
                if (shape[fullSize - ((ocounter * sizeY) + icounter)] == '*')
                {
                    pickups[icounter, ocounter] = Instantiate(pickup, new Vector3(999, 999, 999), Quaternion.identity) as GameObject;
                    pickups[icounter, ocounter].transform.Rotate(90, 0, 0);
                }
                else
                    pickups[icounter, ocounter] = null;
            }

        //---

    }

    //---

}

 


 

Now we have pickup objects which can be created, positions, moved and destroyed. However they can’t actually be ‘picked up’.

The pickups themselves are prefabs made up of little more than cylinder objects scaled to a suitable coin-type size.

I set the attached capsule collider to function as a trigger . Attached a simple C# script whose main function is to check and see if the player has collided with the pickup – If a collision has occurred, the pickup is destroyed, a sound effect plays and the score is updated.

void OnTriggerEnter(Collider other)
{
    if (other.gameObject.CompareTag("Player"))
    {
        gameController.AddScorePickupHit();
        Destroy(gameObject);
    }
}

 


 
That’s about it.

I did update the jump code again – Modifying the original jump code to allow for double-jumps. I think the game plays better with this code -But I’m not sure how apparent the double jumps are to other players?

 


 

Play this build (WebGL)

 


 

Grab a copy of the project here

 


 

 


 

Next post: 1.7a Highscores (Server Side)

Last post: 1.5 Initial Audio

Contents page.

 


 

Adventures in Unity – 1.5 Initial Audio

Official_unity_logo

 

#1 What would I ideally like to achieve by the time I finish working today – Add initial audio to the game (in game sound effects & background music) – Update GSM to allow the audio to be turned off/on.

#2 What’s the minimum I’d like to achieve by the close of play – Add initial audio to the game (in game sound effects & background music).

 


 

There are two parts to adding audio to the game – one part is adding the audio, the other is adding the option to turn off the audio.

When developing I updated the GSM first, then added the audio – But for the point of explaining what I did, I’ll start with the sound effects first.

 


 

Adding Audio:

There are quite a few methods for managing audio in Unity. This being my first attempt I wanted to keep things nice and simple. With that in mind, I currently use five sound files – ‘player jump‘, ‘player land‘, ‘background music‘ and a ‘game over tune‘ for in-game sounds & a ‘button down‘ beep for the GSM.

 

I attached ‘Player jump‘ and ‘player land‘ to the ‘player‘ Unity object – I added two audio sources components to the ‘player‘ object, plugged in the desired mp3 sounds and made sure ‘play on awake‘ was deselected.

I have a script called ‘player.cs‘ attached to the ‘player‘ object – This is used to manager the player – Read user input, update when jumping, etc. To this I added a couple of global audio source variables, initialising them in the Awake() method;

AudioSource[] sounds = GetComponents<AudioSource>();
audioJump = sounds[1];
audioLand = sounds[0];

Then when the player jumps, I play the jump sound effect;

if(isJumping && grounded)
     audioJump.Play();

And I set up a public method, so when the player collides with a platform- When the OnCollisionEnter method associated with the ‘platform‘ object is called, the’ land sound‘ effect is played;

public void PlayHit()
{
     audioLand.Play();
}

 

Background music‘ & ‘game over tune‘ are both handled by the GameController object & associated script.Plugged two audio sources into the GameController object – turned off ‘play on awake‘ but for the background music I turned off ‘loop‘. An attached C# script called GameController which I use primarily to handle the game states was used to manage the audio source’s – Again, like with the player object I created two global variables for each source, I initialised them/plugged them in when Awake() is called. I then created a small method which I invoke to play/stop them as required (at the start of the game and end of the game);

public void InGameMusic(bool doPlay)
{
     if (doPlay)
          audioMusicInGame.Play();
     else
     {
          audioMusicInGame.Stop();
          audioMusicEndGame.Play();
     }
}

 

That’s pretty much it – I use  completely different approach for the button clock sound effect, which I’ll explain in the GSM section below.

 


 

Updating the GSM:

When adding sound effects I thought I’d better update the GSM to allow the player to turn the sound on or off as desired.

This turned out to be a much more involved process than I’d anticipated.

 

I removed the ‘press space to play‘ message from the GSM and replaced them with three UI button objects ‘play‘, ‘sound on/off‘ & ‘FreeSFX website link

I grabbed the sound effects from a website called ‘FreeSFX.co.uk‘ – Which provides the sounds free of change so long as they are credited – I added the button since this seemed the easiest way to do so.

For the ‘play‘, ‘sound on/off‘ buttons images, I re-used two pngs I’d created for a number of Android game I developed last year.

FreeSFX website link‘ button – I nabbed from the websites twitter page.

freesfx

 

When a player clicks on the ‘play‘ button – It references a method in the ‘GameController.cs‘ script setting a bool called ‘clickPlay‘ to true.

public void ClickPlay()
{
     clickPlay = true;
}

In GameController’s update method a check is made to see if the user has pressed/os pressing the space bar, or if ‘clickPlay‘ is true – If either are, it starts the game.

if (Input.GetKeyDown(KeyCode.Space) || clickPlay)

 

When the user clicks on the ‘sound on/off‘ button – It references a method in the ‘GameController.cs‘ script which switches a bool called ‘clickSound‘ between true & false (play sound, don’t play sound).

public void ClickSound()
{

     //---

     clickSound = !clickSound;

     SetSound();

     //---

}

It then runs a second method called ‘SetSound‘ which, depending on  ‘clickSound‘s current value, changes the ‘sound on/off‘ buttons current image & sets the  AudioListener‘s volume respectively.

 public void SetSound()
 {

     //---

     if (clickSound)
     {
          buttonSound.image.sprite = soundOnImage;
          AudioListener.volume = 1;
     }
     else
     {
          buttonSound.image.sprite = soundOffImage;
          AudioListener.volume = 0;
     }

     //---

}

 

When the user clicks on the ‘FreeSFX website link‘ button – It references a method in the ‘GameController.cs‘ script which opens a URL to the freesfx website;

public void ClickFreeSFX()
{
     Application.OpenURL("http://www.freesfx.co.uk/");
}

 

In the original Android app, when the player touched a ‘button‘, I shrank the image a little; giving the impression it was being pushed by the user’s action. I wanted to replicate this when using the buttons in the game. It took a little while for me to work how best to implement feature – Eventually I settled on creating a small animation to perform the action for me.

This was a surprisingly involved process – Adding ‘Animator‘ element to the GUI Button object, selecting the ‘auto generate animation‘ button, saving the generated animation file to the assets folder, ensure the animation for the desired button state doesn’t loop, then opening the animation window, selecting the desired button state & creating an animation using the timeline – So now the buttons shrink a little when selected.

ButtonResizeAnimation

 

I also wanted to add a sound effect when the button was clicked – This was much harder to work out than I’d have guessed. Ultimately I implemented the feature by, first adding an audio source element to the button, then adding an ‘EventTrigger‘ script – Which plays the sound effect ‘on pointer Down

ButtonClick

 


 

 

That’s about it – I’ll probably switch sound effects in and out as the game develops – I may try being a little more adventurous with the jump noise – Having a sound play throughout the jump, increasing/decreasing the pitch as the play ascends then descends- We’ll see.

For my next step; I’m not sure – I wanted to build an online highscore table – But so far, everything I’ve found online either requires an account (e.g. google play) or needs a mySQL database (unfortunately I’m too poor to afford that level of hosting).

Maybe I’ll start by adding something for the player to pick up during the game – a secondary objective?

 


 

Play this build (WebGL)

 


 

Grab a copy of the project here

 


 

 


 

Next post: 1.6 PickUps

Last post: 1.4b Tighten & Tidy

Contents page.

 


 

Adventures in Unity – 1.4b Tighten & Tidy

Official_unity_logo

 

There were a few sections of code I added during the ‘tighten & Tidy’ process that I thought I’ve cover in a little more detail; Collision detection, jumping & platform generation.

 


 

Collision Detection

One of the problems with the code prior to the update, was that a player was able to jump whenever they wanted – whether they were rolling along a platform or already in mid-air. This allowed players to potentially ‘fly’ though the game – even jumping up, safely outside the screen\gamer area.

It seemed more sensible if the player could only jump when they were ‘on’ the platform – A ground level from which they could jump – No jumping in mid-air and no jumping if they fall between platforms.

I cheated a little with the solution – Rather than creating a universal collision detection test, I relied on the idea that platforms implemented used non-rotated/rotating box colliders.

I created a C# script called ‘platform.cs‘ which is attached to the platform prefab.

I check ‘OnCollisionEnter‘ & ‘OnCollisionStay‘ – ‘OnCollisionStay‘ is used for situations where the player-platform collision occurs/registers first against the side of the platform rather than the top.

The collision check itself first confirms that the ‘player‘ component is being tested for collision;

if (collision.gameObject.CompareTag("Player"))

if that is the case, it then checks to see if the collision has occurring against the top of the cube;

if (collision.contacts[0].normal == -(transform.up))

If all these conditions are met,  the player component is referenced directly and the players static bool ‘grounded‘ is set to true (allowing jumps to be performed).

Also at this point we check the platforms colour, if it’s set to white (it’s original colour) – We change it to something a little brighter. If the colour is still set, we keep things as they are (to avoid flashing cubes)

& that’s it – Unity’s built in physics engine does the hard work of keeping the objects apart.

void PerformCollisionCheck(Collision collision){

     //Ensure we are checking aginst the player.
     if (collision.gameObject.CompareTag("Player")){

          //---

          //If the top of the cube is being hit
          if (collision.contacts[0].normal == -(transform.up)) {

               //Grab the player
               Player player = collision.gameObject.GetComponent<Player>(); 

               //Set 'grounded' to true (so the player can jump again)
               player.grounded = true; 

               Renderer rend = GetComponent<Renderer>();

               //If the cubes colour hasn't already been set
               if (rend.material.GetColor("_Color") == Color.white) {

                    //Change it from white
                    Color whichColour = WhichColour(); 
                    rend.material.SetColor("_Color", whichColour);
                    rend.material.SetColor("_SpecColor", whichColour);
                    rend.material.SetColor("_EmissionColor", whichColour);
                    rend.material.SetColor("_ReflectColor", whichColour);

                    //Update the players score.
                    gameController.AddScore(GetColor()); 

               }
          }

     //---

     }

 }

 


 

Jumping

The original jump code was taken from the Unity Basic Platform Game tutorial. It’s pretty straight forward, and it does a good job.

Update‘ method checks for user input;

if (!GameController.inGame)
     rb.transform.position = new Vector3(0, 11, 0);
else if (grounded && Input.GetButtonDown("Jump")){
     jump = true;
     grounded = false;
}

If so, the ‘FixedUpdate‘ method applies force to the ‘rigidbody‘ – performing the actual ‘jump’;

if (jump)
{
    velocityY = JumpLaunchVelocity * (1.0f - Mathf.Pow(jumpTime / MaxJumpTime, JumpControlPower));
            
    rb.AddForce(new Vector3(0f, jumpForce, 0f));
    jump = false;
}

This worked well – Certainly well enough that I may reimplement it by the time this game is finished.

The problem is, platforms have random heights & widths – & using a static jump size sometimes made the game feel a little unfair – Some ‘game over’ screens felt undeserved.

Instead I thought I’d try allowing the player to control their jump a little more. The longer the player holds down the jump button – The higher they jump.

This, I hoped, would allow a greater deal of control for the player & making the game a little more skill based & putting the reason for the ‘game over’ back in their court.

I wrote a game for the Xbox 360 a year or two back called Magic Thighs & Slightly Phil – A Bomb Jack clone which implemented this kind jump system.

So, rather than reinvent the wheel, I used a modified (simplified) version of the code for this project.

 

The updated jump code follows the same structure as the original jump code; the update function checks for user input;

if (GameController.gameState != GameController.GameState.InGame)
    rb.transform.position = new Vector3(0, 11, 0); //Set game init position

else
{

    //---

    isJumping = false;

    if (Input.GetButtonDown("Jump") || Input.GetButton("Jump"))
        isJumping = true;

    //---

}

While the ‘FixedUpdate‘ method applies force to the ‘rigidbody

Though the jump code is now a little more complex (though still pretty simple).

With this jump code, we use a timer. When the player initially presses jump, the timer is started.

if ((!wasJumping && grounded) || jumpTime > 0.0f)
{
     jumpTime += Time.deltaTime;
 }

While the timer is running we apply force to the player objects ‘rigidbody

velocityY = JumpLaunchVelocity * (1.0f - Mathf.Pow(jumpTime / MaxJumpTime, JumpControlPower));                 

rb.AddForce(new Vector3(0f, velocityY, 0f));

Once the timer reaches a predefined ‘MaxJumpTime‘ or the player releases the jump button, we stop applying velocity to the ‘rigidbody‘ and don’t allow the player to try jumping again until he hits the ground/platform top

if (isJumping)
{

    if ((!wasJumping && grounded) || jumpTime > 0.0f)
    {
        jumpTime += Time.deltaTime;
    }

    // If we are in the ascent of the jump
    if (0.0f < jumpTime && jumpTime <= MaxJumpTime)
    {                 

        velocityY = JumpLaunchVelocity * (1.0f - Mathf.Pow(jumpTime / MaxJumpTime, JumpControlPower));                 
        if (velocityY > jumpForce)
        {
            velocityY = jumpForce;
        }

        rb.AddForce(new Vector3(0f, velocityY, 0f));

    }

    else // Reached the apex of the jump
    {
        jumpTime = 0.0f;
    }

}

else // Continues not jumping or cancels a jump in progress
{
    jumpTime = 0.0f;
}

wasJumping = isJumping;

 

It’s not perfect, but, for now at least, it does the job.

 


 

Platform Generation

I’m intending to play around with platform a little before this game is complete, so for the moment I’ve kept things pretty simple.

The idea is I need each platform to be a different height – Not so different that the player cant jump onto it – But not so similar that the game doesn’t present a challenge.

I populate the level with platforms (from left to right) – Using a ‘farLeft‘/’farRight‘ variables to determine desired start and end ‘X‘ positions.

Every game loop I scroll the platforms left a little – Removing the platforms whose ‘X‘ position is < ‘farLeft‘ – Adding platforms until the ‘X‘ position of right most platform  reaches (or exceeds) ‘farRight‘.

private void PopulatePlatforms()
{

    float posX = farLeft;
    if (platforms.Count <= 0)
        initalPlatform();

    //---

    //Find the X psition of the last platform in the list (furthest right)
    posX = (platforms[platforms.Count - 1].transform.position.x + (platforms[platforms.Count - 1].transform.localScale.x / 2)) + 1;

    //If we don't have enough platofrms to fill the screen - we need to add some
    while (posX < farRight)
    {

        float width = Random.Range(widthMin, widthMax); //randomise platform width
        float height = NextHeight(); //Find platforms height

        posX += width / 2; //Find the relevent X pos for the new platform.

        //Combine and define the final position
        Vector3 position = new Vector3(posX, basePosition + (height / 2), 0); 

        //Combine and define the final scale
        Vector3 scale = new Vector3(width, height, 10); 

        //---

        //Add the platform
        platforms.Add(Instantiate(platform, position, Quaternion.identity) as GameObject);
        platforms[platforms.Count - 1].transform.position = position;
        platforms[platforms.Count - 1].transform.localScale = scale;

        //Update the X pos so the while loop to chack (+1 to add a space between platforms)
        posX += (width / 2) + 1; 

    };

}

When adding a new platform I randomise the platforms width (within a range) – Use the ‘NextHeight‘ method to determine it’s height.

NextHeight‘ initaly sets the height of the new platform to match that of the last platform.

It them applies a random value to the height (within a range). This range is between -‘heightRangeMax‘ and +’heightRangeMax

height = lastHeight + Random.Range(-heightRangeMax, heightRangeMax);

heightRangeMax‘ represents the largest difference in height between platforms that a player can still be expected to jump up to.

(Note: The minimum range could really be any height, since falling isn’t a problem, for for now this seems more succinct).

After generating the platforms height, which we ensure the platform stays within the acceptable maximum and minimum height for the play area/screen.

        if (height > heightMax)
            height = heightMax;
        else if (height < heightMin)
            height = heightMin;

We make sure to the difference in height between the last platform and the new platform is suitably large.

while (Mathf.Abs(height - lastHeight) <= heightRangeMin)

& If everything looks good, we update the ‘lastHeight‘ variable with this platforms height and return to ‘PopulatePlatforms‘.

float NextHeight()
{

    float height = 0;

    do
    {

        //Randomise a height for the new platform
        height = lastHeight + Random.Range(-heightRangeMax, heightRangeMax);

        //Ensure the height isn't too large or too small
        if (height > heightMax)
            height = heightMax;
        else if (height < heightMin)
            height = heightMin;

    } while (Mathf.Abs(height - lastHeight) <= heightRangeMin); //Check the height difference is at least equal to the minimum acceptable size difference.

    lastHeight = height; //Save height to last height so the next platform has something to measure against.

    return height;
}

 


 

There;s lots more to tweaks with this code still to go – The player jump feels a little sluggish & the size difference between platforms is still a little small.

But I can tweak these as things go on.

Next I’m going to try to add a little audio to the game.

(I’ve absolutely been relying far too much on the ‘energetic‘ tune from Microsoft’s ‘MovieMoments‘ app to keep things sounding interesting).

 


 

Play this build (WebGL)

 


 

Grab a copy of the project here

 


 

 


 

Next post: 1.5 Initial Audio

Last post: 1.4a Tighten & Tidy

Contents page.