Prevent a player from spamming a key in C# (Unity) - c#

I am currently doing the tutorial learn with code from unity, in this section there are bonus challenges, that do not help you in resolving it. It says that i have to prevent the player from spamming the spacebar key to spawn dogs.
I am new to C#, i started to looking online but i see something about CoRoutines and i still dont know what that is, is there a simple way to do this, searching online i found something like this, but i cant make it work.
I also tried to make some conditional like canSpawn but i do not know how to implement it well, and Unity gives me an error that i cant use && between a bool and a keypress event
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerControllerX : MonoBehaviour
{
public GameObject dogPrefab;
public float time = 2.0f;
public float timer = Time.time;
// Update is called once per frame
void Update()
{
timer -= Time.deltaTime;
if (timer > time)
{
// On spacebar press, send dog
if (Input.GetKeyDown(KeyCode.Space))
{
spawnDog();
}
timer = time;
}
void spawnDog()
{
Instantiate(dogPrefab, transform.position, dogPrefab.transform.rotation);
}
}

You were close. One thing that might make it easier to understand the logic, is to just count up instead of trying to count down. So, in your case, the code would look like this:
void Update ( )
{
timer += Time.deltaTime;
if ( timer >= time )
{
if ( Input.GetKeyDown ( KeyCode.Space ) )
{
spawnDog ( );
timer = 0;
}
}
}
void spawnDog ( )
{
Instantiate ( dogPrefab, transform.position, dogPrefab.transform.rotation );
}
The timer keeps being added to, and when it's greater than your time value (in this case 2.0f), it allows you to press a key. IF a key is then pressed, the timer is reset to 0, and the player needs to wait time time (2.0f) before being able to press the space key again.

I used Coroutines for this task, it has a little bit more code but it works nicely.
public class PlayerControllerX : MonoBehaviour
{
public GameObject dogPrefab;
private bool isCoolDown = false;
private float coolDown = 1f;
private void Update( )
{
if (Input.GetKeyDown(KeyCode.Space))
{
if (isCoolDown == false)
{
SpawnDog( );
StartCoroutine(CoolDown( ));
}
}
}
IEnumerator CoolDown( )
{
isCoolDown = true;
yield return new WaitForSeconds(coolDown);
isCoolDown = false;
}
private void SpawnDog( )
{
Instantiated(dogPrefab, transform.position, dogPrefab.transform.rotation);
}
}

I am using my phone. I am sorry if i made some syntax error.
bool isReadyForInstantiate;
void Start(){
isReadyForInstantiate = true;
}
void Update(){
if(isReadyForInstantiate && Input.GetKeyDown(KeyCode.Space)){
StartCoroutine(PreventSpam());
Instantiate(dogPrefab, transform.position, Quaternion.identity);
}
}
IEnumerator PreventSpam(){
isReadyForInstantiate = false;
yield return new WaitForSeconds(2);
isReadyForInstantiate = true;
}

here my solution based on a StopWatch:
using UnityEngine;
using System.Diagnostics; // hides UnityEngine.Debug. if needed use qualified call
public class PlayerControllerX : MonoBehaviour
{
public GameObject dogPrefab;
public double dogDelayMillis = 2000d;
private Stopwatch stopWatch;
private void Start()
{
stopWatch = new Stopwatch();
}
private void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
if (stopWatch.IsRunning)
{
if (stopWatch.Elapsed.TotalMilliseconds > dogDelayMillis)
{
stopWatch.Reset();
SpawnDog();
}
}
else
{
SpawnDog();
}
}
}
private void SpawnDog()
{
stopWatch.Start();
Instantiate(dogPrefab, transform.position, dogPrefab.transform.rotation);
}
}

Another example just for fun
public GameObject dogPrefab;
[Range(0f,2f)]
private float timer = 1.0f;
private float waitTime = 1.0f;
// Update is called once per frame
void Update()
{
// Delay Input Timer - only execute the spacebar command if timer has caught up with waitTime
if (timer < waitTime)
{}
// On spacebar press, send dog
else if (Input.GetKeyDown(KeyCode.Space))
{
Instantiate(dogPrefab, transform.position, dogPrefab.transform.rotation);
// Resets Timer
timer = 0;
}
// Run Timer every frame
timer += Time.deltaTime;
Debug.Log(timer);
}
}

