Unity 2D: How to decrement static int in while loop using deltaTime - unity3d

In my Player script I set a static int called boost to be used from any script.
I have a few boost pick up coins through out the game, when the player gets one it fills their boost tank by +1
You need a min of 3 boost coins to start boosting in the vehicle. When the player is using boost, the boost will last for about as many seconds as many boost coins collected. Using the boost should decrement the boost tank.
Player Script
public static int boost;
private void OnTriggerEnter2D(Collider2D otherObject)
{
if (otherObject.tag == "Boost")
{
boost++;
GetComponent<AudioSource>().PlaySound(BoostSound, 5.7F);
Destroy(otherObject.gameObject);
}
}
Button call code
public void BoostButton()
{
StartCoroutine("Use_VehicleBoost");
}
Booster code that a button calls.
IEnumerator Use_VehicleBoost()
{
// Check which boost package player picked
int boostLevel = SecurePlayerPrefs.GetInt("BoostLevel");
if (boostLevel == 0)
{
Debug.Log("Boost Level: None");
yield return null;
}
else if (boostLevel == 1)
{
Debug.Log("Boost Level: 1");
float aceleRate = 400, maxFWD = -2500;
while (Player.boost >= 3)
{
vehicleController.GetComponent<CarMovement>().accelerationRate += aceleRate;
vehicleController.GetComponent<CarMovement>().maxFwdSpeed += maxFWD;
// Meant to slowly take one point/ Time second away from boost tank // Problem is here ----->>>
Player.boost = Player.boost - Mathf.RoundToInt(Time.deltaTime);
yield return null;
}
if (Player.boost <= 0)
{
yield return null;
}
}
yield return null;
}
Problem is here in this line
Player.boost = Player.boost - Mathf.RoundToInt(Time.deltaTime);
It is supposed to be decrementing with seconds from Player.boost. For example if player collects 3 boost coins then boost is active when used it will last for 3 seconds before turning off.
Not exactly sure on what to do here. They told me that in the while loop the deltaTime stays at value 0 because its stuck in one frame? Am I supposed to start a timer variable? Thank you.

Not exactly sure on what to do here. They told me that in the while
loop the deltaTime stays at value 0 because its stuck in one frame? Am
I supposed to start a timer variable?
Yes, I did say that but you have fixed the one frame issue with yield return null; which let's the loop to wait one frame therefore giving Time.deltaTime chance to change. Even with that being fixed, there is still 0 issue I told you but because while (Player.boost >= 3) is still true. That is true because Mathf.RoundToInt(Time.deltaTime); is returning zero. You can verify this with Debug.Log(Mathf.RoundToInt(Time.deltaTime)).
If Player.boost type is an int, change it to a float type, to gradually decrease it with Time.deltaTime directly:
while (Player.boost >= 3)
{
vehicleController.GetComponent<CarMovement>().accelerationRate += aceleRate;
vehicleController.GetComponent<CarMovement>().maxFwdSpeed += maxFWD;
// Meant to slowly take one point/ Time second away from boost tank
Player.boost = Player.boost - Time.deltaTime;
//OR Player.boost -= Time.deltaTime;
yield return null;
}
If Player.boost type is an int but you don't want to change it to float, remove the Time.deltaTime as that's used for float values then do the waiting with WaitForSeconds. With this you can subtract one from boost after each WaitForSeconds call.
while (Player.boost >= 3)
{
vehicleController.GetComponent<CarMovement>().accelerationRate += aceleRate;
vehicleController.GetComponent<CarMovement>().maxFwdSpeed += maxFWD;
// Meant to slowly take one point/ Time second away from boost tank
Player.boost = Player.boost - 1;
//OR Player.boost -= 1;
yield return new WaitForSeconds(1f);
}

Related

stop instantiating prefab if hit limit but continue if prefabs get destroyed

