How to not have my function executed in a single frame? - c#

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
}

Related

Defeat all targets

I have this script, but doesn't work, when i kill one enemy the door opens, i need to know how active the door when i kill all enemies. I need to destroy all enemies to open the door, but doesn't work good, if i kill one enemy the door opens for no reason.
public class DoorScript : MonoBehaviour
{
private GameObject[] enemyToKill;
public Transform pos1, pos2;
public float speed;
public Transform startPos;
Vector3 nextPos;
public bool EnemyDead = false;
// Start is called before the first frame update
void Start()
{
enemyToKill = GameObject.FindGameObjectsWithTag("EnemyR1");
nextPos = startPos.position;
}
// Update is called once per frame
void Update()
{
foreach (GameObject enemy in enemyToKill)
{
if (enemy == null)
{
EnemyDead = true;
if (transform.position == pos1.position && EnemyDead)
{
nextPos = pos2.position;
}
transform.position = Vector3.MoveTowards(transform.position, nextPos, speed * Time.deltaTime);
}
}
}
private void OnDrawGizmos()
{
Gizmos.DrawLine(pos1.position, pos2.position);
}
}
Well you are setting your bool to true for the first enemy that is killed.
You should rather check if all enemies are non existent. In general do never compare anything derived from UnityEngine.Object for null! Rather use the implicit bool operator.
Then you could use Linq Any (or All) like e.g.
using System.Linq;
...
void Update()
{
// In general use a bool check!
// A destroyed object reference is not necessarily null
// This returns true if there is still an existing enemy
if(!EnemyDead && enemyToKill.Anyy(enemy => enemy)) return;
// Still keep the flag so the check can be skipped
// As soon as the flag is already set
EnemyDead = true;
if (transform.position == pos1.position)
{
nextPos = pos2.position;
}
transform.position = Vector3.MoveTowards(transform.position, nextPos, speed * Time.deltaTime);
}
If I assume correctly (it is not clear from above code), You have set to all enemies some tag "EnemyR1" and now You want to check, if those enemies exists or not. If there are no enemies, you want to open the doors (like exit point for player).
It also depends, if you kill the enemy immediately (and destroy the GameObject), or you only mark enemy as something (set some property/set is non-active/set different tag).
In the code you want to:
void Start()
{
// There you will get ALL objects that have the tag (that was ok before)
// This function always returns Array. Sometimes empty.
enemyToKill = GameObject.FindGameObjectsWithTag("EnemyR1");
nextPos = startPos.position;
}
void Update()
{
// Now it depends how your logic is implemented:
// If you destroy automatically all objects, array will be empty, check length:
if (enemyToKill.Length == 0)
{
//There are no enemies to be killed, You can open the door
}
// Else if objects still exist, you must check if all have the condition.
// They can be disabled, have some property, etc.. In that case You need to:
bool allKilled = true;
foreach (GameObject enemy in enemyToKill)
{
if (enemy.active) //update this condition as you need
{
allKilled = false; // Set to false, when enemy is not killed
break; // and exit the loop
}
}
if (allKilled)
{
//There are no enemies to be killed, You can open the door
}
}

Is there a way to start a method from button click without using Update() function in unity

