Dinozards

July 13, 2019

Project Details

  • Platform: PC
  • Genre: Local multi-player / Party
  • Engine: Unity
  • Development Time: 7 weeks

  • *Controllers are required

Concept



Dinozards is a couch multi-player party game with a battle royale twist


Dinozards is an arena based top down multi-player brawler where you play as a Magical Dino-baby having to fight for its survival against other Dino Babies for the affection of your Wizard Father The last Dino standing wins! You can play 2-4 player Free for All or 2v2 Team Mode.




Roles and Responsibilities

  • Project Management

    Scrum master, Team management and communication, Pitch presentation


  • Game Design/Programming C#

    Game core and game loop design, Game systems and UI scripting


  • Sound Design

    Sound mixing, Gameplay audio scripting



Game Design - MDA



We looked at succesful arena-type games. Games based on spells and crisp movement. This has exclusively been successful with a desktop, mouse and keyboard. We wanted to take this genre and turned it on its head, implementing it all into a couch co-op experience adding a battle royale twist.



MDA


MDA


MDA


MDA


Programming C#



During this project I programmed all the in-game UI, Menus, Camera, Game Manager and several world Gameplay Components using Visual Studio + Visual Assist. My priority was to focus on gameplay, building components that connects with the game loop and at the same time scalable in a way where other programmers and designers can use it or continue work on it.



Shrinking Storm


The storm main goal is to inflict damage to characters outside its radius. But it also has other mechanics to add different dynamics to the gameplay: radius resizing, path following, movement speed, scale speed and intervals. It follows multiple random paths that can be easily created in a few seconds.
The storm system is composed by 3 components. Storm Path Follower, Storm Behavior, Storm Effect





Storm Path Follower


This component allows designers to create multiple storm paths. Each match it randomly picks one of the paths



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

public class StormPathFollower : MonoBehaviour
{
    [SerializeField]
    private List pathCreatorList;

    private float speed;

    [SerializeField]
    private bool alignRotation = false;

    private StormBehavior stormInstance;

    [SerializeField]
    private PathCreator pathCreator;

    public float distanceTraveled;

    public void SetPath()
    {
        int myInt = Random.Range(0, pathCreatorList.Count);
        pathCreator = pathCreatorList[myInt];
    }

    private void Awake()
    {
        stormInstance = FindObjectOfType();
        speed = stormInstance.stormSpeed;
    }

    private void Update()
    {
        if (stormInstance.isMoving)
            distanceTraveled += speed * Time.deltaTime;

        transform.position = pathCreator.path.GetPointAtDistance(distanceTraveled); //Follows the path

        if (alignRotation)
        {
            transform.rotation = pathCreator.path.GetRotationAtDistance(distanceTraveled); //Rotates toward the path
        }
    }
}





Storm Behavior


The Storm Behavior component is responsible for the storm movement, scale shrinking and damage check. Designers can script different actions intervals, setting new movement speed, shrinking speed and timers



using System.Collections.Generic;
using UnityEngine;

public class StormBehavior : MonoBehaviour
{
    private static StormBehavior instance;

    private Transform circleTransform;

    private Vector3 circleCurrentSize;

    private Vector3 circleCurrentPosition;

    private bool isStormFollowingPath;

    private float shrinkingSpeed;

    private StormPathFollower stormFollower;

    private int listIndex;

    private bool doneBroadcasting = false;

    [HideInInspector]
    public bool isShrinking = false;

    [SerializeField, Header("Storm Visual Reference")]
    private bool showVisualReference = true;

    [HideInInspector]
    public float stormCurrentRadius;

    [SerializeField]
    private GameObject stormVR;

    [SerializeField]
    private GameObject stormCenter;

    [SerializeField]
    private GameObject stormStencil;

    [SerializeField]
    private GameObject stormTop;

    [SerializeField]
    private GameObject stormBottom;

    [SerializeField]
    private GameObject stormRight;

    [SerializeField]
    private GameObject stormLeft;

    [SerializeField, Header("Rain")]
    private GameObject rain;

    [SerializeField]
    private GameObject lightning;

    [SerializeField]
    private GameObject lightningScreen;

    [Space]

    [Header("Storm General")]
    [SerializeField, Tooltip("How many seconds to enable the storm.")]
    private float stormStartTimer = 0f;

    [SerializeField]
    private float stormTargetRadius = 25f;

    public float stormInitialRadius = 25f;

    [SerializeField]
    private float minimumRadius = 5f;

    [SerializeField]
    private float newTargetRadius = 50f;

    [Range(0f,10f)]
    public float stormSpeed = 2f;

    [SerializeField]
    private float movementStopInterval;

    [SerializeField, Tooltip("This will only be used if the intervals list is empty. The higher this value, fast will be.")]
    private float defautShrinkingSpeed = 3f;

    [SerializeField]
    private float stormHeight = 10f;

    [SerializeField]
    private List wizardLaughsAudioNames;

    [HideInInspector]
    public bool enableDamage;

    [Space]

    [Header("Storm Damage")]
    public float stormDamageValue = 0.1f;

    [Space]

    [SerializeField, Header("Shrinking Intervals")]
    private IntervalRangeClass[] shrinkIntervals;

    [SerializeField]
    private LavaGround lavaGround;

    [SerializeField]
    private float timeToRaiseLava = 3f;

    public bool isMoving;

    public delegate void LavaActivated();

    public static event LavaActivated LavaActivatedInfo;

    public delegate void StormActivated();

    public static event StormActivated StormActivatedInfo;

    [Range(0.1f, 5f)]
    public float timerDuration = 1f;

    private void Awake()
    {
        instance = this;
        stormFollower = FindObjectOfType();
    }

    private void Start()
    {      
        stormFollower.SetPath();
        circleTransform = transform;
        stormCurrentRadius = stormInitialRadius;
        circleCurrentSize = new Vector3(stormCurrentRadius, stormCurrentRadius, stormCurrentRadius);
        newTargetRadius = stormInitialRadius;
        listIndex = 0;
        stormVR.SetActive(showVisualReference);
        Invoke("StartStorm", stormStartTimer);
    }