i have a wave system set up and it instantiates a specific amount of enemy prefabs, however say there are supposed to be 100 enemies in total that have to be instantiated on said wave, thats a lot for the player to handle so i want to set a limit (doing it like in COD: Zombies) for how many prefabs are allowed to be active at a time.
IEnumerator SpawnWave (Wave _wave)
{
for (int i = 0; i < _wave.count; i++)
{
if (curZombiesSpawned < maxZombiesSpawned)
{
SpawnEnemy();
Debug.Log("spawnenemy");
}
yield return new WaitForSeconds( 1f/_wave.rate );
}
state = SpawnState.WAITING;
yield break;
}
void SpawnEnemy()
{
Transform _sp = spawnPoints[Random.Range(0, spawnPoints.Length)];
PhotonNetwork.InstantiateRoomObject (enemyObject.name, _sp.position, _sp.rotation);
curZombiesSpawned++;
}
before i instantiate the enemy i want to check if the currently spawned enemies is less than the max amount of enemies spawned at a time.
but lets say on x wave there are supposed to be 50 enemies, but the limit is 24, i only want 24 to spawn, and when i kill x amount of enemies, i want to spawn more enemies till it reaches the limit again. doing that until all 50 are dead, then the next wave starts again
current problem: if the enemy amount on x wave is greater than the limit, it spawns x amount of enemies (spawn the amount of what the limit is set to) but when i kill some (thus destroy the object) they dont keep spawning again.
You can try something like this:
IEnumerator SpawnWave (Wave _wave)
{
int spawnedEnemiesCount = 0;
while(spawnedEnemiesCount < _wave.count)
{
while (curZombiesSpawned < maxZombiesSpawned && spawnedEnemiesCount < _wave.count)
{
SpawnEnemy();
spawnedEnemiesCount++;
Debug.Log("spawnenemy");
}
yield return new WaitForSeconds( 1f/_wave.rate );
}
}

I was wondering why my countdown timer is as I will describe

This is just a portion of my code, and I hope will be easy enough to understand. I found a way to "fix" it, but I still don't understand this:
I set my float countDownTime to 2f. In DisplayTime(), i thought the do-while loop would count from 2 down to 0, but instead starts counting down from 0 to negative numbers. I thought that it would count down and stop when countDownTime reaches 0, as assigned in the while(countDownTime >= 0) but it continues beyond that. Thanks for any feedback or assistance.
// Update is called once per frame
void Update()
{
if (Input.GetKeyDown(KeyCode.Space) && !hasStarted)
{
hasStarted = true;
StartGame();
}
DisplayTime();
}
void DisplayTime()
{
if (timerStart)
{
do
{
countDownTime -= Time.deltaTime;
} while (countDownTime >= 0);
timer2.text = Math.Round(countDownTime, 2).ToString();
}
}
I made changes to it, which is my fix:
// Update is called once per frame
void Update()
{
if (Input.GetKeyDown(KeyCode.Space) && !hasStarted)
{
hasStarted = true;
StartGame();
}
DisplayTime();
}
void DisplayTime()
{
if (timerStart && countDownTime >= 0)
{
do
{
countDownTime -= Time.deltaTime;
} while (countDownTime >= 2);
timer2.text = Math.Round(countDownTime, 2).ToString();
}
}
First off, I don't see where you're initially setting countDownTime to 2.
Second, countDownTime will more likely than not stop on a negative number rather than zero. This is because deltaTime is a measure of the ms that have passed since the last update, and varies quite a bit. So you should round it back up 0 if you don't want to be shown as negative.
Finally, I think you want to execute this code as a Coroutine, otherwise the entire loop will execute in a single frame, which is most likely not what you want.
IEnumerator DisplayTime() {
countDownTime = 2;
while (countDownTime > 0) {
countDownTime -= Time.deltaTime;
timer2.text = Math.Round(countDownTime, 2).ToString();
yield return new WaitForEndOfFrame();
}
}
void Start() {
// start coroutine
StartCoroutine(DisplayTime);
}
If you did not want a coroutine, and wanted to called it each frame from your Update method, you can simply drop the while loop.
void DisplayTime() {
if (timerStart) {
if (countDownTime > 0) {
countDownTime -= Time.deltaTime;
}
}
timer2.text = Math.Round(countDownTime, 2).ToString();
}
The benefit of the Coroutine method is it usually easier to manage. Rather than calling a method every time from Update, you can call it once and just let it run. Since the looping is confined to the method itself you can use DisplayTime to set the initial value of countDownTime to 2.
You need to consider 2 factors.
Update() Method is called on every frame. You are executing do-while loop in DisplayTime() which is called from Update() so do while loop will execute many time (once per frame).
do-while loop works differently from normal while loop. do-while loop first executes code inside do{} and only after checks condition and breaks loop if condition is false. so (even if condition is false) code inside do{} will run at least once once. In case of normal while if condition is false code inside loop will not run.
So in your case on first frame countDownTime is subscribed until it will become be less than 0. (So on screen you immediately see 0 instead of 2, because after first frame its
already 0).
after 1st frame on each frame when do-while is executed code inside do is executed only once and than loop immediately breaks because condition countDownTime >= is already false. So after 1st frame countDownTime -= Time.deltaTime;this code gets executed once per frame (without looping multiple times in do-while), that is why it works "correctly" afterwards.

