How to make a combotimer in Unity - c#

I'm making a game currently and I got stuck while I was thinking about a combotimer.
Now in my game, there are enemies that run through my character and I hit them. Yet, at some point, if I can correctly hit the enemies, I want to add a powerful combo option like hitting them without any difficulty. I thought like I can create a class or a value that keeps the correct hits and let's say it's 5. When it reaches 5, then I can change the hitting options. Yet, where I'm stuck is that how I can identify after how many hits or seconds the combo can end. And in here what came to my mind is that I can make it with time. So here's the thing. I want to detect 5 hits and then I want a combotimer which makes the value of 5 decreased. So that when the value reaches 0 then I can continue to play my game with the normal standards of it. How can I do this?

Since you didn't provide anything here is what I would do as dummy code. This does
Everytime you hit an enemy check the time since last hit
If under the maximum delay => add to combo
If not reset the combo counter and start over
If reaching enough hits => enable isSuperCombo
Over time reset the isSuperCombo
As long as you are isSuperCombo you can still add hits even if they happen after the normal maxTimeBetweenHits to enlarge the duration of isSuperCombo as a little bonus
Something like
public class ComboCounter : MonoBehaviour
{
public bool isSuperCombo;
// Maimum delay in seconds since the hit for counting the current hit as combo
[SerializeField] private float maxTimeBetweenHits = 1;
// Requried hits in one combo in order to activate power bonus
[SerializeField] private int hitsUntilSuperCombo = 5;
// Delay in seconds to reset the powerup after the last hit
[SerializeField] private float powerUpDuration = 5;
private int hitCounter;
private float lastHitTime;
private float powerUpResetTimer;
private void Update()
{
if(isSuperCombo)
{
powerUpResetTimer -= Time.deltaTime;
if(powerUpResetTimer <= 0)
{
isSuperCombo = false;
hitCounter = 0;
}
}
}
// Call when you hit an enemy
public void AddHit()
{
if(Time.time - lastHitTime < maxTimeBetweenHits)
{
// then add to the hit counter
hitCounter++;
if(hitCounter >= hitsUntilSuperCombo)
{
isSuperCombo = true;
powerUpResetTimer = powerUpDuration;
}
}
else
{
// otherwise the delay was too big => not a combo anymore
// => Reset the counter and start over with this hit as the first one
hitCounter = 1;
}
// update the lastHitTime
lastHitTime = Time.time;
}
}
Then you could e.g. in another class check something like
public void CauseDamage(Enemy enemy)
{
enemy.health -= GetComponent<ComboCounter>().isSuperCombo ? 4 : 1;
}

If you need to note 5 seconds of increased damage you can use coroutine
private float _damage = 10;
public void StartCombo(float duration = 5)
{
StartCoroutine(ComboHandler(duration));
}
private IEnumerator ComboHandler(float duration)
{
_damage *= 2; //_damage = _damage * 2;
yield return new WaitForSeconds(duration); //wait necessary amount of real time
_damage /= 2; //_damage = _damage / 2;
}
If you need to do only 5 blows with increased damage, you can use something
private float _damage = 10;
private int _increasedBlows = 0; //amount of blows with increased damage
private float _increasingCoeff = 2; //ratio of damage increase of blow
//add new increased blows after doing combo
public void AddIncreasedBlows(int amount)
{
_increasedBlows += amount;
}
//return blow damage
public float GetHitDamage()
{
if (_increasedBlows > 0)
{
_increasedBlows--;
return _damage * -_increasingCoeff; //return increased damage
}
else
{
return _damage; //return usual damage
}
}

Related

How to execute multiple times a couroutine without stopping it?

