Code Samples

Code Samples

Character Controller

Neon Breakline

Full code for the Character Controller in Neon Breakline (Careful its around 700 lines long)

Click to expand

using System.Collections;
using Unity.Cinemachine;
using UnityEngine;
using UnityEngine.InputSystem;

public class PlayerController : MonoBehaviour
{
    //RULE: the player ALWAYS moves forward
    //There is NO input that stops or reverses movement

    public static PlayerController Instance;

    [Header("Player Stats")]
    public bool advance = true;
    public bool levelFinished = false;
    public int maxHealth = 3;
    public int currentHealth;
    [HideInInspector] public enum SpeedTier { Base, Lv1, Lv2, Lv3 }
    public SpeedTier speedTier = SpeedTier.Base;
    public bool playerDied = false;

    [Header("Forward Movement Settings")]
    public float baseSpeed = 5f;
    public float lv1Speed = 7f;
    public float lv2Speed = 9f;
    public float lv3Speed = 12f;

    public float baseAnimSpeed = .8f;
    public float lv1AnimSpeed = 1f;
    public float lv2AnimSpeed = 1.1f;
    public float lv3AnimSpeed = 1.2f;

    [Header("Jump Settings")]
    public float jumpForce = 8f;
    public Transform groundCheck;
    [SerializeField] private float groundCheckY = 0.2f;
    [SerializeField] private float groundCheckX = 0.5f;
    public LayerMask groundLayer;
    public bool isGrounded;
    
    [Header("Jump Assist")]
    [SerializeField] private float coyoteTime = 0.1f;
    [SerializeField] private float jumpBufferTime = 0.1f;
    private float coyoteTimeCounter;
    private float jumpBufferCounter;

    [Header("Slide Settings")]
    public float slideDuration = 1f;

    [Header("Attack Settings")]
    public GameObject attackHitbox;
    public float attackCooldown = 0.25f;
    private float lastAttackTime;
    public GameObject slashFX;
    [SerializeField] private float attackBufferTime = .1f;
    private float attackBufferCounter;

    [Header("Parry Settings")]
    public GameObject parryHitbox;
    public float parryAnimDuration = 0.12f;
    public float parrySpeedMultiplier = 0.5f;

    [Header("Pushback Settings")]
    public float pushbackForce = 6f;
    public float pushbackDuration = 0.2f;
    private Coroutine pushbackCoroutine;

    [Header("Hitstop Settings")]
    public float hitstopDuration = 0.07f;
    public float parryHitstopDuration = 0.1f;
    public bool isInHitstop = false;

    [Header("Screen Shake Settings")]
    public CinemachineImpulseSource wallHitImpulseSource;
    public CinemachineImpulseSource parryImpulseSource;
    public CinemachineImpulseSource attackImpulseSource;

    [Header("Camera Settings")]
    public float baseFOV = 40f;
    public float lv1FOV = 41f;
    public float lv2FOV = 43f;
    public float lv3FOV = 45f;

    [Header("Particles")]
    public ParticleSystem parryParticles;
    public ParticleSystem jumpParticles;
    public ParticleSystem bloodParticles;
    

    #region Input Actions
    private PlayerInput playerInput;
    private InputAction jumpAction;
    private InputAction attackAction;
    private InputAction parryAction;
    private InputAction crouchAction;
#endregion
    #region Input Bools
    public bool JumpJustPressed { get; private set; }
    public bool JumpBeingHeld { get; private set; }
    public bool JumpReleased { get; private set; }
    public bool AttackInput { get; private set; }
    public bool ParryInput { get; private set; }
    public bool CrouchInput { get; private set; }
    #endregion


    [HideInInspector] public Rigidbody2D rb;
    [HideInInspector] public Animator anim;
    [HideInInspector] public AudioSource audsrc;
    [HideInInspector] public PlayerStates pState;
    [HideInInspector] public AudioManager audioManager;


    void Awake()
    {

        if (Instance != null && Instance != this)
        {
            Destroy(gameObject);
        }
        else
        {
            Instance = this;
        }
        playerInput = GetComponent();
        SetupInputActions();

        rb = GetComponent();
        anim = GetComponent();
        audsrc = GetComponent();
        pState = GetComponent();
    }

