Unity: How to Delay button call by time? - c#

I desire to increment an float by 0.5 every time the user clicks the UI button and if the user presses the button for more than 2 second want to continuously increment the float by 0.5, to do so i use Event trigger (PointerDown, PointerUp) and call the functions in update. When i user the down below code i cant increment the float value continuously.
Update Code
void Update () {
transform.rotation = Quaternion.Lerp (qStart, qEnd, (Mathf.Sin(Time.time * speed) + 1.0f) / 2.0f);
if(Time.timeScale == 0)
transform.rotation = Quaternion.Euler(0,0,0);
clickCondition ();
}
PointerDown Function
public void WhenIncreaseClicked()
{
if (timeDown < 2.0f)
IncreaseBPM ();
else
increase = true;
}
PinterUp function
public void WhenIncreaseNotClicked()
{
increase = false;
Time.timeScale = 1;
}
IncreaseBPM
public void IncreaseBPM()
{
if (speed < 12)
{
speed += 0.05f;
bpmText.GetComponent<BeatTextControl> ().beats += 1;
PlayerPrefs.SetFloat ("savedBPM", speed);
}
}
ClickCondition
public void clickCondition()
{
if(increase)
{
IncreaseBPM();
}
else if(decrease)
{
DecreaseBPM();
}
}
Start
void Start () {
qStart = Quaternion.AngleAxis ( angle, Vector3.forward);
qEnd = Quaternion.AngleAxis (-angle, Vector3.forward);
timeDown = Time.deltaTime;
if (PlayerPrefs.HasKey ("savedBPM"))
speed = PlayerPrefs.GetFloat ("savedBPM");
else
speed = 1.5f;
}
I have set timeDown = Time.deltaTime in Start().

Assuming no other code alters your increase variable, you'll be unable to increment it continuously because it will never be set to true.
In Start() you have a line timeDown = Time.deltaTime;, so timeDown will be equal to the number of seconds since the last frame. Not only is this an issue because timeDown never changes (Start() is only called once), but it's an issue because it's not likely to ever be above 2.0f since a single frame is highly unlikely to take 2 seconds to complete. It will generally be a very small number, for example 0.06f.
Due to this, in your WhenIncreaseClicked() method, the if (timeDown < 2.0f) will always equate to true. Thus the else clause is never executed and increase is never set to true.
To resolve this you could create a new boolean variable, e.g. clicked and set it to true at the start of WhenIncreaseClicked() and false at the start of WhenIncreaseNotClicked(). Then in Update() you can just add Time.deltaTime to timeDown if clicked is true. You'll also want to move that if/else outside of WhenIncreaseClicked() also, and into Update(), making sure it's only run when clicked is true.
For example:
void Update () {
transform.rotation = Quaternion.Lerp (qStart, qEnd, (Mathf.Sin(Time.time * speed) + 1.0f) / 2.0f);
if(Time.timeScale == 0)
transform.rotation = Quaternion.Euler(0,0,0);
if(clicked) {
timeDown += Time.deltaTime;
if (timeDown >= 2.0f) // note the >= not <
increase = true;
}
clickCondition ();
}
and WhenIncreaseClicked():
public void WhenIncreaseClicked()
{
clicked = true;
IncreaseBPM();
}
and WhenIncreaseNotClicked():
public void WhenIncreaseNotClicked()
{
clicked = false;
increase = false;
Time.timeScale = 1;
}
You can also remove the assignment to timeDown in Start() since it's not useful.

First of all, Unity documentation says about Time.deltaTime
The time in seconds it took to complete the last frame.
When you set timeDown = Time.deltaTime timeDown becomes a tiny fraction, for example 0.01621689. Therefore, timeDown < 2.0f always returns true and your code never reaches increase = true; line.
Second point you should know is Time.time
The time at the beginning of this frame (Read Only). This is the time in seconds since the start of the game.
You used Time.time in your Update() method as Mathf.Sin(Time.time * speed). Bear in mind that Time.time can be enough large number representing the time in seconds since the start of the game as mentioned in document. When you do Time.time * speed the output number may be huge (assuming speed > 1) causing some problems. Luckily, you are using it inside Mathf.Sin() you get number only between [0, 1]. Probably, you intended to use Time.deltaTime instead.
Let's remember Time.time:
The time in seconds since the start of the game
You can use this to check time difference.
float lastDownTime;
bool isDown;
void OnPointerDown() // Method name itself says when it should be called
{
lastDownTime = Time.time;
isDown = true;
}
void OnPointerUp()
{
isDown = false;
}
void Update ()
{
if (isDown)
{
if (Time.time - lastDownTime > 2) // 2 seconds
{
IncreaseBPM ();
}
}
}
Note: Unity says about Time.timeScale:
Except for realtimeSinceStartup, timeScale affects all the time and delta time measuring variables of the Time class.
So if you insist on using Time.timeScale, instead of using Time.time you should use Time.realtimeSinceStartup in above example.