    private void StartStorm()
    {
        enableDamage = true;
        StartShrinkInterval();
        FindObjectOfType().ShowWizardWarning01();
        if (movementStopInterval != 0)
        {
            Invoke("ToggleMoving", movementStopInterval);
        }
    }

    private void Update()
    {      
        SetCircleSizeAndPosition(circleCurrentSize);
        StormShrinking(newTargetRadius, shrinkingSpeed);
        if (isShrinking && stormCurrentRadius - newTargetRadius < 0.01)
        {
            isShrinking = false;
            NextShrinkInterval();
        }
        if (!doneBroadcasting && stormCurrentRadius <= 35)
        {
            doneBroadcasting = true;
        }

    }

    private void SetCircleSizeAndPosition(Vector3 size) 
    {
        circleTransform.localScale = size;
    }

    private bool IsOutsideCircle(Vector3 position)
    {
        return Vector3.Distance(position, transform.position) > circleCurrentSize.x * .5f;
    }

    public static bool IsOutsideCircle_Static(Vector3 position)
    {
        return instance.IsOutsideCircle(position);
    }


    //Lerp the Storm Radius circle values
    private void StormShrinking(float targetSize, float speed)
    {
        if (stormCurrentRadius != targetSize)
        {
            stormCurrentRadius = Mathf.Lerp(stormCurrentRadius, targetSize, Time.deltaTime * speed/10);
            circleCurrentSize = new Vector3(stormCurrentRadius, stormCurrentRadius, stormHeight);
        }
    }

    private void ToggleMoving()
    {
        if (isMoving)
        {
            isMoving = false;
            Invoke("ToggleMoving", movementStopInterval);
        }
        else
        {
            isMoving = true;
            Invoke("ToggleMoving", movementStopInterval);
        }
    }

    private void StartShrinkInterval()
    {
        if (shrinkIntervals.Length != 0) //Use the list parameters
        {
            float intervalTimer;
            intervalTimer = shrinkIntervals[listIndex].preCounter;
            Invoke("ShrinkInterval", intervalTimer);
        }
    }

    private void ShrinkInterval()
    {
        shrinkingSpeed = shrinkIntervals[listIndex].shrinkSpeed;
        newTargetRadius = stormCurrentRadius - shrinkIntervals[listIndex].units;
        isShrinking = true;
        isMoving = shrinkIntervals[listIndex].moveStorm;

        if (shrinkIntervals[listIndex].raiseLava)
        {
            Invoke("EnableLava", timeToRaiseLava);
            int listIndex = Random.Range(0, wizardLaughsAudioNames.Count);

            AudioManager.instance.Play(wizardLaughsAudioNames[listIndex]);
        }

        if (shrinkIntervals[listIndex].thunderWeak)
        {
            lightningScreen.SetActive(true);

            AudioManager.instance.Play("Thunderstorm_02");
        }

        if (shrinkIntervals[listIndex].thunderStrong)
        {
            FindObjectOfType().ShowWizardWarning02();
            int listIndex = Random.Range(0, wizardLaughsAudioNames.Count);
            rain.SetActive(true);
            lightning.SetActive(true);
            lightningScreen.SetActive(true);

            AudioManager.instance.Play(wizardLaughsAudioNames[listIndex]);
            AudioManager.instance.Play("Thunderstorm_01");
            AudioManager.instance.Play("ThunderstormAmbience_01");
            AudioManager.instance.Play("Rain_01");
        }
    }

    private void EnableLava()
    {
        lavaGround.enabled = true;
        DamagingZoneBehaviour.EnableZone();

        AudioManager.instance.Play("LavaRaising_01");
    }

    private void NextShrinkInterval()
    {
        if (stormCurrentRadius >= minimumRadius)
        {
            listIndex++;
            StartShrinkInterval();
        }
    }

    public void RestartStorm()
    {
        stormFollower.distanceTraveled = 0f;

        Start();
    }

    public void StopStorm()
    {
        enableDamage = false;
        isMoving = false;
        isShrinking = false;

        AudioManager.instance.Stop("ThunderstormAmbience_01");
        AudioManager.instance.Stop("Rain_01");
    }
}






Storm Effect


This component checks if the player character is outside the storm range, provoking damage to it when that condition is true.



using UnityEngine;

public class StormPlayerEffect : MonoBehaviour
{

    private PlayerHit myHitComponent;

    private StormBehavior gameStorm;

    private float initialStormTimer;

    private float currentStormTimer;


    private void Awake()
    {
        myHitComponent = GetComponent();
        gameStorm = FindObjectOfType();
        if (gameStorm != null)
        {
            initialStormTimer = gameStorm.timerDuration;
            currentStormTimer = initialStormTimer;
        }
    }

    private void Update()
    {
        if (gameStorm != null)
        {
            StormTimer();
        }
    }

    private void StormTimer()
    {
        currentStormTimer -= Time.deltaTime;

        if (currentStormTimer <= 0.0f)
        {
            MakeDamage();
            currentStormTimer = initialStormTimer;
        }
    }

    void MakeDamage()
    {
        if (StormBehavior.IsOutsideCircle_Static(transform.position))
        {
            if (myHitComponent)
            {
                myHitComponent.OnPlayerHit(Vector3.zero, 0, gameStorm.stormDamageValue, 0, 0, PlayerHit.HitBy.Storm, 0.2f, 0, 1);
            }
        }
    }
}








Camera


The camera sits on a dolly and moves backwards and forwards depending on the players distance; it also follows the players by calculating their bounds each frame. This ensures that all characters are within the camera's view and create an engaging gameplay dynamic.




Multiple Target Camera



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

public class MultipleTargetCamera : MonoBehaviour
{

    private List targets;

    [SerializeField]
    private float smoothTime = .5f;

