I am trying to invoke a shooting animation in the Update function and then wait for 0.5 seconds before spawning a laser shot. The below code isn't working for me. What can I do to achieve the desired result?
void Update()
{
if (Input.GetMouseButtonDown (0))
{
animator.SetTrigger("Shoot"); // Start animation
WaitAndShoot();
}
}
IEnumerator WaitAndShoot()
{
yield return new WaitForSeconds(0.5f);
Instantiate (shot, shotSpawn.transform.position,shotSpawn.transform.rotation);
}
You're forgetting to call it as a coroutine using StartCoroutine().
It should be:
void Update()
{
if (Input.GetMouseButtonDown (0))
{
animator.SetTrigger("Shoot"); // Start animation
StartCoroutine(WaitAndShoot());
}
}
IEnumerator WaitAndShoot()
{
yield return new WaitForSeconds(0.5f);
Instantiate (shot, shotSpawn.transform.position,shotSpawn.transform.rotation);
}
Keep in mind that this still allows you to trigger multiple shots before the first shot has been spawned. If you want to prevent that from happening, keep track of a shot being fired or not with a boolean, and check for that in addition to your GetMouseButtonDown.
Related
I'm making a first-person game and am having trouble animating my character. I have the right animations in place. I need to find a way to make the game detect when the player has just landed on the ground so that I can play the 'landing' animation. The problem is that the only way I've thought of thus far to do this is with a coroutine. But the coroutine, when initiated, completely freezes my whole application. I suspect it's because the act of being midair initiates the coroutine once per frame. Here's the script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerAnimationHandler : MonoBehaviour
{
Animator animator;
IEnumerator LandDetect()
{
while (!playermove.isGrounded)
{
animator.SetBool("Midair", true);
}
animator.SetTrigger("Land");
yield return null;
}
PlayerMove playermove;
void Start()
{
// These are the two most important components for this
// script. I'll need PlayerMove for the mini-API
// that I have in there and I'll need the animator
// for obvious reasons.
playermove = GetComponent<PlayerMove>();
animator = GetComponent<Animator>();
}
// Update is called once per frame
void Update()
{
JumpHandler();
}
void JumpHandler()
{
if (!playermove.isGrounded)
{
if (playermove.doubleJumpOccur)
{
animator.SetTrigger("DoubleJump");
StartCoroutine(LandDetect());
}
else
{
animator.SetBool("Midair", true);
StartCoroutine(LandDetect());
}
}
else if (playermove.jumpOccur)
{
animator.SetTrigger("Jump");
StartCoroutine(LandDetect());
}
}
}
I suspect that the application doesn't leave
while (!playermove.isGrounded)
{
animator.SetBool("Midair", true);
}
until you land. This loop is continuously executed in the same frame (which freezes your game) because you're not skipping frames. You need to put yield return 0 (0, not null. Null won't cause the coroutine to skip any frames) somewhere in it so it can be resumed in the next frame. Also may remove yield return null at the end of the method, I doesn't do anything at this point
That's how I think it should look:
IEnumerator LandDetect()
{
while (!playermove.isGrounded)
{
animator.SetBool("Midair", true);
yield return 0;
}
animator.SetTrigger("Land");
}
Also I don't know if using a coroutine is the best choice here. I usually use some trigger/collision detection for stuff like this.
I have an array of positions that I want my camera to move/lerp between. There are two buttons (button A and button B) that trigger the camera to move position. If the user presses button A, the camera will lerp to the previous position in the array. If the user presses button B, the camera will lerp to the next position in the array. However, before moving to a new position, I want the camera to lerp to an intermediate position, pause there for a couple of seconds, and then move. Here is the pseudocode for what I have at the moment:
void Update()
{
if (buttonPress == a) {
positionToMoveTo = positions[currentPosition--];
}
if (buttonpress == b) {
positionToMoveTo = positions[currentPosition++];
}
}
void LateUpdate()
{
camera.lerp(intermediatePosition);
StartCoroutine(pause());
}
IEnumerator pause()
{
yield return new WaitForSeconds(3f);
camera.lerp(positionToMoveTo);
}
This doesn't work though because I get strange jittering when switching camera positions and my intermediate position doesn't always occur. I think my problem has something to do with execution order but I can't figure it out. Any help would be great :)
You start a new Coroutine every frame since LateUpdate runs every frame after all Update calls are finished!
You could avoid this by a slightly different approach:
private bool isIntermediate;
private bool moveCamera;
private void LateUpdate ()
{
if(!moveCamera) return;
if(isIntermediate)
{
camera.lerp(intermediatePosition);
}
else
{
camera.lerp(positionToMoveTo);
}
}
private IEnumerator MoveCamera()
{
moveCamera = true;
isIntermediate=true;
yield return new WaitForSeconds(3f);
isIntermediate=false;
// Wait until the camera reaches the target
while(camera.transform.position == PositionToMoveTo){
yield return null;
}
// Stop moving
moveCamera = false;
// just to be sure your camera has exact the correct position in the end
camera.transform.position = PositionToMoveTo;
}
Alternatively you could do all the movement in the Coroutine without LateUpdate (but honestly I'm not sure if the Coroutines are done before or after Update)
private IEnumerator MoveCamera()
{
float timer = 3f;
while(timer>0)
{
timer -= Time.deltaTime;
camera.lerp(intermediatePosition);
yield return null;
}
// Wait until the camera reaches the target
while(camera.transform.position == PositionToMoveTo){
camera.lerp(PositionToMoveTo);
yield return null;
}
// just to be sure your camera has exact the correct position in the end
camera.transform.position = PositionToMoveTo;
}
This second one would be cleaner bjt as said I don't know if it is a requirement for you to have it run in LateUpdate
Note: the == operator of Vector3 has a precision of 0.00001. If you need a better or weaker precision you have to change to
if(Vector3.Distance(camera.transform.position, PositionToMoveTo) <= YOUR_DESIRED_THRESHOLD)
Now all you have to do is to call your Coroutine Everytime you want to change the camera position.
void Update()
{
if (buttonPress == a)
{
// Make sure the Coroutine only is running once
StopCoroutine(MoveCamera);
positionToMoveTo = positions[currentPosition--];
StartCoroutine (MoveCamera);
}
if (buttonpress == b)
{
// Make sure the Coroutine only is running once
StopCoroutine (MoveCamera);
positionToMoveTo = positions[currentPosition++];
StartCoroutine (MoveCamera);
}
}
I have a BonusController script as a component of a Bonus gameObject. This Bonus must be destroyed on collision but has to "act" for a few seconds. I have started a coroutine for this, but the script stops its execution (I think it's because I set the gameObject inactive). Here is the code of BonusController:
void OnTriggerEnter2D(Collider2D col)
{
StartCoroutine(speedUp(1));
gameObject.SetActive(false);
}
IEnumerator speedUp(float seconds)
{
Debug.Log("before");
yield return new WaitForSeconds(seconds);
Debug.Log("after"); // <--- This is never called
}
How can I remove the object and don't stop the coroutine script execution?
Can't you just disable the mesh renderer and collider? This way the gameobject will still exists, but the user won't be able to see it.
You cannot pull the ground on which you are standing. :)
Just disable the SpriteRenderer as you are using 2D methods. And keep the object alive and enable.
{
StartCoroutine(speedUp(1));
//gameObject.SetActive (false);
GetComponent<SpriteRenderer> ().enabled = false;
GetComponent<Collider2D> ().enabled = false;
// Above line will deactivate the first collider it will find.
}
IEnumerator speedUp(float seconds)
{
Debug.Log("before");
yield return new WaitForSeconds(seconds);
Debug.Log("after"); // <--- This is never called
}
In order to destroy the object after some time, call destroy inside the coroutine after the yield.
yield return WaitForSeconds(second);
Destroy(this);
Now it is properly destroyed and will free up memory opposed to still being there, invisible, but taking up resources.
I'm trying to implement a damage over time system, but Unity keeps saying "Trying to Invoke method...Couldn't be Called." The method I want to call uses the parameters "Collider coll", but from my research you can't invoke if the method has said paremters.
Here is my code:
using UnityEngine;
using UnityEngine.SceneManagement;
using System.Collections;
public class DamageOverTime : MonoBehaviour
{
public int PHP; //PHP = Player Health from PlayerHealth.cs script.
public int Damage; //Amount of damage.
public int DamageOverTime; //Damage over time.
public float DamageInterval_DOT = .25f; //Damage interval for damage over time.
public string Level;
PlayerHealth player;
void Start()
{
player = GameObject.Find("Player").GetComponent<PlayerHealth>();
InvokeRepeating("OnTriggerEnter", DamageInterval_DOT, DamageInterval_DOT);
}
void Update()
{
PHP = GameObject.Find("Player").GetComponent<PlayerHealth>().PlayerHP;
if (PHP <= 0)
{
SceneManager.LoadScene(Level);
}
}
void OnTriggerEnter(Collider coll)
{
if (coll.gameObject.tag == "Player")
{
GameObject.Find("Player").GetComponent<PlayerHealth>().PlayerHP = PHP - Damage;
}
if (coll.gameObject.tag == "Ball")
{
gameObject.SetActive(false);
SceneManager.LoadScene(Level);
}
}
}
My goal is to get the OnTriggerEnter function to loop ever 1/4 of a second (or lower possibly). Current upon entering a collider my health is drained by 60% in about a second which is far too fast. How should I work around this?
You can't use InvokeRepeating with OnTriggerEnter, because it's a trigger, which means it will trigger once when entrance of its holder occured.
Also InvokeRepeating means that you want to keep repeating an action continously which is not the case here. You want your trigger to occur once and then remove health points over time.
Solution - Coroutine
Unity3D makes custom usage of IEnumerable and yield keyword called Coroutine that always returns an IEnumerator. How it works? It will return control on every yield there is in our Coroutine and then will go back to exact point where it gave back control instead of starting function execution from scratch.
Code:
void OnTriggerEnter(Collider coll)
{
if (coll.gameObject.tag == "Player")
{
StartCoroutine("DamageOverTimeCoroutine");
}
if (coll.gameObject.tag == "Ball")
{
gameObject.SetActive(false);
SceneManager.LoadScene(Level);
}
}
public IEnumerator DamageOverTimeCoroutine()
{
var dotHits = 0;
while (dotHits < 4)
{
//Will remove 1/4 of Damage per tick
GameObject.Find("Player").GetComponent<PlayerHealth>().PlayerHP -= Damage / 4;
dotHits++;
//Will return control over here
yield return new WaitForSeconds(DamageInterval_DOT);
//And then control is returned back here once 0.25s passes
}
}
There's of course room for improvement in this Coroutine. You can pass parameters to it, same as you can to any other method in C#. Also you can implement other invervals that are not hardcoded. Code above is just a simple example on how to deal with such scenarios.
For continous damage over time
public IEnumerator DamageOverTimeCoroutine()
{
var dotHits = 0;
var player = GameObject.Find("Player").GetComponent<PlayerHealth>();
while (true)
{
//Stop removing damage, player is dead already
if (player.PlayerHP <= 0)
yield break;
//Will remove 5 Damage per tick
player.PlayerHP -= 5;
dotHits++;
//Will return control over here
yield return new WaitForSeconds(DamageInterval_DOT);
//And then control is returned back here once 0.25s passes
}
}
To stop Coroutine somewhere else from code use StopCoroutine("DamageOverTimeCoroutine") to stop certain coroutine type or StopAllCoroutines() to stop all coroutines that are active now.
I m new to unity and c# as well. I want to delay a function hence using WaitForSeconds() but my problem is that the statement after WaitForSeconds() is not executed and hence there is no delay shown.
Below is the code:
public void GameOver(){
StartCoroutine (Load ());
Debug.Log("loadDelay");
}
IEnumerator Load(){
Debug.Log ("enum");
yield return new WaitForSeconds(3);
Debug.Log("waited");
}
the output on console shows:
enum
loadDelay
At the same time without any delay when the GameOver() is called and Debug.Log("waited");
is not executed at all. I really don't understand the problem. Please explain if I'm doing something wrong.
Thanks
Code where GameOver() is called:
public class Collision : MonoBehaviour {
public GameObject explosion;
BgScroll bg;
void Start () {
bg = GetComponent<BgScroll> ();
}
// Update is called once per frame
void Update () {
}
void OnCollisionEnter2D(Collision2D col){
if (col.gameObject.tag == "Enemy") {
//Debug.Log("destroyed");
explosion.renderer.sortingLayerName = "foreground";
Instantiate (explosion, transform.position, Quaternion.identity);
Destroy (gameObject);
Debug.Log("destroyed");
bg.GameOver();
}
}
}
According to Unity's Documentation for WaitForSeconds you are doing it right. and your code works good when i tried to run it. There must be some other problem, or you might have other logs before "waited". I got output in console window as
enum
loadDelay
waited
and obviously "waited" will be printed after 3 seconds if your player is running at that time.