boredom. / Assets / Scripts / PlayerController.cs
PlayerController.cs
Raw
using DG.Tweening;
using System;
using System.Collections;
using TMPro;
using UnityEngine;
using UnityEngine.UI;

public class PlayerController : MonoBehaviour {

    [Header("References")]
    [SerializeField] private GameObject arrow;
    private Vector3 arrowTarget;
    private AudioManager audioManager;
    private TaskManager taskManager;
    private Rigidbody rb;
    private Animator animator;

    [Header("Camera")]
    //[Range(0f, 179f)][SerializeField] float camBaseFov;
    //[Range(0f, 179f)][SerializeField] float camFatigueFov;
    //[Range(0f, 179f)][SerializeField] float camDopamineFov;
    //need coroutine for allat

    [Header("Mechanics")]
    private bool[] mechanicStatuses;

    [Header("Movement")]
    [SerializeField] private float baseMoveSpeed;
    [SerializeField] private float flipTime;
    private float horizontalInput;
    private float verticalInput;
    private float moveSpeed;

    [Header("Boredom")]
    [SerializeField] private float initialBoredom;
    [SerializeField] private float boredomMax;
    [SerializeField] private float boredomRecoveryRate;
    [Range(0f, 1f)][SerializeField] private float boredomFatigueThreshold; //ex: if this is 0.3, then when under 30% of boredom, you get fatigued
    [SerializeField] private float fatigueSpeedModifier;
    [Range(0f, 1f)][SerializeField] private float boredomDopamineThreshold; //ex: if this is 0.7, then when above 70% of boredom, you get fatigued
    [SerializeField] private float dopamineSpeedModifier;
    //[SerializeField] private float dopamineRushSpeedMult; 
    // [SerializeField] private float dopamineRushDuration;
    [SerializeField] private float boredomMultiplier;
    [SerializeField] private float flashWaitDuration;
    [SerializeField] private GameObject meterReference;
    [SerializeField] private Gradient meterGradient;
    [SerializeField] private TMP_Text boredomText;
    private Image meter;
    private Coroutine boredomCoroutine;
    private float boredom;
    private bool isAnimatingBoredom;
    private Tweener fillTweener;
    private Coroutine flashMeterCoroutine;
    //float dopamineAmount;

    [Header("Phone")]
    private bool hasPhoneOut;

    [Header("Interactables")]
    [SerializeField] private float interactRadius;
    [SerializeField] private LayerMask interactMask;
    private Color startColor;

    [Header("Interact Icon")]
    [SerializeField] private SpriteRenderer interactKeyIcon;
    [SerializeField] private float iconFadeDuration;
    [SerializeField] private float iconAnimScaleMultiplier;
    [SerializeField] private float iconAnimScaleDuration;
    private Vector2 iconStartScale;
    private Tweener keyIconTween;
    private bool keyIconVisible;

    [Header("Keybinds")]
    [SerializeField] private KeyCode interactKey;
    [SerializeField] private KeyCode phoneKey;

    private void Start() {

        arrowTarget = Vector3.zero;
        SetArrowVisible(true);
        audioManager = FindObjectOfType<AudioManager>();
        taskManager = FindObjectOfType<TaskManager>();
        rb = GetComponent<Rigidbody>();
        animator = GetComponent<Animator>();
        meter = meterReference.GetComponent<Image>();

        moveSpeed = baseMoveSpeed;

        // default all mechanics to true
        mechanicStatuses = new bool[Enum.GetValues(typeof(MechanicType)).Length];

        foreach (MechanicType mechanicType in Enum.GetValues(typeof(MechanicType)))
            mechanicStatuses[(int) mechanicType] = true;

        boredom = initialBoredom;
        boredomText.text = $"{boredom}";
        meter.fillAmount = boredom / 100f;
        meter.color = meterGradient.Evaluate(boredom - boredomFatigueThreshold);
        StartBoredomTick();

        startColor = interactKeyIcon.color;
        interactKeyIcon.gameObject.SetActive(false);
        interactKeyIcon.color = Color.clear; // set to clear for fade in

        iconStartScale = interactKeyIcon.transform.localScale;

    }