    [SerializeField]
    private float camMinZoom = 9f;

    [SerializeField]
    private float camAngle = 75f;

    private Vector3 velocity;

    [SerializeField]
    private Camera myCamera;

    private Vector3 centerPoint;

    private float playersBoundSize;

    private Vector3 originalPos;

    [SerializeField]
    private float zoomOutMultiplier;

    [HideInInspector]
    public float boundLerp;

    public void StartCamera()
    {
        targets.Clear();

        foreach (var item in GameObject.FindGameObjectsWithTag("Player"))
        {
            targets.Add(item);
        }

        centerPoint = GetCenterPoint();
    }

    public void RemovePlayer(GameObject killedPlayer)
    {
        targets.Remove(killedPlayer);
        
    }
   
    private void LateUpdate()
    {
        if (targets.Count == 0)
        {
            return;
        }
        centerPoint = GetCenterPoint();
        playersBoundSize = GetPlayersBoundSize();

        transform.rotation = Quaternion.Euler(camAngle, 0f, 0f);
        transform.position = Vector3.SmoothDamp(transform.position, centerPoint, ref velocity, smoothTime);

        boundLerp = Mathf.Clamp(Mathf.SmoothStep(boundLerp, playersBoundSize, smoothTime), camMinZoom, 999f);

        myCamera.transform.localPosition = new Vector3(0, 1f, 0) * (boundLerp + 5f) * zoomOutMultiplier;
    }

    private Vector3 GetCenterPoint()
    {
        if (targets.Count == 1)
        {
            return targets[0].transform.position;
        }

        Bounds bounds = new Bounds(targets[0].transform.position, Vector3.zero);
        for (int i = 0; i < targets.Count; i++)
        {
            bounds.Encapsulate(targets[i].transform.position);
        }
        return bounds.center;
    }

    private float GetPlayersBoundSize()
    {
        if (targets.Count == 1)
        {
            return camMinZoom;
        }

        Bounds bounds = new Bounds(targets[0].transform.position, Vector3.zero);
        for (int i = 0; i < targets.Count; i++)
        {
            bounds.Encapsulate(targets[i].transform.position);
        }

        if (bounds.size.x >= bounds.size.z)
        {
            return bounds.size.x;
        }
        else
        {
            return bounds.size.z;
        }
    }





Game Manager


The game manager is composed by two components: Game Manager and Gameplay Handler. The first manages all the game states and conditions such: Gameplay Mode, Activate/Deactivate Players, Pause game, Load Scenes, Scoring, Quit application. Gameplay Handler: Handles the gameplay match conditions such: Spawning/Despawn Characters, Start match, Game Over, Trigger world dynamic events




Game Manager



using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
using XInputDotNetPure;


public class GameManager : MonoBehaviour
{

    // Public
    public static GameManager managerInstance;

    #region Fields    
    public enum eGameState
    {
        CINEMATIC,
        START,
        SELECT,
        PLAYING,
        WAITING,
        DEAD,
        ROUNDOVER,
        GAMEOVER,
        CREDITS,
        HELP,
        PAUSE,
        QUIT,
        NULL
    }

    public eGameState gameState;

    public enum eTeamMode
    {
        SOLO,
        DOUBLES
    }

    public eTeamMode teamMode;

    [HideInInspector]
    public bool p1Active = true;
    [HideInInspector]
    public bool p2Active = true;
    [HideInInspector]
    public bool p3Active = true;
    [HideInInspector]
    public bool p4Active = true;

    [SerializeField, Header("Character Selection Settings")]
    private float characterSelectionCountdownTimer = 5f;

    [SerializeField, Header("Match Settings")]
    private float roundCountdownTimer = 3f;

    public int maxNumberOfRounds = 3;

    [SerializeField, Header("Audio")]
    private List playerJoinedAudioNames;

    //Private

    private int numberOfPlayersJoined;

    public CharacterSelectionTimer charSelectionTimer;

    private GamePlayHandler gamePlayHandler;

    private CharacterPortrait p1Portrait;

    private CharacterPortrait p2Portrait;

    private CharacterPortrait p3Portrait;

    private CharacterPortrait p4Portrait;

    [HideInInspector]
    public int player1Victories = 0;

    [HideInInspector]
    public int player2Victories = 0;

    [HideInInspector]
    public int player3Victories = 0;

    [HideInInspector]
    public int player4Victories = 0;

    [HideInInspector]
    public int draws = 0;

    #endregion Fields

    private void Awake()
    {
        DontDestroyOnLoad(gameObject);

        if (managerInstance == null)
        {
            managerInstance = this;
            OnLevelWasLoaded(SceneManager.GetActiveScene().buildIndex);
            return;
        }
        else
        {
            Destroy(gameObject);
        }

    }

    private void Start()
    {
        GamePlayHandler.PlayerVictoryInfo += HandlePlayerVictory;
    }

    public void InitiateVariables()
    {
        p1Active = false;
        p2Active = false;
        p3Active = false;
        p4Active = false;
        numberOfPlayersJoined = 0;
        teamMode = eTeamMode.SOLO;
    }

    #region Character Selection
    public void PlayerJoin(PlayerIndex playerIndex)
    {
        if (playerIndex == PlayerIndex.One)
        {
            if (!p1Active)
            {
                p1Active = true;
                CheckNumberOfPlayers(true);

                p1Portrait.PlayerActive();
            }
            else
            {
                //Add visual animation if needed
                return;
            }
        }

        else if (playerIndex == PlayerIndex.Two)
        {
            if (!p2Active)
            {
                p2Active = true;
                CheckNumberOfPlayers(true);

                p2Portrait.PlayerActive();
            }
            else
            {
                //Add visual animation
                return;
            }
        }

        else if (playerIndex == PlayerIndex.Three)
        {
            if (!p3Active)
            {
                p3Active = true;
                CheckNumberOfPlayers(true);

                p3Portrait.PlayerActive();
            }
            else
            {
                //Add visual animation
                return;
            }
        }

        else if (playerIndex == PlayerIndex.Four)
        {
            if (!p4Active)
            {
                p4Active = true;
                CheckNumberOfPlayers(true);

                p4Portrait.PlayerActive();
            }
            else
            {
                //Add visual animation
                return;
            }
        }
    }

