I am looking for a help with make a delay in Unity in Update function.
I created something like this below. The cube is moving rotates once and then is waiting > rotates once > waiting ....
And there is my question. How i can make cube rotates constantly for some time instead of once. For Example: Wait 2sec, rotating constantly 5sec, Wait 2sec, rota....
I thinked about replace
ForCube.transform.Rotate (10, 10, 10);
by rotating Animation. But I want create it with transform.Rotate. Is there any option to do this?
using UnityEngine;
using System.Collections;
public class Ruch : MonoBehaviour
{
public float speed = 5;
public GameObject ForCube;
bool work = true;
// Use this for initializat
void Start ()
{
ForCube = GameObject.Find ("Cube");
Debug.Log (ForCube);
}
// Update is called once per frame
void Update ()
{
if (work) {
StartCoroutine (WaitSome ());
}
}
private IEnumerator WaitSome ()
{
work = false;
yield return new WaitForSeconds (3f);
ForCube.transform.Rotate (10, 10, 10);
work = true;
}
}
At the moment it looks like to me you are using a StartCoroutine which will work fine, but if you want maybe a little more control over when to rotate and when to stop you can use the Time.deltaTime The time in seconds it took to complete the last frame (Read Only).http://docs.unity3d.com/ScriptReference/Time-deltaTime.html
So basically you have yourself a float variable called Rotate which is lets say 10f
Then inside of your Update function
void Update ()
{
if(Rotate > 0)
{
Rotate -= Time.deltaTime;
ForCube.transform.Rotate (10, 10, 10);
}
}
Then when Rotate is equal to 0 it will stop, but then you can use your work bool to start a new timer.
One big think to take in is to use the Time.deltaTime, if you don't use this and you just use an int or whatever variable type the timer will differ depending on the FPS of the game for the player.
Let me know if you need anymore help :)
Instead of using coroutines, you can do it directly in the update function like this:
[SerializeField]
private float timeToWait; //In seconds
[SerializeField]
private float timeToRotate; //In seconds
private float timer = 0;
private bool waiting = true; //Set this to false if you want to rotate first, wait later
void Update()
{
if(!waiting) RotateYourObjectALittleBit(); //Call your own function or do whatever you want
timer += Time.deltaTime;
if(timer >= timeToWait && waiting) {
waiting = false;
timer = 0;
}
else if(timer >= timeToRotate && !waiting) {
waiting = true;
timer = 0;
}
}
This code is untested, so please let me know if you require further clarification or if it doesn't work.
Thanks everyone for fast Answer and help to solve my problem. I really appreciate that.
I created something like this:
Version 1.0
When the space key is down the cube start rotating for RotateTime, after this the Timer reset to start value(RotationTime), and u can click again button for rotate.
using UnityEngine;
using System.Collections;
public class Ruch : MonoBehaviour
{
public GameObject ForCube;
public float RotateTime = 5;
public float Timer = 0;
private bool Rotate = false;
// Use this for initializat
void Start ()
{
Timer = RotateTime;
ForCube = GameObject.Find ("Cube");
Debug.Log (ForCube);
}
// Update is called once per frame
void Update ()
{
//Start Rotating When Press Space Key
if (Input.GetKeyDown (KeyCode.Space)) Rotate = true;
else if (!(Input.GetKeyDown (KeyCode.Space))&&Timer <=0) Rotate = false;
RotateForSec (ref Timer);
}
//Function to Rotate for X sec
void RotateForSec(ref float sec)
{
if (Rotate && sec > 0) {
Debug.Log (Time.time);
ForCube.transform.Rotate (10, 10, 10);
sec -= Time.deltaTime;
}
//Reset Rotating Time after rotating
if (!Rotate && sec <= 0) Timer = RotateTime;
}
}
Version 2.0
The rotating of cube continues for 5 seconds and then automatically without pressing a key it wait some time and start over rotating. Again again and again ...
public GameObject ForCube;
public float RotateTime = 5;
public float Timer = 0;
public float PauseTime = 0;
private bool Pause = false;
private bool Rotate = true;
// Use this for initializat
void Start ()
{
Timer = RotateTime;
PauseTime = RotateTime;
ForCube = GameObject.Find ("Cube");
Debug.Log (ForCube);
}
// Update is called once per frame
void Update ()
{
//Start Rotating When Press Space Key
if (Rotate)
Pause = false;
else if (!Rotate) {
Pause = true;
}
if (!Pause)
RotateForSec (ref Timer);
else RotatePause ();
}
//Function to pause PauseTime sec
void RotatePause()
{
if (PauseTime > 0) {
PauseTime -= Time.deltaTime;
} else {
Pause = false;
Rotate = true;
PauseTime = RotateTime;
}
}
//Function to Rotate for X sec
void RotateForSec(ref float sec)
{
if (Rotate && sec > 0) {
Debug.Log (Time.time);
ForCube.transform.Rotate (10, 10, 10);
sec -= Time.deltaTime;
} else Rotate = false;
//Reset Rotating Time after rotating
if (!Rotate && sec <= 0) Timer = RotateTime;
}
}
Its working but what you think about that, is it done correctly or it is a bad way?
Related
I have a script which updates the slider value based on slowMotionTimeLeft, to decreaseslowMotionTimeLeft. I am using a for loop with a coroutine which has a delay of 1 second, like this:
IEnumerator DecreaseSlowMotionTime()
{
for(int i = 0; i < 5f; i++)
{
slowMotionTimeLeft -= 1f; // decrease slow motion
///<summary>
/// if slow motion time is less than or equal to 0 break the loop
/// </summary>
if (slowMotionTimeLeft <= 0f)
{
onSlowMotion = false;
break;
}
yield return new WaitForSecondsRealtime(1f); // delay
}
}
now I have a slider, I want to smoothly update that value based on slowMotiontimeLeft, but it is not smooth, it is updating every second like the coroutine, I tried to lerp the values but it does not work.
Here is the script:
// Slow Motion Bar
[Header("Slow Motion Slider Bar")]
[SerializeField] private Slider slowMoLeft;
[SerializeField] private float slowSliderSmoothSpeed = 0.125f;
// Other
private Player playerScript;
void Awake()
{
playerScript = FindObjectOfType<Player>();
}
void Update()
{
UpdateSlowMoSlider();
}
public void UpdateSlowMoSlider()
{
slowMoLeft.value = playerScript.slowMotionTimeLeft;
}
any tips of what can I do? do I need to update the coroutine timer to make wait 0.1 seconds?
I'd do it in Update:
// Remove the IEnumerator DecreaseSlowMotionTime(), instead add this, or if you already use Update() add the content of this method to Update()
public void Update()
{
if (slowMotionTimeLeft > 0)
{
slowMotionTimeLeft -= 1f * Time.deltaTime;
}
}
// Slow Motion Bar
[Header("Slow Motion Slider Bar")]
[SerializeField] private Slider slowMoLeft;
[SerializeField] private float slowSliderSmoothSpeed = 0.125f;
// Other
private Player playerScript;
void Awake()
{
playerScript = FindObjectOfType<Player>();
}
void Update()
{
slowMoLeft.value = playerScript.slowMotionTimeLeft;
}
You can make your code better:
Instead of updating your slowMotionTimeLeft once every second, you can do something like this:
IEnumerator DecreaseSlowMotionTime()
{
while (onSlowMotion)
{
// decrease slow motion without considering Time.timeScale
slowMotionTimeLeft -= Time.unscaledDeltaTime;
// if slow motion time is less than or equal to 0 break the loop
if (slowMotionTimeLeft <= 0f)
{
onSlowMotion = false;
}
// will execute again in the next frame
yield return null;
}
}
Then your slider will reduce smoothly and at a 1:1 time speed.
I know a another way
Just download DoTweenModuleUI and you are good to go
https://github.com/fisheraf/Stroids/blob/master/Stroids/Assets/Demigiant/DOTween/Modules/DOTweenModuleUI.cs
just save this script in any external plugins folder and use
yourimageName.DoFillAmout(TargetValue,Duration); That's it
Something like this imageSlider.DOFillAmount(perSecElixir / 10, 0.5f);
I am creating a 3D Shooter and I am trying to make an enemy deal damage to the player every set seconds. I have made the enemy deal damage with a raycast but it deals the damage way too fast.
I thought using yield return new WaitForSeconds(2) would take 1 damage away from the player every 2 seconds but it deals damage to the player a lot faster.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class EnemyMove : MonoBehaviour
{
public Transform target;
public Transform player;
public float enemySpeed;
public int moveTrigger = 1;
public int isAttacking;
public float distanceFromPlayer;
void Update()
{
distanceFromPlayer = Vector3.Distance(target.transform.position, player.transform.position);
if (distanceFromPlayer <= 10 && moveTrigger == 1)
{
transform.LookAt(target);
StartCoroutine(EnemyDamage());
}
if (distanceFromPlayer <10 && moveTrigger == 1 && distanceFromPlayer >3)
{
transform.Translate(Vector3.forward * enemySpeed * Time.deltaTime);
}
}
IEnumerator EnemyDamage()
{
RaycastHit PlayerHit;
if (Physics.Raycast(target.transform.position, target.transform.forward, out PlayerHit))
{
Debug.Log(PlayerHit.transform.name);
Target target = PlayerHit.transform.GetComponent<Target>();
if (target != null)
{
yield return new WaitForSeconds(2);
GlobalHealth.playerHealth -= 1;
yield return null;
}
}
}
}
You are starting a coroutine in every update loop so every frame your damage coroutine gets called and applies your 1 damage after 2 seconds.
If you want a damage over time effect the suggestion of using an game tick timer is correct, but if you want your enemy to only be able to attack every x seconds you need to implement some kind of cooldown on your damage function. For example add up Time.deltaTime until the time you want has past, and only then the enemy is able to deal damage again. You can do that with a boolean.
As mentioned in other answers, you are starting a new coroutine each frame. You should do all your waiting and looping inside your coroutine, as execution exits and re-enters your coroutine from the yeild statment. This is how you would write this to work
// in update
if (distanceFromPlayer <= 10 && moveTrigger == 1){
transform.LookAt(target);
if(!isAttacking)
StartCoroutine(EnemyDamage());
}
IEnumerator EnemyDamage()
{
isAttacking = true;
while(distanceFromPlayer <= 10){ // in range
RaycastHit PlayerHit;
if (Physics.Raycast(target.transform.position, target.transform.forward, out PlayerHit)){
Target target = PlayerHit.transform.GetComponent<Target>();
if (target != null){
GlobalHealth.playerHealth -= 1;
yield return new WaitForSeconds(2);
}
}
}
isAttacking = false; // out of range
yield return null;
}
You can use FixedUpdate → set a local variable bool alreadyShoot = false.
In your EnemyDamage() add a if(!alreadyShoot) before damages application and set alreadyShoot to true after damages application.
In FixedUpdate you can set alreadyShoot to false.
You can choose an other solution to set alreadyShoot to false instead of FixedUpdate (for example a coroutine that trigger each seconds, ...)
in update loop, check when two seconds have passed when the player is in range
float timeInRange = 0.0f;
bool inRange = false;
if (distanceFromPlayer <= 10 && moveTrigger == 1)
{
inRange = true;
}
else
{
inRange = false;
timeInRange = 0;
}
if (inRange)
{
timeInRange += Time.DeltaTime;
}
if (timeInRange > 2.0f)
{
GlobalHealth.playerHealth -= 1;
timeInRange = 0.0f;
}
I need to make an object flicker every X seconds. So far I have the code for the actual flickering and it works great, however I need the flicker to come on for X seconds, then turning off, then come on for X seconds. Similar to turning a strobe light on (it flickers), then off.
I know something like invokeRepeating would work however the flickering relies on being in the FixedUpdate for it to run every frame.
For anyone wondering I'm actually trying to do something with image modulation and attention. Here is what I have so far:
public class scrFlicker : MonoBehaviour {
public SpriteRenderer sRen;
public float cycleHz; // Hz, the mesurement of cycles.
private float dtime = 0; // delta time
private Color alpha;
// Use this for initialization
void Start () {
sRen = GetComponent<SpriteRenderer>();
GetComponent<SpriteRenderer>().enabled = false;
alpha = sRen.color;
alpha.a = 0.4f;
sRen.color = alpha;
}
// Update is called once per frame
void FixedUpdate () {
startFlicker();
}
void startFlicker() {
dtime += Time.deltaTime;
float wave = Mathf.Sin((dtime * 2.0f * Mathf.PI) * cycleHz);
if(wave > 0.0f) {
GetComponent<SpriteRenderer>().enabled = true;
} else {
GetComponent<SpriteRenderer>().enabled = false;
}
if (wave == 0.0f) {
dtime = 0.0f;
}
}
}
You can create something like a timer below to manage the on and off time:
float toggletime;
void FixedUpdate () {
toggletime += Time.deltaTime;
if (toggletime < 2) // flicker will occur from 0-2 seconds
startFlicker();
else if (toggletime > 4) // nothing will occur between 2-4 seconds
toggletime = 0; // reset timer after 4 seconds have passed
}
"I know something like invokeRepeating would work however the flickering relies on being in the FixedUpdate for it to run every frame."
FixedUpdate is for Physics. Sure you can use it for other purposes but if they not physics related then it is not the primary purpose.
Invoke would actually do just fine, you have full control of it.
float timer = 2f;
bool isOn = false;
void Start()
{
Invoke("Method", timer);
}
void Method()
{
// you can change timer if needed
this.timer = Random.Range(0f, maxTimer);
this.isOn = !this.isOn;
Invoke("Method", this.timer);
}
void CancelMethodAndResetTimerForAnyReason()
{
CancelInvoke();
this.timer = Random.Range(0f, maxTimer);
Invoke("Method", this.timer);
}
I'm wondering how to smoothly zoom in and smoothly zoom out on button press in Unity3d using c#. I've got zooming part down already, but not sure how to make a transition of zooming in and out smooth. As an example, I'd like it to zoom in as smooth as it is in ARMA or DayZ game.
Here's my code:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class zoomIN : MonoBehaviour {
public Camera cam;
// Use this for initialization
void Start () {
}
// Update is called once per frame
void Update () {
if (Input.GetMouseButton (1)) {
cam.fieldOfView = 20;
}
if (Input.GetMouseButtonUp (1)) {
cam.fieldOfView = 60;
}
}
}
I'd appreciate any help!
Thanks and Merry Xmas!
Use coroutine to do this. You can use it to enable the speed or duration of the zooming. Perform a Mathf.Lerp between cam.fieldOfView and the destination( 20 or 60) depending on if the key is pressed or released.
Note: You must change Input.GetMouseButton to Input.GetMouseButtonDown otherwise your first if statement will be running every frame while the right mouse button is held down. I think you want to be true once only.
public Camera cam;
Coroutine zoomCoroutine;
// Update is called once per frame
void Update()
{
if (Input.GetMouseButtonDown(1))
{
//Stop old coroutine
if (zoomCoroutine != null)
StopCoroutine(zoomCoroutine);
//Start new coroutine and zoom within 1 second
zoomCoroutine = StartCoroutine(lerpFieldOfView(cam, 20, 1f));
}
if (Input.GetMouseButtonUp(1))
{
//Stop old coroutine
if (zoomCoroutine != null)
StopCoroutine(zoomCoroutine);
//Start new coroutine and zoom within 1 second
zoomCoroutine = StartCoroutine(lerpFieldOfView(cam, 60, 1f));
}
}
IEnumerator lerpFieldOfView(Camera targetCamera, float toFOV, float duration)
{
float counter = 0;
float fromFOV = targetCamera.fieldOfView;
while (counter < duration)
{
counter += Time.deltaTime;
float fOVTime = counter / duration;
Debug.Log(fOVTime);
//Change FOV
targetCamera.fieldOfView = Mathf.Lerp(fromFOV, toFOV, fOVTime);
//Wait for a frame
yield return null;
}
}
A simple way to get your smooth zoom animation is by performing the zoom operation over multiple frames. So instead of changing the fieldOfView from 20 to 60 right away, increase the fieldOfView with 5 every frame until you reach your target of 60. (To lengthen the animation you can of course take a smaller number than 5.) So based on the mouse input you can keep a state _zoomedIn and based on that state and on the current fieldOfView you can determine whether you still need to add or substract to the value. Which gives you something like the following code: (not tested)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class zoomIN : MonoBehaviour {
public Camera cam;
private bool _zoomedIn = false;
private int _zoomedInTarget = 60;
private int _zoomedOutTarget = 20;
// Update is called once per frame
void Update () {
if (Input.GetMouseButtonDown (1))
_zoomedIn = true;
if (Input.GetMouseButtonUp (1)) {
_zoomedIn = false;
}
if (_zoomedIn) {
if (cam.fieldOfView < _zoomedInTarget)
cam.fieldOfView += 5;
} else {
if (cam.fieldOfView > _zoomedOutTarget)
cam.fieldOfView -= 5;
}
}
The first script is for the F pressing and calling the ScaleChange:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class DroidMove : MonoBehaviour
{
public GameObject droid;
public ChangeScale changeScale;
private bool toDisplay = false;
private void Start()
{
droid.transform.localScale = new Vector3(0, 0, 0);
}
private void Update()
{
if (Input.GetKeyDown(KeyCode.F))
{
toDisplay = !toDisplay;
changeScale.Scale(toDisplay);
}
}
}
The second script make the scaling:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ChangeScale : MonoBehaviour
{
public GameObject objectToScale;
private float _currentScale = InitScale;
private const float TargetScale = 0.7f;
private const float InitScale = 0f;
private const int FramesCount = 10;
private const float AnimationTimeSeconds = 0.1f;
private float _deltaTime = AnimationTimeSeconds / FramesCount;
private float _dx = (TargetScale - InitScale) / FramesCount;
private IEnumerator ScaleUp()
{
bool upscaling = true;
while (upscaling)
{
_currentScale += _dx;
if (_currentScale > TargetScale)
{
upscaling = false;
_currentScale = TargetScale;
}
objectToScale.transform.localScale = Vector3.one * _currentScale;
yield return new WaitForSeconds(_deltaTime);
}
}
private IEnumerator ScaleDown()
{
bool downscaling = true;
while (downscaling)
{
_currentScale -= _dx;
if (_currentScale < InitScale)
{
downscaling = false;
_currentScale = InitScale;
}
objectToScale.transform.localScale = Vector3.one * _currentScale;
yield return new WaitForSeconds(_deltaTime);
}
}
public void Scale(bool scaleUp)
{
if (scaleUp)
{
StartCoroutine(ScaleUp());
}
else
{
StartCoroutine(ScaleDown());
}
}
}
The problem is if I press on F and it start scaling up and not finished yet the scaling and I press F again instead start scaling down it's like pausing and if I keep pressing F it will keep scaling up each time a bit.
But what I want to do is when I press first time F start scaling up and if in the middle while scaling i'm pressing F again then from the current scaling point start scaling down and if I press F again then up and down like a smooth switch.
But now it's just pausing each time I press F many times.
It's like I need to wait now for the scaling to finish first before I can press F again if not it will just pause it and i want that each time I press F it will switch the scaling up/down.
You don't need multiple functions for scaling up and down. One should do it. Just make it take a scale amount as parameter then re-use that function. The scale Min and Max should be a Vector3 not float since that's how scale is represented and you want to cover all three axis (x,y,z).
How to fix the problem of scaling up taking so long to finish and not switching direction when key is pressed:
When you start a coroutine, get a reference to that running coroutine. Before you start the coroutine again, use the old reference to stop the old one. It's as simple as that. It's much better to re-write the whole code than to fix the existing one you have.
Below is a simplified version of your code. Make sure to set the minSize, maxSize and objectToScale variables from the Editor. See code comments if you have any question.
public GameObject objectToScale;
public Vector3 minSize;
public Vector3 maxSize;
private bool scaleUp = false;
private Coroutine scaleCoroutine;
// Use this for initialization
void Update()
{
if (Input.GetKeyDown(KeyCode.F))
{
//Flip the scale direction when F key is pressed
scaleUp = !scaleUp;
//Stop old coroutine
if (scaleCoroutine != null)
StopCoroutine(scaleCoroutine);
//Scale up
if (scaleUp)
{
//Start new coroutine and scale up within 5 seconds and return the coroutine reference
scaleCoroutine = StartCoroutine(scaleOverTime(objectToScale, maxSize, 5f));
}
//Scale Down
else
{
//Start new coroutine and scale down within 5 seconds and return the coroutine reference
scaleCoroutine = StartCoroutine(scaleOverTime(objectToScale, minSize, 5f));
}
}
}
IEnumerator scaleOverTime(GameObject targetObj, Vector3 toScale, float duration)
{
float counter = 0;
//Get the current scale of the object to be scaled
Vector3 startScaleSize = targetObj.transform.localScale;
while (counter < duration)
{
counter += Time.deltaTime;
targetObj.transform.localScale = Vector3.Lerp(startScaleSize, toScale, counter / duration);
yield return null;
}
}