How do I execute a line of code repeatedly? - c#

I am coding a stamina bar in my game and have had it all hooked up, though the tutorial I followed for it was specifyed for a burst of stamina used. I was hoping I would be able to slowly deminish it until there is none left. I tried to apply a while loop hooked up to a coroutine though it freezes unity. I just want way to lose stamina consistantly while a button is held. Its unable to be put under the void update because the code to check if shift is activated only plays it once. I activate the script by using "stamina.instance.UseStamina(5);". Heres the code.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class stamina : MonoBehaviour
{
public Slider staminabar;
private int maxstamina = 100;
private int currentstamina;
public static stamina instance;
private WaitForSeconds regenTick = new WaitForSeconds(0.1f);
private Coroutine regen;
private void Awake()
{
instance = this;
}
// Start is called before the first frame update
void Start()
{
currentstamina = maxstamina;
staminabar.maxValue = maxstamina;
staminabar.value = maxstamina;
}
// Update is called once per frame
void Update()
{
}
public void UseStamina(int amount)
{
if (currentstamina - amount >= 0)
{
currentstamina -= amount;
staminabar.value = currentstamina;
if (regen != null)
StopCoroutine(regen);
regen = StartCoroutine(regenstamina());
}
else
{
Debug.Log("out of stamina!");
}
}
private IEnumerator regenstamina()
{
yield return new WaitForSeconds(2);
while (currentstamina < maxstamina)
{
currentstamina += maxstamina / 100;
staminabar.value = currentstamina;
yield return regenTick;
}
regen = null;
}
}
I was expecting with a while loop for a smooth way for the slider to go down instead it just froze with me needing to go into taskmanager to close unity. I also tried putting a small wait for seconds hoping that it was a issue with lag.

If I'm not mistaken, there's a couple of issues that I see.
Firstly, your stamina bar value should be a value between 0f and 1f. But your code has set a maxValue, which I'm not sure where that's coming from. So maybe you're using a different stamina bar? Not sure. But the default Slider uses a value between 0f and 1f.
Moving on to the second issue. You're doing divisions on integers. If you use any stamina, and your current stamina is less that the max stamina (which it almost always will be), then your code is always going to get stuck. For example, you use 1 stamina, then your calculation is 99 / 100 = 0. YHour current stamina will never be increased. That's just the way integer calculations work.
I also noted your UseStamina method reduces the stamina by the correct amount, but then your co-routine is trying to do modify the current stamina again. It's doing the caluclations twice, and only the first is the correct one.
You can cast to floats first, then back to an int. Or you could use a different measurement. For example:
public bool UseStamina ( int amount )
{
if ( currentstamina - amount <= 0 )
{
Debug.Log ( "No enough stamina!" );
return false;
}
if ( regen != null )
StopCoroutine ( regen );
currentstamina -= amount;
regen = StartCoroutine ( regenstamina ( ) );
return true;
}
private IEnumerator regenstamina ( )
{
yield return new WaitForSeconds ( 2 );
var timer = 0f;
while ( timer < 1f )
{
timer += Time.deltaTime;
staminabar.value = Mathf.Lerp ( staminabar.value, ( float ) currentstamina / maxstamina, timer );
yield return null;
}
regen = null;
}
Not that using Lerp like this is often seen as an error. You generally want set starting and ending points, but by having the starting point being updated to the current value, you end up with a bit of an 'easing' effect. Play around with that if the effect isn't what you're looking for.
Also note, the code was written but not tested... but, I stand by my previous comments.

I just want way to lose stamina consistantly while a button is held
I had this exact problem, when I started Unity development two years ago.
Perhaps, as stated by someone, this answer might not directly help OP. But, the general community of stackoverflow might benefit from this answer. I'm sharing this because it's the most readable and performant solution I'm currently using. If you have other approaches or better solutions readability/performance wise, please let me know.
We should refrain from using coroutines due to reasons stated in Why Rx?:
Using Coroutine is not good practice for asynchronous operations for the following (and other) reasons:
Coroutines can't return any values, since its return type must be IEnumerator.
Coroutines can't handle exceptions, because yield return statements cannot be surrounded with a try-catch construction.
Also stated that:
UniRx helps UI programming with uGUI. All UI events (clicked, valuechanged, etc) can be converted to UniRx event streams. And streams are cheap and everywhere.
You can achieve on hold operation without coroutine if you use UniRx. For example:
button.OnPointerDownAsObservable()
.Throttle(TimeSpan.FromSeconds(0.4f)) // How many seconds to hold to trigger
.SelectMany(_ => Observable.Interval(TimeSpan.FromSeconds(0.04f))) // Diminishing speed
.TakeUntil(button.OnPointerUpAsObservable()) // Execute until button is released
.RepeatUntilDestroy(button)
.Subscribe(_ =>
{
if ((currentstamina - amount) > 0)
{
currentstamina -= amount;
staminabar.value = currentstamina;
}
else{
currentstamina = 0;
staminabar.value = currentstamina;
}
});
The above code might not meet your exact needs if you just copy-paste. It's just an example but I really hope it gives you a general idea and knowledge about other ways of achieving what you're looking for.

