Month: November 2015

A Very Simple Random Level Generation Script for Unity

Randomness is a useful tool in game development to get a lot of content with less work, if utilized correctly. A common use for randomness is level generation.

Random level generation can be implemented in several ways. The following example is kind of faux random level generation, as it uses pre-made “blocks”, akin to rooms, to create one continuous level. For more “true” random generation, I recommend looking into the Unity tutorial on using Cellular Automata for procedural cave generation.

Let’s get to the actual script:

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

public class LevelGenerationController : MonoBehaviour {

    public Transform player;
    public GameObject floor;
    public GameObject[] parts; // level parts used
    public float allowedDistanceUntilEnd = 10f; // if distance from end is lower than this value, moving is triggered 
    public GameObject currentPart;
    private ConnectionPointManager currentConnectionPoints;
    public GameObject nextPart;
    private ConnectionPointManager nextConnectionPoints;

    private int currentPartIndex; // for preventing the use of the same part twice in a row

	// Use this for initialization
	void Start () {
        // find player and floor
        player = GameObject.FindGameObjectWithTag("Player").transform;
        floor = GameObject.FindGameObjectWithTag("Floor");

        // assign the origin level part or block as the current part
        currentPart = floor.transform.FindChild("Origin").gameObject;
        currentConnectionPoints = currentPart.GetComponent<ConnectionPointManager>();

        // pick randomly the next part to be moved to the end of the level
        int nextPartIndex = Random.Range(0, parts.Length);
        nextPart = parts[nextPartIndex];
        nextConnectionPoints = nextPart.GetComponent<ConnectionPointManager>();
        currentPartIndex = nextPartIndex; // cycle indexes
	}
	
	// Update is called once per frame
	void Update () {
        ObservePlayerDistance();
	}

    // move the randomly chosen next part to the end of the level
    // the next part is placed so that it seamlessly joins the previous part
    void MoveNextPart()
    {
        float nextY = currentConnectionPoints.endPoint.position.y - nextConnectionPoints.beginPoint.localPosition.y;
        nextPart.transform.position = new Vector3(currentPart.transform.position.x + 50, nextY, 0);
    }

    void ObservePlayerDistance()
    {
        // if the player is close to the end of the level (or more than allowed)
        if (currentConnectionPoints.endPoint.position.x - player.transform.position.x < allowedDistanceUntilEnd)
        {
            MoveNextPart(); // move next part to the end of the level
            CycleParts(); // assign the moved part to the current part and choose a new next part
        } 
    }

    void CycleParts()
    {
        currentPart = nextPart;
        currentConnectionPoints = nextConnectionPoints;

        // choose a random number and use it as index to pick a new part
        int nextPartIndex = Random.Range(0, parts.Length);
        // prevent the same part from being used twice
        while (nextPartIndex == currentPartIndex)
        {
            nextPartIndex = Random.Range(0, parts.Length);
        }
        nextPart = parts[nextPartIndex];
        currentPartIndex = nextPartIndex; // cycle indexes
        nextConnectionPoints = nextPart.GetComponent<ConnectionPointManager>();
    }
}

So what is actually going on there? The process is rather simple (hence the name of the post).

  1. During Start(), the script looks for the player and floor (requires a GameObject with tag “Floor” to exist), after which the current part is assigned and next part is chosen randomly from the collection of given parts, from which to build the level from.
  2. During Update(), the player character’s position is observed. If the player character is closer to end of the level than allowedDistanceUntilEnd specifies, MoveNextPart() is run. This moves the randomly chosen part to the end of the level, to a position in which it will seamlessly connect to the previous part. Determining what that position is what I will discuss later in this post.
  3. In Update(), after MoveNextPart() has been run, the parts are cycled in CycleParts(). Here the script assigns the previously moved “next part” as the current part and picks a new next part.

Now, to answer how the next position is determined: the controller is told where the beginning points and end points of the current and next parts are. These beginning points and end points are GameObjects that are placed to the end and beginning coordinates of each part or “block”. Each part or “block” also has a script called ConnectionPointManager, which search for the start and end points when the scene is loaded. Basically, the functioning of this script depends on the correct placement of these beginning and end points.

Example of the correct placement of the Beginning Point.
Example of the correct placement of the Beginning Point.

Here is what the ConnectionPointManager looks like:

using UnityEngine;
using System.Collections;

public class ConnectionPointManager : MonoBehaviour {

    public Transform beginPoint;
    public Transform endPoint;

	// Use this for initialization
	void Start () {
        Transform[] points = GetComponentsInChildren<Transform>();
        foreach (Transform point in points)
        {
            if (point.tag.Equals("Begin Point"))
            {
                beginPoint = point;
            }
            if (point.tag.Equals("End Point"))
            {
                endPoint = point;
            }
        }
	}
}

In Start(), all Transform components are collected from the child GameObjects of the part or “block”, after which they are looped through. Depending on the tag of the GameObject, the Transform is assigned to either beginPoint or endPoint.

Beginning and End Points as child objects of "Blocks".
Beginning and End Points as child objects of “Blocks”.

If you want to try out the code, you can download the UnityPackage with all the assets and a functioning test scene from here.

All the visual assets are the creations of Kenney. You can download the asset pack (Platformer Pack Redux) from here.

Simple Player Controller Script In Unity