    void Start()
    {
        ToggleCursorOff();
        audioManager = AudioManager.Instance;
        currentHealth = maxHealth;
        PlayerHUDManager.Instance.UpdateHealthUI();
        PlayerHUDManager.Instance.UpdateSpeedTierUI();
    }

    private void SetupInputActions()
    {
        jumpAction = playerInput.actions["Jump"];
        attackAction = playerInput.actions["Attack"];
        parryAction = playerInput.actions["Parry"];
        crouchAction = playerInput.actions["Crouch"];
    }
    public void GetInputs()
    {
        AttackInput = attackAction.WasPressedThisFrame();
        ParryInput = parryAction.WasPressedThisFrame();
        CrouchInput = crouchAction.IsPressed();
        JumpJustPressed = jumpAction.WasPressedThisFrame();
        JumpBeingHeld = jumpAction.IsPressed();
        JumpReleased = jumpAction.WasReleasedThisFrame();
    }

    void Update()
    {
        if (GameManager.Instance.gameIsPaused || levelFinished || playerDied) return;

        CheckGround();
        GetInputs();
        HandleJump();
        Slide();
        HandleAttack();
        Parry();
    }

    void FixedUpdate()
    {
        if (GameManager.Instance.gameIsPaused || levelFinished) return;
        
        if (advance && !playerDied)
        {
            rb.linearVelocity = new Vector2(GetSpeedFromTier(), rb.linearVelocityY);
        }
    }

    private void Jump()
    {
        if (!pState.jumping && !pState.sliding && !pState.parrying)
        {
            pState.jumping = true;
            anim.SetBool("Jumping", true);
            jumpParticles.Play();
            audioManager.PlayRandomJumpSound();
            rb.linearVelocity = new Vector2(rb.linearVelocityX, jumpForce);
        }
    }

    private void HandleJump()
    {
        UpdateCoyoteTime();
        UpdateJumpBuffer();
        TryJump();
    }
    private void UpdateCoyoteTime()
    {
        if (isGrounded)
        {
            coyoteTimeCounter = coyoteTime;
        }
        else
        {
            coyoteTimeCounter -= Time.deltaTime;
        }
    }
    private void UpdateJumpBuffer()
    {
        if (JumpJustPressed)
            jumpBufferCounter = jumpBufferTime;
        else
            jumpBufferCounter -= Time.deltaTime;
    }
    private void TryJump()
    {
        if (jumpBufferCounter > 0f && coyoteTimeCounter > 0f)
        {
            Jump();
            jumpBufferCounter = 0f;
            coyoteTimeCounter = 0f;
        }
    }

    private void Slide()
    {
        if (CrouchInput && isGrounded && !pState.sliding && !pState.attacking && !pState.parrying)
        {
            StartCoroutine(PerformSlide());
        }
    }

    private IEnumerator PerformSlide()
    {
        pState.sliding = true;
        anim.SetTrigger("Slide");
        audioManager.PlaySFX(audioManager.slideSound);
        yield return new WaitForSeconds(slideDuration);
        pState.sliding = false;
    }

    private void HandleAttack()
    {
        UpdateAttackBuffer();
        TryAttack();
    }

    private bool Attack()
    {
        if (Time.time >= lastAttackTime + attackCooldown && !pState.attacking && !pState.sliding && !pState.parrying)
        {
            pState.attacking = true;
            anim.SetTrigger("Attack");
            audioManager.PlayRandomAttackSound();

            lastAttackTime = Time.time;
            return true;
        }

        return false;
    }

    private void UpdateAttackBuffer()
    {
        if (AttackInput)
            attackBufferCounter = attackBufferTime;
        else
            attackBufferCounter -= Time.deltaTime;
    }

    private void TryAttack()
    {
        if (attackBufferCounter > 0f)
        {
            if (Attack())
            {
                attackBufferCounter = 0f;
            }
        }
    }

    private void Parry()
    {
        if (ParryInput && !pState.parrying && !pState.attacking && !pState.sliding && !pState.beingPushedBack)
        {
            StartCoroutine(ParryRoutine());
        }
        
    }