Related

Setting a fire rate for a weapon in Unity

I am trying to set a fire rate in Unity so that when I hold down up arrow, I will shoot a projectile up every 1 second. Currently my code is shooting a projectile every frame update, even though I have a Coroutine set up.
public GameObject bulletPrefab;
public float bulletSpeed;
public float fireRate = 1f;
public bool allowFire = true;
void Update()
{
//shooting input
if (Input.GetKey(KeyCode.UpArrow) && allowFire == true)
{
StartCoroutine(Shoot("up"));
}
}
IEnumerator Shoot(string direction)
{
allowFire = false;
if (direction == "up")
{
var bulletInstance = Instantiate(bulletPrefab, new Vector3(transform.position.x, transform.position.y, transform.position.z + 1), Quaternion.identity);
bulletInstance.GetComponent<Rigidbody>().AddForce(Vector3.forward * bulletSpeed);
}
yield return new WaitForSeconds(fireRate);
allowFire = true;
}
Coroutine
You can use the coroutine, but since you're calling it in an Update loop you much wait for it to finish before starting another one.
Coroutine currentCoroutine;
if(currentCoroutine == null)
currentCoroutine = StartCoroutine(DoShoot());
IEnumerator DoShoot() {
// Shoot.
yield return new WaitForSeconds(1f);
currentCoroutine = null;
}
Timestamp
You can also use a timestamp for when cooldown is ready. It's basically current time plus some duration.
float cooldown = 1f;
float cooldownTimestamp;
bool TryShoot (Vector2 direction) {
if (Time.time < cooldownTimestamp) return false;
cooldownTimestamp = Time.time + cooldown;
// Shoot!
}
I usually end up doing something like:
Variables
[Serializefield] float shootDelay = 1f;
float T_ShootDelay
Start()
T_ShootDelay = shootDelay;
Update()
if(T_ShootDelay < shootDelay)
T_ShootDelay += Time.deltaTime;
ShootInput()
if(T_ShootDelay >= shootDelay)
{
T_ShootDelay = 0;
Shoot();
}
What this does is:
Check if the shootDelay timer is less than the shootDelay.
Add up the timer by 1 per second.
Check the timer's status every time you want to shoot.
Set the timer to 0 after shooting
Considering a M4 AR that fires at least 700 rounds per minute.
700 rounds / 60 (seconds) ~= 11,66 rounds per second
1 second / 11 rounds ~= 0,085 seconds of delay between each round
You could simply try this:
yield return new WaitForSeconds(1 / (fireRate / 60f));

Lerp for lighting brightness is not gradually increasing

My implementation of Lerp should gradually increase the light from minbrightness to maxBrightness over time. It does change the range of my light however it is immediately going to my maxBrightness instead of gradually increasing. Where have I gone wrong?
Here is the code I am using
private void Update()
{
//this is a timer that will only only count down when collided = true
if (_collided == true)
{
timeLeft -= Time.deltaTime;
_light1.range = Mathf.Lerp(minbrightness, maxBrightness, lightTimer);
//increases interloper
lightTimer += _brightenRate * Time.deltaTime;
}
else
{
//this sets the lights range value to be bigger then normal
_light1.range = Mathf.Lerp(maxBrightness, minbrightness, lightTimer);
//increases interloper
lightTimer += _brightenRate * Time.deltaTime;
}
//checks if the time value is 0
if (timeLeft <= 0)
{
//this resets the value back
timeLeft = timeLeftReset;
_collided = false;
}
}
I have heard of IEnumerator, but am unsure of how to apply it to my current solution or if that will even fix my current problem.

I want to create a timer/stopwatch on an event

