I have a co-routine that is triggered when the bool of a toggle button changes, when the bool is changed again that co-routine should be stopped and another one should start. This is my code:
public class thermoPowerControlPanel : MonoBehaviour {
private ThermoElectric thermo;
public bool toggleBool1;
public int temperature;
private int tempUp = 10;
private int tempDown = 1;
public thermoPowerControlPanel (){
temperature = 100;
}
public void turbine1State (bool toggleBool1) {
if (toggleBool1 == false) {
Debug.Log (toggleBool1);
Invoke("ReduceTemperatureEverySecond", 1f);
}
if (toggleBool1 == true) {
Debug.Log (toggleBool1);
Invoke("IncreaseTemperatureEverySecond", 1f);
}
}
private void ReduceTemperatureEverySecond()
{
if (toggleBool1 == true)
{
Debug.Log("I was told to stop reducing the temperature.");
return;
}
temperature = temperature - tempDown;
Debug.Log (temperature);
Invoke("ReduceTemperatureEverySecond", 1f);
}
private void IncreaseTemperatureEverySecond()
{
if (toggleBool1 == false)
{
Debug.Log("I was told to stop increasing the temperature.");
return;
}
temperature = temperature + tempUp;
Debug.Log (temperature);
Invoke("ReduceTemperatureEverySecond", 1f);
}
}
When the function turbine1State(bool t1) receives the first bool (false), the routine decreaseTemperatureEverySecond() starts but it stops immediately after, sending the Debug.Log message, it should keep reducing the temperature until the bool (activated by the toggle button) turned true.
.
Can you help?
It is this easy!
public Toggle tog; // DONT FORGET TO SET IN EDITOR
in Start ...
InvokeRepeating( "Temp", 1f, 1f );
... and then ...
private void Temp()
{
if (tog.isOn)
temperature = temperature + 1;
else
temperature = temperature - 1;
// also, ensure it is never outside of 0-100
temperature = Mathf.Clamp(temperature, 0,100);
}
If you ever need to "totally stop" that action (both up and down), just do this
CancelInvoke("Temp");
So easy!
NOTE purely FYI, the other pattern I explained is this:
bool some flag;
Invoke("Temp", 1f);
private void Temp()
{
if (some flag is tripped) stop doing this
.. do something ..
Invoke( ..myself again after a second .. )
}
In real life, it is usually better to "keep Invoking yourself" rather than use InvokeRepeating.
In this simple example, just use InvokeRepeating, and then CancelInvoke.
You can stop coroutine only by it name.
Just try some like StartCoroutine("increaseTemperature"); and then StopCoroutine("increaseTemperature");.
http://docs.unity3d.com/Manual/Coroutines.html
There are a number of ways to call StartCoroutine. You can "stop" a coroutines IF you start it with the "string" method, like this StartCoroutine("FunctionNameAsStringHere"); If you start it like this StartCoroutine(FunctionNameAsStringHere()); you cannot stop it by name.
(You can also access the actual enumerator to stop coroutines, but that is far beyond the scope of a beginner using coroutines.)
Related
I have a simple jumping action in my game. The player presses jump, gets launched into the air, transitions to an air-state and falls down to ground again. The usual stuff.
However, if the player lands on the ground and the user is still holding the jump key, the player automatically jumps again upon landing. I don't want that. I want the user to let go of the key before being able to jump again.
Here's a simplified version of my player movement code, with only the nessessary stuff in.
public class GenesisPhysics : MonoBehaviour {
public bool isGrounded = true;
public bool jumpIsPressed;
public IA inputActions; // New input system's Input Action Asset C# class
private void Awake()
{
inputActions = new IA();
// Movement actions stuff...
inputActions.PlayerControls2D.Jump.performed += jumpContext => jumpIsPressed = true;
inputActions.PlayerControls2D.Jump.canceled += jumpContext => jumpIsPressed = false;
}
private void FixedUpdate()
{
if(isGrounded)
{
ExecuteGroundStateLoop()
} else {
ExecuteAirStateLoop()
}
}
private void ExecuteGroundStateLoop()
{
// Do stuff...
if(jumpPressed) {
isGrounded = false;
// Set x and y speeds to be applied in Air state
} else {
// Do some more stuff...
}
}
private void ExecuteAirStateLoop()
{
// Applying gravity, jump force, checking for ground and other stuff...
// If ground is found, set isGrounded to true.
}
void OnEnable()
{
inputActions.Enable()
}
void OnDisable()
{
inputActions.Disable()
}
}
This might not be enough code to fix the issue. I'll update this post if more is needed.
Create a new bool variable "jumpPressAvailable" and set it to true in the beginning. Use this new variable to control, if a jump is available. For example, if jump button is pressed, set "jumpPressAvailable" to false, and if jump button is released or "NOT pressed", set "jumpPressAvailable" to true again. A jump can only be started if jump button is pressed and "jumpPressAvailable" is true.
public bool jumpPressAvailable=true;
Add some lines in the Ground State Loop:
if(jumpPressed && jumpPressAvailable) {
isGrounded = false;
jumpPressAvailable = false;
// Set x and y speeds to be applied in Air state
} else {
if(jumpPressed == false)
{
jumpPressAvailable = true;
}
// Do some more stuff...
}
I'm learning Unity but my key press isn't being detected
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Player : MonoBehaviour
{
public Rigidbody myBody;
private float time = 0.0f;
private bool isMoving = false;
private bool isJumpPressed = false;
void Start(){
myBody = GetComponent<Rigidbody>();
}
void Update()
{
isJumpPressed = Input.GetKeyDown(KeyCode.Space);
Debug.Log(isJumpPressed);
}
void FixedUpdate(){
if(isJumpPressed){
myBody.velocity = new Vector3(0,10,0);
isMoving = true;
Debug.Log("jump");
}
if(isMoving){
time = time + Time.fixedDeltaTime;
if(time>10.0f)
{
//Debug.Log( Debug.Log(gameObject.transform.position.y + " : " + time));
time = 0.0f;
}
}
}
}
why isJumpPressed always false. What am I doing wrong? From what I understand this should work but I'm obviously missing something
UPDATE:
Thanks to everyone who proposed ideas. I got the isJumpPressed to return true when I stopped trying to detect the space bar.
isJumpPressed = Input.GetKeyDown("a");
anyone got any ideas why this would work and not
isJumpPressed = Input.GetKeyDown(KeyCode.Space);
or
isJumpPressed = Input.GetKeyDown("space");
UPDATE 2:
Apparently this is a bug in Linux. I've read it won't happen when the game is built just in the editor. I've found a workaround at
https://forum.unity.com/threads/space-not-working.946974/?_ga=2.25366461.1247665695.1598713842-86850982.1598713842#post-6188199
If any Googler's Stumble upon this issue reference the following code because this is working for me.
public class Player : MonoBehaviour
{
public Rigidbody myBody;
private float time = 0.0f;
private bool isMoving = false;
private bool isJumpPressed = false;
void Start(){
myBody = GetComponent<Rigidbody>();
}
void Update()
{
isJumpPressed = Input.GetKeyDown(SpacebarKey());
if(isJumpPressed)
{
Debug.Log(isJumpPressed);
}
}
void FixedUpdate(){
if(isJumpPressed){
myBody.velocity = new Vector3(0,10,0);
isMoving = true;
Debug.Log("jump");
}
if(isMoving){
time = time + Time.fixedDeltaTime;
if(time>10.0f)
{
//Debug.Log( Debug.Log(gameObject.transform.position.y + " : " + time));
time = 0.0f;
}
}
}
public static KeyCode SpacebarKey() {
if (Application.isEditor) return KeyCode.O;
else return KeyCode.Space;
}
}
The problem is that FixedUpdate and Update are not called one after the other. Update is called once per frame and FixedUpdate is called once per physics update (default is 50 updates per second).
So the following could happen:
Update is called -> GetKeyDown is true (this frame only) -> isJumpPressed = true
Update is called -> GetKeyDown is false -> isJumpPressed = false
FixedUpdate is called -> isJumpPressed is false
Here is an example that prints "Update Jump" every time you press space, and "FixedUpdate Jump" only sometimes when you press space. Do not do this:
bool isJumpPressed;
void Update()
{
isJumpPressed = Input.GetKeyDown(KeyCode.Space);
if (isJumpPressed)
{
Debug.Log("Update Jump");
}
}
private void FixedUpdate()
{
if (isJumpPressed)
{
Debug.Log("FixedUpdate Jump");
}
}
Do this instead:
bool isJumpPressed;
void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
isJumpPressed = true;
Debug.Log("Update Jump");
}
}
private void FixedUpdate()
{
if (isJumpPressed)
{
Debug.Log("FixedUpdate Jump");
isJumpPressed = false;
}
}
One possible problem is that your project might be using the new Input System package. If you are using this package, the old Input Manager functions will not work. To check this, go to Edit > Project Settings... > Player > Other Settings and Active Input Handling should be set to Input Manager (Old) (or Both might also work).
If you actually want to use the Input System package, you have to install it if you don't already have it, and you would check if the spacebar is pressed like so:
using UnityEngine.InputSystem;
...
jumpPressed = Keyboard.current.space.wasPressedThisFrame;
I had the same issue. Spend some hours trying to figure out.
Maybe all was good from the beginning until I figured out.
When you I executed the play to see the result and test the movement, by mistake I chose the scene tab. If I want to see the result (to play) I must click the "Game Tab". In Game Tab the keys were detected.enter image description here
I'm making my first game and I have one last thing left to do. I have a shop where you can buy a lot of stuff. One of them obstacles don't spawn, but it was OP. So I decided to make a button that would do this effect for 10 seconds and have it cool down 20s before the next use. I tried the known for me methods but in this case they didn't work. I just want the boolean to change to true and after 10 seconds to false and to be cool down 20s before it can be pressed again. Any ideas?
Thanks for any help :)
EDIT:
So this is my code for on click button event:
public static bool isActive;
public Animator anim;
void use()
{
isActive = true;
anim.SetBool("isAngry", true);
}
void stop()
{
isActive = false;
anim.SetBool("isAngry", false);
}
public void clickButton()
{
StartCoroutine("start");
}
IEnumerator start ()
{
while (true)
{
use();
yield return new WaitForSeconds(10f);
stop();
}
}
and this is my code for obstacles:
void Start()
{
screenBounds = Camera.main.ScreenToWorldPoint(new Vector3(Screen.width, Screen.height, Camera.main.transform.position.z));
StartCoroutine(obstaclesWave());
}
private void spawnObstacles()
{
GameObject a = Instantiate(obstaclesPrefab) as GameObject;
a.transform.position = new Vector2(Random.Range(-screenBounds.x, screenBounds.x), screenBounds.y);
}
IEnumerator obstaclesWave()
{
while (true)
{
yield return new WaitForSeconds(respawnTime);
if(usePszczoła.isActive == false)
spawnObstacles();
yield return new WaitForSeconds(respawnTime);
}
}
}
Maybe you can use Coroutine. Simple code here:
private bool isCooldown = false;
public void ActivateSkill()
{
if (!isCooldown)
{
// Activate Skill
StartCoroutine(_ActivateSkillCoroutine());
}
else
{
Debug.Log("The skill is in cooldown!");
}
}
private IEnumerator _ActivateSkillCoroutine()
{
// Sets that this skill is on cooldown.
isCooldown = true;
// activate your skill, and wait for the end of it's effect.
yield return _SkillEffect();
// then wait for it's cooldown.
yield return new WaitForSeconds(20f);
// after 20 seconds, sets that the skill is ready to use.
isCooldown = false;
}
private IEnumerator _SkillEffect()
{
// 10 seconds for your skill. Try implement it yourself!
yield return new WaitForSeconds(10f);
}
This code is mere example, so you will be able to find your own way to do this.
More references : UnityEngine.Coroutine
Edit: As I see in your code, It seems like your code is, when the button is clicked, it stops spawning and waits for 10 seconds, and enables spawning, and immediatly stops spawning for 10 seconds, which loops forever. Try this:
private bool isCooldown = false
IEnumerator start ()
{
if (isCooldown) yield break;
isCooldown = true;
use();
yield return new WaitForSeconds(10f);
stop();
yield return new WaitForSeconds(20f);
isCooldown = false;
}
I am working on an RPG and started working on ability cooldowns. I have a player that is controlled by the user (player1), and a player controlled by the computer (player2) for testing purposes. The cooldown for abilities work perfectly for the user controlled player, but when it comes to the AI, the computer will not execute the ability unless the computer goes first (when the ability is off cooldown by default). Below is the code I think is relevant to the question, but if more information is needed I will update the post.
The ability class:
using UnityEngine;
public class attackList : MonoBehaviour{
public int dmg;
public float coolDown;
public float _attackTimer;
public void Attack1(basePlayer user, basePlayer target, int D)
{
coolDown = 2.5f;
if (user._attackTimer == 0)
{
dmg = D;
user._attackTimer = coolDown;
target.curHealth -= dmg;
}
}
public void cooldown(basePlayer user)
{
if (user._attackTimer > 0) {
user._attackTimer -= Time.deltaTime;
if (user._attackTimer < 0) {
user._attackTimer = 0;
}
}
}
}
The attack logic class:
public class attackLogic : MonoBehaviour {
private attackList _attackList = new attackList();
private playerList _playerList = new playerList();
public bool player1_turn = false; //player1 is not allowed to go first by default
public bool player2_turn = false; //player2 is not allowed to go first by default
void Start()
{
int rnd = Random.Range (1,3); // random value between 1 and 2 generated, which will determine what player goes first
if (rnd == 1)
{
player1_turn = true;
}
if (rnd == 2)
{
player2_turn = true;
}
//current health set to the maximum health at the beginning of each fight
_playerList.player1.curHealth = _playerList.player1.maxHealth;
_playerList.player2.curHealth = _playerList.player2.maxHealth;
}
void Update()
{
Logic(); //run through the logic of the battle
}
public void Logic()
{
if (player1_turn == true)
{
_attackList.cooldown(_playerList.player1);
if (Input.GetKeyUp("1"))
{
_attackList.Attack1(_playerList.player1, _playerList.player2, 5);
player1_turn = false;
player2_turn = true;
}
}
if (player2_turn == true)
{
_attackList.cooldown(_playerList.player2);
_attackList.Attack1(_playerList.player2, _playerList.player1, 5);
player1_turn = true;
player2_turn = false;
}
}
}
1) I have a nasty suspicion that the actual problem is in Time.deltaTime, but you didn't show the code so I can't confirm this.
2) Since you have time-based code here you can't expect it to behave the same when you step through the code. Thus it often helps to dust off some of the old techniques--namely, print statements. Of course things have changed enough over the years that it's become write but the ideas still work. Take a part of the screen that isn't important to what you're testing and write the variables out that are involved in what you are doing. I think you'll find player 2 is cooling down extremely slowly.
3) I think you need a better understanding of objects. You appear to be simply using them as containers. I also dislike player1 and player2--you should simply have a list of players. Remember how the WOPR was defeated in War Games?--that can be very useful at times in balancing things.
i've got a player and an enemy. When i rightclick the enemy his HP goes down and a hitcounter goes up. I want to make it like when you hit the enemy the text label becomes visible and when you stop attacking it stays visible for a couple more seconds and then hides and sets the hitcounter back to 0.
This is what i have at the moment.
public Text GUIHit;
public int HitCounter = 0;
void OnMouseOver()
{
if (Input.GetMouseButtonDown(1))
{
HitCounter++;
StartCoroutine(ShowHitCounter(HitCounter.ToString(), 2));
}
}
IEnumerator ShowHitCounter(string message, float delay)
{
GUIHit.text = message;
GUIHit.enabled = true;
yield return new WaitForSeconds(delay);
HitCounter = 0;
GUIHit.enabled = false;
}
What happens is that it works for 2 seconds, but even when im still attacking it goes invisible and the hit counter goes back to 0, the coroutine does not get reset back to a starting point.
Lets analyze your code:
void OnMouseOver()
{
if (Input.GetMouseButtonDown(1)) //you get passed that if when you hit first time
{
HitCounter++;
StartCoroutine(ShowHitCounter(HitCounter.ToString(), 2)); //you call your label with delay of 2 sec
}
}
IEnumerator ShowHitCounter(string message, float delay)
{
GUIHit.text = message;
GUIHit.enabled = true;
yield return new WaitForSeconds(delay); // still on your first hit you get to here and wait 2 seconds
HitCounter = 0; //after 2 seconds you reset hitcounter and disable label
GUIHit.enabled = false;
}
To fix it you need to know when you stopped hitting, and then reset hitcounter and disable label.
I would change showhitcounter to below:
IEnumerator ShowHitCounter(string message)
{
GUIHit.text = message;
GUIHit.enabled = true;
}
void ClearLabel()
{
HitCounter = 0;
GUIHit.enabled = false;
}
}
I made clearLabel to have separate method that clears label. Your logic will have to be in different places and call this method.
One place would onmouseleave event.
Other place would be in your onmouseover and added a property
public static DateTime TimeLeft { get; set; }
void OnMouseOver()
{
TimeSpan span = DateTime.Now - TimeLeft;
int ms = (int)span.TotalMilliseconds;
if (ms > 2000)
{
ClearLabel();
}
if (Input.GetMouseButtonDown(1))
{
HitCounter++;
StartCoroutine(ShowHitCounter(HitCounter.ToString(), 2));
}
}
Also you need to initialize TimeLeft somewhere before
Just finished with my solution and realized there is an answer already. Can't discard it. Just putting it as a solution with no memory allocation.
You don't need to start Coroutine each time right mouse is clicked like you did in the code in your question. I say this because of constant memory allocation when StartCoroutine() is called after each mouse click. Timer in the code below is based on frame-rate but can be easily changed to real-time by using DateTime.Now. You can also put the code in a while loop in a Coroutine then call it once from Start function.
public Text GUIHit;
public int HitCounter = 0;
bool firstRun = true;
float waitTimeBeforeDisabling = 2f;
float timer = 0;
void Update()
{
//Check when Button is Pressed
if (Input.GetMouseButtonDown(1))
{
//Reset Timer each time there is a right click
timer = 0;
if (!firstRun)
{
firstRun = true;
GUIHit.enabled = true;
}
HitCounter++;
GUIHit.text = HitCounter.ToString();
}
//Button is not pressed
else
{
//Increement timer if Button is not pressed and timer < waitTimeBeforeDisabling
if (timer < waitTimeBeforeDisabling)
{
timer += Time.deltaTime;
}
//Timer has reached value to Disable Text
else
{
if (firstRun)
{
firstRun = false;
GUIHit.text = HitCounter.ToString();
HitCounter = 0;
GUIHit.enabled = false;
}
}
}
}
Awh, okay then, here's another concept, just for the sake of it :)Did not test it and such so handle with care, but the thing is, starting a coroutine, etc looks too much (and too expensive) for me for something as little as what you want.
private float holdOutTime = 2.0f;
private float lastHitTime = 0.0f;
void OnMouseOver() {
if (Input.GetMouseButtonDown(1)) { IncHitAndShowUI() } //compacted
}
private void Update() {
if (GUIHit.enabled) { TestAndDisableHitUI(); } //compacted
}
#region priv/helper methods
//would force it inline if it was possible in Unity :)
private void IncHitAndShowUI() {
HitCounter++;
lastHitTime = Time.time;
GUIHit.text = HitCounter.ToString();
GUIHit.enabled = true;
}
//same here :)
private void TestAndDisableHitUI() {
if (lastHitTime + holdOutTime >= Time.time) {
GUIHit.enabled = false;
}
}
#endregion