Star-Gazer

Project Type: Personal Project 

Software Used: Unity, Visual Studio, Github

Language Used: C#

Primary Role(s): Gameplay, A.I, Design, Art, Audio, UI

Project Outline

Star Gazer is an endless 2D space shooter survival game developed for mobile devices. Players will shoot incoming obsticles to defend the planet their cannon has been placed on. Obsticles never stop spawning until the game is over. Players will obtain star dust to upgrade their cannons states and can also purchase different planets with the ingame currency. 

What I did in this project

  •  I made the entire project and all of its assets


      • I made the player class that allows for movement and shooting functionality. Allowing players to shoot their cannon and reload it too.

          • The Player class is set up with a parent/child structure, all the art is inside the children objects
          • there is a LineRenderer component to one of the children objects to make a laser effect
          • The parent object is just an empty game object with all our code so the cannon can rotate on a pivot 360 degrees.
    •  
      • I made the AI classes to allow for meteors and stars to spawn during runtime in the game world and made a controller so the spawned objects could move

 

          • The AI spawner class makes the obstacles spawn at a set rate that increases over time, the obstacles spawn within a radius that is set and changes as the game goes on to add difficultly
          • The obstacle controller makes the meteor or star move towards their target, this being the planet the player defends
          • Each obstacle has a polygon collider component added. 
          • There is a Rigidbody2D component added to detect something has been hit. 
          • The obstacles have a health value that can be changed when shot and then explodes the obstacle
    •  
      • I made the game-manager class which balances and monitors the in-game runtime states of the game.

 

          • It finds and balances all the UI components inside the game
          • It waits to detect if the game has started which is balanced by detecting what menu we are in and also a fade in and out effect when transitioning to different parts of the game
          • it increases a timer to provide players with a high score
          • it saves data in “PlayerPrefs” which is built into unity so data can be “set” or “get”
          • The manager detects what art the player has unlocked and has equipped
          • The manager balances when the game is over and initiates shaders or stops certain behaviours; like spawning obstacles or keeping obstacles inside the scene.
        • The Store manager does the exact same logic as the game manager however, it mainly controls the UI store menu. Controlling animations for the UI vendor or button UI components. It also just allows players to make purchases and disallowed them when money is insignificant. 

 

      • I made all the art for the game in photoshop making basic cartoon-like art assets. I also made particle effects inside unity for obstacle explosions or laser shooting muzzle flashes. 
      • I made shaders using the universal renderer pipeline inside unity, to make a dissolve effect or a sprite outline for the obstacles. I mainly used the pipeline to create a forcefield for the games art.
      • I also made all the music and sounds for the game using Audacity to blend noises and layer them so different sounds could be created. 

Player Controller Class

[expand title=”Variables & Set-up”]

  1. A list of variables in which would be used and changed throughout the player class
  • In the Start() Function all components are found and set up for runtime, some variables are found through the Resources.Load() function so they are gathered from the asset folder data. 
  • Two functions are also called at Start() being the function for disabling the laser beam for the cannon players shoot and another function to add a particle effect on the fire position of our cannon. 

[/expand]

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

public class SpaceCannon_Controller : MonoBehaviour
{
    // Shooting
    // distance in which we shoot
    float range = 200;
    // The max ammo the cannon can hold by default
    [HideInInspector]
    public int maxAmmo = 4;
    [Tooltip("What layers is the cannon bullets allowed to hit?")]
    public LayerMask whatToHit;
    [Tooltip("The current ammo that we have loaded inside the cannon")]
    public int currentAmmo;
    bool isReloading = false;
    public float reloadSpeed = 2f;

    private float nextTimeToFire;
    public float FireRate = 15f;

    private Game_Manager GM;
    private AudioSource AS;

    private AudioClip shoot;

    #region Laser Variables
    [Header("Laser Variables")]
    public LineRenderer lineRenderer;
    // Position where we fire the projectile bullet from
    public Transform firePoint;

    public GameObject startVFX;

    private List<ParticleSystem> LaserParticles = new List<ParticleSystem>();
    #endregion


    // Effects 
    public Image reloadImage;

    private AudioClip reloadChargeUp, reloadClick;

    // Start is called before the first frame update
    void Start()
    {
        if(AS == null)
        {
            AS = gameObject.AddComponent<AudioSource>();
        }
        else
        {
            AS = gameObject.GetComponent<AudioSource>();
        }

        AS.loop = false;

        GM = GameObject.Find("GameManager").GetComponent<Game_Manager>();
        // find the first child to get the fire position Transform component
        firePoint = gameObject.transform.GetChild(0).GetComponent<Transform>();
        // make sure the cannon is loaded with the max amount of bullets
        currentAmmo = maxAmmo;

        lineRenderer = gameObject.transform.GetChild(1).GetComponentInChildren<LineRenderer>();

        startVFX = gameObject.transform.GetChild(1).GetChild(1).gameObject;

        reloadImage = GameObject.Find("ReloadImage").GetComponent<Image>();

        shoot = Resources.Load<AudioClip>("Audio/Player/Shoot Cannon");
        reloadChargeUp = Resources.Load<AudioClip>("Audio/Player/Cannon Reload Charge");
        reloadClick = Resources.Load<AudioClip>("Audio/Player/Cannon Reload Click");

        FillParticleLost();
        DisableLaser();
    }

[expand title=”Runtime Function”]

  • Update() Holds logic within a key condition checking if the game has actually started which is balanced by a boolean in the game manager class.
  • When the game has started Functions like CannonRotation(), Shoot() and reload are monitored to check the state of play.
  • Reloading the gun prevents the player from shooting it but does not prevent the player from rotating it. Runtime also stops particle effects from rendering. 

[/expand]

// Update is called once per frame
    void Update()
    {
        if (GM.startGame != true)
            return;
        else
        {
            CannonRotation(5, -90);


            if (isReloading)
            {
                for (int i = 0; i < LaserParticles.Count; i++)
                    LaserParticles[i].Stop();
                return;
            }


            // shoot when we press left click
            if (Input.GetButtonDown("Fire1") && currentAmmo > 0 && Time.time > nextTimeToFire)
            {
                // Calculate next time to shoot cannon
                nextTimeToFire = Time.time + FireRate;
                // Shoot the cannon
                Shoot();
            }
            // Dont make distant distort sound on shoot 
            if(AS.time >= 2 && !isReloading)
            {
                AS.volume = 0;
            }

            // we are now reloading
            if (currentAmmo <= 0)
                StartCoroutine(ReloadCannon(reloadSpeed));
        }
    }

[expand title=”Functions to be called on runtime”]

  • The CannonRotation() function gathers two parts of important data. 
  • The first part is the direction in which the cannon is going to face. This is important as we need to know what direction we face to shoot the cannon
  • Secondly, the other data is rotation. getting the angle so we can Slerp() towards the direction and have the correct angle. 
  • So both of these pieces of data work together so when the player presses the screen of their smartphone device, the cannon can detect that they have inputted and then the cannon will rotate towards that position. 
  • The Shoot() function edits certain variables like an audio source to balance the volume of the shooting sound. 
  • Shoot() uses 2D raycasts to detect if the player has hit an object of relevant layer using a layer mask, called “whatToHit”
  • Then the “hit” data can return true or false and when its true then we enable the laser animation and start our shoot effect, then finding data from the object we hit and then detecting health so the obstacle will explode. 
  • Finally, we have the ReloadCannon() IEnumerator, which allows the cannon to reload to max ammo within a set time during frames. The class has a “reloadTime” variable
  • The time will be deducted in the function so when the time is zero then we have max again. 
  • We also use an IEnumerator as we are editing the fillamount of a UI image inside unity. So it can provide a smooth annimation. 

[/expand]

void CannonRotation(float rotationSpeed, float offset)
    {
        // OBTAIN DIRECTION \\
//         ----------------
        // get the mouse position 
        Vector3 mousePosition = Input.mousePosition;
        // get the mouse position on screen
        mousePosition = Camera.main.ScreenToWorldPoint(mousePosition);
        // get the direction in which we will be facing towards (where the mouse is)    // target
        Vector2 direction = new Vector2(mousePosition.x - transform.position.x, mousePosition.y - transform.position.y);

        // SET ROTATION \\
//        -------------  

        // Get the angle from the current direction to the desired target
        float angle = Mathf.Atan2(direction.y, direction.x) * Mathf.Rad2Deg;
        // set the angle into a quaternion + sprite offset depending on initial sprite facing direction
        Quaternion rotation = Quaternion.Euler(new Vector3(0, 0, angle + offset));
        rotation.eulerAngles = new Vector3(0, 0, angle + offset);
        // Rotate current game object to face the target using a slerp function which adds some smoothing to the movement
        transform.rotation = Quaternion.Slerp(transform.rotation, rotation, rotationSpeed * Time.deltaTime);

    }
    Obsticle_Controller hazzardScript;
    void Shoot()
    {
        AS.volume = 0.5f;
        AS.clip = shoot;
        // Draw the ray in scene
        Debug.DrawRay(firePoint.transform.position, firePoint.transform.TransformDirection(Vector2.up) * range, Color.blue);
        // Data to understand what passes through Hit
        RaycastHit2D hit = Physics2D.Raycast(firePoint.position, firePoint.transform.TransformDirection(Vector2.up), range, whatToHit);

        if(hit)
        {
            EnableLaser();
            startVFX.transform.position = firePoint.position;

            StartCoroutine(LaserAnimation(firePoint.position, hit.point));

            // Debug message to show we hit something
            Debug.Log("Hit Object" + "" + "Name" + "" + hit.collider.name);
            currentAmmo--;
            hazzardScript = hit.transform.GetComponent<Obsticle_Controller>();
            AS.Play();
        }
    }
    
        IEnumerator ReloadCannon(float reloadTime)
    {
        AS.volume = 1;
        // Start the event
        isReloading = true;
        // get the current realtime of the reload value time
        float currentTime = reloadTime;
        // turn on the image that will show when we reload
        reloadImage.enabled = true;
        // set the value for the fill to be 0 so the effect is about to start
        reloadImage.fillAmount = 0;
        // show we are reloading in the console
        Debug.Log("Reloading . . .");
        // play sound 
        AS.clip = reloadChargeUp;
        AS.Play();
        // Loop check to see if the time we have is still greater than 0
        while (currentTime > 0)
        {
            // Decrease value of current time
            currentTime -= Time.deltaTime;
            // fill the image and divide by the duration to make sure it does not increase to fast
            reloadImage.fillAmount = currentTime / reloadTime;
            // get next frame
            yield return null;
        }
        // AFTER LOOP IS DONE
        AS.volume = 0;
        // reload the cannon by giving ammo back
        currentAmmo = maxAmmo;
        // turn off the effect image
        reloadImage.enabled = false;
        // End function
        isReloading = false;
    }

[expand title=”Line Renderer Animation control”]

  • EnableLaser() allows for the cannon shoot laser particle effect to be called
  • The laser effect has an animation which is called LaserAnimation() it uses a LineRenderer component to fire towards the obstacles
  • the variable “t” increases over time with a for loop so that the LineRenderer.SetPosition(1) which is our target, will animate smoothly towards the target instead of instant travel. 
  • The animation also holds important logic like damaging the obstacles. placing it here makes more sense as we can tell when the animation is over and then we can damage the obstacle and turn off the laser so everything looks normal. If not the obstacle would have destroyed to fast which would have sent the wrong type of feedback to the player. 
  • When the animation is over we call DisableLaser() function which stops all the particle effects from playing and turns off the LineRenderer component.

[/expand]

 #region Laser Functions
    void EnableLaser()
    {
        lineRenderer.enabled = true;

        for (int i = 0; i < LaserParticles.Count; i++)
            LaserParticles[i].Play();
    }
    Vector3 newPos;
    IEnumerator LaserAnimation(Vector2 startPosition, Vector2 endPosition)
    {
        float t = 0;
        float time = 0.3f;
        startPosition = firePoint.position;

        lineRenderer.SetPosition(0, startPosition);

        for (; t < time; t+= Time.deltaTime)
        {
            newPos = Vector2.Lerp(startPosition, endPosition, t / time);
            lineRenderer.SetPosition(1, newPos);

            yield return null;
        }

        if(lineRenderer.GetPosition(1) == newPos)
        {
            hazzardScript.Damage(1);
            yield return new WaitForSeconds(1);
            DisableLaser();
        }

        lineRenderer.SetPosition(1, endPosition);
    }