Related

C# - Unable to increment an int beyond 1. Console prints 1 and it does not increase, despite triggering multiple times

The game is simple, your Player has a running animation and must jump to dodge obstacles, but isn't moving. The obstacles and background are.
My gut instinct is that it is out of scope somehow or is perhaps overflowing - the score increment was increasing far beyond what I had anticipated when I had the AddScore() inside of the Destroy(gameObject); if condition instead of its own function.
However, at this point I am very confused why it isn't working. As a bonus, I cannot get Audio to play from the second commented bit (Value cannot be null.) As for why that happens, no idea. I definitely have the source I have it attached to to the prefab that is spawned, and said spawn should trigger that sound to play when it passes under the player when they jump, I originally thought that there was an issue where the object was deleted before it could reference its audio source but I am unsure.
Edit: I am going to leave the 'bonus' issue above even though I literally fixed it as I typed this up. (I had to add an Audio Source component to the prefab I was spawning.)
I still, for the life of me, cannot get an integer to go above 1 and print to the console. It might be driving me a little insane. Please help friends. I have googled a ton and none of the suggestions from other comments fixed my issue.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MoveLeft : MonoBehaviour
{
private float speed = 30;
private PlayerController playerControllerScript;
private float leftBound = -15;
private float scoreZone = -3;
public AudioClip scoreSound;
private int score = 0;
private AudioSource playerAudio;
// Start is called before the first frame update
void Start()
{
playerControllerScript = GameObject.Find("Player").GetComponent<PlayerController>();
playerAudio = GetComponent<AudioSource>();
}
// Update is called once per frame
void Update()
{
// Moves objects as long as it is not game over.
if (playerControllerScript.gameOver == false)
{
transform.Translate(Vector3.left * Time.deltaTime * speed);
}
//Assigns scoring system, buggy. Commented out sound because of errors.
if (transform.position.x < scoreZone && gameObject.CompareTag("Obstacle"))
{
//playerAudio.PlayOneShot(scoreSound, 1);
}
//Destroy object out of bounds
if (transform.position.x < leftBound && gameObject.CompareTag("Obstacle"))
{
Destroy(gameObject);
AddScore();
}
}
void AddScore()
{
//Score goes to 1.
score++;
//prints 1 in console but does not increase over 1.
Debug.Log("Score! Your Score is" + score);
Debug.Log(score);
}
}
Tried: Numerous changes to configuration of ++, x = x +1, x++, x +=, etc. No idea. Am lost.
This is an issue caused by the local member int. You have an object, which has been created and this MoveLeft component is attached to it. You then destroy this object on collision, and therefore this component as well. You’ll have added one to the local instance int score, but then you destroy the component and lose this local instance.
I suspect what you thought was happening is that all the scripts/components shared the same variable values. That would only be true if you if you made the int score member a static int score, which means the member is the same shared amongst all instances of this component.
Instead of making score static, you might want to have a “Game Manager” that exposes public functions to allow you to increment and retrieve the current score.

How to delay the else statement without coroutine in unity c#?

I have a joystick display picture for my game. Currently, when the player touches the screen the image disappears and when the player is not touching the screen, it reappears. I wrote that using an if else statement.
if (indicator.inputIndicator.x != 0)
{
joystick.SetActive(false);
}
else
{
joystick.SetActive(true);
}
The problem is, I want the image to reappear after some time like 2 seconds. I want to delay the "else", but I do not want to use a coroutine. I want "else" to work after 2 seconds since the player takes his hand off the screen but I couldn't figure out how to do it. any help will be great.
Setting a timer is a pretty common problem you have to solve in Unity. One basic approach is to have a variable that you add Time.deltaTime every update. That way you can tell how long it has been since some condition was met.
Every Update iteration that meets the condition, add Time.deltaTime to the variable. If at some point the condition fails, reset the variable to 0. Then you can just base your joystick.SetActive() call on the value of your variable.
For example, your script might become:
float thresholdTimeToShowPrompt = 2;
// By starting at the threshold, the image is hidden at the start until a touch
float timeSincePlayerTouch = 2;
void Update()
{
// Rather than calling SetActive directly, just update the timer
if (indicator.inputIndicator.x != 0)
{
timeSincePlayerTouch = 0;
}
else
{
timeSincePlayerTouch += Time.deltaTime;
}
// Now we can base visibility on the time since the last user touch
bool shouldShowIcon = timeSincePlayerTouch >= thresholdTimeToShowPrompt;
// Only call SetActive when needed, in case of overhead
if (shouldShowIcon && !joystick.activeSelf)
{
joystick.SetActive(true);
}
else if (!shouldShowIcon && joystick.activeSelf)
{
joystick.SetActive(false);
}
}

