I'm a beginner coder in Unity and, although I don't have much experience coding, I know that my code is a mess.
So, here's the thing: I inserted a certain functionality that allows the player to Lerp from its position, to a "Object1" when I click the mouse(0). At the click, a Time Counter starts and, in the period of 2 seconds, if I click de mouse(0) again, it will Lerp the player to a second time, now to an "Object2". I applied this logic in a way that allows the user to Lerp the Player 4 times to 4 different positions, if each Input was under the five seconds.
With much effort I find a way to make it work, it's functional, but at the cost of many booleans and ifs statements, a complete and terrible Spaguetti. So my question to you folks, is how could I make this whole mess more clean and efficient ? What code structures could I use in this type of situation to make the whole thing more readable, and cut off this huge amount of booleans and conditionals? Thank you all in advance!
Here's the code:
[SerializeField] private Transform cube1;
[SerializeField] private Transform cube2;
[SerializeField] private Transform cube3;
[SerializeField] private Transform cube4;
private float timer;
private bool canCount;
private bool click1;
private bool click2;
private bool click3;
private bool click4;
private bool lerp1;
private bool lerp2;
private bool lerp3;
private bool lerp4;
private void CountDown()
{
if (canCount)
{
timer += Time.deltaTime;
if (timer >= 2)
{
canCount = false;
Debug.Log("Timer End");
}
}
}
private void Lerp()
{
if (lerp1)
{
transform.position = Vector3.Lerp(transform.position, cube1.position, Time.deltaTime * 10);
}
if (lerp2)
{
transform.position = Vector3.Lerp(transform.position, cube2.position, Time.deltaTime * 10);
}
if (lerp3)
{
transform.position = Vector3.Lerp(transform.position, cube3.position, Time.deltaTime * 10);
}
if (lerp4)
{
transform.position = Vector3.Lerp(transform.position, cube4.position, Time.deltaTime * 10);
}
}
private void Update()
{
CountDown();
Lerp();
if (Input.GetMouseButtonDown(0))
{
if (!canCount)
{
click1 = true;
click2 = false;
click3 = false;
click4 = false;
lerp2 = false;
lerp3 = false;
lerp4 = false;
if (click1)
{
click1 = false;
click2 = true;
lerp4 = false;
lerp1 = true;
timer = 0;
canCount = true;
}
else if (click2 && canCount)
{
click2 = false;
click3 = true;
lerp1 = false;
lerp2 = true;
timer = 0;
}
else if (click3 && canCount)
{
click3 = false;
click4 = true;
lerp2 = false;
lerp3 = true;
timer = 0;
}
else if (click4 && canCount)
{
click4 = false;
click1 = true;
lerp3 = false;
lerp4 = true;
timer = 0;
}
}
else
{
if (click1)
{
click1 = false;
click2 = true;
lerp4 = false;
lerp1 = true;
timer = 0;
canCount = true;
}
else if (click2)
{
click2 = false;
click3 = true;
lerp1 = false;
lerp2 = true;
timer = 0;
}
else if (click3)
{
click3 = false;
click4 = true;
lerp2 = false;
lerp3 = true;
timer = 0;
}
else if (click4)
{
click4 = false;
click1 = true;
lerp3 = false;
lerp4 = true;
timer = 0;
}
}
}
The code below should allow you to set an infinite number of objects to move to in order with unique wait times for each object until the player can click again to move to the next object. If the user fails to click in the given time, the next click moves to the first object again.
using UnityEngine;
using System.Collections.Generic;
using System.Collections;
/// <summary>
/// Holds data to a goal target and how long the player has to click to move towards it
/// </summary>
[System.Serializable]
public class CountdownLerpData
{
public float timeToWaitForClick = 0.0f;
public Transform destination = null;
}
public class YourScript : MonoBehaviour
{
[SerializeField] private List<CountdownLerpData> LerpDestinations = new List<CountdownLerpData>();
private Coroutine ExtraMouseClicks = null;
private Coroutine MoveToDestination = null;
// distance until our object has reached our goal point
private const float DISTANCE_EPSILON = 0.001f;
// the speed at which our object moves to a goal position
private float moveSpeed = 10f;
private void Update()
{
// just use update for your first click input, any successive clicks are handled by the Coroutine
if (Input.GetMouseButtonDown(0) && ExtraMouseClicks == null)
ExtraMouseClicks = StartCoroutine(MoveAndDetectMouseClicks(0));
}
/// <summary>
/// Will start a movement coroutine and wait until the time to click is exceeded or
/// when the player clicks, it will start a new coroutine
/// </summary>
/// <param name="idx"></param>
/// <returns></returns>
private IEnumerator MoveAndDetectMouseClicks(int idx)
{
// we wait for the next frame to assure we do NOT use the same mouse click event
yield return null;
// we exceeded our container, so exit the coroutine and set it to null
if (idx >= LerpDestinations.Count)
{
ExtraMouseClicks = null;
yield break;
}
float currentTimer = 0.0f; // the time to wait for our next click
bool hasMouseClick = false; // flag to determine if the user clicked
// start a new Coroutine for the motion - stop it if it is ongoing
if (MoveToDestination != null)
StopCoroutine(MoveToDestination);
// start it with our current index
MoveToDestination = StartCoroutine(MoveTowardsDestination(LerpDestinations[idx].destination.position));
// now wait our time to determine if another mouse click occurs, if it does, then increment our counter
while (currentTimer <= LerpDestinations[idx].timeToWaitForClick && !hasMouseClick)
{
// if we have a mouse click, then
if (Input.GetMouseButtonDown(0) && !hasMouseClick)
{
// the user clicked, so set our flag to true
hasMouseClick = true;
// assign the coroutine
ExtraMouseClicks = StartCoroutine(MoveAndDetectMouseClicks(idx + 1));
}
// increase our time since the last frame
currentTimer += Time.deltaTime;
yield return null;
}
// the user missed the window to click again, so set the reference back to null
if (!hasMouseClick)
ExtraMouseClicks = null;
}
/// <summary>
/// Moves your object to a goal location by moveSpeed speed
/// </summary>
/// <param name="goalPosition"></param>
/// <returns></returns>
private IEnumerator MoveTowardsDestination(Vector3 goalPosition)
{
// now lerp until we reach our destination
while (Vector3.Distance(transform.position, goalPosition) > DISTANCE_EPSILON)
{
transform.position = Vector3.MoveTowards(transform.position, goalPosition, moveSpeed * Time.deltaTime);
yield return null;
}
}
}
Instead of storing multiple clicks and lerps, which is quite the headache, my approach will remove individually tracking this data. Instead, you will have a List
of objects called CountdownLerpData
that hold two fields. The first field is timeToWaitForClick
, which is the time the program will wait after a click occurs to decide if the click will move to the next object in the list, or move back to the first object. The second field is destination
which is the Transform
of the object you want to move to at this time.
When the user clicks first, it will start a plausible chain of reactions through the List
of your object data. The initial click will start what is called a Coroutine
which can simplistically be defined as a special function that allows for small portions of a larger task to be completed over multiple frames.
Once you start the countdown Coroutine
, it will start the MoveTowardsDestination
Coroutine
, which just moves your object to the goal object of the index the countdown Coroutine
is on. If the user happens to click in the needed time interval, it will call the same Coroutine
again but now with the index increased by 1 to move to the next object in our list. If it happens to reach or exceed the List
, it will exit the Coroutine
.
Let me know how this goes for you or if this is not exactly what you wanted. I can tweak the answer if my implementation is not what you had intended. Also comment if you have any questions about how or why it works.