    private IEnumerator ParryRoutine()
    {
        if (pState.beingPushedBack)
            yield break;

        pState.parrying = true;
        anim.SetTrigger("Parry");
        audioManager.PlaySFX(audioManager.parrySound);
        float originalSpeed = GetSpeedFromTier();
        advance = false;
        rb.linearVelocity = new Vector2(originalSpeed * parrySpeedMultiplier, rb.linearVelocityY);
        yield return new WaitForSeconds(parryAnimDuration);
        if (CanAdvance())
        {
            advance = true;
        }
        pState.parrying = false;
    }

    public void OnSuccessfulParry()
    {
        pState.parrying = false;
        anim.SetTrigger("ParrySuccess");
        parryParticles.Play();
        audioManager.PlayRandomParriedSound();
        StartCoroutine(TemporaryInvincibility(0.05f)); // Set Based on animation
        GainSpeedTier();
        StartCoroutine(Hitstop(parryHitstopDuration));
        StartCoroutine(SmallSpeedBoost());
        parryImpulseSource.GenerateImpulse();
    }

    public IEnumerator TemporaryInvincibility(float duration)
    {
        pState.invincible = true;
        yield return new WaitForSeconds(duration);
        pState.invincible = false;
    }

    public IEnumerator SmallSpeedBoost()
    {
        float originalSpeed = GetSpeedFromTier();
        advance = false;
        rb.linearVelocity = new Vector2(originalSpeed * 1.5f, rb.linearVelocityY);
        yield return new WaitForSeconds(0.2f);
        if (CanAdvance())
        {
            advance = true;
        }
    }

    public float GetSpeedFromTier()
    {
        switch (speedTier)
        {
            case SpeedTier.Lv1:
                return lv1Speed;
            case SpeedTier.Lv2:
                return lv2Speed;
            case SpeedTier.Lv3:
                return lv3Speed;
            default:
                return baseSpeed;
        }
    }

    private void CheckGround()
    {
        isGrounded = Physics2D.Raycast(groundCheck.position, Vector2.down, groundCheckY, groundLayer)
            || Physics2D.Raycast(groundCheck.position + new Vector3(groundCheckX, 0, 0), Vector2.down, groundCheckY, groundLayer)
            || Physics2D.Raycast(groundCheck.position + new Vector3(-groundCheckX, 0, 0), Vector2.down, groundCheckY, groundLayer);

        if (isGrounded && rb.linearVelocityY <= 0)
        {
            pState.jumping = false;
            
            anim.SetBool("Jumping", false);
            anim.SetBool("Falling", false);
        }
        else if (!isGrounded)
        {
            anim.SetBool("Falling", true);
        }

    }

    private void OnDrawGizmos()
    {
        if (groundCheck == null) return;

        // Draw the three raycast lines in the Scene view
        Gizmos.color = Color.yellow;

        Vector3 center = groundCheck.position;
        Vector3 right = center + new Vector3(groundCheckX, 0, 0);
        Vector3 left = center + new Vector3(-groundCheckX, 0, 0);

        Gizmos.DrawLine(center, center + Vector3.down * groundCheckY);
        Gizmos.DrawLine(right, right + Vector3.down * groundCheckY);
        Gizmos.DrawLine(left, left + Vector3.down * groundCheckY);
    }

    public void GainSpeedTier()
    {
        if (playerDied) return;
        if (speedTier < SpeedTier.Lv3)
        {
            speedTier++;
            PlayerHUDManager.Instance.UpdateSpeedTierUI();
            audioManager.PlayTierUpPitched(speedTier);
        }
    }
    public void LoseSpeedTier()
    {
        if (playerDied) return;
        if (speedTier > SpeedTier.Base)
        {
            if (pState.invincible) return;

            audioManager.PlayTierDownPitched(speedTier);
            speedTier--;
            PlayerHUDManager.Instance.UpdateSpeedTierUI();
        }
    }

    public void TakeDamage(int damage)
    {
        if (pState.invincible || playerDied) return;
        
        currentHealth -= damage;
        PlayerHUDManager.Instance.UpdateHealthUI();

        if (currentHealth <= 0)
        {
            Die();
        }
    }

    private void Die()
    {
        if (playerDied) return;
        StartCoroutine(DeathRoutine());
    }

    private IEnumerator DeathRoutine()
    {
        audioManager.PlayRandomDeathSound();
        advance = false;
        playerDied = true;
        rb.linearVelocity = new Vector2(0,0);
        anim.Play("PlayerDeath");
        yield return new WaitForSeconds(2f);
        GameManager.Instance.RestartLevelAfterFade();
        
    }

