I want to create some fireflies in Unity. I want to Increase light intensity then wait some seconds and then decrease it in Unity. When they get spawned, I want them increasing their light intensity, wait some seconds and then fade out. How can I create this "process" in a clean way?
private Light pointLight; // The light component of the firefly
private float minLuminosity = 0; // min intensity
private float maxLuminosity = 1; // max intensity
private float luminositySteps = 0.005f; // factor when increasing / decreasing
private float shineDuration = 3; // wait 3 seconds when faded in
private void Start()
{
pointLight = GetComponent<Light>();
pointLight.intensity = Random.Range(minLuminosity, maxLuminosity); // start with a random intensity
StartCoroutine(ChangeIntensity()); // start the process
}
private IEnumerator ChangeIntensity()
{
pointLight.intensity += luminositySteps; // increase the firefly intensity / fade in
yield return new WaitWhile(() => pointLight.intensity >= maxLuminosity); // wait for the maximum intensity
yield return new WaitForSeconds(shineDuration); // wait 3 seconds
pointLight.intensity -= luminositySteps;
yield return new WaitWhile(() => pointLight.intensity <= maxLuminosity); // wait for the minimum intensity
StartCoroutine(ChangeIntensity()); // do it again
}
So obviously the coroutine stops forever at the first WaitWhile() How can I create such a code chain? When fading in or out, I just mean changing the light intensity.
Even though this has been solved, the current solutions are just decrementing the variable and also creates new object (WaitForSeconds) every frame.
The proper way of doing this in Unity is using Mathf.Lerp and Time.deltaTime. This type of operation is what Mathf.Lerp is made for which is to go from your minLuminosity to maxLuminosity. You can read more about this in my other question for fading out/in GameObject using its alpha component here.
I took the fadeInAndOut function from that answer and ported it to work with the Light component. Here is a simple light fade in/out function:
IEnumerator fadeInAndOut(Light lightToFade, bool fadeIn, float duration)
{
float minLuminosity = 0; // min intensity
float maxLuminosity = 1; // max intensity
float counter = 0f;
//Set Values depending on if fadeIn or fadeOut
float a, b;
if (fadeIn)
{
a = minLuminosity;
b = maxLuminosity;
}
else
{
a = maxLuminosity;
b = minLuminosity;
}
float currentIntensity = lightToFade.intensity;
while (counter < duration)
{
counter += Time.deltaTime;
lightToFade.intensity = Mathf.Lerp(a, b, counter / duration);
yield return null;
}
}
Now, to create the exact effect you want which is to increase light intensity then wait some seconds and then decrease it, create another coroutine function that calls the function above and waits for it to finish. You can do that by yielding the fadeInAndOut function. Notice how WaitForSeconds is declared outside the while loop so that it does not create new Object each time.
//Fade in and out forever
IEnumerator fadeInAndOutRepeat(Light lightToFade, float duration, float waitTime)
{
WaitForSeconds waitForXSec = new WaitForSeconds(waitTime);
while (true)
{
//Fade out
yield return fadeInAndOut(lightToFade, false, duration);
//Wait
yield return waitForXSec;
//Fade-in
yield return fadeInAndOut(lightToFade, true, duration);
}
}
USAGE:
public Light lightToFade;
public float eachFadeTime = 2f;
public float fadeWaitTime = 5f;
void Start()
{
StartCoroutine(fadeInAndOutRepeat(lightToFade, eachFadeTime, fadeWaitTime));
}
The problem in your code was that you applied your luminosity change only once, thus your WaitWhile condition would never be reached. I would change both WaitWhile into simple while loops, and then use WaitForEndOfFrame:
private IEnumerator ChangeIntensity()
{
while(true)
{
while(pointLight.intensity <= maxLuminosity)
{
pointLight.intensity += luminositySteps; // increase the firefly intensity / fade in
yield return new WaitForEndOfFrame();
}
yield return new WaitForSeconds(shineDuration); // wait 3 seconds
while(pointLight.intensity > minLuminosity)
{
pointLight.intensity -= luminositySteps;
yield return new WaitForEndOfFrame();
}
}
}
So I got it by using the following while loop
private IEnumerator ChangeIntensity()
{
while (true)
{
pointLight.intensity += isIncreasing ? luminositySteps : -luminositySteps;
if (pointLight.intensity <= minLuminosity)
isIncreasing = true;
if (pointLight.intensity >= maxLuminosity)
{
isIncreasing = false;
yield return new WaitForSeconds(shineDuration);
}
yield return null;
}
}
Related
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 get a game object in Unity 3D to fade out(fade speed should be adjustable from editor), pause/wait 2 seconds (pause length be adjustable from editor), and fade back in, looping infinitely. Coroutine is what I'm trying to utilize here to decrease alpha value but I'm unsure of exactly where I'm making my errors.
I'm getting a single error. (Cannot convert method group "FadeOut" to non-delegate type "object")
Here is my code:
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ScriptFader : MonoBehaviour
{
// attached game object for fading
public GameObject Sphere;
// fade speed length
public float fadeSpeed;
//Pause length between fades
public int fadePause;
void Awake()
{
StartCoroutine(FadeOut(fadeSpeed));
}
//Fade Out Coroutine
public IEnumerator FadeOut(float fadeSpeed)
{
Renderer rend = Sphere.transform.GetComponent<Renderer>();
Color matColor = rend.material.color;
float alphaValue = rend.material.color.a;
//while loop to deincrement Alpha value until object is invisible
while (rend.material.color.a > 0f)
{
alphaValue -= Time.deltaTime / fadeSpeed;
rend.material.color = new Color(matColor.r, matColor.g, matColor.b, alphaValue);
yield return new WaitForSeconds(fadePause);
}
rend.material.color = new Color(matColor.r, matColor.g, matColor.b, 0f);
StartCoroutine(FadeIn(fadeSpeed));
}
//Fade In Coroutine
public IEnumerator FadeIn(float fadeSpeed)
{
//waits for the return value of FadeOut coroutine to commence
yield return FadeOut;
Renderer rend = Sphere.transform.GetComponent<Renderer>();
Color matColor = rend.material.color;
float alphaValue = rend.material.color.a;
//while loop to increment object Alpha value until object is opaque
while(rend.material.color.a < 1f)
{
alphaValue += Time.deltaTime / fadeSpeed;
rend.material.color = new Color(matColor.r, matColor.g, matColor.b, alphaValue);
yield return null;
}
rend.material.color = new Color(matColor.r, matColor.g, matColor.b, 1f);
StartCoroutine(FadeOut(fadeSpeed));
}
}
I second Pluto's answer, but I would approach Fade() a little differently:
private IEnumerator Fade()
{
Renderer rend = Sphere.transform.GetComponent<Renderer>();
Color initialColor = rend.material.color;
Color targetColor = new Color(initialColor.r, initialColor.g, initialColor.b, 0f);
float elapsedTime = 0f;
while (elapsedTime < fadeDuration)
{
elapsedTime += Time.deltaTime;
rend.material.color = Color.Lerp(initialColor, targetColor, elapsedTime / fadeDuration);
yield return null;
}
}
You can, of course, pass in the material and the two colors as arguments. That'd be cleaner. But the logic inside this while loop is, I think, a little easier to understand, as well as more common and ubiquitous, and it lets you explicitly set how long you want the fade animation to take.
Lerp() stands for "linear interpolation". If you're not familiar with it, I suggest you learn it; you'll be using it everywhere.
For example, if you have black as the starting color and white as the target, putting 1/2 as the third argument would get you the shade of gray right in-between; 3/4 would be closer to white, and 1/4 closer to black.
You can use it for colors as Color.Lerp(), but also numbers, vectors, and essentially any value that lives on a spectrum.
You could just write a coroutine that controls the effect:
void Start() => StartCoroutine(FadeInOut());
IEnumerator FadeInOut()
{
var material = Sphere.GetComponent<Renderer>().material;
//forever
while (true)
{
// fade out
yield return Fade(material, 0);
// wait
yield return new WaitForSeconds(fadePause);
// fade in
yield return Fade(material, 1);
// wait
yield return new WaitForSeconds(fadePause);
}
}
IEnumerator Fade(Material mat, float targetAlpha)
{
while(mat.color.a != targetAlpha)
{
var newAlpha = Mathf.MoveTowards(mat.color.a, targetAlpha, fadeSpeed * Time.deltaTime);
mat.color = new Color(mat.color.r, mat.color.g, mat.color.b, newAlpha);
yield return null;
}
}
Your error is here (as I'm sure your stack trace would have shown):
//Fade In Coroutine
public IEnumerator FadeIn(float fadeSpeed)
{
//waits for the return value of FadeOut coroutine to commence
yield return FadeOut; // !!! ERROR
You're missing the actual call to the function (the parentheses). I'm not actually sure what you intended with that line... does it need to exist at all?
But honestly, Coroutines are overkill for simple cyclers (in your case, an Alpha cycle).
See font size with coroutines for an example.
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.
How can I change the light intensity value from 3.08 back to 1.0 after 2 seconds. I have comment in my code for additional info
public class Point_LightG : MonoBehaviour {
public Light point_light;
float timer;
// Use this for initialization
void Start () {
point_light = GetComponent<Light>();
}
// Update is called once per frame
void Update () {
timer -= Time.deltaTime;
lights();
}
public void lights()
{
if (timer <= 0)
{
point_light.intensity = Mathf.Lerp(1.0f, 3.08f, Time.time);
timer = 2f;
}
// so after my light intensity reach 3.08 I need it to gradually change back to 1.0 after 2 seconds.
}
}
To lerp between two values, just use the Mathf.PingPong with the Mathf.Lerp and provide a speed the lerp should happen at.
public Light point_light;
public float speed = 0.36f;
float intensity1 = 3.08f;
float intensity2 = 1.0f;
void Start()
{
point_light = GetComponent<Light>();
}
void Update()
{
//PingPong between 0 and 1
float time = Mathf.PingPong(Time.time * speed, 1);
point_light.intensity = Mathf.Lerp(intensity1, intensity2, time);
}
If you prefer to use a duration instead of a speed variable to control the light intensity then you that is better done with a coroutine function and just the Mathf.Lerp function with a simple timer. The lerp can then be done within x seconds.
IEnumerator LerpLightRepeat()
{
while (true)
{
//Lerp to intensity1
yield return LerpLight(point_light, intensity1, 2f);
//Lerp to intensity2
yield return LerpLight(point_light, intensity2, 2f);
}
}
IEnumerator LerpLight(Light targetLight, float toIntensity, float duration)
{
float currentIntensity = targetLight.intensity;
float counter = 0;
while (counter < duration)
{
counter += Time.deltaTime;
targetLight.intensity = Mathf.Lerp(currentIntensity, toIntensity, counter / duration);
yield return null;
}
}
Usage
public Light point_light;
float intensity1 = 3.08f;
float intensity2 = 1.0f;
void Start()
{
point_light = GetComponent<Light>();
StartCoroutine(LerpLightRepeat());
}
Ok, so I have an animationnthe speed of which is controlled by user's tapping m meaning I cant just lerp for a set time but have to have it depend on if this is true:
cameraAnim.GetCurrentAnimatorStateInfo (0).IsName ("stillOpening")
By the end of this animation (no matter how long it took, fast or slow) I need this float in my material to have lerped to its final value:
skybox.SetFloat ("_Exponent1",Mathf.Lerp(skybox.GetFloat("_Exponent1"), topSkyBoxOpen, ratio));
Meaning it has to be equal to topSkyBoxOpen at the end of "stillOpening". I don't know how to coordinate the timing.
I have tried this in the Update():
void openSkyLerp()
{
float ratio = 0;
float duration = 0.5f; // this is the one that will control how long it takes
// value is in second
float multiplier = 1 / duration;
while (cameraAnim.GetCurrentAnimatorStateInfo (0).IsName ("stillOpening")) {
ratio += Time.deltaTime * multiplier;
skybox.SetFloat ("_Exponent1",Mathf.Lerp(skybox.GetFloat("_Exponent1"), topSkyBoxOpen, ratio));
}
}
But nothing happens at all - I read this might be because its trying to have it all lerp in 1 frame. Is this possible? How can I lerp WHILE an animation is playing regardless of its speed?
Meaning it has to be equal to topSkyBoxOpen at the end of
"stillOpening". I don't know how to coordinate the timing.
The problem is that you are not even waiting for a frame. So, even if your equation is correct, all those cannot happen smoothly in a single frame. You wait for a frame with yield return null; and that requires coroutine.
Answered a very similar but not the-same question few hours ago.
If the stillOpening variable is the destination value, get the current _Exponent1 value before going into the while loop. Have a counter variable that increments each frame while counter is less than topSkyBoxOpen. You can then use Mathf.Lerp(currentVal, topSkyBoxOpen, counter / duration); in the SetFloat function.
bool running = false;
void openSkyLerp()
{
if (running)
{
return;
}
running = true;
StartCoroutine(OpenSky(5));
}
IEnumerator OpenSky(float duration)
{
float currentVal = skybox.GetFloat("_Exponent1");
float counter = 0;
while (counter < topSkyBoxOpen)
{
//Exit if not still opening
if (!cameraAnim.GetCurrentAnimatorStateInfo(0).IsName("stillOpening"))
{
yield break;
}
counter = counter + Time.deltaTime;
float val = Mathf.Lerp(currentVal, topSkyBoxOpen, counter / duration);
skybox.SetFloat("_Exponent1", val);
yield return null; //Wait for a frame
}
running = false;
}