how can i synchronize between the screenshot and the extraction of gameObject location in Unity?

I’m trying to create data for detection.
not on android.
I’m moving an object on screen and every X seconds I’m saving a screenshot and the coordinate of the object on a json file.
The problem is that the screenshot doesn’t happening in the exact time I’m asking it to but the coordinate finding and json file creation is.
I managed to solve this problem by waiting a fixed frame number between each of the operation.
public static class WaitFor
{
public static IEnumerator Frames(int frameCount)
{
while (frameCount > 0)
{
frameCount--;
yield return null;
}
}
}
public IEnumerator CoroutineAction()
{
yield return StartCoroutine(WaitFor.Frames(3));
}
public void Save()
{
ScreenCapture.CaptureScreenshot(#".\" + counter + ".png");
CoroutineAction();
Vector3 location = Camera.main.WorldToScreenPoint(gameObject.transform.position);
System.IO.File.WriteAllText(#".\" + counter + ".json", toJson(location));
counter++;
}
This solution somewhat works but it still not the exact location of the nodes and I would appreciate it if someone would know a better and cleaner solution.
Another problem I have is that if I try to use the super-resolution option
Example :
ScreenCapture.CaptureScreenshot(#".\" + counter + ".png" , 4);
And multiply the location vector by 4.
The locations will not be in the location of the object in the 2D image, not even close. Which doesn’t make any sense and It would really help me if I could create a bigger image without the use of external libraries to extend my picture by 4 (which work perfectly fine).
Might not be answering your question so far but what I meant with the comment is
currently you 1. use the wait coroutine wrong and 2. it doesn't do anything
To 1.:
public void Save()
{
ScreenCapture.CaptureScreenshot(#".\" + counter + ".png");
CoroutineAction();
Vector3 location = Camera.main.WorldToScreenPoint(gameObject.transform.position);
System.IO.File.WriteAllText(#".\" + counter + ".json", toJson(location));
counter++;
}
What happens actually is:
ScreenCapture.CaptureScreenshot(#".\" + counter + ".png");
Vector3 location = Camera.main.WorldToScreenPoint(gameObject.transform.position);
System.IO.File.WriteAllText(#".\" + counter + ".json", toJson(location));
counter++;
is executed immediately. Than "parallel" (not really ofcourse) the coroutine would run if you used StartCoroutine(CoroutineAction());
But now to 2.:
the routine
public IEnumerator CoroutineAction()
{
yield return StartCoroutine(WaitFor.Frames(3));
}
does absolutetly nothing than waiting 3 frames ...
I guess what you wanted to do instead is something like
// actually start a coroutine
public void Save()
{
StartCoroutine(SaveRoutine());
}
// optional parameter superSize => if not provided uses 1
private IEnumerator SaveRoutine(int superSize = 1)
{
// makes sure the factor is always >= 1
var sizeFactor = Mathf.Max(1,superSize);
ScreenCapture.CaptureScreenshot(#".\" + counter + ".png", sizeFactor);
//Wait for 3 frames
// I think this makes more sense than your dedicated class with the static method
for (int i = 0; i < 4; i++)
{
yield return null;
}
Vector3 location = Camera.main.WorldToScreenPoint(gameObject.transform.position) * sizeFactor;
System.IO.File.WriteAllText(#".\" + counter + ".json", toJson(location));
counter++;
}
Than as also said already I'm not sure because I can't tell if you should get the location in the frame where you start the capture or in the frame where you end. But I guess you rather should get the location beforehand and than start the capture:
// optional parameter superSize => if not provided uses 1
private IEnumerator SaveRoutine(int superSize = 1)
{
// make sure the factor is always at least 1
var sizeFactor = Mathf.Max(1,superSize);
var location = Camera.main.WorldToScreenPoint(gameObject.transform.position) * sizeFactor;
ScreenCapture.CaptureScreenshot(#".\" + counter + ".png", sizeFactor);
//Wait for 3 frames
// I think this makes more sense than your dedicated class with the static method
for (int i = 0; i < 4; i++)
{
yield return null;
}
System.IO.File.WriteAllText(#".\" + counter + ".json", toJson(location));
counter++;
}
Also note that as mentioned in the linked duplicate
The CaptureScreenshot returns immediately on Android. The screen capture continues in the background. The resulting screen shot is saved in the file system after a few seconds.
You are waiting 3 Frames .. that's usully not even a 1/10 of a second (depending on the frame rate obviously).
You should rather wait for actual seconds using WaitForSeconds e.g.
yield return new WaitForSeconds(3);
to wait e.g. 3 seconds. BUT also note that this will not work in combination with suggested Time.timeScale since WaitForSecodns depends on the timeScale as well.
I think you also should not / don't need to use
#".\" + counter + ".png"
in the filename but simply
counter + ".png"
since
On mobile platforms the filename is appended to the persistent data path. See Application.persistentDataPath for more information.
Try to pause your movements like this:
Time.timeScale = 0;
(Game loop continues)
You can wait some frames/time, or see if you get some feedback when the screenshot is done.
This works best if you use Time.deltaTime in your Update() method which is recommended anyway.

Why does my game freeze inside coroutine?

sorry if this question is very basic. I created a coroutine that detects collisions every 5 seconds. Inside this coroutine I would like to execute an animation for 3 seconds so I came up with this bit of code:
private IEnumerator OnCollisionStay2D(Collision2D collision)
{
if (collision.collider.tag == "mama") {
//interrupt movement
float currentTime = Time.time;
float madBegin = Time.time;
while (currentTime - madBegin < 3)
{
personAnimator.runtimeAnimatorController = Resources.Load("Animations/human1mad_0") as RuntimeAnimatorController;
currentTime = Time.time;
Debug.Log(currentTime);
}
isAngry = true;
}
yield return new WaitForSeconds(5);
}
I logged the elaped time in the while loop and currentTime does not seem to be updating. As a result, the elapsed time equals 0 and my while loop never exits. Furthermore since this is executing in a coroutine that is supposed to run concurrently I don't get why a nonexiting while loop would freeze the entire game.
Clarification is much appreciated.
From the Unity docs on Time.time
The time at the beginning of this frame (Read Only)
This means that your Time.time will not change over the course of your loop so the difference between currentTime and madBegin will always be 0.
Time.realtimeSinceStartup may be what you're looking for.

Unity - CheckSphere not working? spawn freezes only on device?

I am trying to spawn a set number of cubes within an ever changing area (a plane, ARKit) and NOT have them overlap. Pretty simple I'd think, and I have this working in Unity editor like so:
My problem is deploy to device (iPhone) and everything is different. Several things aren't working, and I don't know why - it's a relatively simple script. First I thought CheckSphere wasn't working, something with scale being different - but this is how I try to get an empty space:
public Vector3 CheckForEmptySpace (Bounds bounds)
{
float sphereRadius = tierDist;
Vector3 startingPos = new Vector3 (UnityEngine.Random.Range(bounds.min.x, bounds.max.x), bounds.min.y, UnityEngine.Random.Range(bounds.min.z, bounds.max.z));
// Loop, until empty adjacent space is found
var spawnPos = startingPos;
while ( true )
{
if (!(Physics.CheckSphere(spawnPos, sphereRadius, 1 << 0)) ) // Check if area is empty
return spawnPos; // Return location
else
{
// Not empty, so gradually move position down. If we hit the boundary edge, move and start again from the opposite edge.
var shiftAmount = 0.5f;
spawnPos.z -= shiftAmount;
if ( spawnPos.z < bounds.min.z )
{
spawnPos.z = bounds.max.z;
spawnPos.x += shiftAmount;
if ( spawnPos.x > bounds.max.x )
spawnPos.x = bounds.min.x;
}
// If we reach back to a close radius of the starting point, then we didn't find any empty spots
var proximity = (spawnPos - startingPos).sqrMagnitude;
var range = shiftAmount-0.1; // Slight 0.1 buffer so it ignores our initial proximity to the start point
if ( proximity < range*range ) // Square the range
{
Debug.Log( "An empty location could not be found" );
return new Vector3 (200, 200, 200);
}
}
}
}
This again, works perfect in editor. This is the code Im running on my device (without check sphere)
public void spawnAllTiers(int maxNum)
{
if(GameController.trackingReady && !hasTriedSpawn)
{
hasTriedSpawn = true;
int numTimesTried = 0;
BoxCollider bounds = GetGrid ();
if (bounds != null) {
while (tiersSpawned.Length < maxNum && numTimesTried < 70) { //still has space
Tier t = getNextTier ();
Vector3 newPos = new Vector3 (UnityEngine.Random.Range(GetGrid ().bounds.min.x, GetGrid ().bounds.max.x), GetGrid ().bounds.min.y, UnityEngine.Random.Range(GetGrid ().bounds.min.z, GetGrid ().bounds.max.z));
//Vector3 newPos = CheckForEmptySpace (bounds.bounds);
if(GetGrid ().bounds.Contains(newPos)) //meaning not 200 so it is there
{
spawnTier (newPos, t);
}
numTimesTried++;
platformsSpawned = GameObject.FindObjectsOfType<Platform> ();
tiersSpawned = GameObject.FindObjectsOfType<Tier> ();
}
if(tiersSpawned.Length < maxNum)
{
print ("DIDNT REACH - maxed at "+tiersSpawned.Length);
}
}
}
//maybe check for num times trying, or if size of all spawned tiers is greater than area approx
}
//SPAWN NEXT TIER
public void spawnTier(Vector3 position, Tier t) //if run out of plats THEN we spawn up like tree house
{
print ("SUCCESS - spawn "+position+"SPHERE: "+Physics.CheckSphere(position, tierDist, 1 << 0));
// Vector3 pos = currentTier.transform.position; //LATER UNCOMMENT - would be the current tier spawning from
//TO TEST comment to this line ---------------------------------------------------------------------------
#if UNITY_EDITOR
Instantiate (t, position, Quaternion.identity);
anchorManager.AddAnchor(t.gameObject);
#else
//------------------------------------------------------------------------------------------
Instantiate (t, position, Quaternion.identity);
anchorManager.AddAnchor(t.gameObject);
#endif
}
This doesnt crash the device but spawns ALL in the same place. I cant understand why. If I do this, CHECKING for overlap:
public void spawnAllTiers(int maxNum)
{
if(GameController.trackingReady && !hasTriedSpawn)
{
hasTriedSpawn = true;
int numTimesTried = 0;
BoxCollider bounds = GetGrid ();
if (bounds != null) {
while (tiersSpawned.Length < maxNum && numTimesTried < 70) { //still has space
Tier t = getNextTier ();
//Vector3 newPos = new Vector3 (UnityEngine.Random.Range(GetGrid ().bounds.min.x, GetGrid ().bounds.max.x), GetGrid ().bounds.min.y, UnityEngine.Random.Range(GetGrid ().bounds.min.z, GetGrid ().bounds.max.z));
Vector3 newPos = CheckForEmptySpace (GetGrid ().bounds);
if(GetGrid ().bounds.Contains(newPos) && t) //meaning not 200 so it is there
{
spawnTier (newPos, t);
}
numTimesTried++;
platformsSpawned = GameObject.FindObjectsOfType<Platform> ();
tiersSpawned = GameObject.FindObjectsOfType<Tier> ();
}
if(tiersSpawned.Length < maxNum)
{
print ("DIDNT REACH - maxed at "+tiersSpawned.Length);
}
}
}
//maybe check for num times trying, or if size of all spawned tiers is greater than area approx
}
Works great in editor again, but completely freezes the device. Logs are not helpful, as I just get this every time, even though they aren't spawning in those positions:
SUCCESS - spawn (0.2, -0.9, -0.9)SPHERE: False
SUCCESS - spawn (-0.4, -0.9, 0.2)SPHERE: False
SUCCESS - spawn (0.8, -0.9, 0.2)SPHERE: False
SUCCESS - spawn (-0.4, -0.9, -0.8)SPHERE: False
SUCCESS - spawn (0.9, -0.9, -0.8)SPHERE: False
What the hell is happening - why would it freeze only on device like this?
Summary:
it sounds like you needed a short gap between each spawn.
(BTW a useful trick is, learn how to wait until the next frame - check out many articles on it.)
All-time classic answer for this
https://stackoverflow.com/a/35228592/294884
get in to "chunking" for random algorthims
observe the handy line of code at "How to get sets of unique random numbers."
Enjoy
Unrelated issue -
Could it be you need to basically wait a small moment between spawning each cube?
For a time in unity it's very simply Invoke - your code pattern would look something like this:
Currently ...
for 1 to 100 .. spawn a cube
To have a pause between each ...
In Start ...
Call Invoke("_spawn", 1f)
and then
func _spawn() {
if count > 70 .. break
spawn a cube
Invoke("_spawn", 1f)
}
Similar example code - https://stackoverflow.com/a/36736807/294884
Even simpler - https://stackoverflow.com/a/35807346/294884
Enjoy