    public void ApplyPushback()
    {
        if (pState.invincible) return;
        
        audioManager.PlaySFX(audioManager.hitWallSound);
        pState.beingPushedBack = true;
        anim.SetTrigger("Hit");
        rb.linearVelocity = Vector2.zero;
        rb.AddForce(new Vector2(-pushbackForce, pushbackForce * 0.5f), ForceMode2D.Impulse);

        LoseSpeedTier();
        wallHitImpulseSource.GenerateImpulse();
        StartCoroutine(Hitstop(hitstopDuration));
        
        if (pushbackCoroutine != null)
        {
            StopCoroutine(pushbackCoroutine);
        }
        pushbackCoroutine = StartCoroutine(ContinueAfterPushback(pushbackDuration));
    }

    public IEnumerator ContinueAfterPushback(float delay)
    {
        advance = false;
        yield return new WaitForSeconds(delay);
        advance = true;
        pState.beingPushedBack = false;
    }
    IEnumerator Hitstop(float time)
    {
        if (isInHitstop)
            yield break;

        isInHitstop = true;

        //float originalTimeScale = Time.timeScale;
        Time.timeScale = 0f;

        yield return new WaitForSecondsRealtime(time);

        if (!GameManager.Instance.gameIsPaused)
        {
            Time.timeScale = /*originalTimeScale*/ 1f;
        }
        isInHitstop = false;
    }

    public void DoHitStop(float time)
    {
        StartCoroutine(Hitstop(time));
    }

    public void DoAttackScreenShake()
    {
        attackImpulseSource.GenerateImpulse();
    }

    public void SpawnSlashFX()
    {
        float randomDir = Random.Range(-4,4);
        Quaternion rotation = Quaternion.Euler(0, 0, randomDir);
        Vector3 spawnPos = transform.position;
        spawnPos.y += Random.Range(-.15f, .15f);

        GameObject slash = Instantiate(slashFX, spawnPos, rotation);

        float randomScale = Random.Range(.9f, 1.1f);
        slash.transform.localScale *= randomScale;
    }

    public void SpawnBloodFX(Vector3 position)
    {
        ParticleSystem particles = Instantiate(bloodParticles, position, Quaternion.identity);
        Destroy(particles.gameObject, particles.main.duration + particles.main.startLifetime.constantMax);
    }


    public void ChangeCameraFov()
    {
        baseFOV = CameraManager.Instance.normalCameraFov;
        switch (speedTier)
        {
            case SpeedTier.Lv1:
                CameraManager.Instance.currentCamera.Lens.FieldOfView = lv1FOV;
                anim.SetFloat("RunSpeed", lv1AnimSpeed);
                break;
            case SpeedTier.Lv2:
                CameraManager.Instance.currentCamera.Lens.FieldOfView = lv2FOV;
                anim.SetFloat("RunSpeed", lv2AnimSpeed);
                break;
            case SpeedTier.Lv3:
                CameraManager.Instance.currentCamera.Lens.FieldOfView = lv3FOV;
                anim.SetFloat("RunSpeed", lv3AnimSpeed);
                break;
            default:
                CameraManager.Instance.currentCamera.Lens.FieldOfView = baseFOV;
                anim.SetFloat("RunSpeed", baseAnimSpeed);
                break;
        }
    }

    public void EnableAttackHitbox()
    {
        attackHitbox.SetActive(true);
    }

    public void DisableAttackHitbox()
    {
        pState.attacking = false;
        attackHitbox.SetActive(false);
    }

    public void EnableParryHitbox()
    {
        parryHitbox.SetActive(true);
    }

    public void DisableParryHitbox()
    {
        pState.parrying = false;
        parryHitbox.SetActive(false);
    }

    public bool CanAdvance()
    {
        return !pState.beingPushedBack; // Add anythyng needed
    }

    public void PlayFootStepSound()
    {
        audioManager.PlayRandomFootStepSound();
    }

    /// UTILITIES

    public void ToggleCursorOn()
    {
        if (playerInput.currentControlScheme == "Controller") return;

        Cursor.lockState = CursorLockMode.None;
        Cursor.visible = true;
    }