    private void Update() {

        /* PHONE */
        if (Input.GetKeyDown(phoneKey) && mechanicStatuses[(int) MechanicType.Movement]) {

            hasPhoneOut = true;
            ResetAnimations();
            animator.SetBool(horizontalInput >= 0f ? "isPhoneOutRight" : "isPhoneOutLeft", true); // moving right or standing still, animation faces right, else left

        }
        /* if (Input.GetKey(phoneKey))
             dopamineAmount += Time.deltaTime;*/
        else if (Input.GetKeyUp(phoneKey) || !mechanicStatuses[(int) MechanicType.Movement]) {
            //StartCoroutine(DopamineRush(dopamineAmount));
            hasPhoneOut = false;
            ResetAnimations();

        }

        if (hasPhoneOut) {

            audioManager.PlaySound(AudioManager.GameSoundEffectType.PhoneLoop);
            HideInteractKeyIcon();
            return;

        }

        /* MOVEMENT */
        horizontalInput = Input.GetAxisRaw("Horizontal");
        verticalInput = Input.GetAxisRaw("Vertical");

        if (verticalInput != 0 || horizontalInput != 0)
            audioManager.PlaySound(AudioManager.GameSoundEffectType.WalkLoop);

        if (!hasPhoneOut && mechanicStatuses[(int) MechanicType.Movement]) {

            // vertical movement gets priority
            if (verticalInput > 0f) {

                ResetAnimations();
                animator.SetBool("isWalkingForward", true);

            } else if (verticalInput < 0f) {

                ResetAnimations();
                animator.SetBool("isWalkingBack", true);

            } else if (horizontalInput < 0f) {

                ResetAnimations();
                animator.SetBool("isWalkingLeft", true);

            } else if (horizontalInput > 0f) {

                ResetAnimations();
                animator.SetBool("isWalkingRight", true);

            } else {

                ResetAnimations();

            }
        }

        /* INTERACTABLES */
        Interactable interactable = null;

        foreach (Collider obj in Physics.OverlapSphere(transform.position, interactRadius, interactMask)) {

            interactable = obj?.GetComponent<Interactable>();

            if (interactable != null && interactable.IsInteractable()) // make sure it's interactable
                break;

        }

        //Physics2D.OverlapCircle(transform.position, interactRadius, interactMask)?.GetComponent<Interactable>(); // get interactable

        if (interactable != null && interactable.IsInteractable()) { // in range and is interactable

            if (Input.GetKeyDown(interactKey)) { // interact key pressed

                if (interactable is TaskInteractable)
                    interactable.SetInteractable(false);

                interactable.Interact();

                interactKeyIcon.transform.DOScale(iconStartScale * iconAnimScaleMultiplier, iconAnimScaleDuration / 2f).OnComplete(() => interactKeyIcon.transform.DOScale(iconStartScale, iconAnimScaleDuration / 2f));

            } else if (interactable is TaskInteractable && interactable.IsInteractable() && !taskManager.IsTaskStarted()) { // task interactable, has current task

                ShowInteractKeyIcon();

            } else if (interactable is not TaskInteractable && interactable.IsInteractable()) { // anything other than task interactable, but key not pressed

                ShowInteractKeyIcon();

            }
        } else {

            HideInteractKeyIcon(); // if no interactables in range, hide interact key icon

        }

        /* PAUSING */
        if (Input.GetKeyDown(KeyCode.Escape))
            taskManager.TogglePause();

        /* ANIMATIONS */
        if (isAnimatingBoredom && !hasPhoneOut)
            isAnimatingBoredom = false;

        /* ARROW */
        /* Vector3 dir = arrowTarget - arrow.transform.position;
         Quaternion rot = Quaternion.LookRotation(dir, Vector3.up);
         arrow.transform.rotation = Quaternion.Euler(0, rot.eulerAngles.y, 0);*/
        arrow.transform.LookAt(arrowTarget);

    }


    private void FixedUpdate() {

        if (mechanicStatuses[(int) MechanicType.Movement] && !hasPhoneOut)
            rb.velocity = new Vector3(horizontalInput, 0, verticalInput).normalized * moveSpeed;
        else
            rb.velocity = Vector3.zero;

    }

    private void ResetAnimations() {

        animator.SetBool("isWalkingForward", false);
        animator.SetBool("isWalkingBack", false);
        animator.SetBool("isWalkingRight", false);
        animator.SetBool("isWalkingLeft", false);
        animator.SetBool("isPhoneOutLeft", false);
        animator.SetBool("isPhoneOutRight", false);

    }

