I've been trying to do a timer if a collectible ring vanishes and is transparent.
My question is that it is only playing the boolean if statement in fixedupdate once? Do I need to change the Gameobject that the script is attached on or do I need to put my script on something that isn't going to be IsActive(false)?
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class RingScript : MonoBehaviour
{
[Header("How long it will take that the ring is active again")]
public float standardtimer;
public float timer;
private int uiTimer;
[Header("The text and GameObject that will show you the time how long it takes to respawn")]
public Text timertext;
public GameObject timerobject;
public GameObject thisRing;
[Header("How many Forestgems you get")]
public int ForestGemCount;
public bool starttimer = false;
// Start is called before the first frame update
void Start()
{
timer = standardtimer;
timerobject.SetActive(false);
thisRing.SetActive(true);
}
// Update is called once per frame
void FixedUpdate()
{
uiTimer = (int)timer;
timertext.text = uiTimer.ToString();
if (starttimer == true)
{
timer -= Time.deltaTime;
GemScript.ForestGems += ForestGemCount;
timerobject.SetActive(true);
thisRing.SetActive(false);
if (timer <= 0.1f)
{
timer = standardtimer;
timerobject.SetActive(false);
thisRing.SetActive(true);
starttimer = false;
}
}
}
private void OnTriggerEnter(Collider collision)
{
if(collision.gameObject.tag == "Player")
{
starttimer = true;
}
}
}
Related
I tried to use a bool to check for trigger before using InvokeRepeating but nothing happened. I can't put anything in the Update() method because then there'll be dozens of projectiles firing constantly.
Currently, the projectile fires every 3 seconds which is fine. However I only want those projectiles to fire if I'm standing in a trigger area (which I already have), but I can't seem to get it to work.
public class FireProjectile : MonoBehaviour
{
public GameObject Player;
public Transform launchPoint;
public GameObject projectile;
public float launchVelocity = 10f;
public bool isFiring;
void Start()
{
InvokeRepeating("fireProjectile", 0f, 3f);
}
void Update()
{
//
}
void fireProjectile()
{
var _projectile = Instantiate(projectile, launchPoint.position, launchPoint.rotation);
_projectile.GetComponent<Rigidbody>().velocity = launchPoint.forward * launchVelocity;
}
private void OnTriggerEnter(Collider other)
{
if (other.gameObject == Player)
{
isFiring = true;
}
}
private void OnTriggerExit(Collider other)
{
if (other.gameObject == Player)
{
isFiring = false;
}
}
}
To mimic your code, we can set up a timer that keeps counting up to your required delay, then resets. When it resets, you can fire the projectile if you were in the trigger.
Note this is just one of many ways to accomplish this. It’s also a very simple way to stop a player spamming projectiles by moving in and out of the trigger area - the player only fires every delay seconds, if they’re currently within the trigger area.
public class FireProjectile : MonoBehaviour
{
public GameObject Player;
public Transform launchPoint;
public GameObject projectile;
public float launchVelocity = 10f;
public bool isFiring;
public float delay = 3;
private float _timer;
void Update()
{
_timer += Time.deltaTime;
if ( _timer >= delay )
{
// reset timer and fire if with trigger area.
_timer -= delay;
if ( isFiring )
fireProjectile();
}
}
void fireProjectile()
{
var _projectile = Instantiate(projectile, launchPoint.position, launchPoint.rotation);
_projectile.GetComponent<Rigidbody>().velocity = launchPoint.forward * launchVelocity;
}
private void OnTriggerEnter(Collider other)
{
if (other.gameObject == Player)
isFiring = true;
}
private void OnTriggerExit(Collider other)
{
if (other.gameObject == Player)
isFiring = false;
}
}
I hope you all are doing well. I have been following a Unity tutorial for a rhythm game and I have found this bug that I could not get past. Essentially, my OnTriggerExit2D is getting called too early. I'll include a picture in the conclusion of this post. I have tried logging the game object and it seems that all of my button objects suffer the same fate. I have included a link of the tutorial that I have been following in the conclusion. Any help towards figuring this out would be helpful.
Tutorial Link: https://www.youtube.com/watch?v=PMfhS-kEvc0&ab_channel=gamesplusjames
What my game looks like, the missed shows up when I've hit it.
Debug Output
GameManager
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class GameManager : MonoBehaviour
{
public AudioSource theMusic;
public bool startPlaying;
public BeatScroller theBS;
public static GameManager instance;
public int currentScore;
public int scorePerNote = 100;
public int scorePerGoodNote = 125;
public int scorePerPerfectNote = 150;
public int currentMultiplier;
public int multiplierTracker;
public int [] multiplierTresholds;
public Text scoreText;
public Text multiText;
// Start is called before the first frame update
void Start()
{
instance = this;
scoreText.text = "Score: 0";
multiText.text = "Multiplier: x1";
currentMultiplier = 1;
}
// Update is called once per frame
void Update()
{
if(!startPlaying){
if(Input.anyKeyDown){
startPlaying = true;
theBS.hasStarted = true;
theMusic.Play();
}
}
}
public void NoteHit(){
Debug.Log("Note Hit On Time");
if(currentMultiplier-1 < multiplierTresholds.Length){
multiplierTracker++;
if(multiplierTresholds[currentMultiplier-1] <= multiplierTracker){
multiplierTracker = 0;
currentMultiplier++;
}
}
multiText.text = "Multiplier: x"+currentMultiplier;
//currentScore += scorePerNote * currentMultiplier;
scoreText.text = "Score: "+currentScore;
}
public void NormalHit(){
currentScore += scorePerNote * currentMultiplier;
NoteHit();
}
public void GoodHit(){
currentScore += scorePerGoodNote * currentMultiplier;
NoteHit();
}
public void PerfectHit(){
currentScore += scorePerPerfectNote * currentMultiplier;
NoteHit();
}
public void NoteMissed(){
Debug.Log("MISSED!");
multiText.text = "Multiplier: x1";
}
}
BeatScroller
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class BeatScroller : MonoBehaviour
{
public float beatTempo;
public bool hasStarted;
// Start is called before the first frame update
void Start()
{
beatTempo = beatTempo / 60f;
}
// Update is called once per frame
void Update()
{
if(!hasStarted){
}else{
transform.position -= new Vector3(0f, beatTempo*Time.deltaTime, 0f);
}
}
}
ButtonController
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ButtonController : MonoBehaviour
{
// Start is called before the first frame update
private SpriteRenderer theSR;
public Sprite defaultImage;
public Sprite pressedImage;
public KeyCode keyToPress;
void Start()
{
theSR = GetComponent<SpriteRenderer>();
}
// Update is called once per frame
void Update()
{
if(Input.GetKeyDown(keyToPress))
{
theSR.sprite = pressedImage;
}
if(Input.GetKeyUp(keyToPress))
{
theSR.sprite = defaultImage;
}
}
}
noteObject
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class noteObject : MonoBehaviour
{
public bool canBePressed;
public KeyCode KeyToPress;
public GameObject hitEffect, goodEffect, perfectEffect, missedEffect;
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
if(Input.GetKeyDown(KeyToPress))
{
if(canBePressed)
{
gameObject.SetActive(false);
if(Mathf.Abs(transform.position.y) > 0.25){
GameManager.instance.NormalHit();
Debug.Log("Normal Hit!");
Instantiate(hitEffect,transform.position, hitEffect.transform.rotation);
}else if(Mathf.Abs(transform.position.y) > 0.05f){
GameManager.instance.GoodHit();
Debug.Log("Good Hit!!");
Instantiate(goodEffect,transform.position, goodEffect.transform.rotation);
}else{
GameManager.instance.PerfectHit();
Debug.Log("PERFECT HIT!!!");
Instantiate(perfectEffect,transform.position, perfectEffect.transform.rotation);
}
}
}
}
private void OnTriggerEnter2D(Collider2D other)
{
if(other.tag == "Activator")
{
canBePressed = true;
}
}
private void OnTriggerExit2D(Collider2D other)
{
if(other.tag == "Activator")
{
Debug.Log("Exited collider on game object: "+ other.gameObject.name);
canBePressed = false;
GameManager.instance.NoteMissed();
Instantiate(missedEffect,transform.position, missedEffect.transform.rotation);
}
}
}
EffectObject
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class EffectObject : MonoBehaviour
{
public float lifeTime = 1f;
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
Destroy(gameObject, lifeTime);
}
}
Hmm ... You say that OnTriggerExit2D is called to early? I assume it's called when the elements are still inside one another? If that's the case I guess your bounding boxes don't have the right size, or the right shape. I see arrows, do they have a rectangular bounding box or a polygon one that follows their shape? Are all your bounding boxes the right size?
I figured out what was wrong thanks to AdrAs's comment.
Turns out I had to check the y position of my arrow and collider were well enough below the height of my button box collider. In addition to that, I reshaped my colliders. I found that this code did the trick for me. Thank You All For the Nudges in the right direction.
noteObject -> new OnTriggerExit2D
private void OnTriggerExit2D(Collider2D other)
{
if(other.tag == "Activator" && transform.position.y < -0.32)
{
Debug.Log("Exited collider on game object: "+ other.gameObject.name);
canBePressed = false;
GameManager.instance.NoteMissed();
Instantiate(missedEffect,transform.position, missedEffect.transform.rotation);
}
}
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Shooting : MonoBehaviour
{
[SerializeField]
private Transform[] firePoints;
[SerializeField]
private Rigidbody projectilePrefab;
[SerializeField]
private float launchForce = 700f;
[SerializeField]
private Animator anim;
[SerializeField]
private bool automaticFire = false;
private void Start()
{
anim.SetBool("Shooting", true);
}
public void Update()
{
if (Input.GetButtonDown("Fire1") && automaticFire == false)
{
if (anim.GetBool("Shooting") == true)
{
anim.Play("SHOOTING");
LaunchProjectile();
}
}
if(automaticFire == true)
{
anim.Play("SHOOTING");
LaunchProjectile();
}
}
private void LaunchProjectile()
{
foreach (var firePoint in firePoints)
{
Rigidbody projectileInstance = Instantiate(
projectilePrefab,
firePoint.position,
firePoint.rotation);
projectileInstance.AddForce(new Vector3(0,0,1) * launchForce);
projectileInstance.gameObject.AddComponent<BulletDestruction>().Init();
}
}
}
If it's automatic :
if(automaticFire == true)
{
anim.Play("SHOOTING");
LaunchProjectile();
}
It's shooting nonstop but it looks like one long bullet.
If for example I want it to shoot nonstop but only one bullet each time ? Or shooting many bullets but with some space between them ?
On each bullet I'm adding this destruction script :
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class BulletDestruction : MonoBehaviour
{
// Start is called before the first frame update
public void Init()
{
StartCoroutine(DestroyBullet());
}
IEnumerator DestroyBullet()
{
yield return new WaitForSeconds(0.2f);
Destroy(gameObject);
}
}
But that's also a problem. If I'm setting the destruction delay to be 0.2 the bullets shoot distance is very short but if I will set the delay time for example to 5 the bullets will be shoot to a longer distance but then again there will be a lot of bullets at the same time.
What is the logic on the destruction and the automatic mode ? And how should I do it in the script/s ?
You can try this:
float attackRate = 100;
float timer = 0;
public void Update()
{
timer -= Time.deltaTime;
if(automaticFire && timer <=0)
{
Shoot();
timer = 1/ attackRate;
}
}
I'm having trouble with some countdown timer functionality. In my GameManager script I'm checking the scene name and checking for idle usage (no mouse movement/no clicking) and kicking off a function in my CountdownTimer script that is counts down the amount of idle time using the function StartPreCountTimer(). Once that timer reaches zero it then instantiates a countdown dialog and kicks off IEnumerator RunTimer() that the user can either cancel to continue playing or let reach zero, at which point the game resets and returns to the intro screen.
The problem I'm having is at the precount step. The GameManager script keeps calling StartPreCountTimer() every frame which obviously prevents the countdown from ever completing because it restarts every frame. How do I fix this? How do a call the function only once from Update?
GameManager Script
using UnityEngine;
using UnityEngine.SceneManagement;
public class GameManager : MonoBehaviour
{
public static GameManager gameManagerInstance = null; // Create Singleton
public GameObject loader;
public GameObject countdownTimer;
public Color defaultBackgroundColor;
public Object startingScene;
public GameObject timeOutWarningDialog;
public float countdownLength;
public float countdownDelay;
private Vector3 prevMousePosition;
private CountdownTimer countdownInstance;
private Scene currentScene;
private GameObject gameManager;
private GameObject canvas;
public static string animalDataFilePathJSON;
public static string animalDataFilePathTex;
void Awake()
{
if (gameManagerInstance == null)
gameManagerInstance = this;
else if (gameManagerInstance != null)
Destroy(gameObject);
DontDestroyOnLoad(gameObject);
gameManager = GameObject.FindGameObjectWithTag("GameManager");
// get and store JSON and Tex filepaths defined in Loader script
animalDataFilePathJSON = GameObject.FindGameObjectWithTag("Loader").GetComponent<Loader>().animalDataFilePathJSON;
animalDataFilePathTex = GameObject.FindGameObjectWithTag("Loader").GetComponent<Loader>().animalDataFilePathTex;
}
void Start()
{
prevMousePosition = Input.mousePosition;
currentScene = SceneManager.GetActiveScene();
countdownInstance = countdownTimer.GetComponent<CountdownTimer>(); // Create an instance of CountdownTimer
}
void Update()
{
currentScene = SceneManager.GetActiveScene();
if (currentScene.name != startingScene.name)
{
countdownInstance.StartPreCountTimer(); // Start Pre-count Timer
if (Input.GetMouseButtonDown(0) || Input.mousePosition != prevMousePosition)
{
countdownInstance.StartPreCountTimer(); // Start Pre-count Timer
if (timeOutWarningDialog != null)
timeOutWarningDialog.SetActive(false);
}
prevMousePosition = Input.mousePosition;
}
}
}
CountdownTimer Script
using System.Collections;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.SceneManagement;
public class CountdownTimer : MonoBehaviour {
public GameObject timeOutWarningDialog;
public float countdownLength;
public float countdownDelay;
public Object startingScene;
private float countdownInterval = 1;
private GameObject countdownTimer;
private IEnumerator counter;
private Button stopCountButton;
private Text timerTextField;
private GameObject timerInstance;
private GameObject canvas;
// GAME TIMER
public void StartPreCountTimer()
{
CancelInvoke();
if (GameObject.FindGameObjectWithTag("Timer") == null)
Invoke("ShowRestartWarning", countdownDelay);
}
void ShowRestartWarning()
{
canvas = GameObject.FindGameObjectWithTag("Canvas");
timerInstance = Instantiate(timeOutWarningDialog); // instantiate timeout warning dialog
timerInstance.transform.SetParent(canvas.transform, false);
timerInstance.SetActive(true);
Text[] textFields = timerInstance.GetComponentsInChildren<Text>(true); // get reference to timer textfields
timerTextField = textFields[2]; // access and assign countdown textfield
stopCountButton = timerInstance.GetComponentInChildren<Button>(); // get reference to keep playing button
stopCountButton.onClick.AddListener(StopTimer); // add button listener
if (timerInstance.activeSelf == true)
{
counter = RunTimer(countdownLength); // create new reference to counter, resets countdown to countdownLength
StartCoroutine(counter);
}
}
IEnumerator RunTimer(float seconds)
{
float s = seconds;
while (s > -1)
{
if (timerTextField != null)
timerTextField.text = s.ToString();
yield return new WaitForSeconds(countdownInterval);
s -= countdownInterval;
}
if (s == -1)
{
RestartGame();
}
}
void StopTimer()
{
StopCoroutine(counter);
Destroy(timerInstance);
}
void RestartGame()
{
SceneManager.LoadScene(startingScene.name);
}
}
I'm getting an error of "Coroutine couldn't be started because the the game object 'TimeOutWarningDialog' is inactive!" but I'm unsure why I'm getting this error.
Just to give a rundown of the code:
I'm looking for inactivity in GameManger.Update()
If inactive for a period of time I call GameManager.ShowRestartWarning()
TimeOutWarningDialog gets SetActive to true
I check if the object is active before calling StartRestartTimer(), if (timerInstance.activeSelf == true) StartRestartTimer();
I call startTimer() in CountdownTimer class
I'm setting the object that I'm instatiating to 'active' before I call the startTimer function which includes the coroutine. what am I doing wrong here?
any help would be great!!
using UnityEngine;
using System.Collections.Generic;
using UnityEngine.SceneManagement;
public class GameManager : MonoBehaviour
{
// Create Singleton
public static GameManager instance = null;
// Set Default Background Color
public Color defaultColor;
// Restart variables
private Vector3 prevMousePosition;
public GameObject timeOutWarningDialog;
public GameObject restartDialog;
public float countdownLength;
public float timeUntilCountdown;
// Game Controller
private GameObject canvas;
private GameObject gameManager;
public GameObject timerInstance;
public Object startingScene;
private Scene currentScene;
// File System List of Folders
public List<string> folders;
void Awake()
{
if (instance == null)
instance = this;
else if (instance != null)
Destroy(gameObject);
DontDestroyOnLoad(gameObject);
gameManager = GameObject.FindGameObjectWithTag("GameManager");
}
void Start()
{
prevMousePosition = Input.mousePosition;
currentScene = SceneManager.GetActiveScene();
}
void Update()
{
if(Input.anyKeyDown || Input.mousePosition != prevMousePosition)
if(currentScene.name != startingScene.name)
StartGameTimer();
prevMousePosition = Input.mousePosition;
}
// GAME TIMER
void StartGameTimer()
{
// Debug.Log("Game Timer Started");
CancelInvoke();
if (GameObject.FindGameObjectWithTag("Timer") == null)
Invoke("ShowRestartWarning", timeUntilCountdown);
}
void ShowRestartWarning()
{
canvas = GameObject.FindGameObjectWithTag("Canvas");
timerInstance = Instantiate(timeOutWarningDialog);
timerInstance.transform.SetParent(canvas.transform, false);
timerInstance.SetActive(true);
if (timerInstance.activeSelf == true)
StartRestartTimer();
}
void StartRestartTimer()
{
CountdownTimer countdownTimer = timeOutWarningDialog.GetComponent<CountdownTimer>();
countdownTimer.startTimer(countdownLength);
CancelInvoke();
Invoke("RestartGame", countdownLength);
}
void RestartGame()
{
SceneManager.LoadScene(startingScene.name);
Debug.Log("Game Restarted");
Debug.Log("Current Scene is " + currentScene.name + ".");
}
void DestroyTimer()
{
Destroy(GameObject.FindGameObjectWithTag("Timer"));
}
}
then I'm calling startTimer in the CountdownTimer class below:
using UnityEngine;
using UnityEngine.UI;
using System.Collections;
public class CountdownTimer : MonoBehaviour
{
public float countdownLength;
public Text timerText;
public bool stop = true;
private float minutes;
private float seconds;
public void startTimer(float from)
{
stop = false;
countdownLength = from;
Update();
StartCoroutine(updateCoroutine());
}
void Update()
{
if (stop) return;
countdownLength -= Time.deltaTime;
minutes = Mathf.Floor(countdownLength / 60);
seconds = countdownLength % 60;
if (seconds > 59) seconds = 59;
if (minutes < 0)
{
stop = true;
minutes = 0;
seconds = 0;
}
}
private IEnumerator updateCoroutine()
{
while (!stop)
{
timerText.text = string.Format("{0:0}:{1:00}", minutes, seconds);
yield return new WaitForSeconds(0.2f);
Debug.Log(string.Format("{0:0}:{1:00}", minutes, seconds));
}
}
}
The problem is in this method:
void StartRestartTimer()
{
CountdownTimer countdownTimer = timeOutWarningDialog.GetComponent<CountdownTimer>();
countdownTimer.startTimer(countdownLength);
CancelInvoke();
Invoke("RestartGame", countdownLength);
}
You start the coroutine first and then invoke RestartGame to load another scene. So the object with the coroutine gets destroyed.
I can't give you the solution because it requires more knowledge regarding your scenes but you may want to try additive scene loading.