    public void PlayerLeave(PlayerIndex playerIndex)
    {
        if (numberOfPlayersJoined == 0)
        {
            LoadScene(1);
        }
        else if (playerIndex == PlayerIndex.One)
        {
            if (p1Active)
            {
                p1Active = false;
                CheckNumberOfPlayers(false);
                p1Portrait.PlayerDeactivate();
            }
        }

        else if (playerIndex == PlayerIndex.Two)
        {
            if (p2Active)
            {
                p2Active = false;
                CheckNumberOfPlayers(false);
                p2Portrait.PlayerDeactivate();
            }
        }

        else if (playerIndex == PlayerIndex.Three)
        {
            if (p3Active)
            {
                p3Active = false;
                CheckNumberOfPlayers(false);
                p3Portrait.PlayerDeactivate();
            }
        }

        else if (playerIndex == PlayerIndex.Four)
        {
            if (p4Active)
            {
                p4Active = false;
                CheckNumberOfPlayers(false);
                p4Portrait.PlayerDeactivate();
            }
        }
    }

    private void CheckNumberOfPlayers(bool isPlayerActive)
    {
        if (isPlayerActive)
        {
            numberOfPlayersJoined++;
            int listIndex = Random.Range(0, playerJoinedAudioNames.Count);
            AudioManager.instance.Play(playerJoinedAudioNames[listIndex]);
            AudioManager.instance.Play("PlayerJoined_01");
        }
        else
        {
            numberOfPlayersJoined--;
        }

        if (charSelectionTimer)
        {
            charSelectionTimer.CheckActivePlayers(numberOfPlayersJoined);
        }
    }

    public void PlayerStartMatch(PlayerIndex playerIndex)
    {
        if (playerIndex == PlayerIndex.One)
        {
            if (p1Active)
            {
                StartMatch();
            }
            else
            {
                //TODO Add visual animation???
                return;
            }
        }

        else if (playerIndex == PlayerIndex.Two)
        {
            if (p2Active)
            {
                StartMatch();
            }
            else
            {
                //TODO Add visual animation???
                return;
            }
        }

        else if (playerIndex == PlayerIndex.Three)
        {
            if (p3Active)
            {
                StartMatch();
            }
            else
            {
                //TODO Add visual animation???
                return;
            }
        }

        else if (playerIndex == PlayerIndex.Four)
        {
            if (p4Active)
            {
                StartMatch();
            }
            else
            {
                //TODO Add visual animation???
                return;
            }
        }
    }

    public void StartMatch()
    {
        switch (teamMode)
        {
            case eTeamMode.SOLO:
                if (numberOfPlayersJoined >= 2)
                {
                    AudioManager.instance.Play("PressStart_01");
                    LoadScene(4);
                }
                break;
            case eTeamMode.DOUBLES:
                if (numberOfPlayersJoined >= 3)
                {
                    AudioManager.instance.Play("PressStart_01");
                    LoadScene(4);
                }
                break;
        }
    }

    #endregion Character Selection

    #region GameOver

    public void FinishMatch()
    {
        AudioManager.instance.Stop("GamePlay_01");
        AudioManager.instance.Play("GameOver_01");
    }

    public void RestartGame()
    {
        LoadScene(2);
    }


    #endregion GameOver
    public void LoadScene(int sceneIndex)
    {
        SceneManager.LoadScene(sceneIndex);
    }

    private void OnLevelWasLoaded(int level)
    {
        if (level == 0)
        {
            gameState = eGameState.CINEMATIC;
        }
        else if (level == 1)
        {
            gameState = eGameState.START;
        }
        else if (level == 2)
        {
            gameState = eGameState.SELECT;
        }
        else if (level == 3)
        {
            gameState = eGameState.PLAYING;
        }
        else if (level == 4)
        {
            gameState = eGameState.WAITING;
        }
        OnGameStateSet();
    }

    private void HandlePlayerVictory(int index)
    {
        switch (index)
        {
            case 0:
                draws++;
                break;
            case 1:
                player1Victories++;
                break;
            case 2:
                player2Victories++;
                break;
            case 3:
                player3Victories++;
                break;
            case 4:
                player4Victories++;
                break;
        }
    }

    public void OnGameStateSet()
    {
        switch (gameState)
        {
            case eGameState.CINEMATIC:
                InitiateVariables();

                break;

            case eGameState.START:
                InitiateVariables();
                InitialScreen initialScreen = FindObjectOfType();
                initialScreen.DisableQuitGame();
                break;

            case eGameState.SELECT:
                InitiateVariables();

                p1Portrait = GameObject.FindGameObjectWithTag("Player_01_Portrait").GetComponent();
                p2Portrait = GameObject.FindGameObjectWithTag("Player_02_Portrait").GetComponent();
                p3Portrait = GameObject.FindGameObjectWithTag("Player_03_Portrait").GetComponent();
                p4Portrait = GameObject.FindGameObjectWithTag("Player_04_Portrait").GetComponent();

                charSelectionTimer = FindObjectOfType();
                charSelectionTimer.matchStartTimer = characterSelectionCountdownTimer;
                charSelectionTimer.ResetCounter();

                AudioManager.instance.Play("PressStart_01");
                break;

            case eGameState.PLAYING:
                gamePlayHandler = FindObjectOfType();
                gamePlayHandler.AwakeLevel(this);

                AudioManager.instance.Play("GamePlay_01");
                break;

            case eGameState.WAITING:
                break;

            case eGameState.GAMEOVER:
                break;

            case eGameState.HELP:
                charSelectionTimer.EnableHelpWindow();
                break;

            case eGameState.CREDITS:
                charSelectionTimer.EnableCreditsWindow();
                break;

            case eGameState.PAUSE:
                gamePlayHandler = FindObjectOfType();
                gamePlayHandler.PauseGame();
                break;

            case eGameState.QUIT:
                InitialScreen initialScreen2 = FindObjectOfType();
                initialScreen2.EnableQuitGame();
                break;

            case eGameState.NULL:
                break;

            default:
                break;
        }
    }