    public void ToggleCursorOff()
    {
        Cursor.lockState = CursorLockMode.Locked;
        Cursor.visible = false;
    }

}
        

Game Manager

Neon Breakline

Handles game states and scene changes.

Click to expand

using System;
using System.Collections;
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.SceneManagement;
using UnityEngine.UI;


public class GameManager : MonoBehaviour
{
    public static GameManager Instance { get; private set; }

    [SerializeField] private GameObject pauseMenu;
    [SerializeField] private Button pauseMenuButton;
    public bool gameIsPaused;

    private PlayerInput playerInput;
    private InputAction pauseAction;


    private void Awake()
    {
        if (Instance != null && Instance != this)
        {
            Destroy(gameObject);
        }
        else
        {
            Instance = this;
        }
        Time.timeScale = 1f;

        playerInput = FindFirstObjectByType();
        pauseAction = playerInput.actions["Pause"];
    }

    private void Update()
    {
        if (pauseAction.WasPressedThisFrame())
        {
            TogglePause();
        }

    }

    public void TogglePause()
    {
        if (PlayerController.Instance.pState.inTutorial || PlayerController.Instance.levelFinished) return;

        if (gameIsPaused)
        ResumeGame();
        else
        PauseGame();
    }

    public void PauseGame()
    {
        Time.timeScale = 0f;
        gameIsPaused = true;
        pauseMenu.SetActive(true);
        pauseMenuButton.Select();
    }

    public void ResumeGame()
    {
        Time.timeScale = 1f;
        gameIsPaused = false;
        pauseMenu.SetActive(false);
    }

    public void ExitToMainMenu()
    {
        Time.timeScale = 1f;
        SceneManager.LoadScene("MainMenu");
    }

    public void RestartLevel()
    {
        Time.timeScale = 1f;
        SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex);
    }

}
        

Save Encryption

Neon Breakline

Helped encrypt the save data for Neon Breakline so it couldn't be easily editable.

Click to expand

using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;
using UnityEngine;

public static class EncryptionUtility
{
    private static readonly string key = "ProjectForwardWallaceKey"; //24 chars
    private static readonly string iv = "ProjectForwardIV"; //16 chars

    public static string Encrypt(string plainText)
    {
        using (Aes aes = Aes.Create())
        {
            aes.Key = Encoding.UTF8.GetBytes(key.Substring(0, 24));
            aes.IV = Encoding.UTF8.GetBytes(iv.Substring(0, 16));

            using (MemoryStream ms = new MemoryStream())
            using (CryptoStream cs = new CryptoStream(ms, aes.CreateEncryptor(), CryptoStreamMode.Write))
            using (StreamWriter sw = new StreamWriter(cs))
            {
                sw.Write(plainText);
                sw.Close();
                return Convert.ToBase64String(ms.ToArray());
            }
        }
    }

    public static string Decrypt(string cipherText)
    {
        using (Aes aes = Aes.Create())
        {
            aes.Key = Encoding.UTF8.GetBytes(key.Substring(0, 24));
            aes.IV = Encoding.UTF8.GetBytes(iv.Substring(0, 16));

            byte[] buffer = Convert.FromBase64String(cipherText);

            using (MemoryStream ms = new MemoryStream(Convert.FromBase64String(cipherText)))
            using (CryptoStream cs = new CryptoStream(ms, aes.CreateDecryptor(), CryptoStreamMode.Read))
            using (StreamReader sr = new StreamReader(cs))
            {
                return sr.ReadToEnd();
            }
        }
    }
}
        

Dialogue Cutscene System

Thousand Paths (Metroidvania)

Handled Dialogue Scenes in my Metroidvania Project.

Click to expand

using System.Collections;
using System.Collections.Generic;
using TMPro;
using UnityEditor.Rendering;
using UnityEngine;
using UnityEngine.UI;

public class CutsceneSystem : MonoBehaviour
{
    [Header("Core")]
    public static CutsceneSystem instance;
    public AudioSource audioSource;

    [Header("Dialogue")]
    public bool isInDialogue;
    public DialogueLines[] lines;
    public int currentLine;
    public float ogTextSpeed;
    public float textSpeed;
    [SerializeField][TextArea] private string[] textInfo;