I have an array of gameobjects that are lights, I'm trying to increase and drecrease range size of a point light over time, problem is lights some times doesn't decrease over time, they just go to range 0 instantly.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class MainMenu : MonoBehaviour
{
public GameObject[] stars;
private void Start()
{
StartCoroutine(ChooseStar());
}
public void PlayGame()
{
SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex + 1);
}
public void QuitGame()
{
Application.Quit();
}
IEnumerator IncreaseRadius(GameObject star, float duration)
{
Debug.Log("Increasing: "+star.name + " radius: " + star.GetComponent<Light>().range);
float counter = 0;
while (counter < duration)
{
counter += Time.deltaTime;
star.GetComponent<Light>().range = counter;
yield return null;
}
StartCoroutine(DecreaseRadius(star));
}
IEnumerator DecreaseRadius(GameObject star)
{
Debug.Log("Decreasing: "+star.name+" radius: "+ star.GetComponent<Light>().range);
float counter = star.GetComponent<Light>().range;
while (star.GetComponent<Light>().range >= 0f)
{
counter -= Time.deltaTime;
star.GetComponent<Light>().range = counter;
yield return null;
}
star.GetComponent<Light>().range = 0f;
}
IEnumerator ChooseStar()
{
float duration = Random.Range(3, 8);
float waitTime = 2f;
GameObject choosenStar = stars[Random.Range(0, stars.Length)];
if (choosenStar.GetComponent<Light>().range <= 0f)
{
StartCoroutine(IncreaseRadius(stars[Random.Range(0, stars.Length)], duration));
}
else
{
waitTime = 0f;
}
yield return new WaitForSeconds(waitTime);
StartCoroutine(ChooseStar());
}
}
the expected result should be this sequence:
1 - pick random star from array of gameobjects
2 - check if the star alredy is being range increased, if yes start again to search a new one if no starts to increase.
3 - light starts increasing until duration, then call decrease function
4 - star starts to decrease, when function is over it reset range to 0
To answer the question in general: You can simply put a
while (true)
{
...
yield return ...
}
around your code. As long as you yield somewhere inside it that's totally valid for Coroutines.
My guess would be that you get concurrent Coroutines because you don't wait for IncreaseRadius to finish before choosing the next random star ... which could be the same as before.
if (chosenStar.range <= 0f)
{
StartCoroutine(IncreaseRadius(stars[Random.Range(0, stars.Length)], duration));
}
else
{
waitTime = 0f;
}
also you do a Random.Range here again although you have already choosen another star before, was this intended?
First in general instead of using GetComponent<Light> all the time over and over again rather simply make
public Light[] stars;
reference the object just in the same way as before but now you are directly dealing with the Light references instead of GameObject.
Then you know that
float duration = Random.Range(3, 8);
actually returns random full int values between 3 and 7. If you rather wanted to have float values also between 3 and 8 so also including e.g. 3.253453f then you should rather use
var duration = Random.Range(3.0f, 8.0f);
Solution 1 - Only one star at a time
As simple alternative you could always animate only one star at a time. You can achieve this by yield return another IEnumerator. That make the other IEnumerator execute and at the same time waits for it to finish. Something like
public Light[] stars;
private void Start()
{
StartCoroutine(ChooseStar());
}
private IEnumerator IncreaseRadius(Light star, float duration)
{
Debug.Log("Increasing: " + star.name + " radius: " + star.range);
float counter = 0;
while (counter < duration)
{
counter += Time.deltaTime;
star.range = counter;
yield return null;
}
// again do the decreasing and at the same time wait for it to finish
yield return DecreaseRadius(star);
}
private static IEnumerator DecreaseRadius(Light star)
{
Debug.Log("Decreasing: " + star.name + " radius: " + star.range);
var counter = star.range;
while (star.range >= 0f)
{
counter -= Time.deltaTime;
star.range = counter;
yield return null;
}
star.range = 0f;
}
IEnumerator ChooseStar()
{
// Looks scary but is totally fine in Coroutines as long as you yield somewhere
// instead of starting a new Coroutine simple continue the one you already have
while (true)
{
var duration = Random.Range(3.0f, 8.0f);
var choosenStar = stars[Random.Range(0, stars.Length)];
// This starts the Increase routine on that star
// and at the same time waits for it to finish!
//
// since we also wait until DecreaseRadius is done this means
// at any time only exactly 1 star is animated at the same time
yield return IncreaseRadius(choosenStar, duration);
}
}
Solution 2 - Filter the random
Alternatively as it looks like you want to allow parallel animations of the stars I would simply filter out the List of available stars (ones that are not currently animated) for getting the random range. Something like
public Light[] stars;
// Use a list for dynamically adding and removing items
private List<Light> availableStars = new List<Light>();
private void Start()
{
// initialize the available list
// copy the references from stars
availableStars.AddRange(stars);
StartCoroutine(ChooseStar());
}
private IEnumerator IncreaseRadius(Light star, float duration)
{
Debug.Log("Increasing: " + star.name + " radius: " + star.range);
// As soon as you start animating this star
// remove it from the list of availables
availableStars.Remove(star);
float counter = 0;
while (counter < duration)
{
counter += Time.deltaTime;
star.range = counter;
yield return null;
}
// Decreasing and at the same time wait for it to finish
yield return DecreaseRadius(star);
// when finished add the star again to the availables
availableStars.Add(star);
}
private static IEnumerator DecreaseRadius(Light star)
{
Debug.Log("Decreasing: " + star.name + " radius: " + star.range);
var counter = star.range;
while (star.range >= 0f)
{
counter -= Time.deltaTime;
star.range = counter;
yield return null;
}
star.range = 0f;
}
IEnumerator ChooseStar()
{
// Looks scary but is totally fine in Coroutines as long as you yield somewhere
while (true)
{
var duration = Random.Range(3.0f, 8.0f);
// in case that currently all stars are being animated
// simply wait until the next one becomes available again
yield return new WaitUntil(() => availableStars.Count > 0);
// Pick a random star from the availables instead
var chosenStar = availableStars[Random.Range(0, availableStars.Count)];
// this check becomes then actually redundant
//if (chosenStar.range <= 0f)
//{
StartCoroutine(IncreaseRadius(chosenStar, duration));
yield return new WaitForSeconds(2f);
//}
}
}