    public void ChangeTeamMode()
    {
        switch (teamMode)
        {
            case eTeamMode.SOLO:
                teamMode = eTeamMode.DOUBLES;
                if (charSelectionTimer)
                {
                    charSelectionTimer.UpdateTeamText(teamMode);
                }
                break;

            case eTeamMode.DOUBLES:
                teamMode = eTeamMode.SOLO;
                if (charSelectionTimer)
                {
                    charSelectionTimer.UpdateTeamText(teamMode);
                }
                break;

        }
        if (charSelectionTimer)
        {
            charSelectionTimer.CheckActivePlayers(numberOfPlayersJoined);
            charSelectionTimer.ResetCounter();
        }

        p1Portrait.SetTeamMode();
        p2Portrait.SetTeamMode();
        p3Portrait.SetTeamMode();
        p4Portrait.SetTeamMode();

        AudioManager.instance.Play("PressStart_01");
    }

    public void UnpauseGame()
    {
        gameState = eGameState.PLAYING;
        gamePlayHandler = FindObjectOfType();
        gamePlayHandler.UnpauseGame();
    }

    public void QuitGame()
    {
        Application.Quit();
    }
}






Gameplay Handler



using System.Collections.Generic;
using UnityEngine;

public class GamePlayHandler : MonoBehaviour
{

    #region Fields

    [SerializeField]
    private GameObject player1Prefab;

    [SerializeField]
    private GameObject player2Prefab;

    [SerializeField]
    private GameObject player3Prefab;

    [SerializeField]
    private GameObject player4Prefab;

    public int preGameCounter = 3;

    public int nextGameCounter = 5;

    [SerializeField]
    private GamePlayUI gamePlayUI;

    [HideInInspector]
    public LavaGround lavaGround;

    [HideInInspector]
    public List playerCharacters = new List();

    [HideInInspector]
    public List playersTransform = new List();

    private int currentRound;

    private bool isGameStarted;

    [HideInInspector]
    public int activePlayers;

    private int blueTeamPlayers;

    private int redTeamPlayers;

    [HideInInspector]
    public int blueTeamScore;

    [HideInInspector]
    public int redTeamScore;

    [HideInInspector]
    public int playerOneScore;

    [HideInInspector]
    public int playerTwoScore;

    [HideInInspector]
    public int playerThreeScore;

    [HideInInspector]
    public int playerFourScore;

    [HideInInspector]
    private StormBehavior storm;

    private Vector3 p1Position;

    private Vector3 p2Position;

    private Vector3 p3Position;

    private Vector3 p4Position;

    private Quaternion p1Rotation;

    private Quaternion p2Rotation;

    private Quaternion p3Rotation;

    private Quaternion p4Rotation;

    private MultipleTargetCamera newCamera;

    public float player1Damage = 0f;

    public float player2Damage = 0f;

    public float player3Damage = 0f;

    public float player4Damage = 0f;

    public int p1Kills;

    public int p2Kills;

    public int p3Kills;

    public int p4Kills;

    public delegate void PlayerVictory(int index);

    public static event PlayerVictory PlayerVictoryInfo;

    #endregion Fields

    #region Awake
    public void AwakeLevel(GameManager currentGameManager)
    {
        InitializeVariables();

        storm = FindObjectOfType();
        newCamera = FindObjectOfType();

        GetPlayersInitialLocation();
    }

    private void InitializeVariables()
    {
        isGameStarted = false;
    }

    #endregion Awake

    #region Start
    private void Start()
    {
        SetPlayersTeams(); //Set proper teams according to game team mode
        DeactivateAllPlayers();
        gamePlayUI.StartPreGame();  //Triggers StartRound() + Trigger UI Panel check GamePlayUI.cs for more details --- 

        DashAbility.damageDealtInfo += PlayerDamageDealtHandle;
        Projectile.damageDealtInfo += PlayerDamageDealtHandle;
        ProjectileMergeExplosion.damageDealtInfo += PlayerDamageDealtHandle;
    }

    void PlayerDamageDealtHandle(int index, float amount)
    {
        switch (index)
        {
            case 1:
                player1Damage += amount;
                break;

            case 2:
                player2Damage += amount;
                break;

            case 3:
                player3Damage += amount;
                break;

            case 4:
                player4Damage += amount;
                break;

            default:
                break;
        }
    }

    public void StartRound()
    {

        if (GameManager.managerInstance.p1Active)
        {
            player1Prefab.SetActive(true);
        }

        if (GameManager.managerInstance.p2Active)
        {
            player2Prefab.SetActive(true);
        }

        if (GameManager.managerInstance.p3Active)
        {
            player3Prefab.SetActive(true);
        }

        if (GameManager.managerInstance.p4Active)
        {
            player4Prefab.SetActive(true);
        }

        SetPlayersInitialLocation();
        newCamera.StartCamera();
        GetPlayersInSession();
        currentRound++;

            if (currentRound > 1)
            {
                storm.RestartStorm();
            }
        isGameStarted = true;
    }

    private void GetPlayersInSession()
    {
        activePlayers = 0;
        playerCharacters.Clear();
        foreach (var item in GameObject.FindGameObjectsWithTag("Player"))
        {
            playerCharacters.Add(item);
            playersTransform.Add(item.transform);
            activePlayers++;
        }
    }

    #endregion Start       