Unity - coroutines got inaccurate with timing, want to get around [duplicate]

This question already has answers here:
Coroutines and while loop
(3 answers)
Closed 6 years ago.
I wish to move an object over time using coroutines. I wanted to get an object from point A to point B over say 2 seconds. To achieve this I used the following code:
IEnumerator _MoveObjectBySpeed()
{
while (TheCoroutine_CanRun)
{
if(myObject.transform.position.y <= UpperBoundary.position.y)
{
myObject.transform.position = new Vector3(myObject.transform.position.x,
myObject.transform.position.y + Step, myObject.transform.position.z);
}
yield return new WaitForSeconds(_smoothness);
}
TheCoroutine_CanRun = true;
moveAlreadyStarted = false;
}
and the Step is calculated like
private void CalculateSpeed()
{
Step = _smoothness * allDistance / timeToReachTop;
}
where allDistance is the distance between the bottom and the top boundary.
_smoothness is a fix value. The thing is, the bigger I get this value, the more accurate the time gets to get from bottom to up. Note that a small value here means smoother movement. This smoothness is the time the coroutine waits in between moving the myObject.
The time is measured like this:
void FixedUpdate()
{
DEBUG_TIMER();
}
#region DEBUG TIME
public float timer = 0.0f;
bool allowed = false;
public void DEBUG_TIMER()
{
if (Input.GetButtonDown("Jump"))
{
StartTimer();
}
if (myObject.transform.position.y >= UpperBoundary.position.y)
{
StopTimer();
Debug.Log(timer.ToString());
//timer = 0.0f;
}
if (allowed)
{
timer += Time.fixedDeltaTime;
}
}
void StartTimer()
{
timer = 0;
allowed = true;
}
void StopTimer()
{
allowed = false;
}
#endregion
The results were:
When I wanted the object to reach the top under 1 second and set the _smoothness to 0.01, the time the myObject took to get to the top was 1.67 seconds. When _smoothness was 0.2s, the time to actually reach the top was 1.04s.
So why is this so inaccurate and how to make it work fine?
This smoothness is the time the coroutine waits in between moving the myObject
The mistake you're making is assuming that a co-routine waits the perfect time before executing. Rather, it probably executes on the next frame after the timeout has finished.
Assuming you want smooth motion, you want to move the object every frame (e.g. in Update or co-routine that uses 'yield return null').
Note: Each frame may take a different duration (consider 144fps vs 15fps), and you can discover this in Time.deltaTime . https://docs.unity3d.com/520/Documentation/ScriptReference/Time-deltaTime.html

Calculating the frame-rate in a Unity scene

