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);
}
}
Related
I'm making a 3D roulette game, where when the player presses the 'bet' button the ball will be positioned in a given location and also a force will be added to the ball. I've added 37 individual box colliders to know where the ball lands.
My problem is that from my understanding the bet function gets executed in a single frame. This means that the script checks for the fallen number before the ball has finished moving and landed inside a box collider. So the for the first bet the number fallen will be 0 even if it lands on another number, and then on the 2nd bet it will have the value of the first fallen number, etc...
public void BetSpinWheel()
{
uiController.repeatButton.interactable = true;
Spin();
int earnings = CalculateEarnings(currentBet.ToArray(), lastNumbers);
UpdateBalance(earnings);
lastBet.Clear();
foreach(Bet bet in currentBet)
{
lastBet.Add(bet);
}
currentBet.Clear();
updateUI();
uiController.ChangeLastNumberText();
}
private void Spin()
{
audioSource.Stop();
audioSource.Play();
ballController.SpinBall();
while (ballController.isMoving)
{ random++; }
if (!ballController.isMoving && ballController.hasBallEnteredCollider)
{
lastNumbers = ballController.numberFallen;
print(lastNumbers);
}
}
and here is the ballController.SpinBall() function:
public void SpinBall()
{
rb.constraints = RigidbodyConstraints.None;
transform.position = ballStartPosition;
rb.velocity = Vector3.zero;
transform.rotation = Quaternion.Euler(0f, 0f, 0f);
rb.AddForce(forceToAdd, ForceMode.Impulse);
print(isMoving);
}
If you would like to view the whole project, you can find it here:
https://github.com/hamzaOud/Assignment02
Use a Coroutine:
// The button should call this
public void BetSpinWheel() {
StartCoroutine(BetSpinWhellCoroutine());
}
private IEnumerator BetSpinWheelCoroutine() {
// Your bet stuff here
}
You can also 'store' a coroutine, so that you can stop it if you need to:
private Coroutine betSpinWheelCoroutine
// The button should call this
public void BetSpinWheel() {
// Stop the coroutine first if it was running.
if (betSpinWheelCoroutine != null){
StopCoroutine(betSpinWheelCoroutine);
}
betSpinWheelCoroutine = StartCoroutine(BetSpinWhellCoroutine());
}
private IEnumerator BetSpinWheelCoroutine() {
// Your bet stuff here
}
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'm going to enable bloody screen when got attack then disble it for two seconds.
The first way I tried is using SetActive (true/flase) .
It could work once but never worked back anymore.
I found the question by google, it seems that function will disable everything of that component.
So I found the second way which is using Image Class to do this.
but I not sure whether the way I used correctly or not.
The error msg : Object reference not set to an instance of an object
First Way (working once)
public class GameControllerScript : MonoBehaviour {
public GameObject bloodyScreen;
void Start () {
bloodyScreen.gameObject.SetActive (false); // disable at first
}
void Update () {
}
public void zombieAttack(bool zombieIsThere ){
bloodyScreen.gameObject.SetActive (true);
StartCoroutine(WaitTwoSeconds());
}
IEnumerator WaitTwoSeconds(){
yield return new WaitForSeconds (2f);
bloodyScreen.gameObject.SetActive (false);
} }
Second Way (not working)
public class GameControllerScript : MonoBehaviour {
public Image image;
void Start () {
GameObject go = GameObject.Find("Canvas");
if (!go)
return;
image = go.GetComponent<Image>();
image.enable = false;
}
void Update () {
}
public void zombieAttack(bool zombieIsThere ){
image.enabled = true;
StartCoroutine(WaitTwoSeconds());
}
IEnumerator WaitTwoSeconds(){
yield return new WaitForSeconds (2f);
image.enabled = false;
} }
Sorry for my english if soemthing is not clear.
What my object likes https://i.stack.imgur.com/DChuS.png
there are so many ways of doing the BLOODY SCREEN.
1.)
STEP 1
Make an array of texture then put all of your texture there
STEP 2
On your OnTriggerEnter or OnCollisionEnter or what ever function you want and try implementing this code
Texture[] arrayOfTexture = new Texture
foreach(Texture textures in arrayOfTexture){
//set the alpha here from 1-0/0-1
}
2.)
You can do it also in an Update() function and put it on a timer you can do it something like
float timer = 10f;
void Update(){
timer -= time.DeltaTime;
if(timer < 1){
//reset timer here
}else{
//do the alpha thing here
}
}
void ResetTimer(){
timer = 10f;
}
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.
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.