    private void Update()
    {
        switch (GameManager.managerInstance.teamMode)
        {
            case GameManager.eTeamMode.SOLO:
                if (isGameStarted && activePlayers <= 1)
                {
                    isGameStarted = false;
                    EndRound();
                }
                break;

            case GameManager.eTeamMode.DOUBLES:
                if (isGameStarted && (redTeamPlayers == 0 || blueTeamPlayers == 0))
                {
                    isGameStarted = false;
                    EndRound();
                }
                break;
        }
    }

    private void EndRound()
    {
        isGameStarted = false;

        switch (GameManager.managerInstance.teamMode)
        {
            case GameManager.eTeamMode.SOLO:
                if (playerCharacters.Count == 0)
                {
                    PlayerVictoryInfo(0);
                }

                else if (playerCharacters[0].GetComponent().playerIndex.ToString() == "One")
                {
                    playerOneScore++;
                    PlayerVictoryInfo(1);
                }
                else if (playerCharacters[0].GetComponent().playerIndex.ToString() == "Two")
                {
                    playerTwoScore++;
                    PlayerVictoryInfo(2);
                }
                else if (playerCharacters[0].GetComponent().playerIndex.ToString() == "Three")
                {
                    playerThreeScore++;
                    PlayerVictoryInfo(3);
                }
                else if (playerCharacters[0].GetComponent().playerIndex.ToString() == "Four")
                {
                    playerFourScore++;
                    PlayerVictoryInfo(4);
                }
                break;

            case GameManager.eTeamMode.DOUBLES:
                if (playerCharacters.Count == 0)
                {
                    PlayerVictoryInfo(0);
                }

                else if (redTeamPlayers == 0)
                {
                    blueTeamScore++;
                }
                else if (blueTeamScore == 0)
                {
                    redTeamScore++;
                }
                break;
        }

        storm.StopStorm();
        CheckRoundEnd();
    }

    private void CheckRoundEnd()
    {
        switch (GameManager.managerInstance.teamMode)
        {
            case GameManager.eTeamMode.SOLO:
                if (playerOneScore >= GameManager.managerInstance.maxNumberOfRounds)
                {
                    EndGame(1);
                }
                else if (playerTwoScore >= GameManager.managerInstance.maxNumberOfRounds)
                {
                    EndGame(2);
                }
                else if (playerThreeScore >= GameManager.managerInstance.maxNumberOfRounds)
                {
                    EndGame(3);
                }
                else if (playerFourScore >= GameManager.managerInstance.maxNumberOfRounds)
                {
                    EndGame(4);
                }
                else
                {
                    EndGame(0);  
                    DeactivateAllPlayers();
                }
                break;

            case GameManager.eTeamMode.DOUBLES:
                if (blueTeamScore >= GameManager.managerInstance.maxNumberOfRounds)
                {
                    blueTeamScore++;
                    EndGame(1);
                }
                else if (redTeamScore >= GameManager.managerInstance.maxNumberOfRounds)
                {
                    redTeamScore++;
                    EndGame(2);
                }
                break;
        }
    }

    private void EndGame(int winnerPlayer)
    {
        GameManager.managerInstance.FinishMatch();

        switch (GameManager.managerInstance.teamMode)
        {
            case GameManager.eTeamMode.SOLO:
                switch (winnerPlayer)
                {
                    case 1:
                        player1Prefab.GetComponent().SetStateTo(PlayerStates.PossibleStates.IsWinning);
                        player1Prefab.transform.rotation = new Quaternion(0, 260, 0, 0);
                        break;

                    case 2:
                        player2Prefab.GetComponent().SetStateTo(PlayerStates.PossibleStates.IsWinning);
                        player2Prefab.transform.rotation = new Quaternion(0, 260, 0, 0);
                        break;

                    case 3:
                        player3Prefab.GetComponent().SetStateTo(PlayerStates.PossibleStates.IsWinning);
                        player3Prefab.transform.rotation = new Quaternion(0, 260, 0, 0);
                        break;

                    case 4:
                        player4Prefab.GetComponent().SetStateTo(PlayerStates.PossibleStates.IsWinning);
                        player4Prefab.transform.rotation = new Quaternion(0, 260, 0, 0);
                        break;
                }
                break;

            case GameManager.eTeamMode.DOUBLES:
                switch (winnerPlayer)
                {

                    case 1:
                        player1Prefab.GetComponent().SetStateTo(PlayerStates.PossibleStates.IsWinning);
                        player1Prefab.transform.rotation = new Quaternion(0, 260, 0, 0);
                        player2Prefab.GetComponent().SetStateTo(PlayerStates.PossibleStates.IsWinning);
                        player2Prefab.transform.rotation = new Quaternion(0, 260, 0, 0);
                        break;

                    case 2:
                        player3Prefab.GetComponent().SetStateTo(PlayerStates.PossibleStates.IsWinning);
                        player3Prefab.transform.rotation = new Quaternion(0, 260, 0, 0);
                        player4Prefab.GetComponent().SetStateTo(PlayerStates.PossibleStates.IsWinning);
                        player4Prefab.transform.rotation = new Quaternion(0, 260, 0, 0);
                        break;
                }
                 break;
        }

        storm.StopStorm();
        gamePlayUI.StartGameOver(winnerPlayer);
    }

    public void PlayerKilled(GameObject killedPlayer, int instigator) {

        switch (GameManager.managerInstance.teamMode)
        {
            case GameManager.eTeamMode.SOLO:
                activePlayers--;
                break;
            case GameManager.eTeamMode.DOUBLES:
                if (killedPlayer.GetComponentInParent().inTeam == PlayerStates.Team.Team1)
                {
                    blueTeamPlayers--;
                }
                else if (killedPlayer.GetComponentInParent().inTeam == PlayerStates.Team.Team2)
                {
                    redTeamPlayers--;
                }
                break;
        }
        playerCharacters.Remove(killedPlayer);


        if (instigator == 1)
        {
            p1Kills++;
        }
        else if (instigator == 2)
        {
            p2Kills++;
        }
        else if (instigator == 3)
        {
            p3Kills++;
        }
        else if (instigator == 4)
        {
            p4Kills++;
        }

        killedPlayer.GetComponent().SetStateTo(PlayerStates.PossibleStates.IsDead); // Here a bunch of other Dead Stuff gets set as well       
        newCamera.RemovePlayer(killedPlayer);

    }

