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);
Related
Alright, so here's what's happening: When I hit play and left click to shoot, unity editor freezes and I have to do the old Ctrl + Alt + Del, now, I am almost certain this script is the source of the issue, because when a bullet is shot, this script is immediately added to it, so here's the script(It's called BulletLife.cs, just letting you know)
using System.Timers;
using System.Collections;
using System.Collections.Generic;
using System.Threading;
using UnityEngine;
public class BulletLife : MonoBehaviour
{
public GameObject bullet;
public double bulletLifeSpan = 3;
bool bulletLifeEnded;
public LayerMask targetMask;
bool hasHitTarget;
// Start is called before the first frame update
void Start()
{
var bulletAge = new System.Timers.Timer(bulletLifeSpan * 1000);
bulletAge.Elapsed += OnTimedEvent;
bulletAge.AutoReset = false;
while(hasHitTarget == false && bulletLifeEnded == false) {
hasHitTarget = Physics.CheckSphere(bullet.transform.position, bullet.transform.localScale.y, targetMask);
}
Destroy(bullet);
Debug.Log("Finish");
}
private void OnTimedEvent(System.Object Source, ElapsedEventArgs e) {
bulletLifeEnded = true;
}
}
Also, here's the Shoot.cs script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Shoot : MonoBehaviour
{
public Transform gun;
public GameObject bullet;
public LayerMask targetMask;
public float bulletSpeed = 1000f;
bool hasHitTarget = false;
// Update is called once per frame
void Update()
{
if(Input.GetButtonDown("LeftClick")) {
GameObject bulletInstance;
bulletInstance = Instantiate(bullet, gun.position, new Quaternion(gun.rotation.w, gun.rotation.x, gun.forward.y, gun.rotation.z));
bulletInstance.AddComponent<Rigidbody>();
bulletInstance.GetComponent<Rigidbody>().useGravity = false;
bulletInstance.GetComponent<Rigidbody>().AddForce(gun.up * bulletSpeed);
bulletInstance.AddComponent<BulletLife>();
bulletInstance.GetComponent<BulletLife>().bullet = bulletInstance;
}
}
}
NOTE: I am using Unity 2019.4.15f1
Well everytime you instantiate a bullet in Start you do
while(hasHitTarget == false && bulletLifeEnded == false)
{
hasHitTarget = Physics.CheckSphere(bullet.transform.position, bullet.transform.localScale.y, targetMask);
}
this loop will never finish since none of the conditions is changed inside the loop. There either is a hit or not .. but then the parameters for the raycast are never changed, the position isn't updated since you are still in the same frame => endless loop => freeze the main thread completely.
What you rather wanted to do is move that thing to Update which is called once a frame like e.g.
//public GameObject bullet; // not needed
public double bulletLifeSpan = 3;
//bool bulletLifeEnded; // not needed
public LayerMask targetMask;
//bool hasHitTarget; // not needed
void Start()
{
var bulletAge = new System.Timers.Timer(bulletLifeSpan * 1000);
bulletAge.Elapsed += OnTimedEvent;
bulletAge.AutoReset = false;
}
private void Update()
{
if(Physics.CheckSphere(transform.position, transform.localScale.y, targetMask))
{
Destroy(gameObject);
Debug.Log("Finish");
}
}
private void OnTimedEvent(System.Object Source, ElapsedEventArgs e)
{
Destroy(gameObject);
Debug.Log("Finish");
}
Or make it a single Coroutine
// If Start returns IEnumerator it is automatically started as Coroutine
// So no need to start an extra routine
private IEnumerator Start()
{
// Keeps track of how long your bullet exists already
var bulletAge = 0f;
while(bulletAge < bulletLifeSpan && !Physics.CheckSphere(transform.position, transform.localScale.y, targetMask))
{
// Increase by the time passed since last frame
bulletAge += Time.deltaTime;
// "Pause" this routine, render this frame
// and continue from here in the next frame
yield return null;
}
Destroy(gameObject);
Debug.Log("Finish");
}
Btw note that in Shoot you can shorten this a lot
void Update()
{
if(Input.GetButtonDown("LeftClick"))
{
// Note that your quaternion made no sense -> simply pass in the gun.rotation
var bulletInstance = Instantiate(bullet, gun.position, gun.rotation);
var rb = bulletInstance.AddComponent<Rigidbody>();
rb.useGravity = false;
rb.AddForce(gun.up * bulletSpeed);
var life = bulletInstance.AddComponent<BulletLife>();
// Assigning the gameObject reference is completely unnecessary
// within BulletLife simply use "gameObject" as show before
}
}
You could shorten this even more by making sure these components already exist on your prefab object and are configured correctly. Then you wouldn't need any of these line but just Instantiate it.
And finally you shouldn't use thisCheckSphere at all but rather let Unity handle its Collision detection itself and use OnCollisionEnter and configure your Collision Layers according to your needs!
The issue with your solution is: If your bullet moves fast it might simply pass a target without your CheckSphere noting it namely if its velocity is higher then localScale.y * 2.
Your Start method is blocking, thus freezing your game.
You'll have to use Update or a Coroutine to make your hit tests.
public class BulletLife : MonoBehaviour
{
public GameObject bullet;
public double bulletLifeSpan = 3;
bool bulletLifeEnded;
public LayerMask targetMask;
bool hasHitTarget;
// Start is called before the first frame update
void Start()
{
StartCoroutine(CheckHit(0, bulletLifeSpan));
}
private IEnumerator CheckHit(float interval, float lifetime){
bool checkEveryFrame = interval <= 0;
WaitForSeconds wait = checkEveryFrame ? null : new WaitForSeconds(interval);
while(lifetime > 0){
yield return wait;
lifetime = lifetime - (checkEveryFrame ? Time.deltaTime : interval);
hasHitTarget = Physics.CheckSphere(bullet.transform.position, bullet.transform.localScale.y, targetMask);
}
bulletLifeEnded = true;
Destroy(bullet);
Debug.Log("Finish");
}
}
When the game starts, a random waypoint is selected from an array. The camera should then rotate to face the selected random waypoint and start moving towards it.
Once the camera has reached the waypoint, it should wait 3 seconds before rotating to face and move towards the next random waypoint.
The problem I have is in Start(). The camera does not rotate to face the first waypoint before it starts moving towards it. Instead, it moves towards the first waypoint backwards. Then, when it reaches the waypoint, it waits 3 seconds to rotate and move towards the next waypoint.
It's working fine except that the camera does not rotate to face the first selected random waypoint. It's moving to it backward without first rotating to face it.
Here's my code:
The waypoints script
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Waypoints : MonoBehaviour
{
public GameObject[] waypoints;
public GameObject player;
public float speed = 5;
public float WPradius = 1;
public LookAtCamera lookAtCam;
private int current = 0;
private bool rot = false;
public void Init()
{
waypoints = GameObject.FindGameObjectsWithTag("Target");
if(waypoints.Length > 0)
{
StartCoroutine(RotateFacingTarget(waypoints[UnityEngine.Random.Range(0, waypoints.Length)].transform));
}
}
void Update()
{
if (waypoints.Length > 0)
{
if (Vector3.Distance(waypoints[current].transform.position, transform.position) < WPradius)
{
current = UnityEngine.Random.Range(0, waypoints.Length);
rot = false;
StartCoroutine(RotateFacingTarget(waypoints[current].transform));
if (current >= waypoints.Length)
{
current = 0;
}
}
if (rot)
transform.position = Vector3.MoveTowards(transform.position, waypoints[current].transform.position, Time.deltaTime * speed);
}
}
IEnumerator RotateFacingTarget(Transform target)
{
yield return new WaitForSeconds(3);
lookAtCam.target = target;
rot = true;
}
}
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;
//values for internal use
private Quaternion _lookRotation;
private Vector3 _direction;
// Update is called once per frame
void Update()
{
//find the vector pointing from our position to the target
if (target)
{
_direction = (target.position - transform.position).normalized;
//create the rotation we need to be in to look at the target
_lookRotation = Quaternion.LookRotation(_direction);
//rotate us over time according to speed until we are in the required rotation
transform.rotation = Quaternion.Slerp(transform.rotation, _lookRotation, Time.deltaTime * RotationSpeed);
}
}
}
How can I fix this?
Let's assume that Waypoints.Init() is being called and your waypoints variable has an array of 3.
Waypoints.Init() starts a coroutine
Your coroutine waits 3 seconds
After 3 seconds, you set your camera target which Slerps to face the position
Update on its first frame says waypoints.Length > 0 == true
It's not close to its target, and rot is false, so it does not move
Now, you're waiting 3 seconds, not rotating, and not moving.
Your coroutine's 3 second wait time is up and starts the rotation toward your target
rot is now true at the start of your rotation, so your Update method starts moving toward the target as well
It would seem that your logic is off in how the order of operations works. If it needs to act as you describe, I suggest that you operate on the target differently.
I've implemented the following using an enum:
Waypoints
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;
private int current = 0;
private bool isCameraRotating = false;
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;
cameraState = CameraState.Rotating;
break;
}
// This state only needs to detect when the camera has completed rotation to start movement
case CameraState.Rotating:
{
if (lookAtCam.IsFinishedRotating)
cameraState = CameraState.StartMoving;
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");
}
}
LookAtCamera
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;
}
}
}
i have the infinite background set up with the tiling method which is moving from the right to the left, what i want to do is that if the player reaches an amount of score(like 1000), i want to change the background via a transition, like the next background will move in from the left to the right like the other one did, but when it moves in, it stays till the player reaches another amount of score(like 2000). Then it happens again but now with an other texture.
The current script looks like this:
public class BackgroundScroll : MonoBehaviour {
Material material;
public Material materialChange;
Vector2 offset;
public int xVelocity, yVelocity;
private float backgroundSpeed = 1f;
public static float speed = 10f;
private float speedUp = 0.1f;
private int changed = 0;
private void Awake()
{
material = GetComponent<Renderer>().material;
speedUp = 0.1f;
}
// Use this for initialization
void Start () {
changed = 0;
//offset = new Vector2(xVelocity, yVelocity);
StartCoroutine(Speed());
}
// Update is called once per frame
void Update () {
offset = new Vector2(xVelocity, yVelocity);
material.mainTextureOffset += offset * Time.deltaTime * backgroundSpeed;
BackgroundChange();
}
void BackgroundChange()
{
if (ScoreHandler.score >= 100 && changed == 0)
{
material.mainTexture = materialChange.mainTexture;
changed = 1;
}
}
IEnumerator Speed()
{
yield return new WaitForSeconds(20);
speed += speedUp;
backgroundSpeed += speedUp;
}
}
This line changes the background, but there is no animation or transition at all, it just changes it: material.mainTexture = materialChange.mainTexture;
So my question is that how can i change the background with an animation, so it slides it then it stays till the other one slides in and that stays. etc...
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;
}
}