I want to create a timer/stopwatch on an event

I have a code that works just fine but i think if i keep following the same strategy to do all the same things it's gonna over load on the processor for no reason
I have a variable that represents the time and this variable rests to 0 in every frame that the gameObject has a velocity higher than 0.5f so instead of resetting it every frame i want to start the timer when it's below 0.5f
if (speed >= 0.5f)
{
t = 0;
}
t = t + Time.deltaTime;
You could use a bool value to save performance.
public static bool isTimerMoving = false;
public void Update()
{
if (speed < 0.5f)
{
t = t + Time.deltaTime;
isTimerMoving = true;
}
else if (isTimerMoving) {
t = 0;
isTimerMoving = false;
}
}
This code resets the timer whenever speed reaches 0.5f. If you only want to pause the timer, you can remove the t = 0 from it.
P.S. using > or < is faster than using <= or >=. Not by very much, but I like keeping things efficient ;)
EDIT: After asking a question, responses indicate that this statement is false, my apologies.

Unity Increasing a value and then decrease it (DayNightCycle)

I have a cycle and want the cycle bar, having a value range from 0 to 1 and back to 0.
So, currently I use this code
public class DayNightCycle : MonoBehaviour
{
private float currentTime = 0; // current time of the day
private float secondsPerDay = 120; // maximum time per day
private Image cycleBar; // ui bar
private void Start()
{
cycleBar = GetComponent<Image>(); // reference
UpdateCycleBar(); // update the ui
}
private void Update()
{
currentTime += Time.deltaTime; // increase the time
if (currentTime >= secondsPerDay) // day is over?
currentTime = 0; // reset time
UpdateCycleBar(); // update ui
}
private void UpdateCycleBar()
{
cycleBar.rectTransform.localScale = new Vector3(currentTime / secondsPerDay, 1, 1);
}
}
but now I want a behaviour as mentioned by the picture above. How can I increase currentTime from 0 to 1 and then back to 0?
The problem: My cycle bar should still increase from the left to the right.
The night should last 40% of the maximum time, the other ones 20%.
If you are looking for a way to increase a variable from 0 to 1 then from 1 to 0, Mathf.PingPong is the answer. There are many other ways to do this but Mathf.PingPong is made for tasks like this one.
public float speed = 1.19f;
void Update()
{
//PingPong between 0 and 1
float time = Mathf.PingPong(Time.time * speed, 1);
Debug.Log(time);
}
Do this by Mathf.Sin() function. But you must get absolute value of it. Mathf.abs(mathf.sin());
It will change between 0 to 1 then back to zero. But its not smooth in zero.
Or offset sin function by +1 at the end multiply it by 0.5f to let it back to one again.
float timer = 0;
float cycle = 0;
public float speed = 1;
void Update()
{
timer += Time.deltaTime;
Cycle();
}
void Cycle()
{
cycle = (Mathf.Sin(timer) + 1) * 0.5f;
}
instead of 0 to 1, use -1 to 1.
The timer starts from -1, increase in update function by deltaTime, and then when it will become moreThan equal 1 it will be reset to -1. its a loop...
float timer = -1;
void Update()
{
timer += Time.deltaTime;
if(timer >= 1)
{
timer = -1;
}
Cycle();
}
void Cycle()
{
//Do Your Cycle
//-1 is left night, 0 is middle day, 1 is right night
}

