Lock Puzzle Controller Script

mail@pastecode.io avatar
unknown
plain_text
a year ago
7.5 kB
7
Indexable
Never
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using DG.Tweening;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.EventSystems;


public class LockerController : MonoBehaviour, IPointerDownHandler, IPointerUpHandler
{
    [SerializeField, Tooltip("The Z rotation for which the locker current number should be 0")]
    private float rotationOffset = 297;
    [SerializeField, Tooltip("The amount of numbers or positions the locker has")]
    private int numbersQuantity = 40;
    [SerializeField, Tooltip("The time for the handle to rotate")]
    private float rotationTime = 0.5f;
    [SerializeField, Tooltip("The time for the handle to rotate on resetting")]
    private float resetRotationTime = 2f;
    [SerializeField, Tooltip("The amount of units the mouse has to be moved before moving the number")]
    private float mouseRequiredAngle = 45;
    [SerializeField]
    private int firstNumber;

    private bool isDragging = false;
    
    public UnityEvent OnCorrectNumber;
    public UnityEvent OnWrongNumber;
    public UnityEvent OnPuzzleSolved;
    public UnityEvent OnReset;

    private Camera cam;
    private int previousNumber;

    private int currentNumber;
    private int CurrentNumber
    {
        get
        {
            return currentNumber;
            // float value = -(Angle - rotationOffset) / NumberAngle % numbersQuantity + numbersQuantity;
            // if (value == numbersQuantity)
            //     value = 0;
            // return (int)value;
        }
        set
        {
            if (value == CurrentNumber) return;
            DangerouslySetCurrentNumber(value);
            OnNumberChange.Invoke(currentNumber);
        }
    }
    private void DangerouslySetCurrentNumber(int value)
    {
        int temp = value % numbersQuantity;
        if (temp < 0)
            temp += numbersQuantity;
        currentNumber = temp;
    }

    private float Angle => transform.rotation.eulerAngles.z;
    private float NumberAngle => 360f / numbersQuantity;
    private Action<int> OnNumberChange;

    private int[] target = null;
    private bool[] status = {false, false, false, false};
    private readonly bool[] direction = {false, true, false, true}; // true = clockwise

    private bool wasMovingClockwise = false;

    private bool IsMovingClockwise()
    {
        if (previousNumber == (numbersQuantity - 1) && CurrentNumber == 0)
            return true;
        if (CurrentNumber == (numbersQuantity - 1) && previousNumber == 0)
            return false;
        return CurrentNumber > previousNumber;
    }

    private void ResetStatus() => status = new [] {false, false, false, false};

    private void Puzzle()
    {
        if (previousNumber == CurrentNumber) return;

        bool isMovingClockwise = IsMovingClockwise();
        bool wasMovingClockwiseTemp = wasMovingClockwise;
        wasMovingClockwise = isMovingClockwise;

        if (status[status.Length - 2]) // waiting for the last one
        {
            if (isMovingClockwise == direction[direction.Length - 1] && CurrentNumber == target[target.Length - 1])
            {
                status[status.Length - 1] = true;
                OnPuzzleSolved.Invoke();
            }
        }

        if (wasMovingClockwiseTemp == isMovingClockwise) return; // moving to the same side

        int targetIndex = Array.IndexOf(status, false);

        if (previousNumber != target[targetIndex])
        {
            ResetStatus();
            return;
        }

        if (wasMovingClockwiseTemp != direction[targetIndex])
        {
            ResetStatus();
            return;
        }
        
        status[targetIndex] = true;
    }

    private Vector2 originPosition;
    
    private void Awake()
    {
        OnNumberChange += DoRotation;
        OnNumberChange += PlayClick;
        cam = Camera.main;
    }

    private void OnEnable()
    {
        originPosition = transform.position;
        SetTarget();
    }

    private void SetTarget()
    {
        if (target != null) return;

        if (Game.Current.LockPuzzleTarget != null)
        {
            target = Game.Current.LockPuzzleTarget;
            return;
        }

        target = new int[4];
        target[0] = firstNumber;
        
        System.Random random = new System.Random();
        for (int i = 1; i < target.Length; i++)
        {
            int proposed;

            do proposed = random.Next(0, numbersQuantity);
            while (target.Contains(proposed));
            target[i] = proposed;
        }
        Game.Current.LockPuzzleTarget = target;
    }

    private void PlayClick(int number)
    {
        int targetIndex = Array.IndexOf(status, false);
        if (number == target[targetIndex] && IsMovingClockwise() == direction[targetIndex])
            OnCorrectNumber.Invoke();
        else
            OnWrongNumber.Invoke();
    }

    private static float ConvertTo360(float angle)
    {
        if (angle < 0)
            angle += 360;
        if (angle >= 360)
            angle -= 360;
        return angle;
    }

    private static int Floor(float number)
    {
        if (number < 0)
            return -1 * Mathf.FloorToInt(number * -1);
        return Mathf.FloorToInt(number);
    }

    private void DoRotation(int targetNumber) => DoRotation(targetNumber, null);
    private void DoRotation(int targetNumber, float? customRotationTime)
    {
        float tempRotationTime = customRotationTime ?? rotationTime;
        float targetAngle = (rotationOffset - targetNumber * NumberAngle);
        targetAngle = ConvertTo360(targetAngle);
        Vector3 endValue = Vector3.forward * targetAngle;
        transform.DORotate(endValue, tempRotationTime, RotateMode.Fast).SetEase(Ease.Linear);
    }

    private void Update()
    {
        HandleDragging();
        
        Puzzle();

        previousNumber = CurrentNumber;

        // for debug only
        current = CurrentNumber;
    }

    private Vector2 initialMouseVector;

    private void HandleDragging()
    {
        if (!isDragging) return;

        Vector2 newPos = cam.ScreenToWorldPoint(Input.mousePosition);
        Vector2 newMouseVector = newPos - originPosition;

        float deltaAngle = Vector2.SignedAngle(initialMouseVector, newMouseVector);

        int deltaNumber = -1 * Floor(deltaAngle / mouseRequiredAngle);
        CurrentNumber += deltaNumber;

        if (deltaNumber != 0)
            initialMouseVector = newMouseVector;
    }

    public void Reset()
    {
        DangerouslySetCurrentNumber(0);
        DoRotation(0, resetRotationTime);
        ResetStatus();
        OnReset?.Invoke();
    }

    public void OnPointerDown(PointerEventData eventData)
    {
        if (eventData.button == PointerEventData.InputButton.Left)
        {
            Vector2 initialMousePos = cam.ScreenToWorldPoint(Input.mousePosition);
            initialMouseVector = initialMousePos - originPosition;
            isDragging = true;
        }
        else
        {
            isDragging = false;
        }
    }

    public void OnPointerUp(PointerEventData eventData)
    {
        isDragging = false;
    }

    [Header("Debug")]
    [SerializeField]
    private int current;
}