I have a code that works just fine but i think if i keep following the same strategy to do all the same things it's gonna over load on the processor for no reason
I have a variable that represents the time and this variable rests to 0 in every frame that the gameObject has a velocity higher than 0.5f so instead of resetting it every frame i want to start the timer when it's below 0.5f
if (speed >= 0.5f)
{
t = 0;
}
t = t + Time.deltaTime;
You could use a bool value to save performance.
public static bool isTimerMoving = false;
public void Update()
{
if (speed < 0.5f)
{
t = t + Time.deltaTime;
isTimerMoving = true;
}
else if (isTimerMoving) {
t = 0;
isTimerMoving = false;
}
}
This code resets the timer whenever speed reaches 0.5f. If you only want to pause the timer, you can remove the t = 0 from it.
P.S. using > or < is faster than using <= or >=. Not by very much, but I like keeping things efficient ;)
EDIT: After asking a question, responses indicate that this statement is false, my apologies.

Why the camera is not moving at all if the rotation speed value is for example set to 0.01?

If I set the rotation speed to 5 for example it will rotate facing the next target waypoint and then will move to it. But the camera rotation will be too fast.
Changing the speed to 0.01 make it rotating in a good slowly smooth speed. But then at 0.01 the camera rotate facing the next waypoint but never move to it. It stay on place.
This is the waypoints script:
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Waypoints : MonoBehaviour
{
private GameObject[] waypoints;
private Transform currentWaypoint;
private enum CameraState
{
StartRotating,
Rotating,
Moving,
Waiting
}
private CameraState cameraState;
public GameObject player;
public float speed = 5;
public float WPradius = 1;
public LookAtCamera lookAtCam;
void Start()
{
cameraState = CameraState.StartRotating;
}
void Update()
{
switch (cameraState)
{
// This state is used as a trigger to set the camera target and start rotation
case CameraState.StartRotating:
{
// Sanity check in case the waypoint array was set to length == 0 between states
if (waypoints.Length == 0)
break;
// Tell the camera to start rotating
currentWaypoint = waypoints[UnityEngine.Random.Range(0, waypoints.Length)].transform;
lookAtCam.target = currentWaypoint;
lookAtCam.setTime(0.0f);
cameraState = CameraState.Rotating;
break;
}
// This state only needs to detect when the camera has completed rotation to start movement
case CameraState.Rotating:
{
if (lookAtCam.IsRotationFinished)
cameraState = CameraState.Moving;
break;
}
case CameraState.Moving:
{
// Move
transform.position = Vector3.MoveTowards(transform.position, currentWaypoint.position, Time.deltaTime * speed);
// Check for the Waiting state
if (Vector3.Distance(currentWaypoint.position, transform.position) < WPradius)
{
// Set to waiting state
cameraState = CameraState.Waiting;
// Call the coroutine to wait once and not in CameraState.Waiting
// Coroutine will set the next state
StartCoroutine(WaitForTimer(3));
}
break;
}
case CameraState.Waiting:
// Do nothing. Timer has already started
break;
}
}
IEnumerator WaitForTimer(float timer)
{
yield return new WaitForSeconds(timer);
cameraState = CameraState.StartRotating;
}
public void RefreshWaypoints()
{
waypoints = GameObject.FindGameObjectsWithTag("Target");
}
}
And the look at camera script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class LookAtCamera : MonoBehaviour
{
// Values that will be set in the Inspector
public Transform target;
public float RotationSpeed;
private float timer = 0.0f;
public bool IsRotationFinished
{
get { return timer > 0.99f; }
}
// Update is called once per frame
void Update()
{
if (target != null && timer < 0.99f)
{
// Rotate us over time according to speed until we are in the required rotation
transform.rotation = Quaternion.Slerp(transform.rotation,
Quaternion.LookRotation((target.position - transform.position).normalized),
timer);
timer += Time.deltaTime * RotationSpeed;
}
}
public void setTime(float time)
{
timer = time;
}
}
Problem
Your script basically works! The problem is in
private void Update()
{
if (target != null && timer < 0.99f)
{
transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation((target.position - transform.position).normalized), timer);
timer += Time.deltaTime * RotationSpeed;
}
}
there are two issues with that:
You add Time.deltaTime * RotationSpeed so the time it takes to reach the 1 or in your case 0.99 simply takes 1/RotationSpeed = 100 times longer than usual. So your camera will stay in the Rotating state for about 100 seconds - after that it moves just fine!
(This one might be intentional but see below for a Better Solution) Quaternion.Slerp interpolates between the first and second rotation. But you always use the current rotation as startpoint so since the timer never reaches 1 you get a very fast rotation at the beginning but a very slow (in fact never ending) rotation in the end since the distance between the current rotation and the target rotation gets smaller over time.
Quick-Fixes
Those fixes repair your current solution but you should checkout the section Better Solution below ;)
In general for comparing both float values you should rather use Mathf.Approximately and than use the actual target value 1.
if (target != null && !Mathf.Approximately(timer, 1.0f))
{
//...
timer += Time.deltaTime * RotationSpeed;
// clamps the value between 0 and 1
timer = Mathf.Clamp01(timer);
}
and
public bool IsRotationFinished
{
get { return Mathf.Approximately(timer, 1.0f); }
}
You should either use Quaternion.Slerp storing the original rotation and use it as first parameter (than you will see that you need a way bigger RotationSpeed)
private Quaternion lastRotation;
private void Update()
{
if (target != null && !Mathf.Approximately(timer, 1.0f))
{
transform.rotation = Quaternion.Slerp(lastRotation, Quaternion.LookRotation((target.position - transform.position).normalized), timer);
timer += Time.deltaTime * RotationSpeed;
}
else
{
lastRotation = transform.rotation;
}
}
Or instead of Quaternion.Slerp use Quaternion.RotateTowards like
transform.rotation = Quaternion.RotateTowards(transform.rotation, Quaternion.LookRotation((target.position - transform.position).normalized), RotationSpeed * Time.deltaTime);
Better Solution
I would strongly suggest to use the Coroutines for everything instead of handling this kind of stuff in Update. They are way easier to control and makes your code very clean.
Look how your scripts would shrink and you wouldn't need all the properties, fields and comparing floats anymore. You could do most things you are currently getting and setting to wait for a certain thing to happen in only a few single lines.
In case you didn't know: You can actually simply yield return another IEnumerator on order to wait for it to finish:
Waypoints
public class Waypoints : MonoBehaviour
{
private GameObject[] waypoints;
public GameObject player;
public float speed = 5;
public float WPradius = 1;
public LookAtCamera lookAtCam;
private Transform currentWaypoint;
private void Start()
{
// maybe refresh here?
//RefreshWaypoints();
StartCoroutine(RunWaypoints());
}
private IEnumerator RunWaypoints()
{
// Sanity check in case the waypoint array has length == 0
if (waypoints.Length == 0)
{
Debug.Log("No Waypoints!", this);
yield break;
}
// this looks dnagerous but as long as you yield somewhere it's fine ;)
while (true)
{
// maybe refresh here?
//RefreshWaypoints();
// Sanity check in case the waypoint array was set to length == 0 between states
if (waypoints.Length == 0)
{
Debug.Log("No Waypoints!", this);
yield break;
}
// first select the next waypoint
// Note that you might get the exact same waypoint again you currently had
// this will throw two errors in Unity:
// - Look rotation viewing vector is zero
// - and transform.position assign attempt for 'Main Camera' is not valid. Input position is { NaN, NaN, NaN }.
//
// so to avoid that rather use this (not optimal) while loop
// ofcourse while is never good but the odds that you will
// always get the same value over a longer time are quite low
//
// in case of doubt you could still add a yield return null
// than your camera just waits some frames longer until it gets a new waypoint
Transform newWaypoint = waypoints[Random.Range(0, waypoints.Length)].transform;
while(newWaypoint == currentWaypoint)
{
newWaypoint = waypoints[Random.Range(0, waypoints.Length)].transform;
}
currentWaypoint = newWaypoint;
// tell camera to rotate and wait until it is finished in one line!
yield return lookAtCam.RotateToTarget(currentWaypoint);
// move and wait until in correct position in one line!
yield return MoveToTarget(currentWaypoint);
//once waypoint reached wait 3 seconds than start over
yield return new WaitForSeconds(3);
}
}
private IEnumerator MoveToTarget(Transform currentWaypoint)
{
var currentPosition = transform.position;
var duration = Vector3.Distance(currentWaypoint.position, transform.position) / speed;
var passedTime = 0.0f;
do
{
// for easing see last section below
var lerpFactor = passedTime / duration;
transform.position = Vector3.Lerp(currentPosition, currentWaypoint.position, lerpFactor);
passedTime += Time.deltaTime;
yield return null;
} while (passedTime <= duration);
// to be sure to have the exact position in the end set it fixed
transform.position = currentWaypoint.position;
}
public void RefreshWaypoints()
{
waypoints = GameObject.FindGameObjectsWithTag("Target");
}
}
LookAtCamera
public class LookAtCamera : MonoBehaviour
{
// Values that will be set in the Inspector
public float RotationSpeed;
public IEnumerator RotateToTarget(Transform target)
{
var timePassed = 0f;
var targetDirection = (target.position - transform.position).normalized;
var targetRotation = Quaternion.LookRotation(targetDirection);
var currentRotation = transform.rotation;
var duration = Vector3.Angle(targetDirection, transform.forward) / RotationSpeed;
do
{
// for easing see last section below
var lerpFactor = timePassed / duration;
transform.rotation = Quaternion.Slerp(currentRotation, targetRotation, lerpFactor);
timePassed += Time.deltaTime;
yield return null;
} while (timePassed <= duration);
// to be sure you have the corrcet rotation in the end set it fixed
transform.rotation = targetRotation;
}
}
Note
Again instead of Quaternion.Slerp and currentRotation you could also simply use Quaternion.RotateTowards like
transform.rotation = Quaternion.RotateTowards(transform.rotation, targetRotation, RotationSpeed * Time.deltaTime);
And for the movement you can also still use Vector3.MoveTowards if you want
while (Vector3.Distance(currentWaypoint.position, transform.position) < WPradius)
{
transform.position = Vector3.MoveTowards(transform.position, currentWaypoint.position, Time.deltaTime * speed);
yield return null;
}
but I would prefer to use the Lerp solutions. Why I suggest to rather use Lerp?
You can very easy controll now whether you want to move/rotate by a certain speed or rather give it fixed duration in which the move/rotation shall be finished regardless how big the differenc is - or even have some additional checks in order to decide for one of those options!
You can ease-in and -out the movement/rotation! See below ;)
Hint for easing Lerp movements
For still maintaining an eased-in and/or eased-out movement and rotation I found this block How to Lerp like a pro very helpfull! (adopted to my examples)
For example, we could “ease out” with sinerp:
var lerpFactor = Mathf.Sin(passedTime / duration * Mathf.PI * 0.5f);
Or we could “ease in” with coserp:
var lerpFactor = 1f - Mathf.Cos(passedTime / duration * Mathf.PI * 0.5f);
We could even create exponential movement:
var lerpFactor = Mathf.Pow(passedTime / duration, 2);
The multiplication property mentioned above is the core concept behind some interpolation methods which ease in and ease out, such as the famous “smoothstep” formula:
var lerpFactor = Mathf.Pow(passedTime / duration, 2) * (3f - 2f * passedTime / duration);
Or my personal favorite, “smootherstep”:
var lerpFactor = Mathf.Pow(passedTime / duration, 3) * (6f * (passedTime / duration) - 15f) + 10f);

