Getting Started With Game Framework for Unity – Part 9 – A Better Game


Skill level: Beginner / Intermediate
Tutorial time: 30 minutes
Implementing in your projects: 30 minutes

Part 1 – The Basics, Audio, Settings and Localisation
Part 2 – Level Setup and Selection
Part 3 – Game Loop, Score and Coins
Part 4 – Unlocking and In App Purchase
Part 5 – Applying a Theme
Part 6 – Worlds and Resources
Part 7 – GameItems, Lives, Health and Stars
Part 8 – Messaging Essentials
Part 9 – A Better Game

So far we only added in a dummy game loop and have been using the cheat functions window to adjust the health and coins. In this part of the tutorial we will explore some of the features of Game Framework that can be used in an actual game and in the process create a simple 2D game.

Adding a Player

When we run our game we still get a warning about not being able to find a Player GameItem. Let’s clear that up first and also setup our player sprite.

Similar to how you did for levels create a Player configuration file at Resources\Player\Player_0. You can go with most of the default options however we will dynamically setup our player sprite in game so under Sprites click Add Sprite Entry, and in the newly added entry change Type to In Game and then find a sprite that you want to use and add this into the Sprite field.

playerspriteconfiguration

We now need to create our player object in game so open your Game scene, right click in the Hierarchy window and create a new 2D Object | Sprite renaming it to Player and check it’s position is 0,0,0.

We could just assign a sprite to the newly created Sprite Renderer directly, however we will do this slightly differently to demonstrate some of the framework features. To your newly created Player gameobject, add a new component Game Framework | Game Structure | Players | Set SpriteRenderer to Player Sprite. Leave GameItem Context to Selected as we want to reference the selected Player (our only one). Change Sprite Type to In Game and run the scene.

setplayeringamesprite

You should see that the sprite is assigned to the one that we set in our player configuration. Using this configuration we could allow multiple local players with different sprites or add a character selection screen and use the Set SpriteRender to Character Sprite component to display a sprite representing hte chosed character. You could also setup prefabs instead of sprites in the configuration files and use the ShowXxxPrefab or InstantiateXxxPrefab componets if you want 3D models or a more advanced structure.

A small tip, but for design purposes it might be useful to assign a default sprite to the Sprite Renderer component.

As a final step we need to add some way of controlling our sprite. Create a new C# script named PlayerController to the Scripts folder and add the following code:

This script gets a reference to a physics Rigidbody2D in Awake and then in FixedUpdate if the level is running and the mouse button is pressed it gets the mouse world position and applies a force pushing the player towards this position where the strength is based upon the distance – so clicking further away from the player applies a bigger force making the player move faster.

