Game Framework (Advanced) – Extending Levels


Deprecated: This tutorial is deprecated as improvements in the API mean there are simpler ways of doing this.See the Getting Started series for more information.

In this tutorial we will look at how you can extend levels by adding our own custom properties or level configuration. In this example we will extend the getting started tutorial by adding a difficulty parameter to the level select screen and a time value that we will use to control our game.

Skill level: Advanced
Tutorial time: 45 minutes
Implementing in your projects: 30 minutes

Note: If you have followed the getting started series then you can use a copy of the completed project, otherwise the Game Framework Asset Store package includes files for all the tutorials as well as themes, other samples and resources. It also supports our effort in maintaining this framework.

Configuration File

We can optionally get any GameItem to load its configuration from a json file in the resources folder. As we are working with levels (Level class), create a new text (json) file at /Resources/Level/Level_1.json with the following contents:

{
 "valuetounlock": 0,
 "difficulty": "Easy",
 "time": 5
}

We need a json file for each level. For this example we will limit to just 2 levels, so create a copy of the above file named Level_2.json and edit the contents to the following

{
 "valuetounlock": 5,
 "difficulty": "Hard",
 "time": 10
}

If you are automatically creating the levels (or any other GameItem such as worlds and characters) through the GameManager then you can just enable the corresponding Load From Resources option and the json file will automatically be loaded and available through code via the items JsonConfigurationData property. Some specific item properties such as ValueToUnlock (in json “valuetounlock”) are also recognised automatically and updated.

You can use these files to hold whatever metadata or dynamic configuration that you need for your worlds, levels and other game items and have it available to you whenever you need.

Custom Level Classes

NOTE: Whilst the below is still valid, you may prefer to look into GameItem extensions as an easier way to achieve the below with less coding

If you want to be more structured or do further processing on the json configuration data, when you can create your own subclasses. Here we look at doing this for Levels, however the same principles apply for other types.

A reference to the current list of Levels is held on GameManager.Instance.Levels which references a generic GameItemsManager class. We need to do 3 things:

  1. Create a new Level subclass to load and hold our configuration.
  2. Create a new GameItemsManager subclass with a list of our new Levels.
  3. Set GameManager.Instance.Levels with our GameItemsManager subclass rather than using the default setup.

Level Subclass

Create a new file Scripts/CustomLevel.cs with the following contents

using FlipWebApps.GameFramework.Scripts.GameStructure.Levels.ObjectModel;
public class CustomLevel : Level
{
    public int Time;
    public string Difficulty;

    public CustomLevel(int levelNumber)
    {
        Initialise(levelNumber, loadFromResources: true);
    }
}

This inherits from the Level GameItem and adds variables to store our new configuration. The call from the constructor to the base class with loadFromResources: true indicates that we have a json file with the configuration data.

GameItemsManager Subclass

GameItemsManager contains a list of GameItems and functions for managing them such as the currently selected item, selection change and unlock events etc. We subclass this to setup our new CustomLevel classes.

Create a new file Scripts/Levels.cs with the following contents

using FlipWebApps.GameFramework.Scripts.GameStructure.GameItems;
using FlipWebApps.GameFramework.Scripts.GameStructure.GameItems.ObjectModel;
using FlipWebApps.GameFramework.Scripts.GameStructure.Levels.ObjectModel;

public class Levels : GameItemsManager<Level, GameItem>
{
    protected override void LoadItems()
    {
        Items = new Level[] {
            new CustomLevel(1),
            new CustomLevel(2)
        }
    };
}

This creates a list of all the levels that we will have available (in this case only 2). Depending on your game, you could have multiple such classes to group levels e.g. for things like different worlds.

Note: For completeness, we have chosen to load configuration data from json files, however we could always pass in custom values to  the CustomLevel constructor directly here if we wanted, indeed such an approach might be simpler if you only have a limited number of variables you need to setup. In such a case omit the loadFromResources call in your custom Level subclass.

GameManager Setup

Finally we need to set GameManager.Instance.Levels to the subclass created above and call it’s LoadItems() function to load the levels. There are various ways we can do this including inheriting from GameManager, however for now create a new file Scripts/SetLevels.cs with the following contents

using UnityEngine;
using FlipWebApps.GameFramework.Scripts.GameStructure;

public class SetLevels : MonoBehaviour
{
    void Awake()
    {
        GameManager.Instance.Levels = new Levels();
        GameManager.Instance.Levels.Load();
    }
}

Important: The Levels need to be set fairly early if they are accessed by other scripts. If you experience a null reference exception then try setting the Script Execution Order for this script to something like -10 !

Add the above component to our _GameScope prefab so it gets called, set _GameScope->Game Manager->Basic Level Setup->Unlock Level Mode to Custom so that the default level loading code doesn’t run and then apply the changes to the prefab if needed.

Testing it out

We haven’t done anything with the display elements just yet, however if you run the game now and browse to the level select screen, you should see that we correctly have 2 levels displayed and whereas before the default was 10 coins to unlock level 2, it is now showing 5 as configured in our json file. “valuetounlock” is one of the default values that is checked for when we use json configuration. Feel free to change this value of this property in the json files and see how it updates in the game.

ValueToUnlock

Reading Our Custom Level Values

We have shown that we are loading data from the json file, but we still need to access our custom values. Add the following to CustomLevel.cs to read this in.

To namespaces section:

using FlipWebApps.GameFramework.Scripts.Helper;

