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.
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.
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.
I am looking for a help with make a delay in Unity in Update function.
I created something like this below. The cube is moving rotates once and then is waiting > rotates once > waiting ....
And there is my question. How i can make cube rotates constantly for some time instead of once. For Example: Wait 2sec, rotating constantly 5sec, Wait 2sec, rota....
I thinked about replace
ForCube.transform.Rotate (10, 10, 10);
by rotating Animation. But I want create it with transform.Rotate. Is there any option to do this?
using UnityEngine;
using System.Collections;
public class Ruch : MonoBehaviour
{
public float speed = 5;
public GameObject ForCube;
bool work = true;
// Use this for initializat
void Start ()
{
ForCube = GameObject.Find ("Cube");
Debug.Log (ForCube);
}
// Update is called once per frame
void Update ()
{
if (work) {
StartCoroutine (WaitSome ());
}
}
private IEnumerator WaitSome ()
{
work = false;
yield return new WaitForSeconds (3f);
ForCube.transform.Rotate (10, 10, 10);
work = true;
}
}
At the moment it looks like to me you are using a StartCoroutine which will work fine, but if you want maybe a little more control over when to rotate and when to stop you can use the Time.deltaTime The time in seconds it took to complete the last frame (Read Only).http://docs.unity3d.com/ScriptReference/Time-deltaTime.html
So basically you have yourself a float variable called Rotate which is lets say 10f
Then inside of your Update function
void Update ()
{
if(Rotate > 0)
{
Rotate -= Time.deltaTime;
ForCube.transform.Rotate (10, 10, 10);
}
}
Then when Rotate is equal to 0 it will stop, but then you can use your work bool to start a new timer.
One big think to take in is to use the Time.deltaTime, if you don't use this and you just use an int or whatever variable type the timer will differ depending on the FPS of the game for the player.
Let me know if you need anymore help :)
Instead of using coroutines, you can do it directly in the update function like this:
[SerializeField]
private float timeToWait; //In seconds
[SerializeField]
private float timeToRotate; //In seconds
private float timer = 0;
private bool waiting = true; //Set this to false if you want to rotate first, wait later
void Update()
{
if(!waiting) RotateYourObjectALittleBit(); //Call your own function or do whatever you want
timer += Time.deltaTime;
if(timer >= timeToWait && waiting) {
waiting = false;
timer = 0;
}
else if(timer >= timeToRotate && !waiting) {
waiting = true;
timer = 0;
}
}
This code is untested, so please let me know if you require further clarification or if it doesn't work.
Thanks everyone for fast Answer and help to solve my problem. I really appreciate that.
I created something like this:
Version 1.0
When the space key is down the cube start rotating for RotateTime, after this the Timer reset to start value(RotationTime), and u can click again button for rotate.
using UnityEngine;
using System.Collections;
public class Ruch : MonoBehaviour
{
public GameObject ForCube;
public float RotateTime = 5;
public float Timer = 0;
private bool Rotate = false;
// Use this for initializat
void Start ()
{
Timer = RotateTime;
ForCube = GameObject.Find ("Cube");
Debug.Log (ForCube);
}
// Update is called once per frame
void Update ()
{
//Start Rotating When Press Space Key
if (Input.GetKeyDown (KeyCode.Space)) Rotate = true;
else if (!(Input.GetKeyDown (KeyCode.Space))&&Timer <=0) Rotate = false;
RotateForSec (ref Timer);
}
//Function to Rotate for X sec
void RotateForSec(ref float sec)
{
if (Rotate && sec > 0) {
Debug.Log (Time.time);
ForCube.transform.Rotate (10, 10, 10);
sec -= Time.deltaTime;
}
//Reset Rotating Time after rotating
if (!Rotate && sec <= 0) Timer = RotateTime;
}
}
Version 2.0
The rotating of cube continues for 5 seconds and then automatically without pressing a key it wait some time and start over rotating. Again again and again ...
public GameObject ForCube;
public float RotateTime = 5;
public float Timer = 0;
public float PauseTime = 0;
private bool Pause = false;
private bool Rotate = true;
// Use this for initializat
void Start ()
{
Timer = RotateTime;
PauseTime = RotateTime;
ForCube = GameObject.Find ("Cube");
Debug.Log (ForCube);
}
// Update is called once per frame
void Update ()
{
//Start Rotating When Press Space Key
if (Rotate)
Pause = false;
else if (!Rotate) {
Pause = true;
}
if (!Pause)
RotateForSec (ref Timer);
else RotatePause ();
}
//Function to pause PauseTime sec
void RotatePause()
{
if (PauseTime > 0) {
PauseTime -= Time.deltaTime;
} else {
Pause = false;
Rotate = true;
PauseTime = RotateTime;
}
}
//Function to Rotate for X sec
void RotateForSec(ref float sec)
{
if (Rotate && sec > 0) {
Debug.Log (Time.time);
ForCube.transform.Rotate (10, 10, 10);
sec -= Time.deltaTime;
} else Rotate = false;
//Reset Rotating Time after rotating
if (!Rotate && sec <= 0) Timer = RotateTime;
}
}
Its working but what you think about that, is it done correctly or it is a bad way?
So here's what I'd like to do: The player is idling on the ground, not moving at all. And after some time a random idle animation should be played. How do I detect that the player hasn't been moving for a certain amount of time?
IEnumerator Idle()
{
// check if player is idling on the ground
if (grounded && (_controller.velocity.x == 0))
{
// Now what?
//...
}
idleIndex = IdleRandom();
_animator.SetInteger("IdleIndex", idleIndex);
_animator.SetTrigger("Idle");
}
int IdleRandom()
{
// choose random index of idle animations
int i = Random.Range(0, numberOfIdleAnims);
// if it's the same as the previous one...
if (i == idleIndex) {
// try another one
return IdleRandom ();
}
else
return i;
}
I've already set up my animator controller so that it would play one of the idle animations (chosen by the idleIndex) if the idle-Trigger is pushed. The only thing I cannot figure out is the not-moving-in-certain-time thing!
You need to count the time your player has been idle, not moving. You can do this in the void FixedUpdate() function.
I have drastically changed my code. Make sure you include the code for playing animations.
public class IdleManager : MonoBehaviour
{
private isIdle = false;
private float previousTime;
private const float IDLE_TIME = 5.0f;
void Update()
{
if(!isIdle && grounded && (_controller.velocity.x == 0))
{
isIdle = true;
previousTime = Time.timeSinceLevelLoad();
}
else if(isIdle && Time.timeSinceLevelLoad - previousTime > IDLE_TIME)
{
// Play animation here.
// Reset previousTime.
previousTime = Time.timeSinceLevelLoad;
}
else if(grounded || _controller.velocity.x > 0)
isIdle = false;
}
}
Building a FSM for my Game AI class using C# in Unity3D game engine. I have 2 simple game objects right now, a Cube (AI) and a bullet (instantiates when a function is called, code below). Just building the foundation for a much more complex FSM, but early in the semester so building it as I'm learning.
My AI shoots out bullets in throws 5 bullets at a time, when bulletCount is 5 then it changes the state. So essentially I just want it to shoot 5 bullets, wait for a time I choose, reload, shoot 5 more, and continue same process. Basically what happens is perfectly what I want it to do first, as soon as it exits my IEnumerator, it shoots an infinite amount of bullets, even though the first time it did what I wanted.
AIClass
using UnityEngine;
using System.Collections;
public class AIClass : MonoBehaviour
{
public GameObject bullet;
int bulletCount;
float stunned;
public enum CombatAIStates
{
Firing = 0,
Stunned = 1,
Reloading = 2,
Following = 3,
Idle = 4
}
public CombatAIStates currentState = CombatAIStates.Firing;
void Update()
{
switch (currentState)
{
case CombatAIStates.Firing:
StartCoroutine (WaitMethod ());
if(bulletCount <= 5)
{
spawnBullets ();
Debug.Log ("Firing. ");
Debug.Log ("Bullet: ");
Debug.Log (bulletCount);
StartCoroutine (WaitMethod ());
++bulletCount;
}
if(bulletCount > 5)
{
currentState = CombatAIStates.Reloading;
}
break;
case CombatAIStates.Stunned:
Debug.Log ("Stunned.");
StartCoroutine(WaitMethod());
currentState = CombatAIStates.Firing;
//currentState = CombatAIStates.Firing;
break;
case CombatAIStates.Reloading:
Debug.Log ("Reloading.");
StartCoroutine (WaitMethod ());
currentState = CombatAIStates.Stunned;
break;
}
}
IEnumerator WaitMethod()
{
float waitTime = 10;
Debug.Log ("Before yield.");
yield return new WaitForSeconds (waitTime);
Debug.Log ("After yield.");
bulletCount = 0;
}
void spawnBullets()
{
Instantiate(bullet, transform.position, transform.rotation);
}
}
I suspect this is what you are trying to do, stripped down slightly for simplicity:
public int BulletCount = 0;
public enum CombatAIStates
{
Firing = 0,
Reloading = 1,
}
CombatAIStates currentState = CombatAIStates.Firing;
// Update is called once per frame
void Update () {
switch (currentState) {
case CombatAIStates.Firing:
if (BulletCount < 5) {
Debug.Log ("Firing: " + BulletCount);
++BulletCount;
} else {
currentState = CombatAIStates.Reloading;
StartCoroutine(Reload ());
}
break;
case CombatAIStates.Reloading:
// Nothing to do here, Reload() coroutine is handling things.
// Maybe play a 10 second animation here or twiddle thumbs
break;
}
}
IEnumerator Reload()
{
yield return new WaitForSeconds (10.0f);
BulletCount = 0;
//Now update the current combat state
currentState = CombatAIStates.Firing;
}
I didn't change much with the original code. I just moved a state change in to a Reload coroutine that switches back to Firing after 10 seconds and resets the bulletCount. Alternatively you could have the state change in the reload case of your switch. But instead of calling a coroutine just check if bulletCount >= 5, if not then reloading is done and you can switch back to firing.