I want to simulate health regeneration in my game in unity, in the function RestoreHealth().
Am I overthinking it by wanting to create a child process, so when I call wait, it won't affect any current running process or thread and the child process will die when the function is done.
public void RestoreHealth() {
if (health >= MaxHealth) return; // health and MaxHealth are Class variables
if (health % 10 != 0) { // if health is not dividable by 10
int temp = health % 10; // then we round it to the closest
//tenth
temp = 10 - temp;
health += temp;
}
int i = health;
for (; i < MaxHealth; i += 10) { // where the health grows till 100
health += 10;
// sleep(1000); // make function wait for '1 second' to iterate again
Debug.Log("Health: " + health);
}
}
How do I create a child process in C# or unity in this case and cause it to wait?
Is there an equivalent to Fork(); like in C?
Also, this function is called when the player initially receives damage.
Solution:
note: I changed Health to Armour
public IEnumerator RestoreArmour() {
while (_Armour < _MaxArmour) {
_Armour++;
Debug.Log("Health: " + _Armour);
yield return new WaitForSeconds(ArmourRegenRate); // ArmourRegenRate is a
// float for the seconds
}
}
and use this to initiate the coroutine
void Start(){
StartCoroutine(player.RestoreArmour());
}
Basic Coroutine concept
In Unity you work with Coroutines to achieve this asychronous "threaded" behaviour
IEnumerator RestoreHealth() {
while (health != MaxHealth) {
health++;
yield return new WaitForEndOfFrame();
}
}
and then invoke it with
StartCoroutine(RestoreHealth());
Restarting a Coroutine
In order to stop an existing Coroutine from running and start a new one, this is how you would achieve that:
private Coroutine _myCoroutine = null;
void SomeMethod()
{
if (_myCoroutine != null)
StopCoroutine(_myCoroutine);
_myCoroutine = StartCoroutine(SomeOtherMethod());
}
Pausing armor restoration for X seconds when player has taken damage
A common functionality is to have something restore armor when player hasn't taken damage for X seconds:
private bool _shouldRestoreArmour = true;
private Coroutine _pauseArmorCoroutine = null;
void Update()
{
if (_shouldRestoreArmour)
Armor += ArmorRegenerationPerSecond * Time.deltaTime;
}
void PlayerTakeDamage()
{
if (_pauseArmorCoroutine != null)
StopCoroutine(_pauseArmorCoroutine);
_pauseArmorCoroutine = StartCoroutine(PauseRestoreArmor());
// Take damage code
}
IEnumerator PauseRestoreArmor()
{
_shouldRestoreArmor = false;
yield return new WaitForSeconds(RESTORE_ARMOR_DELAY_TIME);
_shouldRestoreArmor = true;
}
Here the player will regenerate armor at all times, except for X seconds after the player has taken damage. If the player takes damage multiple times we will simply abort the previous coroutine and start a new one, so that it will be a fresh X seconds from the last hit.
You can create a timer assuming that you run this in the update method
private float timer = 1;
private float timerReset = 1;
private void Update(){
RestoreHealth();
}
public void RestoreHealth() {
if (health >= MaxHealth) return; // health and MaxHealth are Class variables
if (health % 10 != 0) { // if health is not dividable by 10
int temp = health % 10; // then we round it to the closest tenth
temp = 10 - temp;
health += temp;
}
int i = health;
for (; i < MaxHealth; i += 10) { // where the health grows till 100
if(timer > 0){
timer -= 1 * time.deltaTime;
}else{
health += 10;
Debug.Log("Health: " + health);
timer = timerReset;
}
}
}
or you can simply just give your player 1 health every second or whatever amount for instance
public void RestoreHealth() {
if (health >= MaxHealth) return; // health and MaxHealth are Class variables
if (health % 10 != 0) { // if health is not dividable by 10
int temp = health % 10; // then we round it to the closest tenth
temp = 10 - temp;
health += temp;
}
int i = health;
for (; i < MaxHealth; i += 10) { // where the health grows till 100
health += 10 * 1 * time.deltaTime;
Debug.Log("Health: " + health);
timer = timerReset;
}
}
So in this scenario your player recieves 10 health every second but the value will always go up so in the matter of a second the player health would go up 1 health every 100ms
Related
I am very new to c# and I've come across a problem with my enemy spawner. I am making an endless top-down shooter, and after 20 enemies have been spawned and then destroyed, the enemies stop appearing. My score counter continues to increase though, which leads me to believe they're somehow being destroyed.
Here is the enemy spawn controller:
void Update()
{
if (!spawningObject && GameController.EnemyCount < spawnSettings[0].maxObjects)
{
spawningObject = true;
float pick = Random.value * totalWeight;
int chosenIndex = 0;
float cumulativeWeight = enemySpawnables[0].weight;
while(pick > cumulativeWeight && chosenIndex < enemySpawnables.Count - 1)
{
chosenIndex++;
cumulativeWeight += enemySpawnables[chosenIndex].weight;
}
StartCoroutine(SpawnObject(enemySpawnables[chosenIndex].type, Random.Range(spawnSettings[0].minWait / GameController.DifficultyMultiplier, spawnSettings[0].maxWait / GameController.DifficultyMultiplier)));
Spawn();
}
}
private IEnumerator SpawnObject(string type, float time)
{
yield return new WaitForSeconds(time);
randomSpawnPoint = Random.Range(0, enemySpawners.Length);
randomEnemy = Random.Range(0, enemy.Length);
enemyPool.SpawnObject(enemySpawners[randomSpawnPoint].position, transform.rotation);
spawningObject = false;
GameController.EnemyCount++;
}
And here is the enemy controller (attached to enemy prefab):
public void Start()
{
myRB = GetComponent<Rigidbody>();
player = FindObjectOfType<PlayerController>();
}
private void OnEnable()
{
player = FindObjectOfType<PlayerController>();
}
void Update()
{
if (health <= 0)
{
Die();
}
}
void FixedUpdate()
{
transform.LookAt(player.transform.position);
myRB.velocity = (transform.forward * moveSpeed);
//Falling
if(myRB.velocity.y < 0)
{
myRB.velocity += Vector3.up * Physics.gravity.y * (fallMultiplier - 1) * Time.deltaTime;
}
}
public void Die()
{
print("Enemy" + this.gameObject.name + " has died!");
EnemySpawn.instance.enemyPool.ReturnObject(this.gameObject);
player.GetComponent<PlayerController>().points += pointsToGive;
ScoreController.scoreValue += 1;
}
The only thing that should affect enemy health is the bullet. Here is the part of the bullet controller that affects enemy health:
public void OnTriggerEnter(Collider other)
{
if(other.tag == "Enemy")
{
triggeringEnemy = other.gameObject;
triggeringEnemy.GetComponent<EnemyController>().health -= damage;
PlayerController.instance.bulletPool.ReturnObject(gameObject);
}
}
The issue always happens after 20 enemies have been spawned/destroyed and 20 points have been achieved. Before then, enemy movement is normal and everything seems to be working as it should. It could very well be something very simple that I'm missing, but I'd appreciate any help!
When you do engage logic mistake good way to find it is Visual Studio debugging.
Here is good official article about whole process - https://docs.unity3d.com/Manual/ManagedCodeDebugging.html.
I would place breakpoint right at start of Update method to see that EnemyCount increases over time and then you will understand that you forgot to do decrement it.
I need some help on my mobile open world game project. I have a player who can walk and runs when we press a button. I made a stamina wheel (like in zelda botw), and when my player run the stamina decreases. I have also put a regeneration coroutine that make the stamina regen. But because my maxStamina = 1, the time between it is empty and full is really fast. Here is my code :
public static StaminaUI instance;
private WaitForSeconds regenTick = new WaitForSeconds(0.1f);
private Coroutine regen;
private void Awake()
{
instance = this;
}
void Start()
{
fillAmount = maxFill;
}
void Update()
{
if (Ybot.MoveSpeed > 5f)
{
UseStamina(0.015f);
}
if (fillAmount < 0.01f)
{
Ybot.MoveSpeed = 0f;
Ybot.animator.SetBool("isRunning", false);
Ybot.animator.SetBool("isWalking", false);
}
}
public void UseStamina(float amount)
{
if (fillAmount - amount >= 0)
{
fillAmount -= amount;
if (regen != null)
{
StopCoroutine(regen);
}
regen = StartCoroutine(RegenStamina());
}
else
{
Debug.Log("Not enough stamina");
}
}
private IEnumerator RegenStamina()
{
yield return new WaitForSeconds(1);
while (fillAmount < maxFill)
{
fillAmount += maxFill/1;
yield return regenTick;
}
regen = null;
}
Hope someone can help me to this little problem.
Since you increment every 0.1 seconds I think it should be
fillAmount += maxFill / desiredDuration * 0.1f;
where desiredDuration is the time in seconds needed to completely fill the stamina if it is 0.
Or as an alternative for smooth Updates instead of the 0.1 second steps you could do
while (fillAmount < maxFill)
{
fillAmount += maxFill / desiredDuration * Time.deltaTime;
yield return null;
}
You need to use Time.time and then do the regen if the TIme.time is > than a fixed interval. You have to realise that Update is called once per frame. This could mean it can be called anywhere between 30-90 times a second depending on your device's refresh rate.
Update is called once per frame so in your code you have to do something similar to this:
lastRegenTime and regenIntervals can both be floats (which can be expensive), but in Update the logic would be ->
if (Time.time - lastRegenTime > regenInterval)
{
Regenerate();
}
Set regenInterval to something low, and then set the incrementor for life at life+=0.02f; You can even do it more incrementally, or *=0.00002f;
I'm trying to create a wave spawner for a top down game that I'm creating. I have created the wave spawner script but when I hit play, nothing happens. The countdown doesn't begin. Ideally it should start from 2, once it reaches 0, the first wave should spawn with one enemy. Once that enemy is killed, the countdown should begin from 5 and once 0 is reached, the next wave with 2 enemies should begin and so on. The new wave should not begin until all the current enemies are destroyed.
public enum SpawnState { SPAWNING, WAITING, COUNTING };
public SpawnState state = SpawnState.COUNTING;
public Transform enemy;
public float timeBetweenWaves = 5f;
public float countDown = 2f;
private int waveIndex = 0;
public float searchCountdown = 1f;
void Update()
{
if (state == SpawnState.WAITING)
{
if (!EnemyisAlive())
{
WaveCompleted();
}
else
{
return;
}
}
if (countDown <= 0f)
{
if (state != SpawnState.SPAWNING)
{
StartCoroutine(SpawnWave());
countDown = timeBetweenWaves;
}
else
{
countDown -= Time.deltaTime;
}
}
}
void WaveCompleted()
{
state = SpawnState.COUNTING;
countDown = timeBetweenWaves;
SpawnWave();
}
bool EnemyisAlive()
{
searchCountdown -= Time.deltaTime;
if (searchCountdown <= 0)
{
searchCountdown = 1f;
if (GameObject.FindGameObjectsWithTag("Enemy").Length == 0)
{
return false;
}
}
return true;
}
IEnumerator SpawnWave()
{
state = SpawnState.SPAWNING;
waveIndex++;
for (int i = 0; i < waveIndex; i++)
{
SpawnEnemy();
yield return new WaitForSeconds(0.5f);
}
state = SpawnState.WAITING;
yield break;
}
void SpawnEnemy()
{
Instantiate(enemy, transform.position, transform.rotation);
}
I would recommend you to use a Coroutine for all of it. That makes some things easier. You can e.g. simply wait until another Ienumertaor is finished. Then I would simply add the spawned enemies to a list, filter it for null entries and use the count. Using Find or in your case FindGameObjectsWithTag each frame is highly inefficient!
using System.Linq;
using System.Collections.Generic;
...
public Transform enemy;
public float timeBetweenWaves = 5f;
public float countDown = 2f;
//public float searchCountdown = 1f;
private List<Transform> enemies = new List<Transform>();
private int waveIndex = 0;
private void Start()
{
StartCoroutine(RunSpawner());
}
// this replaces your Update method
private IEnumerator RunSpawner()
{
// first time wait 2 seconds
yield return new WaitForSeconds(countDown);
// run this routine infinite
while(true)
{
state = SpawnState.SPAWNING;
// do the spawning and at the same time wait until it's finished
yield return SpawnWave();
state = SpawnState.WAITING;
// wait until all enemies died (are destroyed)
yield return new WaitWhile(EnemyisAlive);
state = SpawnState.COUNTING
// wait 5 seconds
yield return new WaitForSeconds(timeBetweenWaves);
}
}
private bool EnemyisAlive()
{
// uses Linq to filter out null (previously detroyed) entries
enemies = enemies.Where(e => e != null).ToList();
return enemies.Count > 0;
}
private IEnumerator SpawnWave()
{
waveIndex++;
for (int i = 0; i < waveIndex; i++)
{
SpawnEnemy();
yield return new WaitForSeconds(0.5f);
}
}
private void SpawnEnemy()
{
enemies.Add(Instantiate(enemy, transform.position, transform.rotation));
}
To be slightly more efficient you could also avoid instantiating and destroying but rather use Object Pooling - only enabling and disabling the objects and eventually spawn new ones only when needed.
I want to remove(Destroy) my gameobjects(hurdles) from the loop that are random generated after some time by random.range but it is not removing. I tried to change the " Destroy(newSelected[ran], 5)" position but not working.
private void OnTriggerExit(Collider theCollision)
{
if (theCollision.gameObject.tag == "Ob2")
{
a = theCollision.transform.position;
x = a.z + 3f;
x_ = a.z - 1f;
startingpoint = a.x;
for (int i = 8; i <= 64; i += 8)
{
var ran = Random.Range(0, 4);
//selecting gameobjects(hurdles) by default.
print(ran);
b = new Vector3(startingpoint + i, 0.050f, Random.Range((int)x_, (int)x));
if (ran == 2)
{
newSelected[2]=Instantiate(gameObjects[2], new Vector3(startingpoint + i, 0.050f, a.z), Quaternion.identity);
print(x_);
}
else if (ran != 2)
{
newSelected[ran]=Instantiate(gameObjects[ran], b, Quaternion.identity);
}
Destroy(newSelected[ran], 5);
//I want to destroy my gameobjects(hurdles) here that are instantiate after some time.
}
}
}
}
Your code above will attempt 7 times to destroy an object in the newSelected array at a random index of 0, 1, 2 or 3. Meaning that, for example, it might attempt to destroy the object at index 0 multiple times and therefore returning a null error after the object has been destroyed the first time.
To avoid that error you should check that newSelected[ran] is not null before attempting to destroy it.
I would recommend you create a separate component to handle the random delay destroy functionality and add it to the GameObject you are instantiating.
Here's a quick C# Script that will handle destroying the GameObject it is attached to:
using UnityEngine;
public class DestroyAfterRandomDelay : MonoBehaviour
{
[Tooltip("Minimum delay value")]
public float MinimumDelay = 0f;
[Tooltip("Maximum delay value")]
public float MaximumDelay = 4f;
// keeps track of elapsed time
private float _elapsedTime= 0;
// Keeps track of random delay
private float _randomDelay;
// Start is called before the first frame update
void Start()
{
// Sets random delay on start
_randomDelay = Random.Range(MinimumDelay, MaximumDelay);
Debug.LogFormat("{0} will be destroyed in {1} seconds!", this.name, _randomDelay);
}
// Update is called once per frame
void Update()
{
// Increment time passed
_elapsedTime += Time.deltaTime;
// Proceed only if the elapsed time is superior to the delay
if(_elapsedTime < _randomDelay) return;
Debug.LogFormat("Destroying {0} after {1} seconds! See Ya :)", this.name, _elapsedTime);
// Destroy this GameObject!
Destroy(this.gameObject);
}
}
l am working on a mobile endless running game on unity.My character's speed is equal to a var called "speed":
var speed = 7;
GetComponent<Rigidbody> ().velocity = new Vector3 (0, 0, speed);
and l want that var (speed) to increase by 4 every 30 seconds using C#. Any ideas?
There are many ways to do this in Unity.
1.In the Update function with Time.deltaTime by using it to increment a variable
int speed = 7;
float counter = 0;
void Update()
{
//Increment Counter
counter += Time.deltaTime;
if (counter >= 30)
{
//Increment Speed by 4
incrementSpeed();
//RESET Counter
counter = 0;
}
}
void incrementSpeed()
{
speed += 4;
}
2.With coroutine and WaitForSeconds or WaitForSecondsRealtime.
void Start()
{
StartCoroutine(incremental());
}
IEnumerator incremental()
{
while (true)
{
//Wait for 30 seconds
yield return new WaitForSeconds(30);
//Increment Speed
incrementSpeed();
}
}
void incrementSpeed()
{
speed += 4;
}
Which one to use really depends on if you want to see the counter or not. With the second solution, you can't see the status of the timer. It will just increment after every 30 seconds. These are the most basic ways.