    public void SetPlayersTeams()
    {
        switch (GameManager.managerInstance.teamMode)
        {
            case GameManager.eTeamMode.SOLO:
                player1Prefab.GetComponentInParent().inTeam = PlayerStates.Team.Team1;
                player2Prefab.GetComponentInParent().inTeam = PlayerStates.Team.Team2;
                player3Prefab.GetComponentInParent().inTeam = PlayerStates.Team.Team3;
                player4Prefab.GetComponentInParent().inTeam = PlayerStates.Team.Team4;
                break;

            case GameManager.eTeamMode.DOUBLES:
                player1Prefab.GetComponentInParent().inTeam = PlayerStates.Team.Team1;
                player2Prefab.GetComponentInParent().inTeam = PlayerStates.Team.Team1;
                player3Prefab.GetComponentInParent().inTeam = PlayerStates.Team.Team2;
                player4Prefab.GetComponentInParent().inTeam = PlayerStates.Team.Team2;
                redTeamPlayers = 2;
                blueTeamPlayers = 2;
                break;
        }
    }

    public void DeactivateAllPlayers()
    {
        player1Prefab.SetActive(false);
        player2Prefab.SetActive(false);
        player3Prefab.SetActive(false);
        player4Prefab.SetActive(false);
    }

    private void GetPlayersInitialLocation()
    {
        p1Position = player1Prefab.transform.position;
        p2Position = player2Prefab.transform.position;
        p3Position = player3Prefab.transform.position;
        p4Position = player4Prefab.transform.position;
        p1Rotation = player1Prefab.transform.rotation;
        p2Rotation = player2Prefab.transform.rotation;
        p3Rotation = player3Prefab.transform.rotation;
        p4Rotation = player4Prefab.transform.rotation;
    }

    private void SetPlayersInitialLocation()
    {
        player1Prefab.transform.position = p1Position;
        player1Prefab.transform.rotation = p1Rotation;
        player2Prefab.transform.position = p2Position;
        player2Prefab.transform.rotation = p2Rotation;
        player3Prefab.transform.position = p3Position;
        player3Prefab.transform.rotation = p3Rotation;
        player4Prefab.transform.position = p4Position;
        player4Prefab.transform.rotation = p4Rotation;
    }
    public void PauseGame()
    {
        Time.timeScale = 0f;
        gamePlayUI.ShowPauseMenu();
    }

    public void UnpauseGame()
    {
        Time.timeScale = 1f;
        gamePlayUI.HidePauseMenu();
    }
}





UI and Menus


I implemented and animated all the Menus and In-game UI elements. My focus was to provide good visual feedbacks and game feeling experience.




Gameplay UI



using UnityEngine;
using UnityEngine.UI;

public class GamePlayUI : MonoBehaviour
{

    #region Fields
    [SerializeField]
    private GamePlayHandler gamePlayHandler;

    [SerializeField, Header("Pre-Game")]
    private GameObject preCounterPanel;

    [SerializeField]
    private Text preGameCounterText;

    [SerializeField, Header("Round Over Screen")]
    private GameObject nextRoundPanel;

    [SerializeField]
    private Text p1KillScore;

    [SerializeField]
    private Text p2KillScore;

    [SerializeField]
    private Text p3KillScore;

    [SerializeField]
    private Text p4KillScore;

    [SerializeField]
    private Text p1DamageScore;

    [SerializeField]
    private Text p2DamageScore;

    [SerializeField]
    private Text p3DamageScore;

    [SerializeField]
    private Text p4DamageScore;

    [SerializeField]
    private Text nextRoundTimer;

    [SerializeField, Header("Game Over Screen")]
    private GameObject gameOverPanel;

    [SerializeField]
    private Text winner;

    [SerializeField]
    private float timeToShowEndScreen = 3f;

    [SerializeField]
    private Sprite p1WinnerPortrait;

    [SerializeField]
    private Sprite p2WinnerPortrait;

    [SerializeField]
    private Sprite p3WinnerPortrait;

    [SerializeField]
    private Sprite p4WinnerPortrait;

    [SerializeField]
    private Sprite blueTeamWinnerPortrait;

    [SerializeField]
    private Sprite redTeamWinnerPortrait;

    [SerializeField]
    private Sprite drawPortrait;

    [SerializeField]
    private Sprite winnerTextImage;

    [SerializeField]
    private Sprite drawTextImage;

    [SerializeField]
    private Image winnerPortrait;

    [SerializeField]
    private Image resultScreenImage;

    private bool preGameTimerStarted = false;

    private bool nextRoundTimerStarted = false;

    private float uiNextGameCounter;

    [SerializeField, Header("Wizard Warnings")]
    private GameObject wizardWarning;

    [SerializeField]
    private GameObject wizardWarning2;

    [SerializeField, Header("Pause Menu")]
    private GameObject pauseMenu;

    private int warningsListIndex;

    private int preGameCounterIndex;

    private float soundCounterTimer = 0.9f;

    private bool matchStarted;

    #endregion Fields

    public void StartPreGame() //Set Countdown condition true
    {
        Invoke("CountdownBegin", 1f);
    }

    public void CountdownBegin()
    {
        preCounterPanel.SetActive(true);
        AudioManager.instance.Play("GameStart_01");
        Invoke("CountdownEnd", 2.5f);
    }

    public void CountdownEnd()
    {
        gamePlayHandler.StartRound();
    }

    public void StartRoundOver()
    {
        uiNextGameCounter = gamePlayHandler.nextGameCounter;
        nextRoundPanel.SetActive(true);
        nextRoundTimerStarted = true;       //Start Timer
    }

