In unity I am aware how to translate and rotate an object and I always do it in the Update function, the problem I am having is that I want a series of translations and rotations to happen in sequence but the only translation/rotation that occurs is the one that I call first in the code, is there any way to do a translation, wait a certain amount of time and then carry out another translation for example. Thanks.
void Update ()
{
if (enemyHit == false)
{
//enemy moving
transform.LookAt(TTarget);
}
else if (enemyHit == true)
{
Debug.Log (enemyHit);
Evade();
}
}
IEnumerator Wait(float duration)
{
yield return new WaitForSeconds(duration);
}
void Evade()
{
enemyHit = playerMovement.hitEnemy;
transform.Translate(Vector3.back * Time.deltaTime * movementSpeed);
Wait(2);
transform.Rotate(0,90,0);
}
I tried using a seperate function but that didnt seem to do anything.
IEnumerator Wait()
{
yield return new WaitForSeconds(2);
}
You may want to start a CoRoutine as well.
StartCoroutine("Wait");
This should allow you to achieve what you're trying to do.
Related
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 player, which I rotate using script below. It rotate player on 20 degree, not 10 (and I need 10). Can't understand why. When I press q, it executes only 1 time.
private UnityStandardAssets.Characters.FirstPerson.FirstPersonController firstPersonController;
public GameObject player;
void Start ()
{
firstPersonController = player.GetComponent<UnityStandardAssets.Characters.FirstPerson.FirstPersonController>();
}
void Update ()
{
StartCoroutine("RotatePlayerDelay");
}
IEnumerator RotatePlayerDelay()
{
Debug.Log("1");
firstPersonController.m_MouseLook.myAngle += 10; // here I have +20, not +10
yield return new WaitForSeconds(0.005f);
firstPersonController.m_MouseLook.myAngle = 0;
Debug.Log("2");
}
P.S. I need coroutine, because without it it will be rotate forever
Unlike FixedUpdate which is called once every Time.fixedDeltaTime seconds, there is no exact time step for Update method. It is a coroutine which depends on FPS of the game and duration of all the renderers and behaviours and thus dynamically changes.
You can think of Update as:
void Start ()
{
StartCoroutine(_update);
}
IEnumerator _update()
{
while(true)
{
Update();
yield return null;
}
}
When you start your coroutine in Update method you can't tell whether or not the previous frame has ended or not. If your FPS is lower than 1/0.005 = 200.0 FPS the different calls of the coroutine surely overlap with each other.
0.005 here refers to yield return new WaitForSeconds(0.005f)
Try not to start the coroutine in another coroutine:
void Start ()
{
StartCoroutine("RotatePlayerDelay");
}
IEnumerator RotatePlayerDelay()
{
while(true)
{
Debug.Log("1");
firstPersonController.m_MouseLook.myAngle += 10; // here I have +20, not +10
yield return new WaitForSeconds(0.005f);
firstPersonController.m_MouseLook.myAngle = 0;
Debug.Log("2");
yield return new WaitForSeconds(0.005f);
}
}
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.
Im working in Unity3d (this is more a C# question, so I doubt that is an issue). Im working on a movement system like you would find in Civilization. I have a loop setup so that you can move 2 squares per turn. This works fine. I click on a square 10 blocks away and it takes 5 turns to get there. Now im trying to make the pawn lerp between blocks. I have got lerp to work, problem is, it jumps from the current tile to the 1st tile, then transitions to the 2nd tile where its supposed to be. I used a coroutine to make this work instead of the update function (as the update would cause it to just lerp to the final destination instead of from current, to first, to second). So what im running into is the loop that goes through each move the pawn has, isnt waiting for the coroutine to complete before continuing its own loop. Here is the code
public void MoveNextTile()
{
float remainingMoves = moveSpeed;
while (remainingMoves > 0)
{
if (currentPath == null)
return;
//Get the cost from current tile to next tile
remainingMoves -= map.CostToEnterTile(currentPath[0].x, currentPath[0].y, currentPath[1].x, currentPath[1].y);
//Move us to the next tile in the sequence
toVector = map.TileCoordToWorldCoord(currentPath[1].x, currentPath[1].y);
Vector3 fromVec = transform.position;
StartCoroutine(MoveObject(fromVec, toVector, 1.0f));
//transform.position = map.TileCoordToWorldCoord(currentPath[1].x, currentPath[1].y);
//Remove the old current tile
this.tileX = currentPath[0].x;
this.tileY = currentPath[0].y;
currentPath.RemoveAt(0);
if (currentPath.Count == 1)
{
this.tileX = currentPath[0].x;
this.tileY = currentPath[0].y;
currentPath = null;
}
}
}
IEnumerator MoveObject(Vector3 source, Vector3 target, float overTime)
{
float startTime = Time.time;
while (Time.time < startTime + overTime)
{
transform.position = Vector3.Lerp(source, target, (Time.time - startTime) / overTime);
yield return null;
}
transform.position = target;
}
I know this is a noob question. I just never have needed to do this in C# before. Thank in advance for all the help
I suggest you research into how coroutines work.
Your coroutine doesn't execute fully and then return to complete the rest of your MoveNextTile function. It actually executes up until the first yield statement and then continues execution of MoveNextTile. Each subsequent frame will continue to run one 'step' of the coroutine until the next yield statement in an attempt to replicate asynchronous methods.
What you want to do is tell your program to explicitly wait for your coroutine to finish. To do so;
yield return StartCoroutine(MoveObject(fromVec, toVector, 1.0f));
Of course, you can only use this statement inside of an IEnumerator. So you would have to change your void MoveNextTile function to IEnumerator MoveNextTile. You end up with something as follows;
public IEnumerator MoveNextTile() {
// Ommited
while (remainingMoves > 0) {
//Ommited
yield return StartCoroutine(MoveObject(fromVec, toVector, 1.0f));
// Now MoveNextTile will continue after your MoveObject coroutine finishes.
}
}
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.