Unity increment velocity based on interval

I am trying to get a background to start moving to the left faster and faster.
Was thinking of using two values one for the amount in percent to increase and one value for the interval between incrementing which would also get larger the more times an interval is hit?
public float interval = 1; // 1 second between intervals starting off
public float speed = 2; // the starting speedi
void Start () {
// move left
GetComponent<Rigidbody2D>().velocity = Vector2.left * speed;
}
void Update () {
// check if interval has been reached? How?
//if interval has been reached then ( This does not work for me..
GetComponent<Rigidbody2D>().velocity = GetComponent<Rigidbody2D>().velocity * 0.01f;
interval = interval * 2;
}
I think there's a few things you need to fix here:
1.) you are multiplying velocity by .01 every frame, this will practically freeze it completely. I'm not sure what you mean to do by that.
2.) You are calling GetComponent>Rigidbody2D<() every frame, which is very expensive! You should create a variable for it something like:
Rigidbody2d RB;
void Start () {
RB = GetComponent<Rigidbody2D>();
}
For how to increase speed after each interval, I might declare a counter which you increase every frame. And if the counter > interval then interval *= 2 and counter = 0
Well, first of all take Rigidbody2D in a variable once then play with it.
You can use Coroutine for this purpose.
public float interval = 1; // 1 second between intervals starting off
public float speed = 2; // the starting speed
Rigidbody2D _rb;
void Start () {
// move left
_rb = GetComponent<Rigidbody2D>();
_rb.velocity = Vector2.left * speed;
StartCoroutine("IncreaseSpeedWithInterval");
}
void Update () {
}
IEnumerator IncreaseSpeedWithInterval()
{
while(true){
yield return new WaitForSeconds(interval);
// Now either Multiply your velocity by 1.01f or Add by 0.01f
_rb.velocity *= 1.01f;
// ========== OR ========== //
_rb.velocity += (Vector2.one * 0.01f);
}
}

Categories