Room Escape Game in Unity3D – Part 2

UPDATE: ALL THESE AND MORE CAN BE FOUND ON MY NEW SITE http://www.returnofthebrain.co.uk/

Last time we constructed a set of objects we could respond to clicks on and a door object that we can open and proceed through to the next scene. This time, we’re going to create a base class that outlines some generic behavior for our minigames, and then extend it to make a puzzle that involves fitting blocks into a square space.

The final playable result can be found here on itch.io, and the source code can be found here on github.

Designing Our First Puzzle – Blockfit

Before we jump into code we need to just define the behavior of our simple puzzle. We are going to provide the player with a handful of shapes which the player has to arrange into a square. Each piece will start outside of the square and will need to be dragged into the correct position by the player, if the piece is near to the correct location, it will snap into place. When the player has correctly placed all of the pieces the puzzle will mark itself as solved.

This means we will need to know the correct position for each piece beforehand, and then move them into their starting positions when the puzzle is instantiated. With that in mind, it’s logical that we will want to create an empty UI for our puzzle that will load the actual pieces from a prefab we create separately.

puzzle_design

On the left we have the solution to the puzzle, and on the right are the puzzle piece sprites. You’ll need to import these and set them up, if you’re using the ones included with the tutorial assets this is pretty simple. Import blocks.png into Unity as you would a regular sprite, then change the sprite mode to ‘multiple’ and click ‘Sprite Editor’ to see the sprite editor window.

sprite_blocks

You’ll need click and drag to create a new sprite from a section of the image, and then adjust the position of each side so that it fits the puzzle piece properly. You’ll want to name them appropriately too, I’ve gone with “block_T”, “block_Step”, “block_I” and “block_L” to match their shapes.

Setting up the UI

We need to set up a GUI screen for this puzzle to take place on. In part 1 we set up a GameCanvas object, as well as our screen panels. We now need to select the ‘Minigame_BlockFit_Screen’ panel we created and add a sprite to the image component. You’ll want to add an Image component and use the ‘bg_block_puzzle’ image included with the resources as the sprite. You’ll also want to make sure it is centered to hit ‘Set Native Size’ so that it’s correctly fitted.
gui_from_part_1.gif
Additionally we need to add empty panels that we can use in code to denote areas of the screen we need, in this case we need to know the square part of the screen that houses the actual puzzle, and the part that denotes where the pieces will be placed once the puzzle is started. Create two new ui panels, and remove the image components, then name them ‘puzzle_container’ and ‘puzzle_piece_container’ respectively. Then position them over the appropriate parts of the screen panel.
layout_overview
Once you’re done, it should look something like this…
Lastly, you’ll want to create a ui button, and place it somewhere appropriate on the screen so the player can use it to back out of the puzzle if they need to. You’ll need to set the button’s OnClick event to the screen panels ‘SetActive’ function with an input of false, this will disable the puzzle screen when the player clicks it.
 button_hidescreen_setup

Puzzle Prefab

In order to know the correct position for each piece of the puzzle and ensure we load the puzzle with the correct pieces, we need to set up a prefab that contains each piece in its correct position within the shape. Because we will be working with UI panels, you’ll need to ensure that the object is attached to the GameCanvas while we are setting it up, or a test canvas in another scene, or you won’t be able to see it!
To set up the prefab create a new ui panel on our GameCanvas, called ‘block_puzzle_one’ and position it in the same place, with the same dimensions as the puzzle_container object in our minigame screen panel. Now you need to create 4 sub-panels, one for each of the puzzle pieces, give them appropriate names and set their image components to use the correct puzzle pieces. Then position them correctly so they match the completed puzzle state.
puzzle_prefab_setup

Lastly, create a prefabs folder in the project heirarchy to store this, and drag the ‘block_puzzle_one’ prefab into it, then delete it from the GameCanvas panel as we will instantiate it ourselves at startup.

Minigame Class

Create a new script and call it “Minigame”

public class Minigame : MonoBehaviour {

    protected System.Action _onComplete;

    // ui canvas
    protected RectTransform _canvas;
    protected float _canvasWidth;
    protected float _canvasHeight;

    [SerializeField]
    protected bool _isComplete = false;

    public virtual bool IsComplete { get { return _isComplete; }}

    public void SetOnCompleteCallback(System.Action callback)
    {
        _onComplete = callback;
    }

    protected void SetComplete()
    {
        _isComplete = true;
        if (_onComplete != null)
        {
            _onComplete();
        }
    }
 }

This is the base class all our minigames will use, all it does is hold a reference to a RectTransform that will act as the screen for our minigame, and a bool that we can use to know whether or not the puzzle has been completed.

Coding the Blockfit Puzzle

Before we build the main puzzle class, we’re going to build some functionality that our individual pieces will need. Create a new C# script called ‘BlockFitPiece’.

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    using UnityEngine.UI;
    using UnityEngine.EventSystems;
    using System;

We need to include a few extra namespaces for our puzzle piece class that will give us access to UI events again.