Below is my C# script. I added a button to my project with a On Click event and called the Rotate() method. But for some reason it is not working
using System.Threading;
using UnityEngine;
public class Orbit : MonoBehaviour {
public GameObject sun;
public float speed;
// Use this for initialization
void Start () {
}
public void Update()
{
Rotate();
}
public void Rotate()
{
transform.RotateAround(sun.transform.position, Vector3.up, speed *
Time.deltaTime);
}
}
I commented the Update() method when calling the Rotate() method. I also created a game object for the script.
The reason why it only works in Update currently is that
public void Rotate()
{
transform.RotateAround(sun.transform.position, Vector3.up, speed * Time.deltaTime);
}
needs to be called repeatetly. Otherwise it will only rotate for exactly one frame and cause of Time.deltaTime only a very small amount. But the onClick event of the Button component is fired only once. It is similar to e.g. Input.GetKeyDown which is only called once when the key goes down. There is no implementation in the Button component itslef to handle a continued button press.
What you want instead as far as I understand is rotating the object after the button click
start to rotate for ever
for a certain duration
until you press the button again
until it is released (-> implement a continuesly firing button see below)
The Button component alone can only do the first three:
Rotate for ever
Either using a Coroutine
private bool isRotating;
public void Rotate()
{
// if aready rotating do nothing
if(isRotating) return;
// start the rotation
StartCoroutine(RotateRoutine());
isRotating = true;
}
private IEnumerator RotateRoutine()
{
// whuut?!
// Don't worry coroutines work a bit different
// the yield return handles that .. never forget it though ;)
while(true)
{
// rotate a bit
transform.RotateAround(sun.transform.position, Vector3.up, speed * Time.deltaTime);
// leave here, render the frame and continue in the next frame
yield return null;
}
}
or still doing it in Update
private bool isRotating = false;
private void Update()
{
// if not rotating do nothing
if(!isRotating) return;
// rotate a bit
transform.RotateAround(sun.transform.position, Vector3.up, speed * Time.deltaTime);
}
public void Rotate()
{
// enable the rotation
isRotating = true;
}
Note that the Update solution is only for your understanding what is happening. It should not be used like that because it is not that efficient since Update is called continously and checks the bool also if not rotating yet. That produces unnecessary overhead. The same applies to all following examples: Prefere to use the Coroutines over Update (In this case! In other cases it is actuall better and more efficient to use one Update method instead of multiple concurrent Coroutines .. but that's another story.)
Rotate for a certain duration
As Coroutine
// adjust in the inspector
// how long should rotation carry on (in seconds)?
public float duration = 1;
private bool isAlreadyRotating;
public void Rotate()
{
// if aready rotating do nothing
if(isAlreadyRotating) return;
// start a rottaion
StartCoroutine(RotateRoutine());
}
private IEnumerator RotateRoutine()
{
// set the flag to prevent multiple callse
isAlreadyRotating = true;
float timePassed = 0.0f;
while(timePassed < duration)
{
// rotate a small amount
transform.RotateAround(sun.transform.position, Vector3.up, speed * Time.deltaTime);
// add the time passed since last frame
timePassed += Time.deltaTime;
// leave here, render the frame and continue in the next frame
yield return null;
}
// reset the flag so another rotation might be started again
isAlreadyRotating = false;
}
or in Update
public float duration;
private bool isRotating;
private float timer;
private void Update()
{
// if not rotating do nothing
if(!isRotating) return;
// reduce the timer by passed time since last frame
timer -= Time.deltaTime;
// rotate a small amount
transform.RotateAround(sun.transform.position, Vector3.up, speed * Time.deltaTime);
// if the timer is not 0 return
if(timer > 0) return;
// stop rottaing
isRotating = false;
}
public void Rotate()
{
// if already rotating do nothing
if(isRotating) return;
// start rotating
isRotating = true;
// enable timer
timer = duration;
}
Toggle rotation
This is very similar to the one before but this time instead of the timer you stop the rotation by clicking again. (You even could combine the two but than be carefull to reset the isRotating flag correctly ;) )
As Coroutine
private bool isRotating;
public void ToggleRotation()
{
// if rotating stop the routine otherwise start one
if(isRotating)
{
StopCoroutine(RotateRoutine());
isRotating = false;
}
else
{
StartCoroutine(RotateRoutine());
isRotating = true;
}
}
private IEnumerator RotateRoutine()
{
// whuut?!
// Don't worry coroutines work a bit different
// the yield return handles that .. never forget it though ;)
while(true)
{
// rotate a bit
transform.RotateAround(sun.transform.position, Vector3.up, speed * Time.deltaTime);
// leave here, render the frame and continue in the next frame
yield return null;
}
}
or as Update
private bool isRotating;
private void Update()
{
// if not rotating do nothing
if(!isRottaing) return;
// rotate a bit
transform.RotateAround(sun.transform.position, Vector3.up, speed * Time.deltaTime);
}
public void ToggleRotation()
{
// toggle the flag
isRotating = !isRotating;
}
Rotate until released
This is the most "complicated" part since the Button alone can not accomplish this (there is no "on Release"). But you can implement this using IPointerXHandler interfaces.
The good news: You can keep your original script as you have it currently
public void Rotate()
{
transform.RotateAround(sun.transform.position, Vector3.up, speed *
Time.deltaTime);
}
Now you need an extension for the button. It will call the whilePressed event repeatedly every frame like Update so you just have to reference your Rotate method in whilePressed instead of the onClick.
Again there are two options either implementing it as a Coroutine:
[RequireComponent(typeof(Button))]
public class HoldableButton : MonoBehaviour, IPointerDownHandler, IPointerUpHandler, IPointerExitHandler
{
// reference the same way as in onClick
public UnityEvent whilePressed;
private Button button;
private bool isPressed;
private void Awake()
{
button = GetComponent<Button>();
if(!button)
{
Debug.LogError("Oh no no Button component on this object :O",this);
}
}
// Handle pointer down
public void OnPointerDown()
{
// skip if the button is not interactable
if(!button.enabled || !button.interactable) return;
// skip if already rotating
if(isPressed) return;
StartCoroutine(PressedRoutine());
isPressed= true;
}
// Handle pointer up
public void OnPointerUp()
{
isPressed= false;
}
// Handle pointer exit
public void OnPointerExit()
{
isPressed= false;
}
private IEnumerator RotateRoutine()
{
// repeatedly call whilePressed until button isPressed turns false
while(isPressed)
{
// break the routine if button was disabled meanwhile
if(!button.enabled || !button.interactable)
{
isPressed = false;
yield break;
}
// call whatever is referenced in whilePressed;
whilePressed.Invoke();
// leave here, render the frame and continue in the next frame
yield return null;
}
}
}
or you could do the same in Update again as well
[RequireComponent(typeof(Button))]
public class HoldableButton : MonoBehaviour, IPointerDownHandler, IPointerUpHandler, IPointerExitHandler
{
public UnityEvent whilePressed;
private bool isPressed;
private Button button;
private void Awake()
{
button = GetComponent<Button>();
if(!button)
{
Debug.LogError("Oh no no Button component on this object :O",this);
}
}
private void Update()
{
// if button is not interactable do nothing
if(!button.enabled || !button.interactable) return;
// if not rotating do nothing
if(!isPressed) return;
// call whatever is referenced in whilePressed;
whilePressed.Invoke();
}
// Handle pointer down
public void OnPointerDown()
{
// enable pressed
isPressed= true;
}
// Handle pointer up
public void OnPointerUp()
{
// disable pressed
isPressed= false;
}
// Handle pointer exit
public void OnPointerExit()
{
// disable pressed
isPressed= false;
}
}
Place this component next to a Button component. You don't have to reference anything in onClick just leave it empty. Instead reference something in onPressed. Keep the Button component though since it handles also the UI style for us (like hover changes the color/sprite etc.)
Again: The Update solutions might look cleaner/simplier for now but are not as efficient (in this usecase) and easy to controll (this might be opinion based) as the Coroutine solutions.
Please search on the article regarding the key press functionalities . It would help you a lot in finding your answer. Update is used if we need to do something continuously in our project where as the key pressed is used when we have do it for once
this example is also being used to resolve your issue and use this Script when a specific button is is being pressed

How can I get my camera to momentarily pause between different positions when using lerp in Unity3D?

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);
}
}

Hide vision of image temporarily in Unity

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;
}

Cannot use InvokeRepeating with method parameters. How do I work around this?

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.

Categories

Resources