How to increase my score gradually with a timer?

I am currently creating a hidden object game and I have been stuck on how to add a timer and countdown to my game. I have currently a score which generates after all of the objects have been clicked however I would love if the score went up gradually once the user clicked on each one. here is my code below so far.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class trackingclicks : MonoBehaviour {
//static variable added to count users clicks
public static int totalclicks=0;
//"mouseclick" keycode variable to look for mouse click
public KeyCode mouseclick;
public Transform scoreObj;
// Use this for initialization
void Start () {
}
// Update is called once per frame
void Update () {
//checks the change in time, aka how much time has passed- bonus time starts at 90
clickcontrol.timeBonus -= Time.deltaTime;
if (clickcontrol.remainItems == 0)
{
clickcontrol.totalScore += (70 + (Mathf.RoundToInt(clickcontrol.timeBonus)));
scoreObj.GetComponent<TextMesh>().text = "Score : " + clickcontrol.totalScore;
clickcontrol.remainItems = -1;
}
//Check for mouse click
if (Input.GetKeyDown (mouseclick))
{
totalclicks += 1;
}
if (totalclicks >= 5)
{
Debug.Log ("FAIL!!!");
totalclicks = 0;
}
}
}
You can use coroutines for timers (coroutines execute code after a certain amount of time). If you want your score to increment gradually once the player clicks on an object (I think that's what you're asking), then something like this should work:
public int score;
IEnumerator IncrementScore(int amount, int interval) { //amount is the amount of score to add over the time, interval is the time IN SECONDS between adds
for (int i = 0; i < amount; i++) {
yield return new WaitForSeconds(interval);
score++;
}
}
I wasn't sure where the score variable was so I made a new one; you can set it to your own, wherever it is. If you need more help, feel free to ask.

Unity: How to Delay button call by time?