    private IEnumerator TickBoredom() {

        while (true) {

            float prevBoredom = boredom;

            if (hasPhoneOut) // recover boredom
                boredom++;
            else // decay boredom
                boredom--;

            if (boredom > boredomMax) // clamp boredom
                boredom = boredomMax;

            if (boredom <= 0f)
                taskManager.OnGameLoss();

            if (boredom <= boredomMax * boredomFatigueThreshold) { // modify move speed based on boredom

                moveSpeed = baseMoveSpeed * fatigueSpeedModifier;
                //cam.fieldOfView = camFatigueFov;

                if (flashMeterCoroutine == null)
                    flashMeterCoroutine = StartCoroutine(FlashMeter()); // start flashing meter

            } else if (boredom >= boredomMax * boredomDopamineThreshold) {

                moveSpeed = baseMoveSpeed * dopamineSpeedModifier;
                //cam.fieldOfView = camDopamineFov;

            } else if (boredom < boredomMax * boredomDopamineThreshold && boredom > boredomMax * boredomFatigueThreshold) {

                moveSpeed = baseMoveSpeed;
                //cam.fieldOfView = camBaseFov;

                if (flashMeterCoroutine != null) {

                    StopCoroutine(flashMeterCoroutine); // stop flashing meter
                    flashMeterCoroutine = null;

                }
            }

            isAnimatingBoredom = true; // flag to prevent spamming space (taking phone out) while moving from ticking boredom up (op)

            if (fillTweener != null && fillTweener.IsActive()) fillTweener.Kill(); // kill previous tween if still active

            float boredomP = boredom / 100f;
            float duration = Math.Abs(prevBoredom - boredom) * boredomMultiplier; // uses difference in boredom to make animation smooth rather than incremental
            boredomText.text = boredom + "";
            meter.fillAmount = boredomP;
            //fillTweener = meter.DOFillAmount(boredomP, duration).OnStepComplete(() => print(meter.fillAmount)); // for smoothing, doesn't work when it hits 100

            if (flashMeterCoroutine == null) // don't change color if flashing
                meter.DOColor(meterGradient.Evaluate(boredomP - boredomFatigueThreshold), duration);

            //BUG: spamming space (taking phone out) while moving ticks boredom up (op)

            if (hasPhoneOut)
                yield return new WaitForSeconds(1f / boredomRecoveryRate);
            else
                yield return new WaitForSeconds(1f / taskManager.GetBoredomDecayRate());

        }
    }

    public void ShowInteractKeyIcon() {

        if (keyIconVisible) return;

        if (keyIconTween != null && keyIconTween.IsActive()) keyIconTween.Kill();

        keyIconVisible = true;
        interactKeyIcon.gameObject.SetActive(true);
        interactKeyIcon.DOColor(startColor, iconFadeDuration);

    }

    public void HideInteractKeyIcon() {

        if (!keyIconVisible) return;

        keyIconVisible = false;
        keyIconTween = interactKeyIcon.DOColor(Color.clear, iconFadeDuration).OnComplete(() => interactKeyIcon.gameObject.SetActive(false));

    }

    public void SetMechanicStatus(MechanicType mechanicType, bool status) {

        if (taskManager.IsGameComplete()) { // disable mechanic if game is complete

            mechanicStatuses[(int) mechanicType] = false;
            return;

        }

        mechanicStatuses[(int) mechanicType] = status;

    }

    public void StartBoredomTick() { boredomCoroutine = StartCoroutine(TickBoredom()); }

    public void PauseBoredomTick() { if (boredomCoroutine != null) StopCoroutine(boredomCoroutine); }

    public void SetArrowVisible(bool t) { arrow.SetActive(t); }

    public void PointArrow(Vector3 position) => arrowTarget = position;

    private IEnumerator FlashMeter() {

        while (true) {

            meter.color = Color.clear;
            yield return new WaitForSeconds(flashWaitDuration);
            meter.color = meterGradient.Evaluate(boredom / 100f - boredomFatigueThreshold);
            yield return new WaitForSeconds(flashWaitDuration);

        }
    }

    public void StopMeterFlash() {

        if (flashMeterCoroutine != null) StopCoroutine(flashMeterCoroutine);

    }
}