    void DisableLaser()
    {
        lineRenderer.enabled = false;

        for (int i = 0; i < LaserParticles.Count; i++)
            LaserParticles[i].Stop();
    }

    void FillParticleLost()
    {
        for(int i = 0; i < startVFX.transform.childCount; i++)
        {
            var ps = startVFX.transform.GetChild(i).GetComponent<ParticleSystem>();
            if (ps != null)
                LaserParticles.Add(ps);
        }
    }
    #endregion

AI Classes

Obstacle Spawner

[expand title=”AI Spawner StartUp”]

  • The Obstacle spawner variables consist of what we are going to spawn. Two GameObjects, meteor or star. 
  • Variables to create a spawn logic that spawns objects over time too.  
  • In Start() we find obstacles to spawn in the Resources folder and we set values while also finding the Game Manager during the first frame using Find(“”).

[/expand]

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

public class Obsticle_Spawner : MonoBehaviour
{
    private GameObject meteor;
    private GameObject star;

    private const int MAX_AMOUNT = 10;
    public float rateOfSpawn;
    private float nextTimeToSpawn;

    public float distanceAway;

    // minute and second calulator
    [Tooltip("Time in mins until spawnrate goes down")]
    public float Seconds;   // default 30 seconds 
    private float timeRemainingValue;

    private float clampValue = 1.2f;

    bool clampFound = false;
    //[HideInInspector]
    public List<GameObject> allSpawner = new List<GameObject>();


    Game_Manager GM;
    
    // Start is called before the first frame update
    void Start()
    {
        // obtain Variable
        meteor = Resources.Load<GameObject>("Prefabs/Idea #2/Obsticles/Meteor");
        star = Resources.Load<GameObject>("Prefabs/Idea #2/Obsticles/Star");

        GM = GameObject.Find("GameManager").GetComponent<Game_Manager>();

        nextTimeToSpawn = rateOfSpawn;

        timeRemainingValue = Seconds;
    }

[expand title=”Obsticle Spawner On Runtime”]

  • During runtime Update() waits for the “startGame” boolean to be true
  • It means no objects will be spawned until the game has started. 
  • During the game after the countdown has met its limit SpawnMeteors() function gets monitored
  • SpawnMeteors() spawns the GameObjects of a star or meteor. 
  • Update() also checks to see if the changeable spawnRate is clamped. meaning it stops changing its value and has been deducted enough. 
  • The SpawnMeteors() function checks to see if we spawn a star or meteor. there is a 30% chance to spawn a star so most of the time the spawner spawns obstacles. 

[/expand]

// Update is called once per frame
    void Update()
    {
        if(GM.startGame == false)
        {
            return;
        }
        else
        {
            nextTimeToSpawn -= Time.deltaTime;
            if (nextTimeToSpawn <= 0)
            {
                SpawnMeteors();
                nextTimeToSpawn = rateOfSpawn;
            }
            // when to stop altering spawn rate
            if (rateOfSpawn <= clampValue)
            {
                clampFound = true;  // locks function from running
            }
            else
            {
                // alter time on runtime
                TimeAlive();
            }
        }
    }

    void SpawnMeteors()
    {
        // if not run meteor code
        if (Random.value > 0.9f)    // 30% chance to spawn star
        {
            Vector3 centre = transform.position;
            Vector3 position = RandomCircle(centre, distanceAway);
            GameObject s = Instantiate(star, position, Quaternion.identity);
            s.name = "Star";
            allSpawner.Add(s);
        }
        else     // 70% chance to spawn
        {
            // centre of radius position
            Vector3 center = transform.position;
            // Make the spawn position, decided by the centre point and the radius so we know how far away the circle is made.
            Vector3 position = RandomCircle(center, distanceAway);
            // Spawn meteor object <- Later alter this so we can spawn a star to give us more star dust - money
            GameObject m = Instantiate(meteor, position, Quaternion.identity);
            m.name = "Meteor";
            allSpawner.Add(m);
        }

    }

[expand title=”Runtime Functions”]

  • The RandomCircle() function allows for a spawn area within a radius. The obstacles will be spawned within a radius that is set by variables made. 
  • TimeAlive() changes how fast the obstacles are spawned, overtime on runtime when the game has started the “rateOfSpawn” variable will go down which makes spawning faster to add more obstacles in the scene to challenge the player.
  • DisplayTime() simply calculates how long we have been running the game which then in hand helps the TimeAlive() function be more balanced by checking via conditions.

[/expand]

    Vector3 RandomCircle(Vector3 center, float radius)
    {
        // degree of where objects can spawn
        float angle = Random.value * 360;
        // empty vector to pass data into
        Vector3 pos;
        // Get the X and Y coodinates of the return value points
        pos.x = center.x + radius * Mathf.Sin(angle * Mathf.Deg2Rad);
        pos.y = center.y + radius * Mathf.Cos(angle * Mathf.Deg2Rad);
        // Z axis need not be altered
        pos.z = center.z;
        // return this vector to the function
        return pos;
    }


    void TimeAlive()
    {
        if (!clampFound)
        {
            if (Seconds > 0)
            {
                Seconds -= Time.deltaTime;
                DisplayTime(Seconds);
            }
            else
            {
                Debug.Log("Time has run out");
                Seconds = timeRemainingValue;
                Mathf.Clamp(rateOfSpawn, clampValue, clampValue);
                rateOfSpawn -= 0.2f;
            }
        }
        else
        {
            Seconds = timeRemainingValue;
        }
    }

    void DisplayTime(float timeToDsiplay)
    {
        timeToDsiplay += 1;

        float minutes = Mathf.FloorToInt(timeToDsiplay / 60);
        float seconds = Mathf.FloorToInt(timeToDsiplay % 60);
        float milliSeconds = (timeToDsiplay % 1) * 1000;
    }

Obstacle Controller

[expand title=”Variables & Start-up”]

  •  The Star() function calls another function in which gets and sets the data we need to make the class work. I put this into the SetUp() function instead of Start() as it looked less cluttered. 
  • SetUp() mainly finds components in the Resources Folder, it also adds components instead of finding them. and alters settings. 

[/expand]

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

public class Obsticle_Controller : MonoBehaviour
{
    private Rigidbody2D Physics;
    private Collider2D Collision;

    [Header("Health")]
    public int currentHealth;
    private int maxHealth = 1;

    [Header("Movement")]
    public GameObject target;
    public bool doesRotate = false;
    public float RotationSpeed;
    public float movementSpeed;

    Vector2 startPosition;

    public int meteor_Money;
    public int star_Money;

    GameObject meteorExplosion;
    GameObject starExplosion;
    GameObject impactEffect;

    private Game_Manager GM;
    // event to change pulse effect
    public static event System.Action ObsticleDestroyed = delegate { };

    GameObject hitSoundPrefab;

    // Start is called before the first frame update
    void Start()
    {
        SetUp();
        // Reset for multi use 
        ObsticleDestroyed = null;
        currentHealth = maxHealth;
    }
    
    void SetUp()
    {
        startPosition = transform.position;
        // add component
        Physics = gameObject.AddComponent<Rigidbody2D>();
        //Physics.isKinematic = true;     // no real physics used 
        Physics.gravityScale = 0;
        // add component 
        Collision = gameObject.AddComponent<PolygonCollider2D>();

        target = GameObject.Find("Planet");
        GM = GameObject.Find("GameManager").GetComponent<Game_Manager>();

        starExplosion = Resources.Load<GameObject>("Prefabs/Idea #2/Particle Effects/Obsticles/Star_ExplosionEffect");
        meteorExplosion = Resources.Load<GameObject>("Prefabs/Idea #2/Particle Effects/Obsticles/Meteor_ExplosionEffect");
        impactEffect = Resources.Load<GameObject>("Prefabs/Idea #2/Particle Effects/Obsticles/Hit Impact");
        hitSoundPrefab = Resources.Load<GameObject>("Audio/Spawnables/HitSound");

        // Check to see if the money we have is right 
        meteor_Money = GM.MeteorPoints;
        star_Money = GM.StarPoints;

        if(doesRotate)
        {
            // clockwise or counter RNG

            int randomValue = 0;
            randomValue = Random.Range(0, 10);

            if (randomValue < 5)
            {
                // clockwise
                RotationSpeed = 90f;
            }
            else
            {
                // counter clockwise
                RotationSpeed = -90f;
            }
        }
    }

[expand title=”Runtime Movement”]

  • Update() waits until the game is started before moving the obstacles.
  • The obstacles current health is monitored so the class knows when to run the Explode() function. 
  • Movement() function just moves the gameObject towards the target while it also rotates it 

[/expand]

// Update is called once per frame
    void Update()
    {
        if(GM.startGame != true)
        {
            return;
        }
        else
        {
            // Function to move and rotate object
            Movement();
            // monitor whhen we explode
            if (currentHealth <= 0)
            {
                Explode(true);
            }
        }
    }
    
    void Movement()
    {
        if(doesRotate)
        {
            // Rotate Object
            this.gameObject.transform.Rotate(Vector3.forward * RotationSpeed * Time.deltaTime);
        }

        // Move object towards its target 
        transform.position = Vector2.MoveTowards(transform.position, target.transform.position, movementSpeed * Time.deltaTime);
        // Move towards the planet but always hits the dom first for its in the way
    }

[expand title=”Obstacle Death Function & Collision”]

  • The Damage() function just alters the obstacles health value.
  • OnDisable() is a Unity function that stops a System.Action in the game manager
  • Explode() is just a function to balance if the player destroys the obstacles or does it hit the forcefield.
  • Explode() spawns particle effects and increases the player’s in-game currency while also triggering an Action event. 
  • Explode() also edits settings like the obstacle spawner list, removing the current object from it so its not cluttered with mission exceptions
  • The OnCollisionEneter2D() is just a function to detect what the object has hit and what happens when it does hit something. 

[/expand]

public void Damage(int dmg)
{
    currentHealth -= dmg;
}

private void OnDisable()
{
    ObsticleDestroyed -= GM.RunCo;
}

void Explode(bool playerShot)
{
    // send money
    if(playerShot && gameObject.layer == 11)
    {
        // public event happens now 
        ObsticleDestroyed();
        // increase money
        Game_Manager.starDust += meteor_Money;
        GameObject ps_Meteor = Instantiate(meteorExplosion, transform.position, Quaternion.identity);
        Destroy(ps_Meteor, 3f);
    }
    else if(playerShot && gameObject.layer == 8)
    {
        // public event happens
        ObsticleDestroyed();
        // increase money
        Game_Manager.starDust += star_Money;
        GameObject ps_Star = Instantiate(starExplosion, transform.position, Quaternion.identity);
        Destroy(ps_Star, 3f);

    }

        Destroy(this.gameObject);

        GameObject OS = GameObject.Find("Obsticle_Spawner");
        OS.GetComponent<Obsticle_Spawner>().allSpawner.Remove(this.gameObject);

        if(GameObject.Find("HitSound") == null)
        {
            GameObject hit = Instantiate(hitSoundPrefab, Vector3.zero, Quaternion.identity) as GameObject;
            hit.name = "HitSound";
            Destroy(hit, 1f);
        }
    }

    private void OnCollisionEnter2D(Collision2D collision)
    {
        // Planet and Dome (The goal of obsticle)
        if(collision.gameObject.layer == 12 || collision.gameObject.layer == 13)
        {
            GameObject HitEffect = Instantiate(impactEffect, transform.position, Quaternion.identity);
            HitEffect.transform.LookAt(startPosition);
            Explode(false);
        }

        if(collision.gameObject.layer == 12)
        {
            Game_Manager.DomeHealth -= 1;
        }
        else if(collision.gameObject.layer == 13)
        {
            // Game Over
            Game_Manager.PlanetHealth -= 1;
        }
    }

Manager Classes

Game Manager

[expand title=”Start Up & Variables”]

