I successfully used a fader in a practice game I made a few months back. I am now trying to implement a similar approach for my mobile game.
My goal: when a user goes to the play screen, the screen fades out, loads the scene async, and fades back in once that method is finished.
MY CODE
These are my variables:
CanvasGroup canvasGroup;
Coroutine currentActiveFade = null;
int sceneIndexToLoad = -1;
float fadeInTime = 2f;
float fadeOutTime = 1f;
float fadeWaitTime = 0.5f;
These are my secondary methods:
public void FadeOutImmediately()
{
canvasGroup.alpha = 1;
}
public Coroutine FadeOut(float time)
{
return Fade(1, time);
}
public Coroutine Fade(float target, float time)
{
if (currentActiveFade != null)
{
StopCoroutine(currentActiveFade);
}
currentActiveFade = StartCoroutine(FadeRoutine(target, time));
return currentActiveFade;
}
private IEnumerator FadeRoutine(float target, float time)
{
while (!Mathf.Approximately(canvasGroup.alpha, target))
{
canvasGroup.alpha = Mathf.MoveTowards(canvasGroup.alpha, target, Time.deltaTime / time);
yield return null;
}
}
public Coroutine FadeIn(float time)
{
return Fade(0, time);
}
This is my primary function, where I get my error:
public IEnumerator TransitionToNewScene()
{
if(sceneIndexToLoad < 0)
{
Debug.LogError("Scene to load is not set");
yield break;
}
DontDestroyOnLoad(gameObject);
//GETS STUCK ON THIS LINE
yield return FadeOut(fadeOutTime);
yield return SceneManager.LoadSceneAsync(sceneIndexToLoad); //this still executes
yield return new WaitForSeconds(fadeWaitTime);
FadeIn(fadeInTime);
Destroy(gameObject);
}
My code reaches and starts the FadeOut coroutine, and also loads the next scene. When I put a debug statement underneath my FadeOut call, it doesn't reach it, but seems to reach the next yield return.
MY ISSUE: The canvasGroup alpha reaches 1 in the next scene, but that's where it stops. The FadeIn code is not reached, and my gameObject is not destroyed. Why is this happening?
Please note: this code is almost exactly like in my previous project, which works perfectly.
Don't do this Destroy(gameObject); until the fadein finishes, all coroutines will stop when gameobject is destroyed.
I might put it in the end of FadeRoutine:
private IEnumerator FadeRoutine(float target, float time)
{
while (!Mathf.Approximately(canvasGroup.alpha, target))
{
canvasGroup.alpha = Mathf.MoveTowards(canvasGroup.alpha, target, Time.deltaTime / time);
yield return null;
}
if(target == 0)
Destroy(gameObject);
}
GameObject with the transition script on it has to be a top level in the hierarchy.
Basically, it cannot be a child of any GameObject as DontDestroyOnLoad doesn't work for child objects.
Related
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 am creating a 3D Shooter and I am trying to make an enemy deal damage to the player every set seconds. I have made the enemy deal damage with a raycast but it deals the damage way too fast.
I thought using yield return new WaitForSeconds(2) would take 1 damage away from the player every 2 seconds but it deals damage to the player a lot faster.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class EnemyMove : MonoBehaviour
{
public Transform target;
public Transform player;
public float enemySpeed;
public int moveTrigger = 1;
public int isAttacking;
public float distanceFromPlayer;
void Update()
{
distanceFromPlayer = Vector3.Distance(target.transform.position, player.transform.position);
if (distanceFromPlayer <= 10 && moveTrigger == 1)
{
transform.LookAt(target);
StartCoroutine(EnemyDamage());
}
if (distanceFromPlayer <10 && moveTrigger == 1 && distanceFromPlayer >3)
{
transform.Translate(Vector3.forward * enemySpeed * Time.deltaTime);
}
}
IEnumerator EnemyDamage()
{
RaycastHit PlayerHit;
if (Physics.Raycast(target.transform.position, target.transform.forward, out PlayerHit))
{
Debug.Log(PlayerHit.transform.name);
Target target = PlayerHit.transform.GetComponent<Target>();
if (target != null)
{
yield return new WaitForSeconds(2);
GlobalHealth.playerHealth -= 1;
yield return null;
}
}
}
}
You are starting a coroutine in every update loop so every frame your damage coroutine gets called and applies your 1 damage after 2 seconds.
If you want a damage over time effect the suggestion of using an game tick timer is correct, but if you want your enemy to only be able to attack every x seconds you need to implement some kind of cooldown on your damage function. For example add up Time.deltaTime until the time you want has past, and only then the enemy is able to deal damage again. You can do that with a boolean.
As mentioned in other answers, you are starting a new coroutine each frame. You should do all your waiting and looping inside your coroutine, as execution exits and re-enters your coroutine from the yeild statment. This is how you would write this to work
// in update
if (distanceFromPlayer <= 10 && moveTrigger == 1){
transform.LookAt(target);
if(!isAttacking)
StartCoroutine(EnemyDamage());
}
IEnumerator EnemyDamage()
{
isAttacking = true;
while(distanceFromPlayer <= 10){ // in range
RaycastHit PlayerHit;
if (Physics.Raycast(target.transform.position, target.transform.forward, out PlayerHit)){
Target target = PlayerHit.transform.GetComponent<Target>();
if (target != null){
GlobalHealth.playerHealth -= 1;
yield return new WaitForSeconds(2);
}
}
}
isAttacking = false; // out of range
yield return null;
}
You can use FixedUpdate → set a local variable bool alreadyShoot = false.
In your EnemyDamage() add a if(!alreadyShoot) before damages application and set alreadyShoot to true after damages application.
In FixedUpdate you can set alreadyShoot to false.
You can choose an other solution to set alreadyShoot to false instead of FixedUpdate (for example a coroutine that trigger each seconds, ...)
in update loop, check when two seconds have passed when the player is in range
float timeInRange = 0.0f;
bool inRange = false;
if (distanceFromPlayer <= 10 && moveTrigger == 1)
{
inRange = true;
}
else
{
inRange = false;
timeInRange = 0;
}
if (inRange)
{
timeInRange += Time.DeltaTime;
}
if (timeInRange > 2.0f)
{
GlobalHealth.playerHealth -= 1;
timeInRange = 0.0f;
}
I have a list (called path) with GameObjects. The current GameObject (with the script attached) should move from one position of the GameObjects in the path to the next stepwise. My current code makes it move to the last position of the path immediately. I already tried with WaitForSeconds at the end of the Coroutine but as they are all started at once, it has no effect.
What can I do to get the step-after-step effect?
Here my code so far:
public List<GameObject> path;
private Vector3 start;
private Vector3 target;
private float lungeSpeed = .8f;
private float lungeDistance = 5;
private IEnumerator coroutine;
public void StartPath() {
foreach (GameObject field in path) {
start = transform.position;
target = new Vector3(field.transform.position.x + lungeDistance, field.transform.position.y, field.transform.position.z);
coroutine = MoveObject(start, target, lungeSpeed);
StartCoroutine(coroutine);
}
}
IEnumerator MoveObject(Vector3 start, Vector3 target, float speed)
{
float t = 0.0f;
float rate = 1.0f / speed;
while (t < 1.0)
{
t += Time.deltaTime * rate;
transform.position = Vector3.Lerp(start, target, t);
yield return null;
}
yield return new WaitForSeconds(1);
}
Right now your code in StartPath isn't waiting on MoveObject to finish. You could solve this by running StartPath in a coroutine and have use yield return MoveObject(start, target, lungespeed).
Still will stop the execution of the foreach loop in startPath untill MoveObject has finished with the yield return new WaitForSeconds
public IEnumerator StartPath() {
foreach (GameObject field in path) {
start = transform.position;
target = new Vector3(field.transform.position.x + lungeDistance, field.transform.position.y, field.transform.position.z);
coroutine = MoveObject(start, target, lungeSpeed);
yield return StartCoroutine(coroutine);//this will keep the foreach loop from iterating untill the coroutine has finished
}
}
also a little side note:
(because the coroutine is executed in a new parallel thread)
Is incorrect. Coroutines do not run on a seperate thread. A coroutines runs on the main thread as all the rest of your code, it just does a little trick where it pauses and resumes execution based on your yield statements, but still on the main thread.
If you want to run something on a seperate thread you need to call new Thread(). However this is a whole different piece of cake as thread cannnot inherit from Monobehaviour
Basically I want to make a player that can transform into demon at will whenever the user press the power-up button however I want the transformation to end after 60 seconds (when the transformation ends I want the player revert back to his original state). I also want the transformation to end if the player gets hit by an enemy. So far I've made this code and it works but I'm having trouble resetting the yield wait for seconds back to 60 seconds when if the player gets hit by an enemy and if the user decided to press the button to transform the player back into a demon. Can anyone help me with this problem?
In my hierarchy I have my player as the parent and my demon player as the child. A playermovement script attached to the player as well as the transformation script below:
public GameObject demon;
public BoxCollider2D col;
public Renderer rend;
public ParticleSystem par1;
public static Vector3 target;
void Start () {
target = transform.position;
}
void Update () {
target.z = transform.position.z;
}
public void DemonCharacter() {
StartCoroutine (PowerUpCoroutine ());
}
private IEnumerator PowerUpCoroutine() {
yield return new WaitForSeconds (0.3f);
par1.Play (); // particle system animation to cover transformation happening
par1.transform.position = target;
yield return new WaitForSeconds (0.2f);
demon.SetActive (true); // activates demon gameobject
rend.enabled = false; // deactivate players spriterenderer
col.enabled = false;
yield return new WaitForSeconds (60f);
demon.SetActive (false); // deactivates demon gameobject
rend.enabled = true; // activate players spriterenderer
col.enabled = true;
par1.Stop ();
}
And on my demon player, I attached this script;
I works but when the user clicks on the button to transform into a demon the yield waitforseconds doesn't stop, so when the player transform into a demon seconds later the demon player transforms back into the player rather than resetting the yield wait for seconds.
public BoxCollider2D Playercol;
public Renderer PlayerRend;
void Start()
{
}
void Update ()
{
}
void OnTriggerEnter2D(Collider2D col) {
if (col.tag == "enemy") {
demon.SetActive (false);
PlayerRend.enabled = true;
Playercol.enabled = true;
}
}
Another way than what #m.rogalski suggested would be to use a simple float variable as timer:
public GameObject demon;
public BoxCollider2D col;
public Renderer rend;
public ParticleSystem par1;
public static Vector3 target;
private float demonTimer;
void Start()
{
target = transform.position;
demonTimer = 0.0f;
}
void Update()
{
target.z = transform.position.z;
if (demonTimer > 0.0f)
{
demonTimer -= Time.deltaTime;
if (demonTimer <= 0.0f)
{
demon.SetActive(false);
rend.enabled = true;
col.enabled = true;
}
}
}
public void DemonCharacter()
{
par1.Play();
par1.transform.position = target;
demon.SetActive(true);
rend.enabled = false;
col.enabled = false;
demonTimer = 60.0f;
}
public void CancelDemon()
{
demonTimer = 0.0f;
}
Hope this helps,
My suggestion would be for you to modify your Coroutine to not use WaitForSeconds but use it's own timing calculations.
// create the flag indicating interruption
bool _interrupt = false;
// create your coroutine
IEnumerator PowerUpCoroutine()
{
// set the time you want to hold transformation
const float TRANSFORMATION_INTERVAL = 60.0f;
// currently elapsed time
float currentlyElapsed = 0.0f;
// add your logic for pre-transformation
while ( currentlyElapsed < TRANSFORMATION_INTERVAL && !_interrupt )
{
yield return null;
currentlyElapsed += Time.deltaTime;
}
// add post-transformation logic
// revert transformation process
_interrupt = false;
}
Now if you run this coroutine calling StartCoroutine(PowerUpCoroutine()); you can interrupt it setting _interrupt flag to true. eg :
public void Interrupt()
{
_interrupt = true;
}
// in some update :
if ( gotHitThisFrame == true )
Interrupt();
I'm still working on the game and i ran into another problem, I'm trying to make an infinite loop which waits for a couple seconds every execute, i currently have:
StartScript.cs
using UnityEngine;
using System.Collections;
using ProgressBar;
public class StartScript : MonoBehaviour {
private ProgressBarBehaviour hunger;
private ProgressBarBehaviour stamina;
private ProgressRadialBehaviour health;
private ProgressBarBehaviour thirst;
public void OnGUI(){
this.hunger = GameObject.Find("hungerBar").GetComponent<ProgressBarBehaviour>();
this.stamina = GameObject.Find("staminaBar").GetComponent<ProgressBarBehaviour>();
this.health = GameObject.Find("healthBar").GetComponent<ProgressRadialBehaviour>();
this.thirst = GameObject.Find("thirstBar").GetComponent<ProgressBarBehaviour>();
this.health.Value = 100f;
this.stamina.Value = 100f;
StartCoroutine ("runHunger");
}
bool addBar(ProgressBarBehaviour target, float percentage){
Debug.Log (percentage);
if ((target.Value + percentage) <= 100f) {
target.IncrementValue (percentage);
return true;
}
return false;
}
bool damageBar(ProgressBarBehaviour target, float percentage){
if ((target.Value - percentage) >= 0f) {
target.DecrementValue (percentage);
return true;
}
return false;
}
bool addRadial(ProgressRadialBehaviour target, float percentage){
if ((target.Value + percentage) <= 100f) {
target.IncrementValue (percentage);
return true;
}
return false;
}
bool damageRadial(ProgressRadialBehaviour target, float percentage){
if ((target.Value - percentage) >= 0f) {
target.DecrementValue (percentage);
return true;
}
return false;
}
IEnumerator runHunger(){
while (true) {
yield return new WaitForSeconds(10f);
/*if (!this.addBar(this.hunger,5f)) {
this.damageRadial(this.health,3f);
}*/
Debug.Log("Time: "+Time.time);
}
}
IEnumerator runHealth(){
while (true) {
}
}
/*
IEnumerator runThirst(){
while (true) {
if (!this.thirst.Add (2)) {
this.health.Damage(8);
}
}
}*/
}
As you guys can see I'm trying to make a while(true){} loop with an yield return new WaitForSeconds(), the idea is that it runs the function in the loop every (in this test case) 10 seconds.
The first time executing works like a charm, it waits for 10 seconds, but after that it only waits for like 0.1 seconds and executes again.
I hope someone can help me with this problem.
There are too many wrong things I currently see in your code.
This:
IEnumerator runHealth(){
while (true) {
}
}
Change it to
IEnumerator runHealth(){
while (true) {
yield return null;
}
}
Also This:
IEnumerator runThirst(){
while (true) {
if (!this.thirst.Add (2)) {
this.health.Damage(8);
}
}
}
Change it to
IEnumerator runThirst(){
while (true) {
if (!this.thirst.Add (2)) {
this.health.Damage(8);
}
yield return null;
}
}
If you are gonna have while(true) in a coroutine, you must have yield return something or it will lock your program. Fix these problems first. It is good to have it just before the closing bracket of the while loop.
I am sure you did this in other scripts too.
Go over the rest of other scripts and do the same thing. Put yield return null inside each while loop in each coroutine function.
ANOTHER BIG MISTAKE:
Remove every code from OnGUI function and put it in Start function like below. The code is being called too several times per frame and that will lead to slow down as you will be creating many many coroutines that never stops due to your while loop in the coroutine function.Putting it in the Start function will call it once.
void Start()
{
this.hunger = GameObject.Find("hungerBar").GetComponent<ProgressBarBehaviour>();
this.stamina = GameObject.Find("staminaBar").GetComponent<ProgressBarBehaviour>();
this.health = GameObject.Find("healthBar").GetComponent<ProgressRadialBehaviour>();
this.thirst = GameObject.Find("thirstBar").GetComponent<ProgressBarBehaviour>();
this.health.Value = 100f;
this.stamina.Value = 100f;
StartCoroutine ("runHunger");
}
Call the StartCoroutine ("runHunger");
somewhere other than in OnGUI(). It will work then. Since everytime OnGUI is called it starts a new coroutine
Call the StartCoroutine in Start() or someplace else