I was stuck on the same exact thing, thank you. The code below is what I went with because it's short and sweet.
public GameObject dogPrefab;
[Range(0f,2f)]
private float timer = 1.0f;
private float waitTime = 1.0f;
// Update is called once per frame
void Update()
{
// Delay Input Timer - only execute the spacebar command if timer has caught up with waitTime
if (timer < waitTime)
{}
// On spacebar press, send dog
else if (Input.GetKeyDown(KeyCode.Space))
{
Instantiate(dogPrefab, transform.position, dogPrefab.transform.rotation);
// Resets Timer
timer = 0;
}
// Run Timer every frame
timer += Time.deltaTime;
Debug.Log(timer);
}
}

Related

How do I get my collision function into my private void Update() on Unity?

I have object 1 with rigid body and box collider, I want it to hit object 2 (also has collider and rigid body) so I can use the collision to stop object 1 for 5 seconds then have it move through object 2 whish has kinematics on
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class TRAINMOVE : MonoBehaviour
{
private int speed = 200;
private Rigidbody rb;
// Start is called before the first frame update
void Start()
{
StartCoroutine(WaitBeforeMove());
}
// Update is called once per frame
private void Awake()
{
rb = GetComponent<Rigidbody>();
}
private void Update()
{
rb.AddForce(transform.up * speed * Time.deltaTime);
}
//void OnCollision(Collision stop)
// {
// if (stop.gameObject.name == "train trigger station1")
// {
// speed = 0;
// WaitBeforeMove();
// speed = 200;
// rb.AddForce(transform.up * speed * Time.deltaTime);
// }
// else
// {
// speed = 200;
// }
// }
IEnumerator WaitBeforeMove()
{
yield return new WaitForSeconds(5);
}
}
Thats not exactly how Coroutines work.
The method/message you are looking for is called OnCollisionEnter. If it is not exactly written like that and with the expected signature then Unity doesn't find it and never invokes it.
A Coroutine does not delay the outer method which runs/starts it.
A Coroutine has to be started using StartCoroutine otherwise it does nothing (or at least it only runs until the first yield statement).
private void OnCollisionEnter(Collision stop)
{
if (stop.gameObject.name == "train trigger station1")
{
StartCoroutine(WaitBeforeMove());
}
}
private IEnumerator WaitBeforeMove()
{
speed = 0;
// Note that without this you would have no force applied but still the remaining velocity it already has
rb.velocity = Vector3.zero;
yield return new WaitForSeconds(5f);
speed = 200;
}
The method/message OnCollisionEnter can be a Courine itself! So in your specific case you actually can just do
// By making this an IEnumerator Unity automatically starts it as a Coroutine!
private IEnumerator OnCollisionEnter(Collision stop)
{
if (stop.gameObject.name == "train trigger station1")
{
speed = 0;
rb.velocity = Vector3.zero;
yield return new WaitForSeconds(5f);
speed = 200;
}
}
Finally instead of permanently continue to add 0 force I would in general rather use a simple bool flag and pause the adding of force entirely.
You should AddForce not every frame but rather in FixedUpdate.
And then you should not multiply force by Time.deltaTime. Since it is applied in fixed time intervals in FixedUpdate it is already a frame-rate independent event.
private bool stopped;
private void FixedUpdate()
{
if(stopped) return;
rb.AddForce(transform.up * speed);
}
// By making this an IEnumerator Unity automatically starts it as a Coroutine!
private IEnumerator OnCollisionEnter(Collision stop)
{
if (stop.gameObject.name == "train trigger station1")
{
stopped = true;
rb.velocity = Vector3.zero;
yield return new WaitForSeconds(5f);
stopped = false;
}
}
I suggest instead of comparing a name you should rather use a Tag and use stop.gameObject.CompareTag("Station")

How to put a countdown trigger?