I'm making a project with Augmented Reality, using Unity and Vuforia extensions. I'm new to C#, but I was looking for a method similar to ARToolKit's getFrame(), and I'm really not finding anything.
My questions are:
Is it necessary that I can calculate the frame-rate that my scene is operating at?
Which scene object should i use to track the frame-rate?
Thats as simple as:
public float avgFrameRate;
public void Update()
{
avgFrameRate = Time.frameCount / Time.time;
}
Put this code in any MonoBehaviour and attatch it to any GameObject in the scene hierarchy.
Please note: this will only give you an average frame-rate. For a more current frame-rate, other answers have addressed effective ways of accomplishing that.
None of the answers here consider the fact that the timescale can be modified in Unity and if it is, all the above approaches will be incorrect. This is because Time.Delta time is influenced by the timescale.
As such, you need to use Time.unscaledDeltaTime:
int fps = 0;
void Update () {
fps = (int)(1f / Time.unscaledDeltaTime);
}
You should look at Time.smoothDeltaTime. This returns a smoothed Time.deltaTime value which you can use instead of having to smooth it yourself using one of the techniques mentioned in other answers.
You will want something like a timer that tracks the time, and how long it took to update the screen, and extrapolates from that how many frames are drawn in a second.
I am fairly rusty with Unity, but I believe something like 1/Time.deltaTime should give you what you want.
So you'd have something like
public void Update()
{
framerateThisFrame = 1/Time.deltaTime;
}
Next you would have to decide how often to change the displayed FPS, since framerateThisFrame can change a lot during every frame. You might want to change it every two seconds for example.
EDIT
An improvement you might want to make is something like storing the past n frames, and use an average to calculate the FPS, then display it. So you could end up with something like:
public int Granularity = 5; // how many frames to wait until you re-calculate the FPS
List<double> times;
int Counter = 5;
public void Start ()
{
times = new List<double>();
}
public void Update ()
{
if (counter <= 0)
{
CalcFPS ();
counter = Granularity;
}
times.Add (Time.deltaTime);
counter--;
}
public void CalcFPS ()
{
double sum = 0;
foreach (double F in times)
{
sum += F;
}
double average = sum / times.Count;
double fps = 1/average;
// update a GUIText or something
}
EDIT
You might even multiply the frame time by Time.timeScale, if you want to be consistent while you apply slow-down/time altering effects.
Since the framerate can vary constantly, it will change many times during a given second. I've used the following recommended approach to get the current framerate. Just put it in a new script and add it to a new, empty game object in your scene.
float deltaTime = 0f;
void Update() {
deltaTime += (Time.deltaTime - deltaTime) * .1f;
}
Source, including display method: http://wiki.unity3d.com/index.php?title=FramesPerSecond
IEnumerator FramesPerSecond()
{
while (true)
{
yield return new WaitForSeconds(1);
Debug.LogFormat("Fps {0}", Time.frameCount/Time.time);
}
}
private void Start()
{
StartCoroutine(FramesPerSecond());
}

Why is my game lagging huge when I call a method?

I've been messing around trying to create an analog style scoring system for my game that I am building with Unity3D and I've noticed a huge lag when I call AnalogScoreProcessing() from the updateScore() function:
/******************************** Update Score ********************************/
public void updateScore (int newScore)
{
// Send the old score/new score to AnalogScoreProcessing()
if (oldScore != newScore)
{
AnalogScoreProcessing(oldScore, newScore);
}
// Display GUI score
oldScore = newScore;
guiText.text = "" + oldScore;
}
/*************************** Analog Scoring System ****************************/
void AnalogScoreProcessing(int oldScore, int newScore){
for(int i = oldScore; i <= newScore; i++){
print (i);
}
}
Do I have to create a new thread or a co-routine to complete the looping task while the remaining parts of updateScore() are carried out? I've never done any threading but I have used co-routines. I'm just not sure what I should be using here.
The easiest solution is to use coroutines here.
You basically want the players to see the effect of counting up right?
So say you want to display a count up to the user being +1 to the user ever few frames or so so the count up will be perceivable.
Try...
I would do it like this
int current =0;
int target = 0;
float percentateOfTarget = 0;
float step = 0.1f;
IEnumerable AnalogScoreProcessing()
{
while(current <= target)
{
current = (int)Math.Ceiling(Mathf.Lerp(current, target, percentateOfTarget));
return yield new WaitForSeconds(step);
percentageOfTarget += step;
// Display GUI score
guiText.text = "" + current;
}
}
public void updateScore (int newScore)
{
// Send the old score/new score to AnalogScoreProcessing()
if (oldScore != newScore)
{
StopCoroutine("AnalogScoreProcessing");
current = oldScore;
target = newScore;
StartCoroutine("AnalogScoreProcessing");
}
}
Please be aware this code is pulled directly out of my head so youll probably have to tweek some things here and there but should give you something close to what you desire.
You could/should even scale the GUIText up while the coroutine running for an even more dramatic effect. Let me know how it goes.
As I side note coroutines are not separate threads in unity they are ran on unity's main thread.
Well i dont understandt the Logic behind the function, but cant you just print the score once it changes?
if (oldScore != newScore)
{
//AnalogScoreProcessing(oldScore, newScore);
Debug.log(newScore);
}
Also you have to set the GUI calls inside the
OnGUI()
function inside your file.
If there is a large difference between oldScore and newScore then the print function in AnalogScoreProcessing will be run many times.
You might want to look at using a tween to change the displayed value, as you cannot display more than one value per frame anyway. Or if you're printing to the console then... why are you doing that?
edit: have moved quick and dirty solution a more appropriate question/answer

Categories