Select Page
Game Design
Class 07: Additional Game Mechanics

Topics

  • Raycasting, Lerping, Coroutine
  • Additional Game Mechanics
  • Platformer (mechanics) Lab Demonstration
  • 2D Platformer Project

6…6.6!

Raycasts, Lerping, Coroutine

Raycast:

A raycast is a line drawn from a specified point outwards in a given direction and distance. This may be used to detect objects in the Unity scene. The layermask is much like a tag allows you to specify groupings and detect those specifically with raycasts.

You can find the documentation for raycasts here.

using UnityEngine;

public class ExampleClass : MonoBehaviour
{
    void FixedUpdate()
    {
        // Bit shift the index of the layer (8) to get a bit mask
        int layerMask = 1 << 8;

        // This would cast rays only against colliders in layer 8.
        // But instead we want to collide against everything except layer 8. The ~ operator does this, it inverts a bitmask.
        layerMask = ~layerMask;

        RaycastHit hit;
        // Does the ray intersect any objects excluding the player layer
        if (Physics.Raycast(transform.position, transform.TransformDirection(Vector3.forward), out hit, Mathf.Infinity, layerMask))
        {
            Debug.DrawRay(transform.position, transform.TransformDirection(Vector3.forward) * hit.distance, Color.yellow);
            Debug.Log("Did Hit");
        }
        else
        {
            Debug.DrawRay(transform.position, transform.TransformDirection(Vector3.forward) * 1000, Color.white);
            Debug.Log("Did not Hit");
        }
    }
}

Lerp:

Lerp or Linear Interpolation is simply the blending of one value to another. This works for transitioning one value to another over time.

You can find documentation for lerp here.

This platform is lerped on its x position left to right

using UnityEngine;

public class Example : MonoBehaviour
{
    // animate the game object from -1 to +1 and back
    public float minimum = -1.0F;
    public float maximum =  1.0F;

    // starting value for the Lerp
    static float t = 0.0f;

    void Update()
    {
        // animate the position of the game object...
        transform.position = new Vector3(Mathf.Lerp(minimum, maximum, t), 0, 0);

        // .. and increase the t interpolater
        t += 0.5f * Time.deltaTime;

        // now check if the interpolator has reached 1.0
        // and swap maximum and minimum so game object moves
        // in the opposite direction.
        if (t > 1.0f)
        {
            float temp = maximum;
            maximum = minimum;
            minimum = temp;
            t = 0.0f;
        }
    }
}

Coroutines:

Coroutines are cooperative functions. Normally if a function calls another function it may not continue until the function it calls is complete. A coroutine can run but then allow the caller function to continue its business and then when it is time run again. For our purposes this allows us to create more proper “timed” functions.

using UnityEngine;
using System.Collections;

// In this example we show how to invoke a coroutine and
// continue executing the function in parallel.

public class ExampleClass : MonoBehaviour
{
    // In this example we show how to invoke a coroutine and
    // continue executing the function in parallel.

    private IEnumerator coroutine;

    void Start()
    {
        // - After 0 seconds, prints "Starting 0.0"
        // - After 0 seconds, prints "Before WaitAndPrint Finishes 0.0"
        // - After 2 seconds, prints "WaitAndPrint 2.0"
        print("Starting " + Time.time);

        // Start function WaitAndPrint as a coroutine.

        coroutine = WaitAndPrint(2.0f);
        StartCoroutine(coroutine);

        print("Before WaitAndPrint Finishes " + Time.time);
    }

    // every 2 seconds perform the print()
    private IEnumerator WaitAndPrint(float waitTime)
    {
        while (true)
        {
            yield return new WaitForSeconds(waitTime);
            print("WaitAndPrint " + Time.time);
        }
    }
}

Additional Game Mechanics

Platformer (refined mechanics) Lab Demonstration

This is what we are making today

You may download the Unity project here.

Assets

Environment
Foreground
The imagery that is in front of the player
Midground
The imagery that the player character can actually contact, ground, platforms, walls, etc.
Background
The imagery behind the player
Characters
Player
The protagonist controlled by the player
Enemies
The antagonist that kills the player
Other(pickups, UI, etc.)
Lives
Adds life to the players total lives
Points
Adds to the player’s total score