  • The Awake() being a unity built-in function is used here first to make sure the data for the player is found and also finding components that will be turned off during the Start() function
  • Start() mainly finds functions and sets everything up so that it will work during runtime.
  • The OnStartFadeEffect() function gets called on Awake() as we want to fade into the main menu scene when the game starts.

[/expand]

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

public class Game_Manager : MonoBehaviour
{
    public GameObject mainMenu;
    // Game Over Menu
    public GameObject GameOver_Menu;
    // the shop menu 
    public GameObject shop_Menu;
    // Menu that appears when the player is done 
    public GameObject CelebrationMenu;
    // The Players in game canvas UI 
    public GameObject PlayerCanvas;

    // Player Controller script
    public SpaceCannon_Controller playerController;
    // Force Field Object hit detection in scene
    public GameObject ForceFieldCollisionDetect;
    // The Gameobject for the force field
    public GameObject ForceField;
    // Force field shader
    private Material _ForceField_Shader;
    private float crossSectionValue = -8;


    // Planet GameObject in scene
    private GameObject planet;
    // time to fade and dissolve planet
    float fade = 1f;
    // when do we dissolve 
    bool dissolving = false;

    public static int DomeHealth = 3;
    public static int PlanetHealth = 1;
    [Tooltip("Boolean that ends the game")]
    public bool Game_Over = false;
    [Tooltip("Bool to check if the current highscore has been beaten")]
    public bool highScore_Beaten = false;

    [Tooltip("The currency earned to spend in the shop")]
    public static int starDust;
    // value stored from the static value
    public int money;

    // the value to obtain time
    private float startTime;
    // characters to format so it looks like a timer
    private string textTime;
    private string savedTextTime;
    // Display Value of Time after math
    float guiTime;
    // This is our time value, the value of the current Time.time
    public float CurrentTime = 0;
    // the value that we pause at. The CurrentTime we was on before we paused
    public float realTime = 0;
    // the subtract value which is CurrentTime - Realtime = value
    public float value;


    // Player Pref Value - Stores High Score
    [SerializeField]
    float HighScore = 0;
    // Player Pref Value - Jump straight back to game when value = 1
    int RetryGame = 0;

    public bool InShopMenu = false;
    public GameObject whatMenu;

    // boolean that checks if we are in game or not
    public bool startGame = false;
    // The Shop Manager script 
    private ShopManager SM;
    // Particle Effects
    private GameObject Explosion_ParticleEffect;
    private GameObject Confetti_ParticleEffect;
    // In Game Timer (How long we have survived)
    private Text UITimer;
    // The Text that shows the player their new highscore 
    private Text HighScoreUI;
    // The in game money text
    private Text moneyDisplay;
    // The Money Text in the store
    private Text StoreMoneyDisplayText;

    // points that feed into obsticle controller for we use these values when we upgrade new amount of money intake
    public int MeteorPoints = 20;
    public int StarPoints = 40;
    // The Sprite in our assets that shows players that the sound is muted
    Sprite SoundOff;
    // The Sprite in our assets that shows players that the sound is on
    Sprite SoundOn;
    // boolean that monitors the current situation of mute or unmuted
    private bool isMuted = false;

    // Fade between UI and Game
    #region Fade Effect Variables
    public float SpeedScale = 1f;
    public Color fadeColor = Color.black;
    // Rather than Lerp or Slerp, We allow adaptability with a configuration curve
    public AnimationCurve Curve = new AnimationCurve(new Keyframe(0, 1),
        new Keyframe(0.5f, 0.5f, -1.5f, -1.5f), new Keyframe(1, 0));
    public bool StartFadeOut = false;

    public bool FadeEffectHere = false;


    private float alpha = 0f;
    private Texture2D texture;
    private int direction = 0;
    private float time = 0f;

    public bool TransitioningOver = true;

    public float sceneTime;
    private float startScenetime;

    private AudioSource inGameAS;
    private GameObject ExplosionSoundPrefab;

    private Text mainMenuHighScore;
    #endregion

    private void Awake()
    {
        savedTextTime = PlayerPrefs.GetString("MainMenuHighScore");
        FadeEffectHere = true;

        startGame = false;
        TransitioningOver = false;

        // Set up Fade values 
        OnStartFadeEffect();


        startTime = Time.time;

        startScenetime = Time.time;

        // Find UI
        // Find the Text for the timer in game
        UITimer = GameObject.Find("InGameTimer").GetComponent<Text>();
        UITimer.text = "";
        // Obtain UI component for the Money feedback Text
        moneyDisplay = GameObject.Find("MoneyValue").GetComponent<Text>();

        StoreMoneyDisplayText = GameObject.Find("Shop").transform.GetChild(0).GetChild(3).GetChild(1).GetComponentInChildren<Text>();

        //
        HighScoreUI = GameObject.Find("HighScore").GetComponent<Text>();
        HighScoreUI.text = "";

        SM = GameObject.Find("Shop Manager").GetComponent<ShopManager>();

        // set the high score up
        HighScore = PlayerPrefs.GetFloat("Alivetime");
        RetryGame = PlayerPrefs.GetInt("ToGameInstant");

        #region UI
        //Time.timeScale = 0;
        GameOver_Menu = GameObject.Find("Quit_Canvas");
        GameOver_Menu.SetActive(false);

        mainMenu = GameObject.Find("MainMenu_Canvas");
        mainMenuHighScore = mainMenu.transform.GetChild(1).GetChild(8).GetComponent<Text>();
        foreach (Button b in mainMenu.GetComponentsInChildren<Button>())
            b.interactable = false;

        if(HighScore == 0)
        {
            mainMenuHighScore.gameObject.SetActive(false);
            GameObject title = GameObject.Find("HighscoreTitleText");
            title.SetActive(false);
        }
        else
        {
            mainMenuHighScore.gameObject.SetActive(true);
            GameObject title = GameObject.Find("HighscoreTitleText");
            title.SetActive(true);
        }

        CelebrationMenu = GameObject.Find("CelebrationCanvas");
        CelebrationMenu.SetActive(false);

        PlayerCanvas = GameObject.Find("PC_Canvas");

        inGameAS = PlayerCanvas.GetComponent<AudioSource>();

        Explosion_ParticleEffect = Resources.Load<GameObject>("Prefabs/Idea #2/Particle Effects/Environment/Explosion");

        Confetti_ParticleEffect = Resources.Load<GameObject>("Prefabs/Idea #2/Particle Effects/Confetti");

        ExplosionSoundPrefab = Resources.Load<GameObject>("Audio/Spawnables/ExplosionSound");


        if(RetryGame == 1)
        {
            mainMenu.SetActive(false);
            TransitioningOver = true;
            startGame = true;
            inGameAS.Play();
        }
        else
        {
            mainMenu.SetActive(true);
        }
        #endregion
    }

    // Start is called before the first frame update
    void Start()
    {
        // Find scripts
        playerController = GameObject.Find("Space Cannon").GetComponent<SpaceCannon_Controller>();
        // Find Objects
        ForceFieldCollisionDetect = GameObject.Find("ForceField_CollisionDetection");
        ForceField = GameObject.Find("ForceField");
        _ForceField_Shader = ForceField.GetComponent<MeshRenderer>().material;
        // Find in game planet
        planet = GameObject.Find("Planet");
        // check to see if we are muting the audio or unmuting 
        isMuted = PlayerPrefs.GetInt("Muted") == 1;
        // mutes session with boolean value
        AudioListener.pause = isMuted;
        // money save data returned
        starDust = PlayerPrefs.GetInt("Money");
        // Find sprites 
        SoundOff = Resources.Load<Sprite>("2D_Sprites/UI/NoSound");
        SoundOn = Resources.Load<Sprite>("2D_Sprites/UI/Sound");
        // Sprites changed to correct option (Muted - 1)
        if (isMuted = PlayerPrefs.GetInt("Muted") == 1)
        {
            // Make both buttons muted 
            mainMenu.transform.GetChild(1).transform.GetChild(6).GetComponent<Image>().sprite = SoundOff;
            GameOver_Menu.transform.GetChild(1).transform.GetChild(5).GetComponent<Image>().sprite = SoundOff;
            PlayerCanvas.transform.GetChild(5).transform.GetChild(3).GetComponent<Image>().sprite = SoundOff;
        }
        else
        {
            // Make both buttons muted 
            mainMenu.transform.GetChild(1).transform.GetChild(6).GetComponent<Image>().sprite = SoundOn;
            GameOver_Menu.transform.GetChild(1).transform.GetChild(5).GetComponent<Image>().sprite = SoundOn;
            PlayerCanvas.transform.GetChild(5).transform.GetChild(3).GetComponent<Image>().sprite = SoundOn;
        }    // (UnMuted - 0)

        shop_Menu = GameObject.Find("Shop");
        shop_Menu.SetActive(false);
    }

[expand title=”Runtime Function”]

  • During the Update() function within runtime, a lot of conditions are asked to make sure that everything is still the way it should be. Like if there is a player controller in the scene
  • Update() also changes the Text components for in-game money or the in-game timer.
  • Update() Monitors when the game is over which will be when the player planet gets hit by an obstacle. It Updates the InGameTimer() and also monitors UI buttons and the fade effect

[/expand]

// Update is called once per frame
    void Update()
    {
        mainMenuHighScore.text = savedTextTime;
        if (playerController == null)
        {
            playerController = GameObject.Find("Space Cannon").GetComponent<SpaceCannon_Controller>();
            playerController.currentAmmo = playerController.maxAmmo;
        }
        else
        {
            if(GameOver_Menu.activeInHierarchy == true)
            {
                playerController.gameObject.SetActive(false);
            }
        }

        // Update value     // save this value later 
        money = starDust;
        // check if the game is done?
        GameOver();
        InGameTimer();

        // show money obtained
        moneyDisplay.text = money.ToString();
        // Show instore money
        StoreMoneyDisplayText.text = money.ToString();

        PlayerPrefs.SetInt("Money", money);

        // get start of scene time 
        sceneTime = Time.realtimeSinceStartup - startScenetime;
        #region Fade Effect Button Monitor
        if (alpha == 0)
        {
            if(mainMenu.activeInHierarchy == true && GameOver_Menu.activeInHierarchy == false && InShopMenu == false)
            {
                foreach (Button b in mainMenu.GetComponentsInChildren<Button>())
                    b.interactable = true;
            }
            else if (InShopMenu == true)
            {
                foreach (Button b in mainMenu.GetComponentsInChildren<Button>())
                    b.interactable = false;
            }
            else if(GameOver_Menu.activeInHierarchy == true && mainMenu.activeInHierarchy == false && InShopMenu == false)
            {
                foreach (Button b in GameOver_Menu.GetComponentsInChildren<Button>())
                    b.interactable = true;
            }
            else if (GameOver_Menu.activeInHierarchy == true && mainMenu.activeInHierarchy == false && InShopMenu == true)
            {
                foreach (Button b in GameOver_Menu.GetComponentsInChildren<Button>())
                    b.interactable = false;
            }
        }
        else
        {
            if (mainMenu.activeInHierarchy == true && GameOver_Menu.activeInHierarchy == false && InShopMenu == false)
            {
                foreach (Button b in mainMenu.GetComponentsInChildren<Button>())
                    b.interactable = false;
            }
            else if (InShopMenu == true)
            {
                foreach (Button b in mainMenu.GetComponentsInChildren<Button>())
                    b.interactable = false;
            }
            else if (GameOver_Menu.activeInHierarchy == true && mainMenu.activeInHierarchy == false && InShopMenu == false)
            {
                foreach (Button b in GameOver_Menu.GetComponentsInChildren<Button>())
                    b.interactable = false;
            }
            else if (GameOver_Menu.activeInHierarchy == true && mainMenu.activeInHierarchy == false && InShopMenu == true)
            {
                foreach (Button b in GameOver_Menu.GetComponentsInChildren<Button>())
                    b.interactable = false;
            }
        }
        #endregion
        if (sceneTime > 0.2)
        {
            UpdateFadeEffect();
        }

        // Obtain the current time 
        CurrentTime = Time.time;

        // Obsticle has been destroyed by the player
        Obsticle_Controller.ObsticleDestroyed += RunCo;
    }

[expand title=”Gameover & Timer Functions”]