I just got into Game Dev and I need my script to enable when my player steps a cube trigger, but I don't know how.
This is the Script that I typed and I can't figure out if I should erase the Void Start or not?
public class CountdownTimer : MonoBehaviour {
public bool timerIsRunning;
public Text timeText;
public float timeRemaining = 10;
private void Start()
{
timerIsRunning = true;
}
void Update(){
if (timerIsRunning)
{
if (timeRemaining > 0)
{
timeRemaining -= Time.deltaTime;
DisplayTime(timeRemaining);
}
else
{
Debug.Log("Time has run out!");
timeRemaining = 0;
timerIsRunning = false;
}
}
}
void DisplayTime(float timeToDisplay){
timeToDisplay += 1;
float minutes = Mathf.FloorToInt(timeToDisplay / 60);
float seconds = Mathf.FloorToInt(timeToDisplay % 60);
timeText.text = string.Format("{0:00}:{1:00}", minutes, seconds);
}
}
I think you can make good use of a trigger collider. To do that, you need to add a collider of your choice to your zone and set it as a trigger. Be sure to have a collider and a rigidbody on your player.
Then in your script you can use the function OnTriggerEnter who will be called when an object enter in your zone defined by your trigger collider.
For your countdown method, you can make good use of a couroutine. It will look like this :
IEnumerator DisplayTime() {
timeIsRunning = true;
for (int i = 0; i < timeRemaining; i++) {
// your code to change the text
yield return new WaitForSeconds(1);
}
timeIsRunning = false;
}
private void OnColliderEnter(Collider other) {
if(!timeIsRunning) {
StartCouroutine(DisplayTime());
}
}
What this code does is when an object enter in the trigger, it starts a routine who will update the time displayed each second until the countdown is finished.
With that you won't need your Start and your Update methods anymore.
If you want more informations on trigger collider : https://docs.unity3d.com/ScriptReference/Collider-isTrigger.html
If you want more informations on couroutines : https://docs.unity3d.com/Manual/Coroutines.html
If you want more informations on the method OnTriggerEnter : https://docs.unity3d.com/ScriptReference/Collider.OnTriggerEnter.html

How can I wait number of seconds before changing for new random speed? [duplicate]

