Using c#, im trying to fire a bullet every 3 seconds, so heres my workflow:
fire button is pressed
only fire if bool fireAgain is true
set bool fireAgain = false
start timer
timer finished = bool fireAgain = true
When debugging it seems to all work properly, but when I test it, Im still able to shoot like 10 bullets a second. So somehow it just doesnt care about the bool FireAgain being false and shoots anyway even if according to debug bool fireAgain is false at that moment.
public void Update()
{
//If LEFT MouseButton is pressed, cast yer spells.
if (fireAgain == true && Input.GetMouseButtonDown(0))
{
StartCoroutine("LoopRotation");
fireAgain = false;
Debug.Log(fireAgain);
Debug.Log("1 should be FALSE");
}
while (fireAgain == false && timer < bulletTime)
{
fireAgain = false;
timer += Time.deltaTime;
Debug.Log(timer);
Debug.Log(bulletTime);
Debug.Log(fireAgain);
Debug.Log("2");
} if (timer >= bulletTime)
{
fireAgain = true;
timer = 0;
//Debug.Log("Timer is finished");
//Debug.Log(timer);
Debug.Log(fireAgain);
Debug.Log("3 should be true");
And here is the code for the Coroutine:
IEnumerator LoopRotation()
{
pivot.transform.Rotate(triggerAngle,0,0);
GameObject bullet = ObjectPooler.SharedInstance.GetPooledObject();
if (bullet != null) {
bullet.transform.position = SSpawn.transform.position;
bullet.transform.rotation = SSpawn.transform.rotation;
bullet.SetActive(true);
}
yield return new WaitForSeconds(.1f);
pivot.transform.rotation = Quaternion.Slerp(transform.rotation, originalRotationValue, Time.deltaTime * rotationResetSpeed);
StopCoroutine("LoopRotation");
}
Enumerator LoopRotation was originally just to pivot the weapon a few degrees forwards and then backwards so it looks like a wack when you cast a spell, but now its also the shoot function, as it creates bullets.
You have a while loop within Update => this loop will completely run in one single frame => "immediately" will increase the timer until it is big enough => "immediately" will set your bool flag to true again!
What you rather would do is e.g.
public void Update()
{
//If LEFT MouseButton is pressed, cast yer spells.
if (fireAgain && Input.GetMouseButtonDown(0))
{
StartCoroutine(LoopRotation());
fireAgain = false;
timer = 0;
Debug.Log(fireAgain);
Debug.Log("1 should be FALSE");
}
else if(timer < bulletTime)
{
// Only increase this ONCE per frame
timer += Time.deltaTime;
Debug.Log(timer);
Debug.Log(bulletTime);
Debug.Log(fireAgain);
Debug.Log("2");
if(timer >= bulletTime)
{
fireAgain = true;
timer = 0;
//Debug.Log("Timer is finished");
//Debug.Log(timer);
Debug.Log(fireAgain);
Debug.Log("3 should be true");
}
}
}
As alternative you could use Invoke and skip the timer in Update completely:
public void Update()
{
//If LEFT MouseButton is pressed, cast yer spells.
if (fireAgain && Input.GetMouseButtonDown(0))
{
StartCoroutine(LoopRotation());
fireAgain = false;
Invoke (nameof(AllowFireAgain), bulletTme);
}
}
private void AllowFireAgain()
{
fireAgain = true;
}
Note that your Coroutine doesn't make much sense to me. You are only rotating exactly once and only a really small amount.
The StopCoroutine in the end is unnecessary.
Also note: for debugging fine but later you should avoid to have Debug.Log running every frame in a built application. Even though the user doesn't see it the log is still created into the player log file and causes quite an amount of overhead.
Related
Okay, this is probably a dumb question but I'm new to this. I have an enemy AI that walks toward the player only when the enemy is visible to the player and the space key is pressed. I want to make a second if statement that makes the enemy run if the player presses the space bar a second time while the enemy is walking or if the enemy is within 2 meters of the of the players current position.
{
public NavMeshAgent enemy;
public Transform player;
public float speedWalk = 6f;
public float speedRun = 60f;
public float groundDrag;
public float playerHeight;
bool isWalking;
Renderer m_Renderer;
void Move(float speed)
{
enemy.speed = speed;
}
private void Start()
{
m_Renderer = GetComponent<Renderer>();
isWalking = false;
enemy.speed = speedWalk;
}
private void OnBecomeInvisible()
{
enabled = false;
}
//DelayEnemyChase
IEnumerator delayChase()
{
yield return new WaitForSeconds(2);
Move(speedWalk);
enemy.SetDestination(player.position);
}
//Visible by camera
void OnBecameVisible()
{
enabled = true;
//starts walking towards player position
if ((Input.GetKey(KeyCode.Space)) && (m_Renderer.isVisible) && (isWalking == false))
{
StartCoroutine(delayChase());
isWalking = true;
}
//starts walking towards player position
else if ((isWalking == true) && (Input.GetKey(KeyCode.Space)) && (m_Renderer.isVisible))
{
Move(speedRun);
enemy.SetDestination(player.position);
isWalking = false;
}
}
private void Update()
{
//sees if enemy is visible + space bar is pressed
OnBecameVisible();
}
}
This is confusing the heck out of me, this is what I have and it's not workign at all. Any help is appreciated!!!!
The main issue is that GetKey is fired every frame as long as the button stays pressed!
You rather want to use GetKeyDown in order to track only the first key press.
Then you currently also start and run multiple concurrent Coroutines!
I would rather use a kind of state routine and do e.g.
private void OnBecomeInvisible()
{
StopAllCoroutines();
enemy.enabled = false;
}
private void OnBecameVisible()
{
enemy.enabled = true;
Move(0f);
enemy.SetDestination(enemy.transform.position);
StartCoroutine (StatesRoutine());
}
private IEnumerator StatesRoutine ()
{
// wait until the space is pressed the first time
// here it depends on what exactly you want to do
// you can either already track if the key is still pressed already
yield return new WaitUntil (() => Input.GetKey(KeyCode.Space));
// or rather wait until the key goes down the first time after having become visible
//yield return new WaitUntil (() => Input.GetKeyDown(KeyCode.Space));
yield return new WaitForSeconds(2);
Move(speedWalk);
enemy.SetDestination(player.position);
// Then for the second press we definitely wait until it gets down again instead of
// only checking if the button is still pressed
// except again your use case actually wants that behavior
yield return new WaitUntil (() => Input.GetKeyDown(KeyCode.Space));
Move(speedRun);
enemy.SetDestination(player.position);
}
Some things still depend on your exact needs though, in particular what shall happen if the enemy becomes invisible. For now I assume you wanted to reset the behavior and start the process of handling space clicks from scratch.
i have a gameobject that goes up if it stays in collision for 5 Secs, the problem is it only work once , i tried calling the OnGUI in the update when ever the ToggleGUI = true but did't work
public float elapsedTime = 0f;
bool ToggleGUI = false;
bool isCreated = false;
Vector3 firstpos;
private void Update()
{
if(ToggleGUI == true)
{
OnGUI();
}
}
void OnTriggerStay(Collider other)
{
elapsedTime += Time.deltaTime;
if (elapsedTime >= 5.0f)
{
ToggleGUI = true;
}
}
void OnTriggerExit(Collider other)
{
elapsedTime = 0f;
}
void OnGUI()
{
if (ToggleGUI == true)
{
if (!isCreated)
{
firstpos = transform.position;
firstpos.y += 2f;
transform.position = firstpos;
isCreated = true;
}
}
}
after isCreated is set to true there is no place in your code where that variable is set again to false, therefore it can get inside the condition that moves the gameobject just once for the lifecycle of the script.
FYI when you pass a bool to an if you don't need to write ==true to have it execute when that variable is true, the name of the variable is enough
Your code seems a bit to complex for what you are trying to achieve.
If following your description what you want is just that the object goes up by 2f units every time you have stayed within a collider for 5 seconds I would simply use a Corouinte like e.g.
// The currently running Coroutine
private Coroutine _routine;
private int currentTriggers;
private void OnTriggerEnter(Collider other)
{
currentTriggers++;
// Start a routine if none is running already
if(_routine == null)
{
_routine = StartCorouine (Routine());
}
}
private void OnTriggerExit(Collider other)
{
currentTriggers--;
// If there are still other triggers do nothing
if(currentTriggers > 0) return;
// Cancel the routine if one is running
if(_routine != null)
{
StopCoroutine(_routine);
_routine = null;
}
}
IEnumerator Routine()
{
// As the name says wait 5 seconds
yield return new WaitForSeconds (5f);
// This is only reached if the routine wasn't stopped before the time passed
// Then move your object up
transform.position += Vector3.up * 2f;
_routine = null;
}
In general be very careful with your method names!
OnGUI is a special built-in event message that is called for handling events such as Keyboard or mouse input events, Repaint calls from the UI, etc. It might get called multiple times within a single frame and is absolutely not what you want to do here and for sure you should not call this method "manually" from Update.
I am working on a 2D game in Unity. The game is limited to 60 seconds. I would like to have a time bomb which causes time reduction when the player hit the bomb.
In my script, I have a boolean which is named as "hitDetect" and I use a Coroutine() for the countdown.
I tried to push the bomb to the right side when the player hit the bomb and then check whether the collision takes place with these codes:
void OnCollisionEnter2D(Collision2D bombcol)
{
if(bombcol.gameObject.tag == "Enemy")
{
bombcol.gameObject.GetComponent<Rigidbody2D>().AddForce(transform.right * hitForce);
}
hitDetect = true;
}
This is my Coroutine() function which provides me to have a game which is successfully limited to 60 seconds except for the time penalty:
IEnumerator LoseTime()
{
while (true) {
yield return new WaitForSeconds (1);
timeLeft--;
if (hitDetect == true)
{
timeLeft= timeLeft - 5;
}
}
}
I also set the "hitDetect" as false in the start body.
void Start ()
{
StartCoroutine("LoseTime");
hitDetect = false;
}
However, these methods don't lead me to success. When the player hit the bomb, time penalty doesn't work. Where is my mistake? What would you recommend?
I would recommend to calculate the time in the Update() function. So you can be sure the hitDetect is observed every frame and the penalty is set only once if you reset hitDetect after subtracting the penalty.
public bool hitDetect = false;
public float timeLeft = 60.0f;
public float penaltyTime = 5.0f;
void Start(){
timeLeft = 60.0f;
hitDetect = false;
}
void Update(){
timeLeft -= Time.deltaTime;
if(hitDetect){
timeLeft -= penaltyTime;
// reset the hit!
hitDetect = false;
}
if(timeLeft < 0.0f){
// end game?
}
}
With this code your time is subtracted once by the penalty value if hitDetect is set true by your collision.
Hope this helps!
Ok so I'm building a platformer with two types of jump.
Normal jump - whilst on ground, pressing space bar
Air jump - whilst jumping, a limited amount of extra jumps (also whilst pressing space bar)
I've worked out the main mechanics for this jump, But I've hit a snag. the limited amount of airjumps starts as 3 each time you jump. However, with the code I've written, Unity is using the initial "normal jump" as one of the three.
Here is a sample of my code below:
bool pressingJumpButton = Input.GetKeyDown("space");
if (pressingJumpButton)
{
if (canJump)
{
jumping = true;
}
}
if (jumping)
{
if (pressingJumpButton && inAir == false) {
Debug.Log("jump");
GetComponent<Rigidbody>().velocity = new Vector3(
GetComponent<Rigidbody>().velocity.x,
jumpingSpeed,
GetComponent<Rigidbody>().velocity.z);
inAir = true;
}
}
//airJump logic
if(inAir==true)
{
airJump = true;
if (airJump && maxAir>0 && pressingJumpButton)
{
Debug.Log("airJump");
maxAir -= 1;
GetComponent<Rigidbody>().velocity = new Vector3(
GetComponent<Rigidbody>().velocity.x,
jumpingSpeed,
GetComponent<Rigidbody>().velocity.z);
}
}
// on the floor
void OnTriggerStay (Collider otherCollider)
{
if (otherCollider.tag =="JumpingArea")
{
canJump = true;
jumping = false;
inAir = false;
maxAir = 3;
airJump = false;
}
}
I put the debug.logs in to see if I could find the issue and when I'm doing the first jump (from the ground) its registering as both normal and air jumps.
I thought that by having an inAir bool that only returns true after pressing the jump button once would be the fix but no luck.
What am I doing wrong?
You need to combine your jumping and in air test and use an else clause. As it's written, you are doing the jump, setting inAir = true and then after that testing if inAir is true, meaning you are always doing an air jump after every normal jump.
if (jumping && !inAir)
{
Debug.Log("jump");
...
}
else if (inAir && jumping)
{
Debug.Log("airJump");
...
}
There is also no need to recheck pressingJumpButton since you've already tested that when setting jumping = true.
A (IMHO) better version that avoids repeated code and gets rid of unnecessary variables:
if (pressingJumpButton && canJump)
{
if (!inAir)
{
Debug.Log("jump");
inAir = true;
DoJump();
}
else if (maxAir > 0)
{
Debug.Log("airJump");
maxAir--;
DoJump();
}
}
private void DoJump()
{
GetComponent<Rigidbody>().velocity =
new Vector3(GetComponent<Rigidbody>().velocity.x,
jumpingSpeed,
GetComponent<Rigidbody>().velocity.z);
}
in this reload script The gun reloads and THEN plays the reload sound. The thing is that the reload should play first. Another problem is i would like to get rid of the work around I did to make the reload key work. They reason I made the Ammo = 0 when I press reload is because before i added that in the ammo wouldn't reset back to 8. I just don't really understand why when i reload the sound doesn't play until after the delay goes through. Thank you for taking the time to read this post.
using UnityEngine;
using UnityEngine.UI;
using System.Collections;
public class Reload : MonoBehaviour
{
//amount of bullets in the mag
public static int Ammo;
private float timer = 0.0f;
public float reloadTime = 3.0f;
//calls upon a command in a diffrent script that cancels the ablity to shoot
public PauseManager reload;
//plays the audio
public AudioSource reloadfx;
Text text;
void Awake ()
{
reloadfx = GetComponent <AudioSource> ();
text = GetComponent <Text> ();
Ammo = 8;
}
void Update ()
{
//I used this line as a underhanded way of allowing the Reload key to reload the pistol.\
//This is also my work around.
if (Input.GetButtonDown ("Reload") && Ammo < 8)
{
Ammo = 0;
}
//IF Ammo is 0 or I press the reload and i am not full of ammo then reload
if (Ammo == 0 || Input.GetButtonDown ("Reload") && Ammo < 8)
{
//plays the sound
reloadfx.Play ();
ReloadAction ();
}
else
{
//enable the ablity to shoot
reload.shoot = true;
}
//display the ammunition
text.text = Ammo + "/8";
}
public void ReloadAction()
{
//for as long as the timer is smaller then the reload time
if (timer < reloadTime)
{
//disable the abillity to shoot
reload.shoot = false;
//count the time
timer += Time.deltaTime;
}
else
{
//after the reload reset the timer and ammo count
Ammo = 8;
timer = 0.0f;
}
}
}
The immediate reason for this happening is because reloadfx.Play (); is being called for every frame that your reloading is occurring. That means it's getting restarted over and over, until finally your reload finishes and it can play in its entirety.
However, the root cause of this is how you've structured the conditions around your reloading logic. My recommendation is to just have a flag that actually indicates your gun is reloading, rather than indirectly signalling it through Ammo == 0. When Input.GetButtonDown ("Reload") occurs and the conditions are correct for reloading, play the reload sound and set the flag to true. Then, set the flag to false when reloading has completed.
There are a number of ways you can change this, but here's what I feel is a pretty simple rewrite of your approach:
private bool isReloading = false;
void Update ()
{
if (!isReloading)
{
// When ammo has depleted or player forces a reload
if (Ammo == 0 || (Input.GetButtonDown ("Reload") && Ammo < 8))
{
// Play sound once, signal reloading to begin, and disable shooting
reloadfx.Play ();
isReloading = true;
reload.shoot = false;
}
}
else
{
// isReloading will be the opposite of whether the reload is finished
isReloading = !ReloadAction();
if (!isReloading)
{
// Once reloading is done, enable shooting
reload.shoot = true;
}
}
text.text = Ammo + "/8";
}
// Performs reload action, then returns whether reloading is complete
public bool ReloadAction()
{
//for as long as the timer is smaller then the reload time
if (timer < reloadTime)
{
timer += Time.deltaTime;
}
else
{
//after the reload reset the timer and ammo count
Ammo = 8;
timer = 0.0f;
return true;
}
return false;
}
Hope this helps! Let me know if you have any questions.