    [Header("UI")]
    public GameObject dialogueBox;
    public TMP_Text currentText;
    public TMP_Text currentNameText;
    public Image characterImage;
    public Image indicator;
    [HideInInspector] public Animator indicatorAnim;

    [Header("CharacterCheck")]
    public bool solveigTalking;
    public bool asaTalking;
    public bool kurohaTalking;
    public bool ichikaTalking;
    public bool kingTalking;
    public bool mayukaTalking;
    public bool mikaelTalking;


    private Queue dialogueQueue;
    private bool isScrolling;

    private void Awake()
    {
        if (instance != null && instance != this)
        {
            Destroy(gameObject);
        }
        else
        {
            instance = this;
        }
        dialogueQueue = new Queue();
        ogTextSpeed = textSpeed;
        indicatorAnim = indicator.GetComponent();
        this.gameObject.SetActive(false);
    }

    public void Update()
    {
        if (!isInDialogue) return;

        if (textSpeed > ogTextSpeed)
        {
            textSpeed = ogTextSpeed;
        }

        if (Input.GetButtonDown("Submit") || Input.GetKeyDown(KeyCode.Space) || Input.GetKeyDown(KeyCode.Mouse0))
        {
            DisplayNextLine();
            if (isScrolling)
            {
                textSpeed /= 7;
            }
        }
        else if (Input.GetButtonUp("Submit") || Input.GetKeyUp(KeyCode.Space) || Input.GetKeyUp(KeyCode.Mouse0))
        {
            textSpeed *= 7;
        }
    }

    public void StartDialogue(DialogueLines[] lines)
    {
        if (isInDialogue) return;

        textSpeed = ogTextSpeed;
        dialogueQueue.Clear();
        foreach (var line in lines)
        {
            if (!PlayerController.Instance.playedDialogueIds.Contains(line.id))
            {
                dialogueQueue.Enqueue(line);

                if (line.deleteAfterPlay)
                {
                    PlayerController.Instance.playedDialogueIds.Add(line.id);
                }
            }
        }
        isInDialogue = true;
        PlayerController.Instance.pState.cantControl = true;
        PlayerController.Instance.rb.velocity = new Vector3(0, 0, 0);
        PlayerController.Instance.ReturnToIdle();
        dialogueBox.SetActive(true);
        DisplayNextLine();
    }

    public void DisplayNextLine()
    {
        if (isScrolling) return;

        indicatorAnim.SetBool("End", false);
        if (dialogueQueue.Count == 0)
        {
            EndDialogue();
            return;
        }
        if (dialogueQueue.Count == 1)
        {
            indicatorAnim.SetBool("End",true);
        }

        var currentLine = dialogueQueue.Dequeue();
        UpdateUI(currentLine);
        PlayDialogueSound(currentLine);
        StartCoroutine(ScrollText(currentLine.dialogueText));
        
        if (currentLine.action.GetPersistentEventCount() > 0)
        {
            currentLine.action.Invoke();
        }
    }

    private void UpdateUI(DialogueLines line)
    {
        if (line.character.ToString() == "NONE")
        {
            currentNameText.text = "";
            characterImage.sprite = line.GetCharacterPortrait();
        }    
        else
        {
            currentNameText.text = line.character.ToString();
            characterImage.sprite = line.GetCharacterPortrait();
        }
    }
    private IEnumerator ScrollText(string text)
    {
        indicatorAnim.SetTrigger("Scrolling");
        isScrolling = true;
        currentText.text = "";

        foreach (char c in text)
        {
            currentText.text += c;
            yield return new WaitForSeconds(textSpeed);
        }

        isScrolling = false;
        if (indicatorAnim.GetBool("End") == false)
        {
            indicatorAnim.SetTrigger("Next");
        }

    }

    private void PlayDialogueSound(DialogueLines line)
    {
        if (audioSource == null) return;

        AudioClip clip = line.GetSoundEffect();
        if (clip != null)
        {
            audioSource.PlayOneShot(clip);
        }
    }

    public void EndDialogue()
    {
        dialogueBox.SetActive(false);
        isInDialogue = false;
        PlayerController.Instance.pState.cantControl = false;
        this.gameObject.SetActive(false);
    }
}
        

Health Controller

Thousand Paths (Metroidvania)

