How would you find the time since a certain variable was changed? Take for example a boolean variable, how would you find the time since it was last changed? I want to use the boolean variable as a trigger (activating the trigger when it's true), but only after an exact, constant time (such as 0.5s) has passed since it was changed to true (it can only be changed from false to true).
Here is the code I have:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class hitRegistration : MonoBehaviour
{
AudioSource hitSound;
private bool hitState = false;
// Use this for initialization
void Start()
{
hitSound = gameObject.GetComponent<AudioSource>();
}
void OnMouseOver()
{
Debug.Log("Mouse is over game object.");
if (Input.GetKey(KeyCode.X) && hitState == false)
{
hitSound.Play();
hitState = true;
}
}
private void OnMouseExit()
{
Debug.Log("Mouse is no longer over game object.");
if (hitState == true)
{
// sound clip gets cut if the cursor leaves before its finished.
Destroy(gameObject);
}
}
// Update is called once per frame
void Update()
{
}
}
"OnMouseOver()" Is simply a function that is called when the mouse is placed over the game object in question. I want to delay destroying the game object until a certain time has passed.
First off, as noted in a comment, you are probably trying to solve this problem the wrong way and you are probably asking an "XY" question -- a question where you are asking a question about a proposed bad solution instead of asking a question about the actual problem you face.
To answer the question you actually asked, for better or worse: there is no way to associate behaviours with reading or writing a variable in C#, but you can associate behaviours with a property:
private bool hitState; // The "backing store".
private bool HitState
{
get
{
return hitState;
}
set
{
hitState = value;
}
}
You would then use HitState rather than hitState throughout the rest of your class.
Now you can add whatever logic you want that happens when the property is read or written:
private DateTime hitStateTime = default(DateTime);
private bool hitState; // The "backing store".
private bool HitState
{
get
{
return hitState;
}
set
{
hitState = value;
hitStateSet = DateTime.Now;
}
}
Now you know when it was set. And so on.
Unless you really need to keep track of how much time has passed on each single frame, one way to do what you are asking for is to use Unity Coroutines.
A coroutine is a method that runs in parallel with the main thread. To solve your question, you can first create a coroutine in the same script, that waits and then does the thing you want to have delayed. A couroutine in Unity is a method that takes up to one parameter and has an IEnumerator return type. You use yield return WaitForSeconds(t); inside the coroutine to have it delay for t seconds.
Then, once it's time to die, check if the mouse is currently hovering over the object with isHovered (set in your OnMouseOver/OnMouseExit methods). If it is, keep a note that it's time to die. If it isn't, then it can die immediately.
IEnumerator WaitToDie(float delaySeconds)
{
yield return new WaitForSeconds(delaySeconds);
// If the mouse is on the object, let OnMouseExit know we're ready to die
if (isHovered)
{
readyToDie = true;
}
// Otherwise, just die
else
{
Destroy(gameObject)
}
}
And then inside your OnMouseOver code, run the coroutine after starting the sound
void OnMouseOver()
{
isHovered = true;
Debug.Log("Mouse is over game object.");
if (Input.GetKey(KeyCode.X) && !hitState)
{
hitState = true;
hitSound.Play();
// we want to delay for half a second before processing the hit.
float delaySeconds = 0.5;
IEnumerator coroutine = WaitToDie(delaySeconds);
StartCoroutine(coroutine);
}
}
And in your OnMouseExit, let everything know that you're done hovering and check if it's past time to die or not.
private void OnMouseExit()
{
isHovered = false;
Debug.Log("Mouse is no longer over game object.");
if (readyToDie) {
Destroy(gameObject);
}
}
Altogether this code will have the object die when both the mouse is off the object AND the time has elapsed.
As a sidenote, I think you might want to revisit how you are checking for a hit, unless your really want to trigger from the player holding X and then moving the mouse over the object. If you intend to trigger any time X is pressed down while the mouse is on top, you might want to put the check in Update and check Input.GetKey(KeyCode.X) && !hitState && isHovered
keep a seperate variable(DateTime) and call it lastUpdate. then be sure to set it to DateTime.Now, each time the bool you're tracking is updated. then when you need to see how long its been you can just subtract:
DateTime lengthOfTime = DateTime.Now-lastUpdate;
from lengthOfTime you can now access how many days, hours, minutes, and/or seconds have passed.
im on my phone so take it easy on my pseudo-code.
good luck
Related
Trying to repeat the function function OnAttack() continuously while a button is being held down.
Basically I'm looking for an equivalent to Update() { GetKeyDown() {//code }} But with the input system.
Edit: using a joystick, cant tell what button is being pressed.
Okay I solved it by using "press" in the interactions and giving that The trigger behavior "Press and release", then did
bool held = false;
Update()
{
if(held)
{
//animation
}
else if(!held)
{
//idle animation
}
}
OnAttack() {
held = !held;
}
This way if I press the button held goes to true so it repeats the animation every frame, letting go makes "held" untrue and does the idle animation
Essentially, the function you assign to the button will be triggered twice per button press, once when it is pressed (performed), and once when it is released (canceled). You can pass in this context at the beginning of your function, just make sure you are using the library seen at the top of this script.
Now you can toggle a bool on and off stating whether or not the button is pressed, then perform actions during update dependent on the state of the bool
using static UnityEngine.InputSystem.InputAction;
bool held = false;
Update()
{
if(held)
{
//Do hold action like shooting or whatever
}
else if(!held)
{
//do alternatice action. Not Else if required if no alternative action
}
}
//switch the status of held based on whether the button is being pressed or released. OnAttack is called every time the button is pressed and every time it is released, the if statements are what determine which of those two is currently happening.
OnAttack(CallbackContext ctx) {
if (ctx.performed)
held= true;
if (ctx.canceled)
held= false;
}
This is paraphrasing a solution I created for a click to move arpg mechanic.
using System.Threading.Tasks;
using UnityEngine;
[SerializeField] private InputAction pointerClickAction;
private bool pointerHeld;
void Start()
{
pointerClickAction.canceled += ClickMouseMove;
pointerClickAction.started += PointerHoldBegin;
pointerClickAction.performed += ClickMouseMove;
pointerClickAction.canceled += PointerHoldEnd;
}
private void OnEnable()
{
pointerClickAction.Enable();
pointerPositionAction.Enable();
}
private void OnDisable()
{
pointerClickAction.Disable();
pointerPositionAction.Disable();
}
public async void ClickMouseMove(InputAction.CallbackContext context)
{
while (pointerHeld)
{
DoSomething();
await Task.Delay(500);
}
}
public void PointerHoldBegin(InputAction.CallbackContext context)
{
pointerHeld = true;
}
public void PointerHoldEnd(InputAction.CallbackContext context)
{
pointerHeld = false;
}
public void DoSomething()
{
//Your Code
}
In Task.Delay() you can insert your own polling rate in milliseconds, using Task.Yield() seems to be faster than Update so I don't recommend that, you should poll at the minimum the same delay as your physics/fixed update, having a higher delay gives a performance boost if you don't need a high amount of repetitions per loop. I set mine to 500 since I don't need my character to plot its navigation that often. In regards to TC, you would set the delay to something sensible e.g the attack's animation length, or whatever the delay rate would be for how many attacks can be performed per second.
If you want your project to scale you might want to avoid as much as possible assertions(i.e if functions) in your Update/FixedUpdate/LateUpdate functions as they are executed constantly. I recommand you to read this article about coroutines https://gamedevbeginner.com/coroutines-in-unity-when-and-how-to-use-them/
You can build coroutines which act as local update() functions which are executed only when needed. This will lead you to a better organization of your code and might boost performance in some cases.
For exemple in your case you could use something like this.
bool held = false;
Update()
{
/* Whatever you want but the least assertion possible */
}
IEnumerator RenderHeldAnimation()
{
while (held)
{
// held animation
yield return new WaitForFixedUpdate(); /* Will block until next fixed frame right after FixedUpdate() function */
// yield return null /* Will block until next next frame right after Update() function */
}
}
IEnumerator RenderIdleAnimation()
{
while (!held)
{
// idle animation
yield return new WaitForFixedUpdate(); /* Will block until next fixed frame right after FixedUpdate() function */
// yield return null /* Will block until next next frame right after Update() function */
}
}
OnAttack() {
held = !held;
if (held) {
StartCoroutine(RenderHeldAnimation());
} else {
StartCoroutine(RenderIdleAnimation());
}
}
As mentioned in another answer, the context.canceled is not called when using the Press And Release interaction. As a follow up for documentation purposes as this is a top Google result, to correctly use a bool held without doing a blind toggle (held = !held) which may end up with drift, you can access context.control.IsPressed() like the following:
void OnAttack(CallbackContext context)
{
held = context.control.IsPressed();
}
I encountered the same issue and this was the method that seemed to work for me
private float _moveSpeed = 3f;
private float _moveDirection;
private void Update()
{
transform.Translate(_moveSpeed * _moveDirection * Time.deltaTime * transform.forward);
}
public void Move(InputAction.CallbackContext ctx)
{
_moveDirection = ctx.ReadValue<float>();
}
For some odd reason,the hold interaction works properly in reading the input, but I still need the update function to implement the actual logic.
Can't really complain though, it works. Although I'd love to know why it happens this way.
Hopefully this can be of help to someone.
You can use a timer for that purpose, in combination with events KeyUp and KeyDown.
Please look at the following link. It is pretty much similar to your problem.
Link
I'm creating a Pop up menu Option in Unity. Now my Problem here is that the coroutine i made in void update is being called so many times. What i mean by that is on my Unity Console the Debug.Logs are incrementing . It should not right because its already coroutine. Could some help me understand more coroutine and help me solve my little problem .
Here is my code:
[SerializeField]
GameObject Option;
[SerializeField]
Button btn,btn2;
[SerializeField]
GameObject open, close;
[SerializeField]
GameObject[] opt;
bool startFinding = false;
void Start()
{
Option.SetActive(false);
Button popUp = btn.GetComponent<Button>();
Button popUp2 = btn2.GetComponent<Button>();
popUp.onClick.AddListener(PopUpOption);
popUp2.onClick.AddListener(ClosePopUp);
}
void Update()
{
if (startFinding)
{
StartCoroutine(GameOptions());
}
}
IEnumerator GameOptions()
{
//Get All the tags
opt = GameObject.FindGameObjectsWithTag("MobileOptions");
if (opt[0].GetComponent<Toggle>().isOn == true && opt[1].GetComponent<Toggle>().isOn == true)
{
Debug.Log("Disable first the check box then choose only 1 option between" + "'rendering'"+ "and" + "'livestreaming'");
}
//Livestreaming
if (opt[0].GetComponent<Toggle>().isOn == true)
{
Debug.Log("Livestreaming Activate");
} else
{
Debug.Log("Livestreaming Deactivate");
}
//Rendering
if (opt[1].GetComponent<Toggle>().isOn == true)
{
Debug.Log("Rendering Activate");
} else
{
Debug.Log("Rendering Deactivate");
}
//Fog
if (opt[2].GetComponent<Toggle>().isOn == true)
{
Debug.Log("Fog Activated");
} else
{
Debug.Log("Fog Deactivated");
}
//Camera Effect
if (opt[3].GetComponent<Toggle>().isOn == true)
{
Debug.Log("Camera Effect Activated");
} else {
Debug.Log("Camera Effect Deactivated");
}
yield return null;
}
void PopUpOption()
{
startFinding = true;
//Disable The Mobile Option Button
open.SetActive(false);
//Enable the Close Option Button
close.SetActive(true);
//activate the Mobile Options
Option.SetActive(true);
}
void ClosePopUp()
{
startFinding = false;
//eanble the mobile option button
open.SetActive(true);
//disable the close option button
close.SetActive(false);
//deactivate the Mobile Option
Option.SetActive(false);
}
Here is how coroutines work:
Let's say I have a couroutine function called MyRoutine (in your case, you called it GameOptions)
private IEnumerator MyRoutine()
Then, anywhere in my code, calling
StartCoroutine(MyRoutine));
Is going to simply call MyRoutine like any usual method. So if you call it in update, it will be called all the time, as any method would. This is not what you want. What make coroutines special is that you can use the yield keyword in them. There are many ways to use it but the most used (and simple) one is to do yield return null
yield return null means "Stop this coroutine, but resume the execution on next frame". You don't need to call any other function (certainly not StartCoroutine). The execution will resume next frame.
To go back to what you posted in your question, you wrote yield return null at the end. So your method is executing, and just at the end, stops and resumes next frame, but since there is nothing left to do, it exits on the next frame.
A typical way to use coroutines is to have the yield return null in a while loop, so when it resumes, it continues the loop. Here is an example that do it
private IEnumerator MyRoutine()
{
while(running) //running is a member bool that you could set to false to exit
{
// Do all the stuff you want to do in ONE frame
// ...
yield return null;
}
}
Typically, the StartCoroutine would be called in the Start() function, or later when an event is triggered.
If you want to know more about coroutine, or check that you understood them properly, check out this page: https://docs.unity3d.com/Manual/Coroutines.html
or this video https://unity3d.com/learn/tutorials/topics/scripting/coroutines
// Edit: quickly present one useful option
In the snippet above, the while loop is very similar to the Update function (the inside of the loop is executed each frame). One nice option is to replace
yield return null
by
yield return new WaitForSeconds(waitTime)
where waitTime is a the time you want to wait before resuming, in seconds
// End of edit
Do not use StartCoroutine() in the Update method. Call it in another method and use a while loop inside your coroutine function if needed. Just control your StartCoroutine() outside of Update method
Update is called every frame, if your condition is ever true, you launch your coroutine every frame.
Just set down your flag to only join 1 time.
void Update()
{
if (startFinding)
{
startFinding = false;
StartCoroutine(GameOptions());
}
}
Currently I'm simply trying to change the sprites candle from unlit to lit when the player has 'picked up' both the candle and the matches and the candle will 'go out' after a certain amount of time. However, when the space bar is pressed the transition from unlit to lit isn't occurring, even though the debug log is returning true when it should. I'm posting here to get some guidance as I have spent most of the day looking online and literally have no idea how to proceed.
Basically the images I am trying to transition between are two different images which are in the sprites folder under assets.
This is what I've got so far.
//the two sprites transition
public Sprite unlitCandle;
public Sprite litCandle;
private SpriteRenderer spriteRenderer;
bool pickUpMatches = false;
bool pickUpCandle = false;
float timeRemaining =5;
bool candleLit = false;
// Use this for initialization
void Start () {
spriteRenderer = GetComponent<SpriteRenderer>();
if (spriteRenderer.sprite == null)
spriteRenderer.sprite = unlitCandle;
}
// Update is called once per frame
private void OnTriggerEnter2D(Collider2D collision)
{
if(collision.gameObject.CompareTag("Matches"))
{
collision.gameObject.SetActive(false);
pickUpMatches = true;
}
if (collision.gameObject.CompareTag("UnlitCandle"))
{
collision.gameObject.SetActive(true);
pickUpCandle = true;
}
}
public void CandleTimer()
{
if (candleLit == true)
{
timeRemaining = 5;
timeRemaining -= Time.deltaTime;
if (timeRemaining <= 0)
{
candleLit = false;
spriteRenderer.sprite = unlitCandle;
}
}
}
public void ChangeSprite()
{
if (spriteRenderer.sprite == unlitCandle)
{
spriteRenderer.sprite = litCandle;
}
}
void Update () {
if (pickUpCandle == true && pickUpMatches == true)
{
//Debug.Log(candleLit);
if (Input.GetKey(KeyCode.Space) && !candleLit)
{
CandleTimer();
ChangeSprite();
Debug.Log(timeRemaining);
candleLit = true;
//Debug.Log(candleLit);
}
}
}
}
Try comparing with a method like equals() instead of == in
spriteRenderer.sprite == unlitCandle
Because right now you are just comparing references and not the objects.
At least I think thats the problem.
There are a few possible issues with your code. First, you are calling changeSprite at the top of Update, which means that it is unconditionally being called every frame. Therefore, after a single frame of your candle being unlit, it will immediately change its sprite to litCandle.
I assume that the reason you are calling changeSprite every frame is in order to process the timer if you have a lit candle already. Really, you should move the code to process the timer (your whole second if statement in changeSprite) to a separate function and name it something like processCandleTimer. Call that at the top of Update and save the changeSprite method to only be called on the keypress.
Lastly, the issue that I suspect is giving you the most trouble is that you aren't resetting your timer, timeRemaining. The first time you light the candle the timer will go down to 0 after the 5 seconds pass. Every time changeSprite is run after that, you will change the sprite to litCandle in the first if statement and then immediately change it back to unlitCandle because the timer is 0 in the second. To remedy this, you need to add a line like timeRemaining = 5.0f; when the key is hit.
So I have a Unity coroutine method, in which I have some objects. These objects represent values that are being gathered from a server somewhere, and they send out an Updated event when they are ready.
I was wondering what the best way is to wait for all the values to be updated, inside a coroutine in Unity.
public IEnumerator DoStuff()
{
foreach(var val in _updateableValues)
{
if (val.Value != null) continue;
else **wait for val.Updated to be fired then continue**
}
//... here I do stuff with all the values
// I use the WWW class here, so that's why it's a coroutine
}
What would be the best way of doing something like this?
Thanks!
There is no builtin direct method to wait for the event itself, but you can use a synchronous nested coroutine to wait for a flag set by the event:
//Flag
bool eventHappened;
//Event subscriber that sets the flag
void OnEvent(){
eventHappened=true;
}
//Coroutine that waits until the flag is set
IEnumerator WaitForEvent() {
yield return new WaitUntil(eventHappened);
eventHappened=false;
}
//Main coroutine, that stops and waits somewhere within it's execution
IEnumerator MainCoroutine(){
//Other stuff...
yield return StartCoroutine(WaitForEvent());
//Oher stuff...
}
With that in mind, creating a generic coroutine that waits for an UnityEvent is easy:
private IEnumerator WaitUntilEvent(UnityEvent unityEvent) {
var trigger = false;
Action action = () => trigger = true;
unityEvent.AddListener(action.Invoke);
yield return new WaitUntil(()=>trigger);
unityEvent.RemoveListener(action.Invoke);
}
I thing that a better way is to check the sever every frame and not to wait for the an amount of time without any thinking.
public IEnumerator DoStuff()
{
/* wait until we have the value we want */
while( value != desiredValue)
yield return null
//after this loop, start the real processing
}
This is my little fix.
I use the WaitUntil directly where I need it. I found though, that WaitUntil doesn't want a boolean but expects a boolean predicate function - so I provide this in my example below.
The example is real running code from a game. It runs in the very first scene, where I start the music before I do - the slightly lengthly - loading of the other stuff.
//
public class AudioSceneInitializer : MonoBehaviour
{
[SerializeField] private GlobalEventsSO globalEvents;
//globalEvents is a scriptable object that - in my case - holds all events in the game
[SerializeField] private AudioManager audioManager;
//The audio manager which in my case will raise the audio ready event
[SerializeField] public GameObject audioGO;// contains AudioSources used by AudioManager
private bool audioReady = false;
// Start is called before the first frame update
IEnumerator Start()
{
if (audioManager != null)
//better check, if we don't produce the event, we wait forever
{
//subscribe to event
globalEvents.audioManagerReadyEvent += OnAudioReady;
//do something to raise the event - else we wait forever
audioManager.onInitialization(this);//"this" needed to hand over the audioGO to the AudioManager
// waits until the flag is set;
yield return new WaitUntil(IsAudioReady);
}
//now that we have music playing, we load the actual game
SceneManager.LoadSceneAsync(1, LoadSceneMode.Additive);
}
//Event subscriber that sets the flag
void OnAudioReady()
{
//unsubscribing, if - as in my case - it happens only once in the game
globalEvents.audioManagerReadyEvent -= OnAudioReady;
audioReady = true;
}
//predicate function that WaitUntil expects
private bool IsAudioReady()
{
return audioReady;
}
public void OnDisable()
{
audioManager.onCleanup();
}
}
A spin-lock is a solution, however not a very CPU-gentle one. In a spin-lock, you would just wait for the variable to have a certain value, otherwise sleep for a few milliseconds.
public IEnumerator DoStuff()
{
/* wait until we have the value we want */
while( value != desiredValue)
yield return new WaitForSeconds(0.001f);
//after this loop, start the real processing
}
Maybe you might want to think about restructuring your code, sothat no spin-lock is requiered, but a more interupt/event-based based approach can be implemented. That means, if you update a value and something has to take place after it happened, kick it off directly after changing that value. In C#, there's even an interface INotifyPropertyChanged for that design pattern (see MSDN), but you can easily design that yourself, too, e.g. by firing an event when that certain value has changed. We'd need more information on what exactly you want to react here, if you want a better solution than a spinlock, but this should give you some ideas.
I have been working on a building game and whenever I try to destroy a block all of the blocks that contain the script, DestroyBlock.cs are destroyed. I know why the problem is happening, but I don’t know how to fix it. Could anyone please help me? I am trying to get a development build out by this Saturday and I need a quick fix to this.
using UnityEngine;
using System.Collections;
public class DestroyBlock : MonoBehaviour
{
public static bool IsDestroyable = false;
void Update ()
{
if (Input.GetMouseButtonDown(1) && IsDestroyable == true)
{
Destroy(gameObject);
}
}
void OnMouseEnter()
{
renderer.material.color = Color.black;
IsDestroyable = true;
}
void OnMouseExit()
{
renderer.material.color = Color.white;
IsDestroyable = false;
}
}
Okay, so I've looked over the code more, and I'm almost positive the reason this is happening is because your IsDestroyable variable is static. This means a single instance of this is shared between every DestroyBlock object ever created. Any time an instance of DestroyBlock sees that the mouse has entered it, it sets IsDestroyable to true, which means that for every single block in existence IsDestroyable == true. Update is called, and, because there hasn't been a MouseExit yet, all the blocks get destroyed. As it stands, either every block is going to get destroyed or none of them will.
The easiest way to fix this would just be to change IsDestroyable to a non-static property.