I'm trying to implement cooldowns in my project in Unity, while this code seems to make sense, it doesn't work. The code that's posted is an all-around basic movement script.
I tried doing something with a cooldown -=time.deltatime, but that didn't seem to work. I've been trying several methods, but none seem to work.
The code:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MovementScript : MonoBehaviour
{
public float cooldown = 0;
public float actualcooldown = 3f;
public bool isCooldown = false;
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
if (Input.GetKey(KeyCode.R))
{
GetComponent<Renderer>().material.color = Color.red;
}
if (Input.GetKey(KeyCode.G))
{
GetComponent<Renderer>().material.color = Color.green;
}
if (Input.GetKey(KeyCode.B))
{
GetComponent<Renderer>().material.color = Color.blue;
}
if (Input.GetKey(KeyCode.D))
{
transform.Translate(6f * Time.deltaTime, 0, 0);
}
if (Input.GetKey(KeyCode.A))
{
transform.Translate(-6f * Time.deltaTime, 0, 0);
}
if (Input.GetKeyDown(KeyCode.Space) && cooldown <= 0) {
transform.Translate(0f, 20f * Time.deltaTime, 0f);
isCooldown = true;
while (isCooldown == true)
{
coolDownhappening();
}
}
}
public void coolDownhappening()
{
cooldown = actualcooldown;
cooldown -= Time.deltaTime;
if (cooldown <= 0)
{
cooldown = 0;
}
}
}
You do
while (isCooldown == true)
{
coolDownhappening();
}
But you never change isCoolddown anywhere!
Also as was already mentioned in the comments you do not want to use while in the Update method at all, at least not in this usecase! This will freeze the entire mainthread for the given cooldown time - or in your case forever!
There are a lot of other issues in your code so let's go step by step:
Input.GetKey is true every frame while they given key is pressed. However, it makes no sense and only causes unecessary overhead to repeatedly set the materials color to the same value as long as a button stays pressed. What you rather want to do is apply it once.
→ rather use Input.GetKeyDown for these!
GetComponent is a quite expensive call. You should not repeadedly use GetComponent<Renderer>() but rather store the reference once and re-use it later
// most efficient is always to already reference this via the Inspector
[SerializeField] private Renderer _renderer;
// alternatively get it on runtime
private void Awake()
{
if(!_renderer) _rednerer = GetComponent<Renderer>();
}
and then later use
private void Update()
{
if(Input.GetKeyDown(KeyCode.R))
{
_renderer.material.color = Color.red;
}
...
}
Your moving part is actually fine. To make it slightly more readable I would however actually rather do something like
if (Input.GetKey(KeyCode.D))
{
transform.Translate(Vector3.right * 6f * Time.deltaTime);
}
else if (Input.GetKey(KeyCode.D))
{
transform.Translate(Vector3.left * 6f * Time.deltaTime);
}
also note the else here. Depends on your needs of course but usually you want contrary buttons exclusive.
Finally to the real deal: You actually want to have a jump method with a cooldown here.
First here you did it the other way round: Input.GetKeyDown is called only exactly once namely in the frame when the key went down. So your object "jumps" 20 * 1/FPS which for 60 FPS is always about 0.33. You probably rather wanted to move a certain distance upwards over multiple frames. After a certain height is reached, activate a cooldown.
As mentioned in the comments one can do this in Update using a timer but usually this makes the code a bit messy. Rather use a Coroutine:
private bool _canJump;
private void Update()
{
...
// _canJump is cheaper to check so check it first
if (_canJump && Input.GetKeyDown(KeyCode.Space))
{
StartCoroutine(JumpRoutine());
}
}
private IEnumerator JumpRoutine()
{
// avoid concurrent routines
if(!_canJump) yield break;
// disable jumping
_canJump = false;
// Now it really depends on what you actually want to do
// and how your object should move now
// you might e.g. want something like
var jumpedHeight = 0f;
while(jumpedHeight < 20f)
{
var jumpThisFrame = Vector3.up * 6f * Time.deltaTime;
transform.Translate(jumpThisFrame);
// important now! yield tells Unity to "pause" here,
// render this frame, and continue from here int he next frame
// without the yield statements this would again freeze your game until
// the exit condition is fulfilled!
yield return null;
}
// After reaching the target height this waits for 3 seconds but keeps on rendering meanwhile
yield return new WaitForSeconds(actualcooldown);
// after the cooldown allow next jump
_canJump = true;
}
Related
I am learning Unity from a Swift SpriteKit background where moving a sprite's x Position is as straight forward as an running an action as below:
let moveLeft = SKAction.moveToX(self.frame.width/5, duration: 1.0)
let delayAction = SKAction.waitForDuration(1.0)
let handSequence = SKAction.sequence([delayAction, moveLeft])
sprite.runAction(handSequence)
I would like to know an equivalent or similar way of moving a sprite to a specific position for a specific duration (say, a second) with a delay that doesn't have to be called in the update function.
gjttt1's answer is close but is missing important functions and the use of WaitForSeconds() for moving GameObject is unacceptable. You should use combination of Lerp, Coroutine and Time.deltaTime. You must understand these stuff to be able to do animation from Script in Unity.
public GameObject objectectA;
public GameObject objectectB;
void Start()
{
StartCoroutine(moveToX(objectectA.transform, objectectB.transform.position, 1.0f));
}
bool isMoving = false;
IEnumerator moveToX(Transform fromPosition, Vector3 toPosition, float duration)
{
//Make sure there is only one instance of this function running
if (isMoving)
{
yield break; ///exit if this is still running
}
isMoving = true;
float counter = 0;
//Get the current position of the object to be moved
Vector3 startPos = fromPosition.position;
while (counter < duration)
{
counter += Time.deltaTime;
fromPosition.position = Vector3.Lerp(startPos, toPosition, counter / duration);
yield return null;
}
isMoving = false;
}
Similar Question: SKAction.scaleXTo
The answer of git1 is good but there is another solution if you do not want to use couritines.
You can use InvokeRepeating to repeatedly trigger a function.
float duration; //duration of movement
float durationTime; //this will be the value used to check if Time.time passed the current duration set
void Start()
{
StartMovement();
}
void StartMovement()
{
InvokeRepeating("MovementFunction", Time.deltaTime, Time.deltaTime); //Time.deltaTime is the time passed between two frames
durationTime = Time.time + duration; //This is how long the invoke will repeat
}
void MovementFunction()
{
if(durationTime > Time.time)
{
//Movement
}
else
{
CancelInvoke("MovementFunction"); //Stop the invoking of this function
return;
}
}
You can use co-routines to do this. To do this, create a function that returns type IEnumerator and include a loop to do what you want:
private IEnumerator foo()
{
while(yourCondition) //for example check if two seconds has passed
{
//move the player on a per frame basis.
yeild return null;
}
}
Then you can call it by using StartCoroutine(foo())
This calls the function every frame but it picks up where it left off last time. So in this example it stops at yield return null on one frame and then starts again on the next: thus it repeats the code in the while loop every frame.
If you want to pause for a certain amount of time then you can use yield return WaitForSeconds(3) to wait for 3 seconds. You can also yield return other co-routines! This means the current routine will pause and run a second coroutine and then pick up again once the second co-routine has finished.
I recommend checking the docs as they do a far superior job of explaining this than I could here
i want to make a simple script that when u clicked the screen the ball in the game will move (20000 * Time.deltaTime) to the right, and then if i'll click again, it will move to the left side and then right and so on.
I managed to get the ball to move to the right, but i need it to wait after the animation is finish because i need to check if the player clicked again (if he did i need to check to what direction to move the ball).
I tried many methods i found online like checking if Rigidbody.velocity.magnitude == 0.0f that means the ball is not moving..
public Rigidbody rb;
public Transform PlayerPosition;
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
if (Input.GetMouseButtonDown(0))
{
rb.AddForce(20000 * Time.deltaTime, 0, 0); // moving ball to the right
while (rb.velocity.magnitude != 0.0f) // I tried to check until the ball is not moving
{
}
Debug.Log(PlayerPosition.position.x);
}
}
And here is my latest try:
void Update()
{
if (Input.GetMouseButtonDown(0))
{
rb.AddForce(20000 * Time.deltaTime, 0, 0); // moving ball to the right
if(rb.velocity.magnitude < 0.05f) // if i click the ball it just prints it and not wating for the ball to not move
{
Debug.Log(PlayerPosition.position.x);
}
}
}
I expected the output to wait until the animation is finished but instead, its printing the vaule(x) the moment i click the mouse.
Edit
You need to check if your animation is still playing. You are checking only if your velocity is greater than 0.05f, which is correctly printing out the statement.
Use Animation.IsPlaying(string name). One caveat is that this method will return false for the same frame of Update that it was invoked, since the animation hasn't technically started until afterward.
void Update()
{
if (!rb.velocity.magnitude <= 0.01f && !Animation.IsPlaying(nameOfAnimation))
{
Debug.Log("We're not moving and the animation is not playing");
}
}
Original
You should not need to use while in your Update method.
Use an if statement inside of your Update
void Update()
{
if (rb.velocity.magnitude > 0.01f) Debug.Log("We're moving!");
}
First
rb.velocity.magnitude != 0.0f
will almost allways be true due to single precision floatong point : Two float values even if they seem to be equal logical are most likely not.
So you can either use a threshold how you tried already
if(rb.velocity.magnitude <= 0.5f)
or use Mathf.Approximately which uses a very small Epsilon or threshold for the comparing
if(Mathf.Approximately(rb.velocity.magintude, 0))
Than it sounds like you want to wait until the ball has stopped moving and than output the position - like e.g. for a billard game. So actually there seems to be no Animation involved.
In most cases where you think/speek of of an "animation" you actually mean "doing something over time" not to confuse with using an Animator or Animation component with AnimationClips in Unity.
You can/should use a Coroutine for that:
public Rigidbody rb;
public Transform PlayerPosition;
// a flag to make sure there is only one animation at a time
private bool isMoving;
// a flag for altering between left and right movement
private bool isMovingRight;
// Update is called once per frame
void Update()
{
// only allow clicks while not moving already
if (!isMoving && Input.GetMouseButtonDown(0))
{
// stop further input until not moving anymore
isMoving = true;
// add the force
// (you might btw want to skip that Time.deltaTime here it makes no sense)
rb.AddForce(isMovingRight ? 20000 : -20000 * Time.deltaTime, 0, 0);
// alter the direction for the next call
isMovingRight = !isMovingRight;
// if you rather want to be able to interrupt the current animation by clicking again
// remove the isMoving flag and instead use this
//StopCoroutine(WaitForMoveStops());
// Start the routine
StartCoroutine(WaitForMoveStops());
}
}
private IEnumerator WaitForMoveStops()
{
// Inside a Coroutine while is okey now
// as long as you yield somwhere
// check if velocity is below threshold
while (!Mathf.Approximately(rb.velocity.magnitude, 0)
{
// yield in simple words means "leave" this method here, render the frame
// and than continue from here in the next frame
yield return null;
}
// I would now hard reset the velocity just to be sure
rb.velocity = Vector3.zero;
Debug.Log(PlayerPosition.position.x);
// whatever you want to do now
// reset the flag to allow input again
isMoving = false;
}
I think you want to move it if it's stopped, then call AddForce only when it's idle:
var wasMovingLastTime = false;
void Update()
{
var isMoving = rb.velocity.magnitude > 0f;
if (wasMovingLastTime && !isMoving)
{
/// Has just finished moving
Debug.Log(PlayerPosition.position.x);
}
if (Input.GetMouseButtonDown(0))
{
if (!isMoving)
{
rb.AddForce(20000 * Time.deltaTime, 0, 0);
}
}
wasMovingLastTime = isMoving;
}
Hi everyone I have a little problem with my bow shooting script. I mean i want to shoot an arrow when play the one of last frames from my animation. I try to do it by setting an firePoint GameObject, put it by recording in my Animation Tab in desired frame. It's of course disabled but its enabled when animation plays and then its again disabled. So the problem is:
- When i hit button which match my Shooting input, the animation plays,
- My Instantiate appears and it produces multiple arrows,
- When its disabled it stops to produce arrows.
I want to produce only one arrow. Could anyone help?
CombatScript.cs:
/* private bool shootBow;
* public bool needReload = false;
* public float reloadTime = 1.5f;
* public float realoadCD;
*/
public void RangeAttack()
{
if (needReload == false && gameObject.GetComponent<PlayerControls>().grounded == true && Input.GetButtonDown("Ranged"))
{
animator.SetTrigger("shootBow");
attack1 = false; // Melle attacks sets to false in case of lag or smth.
attack2 = false;
attack3 = false;
needReload = true;
if (needReload == true)
{
reloadCD = reloadTime;
}
}
if (reloadCD > 0 && needReload == true)
{
reloadCD -= Time.deltaTime;
}
if (reloadCD <= 0)
{
reloadCD = 0;
needReload = false;
}
if (firePoint.gameObject.activeSelf == true)
{
Instantiate(Missile, new Vector3(firePoint.position.x + 1, firePoint.position.y), firePoint.rotation);
Debug.Log("It's a bird, a plane, no.. it's arrow.");
}
}
Arrow Controller.cs:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ArrowController : MonoBehaviour {
public float speed;
// Use this for initialization
void Start () {
}
// Update is called once per frame
void Update ()
{
GetComponent<Rigidbody2D>().velocity = new Vector2(speed, GetComponent<Rigidbody2D>().velocity.y);
}
private void OnTriggerEnter2D(Collider2D collision)
{
if (collision.gameObject.tag == "Enemy")
{
Destroy(collision.gameObject);
}
Debug.Log("Arrow Broke");
Debug.Log(gameObject.name);
//Destroy(gameObject);
}
public void OnCollisionEnter2D(Collision2D collision)
{
}
}
Example of my situation:
Example of true/false needReload statement:
in right Inspector you have my Player informations, in left (or bottom) Inspector you have Missile (Arrow) inspector
You can use Animation Events and for example a boolean that is your firerate.
And you Code can look something like this:
float LastShoot;
float FireRate = 10;
public void OnShoot()
{
if (LastShoot <= 0)
{
//Shoot your arrow
LastShoot = FireRate;
}
}
void Update()
{
LastShoot -= 0.5f;
}
But i don't know if this is the best solution to calculate a firerate. If someone knows a better one feel free and edit my awnser :)
Okey... couple of hours later (which I wasted...). The answer was ridiculously easy. For future "problems" like that... the thing was to create in my script function called "ProduceArrow()"and (additionaly to what i did) something like Animation Event it's in Animation Tab, when you create your animation timeline you just need to call it clicking right mouse button and then choose it and pick proper function.
Some feedback - gif
The way your code works right now, Instatiate will be called every frame. So one button click, which is probably longer than one frame, will trigger multiple arrows, so you need to set a condition for when you want Instantiate to be called. You already calculate a reload time, which is exactly what you need. You just need to include it in your if-statement like follows:
if (firePoint.gameObject.activeSelf == true && !needReload)
{
Instantiate(Missile, new Vector3(firePoint.position.x + 1, firePoint.position.y), firePoint.rotation);
Debug.Log("It's a bird, a plane, no.. it's arrow.");
}
I am learning Unity from a Swift SpriteKit background where moving a sprite's x Position is as straight forward as an running an action as below:
let moveLeft = SKAction.moveToX(self.frame.width/5, duration: 1.0)
let delayAction = SKAction.waitForDuration(1.0)
let handSequence = SKAction.sequence([delayAction, moveLeft])
sprite.runAction(handSequence)
I would like to know an equivalent or similar way of moving a sprite to a specific position for a specific duration (say, a second) with a delay that doesn't have to be called in the update function.
gjttt1's answer is close but is missing important functions and the use of WaitForSeconds() for moving GameObject is unacceptable. You should use combination of Lerp, Coroutine and Time.deltaTime. You must understand these stuff to be able to do animation from Script in Unity.
public GameObject objectectA;
public GameObject objectectB;
void Start()
{
StartCoroutine(moveToX(objectectA.transform, objectectB.transform.position, 1.0f));
}
bool isMoving = false;
IEnumerator moveToX(Transform fromPosition, Vector3 toPosition, float duration)
{
//Make sure there is only one instance of this function running
if (isMoving)
{
yield break; ///exit if this is still running
}
isMoving = true;
float counter = 0;
//Get the current position of the object to be moved
Vector3 startPos = fromPosition.position;
while (counter < duration)
{
counter += Time.deltaTime;
fromPosition.position = Vector3.Lerp(startPos, toPosition, counter / duration);
yield return null;
}
isMoving = false;
}
Similar Question: SKAction.scaleXTo
The answer of git1 is good but there is another solution if you do not want to use couritines.
You can use InvokeRepeating to repeatedly trigger a function.
float duration; //duration of movement
float durationTime; //this will be the value used to check if Time.time passed the current duration set
void Start()
{
StartMovement();
}
void StartMovement()
{
InvokeRepeating("MovementFunction", Time.deltaTime, Time.deltaTime); //Time.deltaTime is the time passed between two frames
durationTime = Time.time + duration; //This is how long the invoke will repeat
}
void MovementFunction()
{
if(durationTime > Time.time)
{
//Movement
}
else
{
CancelInvoke("MovementFunction"); //Stop the invoking of this function
return;
}
}
You can use co-routines to do this. To do this, create a function that returns type IEnumerator and include a loop to do what you want:
private IEnumerator foo()
{
while(yourCondition) //for example check if two seconds has passed
{
//move the player on a per frame basis.
yeild return null;
}
}
Then you can call it by using StartCoroutine(foo())
This calls the function every frame but it picks up where it left off last time. So in this example it stops at yield return null on one frame and then starts again on the next: thus it repeats the code in the while loop every frame.
If you want to pause for a certain amount of time then you can use yield return WaitForSeconds(3) to wait for 3 seconds. You can also yield return other co-routines! This means the current routine will pause and run a second coroutine and then pick up again once the second co-routine has finished.
I recommend checking the docs as they do a far superior job of explaining this than I could here
I am learning Unity from a Swift SpriteKit background where moving a sprite's x Position is as straight forward as an running an action as below:
let moveLeft = SKAction.moveToX(self.frame.width/5, duration: 1.0)
let delayAction = SKAction.waitForDuration(1.0)
let handSequence = SKAction.sequence([delayAction, moveLeft])
sprite.runAction(handSequence)
I would like to know an equivalent or similar way of moving a sprite to a specific position for a specific duration (say, a second) with a delay that doesn't have to be called in the update function.
gjttt1's answer is close but is missing important functions and the use of WaitForSeconds() for moving GameObject is unacceptable. You should use combination of Lerp, Coroutine and Time.deltaTime. You must understand these stuff to be able to do animation from Script in Unity.
public GameObject objectectA;
public GameObject objectectB;
void Start()
{
StartCoroutine(moveToX(objectectA.transform, objectectB.transform.position, 1.0f));
}
bool isMoving = false;
IEnumerator moveToX(Transform fromPosition, Vector3 toPosition, float duration)
{
//Make sure there is only one instance of this function running
if (isMoving)
{
yield break; ///exit if this is still running
}
isMoving = true;
float counter = 0;
//Get the current position of the object to be moved
Vector3 startPos = fromPosition.position;
while (counter < duration)
{
counter += Time.deltaTime;
fromPosition.position = Vector3.Lerp(startPos, toPosition, counter / duration);
yield return null;
}
isMoving = false;
}
Similar Question: SKAction.scaleXTo
The answer of git1 is good but there is another solution if you do not want to use couritines.
You can use InvokeRepeating to repeatedly trigger a function.
float duration; //duration of movement
float durationTime; //this will be the value used to check if Time.time passed the current duration set
void Start()
{
StartMovement();
}
void StartMovement()
{
InvokeRepeating("MovementFunction", Time.deltaTime, Time.deltaTime); //Time.deltaTime is the time passed between two frames
durationTime = Time.time + duration; //This is how long the invoke will repeat
}
void MovementFunction()
{
if(durationTime > Time.time)
{
//Movement
}
else
{
CancelInvoke("MovementFunction"); //Stop the invoking of this function
return;
}
}
You can use co-routines to do this. To do this, create a function that returns type IEnumerator and include a loop to do what you want:
private IEnumerator foo()
{
while(yourCondition) //for example check if two seconds has passed
{
//move the player on a per frame basis.
yeild return null;
}
}
Then you can call it by using StartCoroutine(foo())
This calls the function every frame but it picks up where it left off last time. So in this example it stops at yield return null on one frame and then starts again on the next: thus it repeats the code in the while loop every frame.
If you want to pause for a certain amount of time then you can use yield return WaitForSeconds(3) to wait for 3 seconds. You can also yield return other co-routines! This means the current routine will pause and run a second coroutine and then pick up again once the second co-routine has finished.
I recommend checking the docs as they do a far superior job of explaining this than I could here