    public void StartGameOver(int winnerPlayer)
    {

        switch (GameManager.managerInstance.teamMode)
        {
            case GameManager.eTeamMode.SOLO:
                if (winnerPlayer == 0)
                {
                    winnerPortrait.sprite = drawPortrait;
                    resultScreenImage.sprite = drawTextImage;
                }
                else if (winnerPlayer == 1)
                {
                    winnerPortrait.sprite = p1WinnerPortrait;
                    resultScreenImage.sprite = winnerTextImage;
                }
                else if (winnerPlayer == 2)
                {
                    winnerPortrait.sprite = p2WinnerPortrait;
                    resultScreenImage.sprite = winnerTextImage;
                }
                else if (winnerPlayer == 3)
                {
                    winnerPortrait.sprite = p3WinnerPortrait;
                    resultScreenImage.sprite = winnerTextImage;
                }
                else if (winnerPlayer == 4)
                {
                    winnerPortrait.sprite = p4WinnerPortrait;
                    resultScreenImage.sprite = winnerTextImage;
                }
                break;
                
            case GameManager.eTeamMode.DOUBLES:
                if (winnerPlayer == 0)
                {
                    winnerPortrait.sprite = drawPortrait;
                    resultScreenImage.sprite = drawTextImage;
                }
                else if (winnerPlayer == 1)
                {
                    winnerPortrait.sprite = blueTeamWinnerPortrait;
                    resultScreenImage.sprite = winnerTextImage;
                }
                else if (winnerPlayer == 2)
                {
                    winnerPortrait.sprite = redTeamWinnerPortrait;
                    resultScreenImage.sprite = winnerTextImage;
                }
                break;
        }

        p1KillScore.text = gamePlayHandler.p1Kills.ToString();
        p2KillScore.text = gamePlayHandler.p2Kills.ToString();
        p3KillScore.text = gamePlayHandler.p3Kills.ToString();
        p4KillScore.text = gamePlayHandler.p4Kills.ToString();

        p1DamageScore.text = gamePlayHandler.player1Damage.ToString("0") + "hp";
        p2DamageScore.text = gamePlayHandler.player2Damage.ToString("0") + "hp";
        p3DamageScore.text = gamePlayHandler.player3Damage.ToString("0") + "hp";
        p4DamageScore.text = gamePlayHandler.player4Damage.ToString("0") + "hp";

        Invoke("ShowEndScreen", timeToShowEndScreen);
    }

    private void ShowEndScreen()
    {
        gameOverPanel.SetActive(true);
        GetComponent().DeactivateAllPlayers();
    }

    private void Update()
    {
        NextRoundTimer();
    }

    private void NextRoundTimer()
    {
        if (nextRoundTimerStarted)
        {
            uiNextGameCounter -= Time.deltaTime;
            nextRoundTimer.text = (uiNextGameCounter).ToString("0");
            if (uiNextGameCounter < 0)
            {
                nextRoundTimerStarted = false;
                nextRoundPanel.SetActive(false);
                gamePlayHandler.StartRound();
            }
        }
    }

    public void ShowWizardWarning01()
    {
        wizardWarning.SetActive(true);
    }

    public void DeactivateWizard()
    {
        wizardWarning.SetActive(false);
    }

    public void ShowWizardWarning02()
    {
        wizardWarning2.SetActive(true);
    }

    public void DeactivateWizard2()
    {
        wizardWarning2.SetActive(false);
    }

    public void ShowPauseMenu()
    {
        pauseMenu.SetActive(true);
    }

    public void HidePauseMenu()
    {
        pauseMenu.SetActive(false);
    }
}






Sound Design


I selected, edited and implemented sound effects and soundtracks in the game. The Audio Manager is the component responsible for handling all the sounds in the game. Watching an Brackeys tutorial I built the script base and created my own custom methods. It has functions to play, stop and randomize the audio pitch.




Audio Manager



using UnityEngine;
using UnityEngine;
using System;

public class AudioManager : MonoBehaviour
{

    public Sound[] singleSounds;

    public static AudioManager instance;

    void Awake()
    {

        if (instance == null)
        {
            instance = this;
        }
        else
        {
            Destroy(gameObject);
            return;
        }

        DontDestroyOnLoad(gameObject);

        foreach (Sound s in singleSounds)
        {
            s.source = gameObject.AddComponent();
            s.source.clip = s.clip;

            s.source.volume = s.volume;
            s.source.pitch = s.pitch;
            s.source.loop = s.loop;
        }
    }


    public void Play(string name)
    {
        Sound s = Array.Find(singleSounds, sound => sound.name == name);
        if (s == null)
        {
            Debug.LogWarning("Sound: " + name + " not found! Miss typo?");
            return;
        }
        s.source.Play();
    }

    public void PlayWithPitch(string name, float newPitch)
    {
        Sound s = Array.Find(singleSounds, sound => sound.name == name);
        if (s == null)
        {
            Debug.LogWarning("Sound: " + name + " is missing");
            return;
        }
        s.source.pitch = Mathf.Lerp(1f, 2f, (newPitch / 10));
        s.source.Play();

    }

    public void PlayWithRandomPitch(string name)
    {
        Sound s = Array.Find(singleSounds, sound => sound.name == name);
        if (s == null)
        {
            Debug.LogWarning("Sound: " + name + " is missing");
            return;
        }
        s.source.pitch = UnityEngine.Random.Range(0.75f, 2f);
        s.source.Play();

    }

    public void SetPitch(string name, float newPitch)
    {
        Sound s = Array.Find(singleSounds, sound => sound.name == name);
        if (s == null)
        {
            Debug.LogWarning("Sound: " + name + " is missing");
            return;
        }
        s.source.pitch = newPitch;
        //Debug.Log(newPitch);
    }

    public void Stop(string name)
    {
        Sound s = Array.Find(singleSounds, sound => sound.name == name);
        if (s == null)
        {
            Debug.LogWarning("Sound: " + name + " is missing");
            return;
        }
        s.source.Stop();
    }
}