Add this script to your Player gameobject. A Rigibdbody 2D is added automatically so on this change Gravity Scale to 0.1 so we don’t fall too fast and under Constraints enable Freeze Rotation. Also as this is a 2D game (and so that the mouse position works, change the Main Camera Projection to Orthographic in this and all other scenes.

If you run the game now you will see the plane follows the mouse when you click (or if on a touch device where you tap).

Keep the Player On Screen

At the moment our player can easily disappear off screen so let’s fix that.

To our Player gameobject add a Circle Collider 2D component and set the Radius so that it just about covers our sprite. It is here that it comes in useful to assign a default sprite to the Player SpriteRenderer component so we can preview this in the scene window.

playercollider

Next we need to add borders to the screen. To do this create a new GameObject called Borders (check the position is 0,0,0) with children Gameobjects named Top, Right, Bottom and Left. To each of these add a Box Collider 2D component where the components on the Top and Bottom gameobjects have a size of x=30, y=1 and Right and Left have a size of x=1, y=15. Finally change the gameobject positions so that the colliders form a border around the screen edges as shown below.

border

If you run the game now then you should be stopped from moving off the screen.

Let’s make this a bit more fun.

Create a new folder Materials and into that create a new Physics2D Material (right click the folder | Create menu). Rename this new material to Border and set the Bounciness property to 0.4. Create a second material called BorderBottom with a Bounciness of 0.2. Assign the Border material to the Material field in the Box Collider 2D that we added to the Top, Right and Left borders and the BorderBottom material to the Bottom border collider.

Next to the Bottom border gameobject add a Game Framework | Game Structure | Colliders| Health Collider component. This component will cause the players health to change when a collision occurs in addition to allowing many other options to be configured. In Health collider, enable the Process Within option so that as long as we are in contact with the bottom border we are losing health and set Health Change to -0.01 so we don’t lose health too quickly.

Health Collider contains a Colliding Tag property that by default is set to Player. This determines what items will cause collisions (e.g. we probably don’t want enemies to cause us to lost health if they hit the ground). Go back to our Player gameobject and change the Tag (very top of the inspector) to Player so that it matches. Also whilst we are here, set the Player Rigidbody 2D Sleeping Mode to Never Sleep so that even if the player isn’t moving it will still generate collision events (causing health to be lost if stationary on the ground).

Now run the game and you should see that we can now ‘die’ by losing health from crashing into the ground. You can try playing around with the other collider settings such as playing a crash sound or Instantiating a particle prefab.

Catering for different sized screens

Before we move onto the next part you might notice something strange if you resize the game window and try playing. Namely that the right and left borders are set at a fixed position. Developing for displays of different sizes and aspect ratios is a common challenge (especially on mobile) and there are many different strategies for handling this from adding borders to dynamically changing the layout. Here we will do the latter. To the Left and Right gameobjects add the component Game Framework | Display | Placement | Align Screen Bounds with the Border property set to Left and Right respectively and Offset to -0.5 and 0.5 (our collider is 1 wide so half that value).

Our display will now re-layout accordingly to the screen size when our game is started (it won’t dynamically change in game, but then in most cases the display doesn’t change in game).

Collect Coins to Win

In the next part we will use a similar collider (Coin Collider) to automatically handle collecting of coins.

To cater for that fact that we want our levels to be different we could have had a separate Game scene for each level (using Game{0} as the scene name in our level selection buttons). In our case we will dynamically generate the scene based upon the level configuration.

Create a new c# script called CreateLevelObjects with the following contents:

This will create a number of instances of the specified coin prefab up to the count specified in the Level configuration Star3Target property. The GetValidPosition method just gets a valid location that doesn’t overlap the player start position.

Next we need to create a coin prefab for use in this script so right click the Hierarchy in your Game scene, select 2D Object | Sprite to add a new gameobject with a sprite and rename this new gameobject to Coin. In the Sprite Renderer Sprite field, click the circle of the right and select the IconCoinLarge sprite that comes with the Minimalist theme.

Next, on the Coin gameobject add a CircleCollider2D component setting it’s Is Trigger field to True. Also add a Game Framework | Game Structure | Colliders| Coin Collider component. This component will cause the players coins to change when a collision occurs. In Coin collider change the Disable After Use field to GameObject.

Drag the Coin gameobject into the folder named \Prefab (create if you didn’t earlier) and delete the copy from your main scene.

Finally add the CreateLevelObject script to _SceneScope, assign our new prefab to the CoinsPrefab field and test it out checking that the number of coins equals the value set for Star3Target and that the score updates as the player collects coins.

Winning

You might have noticed that the even though we collected coins we couldn’t win all the stars needed to win the level. Previously we added the StarsWonHandlerCoins component to _SceneScope to handle winning of stars, however at that time we gave specific values. Find this component again and enable the option Targets From Level Config so that the values in the level configuration files are used instead of fixed values. You should also set suitable values in each of the level configuration files for each of the StarTarget properties.

Next on _SceneScope enable the Level Manager option Game Won When All Stars Got option so that we will win the game when we have collected all coins (and so won all stars). One point to note is that this option it will not trigger if the user has already completed the level (if it did then when they replay a level it would complete immediately as all 3 stars are already won). To get around this you should also enable the Level manager option Game Won When Target Coins Reached and in each Level configuration file set Coin Target to be the same as Star 3 Target. The player won’t win any new stars, but the game will be won on the same condition when all coins are collected.

levelproperties

A Small Extra Touch

It is often the small extra touches that really make a game stand out. Here we will add a small particle effect that will play when the user collects a coin.

Right click the Hierarchy in your Game scene, select Particle System, rename this to Coin Particles and set the position and rotation to 0,0,0. Change the following parameters of the Particle System:

  • Duration – 1
  • Looping – Unselected
  • Start Lifetime – 0.5
  • Start Color – Yellow
  • Emission | Rate – 0
  • Emission | Bursts – Click + to add one with time 0, min 30 and max 30
  • Shape | Radius – 0.01
  • Size over Lifetime – A line from top left to bottom right
  • Renderer | Order in Layer – 10

Drag the Coin Particles gameobject into the folder named \Prefab and delete the copy from your main scene. Finally select your Coin prefab from earlier and drag the new Coin Particles prefab onto the When Entering a Trigger | Instantiate Prefab field. Run the game again and you will see a nice effect when we collect coins.

Pooling

Creating a lot of new particle systems / or other objects on the fly like this has an overhead and also each particle gameobject remain in our scene until the level ends. Pooling is a means of preallocating and reusing items that makes things run quicker and saves on memory. The collider components allow for adding items to the scene directly from a Pro Pooling pool. If you don’t have this asset or the Extras Bundle then you may skip to the next section on obstacles.

To use a ProPooling pool, first enable Pro Pooling through the Window | Game Framework | Integrations window. Next add a new GameObject to your scene called PoolManager and add a Pro Pooling | Pool Manager component which is where you will configure the items that you wish to pool. Drag the Coin Particles prefab onto Pool Manager to create a new Pool and set Pre Initialise Count (the number of items to pre allocate) to 3. 

poolmanager

 

On the Coin prefab clear the Entering a Trigger | Instantiate Prefab field and under When Entering a Trigger | Enter, set Add Pooled Item to Coin Particles. Finally on the Coin Particles prefab add the Component Pro Pooling | Return To Pool After Delay and set Delay to 0.5 which was the lifetime of our particle system. This component will automatically return the item to the pool for further reuse after the specified delay. Run the game again and you can see the Coin Particles being allocated and returned back under PoolManager. Simple as that.

Obstacles

This is the part that would probably define your game and the variety between levels is key to keeping people interested in playing. We will add some very basic obstacles and enemies to demonstrate some simple concepts however you will want to spend time carefully designing your levels to ensure that you have challenging and interesting levels for your users.

First find some sprites that you will use for your obstacles (here we will use just 1 ‘rock’ sprite but you can use different sprites for different levels if you so chose). Drag the sprites from the project list into the hierarchy, set their position to 0,0,0 and add a Circle Collider 2D to each. Next add a Game Framework | Game Structure | Colliders| Health Collider to each and set Health Change to -1 (again you can use different values for different obstacles). Finally create prefabs of these by dragging them from the hierarchy to the prefabs folder and then deleting them from the Hierarchy.

As we are dynamically creating the display we will add the sprite prefabs to our level configuration. Open up the Level_1 configuration and drag the rock prefab to the “Drag a Prefab here to create a new entry” box. In the Name field enter rock. Next go to the Variables / Attributes section (note this feature is experimental so may change slightly), expand Name | Int Variables. Set Size to 1 and in the entry that is created enter a Tag of Rock and a Default Value of 4.

levelconfiguration

Next modify the Start method in our CreateLevelObjects script to the below (leave the rest of the script unchanged):

This loops for the number of times we entered in our Rock Count variable each time creating a prefab as identified by the name “rock”.

If you run the game now you will see that 4 rocks are added to the scene and crashing into them causes us to lose a life. As we don’t do any validation you will need to add the same setup to your other Level configuration files to avoid errors. You can use a different prefab for other levels if you want however make sure to keep the name rock as that is how we look up the prefab. You can also change the number of rocks that are generated by adjusting the Rock Count variable.

This works ok for now, although you might occasionally get problems with rocks being placed on top of coins etc. so in a real game you will want to determine the positioning differently such as through a more advanced algorithm, setting the positions as Vector2 variables in the configuration file or using seperate scenes for each level.

Enemies

We will now do something similar to create enemies but with some scripting to move them (here we will use 2 sprites enemy1, enemy2). Drag the sprites you want to use from the project list into the hierarchy, set their position to 0,0,0 add a Circle Collider 2D to each and then add a Rigidbody 2D component – setting Gravity Scale to 0.2. Next add a Game Framework | Game Structure | Colliders| Health Collider to each and set Health Change -0.5. Finally create prefabs of these by dragging them from the hierarchy to the prefabs folder and then deleting them from the Hierarchy.

As we are dynamically creating the display we will add the sprite prefabs to our level configuration. Open up the Level_1 configuration and drag the enemy1 prefab to the “Drag a Prefab here to create a new entry” box. In the Name field enter enemy.

Duplicate the Coin Particle prefab and rename it to Enemy Particle. Change the Start Color to something that matches the enemy. Drag this prefab into the enemy prefab’s Health Collider | When Entering a Trigger | Instantiate Prefab field (or add to PoolManager and put the name Enemy Particle in the Add Pooled Item field). On the health collider change also the Disable After Use property to Game Object. This will now create a particle effect when a collision occurs. You might also want to configure the audio field to play a sound.

Finally add the following to CreateLevelObjects before the line that starts “// Get a position….”

This will wait for a delay and then create an instance of the prefab identified by the name “enemy” from the level configuration placing it at a random position on the left and then adding a random force to move it across the screen.

Run your game and verify everything works as expected.

enemies

Again as we don’t do any validation you will need to add the same to your other Level configuration files to avoid errors optionally using different prefabs with different settings. You could also move some of the hardcoded values from the CreateLevelObjects script into the Level Variables as a way of changing the behaviour of the different levels.

The game is still a bit rough around the edges (and really hard) but we will leave it as an exercise to the reader to add new functionality and adjust the difficulty to find a balance between frustration and challenging. Also if you look at the Hierarchy you will see we keep adding enemies but don’t destroy them. You should also add code to handle this – one way of doing this is to add a new tag (‘Enemy’) to the enemy prefabs and then add a wide collider below the bottom of the screen (similar to what we did with the borders but with e.g. Size X = 100) with the Display | Placement| ObjectDestroyer component added with a Destroy Tag set to Enemy.

Time Limit

As a final feature we will add a time limit to the levels.

First in each of the level configuration files set Time Target to a suitable number of seconds e.g. 20. On _SceneScope in the Level Manager component enable the Game Over When Target Time Reached option. We will now lose the level if we don’t otherwise win within the time limit.

We will also show a counter so that the user can see how long they have left. Under the Canvas Gameobject, duplicate the level name gameobject  (renaming it to Timer) and position it below the level name. Remove the ShowLevelInfo component and instead add the Game Framework | UI | Other | Time Remaining component. Change Counter Mode to Down and enable Limit From Level Time Target.

timeremaining

We now have a working counter.

counter

Wrap Up

That completes our creation of a basic game. You can take this further by creating different scenes for new levels / worlds or adjusting the logic for your own game. For more information it is worth reading the game structure documentation pages that contain further information.

A completed version of this tutorial is included in the Game Framework extras bundle.

If you are using the free version please consider the small price for the extras bundle for access to the tutorial files, themes and lots of other useful assets and samples. This also helps support our efforts to develop and maintain this framework.

If you like the framework then please leave a rating in the asset store.

If you have any questions or feature requests then let us know below.