  • So the GameOver() function monitors when the game is in fact over and when the game is over there is a p[rocess of executed code that allows for a shader dissolve effect to play, an explosion to spawn and the correct menus to display depending on if the player beat their high score or if they did not. 
  • The InGameTimer() function structures the formatted string of how the timer looks via UI text. We measure time within minutes, seconds and milliseconds. However, milliseconds never get used. but the time that gets formated is shown on screen or in the celebration menu. 
  • This function gets thrown into the Update() function so we can always have the correct time every frame. 

[/expand]

// First colour lerp of shader force field
    float Lerp = 0;
    // second colour shader lerp
    float SecondLerp = 0;
    // check when lerping so we can stop when required
    bool LerpOneOver = false;
    bool LerpTwoOver = false;
    // remove celebration menu when true 
    public bool stopCelebrating = false;
    private event System.Action endGameExplosion = delegate { };
    void GameOver()
    {
        // when the game is over
        if (Game_Over)
        {
            PlayerCanvas.transform.GetChild(4).GetComponent<Button>().interactable = false;
            startGame = false;
            // when the effect is about to happen
            if (dissolving)
            {
                // Spawns explosion Sound effect
                if (GameObject.Find("ExplosionSound") == null)
                {
                    GameObject ExplosionSound = Instantiate(ExplosionSoundPrefab, Vector3.zero, Quaternion.identity) as GameObject;
                    ExplosionSound.name = "ExplosionSound";
                }
               
                // Obtain spawner in the scene
                GameObject spawner = GameObject.Find("Obsticle_Spawner");
                // loop through a list of hazzards and destroy them
                foreach (GameObject go in spawner.GetComponent<Obsticle_Spawner>().allSpawner)
                {
                    // Destroy all the hazards
                    // Later i can replace this with the function that holds all the cool effects
                    Destroy(go);
                }
                // clear the list of hazzards 
                spawner.GetComponent<Obsticle_Spawner>().allSpawner.Clear();
                // turn off the script
                spawner.GetComponent<Obsticle_Spawner>().enabled = false;
                // make a material reference and apply the relevent material
                Material MatFade = planet.GetComponent<SpriteRenderer>().material;
                // turn off the player controller
                playerController.enabled = false;
                // declide the fade value 
                fade -= Time.deltaTime / 6;
                // when value is less or equal than 0
                if(fade <= 0f)
                {
                    // make sure effect value stays at 0
                    fade = 0f;
                    // revert boolean o the fade does not run any longer
                    dissolving = false;
                }
                if(fade < 0.5f)
                {
                    // if there is no explosion
                    if(GameObject.Find("Explosion") == null)
                    {
                        // Spawn explosion
                        GameObject explosion = Instantiate(Explosion_ParticleEffect, Vector3.zero, Quaternion.identity)as GameObject;
                        explosion.name = "Explosion";   // Name explosion
                    }
                    // Obtain Objects
                    GameObject Body = GameObject.Find("Cannon Body");
                    Body.transform.parent = null;
                    GameObject Barrel = GameObject.Find("Cannon Barrel");
                    Barrel.transform.parent = null;
                    GameObject Base = GameObject.Find("CannonBase");
                    // check to see if we already have a rigibody on each object (Should start without a Rigidbody2D)
                    if(Body.GetComponent<Rigidbody2D>() == null && Barrel.GetComponent<Rigidbody2D>() == null
                        && Base.GetComponent<Rigidbody2D>() == null)
                    {
                        // Add Physics to each Object
                        Body.AddComponent<Rigidbody2D>();
                        Barrel.AddComponent<Rigidbody2D>();
                        Base.AddComponent<Rigidbody2D>();
                        // Make sure gravity is not on 
                        Body.GetComponent<Rigidbody2D>().gravityScale = 0;
                        Barrel.GetComponent<Rigidbody2D>().gravityScale = 0;
                        Base.GetComponent<Rigidbody2D>().gravityScale = 0;
                        // apply force so it moves in space
                        Body.GetComponent<Rigidbody2D>().AddForce(new Vector3(Random.Range(-1f, 1f), Random.Range(-1f, 1), 0) * Time.deltaTime
                        * 1000, ForceMode2D.Impulse);
                        Barrel.GetComponent<Rigidbody2D>().AddForce(new Vector3(Random.Range(-1f, 1f), Random.Range(-1f, 1), 0) * Time.deltaTime
                            * 1000, ForceMode2D.Impulse);
                        Base.GetComponent<Rigidbody2D>().AddForce(new Vector3(Random.Range(-1f, 1f), Random.Range(-1f, 1), 0) * Time.deltaTime
                            * 1000, ForceMode2D.Impulse);
                    }
                    Body.transform.Rotate(Vector3.forward);
                    Barrel.transform.Rotate(Vector3.forward);
                }
                // as the fade value declines our effect is visually shown by the shader
                MatFade.SetFloat("_Fade", fade);
            }
            // when the dissolve effect is done
            if(!dissolving)
            {
                inGameAS.Stop();
                GameObject Body = GameObject.Find("Cannon Body");
                GameObject Barrel = GameObject.Find("Cannon Barrel");
                Body.transform.Rotate(Vector3.forward);
                Barrel.transform.Rotate(Vector3.forward);

                startGame = false;
                // first run
                if (HighScore == 0 && !InShopMenu)
                {
                    // save value here
                    PlayerPrefs.SetFloat("Alivetime", guiTime);
                    PlayerPrefs.SetString("MainMenuHighScore", textTime);
                    GameOver_Menu.SetActive(true);
                }
                else if (guiTime < HighScore && !InShopMenu)   // first run is over so we check for a comparison
                {
                    ForceField.SetActive(false);
                    // Game Over only dont save value : we dont want to save a lower value then the current value 
                    // Turn on UI 
                    GameOver_Menu.SetActive(true);
                    CelebrationMenu.SetActive(false);
                }
                else if(guiTime > HighScore)
                {
                    // The current time is greater than the stored one 
                    // save value here
                    PlayerPrefs.SetFloat("Alivetime", guiTime);
                    PlayerPrefs.SetString("MainMenuHighScore", textTime);
                    // ##
                    Debug.Log("YAY, New Hight Score");
                    // turn on canvas to celebrate
                    CelebrationMenu.SetActive(true);

                    // so if we are still n the celebration menu
                    if(!stopCelebrating && GameOver_Menu.activeInHierarchy != true)
                    {
                        // stuff here

                        // Spawn confetti 
                        if(GameObject.Find("Confetti") == null)
                        {
                            // spawn confetti 
                            GameObject Confetti = Instantiate(Confetti_ParticleEffect);
                            // rename confetti
                            Confetti.name = "Confetti";
                        }
                        // whatever else here
                    }
                    else
                    {
                        ForceField.SetActive(false);
                        CelebrationMenu.SetActive(false);
                        if (InShopMenu)
                            GameOver_Menu.SetActive(false);
                        else
                        {
                            GameOver_Menu.SetActive(true);
                        }
                        stopCelebrating = true;
                        // Destroy confetti
                        Destroy(GameObject.Find("Confetti"));
                        return;
                    }
                }
            }
        }
        else
        {
            if(DomeHealth == 2)
            {
                // change the thickness here 
                _ForceField_Shader.SetFloat("_EdgeThickness", 0.4f);
                if(!LerpOneOver)
                {
                    Lerp += Time.deltaTime / 2;
                    if (Lerp >= 1)
                    {
                        Lerp = 1;
                        LerpOneOver = true;
                    }
                    _ForceField_Shader.SetFloat("_Lerp", Lerp);
                }
            }
            else if(DomeHealth == 1)
            {
                // Make sure shader does not get confused during lerp - if hit twice at the same time
                Lerp = 1;
                // change the thickness here 
                _ForceField_Shader.SetFloat("_ColourLerpControl", 0);
                _ForceField_Shader.SetFloat("_EdgeThickness", 0.7f);
                if (!LerpTwoOver && LerpOneOver == true)
                {
                    // Use keyword to enable the last phase
                    _ForceField_Shader.EnableKeyword("_LASTHIT_ON");
                    SecondLerp += Time.deltaTime / 2;
                    if (SecondLerp >= 1)
                    {
                        SecondLerp = 1;
                        LerpTwoOver = true;
                    }
                    _ForceField_Shader.SetFloat("_Lerp", SecondLerp);
                }
            }
            #region Death Check
            // when dead
            if (DomeHealth <= 0)
            {
                ForceFieldCollisionDetect.SetActive(false);    // turn off 
                //ForceField.SetActive(false);
                _ForceField_Shader.SetInt("_BoolController", 1);
                crossSectionValue += Time.deltaTime * 10;
                _ForceField_Shader.SetFloat("_CrossSection", crossSectionValue);
            }

                                                    // when dead
            if (PlanetHealth <= 0)  //<- check and see if the player has beasted themselves in high score
            {
                Game_Over = true;   // game is over
                dissolving = true;
            }

            #endregion
        }
    }
private void InGameTimer()
    {
        // if the game has started or is not over
        if(!dissolving && startGame && mainMenu.activeInHierarchy == false)
        {
            // We input the current time into thhe time we display
            // We also subtract the current time of the else condition so time does not fail to be correct
            guiTime = CurrentTime - startTime - value;
            // Show highscore text in here - Celebration menu does not show until game is over
            HighScoreUI.text = textTime;
            value = 0;
            realTime = 0;
        }
        else
        {
            if(guiTime == 0)
            {
                // Keep this value until we start the game or finish the game
                // If not time is unaccurate
                startTime = Time.time;
            }
            else
            {
                if(realTime == 0)
                {
                    realTime = CurrentTime;
                }
                value = Mathf.Abs(CurrentTime - realTime);
            }

        }

        // Time ratios
        int minuets = (int)guiTime / 60;
        int seconds = (int)guiTime % 60;
        int fraction = (int)(guiTime * 100) % 100;
        // Format string so it knows what to display with the values applied
        // Shows miliseconds, seconds and minuets - makes UI look weird
        //textTime = string.Format("{0:00}: {1:00}: {2:000}", minuets, seconds, fraction);

        // Timer which shows minuets and seconds
        textTime = string.Format("{0:00}: {1:00}", minuets, seconds);
        // display Format string onto in game UI
        UITimer.text = textTime;

    }

[expand title=”GUI Fade Effect”]

  • OnStartFadeEffect() starts the fade effect in the GUI so when this function is called it fades the screen
  • UpdateFadeEffect() helps the fade understand if we are fading in or out at this current moment. Without this function, the GUI fade could just black screen on the player
  • UpdateFadeEffect() also, helps with monitoring where the player is in the game; which then allows us to determine when we fade. For Example: if we press start when in the main menu section we fade into the game.
  • OnGUI() is a unity function but draws the texture over the screen so it fades for us and uses the data we set in the other two functions.

[/expand]

    #region Fade Effect 
    void OnStartFadeEffect()
    {
        if (StartFadeOut) alpha = 1f; else alpha = 0f;
        texture = new Texture2D(1, 1);
        texture.SetPixel(0, 0, new Color(fadeColor.r, fadeColor.g, fadeColor.b, alpha));
        texture.Apply();
    }

    void UpdateFadeEffect()
    {
        if(direction == 0 && FadeEffectHere == true)
        {
            if (alpha >= 1f)    // Fully faded out
            {
                alpha = 1f;
                time = 0f;
                direction = 1;
                FadeEffectHere = false;

                // if the main menu is active and the transition is done
                if (mainMenu.activeInHierarchy == true && !InShopMenu)
                {

                    // when the real time in the new or current scene is less than 5 we rturn
                    if (sceneTime <= 5)    // (5 is just a value that can be reached fast without conflictions)
                    {
                        // return before condition is met
                        return;
                    }
                    else    // we are over 5 and can play 
                    {
                        // the transition is over 
                        TransitioningOver = true;
                        // we are in the game
                        startGame = true;
                        inGameAS.Play();
                    }

                    // check that its done 
                    if (TransitioningOver)
                    {
                        // turn off UI
                        mainMenu.SetActive(false);
                    }
                }
                
                if(InShopMenu)
                {
                    if (sceneTime <= 5)
                        return;
                    else
                        TransitioningOver = true;
                    if(TransitioningOver)
                    {
                        whatMenu.SetActive(false);
                        shop_Menu.SetActive(true);
                    }
                }
            }
            else    // Fully faded in
            {
                alpha = 0f;
                time = 1f;
                direction = -1;
            }
        }
    }

    public void OnGUI()
    {
        if (alpha > 0f) GUI.DrawTexture(new Rect(0, 0, Screen.width, Screen.height), texture);
        if(direction != 0)
        {
            time += direction * Time.deltaTime * SpeedScale;
            alpha = Curve.Evaluate(time);
            texture.SetPixel(0, 0, new Color(fadeColor.r, fadeColor.g, fadeColor.b, alpha));
            texture.Apply();
            if (alpha <= 0 || alpha >= 1f) direction = 0;
        }
    }
    #endregion

[expand title=”Button Click Functions”]

  • Most of these functions are used to apply logic to the button clicks in the UI menus.
  • StartGame() turns all of the start menu buttons off and starts a fade effect previously mentioned.
  • QuitGame() is only used to exit the game when players want to.
  • MainMenu() takes players back to the main menu as in the GameOver UI players have a choice to try again and they jump straight back into gameplay.
  • GamePaused() pauses the current runtime of the game 
  • The Shop functions simply control booleans and UI menus to make sure the entire game knows players are heading to the shop and turning on the UI shop menu. 
  • MuteGame() is applied to mute buttons through the game menus which then turn all audio off if players desire to have no sounds. 
  • Lastly, ToQuitMenu() simply takes the player to the quit menu if they are in the celebration menu. 

[/expand]

 #region OnClick Functions
    public void StartGame()
    {
        FadeEffectHere = true;
        // turn off activity of buttons 
        Button[] B =  mainMenu.transform.GetChild(1).GetComponentsInChildren<Button>();
        for (int i = 0; i <= B.Length - 1; i++)
            B[i].interactable = false;
    }

    public void Retry()
    {
        RetryGame = 1;
        PlayerPrefs.SetInt("ToGameInstant", RetryGame);
        // reset game here
        ForceFieldCollisionDetect.SetActive(true);
        ForceField.SetActive(true);
        DomeHealth = 3;
        PlanetHealth = 1;
        Application.LoadLevel(0);
    }

    public void QuitGame()
    {
        RetryGame = 0;
        PlayerPrefs.SetInt("ToGameInstant", RetryGame);
        Application.Quit();
    }

    public void MainMenu()
    {
        FadeEffectHere = true;
        TransitioningOver = false;
        RetryGame = 0;
        PlayerPrefs.SetInt("ToGameInstant", RetryGame);
        ForceFieldCollisionDetect.SetActive(true);
        ForceField.SetActive(true);
        DomeHealth = 3;
        PlanetHealth = 1;
        Application.LoadLevel(0);

    }

    public void GamePaused(bool Paused)
    {
        startGame = Paused;
    }


    #region Shop Menu Balancer
    public void ShopBoolean(bool inShop)
    {
        InShopMenu = inShop;
        TransitioningOver = false;
    }

    public void ShopFade(bool DoWeFade)
    {
        FadeEffectHere = DoWeFade;
    }
    // Reset shop in here too 
    public void ToShop()
    {
        // controls wave animation of the robot
        SM.waveController = 0;
        // then the data will do itself 

        PlayerCanvas.GetComponent<Canvas>().enabled = false;

        if (GameOver_Menu.activeInHierarchy == true && mainMenu.activeInHierarchy != true)
        {
            whatMenu = GameOver_Menu;
            Button[] Bttn = GameOver_Menu.transform.GetChild(1).GetComponentsInChildren<Button>();
            for (int i = 0; i <= Bttn.Length - 1; i++)
                Bttn[i].interactable = false;
            if (!InShopMenu)
            {
                for (int i = 0; i <= Bttn.Length - 1; i++)
                    Bttn[i].interactable = true;

                whatMenu.SetActive(true);
                PlayerCanvas.GetComponent<Canvas>().enabled = true;
                shop_Menu.SetActive(false);
            }
        }
        else if(mainMenu.activeInHierarchy == true && GameOver_Menu.activeInHierarchy != true)
        {
            whatMenu = mainMenu;
            Button[] Bttn = mainMenu.transform.GetChild(1).GetComponentsInChildren<Button>();
            for (int i = 0; i <= Bttn.Length - 1; i++)
                Bttn[i].interactable = false;
            if (!InShopMenu)
            {
                whatMenu.SetActive(true);
                PlayerCanvas.GetComponent<Canvas>().enabled = true;
                shop_Menu.SetActive(false);
            }
        }
        else
        {
            Button[] BT = whatMenu.transform.GetChild(1).GetComponentsInChildren<Button>();
            foreach (Button bt in BT)
                bt.interactable = true;

            shop_Menu.SetActive(false);
            PlayerCanvas.GetComponent<Canvas>().enabled = true;
            whatMenu.SetActive(true);
        }
    }
    #endregion

    public void MuteGame()
    {
        // Find the sprites in the asset folders 
        Sprite SoundOff = Resources.Load<Sprite>("2D_Sprites/UI/NoSound");
        Sprite SoundOn = Resources.Load<Sprite>("2D_Sprites/UI/Sound");
        // The Sound Button indicator
        Image Icon;

        // boolean alteration for each menu (Start menu, quit menu and pause menu)
        if (mainMenu.activeInHierarchy == true)
        {
            // Icon button on this menu found
            Icon = mainMenu.transform.GetChild(1).transform.GetChild(6).GetComponent<Image>();
            // When the icon sprite is of sound off 
            if(Icon.sprite == SoundOff)
            {
                // We tick this boolean so we can unmute the game 
                isMuted = false;
            }
            else
            {
                // If not the right icon sprite then we tick true which Mutes the game
                isMuted = true;
            }
        }
        else if(GameOver_Menu.activeInHierarchy == true)
        {
            // Find the correlated menus button image component so we correlate muted or unmuted 
            Icon = GameOver_Menu.transform.GetChild(1).transform.GetChild(5).GetComponent<Image>();
            // check to change boolean
            if (Icon.sprite == SoundOff)
            {
                isMuted = false;
            }
            else
            {
                isMuted = true;
            }
        }
        else if(PlayerCanvas.activeInHierarchy == true)
        {
            // Pause Menu Button image Component
            Icon = PlayerCanvas.transform.GetChild(5).transform.GetChild(3).GetComponent<Image>();
            // Icon sprite check
            if (Icon.sprite == SoundOff)
            {
                isMuted = false;
            }
            else
            {
                isMuted = true;
            }
        }
        // When the boolean is true
        if (isMuted)
        {
            // Change Icon for each button to muted sprite 
            mainMenu.transform.GetChild(1).transform.GetChild(6).GetComponent<Image>().sprite = SoundOff;
            GameOver_Menu.transform.GetChild(1).transform.GetChild(5).GetComponent<Image>().sprite = SoundOff;
            PlayerCanvas.transform.GetChild(5).transform.GetChild(3).GetComponent<Image>().sprite = SoundOff;
            AudioListener.volume = 0;
            // Pause all the audio sources
            AudioListener.pause = isMuted;
        }
        else
        {
            AudioListener.volume = 1;
            // unmute the game
            AudioListener.pause = isMuted;
            // change the sprite
            mainMenu.transform.GetChild(1).transform.GetChild(6).GetComponent<Image>().sprite = SoundOn;
            GameOver_Menu.transform.GetChild(1).transform.GetChild(5).GetComponent<Image>().sprite = SoundOn;
            PlayerCanvas.transform.GetChild(5).transform.GetChild(3).GetComponent<Image>().sprite = SoundOn;
        }
        // save settings data - On the next play session we know if the game is muted or unmuted
        PlayerPrefs.SetInt("Muted", isMuted ? 1 : 0);
    }
    public void ToQuitMenu(bool Quit)
    {
        stopCelebrating = Quit;
    }

[expand title=”Pulse Text Function”]

  • The TextPulse() increases the scale of a UI text element in which it gets slightly bigger then goes back to normal size
  • We can use it to make text bigger or smaller. I used it to make text bigger when collecting money and making the text smaller when spending it.
  • The function RunCo() is simply used through a public Action variable in which instead of calling the function we can set the action to the function and then the code runs depending on the condition being met.

[/expand]

 IEnumerator TextPulse(bool increase)
    {
        if(increase)
        {
            for (float i = 1f; i < 1.2f; i += 0.005f)
            {
                moneyDisplay.rectTransform.localScale = new Vector3(i, i, i);
                yield return new WaitForEndOfFrame();
            }
            moneyDisplay.rectTransform.localScale = new Vector3(1.2f, 1.2f, 1.2f);

            for (float i = 1.2f; i >= 1f; i -= 0.05f)
            {
                moneyDisplay.rectTransform.localScale = new Vector3(i, i, i);
                yield return new WaitForEndOfFrame();
            }
            moneyDisplay.rectTransform.localScale = new Vector3(1f, 1f, 1f);
        }
        else
        {
            for (float i = 1f; i > 0.8f; i -= 0.005f)
            {
                StoreMoneyDisplayText.rectTransform.localScale = new Vector3(i, i, i);
                yield return new WaitForEndOfFrame();
            }
            StoreMoneyDisplayText.rectTransform.localScale = new Vector3(0.8f, 0.8f, 0.8f);

            for (float i = 0.8f; i <= 1f; i += 0.05f)
            {
                StoreMoneyDisplayText.rectTransform.localScale = new Vector3(i, i, i);
                yield return new WaitForEndOfFrame();
            }
            StoreMoneyDisplayText.rectTransform.localScale = new Vector3(1f, 1f, 1f);
        }
    }

    public void RunCo()
    {
        // change pulse effect logic depending on current position of UI 
        if(shop_Menu.activeInHierarchy != true)
        {
            // increase scale
            // Money Made
            StartCoroutine(TextPulse(true));
        }
        else
        {
            // smaller scale
            // Purchase Made
            StartCoroutine(TextPulse(false));
        }
    }

Shop Manager

[expand title=”Pulse Text Function”]

  • The Shop manager holds functions that allow for different functionality throughout 
  • It monitors purchases the players make, it also monitors what planets and upgrades the player have obtained
  • It manages animations for the robot vendor and also it manages screen transitions so there is a smooth lerp between shop menus. There are two shop menus, the main shop & the window room. Main shop is where you buy upgrades for your cannon, window room is for buying new planets.
  • There are also effects like planet scrolling which is FillIn() & FillOut(), there is also a fade effect for the information panel.
  • The information panel tells players about each shop upgrade for their cannon. 
  • There are functions in which handles players buying upgrades and also planets. Disabling buttons if the money the player has isnt enough.

[/expand]

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class ShopManager : MonoBehaviour
{
    [Range(0, 0.1f)]
    // The speed we lerp by
    [Tooltip("The Speed In Which we lerp from the main shop room and the window room")]
    public float speed = 1f;
    // are we currently lerping?
    bool lerp = false;
    // check if player wants to go to window menu or main store
    bool buttonPressed = false;
    // The target of the Lerp positions
    Vector3 target;
    // Next button to see the next planet
    private Button nextButton;
    // Button to check previous planet in the array
    private Button PreviousButtons;
    // The main Planet image we are changing through an array
    [Tooltip("The UI image that we are manipulating to change the current planet on screen")]
    public Image PlanetImage;
    // Array of all the planets the player can buy
    [Tooltip("The Array of sprites that will be for our planet purchasing")]
    public Sprite[] Planets;
    // The scroller for the planet array
    [SerializeField]
    public int planetScroller;
    // Game Manager variable
    Game_Manager GM;

    // Are we going to let planets fade out or in
    bool FadeIn = false;
    bool FadeOut = false;



    // Arrow Animations
    private bool MainShopMenu = true;
    // Animations for the UI buttons
    Animator ShopRoomSmallArrow, WindowRoomSmallArrow;
    // Animators for the robot Vendor
    Animator rightArmAnimator, leftArmAnimator, RobotEyes;
    AudioSource RobotAS;
    AudioClip greet, celebrate;

    [Range(0, 8)]
    // values that we choose an animation from
    public int eyeDirection;    // 0 is idle, 8 and onward is an animation
    // Time that changes the eye direction
    private float timeUntilNextAnim;


    private bool PurchasedMade = false;

    #region Information Panel Variables
    [Header("Information Menu")]
    [TextArea(1, 4)]
    [Tooltip("A Brief Description of each upgrade")]
    public string[] Descriptions;
    [Tooltip("The Upgrade Strings that change the Updrage In Question Text Component")]
    public string[] UpgradeOrder;
    [Tooltip("The Integar scroller that scrolls through the arrays")]
    private int scroller = 0;
    [Tooltip("The Upgrade Type Text Component that shows the player what upgrade they are readding about")]
    private Text UpradeInQuestion;
    [Tooltip("The Text Component that tells that player about the current shown Upgrade in the information panel")]
    private Text DescriptionText;
    [Tooltip("The Logos of the Upgrades so there can be image feedback")]
    public Sprite[] Logos;
    private Image TypeImage;

    // The Information panel parent
    private GameObject parentPanelInfo;
    #endregion

    #region Buy Planet Controller Variables
    [Header("Planet Buying Variables")]
    // The Planets we equiped to the in game planet
    [Tooltip("The planet sprites that are going to be applied to the in game Planet")]
    public Sprite[] EquipPlanets;
    // The cannons we spawn in when players buy a new planet
    [Tooltip("The different Prefab cannons that we spawn in when buying a new cannon")]
    public GameObject[] CannonPrefabs;
    // the current Cannon prefab in the game
    private GameObject CurrentCannon;
    // the array of booleans to save so we know what planets the player owns 
    public bool[] OwnedPlanets;
    // The different prices of the planets 
    [Tooltip("The prices we give each planet to buy")]
    public PlanetPrices[] PlanetPrices_;
    // The Text component that shows the price of the planets 
    public Text PlanetPriceText;
    // the saved value of what planet we have active
    private int planetActive = 0;
    // Button that we use to buy planets
    Button BuyButton;
    // The sprite renderer of the planet in the game on start
    private SpriteRenderer RunTimePlanet;
    #endregion

    #region Fade Controllers Variables
    [Tooltip("Boolean which allows the UI information Menu to Fade Out so it is not seen" +
        "Fade In Boolean Must be false for this to happen too")]
    bool FadeOutPanel = false;
    [Tooltip("Boolean in which allows the information panel to fade in")]
    bool FadeInPanel = false;
    [Tooltip("Color Variable that changes the Alpha channel of the Text components on the Information Panel" +
        "When it fades out the Alppa subtracts, the alpha increase when the Panel is fadding back in ")]
    Color tempText;
    [Tooltip("Color that will change the alpha channel of the Logo UI image changer")]
    Color tempImage;
    [Tooltip("A Boolean to monitor which button of direction was pressed. This helps with adding and subvtracting the array scroller Integar")]
    bool ClickedLeftInfoButton;
    [Tooltip("A Boolean to detect if the player clicked the Right Button of the information panel to add 1 to the integar scroller")]
    bool ClickedRightInfoButton;
    #endregion

    [Header("Cannon Upgrade Variables")]
    // Upgrade Manager Class
    public List<PriceChange> PriceManager = new List<PriceChange>();
    // Space Cannon script (Player Controller)
    private SpaceCannon_Controller PlayerController;
    // an event to allow for pulse text effects 
    public static event System.Action Purchase = delegate { };
    private AudioSource buyButton_AS;
    private AudioClip buyPlanet, defaultClick;

    private void Awake()
    {
        Debug.Log("First Line Of Awake Function Runs");

        // find current cannon in the scene just in case it needs to be changed later 
        CurrentCannon = GameObject.Find("Space Cannon");

        // Find the planet Object in the game
        RunTimePlanet = GameObject.Find("Planet").GetComponent<SpriteRenderer>();
        // Saved int so we know what planet we apply to the Planet GameObject
        planetActive = PlayerPrefs.GetInt("CurrentPlanet", planetActive);
        // Apply correct sprite player chose before they quite
        RunTimePlanet.sprite = EquipPlanets[planetActive];

        // External file to save Array data as left for the Purchased Planets
        OwnedPlanets = PlayerPrefsX.GetBoolArray("SavedPlanets", false, 7);
        // Make sure this boolean is always true as its the default planet
        OwnedPlanets[0] = true;

        // Spawn cannon on First frame
        GameObject NewCannon = Instantiate(CannonPrefabs[planetActive], Vector3.zero, Quaternion.identity) as GameObject; NewCannon.name = "Space Cannon";
        Destroy(CurrentCannon);
        CurrentCannon = NewCannon;

        // Find the Next Button and Previous Button for the planet selection
        nextButton = GameObject.Find("Next_Button").GetComponent<Button>();
        PreviousButtons = GameObject.Find("Previous_Button").GetComponent<Button>();
        // Make sure previous button cannot be pressed as we cant go negative into the array
        PreviousButtons.interactable = false;
        // Find the Image we are going to change while scrolling thrrough the array of planets
        PlanetImage = GameObject.Find("DisplayPlanet").GetComponent<Image>();
        // Find the Game Manager scrip
        GM = GameObject.Find("GameManager").GetComponent<Game_Manager>();

        // Find the Arrow Button Animators
        ShopRoomSmallArrow = GameObject.Find("ShopRoomSmallArrow").GetComponent<Animator>();
        WindowRoomSmallArrow = GameObject.Find("WindowRoomSmallArrow").GetComponent<Animator>();

        // Obtain the Text component to change the Value of the Planets Text
        PlanetPriceText = GameObject.Find("PriceText").GetComponent<Text>();

        RobotAS = GameObject.Find("Robot Body").GetComponent<AudioSource>();
        greet = Resources.Load<AudioClip>("Audio/UI/Store/Robot Greeting");
        celebrate = Resources.Load<AudioClip>("Audio/UI/Store/Robot Celebration");

        // Robot Animators
        rightArmAnimator = GameObject.Find("RightArm").GetComponent<Animator>();
        leftArmAnimator = GameObject.Find("LeftArm").GetComponent<Animator>();
        RobotEyes = GameObject.Find("Eyes").GetComponent<Animator>();

        // Find the Panel Text
        UpradeInQuestion = GameObject.Find("UpgradeTypeText").GetComponent<Text>();
        DescriptionText = GameObject.Find("DescriptionText").GetComponent<Text>();
        DescriptionText.text = Descriptions[0];

        TypeImage = GameObject.Find("UpgradeTypeImage").GetComponent<Image>();
        TypeImage.sprite = Logos[0];

        BuyButton = GameObject.Find("BuyButton").GetComponent<Button>();

        buyButton_AS = BuyButton.GetComponent<AudioSource>();

        // Make local variable to obtain reference and turn it off when done finding components (The panel does not need to be on when the I button has not been pressed)
        parentPanelInfo = GameObject.Find("InformationPanel");


        // Find Nested List Text and Buttons
        PriceManager[0].UpgradeButton = GameObject.Find("AmmoCapacityButton").GetComponent<Button>();
        PriceManager[0].PriceToChange = PriceManager[0].UpgradeButton.transform.GetChild(0).GetComponent<Text>();

        PriceManager[1].UpgradeButton = GameObject.Find("ReloadSpeedButton").GetComponent<Button>();
        PriceManager[1].PriceToChange = PriceManager[1].UpgradeButton.transform.GetChild(0).GetComponent<Text>();

        PriceManager[2].UpgradeButton = GameObject.Find("FireRateButton").GetComponent<Button>();
        PriceManager[2].PriceToChange = PriceManager[2].UpgradeButton.transform.GetChild(0).GetComponent<Text>();

        PriceManager[3].UpgradeButton = GameObject.Find("MoneyIncreaseButton").GetComponent<Button>();
        PriceManager[3].PriceToChange = PriceManager[3].UpgradeButton.transform.GetChild(0).GetComponent<Text>();



        parentPanelInfo.SetActive(false);

        Debugging();

        PlayerController = GameObject.Find("Space Cannon").GetComponent<SpaceCannon_Controller>();

        Debug.Log("Last Line Of Awake Function Runs");

        buyPlanet = Resources.Load<AudioClip>("Audio/UI/Store/Buy Planet Sound");
        defaultClick = Resources.Load<AudioClip>("Audio/UI/Button Click 2");
    }

    // Start is called before the first frame update
    void Start()
    {
        // on complete make sure this resets 
        #region Save data from the tiers of cannon upgrades
        // Pass through the saved Value of these int keys 
        PriceManager[0].CurrentTier = PlayerPrefs.GetInt("AmmoSave");
        PriceManager[1].CurrentTier = PlayerPrefs.GetInt("ReloadSave");
        PriceManager[2].CurrentTier = PlayerPrefs.GetInt("FireSave");
        PriceManager[3].CurrentTier = PlayerPrefs.GetInt("MoneySave");
        #endregion
        // needs to happen here after we get the saved data values
        // change the text so it shows what tier we are on from the last playtime
        UpradeInQuestion.text = UpgradeOrder[0] + "          " + "\n" + "Tier: "
             + PriceManager[0].CurrentTier + "/" + PriceManager[0].MaxTier;

        // Loop through all the nested Lists To set them up
        for (int i = 0; i <= PriceManager.Count - 1; i++)
        {
            // change price text to current prices
            PriceManager[i].PriceToChange.text = PriceManager[i].Prices[PriceManager[i].CurrentTier].ToString();
            Debug.Log("In Store Price Change Loop Works Here");
        }

        Debug.Log(PriceManager.Count + "Length Of Price Manager List");
        // reset action for multi use 
        Purchase = null;
    }

    void Debugging()
    {
        // Find all variables to see if they are found
        if(rightArmAnimator == null)
        {
            Debug.LogError("RIGHT ARM ANIMATOR NOT FOUND");
        }
        else
        {
            Debug.Log("right arm animator found");
        }

        if(leftArmAnimator == null)
        {
            Debug.LogError("LEFT ARM ANIMATOR NOT FOUND");
        }
        else
        {
            Debug.Log("Left arm animator found");
        }
    }

    // Update is called once per frame
    void Update()
    {
        // if we dont have this variable
        if (PlayerController == null)
        {
            // Find it again, we cant change values on runtime is we dont have it
            PlayerController = GameObject.Find("Space Cannon").GetComponent<SpaceCannon_Controller>();
            PlayerController.currentAmmo = PlayerController.maxAmmo;
        }
        else    // We have the variable so its time to just run the logic below
        {
            if(GM.GameOver_Menu.activeInHierarchy == true)
            {
                PlayerController.gameObject.SetActive(false);
            }
            // values in the nested lists to be saved during runtime
            CannonUpgradeDataSave(PriceManager[0].CurrentTier, PriceManager[1].CurrentTier, PriceManager[2].CurrentTier, PriceManager[3].CurrentTier);

            // run the code to make the text smaller 
            Purchase += GM.RunCo;

            // Run the stats // Changes the cannon states
            for (int i = 0; i <= PriceManager.Count - 1; i++)
            {
                // check names to suggest what state we are changing to stop any conflicts
                if(PriceManager[i].UpgradeName == "Ammo Capacity")
                {
                    // get the new max ammo and set the current to the new value
                    PlayerController.maxAmmo = (int)PriceManager[i].NewStates[PriceManager[i].CurrentTier];
                }
                else if(PriceManager[i].UpgradeName == "Reload Speed")
                {
                    // set the new reload time to the nested list values current position in the list
                    PlayerController.reloadSpeed = PriceManager[i].NewStates[PriceManager[i].CurrentTier];
                }
                else if(PriceManager[i].UpgradeName == "Fire Rate")
                {
                    // fire rate value is now the new value inside nested list
                    PlayerController.FireRate = PriceManager[i].NewStates[PriceManager[i].CurrentTier];
                }
                else
                {
                    // sends to GM
                    // meteor is less points than the star
                    GM.MeteorPoints = (int)PriceManager[i].NewStates[PriceManager[i].CurrentTier];
                    // Multiply by 2 so star provides more points (Money)
                    GM.StarPoints = (int)PriceManager[i].NewStates[PriceManager[i].CurrentTier] * 2;
                }
            }
        }

        // So we are about to Fade Out 
        if(FadeOutPanel && !FadeInPanel)
        {
            FadeInfoPanelOut();
        }
        else if(!FadeOutPanel && FadeInPanel)   // We are going to fade in now 
        {
            FadeInfoPanelIn();
        }

        // Controls the structure of the Robots animations
        RobotAnimations();

        // check if a button has been pressed
        if(buttonPressed && GM.InShopMenu)
        {
            if (lerp)
            {
                GameObject mainMenu = GameObject.Find("MainShop");
                if (mainMenu == null)
                {
                     mainMenu = GameObject.Find("MainShop");
                }
                else
                {
                    mainMenu.transform.position = Vector3.Lerp(mainMenu.transform.position, target, speed);
                    if (mainMenu.transform.position == target)
                        buttonPressed = false;
                }
            }
            else
            {
                GameObject mainMenu = GameObject.Find("MainShop");
                if(mainMenu == null)
                {
                    mainMenu = GameObject.Find("MainShop");
                }
                else
                {
                    mainMenu.transform.position = Vector3.Lerp(mainMenu.transform.position, target, speed);
                    if (mainMenu.transform.position == target)
                        buttonPressed = false;
                }
            }
        }
        #region Planet Fadding
        if (FadeIn)
        {
            StartCoroutine(FillIn());
        }

        if (FadeOut)
        {
            StartCoroutine(FillOut());
        }
        #endregion
        if (MainShopMenu)
        {
            ShopRoomSmallArrow.SetBool("Active", true);
            // Window animator does not move
            WindowRoomSmallArrow.SetBool("Idle", false);
            // 
            ShopRoomSmallArrow.SetBool("Idle", true);
        }
        else
        {
            WindowRoomSmallArrow.SetBool("Active", true);
            //
            ShopRoomSmallArrow.SetBool("Idle", false);
            //
            WindowRoomSmallArrow.SetBool("Idle", true);
        }


        // UI Planet Purchanse Monitor
        // When the Array of bools is true at a position 
        if(OwnedPlanets[planetScroller] == true)
        {
            // We set the price text to owned as we clearly own this Planet
            PlanetPriceText.text = "Owned";
            // change button text to equip
            BuyButton.GetComponentInChildren<Text>().text = "Equip";
            // If the scroller is on the array length that is the current active sprite
            if(planetScroller == planetActive)
            {
                // We dont use the button as you cannot apply something twice
                BuyButton.interactable = false;
                // change the text of the button to Equiped - This is the planet that has been equiped
                BuyButton.transform.GetComponentInChildren<Text>().text = "Equiped";
            }
            else if(planetScroller != planetActive)     // When the planet scroller does not equal to the current planet position sprite
            {
                // Turn the button on 
                BuyButton.interactable = true;
                // But we change the button text to Equip as we can change planets if we own more than 1 
                BuyButton.transform.GetComponentInChildren<Text>().text = "Equip";
            }
        }
        else
        {
            // Show the price of the planets via the UI text
            PlanetPriceText.text = PlanetPrices_[planetScroller].planetPrice.ToString();
            // Turn on the button
            BuyButton.interactable = true;
            // change the button text component to buy
            BuyButton.transform.GetComponentInChildren<Text>().text = "Buy";
        }

        // check to see if we have enough money for each price in each cannon upgrade
        for(int i = 0; i <= PriceManager.Count - 1; i++)
        {
            if(GM.money < PriceManager[i].Prices[PriceManager[i].CurrentTier])
            {
                PriceManager[i].UpgradeButton.interactable = false;
            }
            else
            {
                PriceManager[i].UpgradeButton.interactable = true;
            }

            if(PriceManager[i].Maxed == true)
            {
                PriceManager[i].UpgradeButton.interactable = false;
            }
        }

        for(int i = 0; i <= PlanetPrices_.Length - 1; i++)
        {
            if (GM.money < PlanetPrices_[i].planetPrice)
                BuyButton.interactable = false;
        }
    }

    private void OnDisable()
    {
        Purchase -= GM.RunCo;
    }

    // Resets the store when we leave the shop
    public void ResetStore()
    {
        // information Menu
        // resets what page we are on 
        scroller = 0;
        // go back to first page description
        DescriptionText.text = Descriptions[scroller];
        // first page upgrade
        UpradeInQuestion.text = UpgradeOrder[scroller] + "          " + "\n" + "Tier: "
            + PriceManager[scroller].CurrentTier + "/" + PriceManager[scroller].MaxTier;
        // first page logo
        TypeImage.sprite = Logos[scroller];

        // turn off menu 
        parentPanelInfo.SetActive(false);
        // alpha is full
        tempImage.a = 255;

        // reset as we want it to be at the beginning
        planetScroller = 0; // wont affect PlayerPrefs we store in different value
        // change sprite 
        PlanetImage.sprite = Planets[planetScroller];
    }



    [HideInInspector]
    public int waveController = 0;
    public void RobotAnimations()
    {
        // when in the store
        if (GM.InShopMenu && GM.shop_Menu.activeInHierarchy == true)
        {
            if (!PurchasedMade)
            {
                // Play celebration animation

                ///Wave - First animation
                // Not playing anything
                if (waveController == 0)
                {
                    if (RobotAS.isPlaying == false)
                    {
                        RobotAS.clip = greet;
                        RobotAS.PlayDelayed(0.8f);
                        rightArmAnimator.SetBool("Greeting", true);
                        waveController++;
                    }
                }

                // count upward to get ready to make a condition
                timeUntilNextAnim += Time.deltaTime;
                // when value is greater than or equal to 3 
                if (timeUntilNextAnim >= 3)
                {
                    RobotAS.Stop();
                    rightArmAnimator.SetBool("Greeting", false);
                    // reset the timer
                    timeUntilNextAnim = 0;
                    // pick a number
                    eyeDirection = Random.Range(0, 8);
                    // play animation
                    RobotEyes.SetInteger("Choice", eyeDirection);
                }
            }
            else
            {
                if(RobotAS.isPlaying == false)
                {
                    RobotAS.clip = celebrate;
                    RobotAS.Play();

                    timeUntilNextAnim = 0;
                    // play the one shot clip instead of using the controller
                    rightArmAnimator.Play("Right Arm Celebration"); leftArmAnimator.Play("Left Arm Celebration");

                    // Tick bool to false
                    PurchasedMade = false;
                }
            }
        }
    }

    // detects if we have made a purchase to play robot animation
    public void PurchaseMade(bool purchase)
    {
        Purchase();
        PurchasedMade = purchase;
    }

    #region Functions to change Planets
    public void NextPlanet()
    {
        // Trigger effect
        FadeIn = true;
        
        // increase so sprite changes
        planetScroller++;
        // Change direction of fade 
        // Left
        PlanetImage.fillOrigin = 0;
    }

    public void PlanetBefore()
    {
        // tick true
        FadeOut = true;
        // Decrease value 
        planetScroller--;
        // Change effect origin 
        // Right
        PlanetImage.fillOrigin = 1;
    }

    // Going Right 
    // Fads out on Left Fills in on right
    // Fade out the planet image as we would be changing the sprite
    // gives small effect of moving in space
    IEnumerator FillIn()
    {
        FadeIn = true;
        nextButton.interactable = false;
        PreviousButtons.interactable = false;

        PlanetImage.fillAmount -= Time.deltaTime;

        if (PlanetImage.fillAmount == 0)
        {
            PlanetImage.fillOrigin = 1;
            PlanetImage.sprite = Planets[planetScroller];
        }

        if (PlanetImage.fillOrigin != 0)
        {
            PlanetImage.fillAmount += Time.deltaTime * 2;

            if (PlanetImage.fillAmount == 1)
            {
                int i = 0;
                i = planetScroller + 1;
                if (i >= Planets.Length)
                {
                    FadeIn = false;
                    nextButton.interactable = false;
                    PreviousButtons.interactable = true;
                    PlanetImage.fillOrigin = 0;
                    yield break;
                }
                else
                {
                    FadeIn = false;
                    nextButton.interactable = true;
                    PreviousButtons.interactable = true;
                    PlanetImage.fillOrigin = 0;
                    yield break;
                }
            }
        }

    }
    // Planet reamurges with the new planet sprite in effect and we fill the image in a different direction
    IEnumerator FillOut()
    {
        FadeOut = true;
        nextButton.interactable = false;
        PreviousButtons.interactable = false;

        PlanetImage.fillAmount -= Time.deltaTime;

        if (PlanetImage.fillAmount == 0)
        {
            PlanetImage.fillOrigin = 0;
            PlanetImage.sprite = Planets[planetScroller];
        }

        if (PlanetImage.fillOrigin != 1)
        {
            PlanetImage.fillAmount += Time.deltaTime * 2;

            if (PlanetImage.fillAmount == 1)
            {
                if (planetScroller == 0)
                {
                    FadeOut = false;
                    nextButton.interactable = true;
                    PreviousButtons.interactable = false;
                    PlanetImage.fillOrigin = 0;
                    yield break;
                }
                else
                {
                    FadeOut = false;
                    nextButton.interactable = true;
                    PreviousButtons.interactable = true;
                    PlanetImage.fillOrigin = 0;
                    yield break;
                }
            }
        }
    }
    #endregion

    #region Lerp Menu Button Functions
    public void ButtonPressed(bool pressed)
    {
        buttonPressed = pressed;
    }


    public void Lerp(bool lerpChoice)
    {
        lerp = lerpChoice;
    }

    public void LerpTarget(Transform Target)
    {
        target = Target.transform.position;
    }

    public void MainShopMenuCheck(bool inShopMenu)
    {
        MainShopMenu = inShopMenu;
    }
    #endregion


    #region InformationPanel
    /// <summary>
    /// Makes the UI reappear
    /// </summary>
    public void FadeInfoPanelIn()
    {
        // Condition to just stop the below code when the parent is not active
        if (parentPanelInfo.activeInHierarchy == false)
            return;

        // Local array to find all the information panel Text Components
        Text[] textcomponentChild = parentPanelInfo.GetComponentsInChildren<Text>();
        Image[] imageComponentChild = parentPanelInfo.GetComponentsInChildren<Image>();

        // increase from alpha channel
        tempText.a += Time.deltaTime;
        tempImage.a += Time.deltaTime;
        // Loop to make each Text component change Alpha
        foreach (Text tx in textcomponentChild)
        {
            // make the Text Components equal the color vairable so it obtains the alpha
            tx.color = tempText;
            TypeImage.color = tempImage;
        }
    }
    /// <summary>
    /// Make the UI on screen vanish over time and so it can reappear
    /// </summary>
    public void FadeInfoPanelOut()
    {
        if (parentPanelInfo.activeInHierarchy == false)
            return;

        //Obtain UI Components in children from parent 
        Text[] textcomponentChild = parentPanelInfo.GetComponentsInChildren<Text>();
        Image[] imageComponentChild = parentPanelInfo.GetComponentsInChildren<Image>();

        // obtain the base color forthe Text
        tempText = textcomponentChild[0].color;
        tempImage = TypeImage.color;

        // decrease the alpha 
        tempText.a -= Time.deltaTime;
        tempImage.a -= Time.deltaTime;
        // Loop to change all the UI components
        foreach (Text tx in textcomponentChild)
        {
            // Change alpha by using the Temp colour
            tx.color = tempText;
            TypeImage.color = tempImage;
        }

        // When the alpha has hit 0
        if (tempText.a <= 0)
        {
            // make sure it does not get deducted anymore
            tempText.a = 0;
            // Make this false so we can fade in now 
            FadeOutPanel = false;
            // check to see what boolean we clicked to increase or decrease the scroller int 
            if (ClickedRightInfoButton)
                scroller++;
            else if (ClickedLeftInfoButton)
                scroller--;

            // Change the on screen information since the UI does not have Alpha at a visible level
            DescriptionText.text = Descriptions[scroller];

            UpradeInQuestion.text = UpgradeOrder[scroller] + "          " + "\n" + "Tier: "
                + PriceManager[scroller].CurrentTier + "/" + PriceManager[scroller].MaxTier;
            // Change Image 
            TypeImage.sprite = Logos[scroller];
            // Now we can fade in 
            FadeInPanel = true;
        }
    }
    /// <summary>
    /// Function to be used OnClick of the Information Panel Buttons
    /// Button to go back (Right)
    /// </summary>
    public void NextUpgrade()
    {
        #region Controlling the booleans
        FadeOutPanel = true;
        FadeInPanel = false;
        ClickedRightInfoButton = true;
        ClickedLeftInfoButton = false;
        #endregion

        // Button interactive check
        if (scroller >= 2)
        {
            GameObject.Find("RightButton").GetComponent<Button>().interactable = false;
            GameObject.Find("LeftButton").GetComponent<Button>().interactable = true;
        }
        else
        {
            GameObject.Find("RightButton").GetComponent<Button>().interactable = true;
            GameObject.Find("LeftButton").GetComponent<Button>().interactable = true;
        }
    }
    /// <summary>
    /// Function to be used OnClick of the Information Panel Buttons
    /// Button to go back (Left)
    /// </summary>
    public void PreviousUpgrade()
    {
        #region Controlling The Booleans
        FadeOutPanel = true;
        FadeInPanel = false;
        ClickedLeftInfoButton = true;
        ClickedRightInfoButton = false;
        #endregion

        // Buutton interaction check
        if (scroller == 1)
        {
            GameObject.Find("RightButton").GetComponent<Button>().interactable = false;
        }

        if (scroller <= 1)
        {
            GameObject.Find("LeftButton").GetComponent<Button>().interactable = false;
            GameObject.Find("RightButton").GetComponent<Button>().interactable = true;
        }
        else
        {
            GameObject.Find("LeftButton").GetComponent<Button>().interactable = true;
            GameObject.Find("RightButton").GetComponent<Button>().interactable = true;
        }
    }

    #endregion

    #region Planet Purchase
    public void ChangePlanetSprite()
    {

        // if the planet is owned then we have the option to equipt it 
        if(OwnedPlanets[planetScroller] == true)
        {
            if(RunTimePlanet.sprite == EquipPlanets[planetScroller])
            {
                Debug.Log("<color=cyan> This Planet Is Equipped</color>", this);
                BuyButton.GetComponentInChildren<Text>().text = "Equipped";
                // save the data on this int so we know what planet we are using
                PlayerPrefs.SetInt("CurrentPlanet", planetScroller);
            }
            else
            {
                BuyButton.GetComponentInChildren<Text>().text = "Equip";
                buyButton_AS.clip = defaultClick;
                // Spawn the cannon that matches this planet
                GameObject NewCannon = Instantiate(CannonPrefabs[planetScroller], Vector3.zero, Quaternion.identity) as GameObject; NewCannon.name = "Space Cannon";
                Destroy(CurrentCannon);
                CurrentCannon = NewCannon;
                // Change the main game planet sprite
                RunTimePlanet.sprite = EquipPlanets[planetScroller];
                Debug.Log("<color=cyan> We Just Equipped This Planet</color>", this);
                planetActive = planetScroller;
                // save the data on this int so we know what planet we are using
                PlayerPrefs.SetInt("CurrentPlanet", planetScroller);

            }
            PlanetPriceText.text = "Owned";
            // we can equip these planets
            Debug.Log("<color=cyan> We Own This Planet</color>", this);
        }
        else
        {
            Debug.Log("<color=red> We Do Not Own This Planet</color>", this);

            // because we dont own it we want to buy it instead
            if(GM.money >= PlanetPrices_[planetScroller].planetPrice)
            {
                BuyButton.GetComponentInChildren<Text>().text = "Buy";
                buyButton_AS.clip = buyPlanet;
                // save the data on this int so we know what planet we are using
                PlayerPrefs.SetInt("CurrentPlanet", planetScroller);
                // set value so it knows what planet we use 
                planetActive = planetScroller;

                // buy the planet 
                Debug.Log("<color=cyan> We Now Own This Planet</color>", this);
                // we now own this planet
                OwnedPlanets[planetScroller] = true;
                // we now change the planet in game to the planet we purchased
                RunTimePlanet.sprite = EquipPlanets[planetScroller];
                // we also spawn the new cannon that corresponds with this planet
                GameObject NewCannon = Instantiate(CannonPrefabs[planetScroller], Vector3.zero, Quaternion.identity) as GameObject; NewCannon.name = "Space Cannon";
                // we destroy the old planet
                Destroy(CurrentCannon);
                // the new cannon is set
                CurrentCannon = NewCannon;

                // deduct money from players funds
                Game_Manager.starDust -= PlanetPrices_[planetScroller].planetPrice;

                // Save array data to give back later
                PlayerPrefsX.SetBoolArray("SavedPlanets", OwnedPlanets);
            }
            else
            {
                // Cannot Afford Planet
                Debug.Log("<color=red> We Dont Have Enough Money For This Planet</color>", this);
                BuyButton.interactable = false;
            }
        }
    }
    
    #endregion

    #region Store Upgrader
    public void ChangePrice(int Type)
    {
        // if we do not have the money 
        if(GM.money <= PriceManager[Type].Prices[PriceManager[Type].CurrentTier])
        {
            // turn off the corresponding button for that upgrade
            PriceManager[Type].UpgradeButton.interactable = false;
        }
        else
        {
            // if we do not have a maxed out upgrade we can buy it 
            if (PriceManager[Type].Maxed != true)
            {
                // the button is now on for we can buy this upgrade 
                PriceManager[Type].UpgradeButton.interactable = true;
                // extra check to make sure we have not gone past the max tier of upgrades for that upgrade
                if (PriceManager[Type].CurrentTier >= PriceManager[Type].MaxTier)
                {
                    // turn this off for we have gone past or equal to the max
                    PriceManager[Type].UpgradeButton.interactable = false;
                    // we have maxed out this upgrade
                    PriceManager[Type].Maxed = true;
                    // Text tells player its maxed out
                    PriceManager[Type].PriceToChange.text = "Max";
                }
                else
                {
                    // if not max we simply deduct the funds from the player
                    Game_Manager.starDust -= PriceManager[Type].Prices[PriceManager[Type].CurrentTier];
                    // we are now on a new tier of upgrade for we just purchased 
                    PriceManager[Type].CurrentTier++;
                    // change the old price to the new one 
                    PriceManager[Type].PriceToChange.text = PriceManager[Type].Prices[PriceManager[Type].CurrentTier].ToString();
                }
            }
            else
            {
                // if not relevent to any then return with nothing, blockoff
                return;
            }
        }

    }
    #endregion

    // Simple function that saves the data in a nested list so we can store them on runtime and obtain them on new awake or start
    void CannonUpgradeDataSave(int AmmoSave, int ReloadSave, int FireSave, int MoneySave)
    {
        PlayerPrefs.SetInt("AmmoSave", AmmoSave);
        PlayerPrefs.SetInt("ReloadSave", ReloadSave);
        PlayerPrefs.SetInt("FireSave", FireSave);
        PlayerPrefs.SetInt("MoneySave", MoneySave);
    }
}
#region Upgrade Classes 
[System.Serializable]
public class PriceChange
{
    [Tooltip("The Name Of The Array We Are Altering")]
    public string UpgradeName = "";
    [Tooltip("Text That Changes With Upgrade")]
    public Text PriceToChange;
    [Tooltip("The List Of Prices For Each Upgrade")]
    public List<int> Prices = new List<int>();
    [Tooltip("The value that tells us what tier of upgrade we are on")]
    public int CurrentTier = 0;
    [Tooltip("The Button that belongs to the upgrade")]
    public Button UpgradeButton;
    [Tooltip("The New States of the different upgrades")]
    public List<float> NewStates = new List<float>();
    [Tooltip("The Max Amount Of Upgrades We set to do")]
    public int MaxTier;
    public bool Maxed = false;
}

[System.Serializable]
public class PlanetPrices
{
    public string planetName = "";
    public int planetPrice;
}
#endregion

Shader Graphs

[expand title=”Force Field”]

In the project, I wanted to give the player a slight advantage and provide them with a force field that can be damaged and then destroyed. So I decided to make a shader graph to make the effect I wanted to achieve. So visually I saw the force field as a pulsating object which would change colour after every hit. I also saw it as having a hiney comb texture scrolling over it. I made the shader graph for a 3D sphere in unity.

The Final Version of it is here 

These are all the variables I used to make the graph these variables control, colour lerping, the electric noise control and also the fill amount of the force field which made the force field more transparent. I also needed the force field to pulse to give an effect of power

So now that I have a way to make the force field glow I made a texture scroller to make a texture wrap around the object 

After I made this part of the graph I wanted a way to close the force field when its health had hit zero. So I decided to make a cross-section where the force field would open up from the bottom and disappear when it had reached the top.

This part makes the logic to close the force field

But this part adds to the final result

 

The only other logic this shader needed was being able to lerp its colour and also having an electric noise outline. I made this logic into subgraphs to save space but this is the outcome.

This is the electric noise being applied to the final emission

This is the lerp in the main graph which is held against a branch condition where the players forcefield will change from blue to orange then red as its final colour. 

Electric Noise Sub Graph

These are the variables I used in the electric noise subgraph. Being able to change the tiling and offset of the noise texture and then control how much noise gets exposed while also changing its colour. 

This is the main contents of the subgraph where time effects the tiling and offset output and using the step node to make the output positive while then linking it to the subtract to making it negative so then just the right amount of noise is exposed

 

Colour Lerp Sub Graph

These are the only variables I needed to control the force field lerp. The current colour I have, then the next colour I wanted to change to. Also how long that would take.

Then the variables are hooked up to a Lerp node which then meant I could call this subgraph inside the main graph and then start Lerping colours

[/expand]

[expand title=”Sprite Outline”]

The Sprite outline shader was not that tough to handle, the outline was mainly applying a texture you want to be outlined and applying the texture to the different parts of the sprite so it overlaps a little. This is why thickness is a variable so the texture can get bigger on its outline.

The outline is just the Texture we want to outline but bigger.

This part of the graph finds the left, right, top and bottom part of the sprite. We do this so the entire sprite has an outline. 

We then add and clamp the outcome so the outline will never change. then providing an outline colour from our variable and then input it into the end node.

[/expand]

[expand title=”Dissolve”]

The dissolve shader was made to dissolve the planet the player protects when an obstacle hits it. In a way to show that the planet was about to explode. Before the dissolve ends I spawn an explode effect. 

The variables for the dissolve graph simply made a texture slowly dissolve, using noise to give the dissolve effect and proving a colour so we see a different colour when we dissolve. Fade just simply determine how long it takes for the dissolve to start and finish.

The noise node then provides its information being given the noise scale we set for the dissolve size. then making sure keeping the dissolve noise stays positive with the step node. also providing the sample texture to then multiply and combine with the final outcome. The Colour variable is also passed through too so the dissolving colour can be changed. 

[/expand]

Some Are From The Game

[expand title=”UI Tiles”]

These are the tiles I created for the UI store menu. Although I did not use all of these this was the image I had for the game 

[/expand]

[expand title=”Different Planets & Cannon”]

These are different variations of the planets and cannons I made for the game that you can buy in the store. I just liked the different designs and being able to buy a new design I think players would enjoy, it gives them variety.

[/expand]

[expand title=”UI tiles”]

These are the different variations of backgrounds I made for the game and decided to choose the first one for the game as I felt like it fit well with the game more than a brighter colour

[/expand]

Conclusion

While developing this game I learnt a lot about my future goals and what game platform I want to develop on. I plan to focus on making games for android mobile devices, making WebGLs for websites like Itch.IO and making games to release on platforms like steam. It was a fun experience making a project where I created all the assets alone, stressful at times but an experience I don’t plan on forgetting anytime soon. I would say this project allowed me to learn a lot about sound design. I found myself at the best of times enjoying making sound effects and music while using Audacity to achieve it. I also learnt more about shader graph and now understanding what subgraphs do. I had never used them in the past but now after using them, I feel comfortable more with using shader graph. Lastly, I would say I have realised I have a lot to learn when it comes to UI there was a lot of issues I faced when it came to UI and a lot of mistakes like Lerping the game menu but forgetting the idea people don’t all share the same aspect ratio for their devices. I enjoyed making this project a lot and watching every little detail come to life. It was extremely fun starting a project with really basic standard assets like cubes and circles and then seeing them turn into art assets that have visual effects and audio clips to play. If you would like to view this project I have linked my Github, where all the code and assets can be found. But if you would like to play it then I have also linked it on the Google Play Store.