I desire to increment an float by 0.5 every time the user clicks the UI button and if the user presses the button for more than 2 second want to continuously increment the float by 0.5, to do so i use Event trigger (PointerDown, PointerUp) and call the functions in update. When i user the down below code i cant increment the float value continuously.
Update Code
void Update () {
transform.rotation = Quaternion.Lerp (qStart, qEnd, (Mathf.Sin(Time.time * speed) + 1.0f) / 2.0f);
if(Time.timeScale == 0)
transform.rotation = Quaternion.Euler(0,0,0);
clickCondition ();
}
PointerDown Function
public void WhenIncreaseClicked()
{
if (timeDown < 2.0f)
IncreaseBPM ();
else
increase = true;
}
PinterUp function
public void WhenIncreaseNotClicked()
{
increase = false;
Time.timeScale = 1;
}
IncreaseBPM
public void IncreaseBPM()
{
if (speed < 12)
{
speed += 0.05f;
bpmText.GetComponent<BeatTextControl> ().beats += 1;
PlayerPrefs.SetFloat ("savedBPM", speed);
}
}
ClickCondition
public void clickCondition()
{
if(increase)
{
IncreaseBPM();
}
else if(decrease)
{
DecreaseBPM();
}
}
Start
void Start () {
qStart = Quaternion.AngleAxis ( angle, Vector3.forward);
qEnd = Quaternion.AngleAxis (-angle, Vector3.forward);
timeDown = Time.deltaTime;
if (PlayerPrefs.HasKey ("savedBPM"))
speed = PlayerPrefs.GetFloat ("savedBPM");
else
speed = 1.5f;
}
I have set timeDown = Time.deltaTime in Start().
Assuming no other code alters your increase variable, you'll be unable to increment it continuously because it will never be set to true.
In Start() you have a line timeDown = Time.deltaTime;, so timeDown will be equal to the number of seconds since the last frame. Not only is this an issue because timeDown never changes (Start() is only called once), but it's an issue because it's not likely to ever be above 2.0f since a single frame is highly unlikely to take 2 seconds to complete. It will generally be a very small number, for example 0.06f.
Due to this, in your WhenIncreaseClicked() method, the if (timeDown < 2.0f) will always equate to true. Thus the else clause is never executed and increase is never set to true.
To resolve this you could create a new boolean variable, e.g. clicked and set it to true at the start of WhenIncreaseClicked() and false at the start of WhenIncreaseNotClicked(). Then in Update() you can just add Time.deltaTime to timeDown if clicked is true. You'll also want to move that if/else outside of WhenIncreaseClicked() also, and into Update(), making sure it's only run when clicked is true.
For example:
void Update () {
transform.rotation = Quaternion.Lerp (qStart, qEnd, (Mathf.Sin(Time.time * speed) + 1.0f) / 2.0f);
if(Time.timeScale == 0)
transform.rotation = Quaternion.Euler(0,0,0);
if(clicked) {
timeDown += Time.deltaTime;
if (timeDown >= 2.0f) // note the >= not <
increase = true;
}
clickCondition ();
}
and WhenIncreaseClicked():
public void WhenIncreaseClicked()
{
clicked = true;
IncreaseBPM();
}
and WhenIncreaseNotClicked():
public void WhenIncreaseNotClicked()
{
clicked = false;
increase = false;
Time.timeScale = 1;
}
You can also remove the assignment to timeDown in Start() since it's not useful.
First of all, Unity documentation says about Time.deltaTime
The time in seconds it took to complete the last frame.
When you set timeDown = Time.deltaTime timeDown becomes a tiny fraction, for example 0.01621689. Therefore, timeDown < 2.0f always returns true and your code never reaches increase = true; line.
Second point you should know is Time.time
The time at the beginning of this frame (Read Only). This is the time in seconds since the start of the game.
You used Time.time in your Update() method as Mathf.Sin(Time.time * speed). Bear in mind that Time.time can be enough large number representing the time in seconds since the start of the game as mentioned in document. When you do Time.time * speed the output number may be huge (assuming speed > 1) causing some problems. Luckily, you are using it inside Mathf.Sin() you get number only between [0, 1]. Probably, you intended to use Time.deltaTime instead.
Let's remember Time.time:
The time in seconds since the start of the game
You can use this to check time difference.
float lastDownTime;
bool isDown;
void OnPointerDown() // Method name itself says when it should be called
{
lastDownTime = Time.time;
isDown = true;
}
void OnPointerUp()
{
isDown = false;
}
void Update ()
{
if (isDown)
{
if (Time.time - lastDownTime > 2) // 2 seconds
{
IncreaseBPM ();
}
}
}
Note: Unity says about Time.timeScale:
Except for realtimeSinceStartup, timeScale affects all the time and delta time measuring variables of the Time class.
So if you insist on using Time.timeScale, instead of using Time.time you should use Time.realtimeSinceStartup in above example.

Categories