[RequireComponent(typeof(RectTransform))]
    public class BlockFitPiece : MonoBehaviour
    {
        RectTransform _rt;
        bool isAnchoredToPointer = false;

        bool isPlacedCorrectly = false;
        public bool IsPlacedCorrectly { get { return isPlacedCorrectly; } }

        Action _onWasPlacedCorrectly;
        Vector3 _correctPosition;
        Vector3 _storedPosition;
        float _distanceTolerance;

Above our class decleration, we’ve use the RequireComponent attribute, this ensures that our puzzle piece must have a RectTransform attribute attached to it when we place the script onto the object. This will prevent us accidentally attaching it to something that’s it’s not designed for.
Next we declare the variables we’ll need to use:
‘_rt’ is a reference to the RectTransform component we will use
‘isAnchoredToPointer’ will be set to true if the pieces is currently being dragged by the mouse-pointer
‘isPlacedCorrectly’ keeps track of whether or not the piece has been placed in its correct position within the board.
 ‘_onWasPlacedCorrectly’ is a callback we will call when the piece is correctly placed to inform the main puzzle script.\
‘_correctPosition’ is the position the piece is supposed to be placed at to complete the puzzle.
‘_storedPosition’ is the current position the piece has been placed.
‘_distanceTolerance’ represents how close to the correct position we need to place the piece before it snaps to the correct position.
    void Awake()
    {
        _rt = gameObject.GetComponent<RectTransform>();

        // add the event triggers
        EventTrigger eTrigger = gameObject.AddComponent<EventTrigger>();

        // pointer down event
        EventTrigger.Entry downEvent = new EventTrigger.Entry();
        downEvent.eventID = EventTriggerType.PointerDown;
        downEvent.callback.AddListener(OnPointerDown);
        eTrigger.triggers.Add(downEvent);

        // point up event
        EventTrigger.Entry upEvent = new EventTrigger.Entry();
        upEvent.eventID = EventTriggerType.PointerUp;
        upEvent.callback.AddListener(OnPointerUp);
        eTrigger.triggers.Add(upEvent);
    }

The Awake function will seem familiar from Part 1, we again need to use the EventsSystem to respond to PointerDown and PointerUp events so that we can drag our puzzle pieces around the screen. We have assigned the corresponding events to the OnPointerDown and OnPointerUp functions that we’ll declare below.

    private void OnPointerDown(BaseEventData data)
    {
        isAnchoredToPointer = true;
    }

    private void Update()
    {
        if (isAnchoredToPointer)
        {
            // find the pointers current position and move there
            Vector2 pos;
            pos = Input.mousePosition;
            _rt.position = pos;
        }
    }

    private void OnPointerUp(BaseEventData data)
    {
        isAnchoredToPointer = false;
        if (IsWithinDistanceOfCorrectPosition())
        {
            // correctly placed
            isPlacedCorrectly = true;
            transform.localPosition = _correctPosition;

            // finally disable it
            enabled = false;

            if(_onWasPlacedCorrectly != null)
            {
                _onWasPlacedCorrectly();
            }
        }
    }

    public bool IsWithinDistanceOfCorrectPosition()
    {
        float distance = Vector3.Distance(_correctPosition, transform.localPosition);
        return distance &amp;lt;= _distanceTolerance;
    }

OnPointerDown is a very simple function, we just set ‘isAnchoredToPointer’ to true, everything else will be handled by the Update function which, if ‘isAnchoredToPointer’ is set to true, will position the piece at the mouse pointers screen location.
OnPointerUp sets ‘isAnchoredToPointer’ to false so that we stop dragging it, and then checks to see if we’ve placed the piece correctly by checking if we’re withing the distance tolerance of the correct position by calling IsWithinDistanceOfCorrectPosition. If it is, then we can set isPlacedCorrectly to true, and snap the transforms localPosition to the correct position to ensure it’s in the right place.
Then we disable the piece script as we won’t need it after that, and we don’t want the player to be able to drag pieces around once they’ve been correctly placed. Finally, if we have a callback assigned, we call it to inform whatever it controlling the pieces that it’s been placed correctly.
    public void SetCorrectPosition()
    {
        _correctPosition = transform.localPosition;
    }

    public void SetStoredPosition()
    {
        _storedPosition = transform.localPosition;
    }

    public void SetOnPlacedCallback(Action onPlaced)
    {
        _onWasPlacedCorrectly = onPlaced;
    }

    public void SetDistanceTolerance(float tolerance)
    {
        _distanceTolerance = tolerance;
    }
Lastly we’ve declared some public functions that the main puzzle script will use to inform the pieces what their correct and starting positions are, what the _onWasPlacedCorrectly callback should be, and what distance tolerance to use.

Blockfit Puzzle Script

The BlockFitPuzzle script itself will keep track of the pieces and the state of the puzzle, and handle setting up the puzzle pieces, checking whether or not it’s been completed and then responding appropriately.

    RectTransform _pieceStoreCanvas; // for the unplaced pieces to start/return to
    List<BlockFitPiece> _pieces = new List<BlockFitPiece>();
    float _placeDistanceTolerance = 30.0f;

We don’t need that many variables here, we need the RectTransform that will contain the pieces at the beginning of the puzzle, which I’ve called the ‘piece store’, we need a list of BlockFitPieces which will be our puzzle pieces, and a distance tolerance setting for them, this will control how close to their initial positions they need to be placed before being considered ‘correct’.

public static BlockFitPuzzle CreatePuzzleWithParent(RectTransform boardParent, RectTransform pieceStoreParent, GameObject puzzlePrefab){

    // create the piece store canvas
    GameObject pieceCanvasObject = new GameObject("PieceStore");
    RectTransform pieceCanvas = pieceCanvasObject.AddComponent<RectTransform>();
    pieceCanvas.SetParent(pieceStoreParent);
    pieceCanvas.localPosition = Vector3.zero;
    pieceCanvas.localScale = Vector3.one;
    pieceCanvas.sizeDelta = pieceStoreParent.sizeDelta;

    // create the puzzle canvas and pieces
    GameObject puzzleCanvas = Instantiate(puzzlePrefab);
    RectTransform canvas = puzzleCanvas.GetComponent&amp;lt;RectTransform&amp;gt;();
    canvas.SetParent(boardParent);
    canvas.localPosition = Vector3.zero;
    canvas.localScale = Vector3.one;
    canvas.sizeDelta = boardParent.sizeDelta;

    BlockFitPuzzle blockFitPuzzle = puzzleCanvas.AddComponent<BlockFitPuzzle>();
    blockFitPuzzle.CreatePuzzleBoard(canvas, pieceCanvas);

    return blockFitPuzzle;
  }

This is the only function here marked as static, and there’s a good reason for this. We want the puzzle to be in control of setting itself up, so this function allows us to tell it which panels it should use and which puzzle prefab to use, but then lets the puzzle take care of starting itself up from there, before returning a reference to itself so we can track whether or not it’s been completed elsewhere.
The function itself is quite simple, we first create a duplicate of the piece store panel, and parent it to the original, this allows us to clean it up a bit easier later on. Then we instantiate the puzzle prefab and parent it to the boardParent panel, which will be our ‘puzzle_container’ panel in the GameCanvas.
Then we attach an instance of the BlockFitPuzzle component to the puzzleCanvas object and call CreatePuzzleBoard before returning it.
public void CreatePuzzleBoard(RectTransform boardCanvas, RectTransform pieceStoreCanvas){

        _canvas = boardCanvas;
        _pieceStoreCanvas = pieceStoreCanvas;

        // get all children on the board canvas, these are our actual pieces
        foreach(Transform t in _canvas)
        {
            BlockFitPiece piece = t.gameObject.AddComponent<BlockFitPiece>();
            piece.SetOnPlacedCallback(OnPiecePlaced);
            piece.SetDistanceTolerance(_placeDistanceTolerance);
            _pieces.Add(piece);
        }

        StartCoroutine(SetPieceStartingPositions());
  }

CreatePuzzleBoard stores a reference to both the puzzle board and the piece container panel we will pass in, and then iterates through each of the child objects on the puzzle board, which should be our 4 shapes, and adds a BlockFitPiece component to them before storing a reference to it in the pieces list. We also set the OnPlacedCallback of each piece and the distance tolerance.
public IEnumerator SetPieceStartingPositions()
{
    yield return 0;
    // place them randomly within the piece container rect

    float inset = 40f;
    float max_x = (_pieceStoreCanvas.sizeDelta.x / 2) - inset;
    float max_y = (_pieceStoreCanvas.sizeDelta.y / 2) - inset;

    foreach (BlockFitPiece piece in _pieces)
            {
        // set the correct position
        piece.SetCorrectPosition();
        // parent it to the piece store container to work out the position a bit easier
        piece.transform.SetParent(_pieceStoreCanvas);
        piece.transform.localPosition = new Vector3()
        {
             x = Random.Range(-max_x, max_x),
             y = Random.Range(max_y, -max_y),
             z = 0.0f
        };

        // parent it to the puzzle canvas again so it'll set the correct position
        piece.transform.SetParent(_canvas);
        // set the stored position
        piece.SetStoredPosition();
    }
}

The reason we need to use an IEnumerator for this is because quite often in Unity, if you try to access size/position variables for UI elements on the same frame they are instantiated, you’ll get incorrect results back as the engine hasn’t finished setting them up yet. So to ensure we get the proper values back, we need to wait a frame before reading them.
What this function actually does is take each piece, and place it randomly within the puzzle_piece_container area.
public void OnPiecePlaced()
{
    // check if it's within the target distance
    // if it is, mark it as being completed
    CheckComplete();
}

OnPiecePlaced will be set as the callback our BlockFitPiece scripts call whenever one of them is placed correctly, as we don’t really need to do much else we just call CheckComplete from here.
void CheckComplete()
{
    bool correct = true;
    foreach(BlockFitPiece piece in _pieces)
    {
        if (!piece.IsPlacedCorrectly)
        {
            correct = false;
        }
    }

    if (correct)
    {
        SetInputEnabled(false);
        SetComplete();
    }
 }

CheckComplete is another relatively simple piece of code, we check each piece to see if it’s been placed correctly, and if any of them haven’t, we return false. If they have all been placed correctly, we disable input and set the puzzle as complete by calling SetComplete.
void SetInputEnabled(bool isEnabled)
 {
    foreach(BlockFitPiece piece in _pieces)
    {
        piece.enabled = false;
    }
 }

The quickest way to disable input for this puzzle is simply to disable the BlockFitPiece scripts on each of the pieces, as they are the only things that actually respond to input.

Setting up the Puzzle

Now that we have defined the ui elements that we need to make the puzzle function, we need to replace our default Interactable script with one that will bring up the puzzle UI when the user clicks on the object.

    using UnityEngine.EventSystems;

So create a new script called “Interactable_BlockFitPuzzle” and include the EventsSystems namespace as we need access to that.

    public class Interactable_StartBlockFitPuzzle : Interactable

You’ll also need to inherit the class from Interactable rather than Monobehaviour.

Next we need to be able to assign the parts of the puzzle UI that the puzzle needs to know about in the inspector, so declare them as public variables.

    public RectTransform _blockPuzzleScreen;
    public RectTransform _blockPuzzleContainer;
    public RectTransform _blockFitPieceStoreContainer;
    public GameObject _blockPuzzlePrefab;

    private BlockFitPuzzle _blockFitPuzzle = null;
We’ve declared a RectTransform variable to represent the puzzle screen, the puzzle container and the piece store. These serve 3 purposes as outlined in the puzzle script we wrote earlier.
  • Screen: This is the parent object for the UI that contains all of our puzzle.
  • PuzzleContainer: This is the space that is occupied by the actual ‘board’ of our puzzle, where the player needs to put the pieced.
  • Piece Store: This is a convenience thing for us, a place to put the pieces outside of the puzzle for the player to use.
The last public variable is a GameObject prefab that represents the finished puzzle, from this we’ll get the correct positions of all the pieces contained in the prefab, and then the puzzle will handle moving them to the piece store so that the user can start the puzzle.
Lastly we declare a private instance of BlockFitPuzzle that we will create on starting the puzzle.
public override void OnClick(BaseEventData data)
{
    base.OnClick(data);

    _blockPuzzleScreen.gameObject.SetActive(true);

    if (_blockFitPuzzle == null)
    {
        _blockFitPuzzle = BlockFitPuzzle.CreatePuzzleWithParent(
                _blockPuzzleContainer,
                _blockFitPieceStoreContainer,
                _blockPuzzlePrefab);

        _blockFitPuzzle.SetOnCompleteCallback(OnCompletePuzzle);
    }
}

We want to override the base Interactable’s OnClick behaviour here. We run the base.OnClick function before setting the puzzle screen to active. Then, if we don’t already have a BlockFitPuzzle in progress, usually in the first instance the user clicks the Interactable, we create one by passing in the puzzle container, the piece store and the prefab that contains the puzzle pieces. Then we set the completion callback so the puzzle can inform us when it’s complete.
void OnCompletePuzzle()
{
    OnExitPuzzle();
}

void OnExitPuzzle()
{
    _blockPuzzleScreen.gameObject.SetActive(false);
}

Our OnCompletePuzzle callback is very simple, all it does is close the puzzle screen using OnExitPuzzle. The reason OnExitPuzzle is separate is because we may also want to exit the puzzle in an incomplete state if the user decided to quit.
public override bool IsActivated()
{
     return _blockFitPuzzle != null && _blockFitPuzzle.IsComplete;
} 

Lastly we override the IsActivated function to only return true if the puzzle has been completed. This means the door will now no longer open, until the puzzle has been completed.
settingup_interactable puzzle.gif
Now we can replace the script on one of our interactable objects with this one, and assign the public variables from the BlockFitPuzzle screen.
success.gif
If you hit ‘play’ now, you should not be able to open the door without first solving the blockfit puzzle! Next time we’ll be setting up a more complex sliding-tiles puzzle.
Posted in Uncategorized | Leave a comment

Room Escape Game in Unity3D – Part 1

UPDATE: ALL THESE AND MORE CAN BE FOUND ON MY NEW SITE http://www.returnofthebrain.co.uk/

In this tutorial we will go through the process of building a small game scene comprising a room that the player must ‘escape’ from by solving two puzzles in order to open a door. We’ll set up some objects that can be interacted with by clicking on them, then two different puzzle screens, a sliding blocks puzzle and a puzzle that involves dragging and dropping shapes to fit inside a square space.

The final playable result can be found here on itch.io, and the source code can be found here on github.

Project Setup

First thing we need to do, as usual, is set up a new Unity project, it doesn’t really matte if you use 2D or 3D mode for this as we’re going to be using the UnityGUI system by itself. Then you’ll need to import all the assets from the zip file above with a few key things to keep in mind:

  •  You’ll need to set every image to an import mode of Sprite if it doesn’t do so automatically.
  •  Except for the 3 images inside “UI/cursors” which will need to be set to a special import mode “Cursor”.

Next up you’ll need the standard set of folders inside your Assets folder: Prefabs, Scenes, Scripts and Sprites. Put all of the artwork into the Sprites folder.

Create a new scene and save it as “MainScene” or something else appropriate in the Scenes folder.

GUI and Scene Setup

Right click in the scene Heirarchy and select ‘UI/Canvas” to create our main GUI canvas, call this “GameCanvas”.

It comes with some pre-attached components, one of which, the Canvas Scaler, we need to adjust. You want to make sure it is set to UI Scale Mode – Scale With Screen Size with a reference resolution of 1920 by 1080 (the size of our backgrounds! If you’re using a different resolution as your target, use that.). You should also set the Match mode to “Match Width of Height” and the slider for it all the way over to Width, this should be the default.

02_blackbarsetup.gif

This allows us to place black borders at the top and bottom of the screen for monitors that are higher than 16:9. You can adjust the colour of these by selecting the main camera object and setting it’s Clear Flags property to “Solid Colour” and adjusting the Background colour property;

Then we will set up the elements needed for each of the 4 screens in our game: The main gameplay scene (our Grove), the victory screen and two screens that will house our minigames.

For each of these, we’ll need to right click the canvas object we created and add a “UI/Panel” object. Give each one an appropriate name, I’ve gone with:

  • “GroveScene”
  • “VictoryScene”
  • “Minigame_BlockPuzzle_Screen”
  • “Minigame_SlidePuzzle_Screen”.

You’ll want to make sure the ‘Image’ component that comes by default with each panel is disabled or deleted.

Lastly make sure each one is set to be anchored to the middle-center (click on the anchor preset button, then hold alt+shift and clicking on middle-center icon) and set to a width and height of 1920 by 1080 to match our target size (if you set it to stretch, it won’t maintain the proper aspect ratio when it scales!)

01_scenestructure

Setting up the Grove Scene:

  • Right click the GroveScene object and add a UI/Image, call it “background”.
  • Set its sprite to our ‘bg_grove’ image.
  • Ensure the colour property has no transparency, sometimes the default does
  • Right click the GroveScene again and add a UI/Panel object, call it “interactables”, this will house all of the objects in our scene that we can click on, again disable the Image component it comes with.
  • Set it’s anchor preset to be stretch in both directions, to match the size of its parent.

Add a background object to the VictoryScene object in the same way as above, but this time use the “bg_victory” object, then set the VictoryScreen object to be disabled, we won’t touch it again for a while. Also disable the two puzzle screen objects, we’ll come back to those in part 2.

Here’s a quick overview of how the scene and ui are setup inside of Unity.

04_scenelayout.gif

Interactable Class

Finally we can write some code! The first thing this project needs is the ability to be able to hover over an interactable object and to be able to click on it to interact with it. What that interaction is/does will vary, so we’ll need to group some common functionality into a base class.

Create a new CSharp class and call it “Interactable”.

You’ll need to include the UI and Eventsystems namespaces at the top.

using UnityEngine.UI;
using UnityEngine.EventSystems;

Then declare a variable, a Texture2D called “hoverCursor”, this will enable us to give each interactable a potentially unique cursor that will show when the user moves the pointer over it. Something a lot of classic adventure games do.

public Texture2D hoverCursor;

Next we need to tackle some important setup in the Awake method

public virtual void Awake () {

        EventTrigger eTrigger = gameObject.AddComponent&lt;EventTrigger&gt;();

        // pointer handling
        EventTrigger.Entry pointerClick = new EventTrigger.Entry();
        pointerClick.eventID = EventTriggerType.PointerClick;
        pointerClick.callback.AddListener(OnClick);
        eTrigger.triggers.Add(pointerClick);

        EventTrigger.Entry pointerEnter = new EventTrigger.Entry();

        pointerEnter.eventID = EventTriggerType.PointerEnter;

        pointerEnter.callback.AddListener(OnPointerEnter);

        eTrigger.triggers.Add(pointerEnter);

        EventTrigger.Entry pointerExit = new EventTrigger.Entry();

        pointerExit.eventID = EventTriggerType.PointerExit;

        pointerExit.callback.AddListener(OnPointerExit);
        eTrigger.triggers.Add(pointerExit);
}

The EventTrigger component is what allows things like UI Buttons to respond to UI events that unity generates. We’re going to use it here to allow us to know when the mouse pointer has moved into or out of the bounds of the object, and when the user clicks on it.

We can do this by creating an EventTrigger.Entry class containing the event we want to respond to and the callback we’d like to execute when we do. Then adding this entry to the list of triggers the EventTrigger we added will respond to.

You can see here we’ve subscribed to the PointerClick, PointerEnter and PointerExit events, now we need to write the methods that correspond to them.

public virtual void OnClick(BaseEventData data){
            Debug.Log("We clicked an interactable!");
    }

    public virtual void OnPointerEnter(BaseEventData data)

    {

        if (hoverCursor) {

            CursorMode cursorMode = CursorMode.Auto;

            Vector2 hotSpot = Vector2.zero;

            Cursor.SetCursor(hoverCursor, hotSpot, cursorMode);

        }

    }

    public virtual void OnPointerExit(BaseEventData data)

    {

        CursorMode cursorMode = CursorMode.Auto;

        Cursor.SetCursor(null, Vector2.zero, cursorMode);

    }

Each of these functions needs to take in a BaseEventData object, which contains various information about the ui event. We don’t need any of it right now, but seeing as the triggers expect an Action type we have to match that format. You can read more about Actions and Delegates in the C# DotNet documentation. The methods are also marked as ‘virtual’ because we’ll want subclasses to be able to define their own behavior with an override method.

OnClick simply prints out a debug message to confirm that it’s working.

OnPointerEnter is a bit more interesting, it looks to see if we have an image assigned to the public ‘hoverPointer’ variable we declared, and if there is one uses the Cursor.SetCursor method built into Unity to change the cursor graphic. We set the CursorMode to Auto and hotSpot to Vector2.zero. You can read more about what these do in the Unity documentation.

OnPointerExit sets the cursor back to the default one after we move the pointer back outside the bounds of the object.

note: you can read more about the unity events system here (https://docs.unity3d.com/Manual/EventSystem.html)

Lastly, we need a way of polling an interactable to see if it’s function has been ‘completed’, subclasses will override this method to see if their assigned puzzle has been completed, or maybe to see if the user has a particular item in their inventory, or something to that effect. For now we’ll just return true.

public virtual bool IsActivated()
{
    return true;
}

As we won’t be creating the puzzles until parts 2 and 3, we might as well set up some interactables to test with now. We can do this by adding more UI/Image objects to the ‘interactables’ ui panel we created earlier. Add 2 of them for now, and use the sprites that represent the two objects interactive objects, then position them over the two tree trunks, attach instances of this ‘Interactable’ script to each of them and set their cursor icon to something appropriate, I’ve chosen a question mark. Don’t forget to hit the “Set Native Size” button on the Image components, too, otherwise your images will be squashed.

03interactablesetup.gif

Note: These cursor icons needs to be imported specifically as cursor icons in the unity asset folder. Instead of being imported as sprites.

Interactable Door and showing the Victory Scene

The next thing we’ll do is build a door object that the player will be able to open if the two interactables in our scene have been ‘activated’ (this will eventually mean the user will have to complete both puzzles before they can open the door) and then use to go from this scene to the victory scene.

To create the door, go back to our GroveScene’s interactables object and create a new one, call it “interactable_door” and instead of attaching an Interactable script to it, create a new script and call it “Interactable_Door”. We’ll use this script to control the state of the door, and to activate the victory scene when the player opens it and steps through.

using UnityEngine.EventSystems;
using UnityEngine.UI;

public class Interactable_Door : Interactable
{

        public Interactable[] _requiredCompletedInteractables;

        public Texture2D _openHoverCursor;

        private Image _image;

        public Sprite _open;

        public Sprite _closed;

        public GameObject _currentScreen;

        public GameObject _destinationScreen;

        private bool isOpen = false;
}

void Awake()
{
      base.Awake();
      _image = gameObject.GetComponent&lt;Image&gt;();
      _image.sprite = _closed;
}

Make sure you inherit the class from Interactable by replacing “Monobehaviour” with “Interactable” in the class declaration.

It should be reasonably clear what each variable is for.

_requiredCompletedInteractables is an array of other interactable scripts that must be ‘activated’ in order for the door to be unlocked. In this case, we’ll drag the two other interatables in the scene into here, for the moment we won’t need to do anything with them to unlock the door, however.

We inherit the variables from our Interactable, so we already have hoverCursor, but we might want to show a different one when the door is open.

We need to have access to our Image component as we’ll want to change the sprite to show our door as either open or closed.

_currentScreen should reference the ‘GroveScene’ game object and the _destinationScreen is our ‘VictoryScene’ object. This will allow us to swap them over when the player goes through the door.

Finally ‘isOpen’ just allows us to keep track internally of whether or not the door is open.

In the Awake function, we need to call base.Awake() so that our event trigger code in the superclass (the class we inherited from, Interactable) runs for this object. Then we grab a reference to the image component and set the sprite to the closed door sprite.

    bool CanUse()
    {
        bool canUse = true;
        foreach (Interactable interactable in _requiredCompletedInteractables)
        {
            if (!interactable.IsActivated())
            {
                canUse = false;
            }
        }
     return canUse;
    }

We need to know if the requirements for being able to ‘use’ this object (open the door) have been met, in this case all of the scriptable objects in our array _requiredCompletedInteractables must be ‘activated’.

To find out, we start with a boolean value that is true, and use a foreach loop to iterate through every Interactable in the list, setting that value to false if we find any that are not active. Then we return that boolean value.

The reason this is a function and not a simple variable is that the state of the other objects can change, and we want to check when the user interacts with the door rather than have a messier system when the Interactables try to tell the door that they’ve been activated. This also makes it easier for it to be extended, for example if later on you decide you want to make this door unlocked based on a specific state of another object, such as the position of a lever or dial the player can interact with, but locked in all other cases, you can set that objects ‘activated’ state to correspond to that and the door would be able to re-lock itself if the player changes the state of the other object.


        public override void OnClick(BaseEventData data)

        {

            base.OnClick(data);

            if (!isOpen &amp;&amp; CanUse())

            {

                // open the door!

                _image.sprite = _open;

                isOpen = true;

                // ensure the pointer is up to date

                OnPointerEnter(data);

            }

            else if (isOpen)

            {

                _currentScreen.SetActive(false);

                _destinationScreen.SetActive(true);

                // ensure the pointer is reset

                OnPointerExit(data);

            }

            else

            {

                // hmm, it's locked...

                Debug.Log("The door is locked...");

            }

        }

Now we need to override the OnClick function so that we can see if the door is open or closed, and proceed accordingly.

If we can use the door, and it’s not already open, we change the sprite so that the player can see the door is now open and set our internal ‘isOpen’ value to true so that we know the door is open from that point on. Otherwise, if the door is already open, we will disable the _currentScreen and enable the _destinationScreen. In a real game this would probably be a more generic scene change function. We also need to ensure that the cursor icon is reset, so we manually call the OnPointerExit function.

If neither of these conditions is met, we print out a debug log stating that the door is locked.

public override void OnPointerEnter(BaseEventData data)

        {

            if (_openHoverCursor &amp;&amp; isOpen)

            {

                CursorMode cursorMode = CursorMode.Auto;

                Vector2 hotSpot = Vector2.zero;

                Cursor.SetCursor(_openHoverCursor, hotSpot, cursorMode);

            }

            else

            {

                base.OnPointerEnter(data);

            }

        }

        public override bool IsActivated()

        {

            return CanUse();

        }

The last thing we do in this script is override the ‘OnPointerEnter’ function to account for the fact we have a different cursor icon depending on the state of our door, and the ‘IsActivated’ function to only return true if the door is unlocked. Note that in OnPointerEnter we don’t call base.OnPointerEnter right away because we don’t need that functionality if the door is open and we want to display the secondary cursor.

Before we can play, you’ll need to assign the various public variables in the inspector. Drag the appropriate icons into the HoverCursor and OpenHoverCursor slots, assign the Open and Closed sprites for the door, and assign the GroveScene and VictoryScene as the CurrentScreen and DestinationScreen respectively.

05playing.gif

At this point, although it doesn’t look like much you should be able to ‘play’ the game, click on the closed door to open it, and then proceed through the door to the victory screen. Awesome! But it’s a little too easy don’t you think? In Part 2 we’ll add the first of two puzzles to spice things up a bit.

Posted in Uncategorized | Leave a comment

Tutorial – GUI Screen Manager for Unity

UPDATE: ALL THESE AND MORE CAN BE FOUND ON MY NEW SITE http://www.returnofthebrain.co.uk/

Recently I had need to build a simple GUI manager that could keep track of which screens the user had visited and in which order. So that a user can, for example, access a ‘shop’ page from different locations and easily be able to hit ‘back’ and return to the screen they came from.

For this system we’re going to make use of Unity’s GUI system and create a canvas inside of our scene, then create our individual screens as prefab objects so that we can control which screens we want to load and when.

The source code for the complete tutorial can be found here.

What you’ll need:

SCENE SETUP

Before we write any scripts or create any screens, there’s a couple of things we need to set up in our project first.

First thing we need to do is install the DOTween plugin, which can be done easily through the asset store for free. This is a great tweening plugin, and you don’t have to use this one specifically to complete the tutorial, I’m just going to use it to move screens around a bit easier.

Second thing we need to do is create a prefab that will serve as the starting point for our GUI canvas. This isn’t a complicated process but there’s a few steps:

  1. Create a new scene and save it, call it whatever you like, I’ve gone with “App”.
  2. Create a Canvas by right clicking in the Heirarchy view and selecting “UI -> Canvas”
  3. Drag the newly created “Event System” object onto the Canvas object.
  4. Drag the Canvas object into the Project view to create a prefab from it.
  5. Create a folder called ‘Resources’ and put the canvas prefab in there.

As always, it’s important that the Resources folder is named precisely, as it’s a special case with Unity, more information on which can be found in the documentation.

canvas_create

SCREEN PANELS

Next, we’re going to write a script that can be applied to a UIPanel object to give us some functionality every screen it going to need, such as being able to show or hide the screen and to inform child elements so that they can perform any setup or animation we need.

    public class ScreenPanel : MonoBehaviour
    {

        protected RectTransform _rectTransform;

        protected const string _WillShowScreenEvent = "OnWillShowScreen";
        protected const string _ShowScreenDoneEvent = "OnShowScreenDone";

        protected const string _WillHideScreenEvent = "OnWillHideScreen";
        protected const string _HideScreenDoneEvent = "OnHideScreenDone";

        // Use this for initialization
        public virtual void Awake()
        {
            _rectTransform = gameObject.GetComponent<RectTransform>();
            if (_rectTransform == null)
            {
                Debug.LogError("Error: No Rect Transform found on GameObject ' " + gameObject.name + "'");
            }

            HideScreenImmediate();
        }

        public virtual void ShowScreen(float delay = 0f, float duration = 0.5f)
        {
            gameObject.SetActive(true);
            float widthOffset = _rectTransform.rect.width;
            Vector3 startPosition = new Vector3(widthOffset, 0, 0);
            _rectTransform.localPosition = startPosition;

            // tween the panel to the desired position
            Sequence seq = DOTween.Sequence();
            seq.AppendInterval(delay);
            seq.Append(transform.DOLocalMoveX(0, duration));
            seq.AppendCallback(ShowScreenDone);

            // Inform any screen child objects that we are about to show the screen (screen NOT visible at this point!)
            BroadcastMessage(_WillShowScreenEvent, SendMessageOptions.DontRequireReceiver);
        }

        protected virtual void ShowScreenDone()
        {
            Debug.Log("ShowScreenDone: ");

            // Inform any screen child objects that the screen is now shown and visible
            BroadcastMessage(_ShowScreenDoneEvent, SendMessageOptions.DontRequireReceiver);
        }

        public virtual void ShowScreenImmediate()
        {
            // Inform any screen child objects that we are about to show the screen (screen NOT visible at this point!)
            BroadcastMessage(_WillShowScreenEvent, SendMessageOptions.DontRequireReceiver);

            _rectTransform.localPosition = Vector3.zero;
            gameObject.SetActive(true);
            ShowScreenDone();
        }

        public virtual void HideScreen(float duration = 0.5f, Action onDoneCallback = null)
        {
            if (onDoneCallback != null)
            {
                onDoneCallback(); // we're done as far as the GUIManager is concerned
            }

            float widthOffset = _rectTransform.rect.width;
            _rectTransform.localPosition = Vector3.zero;

            BroadcastMessage(_WillHideScreenEvent, SendMessageOptions.DontRequireReceiver);

            // tween the panel to the desired position
            transform.DOLocalMoveX(-widthOffset, duration).OnComplete(HideScreenDone);
        }

        public virtual void HideScreenDone()
        {

            BroadcastMessage(_HideScreenDoneEvent, SendMessageOptions.DontRequireReceiver);
            gameObject.SetActive(false);
        }

        public virtual void HideScreenImmediate()
        {
            gameObject.SetActive(false);
            HideScreenDone();
        }
    }

This class isn’t too complex. The Awake function grabs a reference to the RectTransform object that controls our panel, and then hides the panel instantly as we want to be able to control when it’s shown to the user from elsewhere, so it will be hidden by default.

One thing to note, though is that it’s been built to be quite easy to extend so that you can have a custom show/hide setup for a particular screen by simply overriding the appropriate functions.

Before we actually build the menu screens this script will be applied to, we’re going to write the GUI Manager script which will keep track of them.

GUI MANAGER

Next up is the real meat of this tutorial, the GUI Manager itself. This is quite a large class so I’m going to split it up into chunks and explain what each part does.

public class GUIManager
{

    public enum Screens
    {
        NONE,
        MAINMENU,
        SETTINGS,
        STORE,
        GAME
    }

    private static Dictionary<Screens, string> _requiredScreens = new Dictionary<Screens, string>() {
        // format: (Screens) screen type, (string) prefab location
        {Screens.MAINMENU, "screens/mainmenu_screen"},
        {Screens.SETTINGS, "screens/settings_screen"},
        {Screens.STORE, "screens/store_screen"},
        {Screens.GAME, "screens/game_screen"}
    };

The first thing in our class is an enumeration containing the name of each screen we’re going to have in our GUI for easy reference.

Next, we want to be able to map those names to the locations of the prefabs that will represent them. We do this with a static Dictionary that maps the Screens enum to a string. The paths represent a path in our Resources folder.

    private static Dictionary<Screens, ScreenPanel> _availableScreens = new Dictionary<Screens, ScreenPanel>();
    private static bool _initialised = false;

    private static Stack<Screens> _uiStack;
    private static GameObject _canvasRootObject;
    private static Screens _currentScreen = Screens.NONE;
    private static float _defaultDuration = 0.5f;

Next up some static variables.

‘_availableScreens’ will hold the screens we were actually able to load from the lists above, and maps the Screens enum to the ScreenPanel script in the object we loaded.

‘_initialised’ just tells us whether or not we’ve been through the process of loading and initialising our screens.

‘_uiStack’ will track our current and previous screens and allow us to add or remove screens so that we can press a generic ‘back’ button on any screen and know which screen we need to end up on. More information on what a Stack is and how it works can be found here or here.

‘_canvasRootObject’ is the game object that will represent the root of our gui, all screens will be parented to this.

‘_currentScreen’ is just that, it’s a reference to the screen we’re currently displaying, and ‘_defaultDuration’ is the default amount of time it will take a screen to go from not showing to showing and active.

        public static void InitialiseGUI()
        {
            if (_initialised == true)
            {
                Debug.LogError("Error: Cannot initialize GUI again, it is already setup.");
                return;
            }

            _canvasRootObject = GameObject.Instantiate(Resources.Load("Canvas") as GameObject);
            _canvasRootObject.name = "ui_root_canvas";

            if (_canvasRootObject == null)
            {
                Debug.LogError("Error: Could not find 'uicanvas' root");
                return;
            }

            foreach (KeyValuePair<Screens, string> entry in _requiredScreens)
            {
                CreateAndCatalogueScreen(entry.Key, entry.Value);
            }

_initialised = true
        }

        private static void CreateAndCatalogueScreen(Screens screenType, string prefabLocation)
        {
            GameObject screenPrefab = Resources.Load<GameObject>(prefabLocation) as GameObject;
            GameObject instantiatedScreen = GameObject.Instantiate(screenPrefab);
            instantiatedScreen.transform.SetParent(_canvasRootObject.transform, false);
            ScreenPanel screenPanel = instantiatedScreen.GetComponent<ScreenPanel>();
            _availableScreens.Add(screenType, screenPanel);
        }

The Initialise function does a couple of things to set up our GUI, first thing we do is instantiate the Canvas prefab we created earlier. The CreateAndCatalogueScreen function uses each key/value pair in the required screens dictionary that we set up to instantiate the prefab we will create for each screen. Those don’t exist yet, but we’ll create them next. It also does some error checking to see if we’re trying to instantiate more than once, and to see if we found the root canvas object. Then it sets the ‘_instantiated’ variable to true so that we will know the gui system has already been initialised.

        public static void OpenPage(Screens screen, bool clearHistory = false)
        {
            if (screen == _currentScreen)
            {
                Debug.LogWarning("Attempted to open the same screen, aborting.");
                return;
            }

            if (_uiStack == null)
            {
                _uiStack = new Stack<Screens>();
            }

            ScreenPanel panel = null;
            _availableScreens.TryGetValue(screen, out panel);

            if (panel != null)
            {
                if (_uiStack.Count > 0)
                {
                    // we may need to delay showing the new screen until the old one calls back
                    _availableScreens[_uiStack.Peek()].HideScreen(_defaultDuration, () =>
                    {
                        _availableScreens[screen].ShowScreen();

                        if (clearHistory)
                        {
                            _uiStack.Clear();
                        }

                        _uiStack.Push(screen);
                        _currentScreen = screen;
                    });
                }
                else // we don't need to wait
                {
                    _availableScreens[screen].ShowScreen();

                    if (clearHistory)
                    {
                        _uiStack.Clear();
                    }

                    _uiStack.Push(screen);
                    _currentScreen = screen;
                }
            }
            else
            {
                Debug.LogError("'" + screen.ToString() + "' is not a valid screen.");
            }
        }

The OpenScreen function contains a couple things that look scary if you’ve not encountered them before! We use the stack functions, and we use something called a lambda expression.

First thing we do is take in a screen name as defined in our Screens enumeration, and a bool value telling us whether or not to cleat the history. Sometimes you may want to show the user a new screen and easily have the ability to ‘pop’ back to that screen as the root.

Next a bit of error checking, we see if the screen we’re trying to open is the current one, and we check to see if the stack is null and initialise it if so.

Then we poll _availableScreens to see if the requested screen is actually loaded and available to show.

If we find a valid screen to show, and it’s not the first screen we intend to show (the stack size is bigger than zero) we ask the current screen to hide itself, and give it a lambda expression as a function to call to let us know when it’s safe to proceed to show the new screen.

First thing this piece of code does it call _uiStack.Peek to get the object at the top of the stack, which is the current screen, and uses that to lookup the ScreenPanel object we mapped in _availableScreens and tells it to hide. We pass in the default duration, as well as the lambda expression containing the code we want to run when it is done.

Other people will be able to explain lambda expressions (sometimes called anonymous methods or delegates) much better than I could, but in this case I’m using it as a nice clean way of providing a very simple callback function so that we can ask the screen panel to do something, and then do something else once it’s finished. If we wanted to do something more complex, it would probably be cleaner to store the requested screen as a variable and write this as a real function and pass the callback that way.

The code inside the lambda does a couple of things; it finds the screen mapped in _availableScreens that matches our requested screen and tells it to show itself, then it clears the stack history if requested, pushes the new screen to the stack and sets the _currentScreen variable.

If there isn’t a screen already on the stack, we do the same thing as above without the lamda expression as we know it’s safe to show the screen right away because it’s the first screen our users will see.

If we couldn’t find the screen we want to show, we put out an error message.

        public static bool CanGoBack()
        {
            return (_uiStack != null && _uiStack.Count > 1);
        }

        public static void GoBack()
        {
            if (!CanGoBack())
            {
                Debug.LogError(" _uiStack not initialized or empty, cannot go back.");
                return;
            }

            _availableScreens[_uiStack.Pop()].HideScreen(_defaultDuration, () =>
            {
                _availableScreens[_uiStack.Peek()].ShowScreen();
                _currentScreen = _uiStack.Peek();
            });
        }

Next up we need the ability to return to the previous screen. So we have a simple method to check whether or not we can go back, it just checks that the ui stack exists and has more than one screen on it and returns the result.

Then we have the actual GoBack function which is very similar to OpenScreen. It verifies that we can actually go back, then it polls the ui stack for the current screen and removes it with the Pop function before telling it to hide and passing in a lambda function which will show the (now top) previous screen by using the Peek function of the ui stack.

        public static void PopToRoot()
        {
            if (!CanGoBack())
            {
                Debug.LogError(" _uiStack not initialized or empty, cannot go to root.");
                return;
            }

            _availableScreens[_uiStack.Pop()].HideScreen(_defaultDuration, () =>
            {

                while (_uiStack.Count > 1)
                {
                    _uiStack.Pop();
                }

                _availableScreens[_uiStack.Peek()].ShowScreen();
                _currentScreen = _uiStack.Peek();
            });
        }

Lastly we may need the ability to instantly pop back to the screen at the bottom of our stack. For example if the player is 4 menus deep in the inventory and exits right back to the game screen, we may want to go directly back to the game screen rather than going back through each menu.

The screen you pop back to with this function is either the first screen you showed, or the most recent screen you showed using the clearHistory flag in OpenScreen.

It’s similar to GoBack except once we’ve verified that we can go back and the current screen has called back to let us know we can proceed, instead of going back by one we use a while loop to Pop all the previous screens from the stack until there is only one left, then show that one.

Almost there, we just need to build some example panels to use as prefabs so we can test this system!

BUILDING OUR MENU SCREENS

Next thing we’re going to do is build some panels to actually form our menu from. We’ll need to build a panel for each of the screens we defined above; main menu, settings, store and game.

This tutorial has already gotten very long, so I’ll be brief here seeing as this topic is covered in much greater detail over on the official Unity documentation.

But what we’ll need to do is create a new scene that we can use to edit our ui prefabs called “gui_prefabs” and put it in a sub-folder of Scenes called Dev, as this scene won’t be used by the game it’s just a convenient place for us to edit and build the screen prefabs.

mainmenu_inprog

Each of these screens will be very similar. In this example, the main menu screen I’ve added a ui text element and 3 buttons to the canvas, then arranged them using a vertical layout group. I’ve then dragged the panel (the highlighted object in the image) into the Resources/screens folder in order to save it as a prefab that the GUI Manager can load.

settingsscreen

The game screen is a little different, I’ve create a top and bottom panel inside the screen panel that will hold the elements as we will demonstrate extending the ScreenPanel class with this later.

gamescreen

We also need a small script for each of our buttons that will call the GUIManager to request a new screen when pressed, and another for “QUIT” and “BACK” buttons.

 

    [RequireComponent(typeof(Button))]
    public class Button_OpenScreen : MonoBehaviour
    {

        private Button _button;
        public GUIManager.Screens _target;

        void Awake()
        {
            _button = gameObject.GetComponent<Button>();
            _button.onClick.AddListener(OnClick);
        }

        public void OnClick()
        {
            GUIManager.OpenPage(_target);
        }
    }

This script is very simple, all it does is fetch the button script on the object it’s attached to, and on Awake assigns the OnClick function to be called when the button is pressed. In the inspector you can choose which screen this should request with a dropdown menu.

openscreenbutton

    [RequireComponent(typeof(Button))]
    public class Button_BackScreen : MonoBehaviour
    {

        private Button _button;

        void Awake()
        {
            _button = gameObject.GetComponent<Button>();
            _button.onClick.AddListener(OnClick);
        }

        public void OnClick()
        {
            GUIManager.GoBack();
        }
    }

This does exactly the same thing but requests that the GUI Manager go back a screen instead of opening a new one.

Lastly, we need to go back to our main scene (which at this point should only have a main camera in it) and attach a script to our main camera that will initialise the gui manager and start things up.

    public class App : MonoBehaviour
    {

        // Use this for initialization
        void Awake()
        {
            // Initialise the GUI Manager
            GUIManager.InitialiseGUI();

            // Show the first screen
            GUIManager.OpenPage(GUIManager.Screens.MAINMENU);
        }
    }

Here we are just initialising the GUIManager on awake and requesting the first screen we want to see, in this case it’s our main menu.

If you hit the play button now, you should have a functional menu system!

functionalmenu

You might notice here that my game screen animates differently, the panels slide in from different directions and slide out again before displaying the next screen. Let’s code that now by extending the screen panel.

EXTENDING THE SCREEN PANEL

You may remember those messages we broadcast to our screen panel objects earlier, _OnWillShowScreenEvent, etc? Here is where those will come on really handy.

We’re going to write a script that inherits from ScreenPanel that will slightly change the way the game screen is shown and allow child objects of the game screen to control how they come onto the screen themselves by receiving those messages.

public class GameScreenPanel : ScreenPanel
    {
        public override void ShowScreen(float delay = 0f, float duration = 0.5f)
        {
            // override the show screen animation with one unique to this screen
            Vector3 startPosition = Vector3.zero;
            _rectTransform.localPosition = startPosition;

            // just use the tween to set a callback for done
            Sequence seq = DOTween.Sequence();
            seq.AppendInterval(delay + duration);
            seq.AppendCallback(ShowScreenDone);

            // Inform any screen child objects that we are about to show the screen (screen NOT visible at this point!)
            gameObject.SetActive(true); // we need to enable the gameobject in order for it to recieve the event
            BroadcastMessage(_WillShowScreenEvent, SendMessageOptions.DontRequireReceiver);
            gameObject.SetActive(false); // we can safely disable it aftr
        }

        protected override void ShowScreenDone()
        {
            // show the screen now, as we wanted to delay it
            gameObject.SetActive(true);

            // Inform any screen child objects that the screen is now shown and visible
            BroadcastMessage(_ShowScreenDoneEvent, SendMessageOptions.DontRequireReceiver);
        }

        public override void HideScreen(float duration = 0.5f, Action onDoneCallback = null)
        {
            // Let our UI know how much time it has to get out of the way!
            BroadcastMessage(_WillHideScreenEvent, duration, SendMessageOptions.DontRequireReceiver);

            // Schedue our callback for the durations end
            Sequence seq = DOTween.Sequence();
            seq.AppendInterval(duration);
            seq.AppendCallback(HideScreenDone);

            // finally append the callback to let the GUIManager know it's safe to continue
            if (onDoneCallback != null)
            {
                seq.AppendCallback(() =>
                {
                    onDoneCallback();
                });
            }
        }
    }

This script should be attached to your game screen panel in place of the ScreenPanel script (not in addition to!)

You’ll notice what we’ve done here is remove the sliding animation and simply moved the screen into position immediately, but withheld showing it until ShowScreenDone is called. This allows us to set up by receiving the ‘_WillShowScreenEvent’ message, and then actually tell our child objects to begin their animations with the ‘_ShowScreenDoneEvent’ message.

One caveat here is that we have to prematurely set our gameobject to active before sending the ‘_WillShowScreenEvent’ message otherwise our child objects won’t receive it, and then set it to inactive again before continuing.

Lastly we’ve modified OnHideScreen so that it will use BroadcastMessage to inform child objects that the screen needs to be hidden, and sends the amount of time it has to do so along with it. For more information on the BroadcastMessage system consult the unity documentation. We then use a lambda expression to append a callback after that amount of time has passed to let the GUIManager know it’s safe to continue.

Next we need to write the code that will control the animation for the child objects.

    public class GameGUIExample : MonoBehaviour
    {

        public RectTransform _topPanel;
        public RectTransform _bottomPanel;

        private Vector3 _startPos_TopPanel;
        private Vector3 _startPos_BottomPanel;

        private Vector3 _destPos_TopPanel;
        private Vector3 _destPos_BottomPanel;

        void Awake()
        {
            // get all our initial positions
            _startPos_TopPanel = _topPanel.localPosition + Vector3.up * 140f;
            _startPos_BottomPanel = _bottomPanel.localPosition + Vector3.right * 320f;

            // get our destination positions from the current positions
            _destPos_TopPanel = _topPanel.localPosition;
            _destPos_BottomPanel = _bottomPanel.localPosition;
        }

        public void OnWillShowScreen()
        {
            // set our gui objects to their off-screen starting positions
            _topPanel.localPosition = _startPos_TopPanel;
            _bottomPanel.localPosition = _startPos_BottomPanel;
        }

        public void OnShowScreenDone()
        {
            Sequence seq = DOTween.Sequence();

            // move the top panel in from above
            seq.Append(_topPanel.DOLocalMove(_destPos_TopPanel, 0.5f));

            // move the bottom panel in from the right-hand side
            seq.Append(_bottomPanel.DOLocalMove(_destPos_BottomPanel, 0.2f));
        }

        public void OnWillHideScreen(float duration)
        {
            Sequence seq = DOTween.Sequence();

            // move the top panel in from above
            seq.Join(_topPanel.DOLocalMove(_startPos_TopPanel, duration));

            // move the bottom panel in from the right-hand side
            seq.Join(_bottomPanel.DOLocalMove(_startPos_BottomPanel, duration));
        }
    }

All I’ve done here is give the script a reference to the Top and Bottom panel objects we set up in the inspector. You’ll need to attach this script to the game screen prefab, and drag the top and bottom panels in.

Then we store a reference to where the panels start (their destination, seeing as we move them to start off-screen) and then store off-screen positions for them to start at.

OnWillShowScreen, OnShowScreenDone and OnWillHideScreen are the functions that will be called when we receive the messages from GameScreenPanel. The names must match exactly the strings you have in ScreenPanel.

OnWillShowScreen moves the panels to their off-screen starting positions, then OnShowScreenDone uses a DOTween sequence to move them both onto the screen, starting with the top panel first and then the bottom panel.

OnWillHideScreen does the same thing in reverse, and uses Join instead of Append so that they will animate at the same time, and uses the duration we pass in for the animation duration.

That’s pretty much it! You can create panels that will be aware of when they should show/hide themselves using this method.

I hope you found this useful and as always, feel free to leave any questions or feedback.

Posted in Uncategorized | Leave a comment

Christmas Break Recap!

Arrgh!

So the tail end of 2016 did not go to plan, lots of personal issues prevented me from really sinking my teeth into my work, and in the end I had two weeks off and basically just gave up and resigned myself to playing some games and relaxing. Which I did with abandon!

However. That doesn’t mean I’m planning on quitting on you 😉 wheels are still turning, code is still being written, in-spite of being extra busy at work these days.

im-sorry-im-just-too-busy-1014x487

Coming up next (hopefully this weekend) is a tutorial on building a simple UI stack that you can use to transition between multiple functional screens in your game. Something that is super useful for basically anything that’s going to be UI-heavy.

I’ll also be updating all of the previous tutorials with slightly better formatting over the next couple of days. Nothing fancy just a bit of readability improvement for the code snippets.

So look out for that. I’m still planning on doing most of the things in my roadmap,  but now that I have less free time it’s an issue of picking my battles, so I’ve got to be careful what I commit to in-case I don’t have the time to finish it. The barbarian game is most definitely still happening although its form has changed significantly and I may not have a lot to show for a while.

Peace!

  • The A Drain
Posted in Uncategorized | Leave a comment

Tutorial – Simple Sound Manager for Unity3D

UPDATE: ALL THESE AND MORE CAN BE FOUND ON MY NEW SITE http://www.returnofthebrain.co.uk/

Note: This tutorial was made in Unity 5.1 but should work for older versions. If you have any questions I can be reached easily on twitter @The_A_Drain

The goal today is to create a nice simple sound utility that we can use to control background music and sound effects, as well as giving us a global volume and the ability to mute/unmute the sound in our game.

What we’re going to produce isn’t massively complex and doesn’t take 3D audio into account, but it is incredibly useful for game jams and smaller games that don’t have complex audio requirements. I use this exact script for pretty much all my game jam and prototype games as it allows me to fire-and-forget sound effects and fade background music with a single function call. It’s a great thing to have in your development toolbox.

Disclaimer: This does mean that there isn’t going to be many visuals this time! This is a very text-heavy tutorial.

First thing we’ll do is set up the class itself as a singleton that we can access an instance of from anywhere. We store a local static instance of our class and then other classes and static methods can use GetInstance to access it. If there isn’t an instantiated instance, we create a new game object and attach our script to it before returning it. This way we don’t need to do any manual setup for this script within the editor. Everything is handled in script.

using UnityEngine;
using System.Collections.Generic;
using System.Collections;

public class SoundManager : MonoBehaviour
{

    // Static instance
    static SoundManager _instance;
    public static SoundManager GetInstance()
    {
        if (!_instance)
        {
    	    GameObject soundManager = new GameObject("SoundManager");
	    _instance = soundManager.AddComponent();
	    _instance.Initialize();
        }

        return instance;
    }

Next we need to create a few variables to track volume. We’ve got a maximum volume for background music and sound effects, while not strictly necessary it’s nice to be able to adjust this if we need to.

Then we have a couple of static variables to represent the current volume as a normalized value between 0 and 1 which we’ll multiply the maximum volume by to determine actual volume. And a variable to track whether or not the sound is muted.

Lastly we have two instance variables, a list of AudioSource components that will play sound effects and a single AudioSource to play our background music. We’ll need multiple sound effects sources to be able to play overlapping sound effects and avoid having new sounds cancel sounds that are already playing.

Our Initialize method sets up the background music source by adding the component, setting it to loop, disabling playOnAwake and setting the initial volume.

Lastly we call DontDestroyOnLoad so that our instance will persist through scene changes.

    const float MaxVolume_BGM = 1f;
    const float MaxVolume_SFX = 1f;
    static float CurrentVolumeNormalized_BGM = 1f;
    static float CurrentVolumeNormalized_SFX = 1f;
    static bool isMuted = false;

    List sfxSources;
    AudioSource bgmSource;
    void Initialize()
    {
        // add our bgm sound source
        bgmSource = gameObject.AddComponent();
        bgmSource.loop = true;
        bgmSource.playOnAwake = false;
	bgmSource.volume = GetBGMVolume();
        DontDestroyOnLoad(gameObject);
    }

Next up we’re going to write a couple of helper methods for getting the current adjusted volume. The logic here is very simple, we either return zero if sound is muted, or we return the maximum volume multiplied by the current normalized volume.

     // ==================== Volume Getters =====================

    static float GetBGMVolume () {
	return isMuted ? 0f : MaxVolume_BGM * CurrentVolumeNormalized_BGM;
    }

    public static float GetSFXVolume () {
 	return isMuted ? 0f : MaxVolume_SFX * CurrentVolumeNormalized_SFX;
    }

First thing we’re going to deal with is background music, and there’s a couple of major things we need to be able to do for our simple sound system.

  • Play a track with or without a fade up
  • Fade out and into another track
  • Stop playing the track

Starting and stopping audio playback is easy using the AudioSource Monobehaviors built-in functions, but fading requires a little bit more work.

To save us duplicating code we’re going to use a single fade coroutine, FadeBGM, that will facilitate fading to a specific volume rather than just up or down, and then call that function from elsewhere.

We pass in a target volume, a delay if we need one, and a duration. Then we just lerp the background audio source’s volume to where it needs to be before exiting.

FadeBGMIn takes in an the audio track as an audioclip, and the delay/duration parameters it passes on to FadeBGM. It then sets the track as our background audio sources clip and tells it to play before starting the fade coroutine.

FadeBGM Out just takes in a duration and starts fading with a target volume of zero.

    // ====================== BGM Utils ======================

    void FadeBGMOut (float fadeDuration)
    {
        SoundManager soundMan = GetInstance();
        float delay = 0f;
        float toVolume = 0f;

        if(soundMan.bgmSource.clip == null){
            Debug.LogError("Error: Could not fade BGM out as BGM AudioSource has no currently playing clip.");
        }

        StartCoroutine(FadeBGM(toVolume, delay, fadeDuration));
     }

     void FadeBGMIn (AudioClip bgmClip, float delay, float fadeDuration)
     {
         SoundManager soundMan = GetInstance();
         soundMan.bgmSource.clip = bgmClip;
         soundMan.bgmSource.Play();

         float toVolume = GetBGMVolume();

         StartCoroutine(FadeBGM(toVolume, delay, fadeDuration));
     }

     IEnumerator FadeBGM(float fadeToVolume, float delay, float duration)
     {
         yield return new WaitForSeconds (delay);

         SoundManager soundMan = GetInstance();
         float elapsed = 0f;
         while (duration > 0) {
             float t = (elapsed / duration);
             float volume = Mathf.Lerp (0f, fadeToVolume*CurrentVolumeModifier_BGM, t);
 soundMan.bgmSource.volume = volume;

             elapsed += Time.deltaTime;
             yield return 0;
         }
     }

Next up are two static functions that will actually handle playing and stopping background music on our instance. These are designed to be called from anywhere without even needing to manually call GetInstance().

PlayBGM takes in our track, a bool that determines whether or not we should fade and a duration for that fade. It fetches the sound manager instance before doing anything.

If we have told PlayBGM we want to fade, it will determine whether we need to first fade-out a currently playing track first, in which case it will fade down the current track and fade up the new one after a delay. Otherwise it will simple fade up the new track. Lastly if we’ve told it we don’t want to fade, it just grabs the audio source and starts playing the track immediately.

StopBGM is simpler, it just gets the soundmanager instance and tells the background source to stop playing immediately if we don’t want to fade, or to FadeBGMOut with the supplied duration if we do.

    // ====================== BGM Functions ======================

    public static void PlayBGM(AudioClip bgmClip, bool fade, float fadeDuration)
    {
 	SoundManager soundMan = GetInstance();

	if (fade) {
	    if (soundMan.bgmSource.isPlaying) {
		// fade out, then switch and fade in
		soundMan.FadeBGMOut(fadeDuration/2);
		soundMan.FadeBGMIn (bgmClip, fadeDuration / 2, fadeDuration / 2);

	    } else {
		// just fade in
		float delay = 0f;
		soundMan.FadeBGMIn(bgmClip, delay, fadeDuration);
	    }
	} else {
	    // play immediately
	    soundMan.bgmSource.volume = GetBGMVolume ();
	    soundMan.bgmSource.clip = bgmClip;
	    soundMan.bgmSource.Play ();
	}
    }

    public static void StopBGM(bool fade, float fadeDuration){
	SoundManager soundMan = GetInstance();
	if (soundMan.bgmSource.isPlaying) {
		// fade out, then switch and fade in
                if(fade){
                     soundMan.FadeBGMOut(fadeDuration);
                } else {
		     soundMan.bgmSource.Stop();
                }
	}
    }

Now we need to write some similar functions for our sound effects. Juggling lots of potential audio sources is a little different from just the one, however. So we need a couple of functions to help with removal of expired sources and a function to handle getting a new one.

GetSFXSource handles creating a new audio source to us to use and returning it. We simply create a new audio source by adding it to our gameObject, set loop and playOnAwake to false and set the volume. We then instantiate our sfx source list if it’s not already been done before adding it to the list and returning the audio source object for use.

RemoveSFXSource is a coroutine that takes in an audiosource and waits for the duration of the clip before removing it from the audio source list and destroying the audio source. This is just a helper coroutine that we can call when we play the sound effect so that it will be cleaned up once it’s done.

RemoveSFXSourceFixedLength does the same thing but with a duration we provide rather than the duration of the clip. This can be useful if we know we want to stop a sound effect early.

    // ======================= SFX Utils ====================================

    AudioSource GetSFXSource()
    {
        // set up a new sfx sound source for each new sfx clip
	AudioSource sfxSource = gameObject.AddComponent();
	sfxSource.loop = false;
	sfxSource.playOnAwake = false;
	sfxSource.volume = GetSFXVolume();

	if (sfxSources == null)
	{
	    sfxSources = new List();
	}

	sfxSources.Add(sfxSource);

	return sfxSource;
    }

    IEnumerator RemoveSFXSource(AudioSource sfxSource)
    {
	yield return new WaitForSeconds(sfxSource.clip.length);
	sfxSources.Remove(sfxSource);
	Destroy(sfxSource);
    }

    IEnumerator RemoveSFXSourceFixedLength(AudioSource sfxSource, float length)
    {
 	yield return new WaitForSeconds(length);
	sfxSources.Remove(sfxSource);
	Destroy(sfxSource);
    }

Next up are the functions responsible for actually playing our sound effects. We want to be able to do a few things here.

  • Play a sound effect
  • Play a sound effect with a slightly randomized pitch
  • Play a sound effect with a fixed duration

These are all relatively simple but provide a good deal of functionality.

PlaySFX is the simplest, all we do here is get the SoundManager instance, fetch a new SFXSource and use it to play the clip before stating the RemoveSFXSource coroutine to clean it up.

PlaySFXRandomized does exactly the same thing but adjusts the pitch slightly using a random value. This is useful for effects that will be played often and just need a very slight variation so as not to become too repetitive. Good for bullets, explosions, footsteps, etc.

Lastly PlaySFXFixedDuration does the same as PlaySFX but cuts off the clip at a specified point.

    // ====================== SFX Functions =================================

    public static void PlaySFX(AudioClip sfxClip)
    {
        SoundManager soundMan = GetInstance();
        AudioSource source = soundMan.GetSFXSource();
		source.volume = GetSFXVolume ();
        source.clip = sfxClip;
        source.Play();

        soundMan.StartCoroutine(soundMan.RemoveSFXSource(source));
    }

    public static void PlaySFXRandomized(AudioClip sfxClip)
    {
        SoundManager soundMan = GetInstance();
        AudioSource source = soundMan.GetSFXSource();
		source.volume = GetSFXVolume ();
        source.clip = sfxClip;
        source.pitch = Random.Range(0.85f, 1.2f);
        source.Play();

        soundMan.StartCoroutine(soundMan.RemoveSFXSource(source));
    }

    public static void PlaySFXFixedDuration(AudioClip sfxClip, float duration, float volumeMultiplier = 1.0f)
    {
        SoundManager soundMan = GetInstance();
        AudioSource source = soundMan.GetSFXSource();
        source.volume = GetSFXVolume() * volumeMultiplier;
        source.clip = sfxClip;
        source.loop = true;
        source.Play();

        soundMan.StartCoroutine(soundMan.RemoveSFXSourceFixedLength(source, duration));
    }

Last thing we need is a set of functions to control enabling/disabling sound and adjusting volume.

DisableSoundImmediate will stop all currently playing sound effects, set the background source volume to zero and set the isMuted attribute so that future requests for volume will return zero.

EnableSoundImmediate re-enabled all of our audio sources and our background source and sets isMuted to false so that GetVolume will return the correct volume levels from now on.

Set Global, BGM and SFX volume just sets the current volume either for BGM, SFX or both and calls AdjustSoundImmediate to update volume levels.

AdjustSoundImmediate will actually update all of our sources to reflect the new volume level by looping through our list of sfx sources and updating our bgm source volume.

    // ==================== Volume Control Functions ==========================

    public static void DisableSoundImmediate()
    {
        SoundManager soundMan = GetInstance();
        soundMan.StopAllCoroutines();
        if (soundMan.sfxSources != null)
        {
            foreach (AudioSource source in soundMan.sfxSources)
            {
                source.volume = 0;
            }
        }
        soundMan.bgmSource.volume = 0f;
        isMuted = true;
    }

    public static void EnableSoundImmediate()
    {
        SoundManager soundMan = GetInstance();
        if (soundMan.sfxSources != null)
        {
            foreach (AudioSource source in soundMan.sfxSources)
            {
                source.volume = GetSFXVolume();
            }
        }
        soundMan.bgmSource.volume = GetBGMVolume();
	isMuted = false;
    }

    public static void SetGlobalVolume(float newVolume)
    {
        CurrentVolumeNormalized_BGM = newVolume;
        CurrentVolumeNormalized_SFX = newVolume;
        AdjustSoundImmediate();
    }

    public static void SetSFXVolume(float newVolume){
        CurrentVolumeNormalized_SFX = newVolume;
        AdjustSoundImmediate();
    }

    public static void SetBGMVolume(float newVolume){
        CurrentVolumeNormalized_BGM = newVolume;
        AdjustSoundImmediate();
    }

    public static void AdjustSoundImmediate()
    {
        SoundManager soundMan = GetInstance();
        if (soundMan.sfxSources != null)
        {
            foreach (AudioSource source in soundMan.sfxSources)
            {
                source.volume = GetSFXVolume();
            }
        }
        Debug.Log("BGM Volume: "+ GetBGMVolume());
        soundMan.bgmSource.volume = GetBGMVolume();
        Debug.Log("BGM Volume is now: " + GetBGMVolume());
    }
}

And we’re done! This script can just sit happily inside a utilities folder in your project and you can call it from any other script with just a single line of code and pass in your AudioClip object.

To play a sound effect:

    SoundManager.PlaySFX(audioClip);

To play background music or fade into a new track:

    SoundManager.PlayBGM(audioClip, shouldFade, fadeDuration);

It’s really that simple.

There’s plenty of room for expanding this script, too. You could add a second BGM audio source and update the fade functions to allow cross-fading so that one track can fade into another rather than having to stop first, for example. Quite easily, too. Give it a try!

Posted in Game Design, Tutorials, Unity3D | Tagged , , , | 9 Comments

#GBJAM – Kitty Quest – Post Mortem

*Update 17/10/2016*

So the voting has ended and we placed 24th out of almost 500! I am ecstatic with that result, so proud of how much positive feedback we’ve received for the game too. There’s talk on the wind of putting out an updated version with more enemies and more levels so stay tuned! View the full results here.

*End Update*

Last week saw almost 500 games made for GBJAM5, a game jam that aims to celebrate all-things Gameboy. What an amazing sight to see, almost 500 games in just 10 days. That’s *insane*. I participated along with my friend Jessica Fleur, and together we made Kitty Quest!

Here is a selection of the ones you should look at because they are amazing!

I loved taking part and although it left me exhausted by submission, I managed to submit an entry I am actually legitimately proud of. Kitty Quest is a reasonably complete-feeling game, it even has sound effects and music, something I usually forget about completely, let alone make my own.

_01_title

What went right

#1 Gameboy Limitations and Sprites!

I am really proud of how we made use of the colour palette limitations. The tiles Jess made look really nice and I’m very happy with how sleek and dynamic the cat sprite looks despite being made out of just two colours. It’s animated, functions well and for the most part it’s quite clear what you can and can’t interact with which aids playability.

leberu1

#2 Levels!

Even though they were made in such a hurry, I’ve had some pretty good feedback about the size and design of the levels. Level 1-3 are quite simple levels that introduce the concepts of wall-jumping and obstacles that hurt you. Then 4 and 5 ramp up the size before 6 and 7 ramp up the difficulty. While it shows they were made in a hurry, I’m pretty happy with how they turned out.

We planned to have more enemies and more features, but ultimately if we’d done that it’s probably we’d only have had 1 or 2 levels and I wouldn’t have been satisfied with that at all. Having fewer things also allowed me more time to polish them and fix some nasty bugs.

04_moretings

On a technical side, I decided very early on in the jam to build them in Tiled with a very simple set of rules (solid square collision tiles and everything else is either a game object or a detail tile) and that worked fantastically. While I did spend some time farting around figuring out how to import my placed objects with Tiled2Unity, it allowed me to build all the levels quickly and relatively painlessly. Which was awesome!

#3  Pre-existing Utilities

I had a couple of pre-existing utilities laying around from previous work that were perfect for this jam and saved me a good chunk of time. A couple of utilities for linear interpolation, playing sounds with a single function call, and my pixel-perfect camera setup. Having those ready to go from initial project setup was so helpful. Additionally, pimping out my camera setup to people who were also participating in the jam helped with point #4.

#4 Social media reach!

I’ve been neglecting social media a lot recently and wanted to change that. So I’m now available on twitter again and I’ve been posting development shots in places like reddit and itch.io. Regular gif updates served two purposes, it got eyes on the game and peoples kind responses to them help keep my motivation topped up. My previous 3 games on itch.io have a combined total of about 400 views over months. Kitty Quest got over 500 in less than a day. So I’m super happy with that.

#5 Early Submission

I made absolutely sure not to leave submission to the last minute to give myself some to fix any game-breaking bugs (which was crucial!) and to build the games itch.io page and to just play through the game and make last-minute fixes without being panicked because the deadline is looming. So I locked everything down 12 hours before submission and then only worked on fixing critical bugsand preparing the submission builds. Which saved me so much hassle.

What went wrong

#1 Planning/Time Managment

Despite being relatively organised, I still had to balance my time between working full-time and working on the jam game. I got a lot done in the mornings by getting up earlier than usual, but this wasn’t sustainable and by the last couple of days I was lagging, badly, and I had to slow it down a bit.

Additionally, with some better planning and structure in the order that I tackled features I could have added some more variety with next to no effort. For example enemies that move vertically rather than just horizontally would’ve given me a lot more flexibility in designing levels and had I thought of it before I made the enemies, easier to implement too. But the haphazard way I’d thrown together placing them in Tiled, importing them into Unity and then spawning them on level load made alterations difficult.

#2 Rust/Lack of Practice

As I’d not done a game jam in a relatively long time, I was unprepared for how quickly you need to race through certain tasks to get back to making the actual game. I spent way too long on things like the menu screens and UI simply because I’d forgotten how to do some simple tasks or had to change the way I do them slightly because last time I’d done them was in Unity 3.x.

Also fonts. I wasted a lot of time sorting out fonts and having to learn on-the-go how I’d implement bitmap fonts, which wasted pretty much a whole afternoon’s work. This is something so basic and fundamental I should have had working from the get-go.

#3 Stress/Anxiety

Due to the jam falling at the same time as a work deadline, things got pretty stressful during the weekdays and my anxiety started to kick in. I started having to take regular breaks and found my pace to be very stop/start. On more than one day I called it quits early simply because I felt like I was banging my head against a brick wall and making no progress and only stressing myself out further.

What to take forward

Having a couple of general utilities around, like the sound management script I mentioned earlier, was a godsend. Really saved my ass from having to re-invent the wheel under pressure and waste time getting unity to do simple things like automatically handle playing and disposing of sounds.

So this got me thinking. There’s no reason I can’t do the same thing for a bunch of other general-purpose functionality like screen/sprite flashes, screen transitions/fades, dialogue boxes, healthbars, timers etc. I want to spend a bit of time essentially putting together a ‘game jam essentials’ kit that I can start new projects or prototypes with. They’ll come in really handy for any future jams and if I make them correctly, can be extensible easily so that anything I make in future jams can be rolled back into that initial package.

Additionally, I can move forward safe in the knowledge that I still got this. I can still do game jams no problemo, all I have to do is not let the stress or self-doubt get to me and I’ve proven to myself here that I can do it.

 

Posted in Uncategorized | Leave a comment

Busy Busy – Upcoming Projects for 2016/2017

The second half of 2016 sure has been busy already! I’ve been working pretty much flat out but it’s actually really great to be able to do so again and stay motivated most of the time. Work proper is taking up the majority of my time at the moment.

Don’t worry! I’ve definitely got more tutorials planned, including a new full game series for Sokoban in Unity.

I’m intending to use this place as a bit more of a development journal/blog alongside my tutorials, so I figured I’d do a list of all the projects I’m hoping to tackle in the final quarter of 2016 as well as the first of 2017. Something of a reminder to myself/commitment.

Upcoming Projects for 2016 and early 2017. Expect the chess update soon followed by the Sokoban game.

#GBJAM

update: I’de totally forgotten this was coming up! So this week is the 5th Gameboy Jam, perfect accompaniment to the stuff I’ve been working on lately so I’m currently taking part in that. I’ll do a post-mortem on it afterwards.

Chess Revamp

Due to how insanely busy I’ve been at work, and working on the Project: Barbarian King prototype, I’ve not had time yet to re-visit the gameboy style Chess game I made, but I do plan on fixing the remaining bugs and implementing checkmate detection.

Here’s a peek at the new title screen and palette-swap options!

s150i9o2xtnx

I’ll also probably convert the project into a full game tutorial some time in the future if there is any demand for it.

Sokoban (Full Game Tutorial)

I’m currently planning and designing a full Sokoban style game to base a tutorial on. Sokoban has been done to death, but I wanted to work on a little variant that I could use to help me get more experience putting together full tutorials, and I’m working with my good friend Miso who’s going to produce some bespoke artwork for the game, so I’m super excited to get that underway soon when things quieten down at work.

I had initially planned to do this in HTML5, I even wrote a prototype in ImpactJS, but I think what I’m going to do for the moment is concentrate on actually implementing the game itself. Then if that goes off without a hitch, I may look into porting it to HTML5 via GameMaker or ImpactJS so that it can run across devices in a browser.

This is going to be the next sizeable project I’m going to tackle with an aim to finish it before Christmas so I can start an entirely new project in the new year…

Project: Barbarian King Prototype

First thing this needs is a much better working title… But yeah, this project is taking off pretty quickly and I’m really enjoying the time I’m putting into it. But there’s an awful lot of work to do to get it anywhere near playable.

One thing I’m wary of however, is becoming burned out on this project as it’s quite large. So I’m going to be working on it in short bursts between other projects, particularly as there’s a lot of things I can break down into smaller sub-projects, for example I can take a break from code by working on a chunk of the artwork or map layouts. I’m planning to use working on this project as a bit of a reward for finishing some of the other things I have planned.

Uckers App

uckers

“It’s a bit like Ludo, but it’s not Ludo.”

This should be a really interesting project, I wanted to tackle another board game after Chess because I found implementing that to be a fun challenge.

Uckers is a really interesting variant of Ludo that seems to be only played in the Navy, I was introduced to it at a biker rally that was mainly ex-servicemen attending. It plays a lot like Ludo but has some interesting strategy layers to it, and a very interesting cheat mechanic which will be difficult to reproduce digitally (it’s only cheating if you get caught!) so this should be quite a fun challenge, and potentially a game I can use to dip my toes into the world of online multiplayer for mobile devices.

It should also help improve my event queue system and I can learn more about implementing board games with this project. This is one for early/mid 2017.

Hopefully this lot should keep me busy in the evenings for quite a while!

 

Posted in Uncategorized | Leave a comment