In this tutorial we'll introduce scoring with and more complex gameplay with a variety of different pickups to collect, some hindering the player and some providing speed boosts.
We'll also introduce a countdown Timer complete with a HUD displaying both the score and time remaining, as well as adding everyone's favourite feature - particles!
Step 1: Creating Different Pickups
Our mini game is going to have several different types of pickups for the user to collect. Type 1 will simply increment the score, type 2 will add or subtract a random score and type 3 will either speed up or slow down the player. To make it more interesting the pickups will randomly change while the player is playing.
The first step is to create a new script that will define what type of pickup each instance is.
Step 2: The Pickup.js Script
Open your project from last time or download the source from above; then within the Project Window right-click on the Scripts folder and select Create -> JavaScript; rename it Pickup.
We'll now add this new script as a Component of our pickup prefab, so select the 'pickup-prefab' from within the Prefabs folder and drag the Pickup script onto the Inspector Window (or onto the 'pickup-prefab' itself).
Every instance of the pickup that's now spawned will feature this new script which will determine the type of pickup it is and various other properties. Let's now define these types.
Step 3: Determining the Type of Pickup
When the pickup is spawned we're going to decide randomly what type it is, so add the following code to the Pickup script:
// variable to hold the type public var type:int; function Awake() { // set a random type to begin SwapType(); } function SwapType() { // generate a random number between 1 and 10 var random:float = Random.Range(1,10); // set the type if (random <= 5) // 50% for type 1 { type = 1; } else if (random <= 8) // 30% for type 2 { type = 2; } else // 20% for type 3 { type = 3; } }
The above block of code defines a variable 'type' which, when the script initialises, is randomly set to a value of 1 (standard point pickup), 2 (point gamble pickup) or 3 (speed powerup) to define the type of the pickup.
We generate the random number using Random.Range(...) and then use a series of if statements to increase the probability of it being a standard point pickup (type = 1) and therefore make the speed powerups more rare to come across.
Step 4: Randomly Changing the Pickup
Once instantiated the pickup will currently remain the same type for the duration of its lifetime. Let's make it a little more interesting by making it change type after a random number of seconds.
Add the following variables and Start() function to the script:
// min and max times for type change public var minTypeChange:int = 10; public var maxTypeChange:int = 30; function Start() { while (true) { // wait for X seconds yield WaitForSeconds(Random.Range(minTypeChange, maxTypeChange)); // set a random type SwapType(); } }
This essentially starts a while loop which will run forever since the condition being tested is always true. The first part of the statement uses a yield WaitForSeconds(...) statement to create a delay for a random number of seconds, between the min and max values specified, before then swapping the type of the pickup by calling the SwapType function we defined earlier on.
So the game should currently work as we expect - but the player has no idea what type of pickup they are collecting! So now let's brighten up our pickup a little to distinguish between the different types. We'll use everyone's favourite feature...
Step 5: Everyone's Favourite: Particles!
So that the player can tell the difference between the pickups, we're going to add different coloured particle systems in the centre of the pickups.
Drag an instance of the pickup-prefab into the scene and focus on it by either using the F key or double-clicking on it within the Hierarchy. Add a Particle System using the top menu: GameObject -> Create Other -> Particle System. Make it a child of the pickup-prefab by dragging it onto the pickup-prefab within the Hierarchy; click Continue when the below dialog warns you about losing the prefab connection.
Use the Inspector to position the Particle System at (0, 0, 0) so that it's in the centre of the pickup model as illustrated below.
Note: if you have the particle system or one of its parents selected then the particle system will emit particles within the Scene Window; it will otherwise cease emitting the particles until reselected.
So you should now have your first particle system positioned in the centre of the pickup.
Particle Systems within Unity are made up of three Components: an Emitter, an Animator and a Renderer. Their names are quite self explanatory and each features a handful of public variables for you to customise, allowing all kinds of awesome visual effects to be produced.
You can read more about each on their Component Reference pages that are linked above. You can also find their Script Reference pages here: Emitter, Animator and Renderer.
Currently our particle system is quite boring, right? Select the particle system and within the Inspector adjust the Emitter Component to match the values below:
All we've done is alter the energy that the particles have, affecting how long they 'live' for, and shrunk the Ellipsoid where they are emitted from so you should now notice that the particles are now all emitted much more closely together in the centre of the pickup.
Now match your particle's Animator Component to the values shown below:
The changes we've made here are the addition of a random force to the particles on each axis, to make them appear more natural, and a size grow value of 1 so that they'll grow larger over time as shown below.
That's our particle system complete; we'll adjust the colours using our Pickup script.
Finally, to update the pickup prefab, with the pickup-prefab object within the hierarchy selected, click the Apply button at the top of the Inspector. You can then delete the pickup from the Hierarchy.
Let's now adjust our Pickup script to alter the colour of the particles depending on what type of pickup it is.
Step 6: Particle Colours
As you may have noticed within the particle systems Animator Component a particle can animate through five different colours, each of which may have a colour and alpha (transparency) value.
If you inspect the Script Reference page for the Particle Animator and take a closer look at the colorAnimation property you'll notice that it's an Array of Color Objects.
Let's now introduce a variable to store the pickup's particle system so we can access its colours, and introduce three new Arrays (datatyped to Color
) to our Pickup script. Go ahead and add the following code to the script:
// the particles gameobject public var particles:GameObject; // colours for each type of pickup public var type1:Color[]; public var type2:Color[]; public var type3:Color[];
Save the script and reselect the pickup-prefab within the Project Window. Expand the pickup-prefab to view its children and drag the particle system which is a child onto the Pickup Components new particles variable.
You should also now notice the three new arrays within the inspector; set each of their sizes to 5 as below since this is how many colours the particles need to animate through.
The colours are all currently black, which isn't so exciting, so select the first element of Type1 and within the resulting colour palette select a bright colour (I used green below) and set the alpha (A) to 50.
Repeat this for the remaining colours of Type 1, increasing the alpha to full value and then back out again so the particles fade in and out - the alpha value for each colour is represented by the white bar along the bottom of the colour as shown above. I also chose to use a slightly darker green for when the particle is fading out - you can of course colour them however you wish.
Repeat this process using different colours for Type2 and Type3 as I have below.
So, that's our colours created. The last thing to do is to actually assign them to the colorAnimation
property of the particle system's Animator.
It's worth noting, as mentioned in the colorAnimation Script Reference, that when modifying the colours via code you must grab, modify and re-assign the whole array rather than individual colours. We can thankfully just overwrite the current colorAnimation Arrays with our own ones, which is nice and easy.
Step 7: Assigning Particle Colours at Runtime
Introduce the following variable and overwrite the current Awake() function with the one below, which retrieves and stores the particles ParticleAnimator Component for us to use later on.
// the particles animator component private var particleAnimator:ParticleAnimator; function Awake() { // retrieve the particle animator Component from the particles particleAnimator = particles.GetComponent(ParticleAnimator); // set a random type to begin SwapType(); }
Finally, overwrite the current SwapType() function with the below:
function SwapType() { // generate a random number between 1 and 10 var random:float = Random.Range(1,10); // set the type if (random <= 5) // 50% for type 1 { type = 1; particleAnimator.colorAnimation = type1; } else if (random <= 8) // 30% for type 2 { type = 2; particleAnimator.colorAnimation = type2; } else // // 20% for type 3 { type = 3; particleAnimator.colorAnimation = type3; } }
This introduces an extra line for each condition of the if
statement, assigning the relevant array to the colorAnimation property of the particleAnimator variable. Job done.
If you now play the game the powerups will each have coloured particles within them and there should be more with the colour used for Type1 than Type2 or Type3. They do still look a little boring, however, since they're just static on the spot; let's quickly add a little random rotation to them.
Step 8: Randomly Spinning Pickups
This is a dead simple addition, but it will make our pickups look a little more interesting.
Add the following variables and Update()
function to the Pickup script:
// min and max rotation speeds public var minRotationSpeed:float = 0.5; public var maxRotationSpeed:float = 2.0; // calculated rotation speed private var rotationSpeed:float; function Update () { // rotate the gameobject every frame transform.Rotate(rotationSpeed, rotationSpeed, 0); // rotate the particles every frame (so they rotate at twice the speed of the whole object) particles.transform.Rotate(rotationSpeed, rotationSpeed, 0); }
Then, overwrite the Awake()
function with the following, which calculates the random rotationSpeed when the script initializes:
function Awake() { // calculate a random rotation speed rotationSpeed = Random.Range(minRotationSpeed, maxRotationSpeed); // retrieve the particle animator Component from the particles particleAnimator = particles.GetComponent(ParticleAnimator); // set a random type to begin SwapType(); }
If you now run the game again, the pickups should all rotate at random speeds! Time to start scoring!
Step 9: Deciding on Game Resolution
Before we construct our interface we have to decide on the resolution of our game and ensure our Game Window is of this resolution so we can accurately position our interface elements.
I'm using the default WebPlayer export sizes (600x450) which you can change using the top menu, Edit -> Project Settings -> Player. You can also decide which version of the WebPlayer template you wish to use.
Once you've decided on the resolution of your game you need to set your Game Window to be of the same size, you can do this using the menu in the top left hand corner of the Game Window shown below
We can now accurately position our interface elements within the Game Window, it's important to remember to position interface elements whilst observing the Game window, NOT the Scene Window.
Lets now start making our interface...
Step 9: Introducing GUITextures
Before we start scripting for the scoring we'll construct the interface to hold the score by introducing GUITextures (GUI = Graphical User Interface), which will form the static portion of the interface.
Download this package which contains the font and texture required for our interface. The Font used is called IrisUPC and is available here. Double-click the package to extract the contents and click Import to import all the assets when prompted with the Import dialog window:
You'll now have two new folders within your Project Window: Fonts and GUI.
Let's now start creating our game's GUI. First, add a new Empty GameObject (using the top menu), rename it to "GUI" and position it at (0, 0, 0). This will be the container for our GUI so our Hierarchy is neatly organised.
Expand the GUI folder within the Project Window and select the 'game-gui' and using the top menu add a GUITexture: GameObject -> Create Other -> GUITexture. You should now have something similar to below:
So, at the moment this isn't too great since we want to align this graphic with the top of the screen.
First, let's alter its pixel inset within the Inspector so that its pivot point is no longer in the centre, and then reposition it along the top of the screen. Select the game-gui and match its Transform and GUITexture Components to those shown below:
What we've done here is altered the GUITexture pixel inset Y value to -64 (which is the height of the image), so its pivot point is along the top of the image. Now when we set the Y value within the Transform Component to 1, the GUITexture aligns perfectly with the top of the screen; there would otherwise be a gap of 32px.
Remember: When positioning GUI Elements within Unity, (0,0) is the bottom left corner of the screen and (1,1) is the top right corner of the screen.
So that's it for our GUITexture; time to add the GUIText...
Step 10: Introducing GUIText
With the graphics for our interface in place it's now time to introduce GUIText which will display the player's score and the time remaining.
Expand the Fonts folder, select UPCIL within Iris, and then create a GUIText GameObject using the top menu: GameObject -> Create Other -> GUIText. Rename it to txt-score and make it a child of GUI.
You should now have some small text saying 'Gui Text' appear in the centre of the screen using the UPCIL font. Note: When creating GUIText it's easier to select the font before creating the GUIText than to set it afterwards.
Within the Inspector, match the Transform and GUIText Components' settings to those shown below:
The above Transform Component positioning will align the GUIText as shown below with the 600x450 WebPlayer resolution - if you are using a different resolution you may need to play with the values to align it correctly. Note that, by setting the Z axis value to 1, the GUIText gets positioned in front of the GUITexture.
We set the text to '888' since this is the widest it will ever be; we've also changed the anchoring to centre the text rather than having it aligned to the left. Finally we've enlarged the font size to 36:
Now follow the exact same steps again - but rename the GUIText to txt-time - and set its Transform Component to the settings shown below so that you have something similar to the image:
So that's the building of the interface complete, now it's time to hook it all together.
Step 11: Connecting the Score
Reopen the PickupController script and add the following two variables and function to the script:
// textfield to hold the score and score variable private var textfield:GUIText; private var score:int; function UpdateScoreText() { // update textfield with score textfield.text = score.ToString(); }
Pretty self explanatory here. Two variables, one to track the score and one to store the textfield the score's displayed in. The UpdateScoreText()
function sets the text of the textfield to the string value of the score variable using the ToString()
function.
Before this function is called, however, we must assign our textfield a value. Add the following code to the Awake()
function:
textfield = GameObject.Find("GUI/txt-score").GetComponent(GUIText);<br /> score = 0; UpdateScoreText();
This will find the 'txt-score' GameObject - assuming you've placed it within your Hierarchy as asked - and retrieves its GUIText component. The score variable is then set to 0 and UpdateScoreText()
is called.
Currently you should get a 0 on the screen when clicking Play; however there is nothing to increment the score, so replace the Collected(...)
function with the one below:
function Collected(pickupCollected:GameObject) { // retrieve name of the collected pickup and cast to int var index:int = parseInt(pickupCollected.name); // pickup has been destroyed so make the spawn index available again spawnIndexAvailableList[index] = true; // retrieve the pickup state var type:int = pickupCollected.GetComponent(Pickup).type; // update the score depending on the type of pickup if (type == 1) { score+=2; // simply add 2 to the score } else if (type == 2) { score += Random.Range(-2, 5); // add a random score between -2 and 5 to the score } // update the score UpdateScoreText(); // destroy the pickup Destroy(pickupCollected); // wait for a random amount of seconds between minimumSpawnDelayTime and maximumSpawnDelayTime yield WaitForSeconds(Random.Range(minimumSpawnDelayTime, maximumSpawnDelayTime)); // spawn a new pickup SpawnPickup(); }
You should notice we've added a few lines to the centre of the function to retrieve the type variable from the pickup that was collected and then increment the score by either a static value of 2 for pickups of type 1, or a random value between -2 and 5 for both type 2 and 3 pickups.
The textfield is then updated after the score is adjusted. Easy, right?
Before making the timer display lets make one of our pickups affect the player's speed.
Step 12: Speed Boosts!
Open the PlayerController script and add the following variables:
// the PickupController component of the 'PickupSpawnPoints' gameObject private var characterMotor:CharacterMotor; // speed variables public var minSpeed:int = 5; public var maxSpeed:int = 35; private var originalSpeed:int;
Now replace the current Awake()
function with the following:
function Awake() { // retrieve the PickupSpawnPoints gameObject var pickupSpawnPoints:GameObject = gameObject.Find("PickupSpawnPoints"); // and then retreive the PickupController Component of the above PickupSpawnPoints gameObject pickupController = pickupSpawnPoints.GetComponent("PickupController"); // retrieve the players CharacterMotor Component characterMotor = GetComponent(CharacterMotor); // retrieve the players original speed from the characterMotor originalSpeed = characterMotor.movement.maxForwardSpeed; }
So what's changed? We've introduced a characterMotor
variable which retrieves the CharacterMotor Component of the player where we can access the player's speed. We also introduced several speed variables to track the original speed and set minimum and maximum speed values.
This is all great so far, but it won't make a difference within our game since we're only storing the original speed. We need to adjust the speed when the player collides with one of the pickups.
Replace the OnControllerColliderHit(...)
function with the below:
function OnControllerColliderHit (hit:ControllerColliderHit) { // if the player hits a pickup if (hit.gameObject.tag == "Pickup") { // call the Collected(...) function of the PickupController Component (script) and // pass the pickup we hit as the parameter for the function pickupController.Collected(hit.gameObject); // if the pickup is set to type 3 then adjust the players speed if (hit.gameObject.GetComponent(Pickup).type == 3) { // stop any coroutines already running StopCoroutine("AdjustSpeed"); // create new coroutine for AdjustSpeed StartCoroutine("AdjustSpeed"); } } }
So we've added an extra if statement that tests whether the pickup type is equal to 3 which will then adjust the speed via the AdjustSpeed()
function. Go ahead and add the AdjustSpeed()
function below to the script:
function AdjustSpeed() { // retrieve random number between 0 and 1 var rand:float = Random.Range(0.0, 1.0); if (rand <= 0.6) // 60% for speed boost { characterMotor.movement.maxForwardSpeed = maxSpeed; } else // 40% for slow down { characterMotor.movement.maxForwardSpeed = minSpeed; } // wait for 5-10 seconds before resetting the speed to the original speed yield WaitForSeconds(Random.Range(5,10)); characterMotor.movement.maxForwardSpeed = originalSpeed; }
The AdjustSpeed()
function generates a random number, which is then used to decide whether to speed up or slow down the player. The function then waits for a time between 5 and 10 seconds before re-assigning the original speed. If you now play the game this should all appear to work correctly.
There is a problem, however.
If you collect a type 3 pickup and then collect another type 3 pickup before the original speed has been reset, then the function is still waiting and so the original speed will be reset far earlier than expected.
The approach we're going with use to solve this issue is by using StartCoroutine(...), passing the function name as the parameter, so we can later use StopCoroutine(...) which will remove the yield WaitForSeconds(...) statement within the function.
Replace the call to AdjustSpeed()
within the if
statement with the following code:
// stop any coroutines already running StopCoroutine("AdjustSpeed"); // create new coroutine for AdjustSpeed StartCoroutine("AdjustSpeed");
Now when you run the game it should function as expected if you can find two type 3 pickups in quick succession (if you've copied the above exactly they should be blue).
Step 13: The Countdown Timer
Creating a countdown timer within Unity is pretty simple! Create a new script called 'CountdownTimer' and add the following variables and Awake()
function:
// the textfield to update the time to private var textfield:GUIText; // time variables public var allowedTime:int = 90; private var currentTime = allowedTime; function Awake() { // retrieve the GUIText Component and set the text textfield = GetComponent(GUIText); UpdateTimerText(); // start the timer ticking TimerTick(); }
All pretty simple so far; we've declared a few variables and when the script initializes we're assigning the GUIText Component of the GameObject that the script is a Component of to the textfield variable.
We then make two function calls to UpdateTimerText()
and TimerTick()
. Let's start by adding the UpdateTimerText()
function below to our script:
function UpdateTimerText() { // update the textfield textfield.text = currentTime.ToString(); }
This is also pretty simple, converting the currentTime
variable into a string and then updating the textfield with that string. Let's now add the timer functionality to our script with the TimerTick()
function below:
function TimerTick() { // while there are seconds left while(currentTime > 0) { // wait for 1 second yield WaitForSeconds(1); // reduce the time currentTime--; UpdateTimerText(); } // game over }
So there's a little more going on in this function but it's still pretty simple; let's take a look...
We have a while
loop which keeps looping until the seconds counter equals 0 - i.e. the game has ended. Within the loop we use the yield WaitForSeconds(...)
statement to stop the script executing for 1 second. When the script begins executing again, the currentTime
variable is reduced by 1 and the textfield is updated by calling UpdateTimerText()
.
Since it's a while
loop, this flow is repeated until the currentTime
equals 0, at which point the game is over and 'Game Over' is printed out to the console.
There is one final thing you need to do to make this work - and that is, of course, adding the CountdownTimer script as a Component of the 'txt-time' GameObject, since otherwise it's just an unused script in a folder!
Now run the game. The clock should tick down from 90 and stop at 0. (You will however be able to still run around afterwards but we'll remedy that in the next tutorial.)
Step 14: Conclusion
Hopefully you've enjoyed this latest Getting Started with Unity tutorial! We've covered GUITextures, GUIText, creating countdown timers and everyones favourite - Particles! In the next and final part of this series, we'll add an intro and ending screen for our game. See you there!
Comments