Inside the CustomLevel class:

    public override void ParseLevelFileData(JSONObject jsonObject)
    {
        base.ParseLevelFileData(jsonObject);
        Difficulty = jsonObject.GetString("difficulty");
        Time = (int)jsonObject.GetNumber("time");
    }

ParseLevelFileData is called automatically when a GameItem is created with the loadFromResources property set to true. There is also a similar method ParseGameData that can be used if you want to seperate loading of in-game data for performance or other reasons. That is triggered through an explicit call to GameItem.LoadGameData() that might typically be invoked at the start of your actual game scene to the currently selected level.

We now have all the data loaded. Next we will look at how we can display and use this in our games.

Custom Level Button

To have our custom values appear on the level select screen we need to do create a modified version of the level button prefab and also extend the LevelButton component to display our values.

Go to the Menu scene, and duplicate the prefab that is used in the gameobject LevelButtons->Buttons->CreateLevelButtons. Rename the duplicate CustomLevelButton and move it to your own prefab folder. Drag the new prefab into the CreateLevelButtons component so that it is the one used for generating the buttons.

CustomLevelButton

We will now modify our new prefab by creating a text field to display the custom difficulty value. Drag the CustomLevelButton prefab onto the LevelButtons->Buttons gameobject so that it is displayed (this makes it easier to edit). Under the prefab, duplicate the Name gameobject and rename it to Difficulty. Rearange the display so that it looks something like the below:

CustomLevelButtonPrefab

The level buttons contain a component LevelButton that handles populating values into the prefab. We will override this class to populate the difficulty value we just added.

Create a new script called CustomLevelButton.cs with the following content.

using FlipWebApps.GameFramework.Scripts.GameStructure.Levels.Components;
using FlipWebApps.GameFramework.Scripts.UI.Other;

public class CustomLevelButton : LevelButton
{
    public override void SetupDisplay()
    {
        base.SetupDisplay();

        var currentLevel = (CustomLevel)CurrentItem;
        UIHelper.SetTextOnChildGameObject(gameObject, "Difficulty", "Difficulty: " + currentLevel.Difficulty);
    }
}

The above hooks overrides the buttons SetupDisplay method that is used for setting up the display. We get a reference to the current GameItem for this button (in this case our CustomLevel instance) and then set some text on a text component on the Difficulty gameobject.

Finally on our prefab instance, delete the existing LevelButton component and add out new one CustomLevelButton component. Set ClickUnlockedScene to Game so the Game scene is loaded when we click upon any button.

Apply the changes to your prefab and delete the instance that you created in your scene.

You can now run your game and should see that the 2 buttons display our custom difficulty value as expected.

FinishedCustomLevelButtons

Using Our Values In Game

As a final exercise, we want to access the current level and use the other time value within our actual game. When we enter a game, the current level is already set and can be accessed through GameManager.Instance.Levels.Selected or LevelManager.Instance.Level. Once we have a reference to the level, it is a simple matter of accessing the custom properties and using them as needed.

We will use the time variable to set the length of the game, so first let’s setup a text component to display this. Go to the game scene, right click Canvas in the heirarchy and select UI->Text to add a new gameobject with a text component. Rename the gameobject Time, update the text value to 00 and position it near the top of the display as shown below.

Time

Drag a TimeRemaining component onto this gameobject (FlipwebApps/GameFramework/Scripts/UI/Other/Components), set Counter Mode to Down and drag the Time gameobject onto the Text property. We will set the Limit property to our custom Level time value to have a countdown from this value.

Create a new scrip GameLoop.cs and enter the following code:

using FlipWebApps.GameFramework.Scripts.GameObjects.Components;
using FlipWebApps.GameFramework.Scripts.GameStructure;
using FlipWebApps.GameFramework.Scripts.GameStructure.Levels;
using FlipWebApps.GameFramework.Scripts.UI.Dialogs.Components;
using FlipWebApps.GameFramework.Scripts.UI.Other.Components;
using UnityEngine;

public class GameLoop : Singleton<GameLoop>
{
    public TimeRemaining Countdown;
    CustomLevel _level;

    void Start()
    {
        _level = (CustomLevel)GameManager.Instance.Levels.Selected;
        Countdown.Limit = _level.Time;
    }

    void Update()
    {
        bool isWon = Input.GetMouseButtonDown(0);
        bool isLost = LevelManager.Instance.SecondsRunning > _level.Time;
        if (LevelManager.Instance.IsLevelRunning && (isWon || isLost))
        {
            LevelManager.Instance.GameOver(isWon);
        }
    }
}

This gets a reference to the currently selected level and initialises the countdown limit. In update we win by just clicking the mouse, or lost if the number of seconds running exceeds our time limit. When the game is over for either condition we show the GameOver dialog.

To add this new component, delete the existing DummyGameLoop from _SceneScope and drag over our new one. Drag the Time game object that we created above onto the Countdown field.

Finally run the game and you will see that the timer starts counting down from our custom limit. If you click the mouse before the time runs out you win, otherwise you lose. Use the cheat functions window if needed to unlock the second level and notice the time limits changes based upon our configuration.

Wrap Up

That completes our look at how to extend Levels and how you can quickly provide for your own items and configuration. You can easily apply the same principles to other GameItems such as Worlds, Characters or your own custom types.

If you like the framework then please consider downloading from the asset store for access to the tutorial files, themes and lots of other useful samples and assets. This also helps support our efforts to develop and maintain this framework.

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