We now have our graphics complete. You can create more as you go.

Scripts

New Scripts
None
No new scripts this week
Updated Scripts
PlayerControllerScript
Add raycasting for detection and lerping for jumping
Old Scripts
PlayerPickupScript
Detects pickups such as lives or score
DeathboxScript
Runs the death function when player enters
VictoryboxScript
Loads next level when player enters
PlayerStatsScript
Keeps track of the player stats such as lives and score
EnemyControllerScript
Moves the enemy sprite and kills the player
GroundCheckScript
Checks that the player character is on the ground
PlayerController.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerController : MonoBehaviour
{
    public float movSpd = 2.5f;
    public float jmpStr = 50f;
    public float jmpBst = 1f;

    public bool ground = false;
    public bool doubleJump = false;

    private Rigidbody2D pRB2D;
    private Animator pAnim;
    private SpriteRenderer pSprite;
    private AudioSource pAudio;
    private GameObject pJumpParticle;

    public AudioClip enemyDeath;
    public AudioClip playerJump;

    private PlayerStats pStats;

    void Awake()
    {
        pRB2D = GetComponent<Rigidbody2D>();
        pAnim = GetComponent<Animator>();
        pSprite = GetComponent<SpriteRenderer>();
        pAudio = GetComponent<AudioSource>();
        pStats = GetComponent<PlayerStats>();
        pJumpParticle = transform.GetChild(0).gameObject;
    }

    void Start()
    {
        pJumpParticle.transform.SetParent(null);
    }

    void Update()
    {
        pRB2D.velocity = new Vector2(Input.GetAxis("Horizontal") * movSpd, pRB2D.velocity.y);

        if(pRB2D.velocity.x > 0.1f) pSprite.flipX = false; 
        if(pRB2D.velocity.x < -0.1f) pSprite.flipX = true; 


        if(Input.GetButtonDown("Jump"))
        {
            if(ground)
            {
                pAudio.clip = playerJump;
                pAudio.Play();
                pJumpParticle.transform.position = transform.position;
                pJumpParticle.GetComponent<ParticleSystem>().Play();
                pRB2D.AddForce(Vector2.up * (jmpStr * jmpBst));
                ground = false;
                doubleJump = true;
            }
            else 
            {
                if(doubleJump)
                {       
                    pAudio.clip = playerJump;  
                    pAudio.Play();           
                    pRB2D.AddForce(Vector2.up * (jmpStr * jmpBst));
                    doubleJump = false;
                }
            }
        }  

        pAnim.SetBool("Ground", ground);
        pAnim.SetFloat("Speed", Mathf.Abs(Input.GetAxis("Horizontal")));  

        if(transform.position.y <= -10)
        {

            pStats.UpdateLives(-1);
        }        
    }

    void OnCollisionEnter2D(Collision2D col)
    {
        if(col.gameObject.CompareTag("Ground"))
            ground = true;
    }

    void OnCollisionExit2D(Collision2D col)
    {
        if(col.gameObject.CompareTag("Ground"))
            ground = false;
    }

    public void PowerUp(string type)
    {
        switch(type)
        {
            case "Jump Boost":
                StartCoroutine(JumpBoost());
                break;                
        }
    }

    IEnumerator JumpBoost()
    {
        jmpBst = 2;
        yield return new WaitForSeconds(10);
        jmpBst = 1;
    }

    void FixedUpdate()
    {
        CheckEnemyBelow();
    }

    void CheckEnemyBelow()
    {
        RaycastHit2D hit = Physics2D.Raycast(new Vector2(transform.position.x, transform.position.y - .5f), -Vector2.up, .5f);
        Debug.DrawRay(new Vector2(transform.position.x, transform.position.y - .5f), -Vector2.up * 0.5f, Color.blue, 2, false);

        if(hit.collider != null)
        {
            if(hit.collider.CompareTag("Enemy"))
            {
                pAudio.clip = enemyDeath;
                pAudio.Play();
                Destroy(hit.collider.gameObject);
            }
                
        }
    }

}
EnemyController.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class EnemyController : MonoBehaviour
{
    public float enemySpeed = 2;

    private int enemyDirection = 1;

    private Rigidbody2D eRB2D;
    private SpriteRenderer eGraphic;

    void Awake()
    {
        eRB2D = GetComponent<Rigidbody2D>();
        eGraphic = GetComponentInChildren<SpriteRenderer>();
    }

    void Update()
    {
        Move();
    }

    void Move()
    {
        eRB2D.velocity = new Vector2 ((enemySpeed * enemyDirection), transform.position.y);
    }

    void OnTriggerExit2D(Collider2D col)
    {
        enemyDirection *= -1;
        if(eGraphic.flipX) eGraphic.flipX = false; 
        else eGraphic.flipX = true;

        if(eGraphic.flipY) eGraphic.flipY = false; 
        else eGraphic.flipY = true;
    }

    void OnTriggerEnter2D(Collider2D col)
    {
        if(col.gameObject.CompareTag("Player"))
        {
            col.gameObject.GetComponent<PlayerStats>().UpdateLives(-1);
        }
    }
}
MeleeAttack.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class MeleeAttack : MonoBehaviour
{
    private SpriteRenderer pSprite;
    public Vector2 attackXpos;

    private GameObject enemy;

    void Awake()
    {
        pSprite = GetComponentInParent<SpriteRenderer>();
    }

    void Start()
    {
        attackXpos = transform.position;
    }

    void Update()
    {
        if(Input.GetButtonDown("Fire1"))
        {
            if(pSprite.flipX)
            {
                transform.localPosition = new Vector2(-attackXpos.x, 0);
            }                
            else
            {
                transform.localPosition = new Vector2(attackXpos.x, 0);
            }
                

            if(enemy)
            {
                Destroy(enemy);
            }
        }
    }

    void OnTriggerEnter2D(Collider2D col)
    {
        if(col.gameObject.CompareTag("Enemy"))
        {
            enemy = col.gameObject;
        }
    }
    
    void OnTriggerExit2D(Collider2D col)
    {
        if(col.gameObject.CompareTag("Enemy"))
        {
            enemy = null;
        }
    }
}
CameraController.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class CameraController : MonoBehaviour
{
    public Transform pTransform;
    public Vector3 offset;

    void Update()
    {
        transform.position = pTransform.position + offset;
    }
}
Victory.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Victory : MonoBehaviour
{

    void OnTriggerEnter2D(Collider2D col)
    {
        if(col.gameObject.CompareTag("Player") && col.gameObject.GetComponent<PlayerStats>().hasKey)
        {
            Debug.Log("you won!");
        }
    }
}
PlayerStats.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using TMPro;