Possibly the simplest game genre to make is the 2D platformer (by now somewhat infamous for that). Still, the character controller for the player character in a 2D platformer can be tricky to implement.

What makes this particular rather simple script be more complicated is the Finite State Machine that it implements. At this point, the state machine prevents player from jumping in mid-air, or basically prevents the player character from existing in multiple states simultaneously. The state machine however makes expansion of the character’s capabilities simple, if such is desired. For instance, if the player character should be able to slide, a slide state simply is made, and all slide related functionality is added to the state.

The Player Controller:

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

[RequireComponent(typeof(Rigidbody2D))]
public class SimplePlayerController : MonoBehaviour {

    public Rigidbody2D myRigidbody { get; private set; }
    [SerializeField] private LayerMask floorLayer;
    
    private IState currentState;
    private Dictionary<string, IState> states;
    public static string IdleState = "Idle";
    public static string RunState = "Run";
    public static string JumpState = "Jump";

    private float horizontalInput; // by default, A and D keys
    private float movement; // the actual movement, movementSpeed multiplied by horizontalInput
    public float movementSpeed = 20f; // the speed multiplier
    public float jumpVelocity = 30f; // the velocity value that is added to the current velocity vector when player jumps

	// Use this for initialization
	void Start () {
        myRigidbody = GetComponent<Rigidbody2D>();

        // instantiate dictionary and add states
        states = new Dictionary<string, IState>();
        states.Add(IdleState, new PlayerIdleState());
        states.Add(RunState, new PlayerRunState());
        states.Add(JumpState, new PlayerJumpState());

        if (floorLayer == 0) // if floor layer has not been set in the inspector, set it to layer named Floor
        {
            floorLayer = LayerMask.GetMask("Floor");
        }

        // set the current state to Idle
        currentState = states[IdleState];
        currentState.OnEnter(this);
	}
	
	// Update is called once per frame
	void Update () {
        horizontalInput = Input.GetAxis("Horizontal"); // collect horizontal Input
        if (currentState is PlayerRunState) // multiply movementSpeed with horizontalInput if Running
        {
            movement = movementSpeed * horizontalInput;
        }
        currentState.OnUpdate(this); // state update
	}

    void FixedUpdate()
    {
        myRigidbody.velocity = new Vector2(movement, myRigidbody.velocity.y);
    }

    public void Transition(string nextState)
    {
        IState endState = states[nextState];
        endState.OnEnter(this);
        currentState.OnExit(this);
        currentState = endState;
    }

    // check if player is on ground
    public bool IsGrounded()
    {
        // cast a 2D ray downwards with distance of 2f, accept collision only if the object is on floor layer
        if (Physics2D.Raycast(transform.position, Vector2.down, 2f, floorLayer)) 
        {
            Debug.Log("Is on ground");
            return true;
        }
        return false;
    }

    // called from outside the controller, from the jump state (when entered)
    public void Jump()
    {
        myRigidbody.velocity += new Vector2(0, jumpVelocity);
    }
}

The State interface that is implemented by all Player States:

public interface IState
{
    void OnUpdate(SimplePlayerController fsm);
    void OnEnter(SimplePlayerController fsm);
    void OnExit(SimplePlayerController fsm);
}

The Player Idle State:

using System;
using UnityEngine;

public class PlayerIdleState : IState
{
    public void OnUpdate(SimplePlayerController fsm)
    {
        if (Mathf.Abs(Input.GetAxis("Horizontal")) > 0.0001f)
        {
            fsm.Transition(SimplePlayerController.RunState);
        }
        if (Input.GetButtonDown("Jump") && fsm.IsGrounded())
        {
            fsm.Transition(SimplePlayerController.JumpState);
        }
    }

    public void OnEnter(SimplePlayerController fsm)
    {
        Debug.Log("Idle State Enter");
    }

    public void OnExit(SimplePlayerController fsm)
    {
        Debug.Log("Idle State Exit");
    }
}

The Player Run State:

using System;
using UnityEngine;

public class PlayerRunState : IState
{
    public void OnUpdate(SimplePlayerController fsm)
    {
        if (Mathf.Abs(Input.GetAxis("Horizontal")) < 0.0001f)
        {
            fsm.Transition(SimplePlayerController.IdleState);
        }
        if (Input.GetButtonDown("Jump") && fsm.IsGrounded())
        {
            fsm.Transition(SimplePlayerController.JumpState);
        }
    }

    public void OnEnter(SimplePlayerController fsm)
    {
        Debug.Log("Run State Enter");
    }

    public void OnExit(SimplePlayerController fsm)
    {
        Debug.Log("Run State Exit");
    }
}

The Player Jump State:

using System;
using UnityEngine;

public class PlayerJumpState : IState
{
    public void OnUpdate(SimplePlayerController fsm)
    {
        if (fsm.IsGrounded())
        {
            // transition back to idle when back on ground
            fsm.Transition(SimplePlayerController.IdleState);
        }
    }

    public void OnEnter(SimplePlayerController fsm)
    {
        fsm.Jump();
        Debug.Log("Jump State Enter");
    }

    public void OnExit(SimplePlayerController fsm)
    {
        Debug.Log("Jump State Exit");
    }
}

You can download all the code and a sample level from here.
All the amazing art that is in the package is done by Kenney. You can download his Platformer Asset Pack Redux from here.