Handled health UI and some other things like the economy HUD.

Click to expand

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

public class HealthController : MonoBehaviour
{
    PlayerController player;

    private GameObject[] sakuraContainer;
    private Image[] sakuraFilled;
    public Transform sakuraParent;
    public GameObject sakuraContainerPrefab;
    public TextMeshProUGUI riceText;
    public TextMeshProUGUI coinsText;
    [SerializeField] public Animator riceCounter;
    [SerializeField] public Animator coinsCounter;
    public bool riceCollected;
    public bool coinsCollected;

    void Start()
    {
        player = PlayerController.Instance;
        sakuraContainer = new GameObject[PlayerController.Instance.maxTotalHealth];
        sakuraFilled = new Image[PlayerController.Instance.maxTotalHealth];

        PlayerController.Instance.onHealthChangedCallback += UpdateSakuraHUD;
        InstantiateSakuraContainers();
        UpdateSakuraHUD();
        UpdateMoneyHUD();
    }

    void SetSakuraContainer()
    {
        for (int i = 0; i < sakuraContainer.Length; i++)
        {
            if (i < PlayerController.Instance.maxHealth)
            {
                sakuraContainer[i].SetActive(true);
            }
            else
            {
                sakuraContainer[i].SetActive(false);
            }
        }
    }

    void SetFilledSakura()
    {
        for (int i = 0; i < sakuraFilled.Length; i++)
        {
            if (i < PlayerController.Instance.Health)
            {
                sakuraFilled[i].GetComponent().SetBool("Filled", true);
            }
            else
            {
                sakuraFilled[i].GetComponent().SetBool("Filled", false);
            }
        }
    }

    void InstantiateSakuraContainers()
    {
        for(int i = 0; i < PlayerController.Instance.maxTotalHealth; i++)
        {
            GameObject temp = Instantiate(sakuraContainerPrefab);
            temp.transform.SetParent(sakuraParent, false);
            sakuraContainer[i] = temp;
            sakuraFilled[i] = temp.transform.Find("Flower").GetComponent();
        }
    }

    void UpdateSakuraHUD()
    {
        SetSakuraContainer();
        SetFilledSakura();
    }

    public void UpdateMoneyHUD()
    {
        if (riceCollected)
        {
            StartCoroutine(RiceCoroutine());
        }
        if (coinsCollected)
        {
            StartCoroutine(CoinsCoroutine());
        }
    }
    public void UpdateMoneyHUDForKatana()
    {
        StartCoroutine(RiceCoroutine());
        StartCoroutine(CoinsCoroutine());
    }

    public IEnumerator RiceCoroutine()
    {
        if (riceCounter.GetBool("Opened") == false)
        {

            riceCollected = false;
            riceCounter.SetTrigger("Open");
            riceCounter.SetBool("Opened", true);
            riceText.text = player.riceOwned.ToString();
            yield return new WaitForSeconds(3f);

            if (!riceCounter.GetBool("Closing"))
            {
                riceCounter.SetBool("Closing", true);
                riceCounter.SetTrigger("Close");
                riceCounter.SetBool("Opened", false);
                yield return new WaitForSeconds(.1f);
                riceCounter.SetBool("Closing", false);
            }
        }
        else
        {
            riceText.text = player.riceOwned.ToString();
        }
    }
    public IEnumerator CoinsCoroutine()
    {
        if (coinsCounter.GetBool("Opened") == false)
        {

            coinsCollected = false;
            coinsCounter.SetTrigger("Open");
            coinsCounter.SetBool("Opened", true);
            coinsText.text = player.coinsOwned.ToString();
            yield return new WaitForSeconds(3f);

            if (!coinsCounter.GetBool("Closing"))
            {
                coinsCounter.SetBool("Closing", true);
                coinsCounter.SetTrigger("Close");
                coinsCounter.SetBool("Opened", false);
                yield return new WaitForSeconds(.1f);
                coinsCounter.SetBool("Closing", false);
            }
        }
        else
        {
            coinsText.text = player.coinsOwned.ToString();
        }
    }

}
        

Professional Experience

NDA

Please check out this Github page to see some of the work I'm doing for a still in development 3D Puzzle Game with a small team.

Click to expand

You can check out some other code samples on my Github Code Portfolio! Here