This question already has answers here:
How to make the script wait/sleep in a simple way in unity
(7 answers)
Closed 4 years ago.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GateControl : MonoBehaviour
{
public Transform door;
public float doorSpeed = 1.0f;
public bool randomDoorSpeed = false;
[Range(0.3f, 10)]
public float randomSpeedRange;
private Vector3 originalDoorPosition;
// Use this for initialization
void Start()
{
originalDoorPosition = door.position;
}
// Update is called once per frame
void Update()
{
if (randomDoorSpeed == true && randomSpeedRange > 0.3f)
{
StartCoroutine(DoorSpeedWaitForSeconds());
}
door.position = Vector3.Lerp(originalDoorPosition,
new Vector3(originalDoorPosition.x, originalDoorPosition.y, 64f),
Mathf.PingPong(Time.time * doorSpeed, 1.0f));
}
IEnumerator DoorSpeedWaitForSeconds()
{
doorSpeed = Random.Range(0.3f, randomSpeedRange);
yield return new WaitForSeconds(3);
}
}
Making StartCoroutine inside the Update is a bad idea. But I want that it will take one random speed when running the game then will wait 3 seconds and change to a new random speed then wait another 3 seconds and change for another new random speed and so on.
And while it's waiting 3 seconds to keep the current speed constant until the next change.
Update:
This is what I tried:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GateControl : MonoBehaviour
{
public Transform door;
public float doorSpeed = 1.0f;
public bool randomDoorSpeed = false;
public bool IsGameRunning = false;
[Range(0.3f, 10)]
public float randomSpeedRange;
private Vector3 originalDoorPosition;
// Use this for initialization
void Start()
{
IsGameRunning = true;
originalDoorPosition = door.position;
StartCoroutine(DoorSpeedWaitForSeconds());
}
// Update is called once per frame
void Update()
{
door.position = Vector3.Lerp(originalDoorPosition,
new Vector3(originalDoorPosition.x, originalDoorPosition.y, 64f),
Mathf.PingPong(Time.time * doorSpeed, 1.0f));
}
IEnumerator DoorSpeedWaitForSeconds()
{
var delay = new WaitForSeconds(3);//define ONCE to avoid memory leak
while (IsGameRunning)
{
if (randomDoorSpeed == true && randomSpeedRange > 0.3f)
doorSpeed = Random.Range(0.3f, randomSpeedRange);
yield return delay;
}
}
}
But there are two problems.
The first problem is that every 3 seconds, when it's changing the speed of the door, it's also changing the door position from its current position. So it looks like the door position is jumping to another position and then continue from there. How can I make that it will change the speed meanwhile the door keep moving from its current position ?
Second problem is how can I change the randomDorrSpeed flag so it will take effect while the game is running? I Want that if randomDorrSpeed is false use the original speed of the door (1.0) and if it`s true use the random speed.
You already know that the coroutine should start from Start:
void Start()
{
//initialization
StartCoroutine(DoorSpeedWaitForSeconds());
}
So make the coroutine a loop with the proper terminate condition:
IEnumerator DoorSpeedWaitForSeconds()
{
var delay = new WaitForSeconds(3);//define ONCE to avoid memory leak
while(IsGameRunning)
{
if(randomDoorSpeed == true && randomSpeedRange > 0.3f)
doorSpeed = Random.Range(0.3f, randomSpeedRange);
if(!randomDoorSpeed)
doorSpeed = 1;//reset back to original value
yield return delay;//wait
}
}
For the other question you asked, if you think about it you can't possibly use ping pong with dynamic speed based on Time.time. You need to change it like this:
bool isRising = true;
float fraq = 0;
void Update()
{
if (isRising)
fraq += Time.deltaTime * doorSpeed;
else
fraq -= Time.deltaTime * doorSpeed;
if (fraq >= 1)
isRising = false;
if (fraq <= 0)
isRising = true;
fraq = Mathf.Clamp(fraq, 0, 1);
door.position = Vector3.Lerp(originalDoorPosition,
new Vector3(originalDoorPosition.x, originalDoorPosition.y, 64f),
fraq);
}
You can solve the original problem without coroutines:
public float timeBetweenChangeSpeed = 3f;
public float timer = 0;
void Update ()
{
// Add the time since Update was last called to the timer.
timer += Time.deltaTime;
// If 3 seconds passed, time to change speed
if(timer >= timeBetweenChangeSpeed)
{
timer = 0f;
//Here you call the function to change the random Speed (or you can place the logic directly)
ChangeRandomSpeed();
}
}
And about opening and closing doors. Here is a script I used to control doors in a maze game I worked in:
You need to set empty gameObjects with to set the boundaries, that is until what point you want to move the door one opening or when closing. You place this empty gameobjects in your scene and link them to the scrip in the correct field. The script will take the transform.position component on it's own. There is also a trigger enter to activate the door when a character approaches. If you dont need that part, I can edit the code tomorrow.
You can use this script also to move platforms, enemies... in general anything which can move in a straight line.
using UnityEngine;
using System.Collections;
public class OpenDoor : MonoBehaviour {
// define the possible states through an enumeration
public enum motionDirections {Left,Right};
// store the state
public motionDirections motionState = motionDirections.Left;
//Variables for State Machine
bool mOpening = false;
bool mClosing = false;
//bool mOpened = false;
//OpenRanges to open/close the door
public int OpenRange = 5;
public GameObject StopIn;
public GameObject StartIn;
//Variables for Movement
float SpeedDoor = 8f;
float MoveTime = 0f;
int CounterDetections = 0;
void Update () {
// if beyond MoveTime, and triggered, perform movement
if (mOpening || mClosing) {/*Time.time >= MoveTime && */
Movement();
}
}
void Movement()
{
if(mOpening)
{
transform.position = Vector3.MoveTowards(transform.position, StopIn.transform.position, SpeedDoor * Time.deltaTime);
if(Vector3.Distance(transform.position, StopIn.transform.position) <= 0)
mOpening = false;
}else{ //This means it is closing
transform.position = Vector3.MoveTowards(transform.position, StartIn.transform.position, SpeedDoor * Time.deltaTime);
if(Vector3.Distance(transform.position, StartIn.transform.position) <= 0)
mClosing = false;
}
}
// To decide if door should be opened or be closed
void OnTriggerEnter(Collider Other)
{
print("Tag: "+Other.gameObject.tag);
if(Other.gameObject.tag == "Enemy" || Other.gameObject.tag == "Player" || Other.gameObject.tag == "Elevator")
{
CounterDetections++;
if(!mOpening)
Opening();
}
}
void OnTriggerStay(Collider Other)
{
if(Other.gameObject.tag == "Elevator")
{
if(!mOpening)
Opening();
}
}
void OnTriggerExit(Collider Other)
{
if(Other.gameObject.tag == "Enemy" || Other.gameObject.tag == "Player")
{
CounterDetections--;
if(CounterDetections<1)
Closing();
}
}
void Opening()
{
mOpening = true;
mClosing = false;
}
void Closing()
{
mClosing = true;
mOpening = false;
}
}
Using timer and setting an interval. The delegate event will fire every time the interval is reached.
var t = new Timer {Interval = 3000};
t.Elapsed += (sender, args) => { /* code here */};

Unity power-up is not working as intended

First off, I am quite new to scripting so there's probably going to be a few flaws in my script.
So basically, I've made a script for the power up, but once my shot or the player touches the power up coin the fire rate does increase however it won't go back to the normal fire rate after 5 seconds... I have no idea what might be the cause, any advice would be helpful!
using UnityEngine;
using System.Collections;
public class FireRatePowerUp : MonoBehaviour {
private bool isPowerUp = false;
private float powerUpTime = 5.0f;
private PlayerShoot playerShoot;
private void Start()
{
playerShoot = PlayerShoot.FindObjectOfType<PlayerShoot>();
}
private void OnTriggerEnter2D(Collider2D collision)
{
if (collision.gameObject.tag == "Player" || collision.gameObject.tag == "Projectile")
{
StartCoroutine(PowerUpTime());
isPowerUp = true;
Destroy(gameObject);
if (collision.gameObject.tag == "Projectile")
{
Destroy(collision.gameObject);
}
}
}
IEnumerator PowerUpTime()
{
playerShoot.fireRate -= 0.13f;
yield return new WaitForSeconds(powerUpTime);
playerShoot.fireRate += 0.13f;
}
}
I think the issue here is that you're destroying the gameobject this script is attached to (the coin) and by so doing, the script itself is destroyed, therefor its code, coroutine or otherwise won't execute.
StartCoroutine(PowerUpTime());
isPowerUp = true;
Destroy(gameObject); //oops, our script has been destroyed :(
You would have to do this very differently, basically moving the bulk of the code to the PlayerShoot class.
Something like this (this being in PlayerShoot.cs)
public void ActivatePowerupFireRate(float time, float amt) {
StartCoroutine(DoActivatePowerupFireRate(time, amt));
}
public IEnumerator ActivatePowerupFireRate(float time, float amt) {
fireRate -= amt;
yield return WaitForSeconds(time);
fireRate += amt;
}
IEumerator is definately one of the ways you can solve this issue.
However I'm not a fan of them here's my solution if you have a timer in game.
public int timePassed = 0;
public int gotPowerUp = 0;
void Start(){
InvokeRepeating("Timer", 0f, 1.0f);
//Starting at 0 seconds, every second call Timer function.
}
void Timer(){
timePassed++; // +1 second.
}
That way when you obtained the powerup you can set gotPowerUp = timePassed. So you have the exact time when powerup is activated.
then you do something like
if( (gotPowerUp + 5) == timePassed ){
//5 seconds have passed.
//deactivate powerup here
}

Pause button not working

I am writing a pause button for my game (Game for mobile phones).
My Pause script:
using UnityEngine;
using System.Collections;
public class PauseScript : MonoBehaviour {
public bool paused;
// Use this for initialization
void Start () {
paused = false;
}
// Update is called once per frame
void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
paused = !paused;
}
if (paused)
{
Time.timeScale = 0;
}
else if (!paused)
{
Time.timeScale = 1;
}
}
public void Pause()
{
paused = !paused;
if (paused)
{
Time.timeScale = 0;
}
else if (!paused)
{
Time.timeScale = 1;
}
}
}
And Jump that is written like this jump = Input.GetButton("Fire1");
When I touch button my player jumps and the game does not pause.
How I can make my game pause and player doesn't jump?
Unity Docs: Time.timeScale
Except for realtimeSinceStartup, timeScale affects all the time and delta time measuring variables of the Time class.
if you want to actually use timeScale you need to use Time.deltaTime in your calculations. Like:
transfomr.position += Vector3.forward * speed * Time.deltaTime;
so, it's not going to magically pause all the scripts. Update(), FixedUpdate() and all the other MonoBehaviour functions will still get invoked normally. also, read about Time.deltaTime if you haven't already.

Categories