public class PlayerStats : MonoBehaviour
{
    public int playerLives = 3;
    public int playerScore = 0;

    public bool hasKey = false;
    private Vector2 startPos;

    public TextMeshProUGUI livesText;
    public TextMeshProUGUI scoreText;

    void Start()
    {
        UpdateLives(0);
        UpdateScore(0);
        startPos = transform.position;
    }

    public void UpdateLives(int lives)
    {
        playerLives += lives;
        livesText.text = "Lives: " + playerLives;

        if(lives < 0)
            transform.position = startPos;
    }

    public void UpdateScore(int points)
    {
        playerScore += points;
        scoreText.text = "Score: " + playerScore;
    }

}
StartMenu.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;

public class StartMenuScript : MonoBehaviour
{
    public void StartGame()
    {
        SceneManager.LoadScene("Level01");
    }

    public void QuitGame()
    {
        Application.Quit();
    }
}
Pickup.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Pickup : MonoBehaviour
{
    private Animator pickupAnim;

    public string pickup = "life";

    void Awake()
    {
        pickupAnim = GetComponentInChildren<Animator>();
    }

    void OnTriggerEnter2D(Collider2D col)
    {
        if(col.gameObject.CompareTag("Player"))
        {
            switch(pickup)
            {
                case "Life":
                    col.gameObject.GetComponent<PlayerStats>().UpdateLives(1);
                    Destroy(gameObject);
                    break;
                case "Point":
                    col.gameObject.GetComponent<PlayerStats>().UpdateScore(1);
                    if(pickupAnim)
                        pickupAnim.SetTrigger("Score");
                    StartCoroutine(WaitToKill(0.25f));
                    break;
                case "Jump Boost":
                    col.gameObject.GetComponent<PlayerController>().PowerUp("Jump Boost");
                    Destroy(gameObject);
                    break;
                case "Key":
                    col.gameObject.GetComponent<PlayerStats>().hasKey = true;
                    Destroy(gameObject);
                    break;
            }
        }
    }

    IEnumerator WaitToKill(float timeToKill)
    {
        yield return new WaitForSeconds(timeToKill);
        Destroy(gameObject);
    }
}
Spring.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Spring : MonoBehaviour
{
    [SerializeField] float springPower = 10f;

    void OnTriggerEnter2D(Collider2D col)
    {
        if(col.gameObject.CompareTag("Player"))
            col.gameObject.GetComponent<Rigidbody2D>().AddForce(Vector2.up * springPower, ForceMode2D.Impulse);
    }
}
MovingPlatform.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class MovingPlatform : MonoBehaviour
{
    public Transform platformPos;
    public Transform targetPos;

    public Vector2 startPos;
    public Vector2 endPos;

    private float t = 0.0f;

    void Start()
    {
        startPos = platformPos.transform.position;
        endPos = targetPos.transform.position;
    }

    void Update()
    {
        // animate the position of the game object...
        transform.position = Vector2.Lerp(startPos, endPos, t);

        // .. and increase the t interpolater
        t += 0.5f * Time.deltaTime;

        // now check if the interpolator has reached 1.0
        // and swap maximum and minimum so game object moves
        // in the opposite direction.
        if (t > 1.0f)
        {
            Vector2 temp = startPos;
            startPos = endPos;
            endPos = temp;
            t = 0.0f;
        }
    }

    void OnCollisionEnter2D(Collision2D col)
    {
        if(col.gameObject.CompareTag("Player"))
        {
            col.gameObject.transform.parent = transform;
        }
    }

    void OnCollisionExit2D(Collision2D col)
    {
        if(col.gameObject.CompareTag("Player"))
        {
            col.gameObject.transform.parent = null;
        }
    }
}
FlipPlayer.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class FlipPlayer : MonoBehaviour
{
    public bool playerFlipped = false;

    Vector3 gravity;
    public float pGroundOffset = 1;

    private Transform pTransform;
    private SpriteRenderer pSprite;
    private PlayerController pController;

    void Awake()
    {
        pTransform = GetComponent<Transform>();
        pSprite = GetComponent<SpriteRenderer>();
        pController = GetComponent<PlayerController>();
        gravity = Physics2D.gravity;
    }

    void Update()
    {
        if(Input.GetButtonDown("Fire2"))
        {
            Flip();

            if(!playerFlipped)
                playerFlipped = true;
            else
                playerFlipped = false;
        }            
    }

    void Flip()
    {        
        //flip the gravity
        gravity.y *= -1;
        Physics2D.gravity = gravity;
        //flip player position
        pGroundOffset *= -1;
        pTransform.position = new Vector2(pTransform.position.x, pTransform.position.y + pGroundOffset);
        //flip the sprite
        if(pSprite.flipY) pSprite.flipY = false;
        else pSprite.flipY = true;
        //flip jump direction
        pController.jmpStr *= -1;
    }
}

2D Platformer Project

2D Platformer Project:

This project is the completion of the game you developed throughout the labs thus far. You will fix any errors, produce supplimental graphics, develop novel elements (mechanics, graphics, etc.), and overall polish the game. Once completed the results should be exported as a final build. The unity project folder and the final game build should be submitted.

You will be graded on the following:
  • Coding
    • Develop functional code that operates as expected.
    • Troubleshoot any errors and optimize the game.
  • Graphics
    • Develop necessary graphics for the game.
    • Clean up and polish graphical assets.
  • Novelty
    • Develop an innovative, creative elements that separate the game from others.
  • Final Build
    • Polish and implement solid craftsmanship to develop a “finished” game.
    • Create a build of the game that is compatible for Mac, PC, Web, and mobile.
Resources:
Assignment Video Tutorials
You may watch the tutorial videos below to help you complete your assignment.

Assignment Video Tutorials

Wait! Before you go!

Did you remember to?

  • Read through this webpage
  • Submit 2D Platformer Project on Blackboard