I have this issue with double clicking in-game. I wrote this code that checks for input and returns the type of input it received.
In editor it works just fine, but when I export the game it always returns double click type. Even if I click just once. Not sure what's causing this issue..
Below is the mouse input script and other is how I use it in other scripts.
Mouse Input script:
using System.Collections;
using UnityEngine.EventSystems;
public class MouseInput : MonoBehaviour
{
#region Variables
private enum ClickType { None, Single, Double }
private static ClickType currentClick = ClickType.None;
readonly float clickdelay = 0.25f;
#endregion
void OnEnable()
{
StartCoroutine(InputListener());
}
void OnDisable()
{
StopAllCoroutines();
}
public static bool SingleMouseClick()
{
if (currentClick == ClickType.Single)
{
currentClick = ClickType.None;
return true;
}
return false;
}
public static bool DoubleMouseClick()
{
if (currentClick == ClickType.Double)
{
currentClick = ClickType.None;
return true;
}
return false;
}
private IEnumerator InputListener()
{
while (enabled)
{
if (Input.GetMouseButtonDown(0))
{ yield return ClickEvent(); }
yield return null;
}
}
private IEnumerator ClickEvent()
{
if (EventSystem.current.IsPointerOverGameObject()) yield break;
yield return new WaitForEndOfFrame();
currentClick = ClickType.Single;
float count = 0f;
while (count < clickdelay)
{
if (Input.GetMouseButtonDown(0))
{
currentClick = ClickType.Double;
yield break;
}
count += Time.deltaTime;
yield return null;
}
}
}
Usage:
if (MouseInput.SingleMouseClick())
{
Debug.Log("Single click");
Select(true);
}
else if (MouseInput.DoubleMouseClick())
{
Debug.Log("Double click");
Select(false);
}
So on the frame Input.GetMouseButtonDown(0) evaluates to true you call into ClickEvent() which then yields on WaitForEndOfFrame(); then eventually gets back to another Input.GetMouseButtonDown(0)).
You problem is that WaitForEndOfFrame() waits until the end of the current frame:
Waits until the end of the frame after all cameras and GUI is
rendered, just before displaying the frame on screen.
It doesn't wait until the next frame. Because of this, all values returned by the Input API are still going to be the same. You want a yield return null instead of WaitForEndOfFrame().
Related
Ok so I have been trying to write a Coroutine that will wait 2 seconds before the health gets deducted more. No errors show up, but the script doesn't wait 2 seconds. I am a beginner, so I don't know what's going on here that could cause the error. I'm guessing I spelled something incorrectly.
Here is the code:
using System.Collections.Generic;
using UnityEngine;
public class Die : MonoBehaviour
{
public int Health;
public int GolemDamage;
public Animator anim;
public Transform enemy;
public bool canAttack = true;
public bool justAttacked = false;
// Start is called before the first frame update
void Start()
{
}
void Update()
{
float distance = Vector3.Distance(enemy.position, transform.position);
canAttack = true;
if(canAttack == false)
{
GolemDamage = 0;
}
if(canAttack == true)
{
GolemDamage = 10;
}
if(distance <= 2.5f)
{
Health = Health - GolemDamage;
justAttacked = true;
}
if(justAttacked == true)
{
canAttack = false;
StartCoroutine("Attack");
}
if(justAttacked == false)
{
canAttack = true;
}
}
IEnumerator Attack()
{
yield return new WaitForSeconds(2);
justAttacked = false;
StartCoroutine("Attack");
}
}
You are again starting the coroutine in the coroutine itself:
IEnumerator Attack() {
yield return new WaitForSeconds(2);
justAttacked = false;
StartCoroutine("Attack");//here
}
Try starting it just where you need it to execute and add some helper logs Like so:
if(justAttacked == true) {
canAttack = false;
Debug.Log("coroutine started");
StartCoroutine("Attack");
}
IEnumerator Attack() {
Debug.Log("log before wait");
yield return new WaitForSeconds(2);
Debug.Log("log after wait");
justAttacked = false;
StartCoroutine("Attack");//here
}
I the console you can check if the logs make sense so that you can move on. Also you can check if between Debug.Log("log before wait"); and Debug.Log("log after wait"); there are 2 seconds, as you can see the time of each log in the console.
I discovered that when you call something that doesn't run inside the unity editor like (Admob) this happen to StartCoroutine, the solution is to set
for example:
public static void show()
{
#if !UNITY_EDITOR
if (interstitial.IsLoaded())
{
interstitial.Show();
requist_ad = true;
}
else
{
requist_ad = true;
}
#endif
//Debug.Log("not mobile");
}
I know that it's not the case here but it's for others that came here about the Startcoroutine problem
I am making a game with a maze with audio hints on what way to go. I have the sounds playing on a coroutine and it work in terms of only starting once. However, what I need to do is be able to stop it from another script with a trigger on so that the audio does not keep playing when the player passes a certain point. This is my code so far.
public AudioSource direction;
private bool running = false;
IEnumerator AudioPlay()
{
while (true)
{
direction.Play();
yield return new WaitForSeconds(2);
}
}
void OnTriggerEnter(Collider col)
{
if (col.gameObject.CompareTag("Player"))
{
if (running == false)
{
StartCoroutine(AudioPlay());
Debug.Log("Started");
running = true;
}
else if (running == true)
{
Debug.Log("Void");
}
}
}
Use StopCoroutine(previouslyRunCoroutine)
If you use the IEnumerator form (StartCoroutine(AudioPlay());) to start the coroutine, the Unity documentation recommends that you save a reference to the IEnumerator and use that in future calls to StopCoroutine:
public AudioSource direction;
private bool running = false;
public IEnumerator audioPlayCoroutine;
IEnumerator AudioPlay()
{
while (true)
{
direction.Play();
yield return new WaitForSeconds(2);
}
}
void OnTriggerEnter(Collider col)
{
if (col.gameObject.CompareTag("Player"))
{
if (running == false)
{
audioPlayCoroutine = AudioPlay();
StartCoroutine(audioPlayCoroutine);
Debug.Log("Started");
running = true;
}
else if (running == true)
{
Debug.Log("Void");
}
}
}
Then, in your other script you can use StopCoroutine:
OtherScript.StopCoroutine(OtherScript.audioPlayCoroutine);
If you were to use the method name form such as StartCoroutine("AudioPlay");, the documentation would recommend using the method name form to stop it:
OtherScript.StopCoroutine("AudioPlay");
I have a script conflict that checks if there is network connection and the version of the game (if it's up to date); but it's skipping those conditions and loading the game directly.
How do I make the script follow this pattern?:
Check if there is internet connection
Check the version of the game
Only load the game if there is an internet connection and the version of the game is current
You'll notice there are some loading function who repeat themselves; I'm not sure which ones to apply.
CheckNetwork.cs
public Text AlertText;
public GameObject alert;
public string URL = "";
public string CurrentVersion;
string latestVersion;
public GameObject newVersionAvailable;
private void Start()
{
StartCoroutine(LoadTxtData(URL));
}
void Awake()
{
if (Application.internetReachability == NetworkReachability.NotReachable)
{
alert.SetActive(true);
AlertText.text = "Sorry, you have to enable your conection to play this game";
if (Input.GetKeyDown(KeyCode.Escape))
Application.Quit();
}
else
{
alert.SetActive(false);
CheckVersion();
}
}
public void CheckVersion()
{
if(CurrentVersion != latestVersion)
{
newVersionAvailable.SetActive(true);
}
else
{
newVersionAvailable.SetActive(false);
}
}
private IEnumerator LoadTxtData(string url)
{
UnityWebRequest www = UnityWebRequest.Get(url);
yield return www.SendWebRequest();
latestVersion = www.ToString();
CheckVersion();
}
public void OpenURL(string url)
{
Application.OpenURL(url);
}
Loading.cs
private Image progressBar;
private void Start()
{
Invoke("LoadAsyncOperation", 3f);
StartCoroutine(LoadAsyncOperation());
}
public IEnumerator LoadAsyncOperation()
{
AsyncOperation gameLevel = SceneManager.LoadSceneAsync(1);
while (gameLevel.progress < 1)
{
progressBar.fillAmount = gameLevel.progress;
yield return new WaitForEndOfFrame();
}
}
Loading2.cs
public GameObject loadingScreenObj;
public Slider slider;
AsyncOperation async;
public void StartGame()
{
SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex + 1);
}
public void LoadScreen(int LVL)
{
//StartCoroutine(loadingScreen());
}
IEnumerator LoadingScreen(int lvl)
{
loadingScreenObj.SetActive(true);
async = SceneManager.LoadSceneAsync(lvl);
async.allowSceneActivation = false;
while (async.isDone == false)
{
slider.value = async.progress;
if (async.progress == 0.9f)
{
slider.value = 1f;
async.allowSceneActivation = true;
}
yield return null;
}
}
SplashScrean.cs
void Start()
{
Invoke("loadMenuLevel", 3f);
Screen.sleepTimeout = SleepTimeout.NeverSleep;
}
void loadMenuLevel()
{
SceneManager.LoadScene("gameplay");
}
void Update()
{
if (Input.GetKey(KeyCode.Mouse0))
{
loadMenuLevel();
}
if (Input.GetKey(KeyCode.Escape))
{
Application.Quit();
}
}
This is happening because you call the Invoke("loadMenuLevel", 3f); method in the Start() method in the SplashScrean.cs script.
Start() is invoked very soon after a MonoBehaviour is attached to an object. Ostensibly, you have it attached to a GameObject in the scene. All of these are being hit at once.
You also have it as invokable via left mouse click in the same script's Update() method:
if (Input.GetKey(KeyCode.Mouse0))
{
loadMenuLevel();
}
I would add bools to each of the loading scripts that are set to true when each task completes. Then, in SplashScrean.cs, change the above condition to load the level automatically if all three are complete. Or, keep the key press condition and add the bools.
if (Application.internetReachability != NetworkReachability.NotReachable
&& loadScriptComplete && isCurrentVersion && Input.GetKey(KeyCode.Mouse0))
{
loadMenuLevel();
}
Now, if you also need the Loading MonoBehaviours to wait to execute their loading logic too, then consider adding Update() logic to them that checks the flag you set in CheckNetwork.cs.
//For example. Instead of this
private void Start()
{
Invoke("LoadAsyncOperation", 3f);
StartCoroutine(LoadAsyncOperation());
}
//Do this
private void Update()
{
//Note that we only need to check if it is a CurrentVersion, as if that is true, then we definitely have internet access.
if(CheckNetwork.IsCurrentVersion) {
Invoke("LoadAsyncOperation", 3f);
StartCoroutine(LoadAsyncOperation());
}
}
Edits/Additional Notes As I notice:
CheckNetwork.cs references CheckVersion(); in the else clause. That seems pointless. It is impossible for LoadTxtData() to have set the logic that CheckVersion() relies on in that time frame. A sort of race condition, if you will. Plus, you handle that check in LoadTxtData() anyways, so I would just do that there.
I can't give you a complete copy-pastable code here (mainy because I don't fully understand yet how all your scripts are connected between each other) but I see you are already using Coroutines.
I would as mentioned before use one central manager to handle all those things.
I will only give you a general pseudo-code here and hope you find your way frome here
public class LoadManager
{
public CheckNetwork check;
public Loading loading;
public Loading2 loading2;
public SplashScrean splashScreen;
// suprise: a not well documented fact
// you can simply make the Start method an IEnumerator (Coroutine)
// so you can directly wait for anything you want
private void IEnumerator Start()
{
// Next suprise: You can simply yield other IEnumerators
// that makes them execute and at the same time waits until they are finished
// so all methods that you want to wait for from here
// should be IEnumerators
// as said this is only an example
// you could directly within that single
// Coroutine do your complete download and check routine together
yield return check.CheckPermissions();
if(!check.isReachable)
{
yield break;
}
// same for chekcing the version
yield return check.CheckVersion();
if(!check.isUpToDate)
{
// do something for update
yield break;
}
// is up to date so start and wait for loading
yield return loading.LoadAsyncOperation();
// and so on
}
}
And there are also some builtin ways for waiting like e.g. WaitUntil, WaitForSeconds etc.
Please do not get too hard about my grammar.
I write follow class for delay
public class Queue_System_Of_Begin_Game : MonoBehaviour
{
// Use this for initialization
void Start()
{
}
// Update is called once per frame
void Update()
{
if (Game_Controller.Player1_First_throws_true && Game_Controller.Player2_First_throws_true)
{
StartCoroutine(ExecuteAfterTime(1));
}
}
//--------------------------------------
public GameObject player1_icon, player2_icon, dice1_p1, dice2_p1, dice1_p2, dice2_p2;
void determine_the_turn()
{
Debug.Log("update");
}
IEnumerator ExecuteAfterTime(float time)
{
yield return new WaitForSeconds(time);
determine_the_turn();
}
}
I receive 62 times the word update on the console.
This problem will cause my next round of games to run 62 times, which slowed down my game.
The Update() method is called once per frame, thats the reason you get 62 "updates".
You can try adding a boolean so it only gets called once like this:
bool ischecked = false;
void Update(){
if (!ischecked){
if (Game_Controller.Player1_First_throws_true && Game_Controller.Player2_First_throws_true) {
ischecked = true;
StartCoroutine (ExecuteAfterTime (1));
}
}
}
I find solution for my problem .I must use a boolean variable in my if commond like this :
public class Queue_System_Of_Begin_Game : MonoBehaviour
{
private bool coroutineStarted;
// Update is called once per frame
void Update()
{
if (!coroutineStarted && Game_Controller.Player1_First_throws_true && Game_Controller.Player2_First_throws_true)
{
coroutineStarted = true ;
StartCoroutine(ExecuteAfterTime(1));
}
}
//--------------------------------------
public GameObject player1_icon, player2_icon, dice1_p1, dice2_p1, dice1_p2, dice2_p2;
void determine_the_turn()
{
Debug.Log("update");
}
IEnumerator ExecuteAfterTime(float time)
{
yield return new WaitForSeconds(time);
determine_the_turn();
}
}
I'm making a game using monogame and trying to use a controller manager to manage user input.
I've created a class ControllerManager to do this. Because I'm trying to take input from 2 controllers, I create three instances of ControllerManager: controllerManager, controller1Manager and controller2Manager.
Now, in my player object I have a local variable, localManager to which I need to assign to either controller1Manager or controller2Manager depending on which player it is.
I've been trying to assign it like:
this.localManager = Controller1Manager;
This results in the localManager variable being set to controllerManager.
So does anyone see what I'm doing wrong? I have no idea other than maybe it has something to do with pointers/references/singleton but I've checked the component list and each individual manager is in there.
Edit:
As requested, here are the constructor, class creation and where I'm trying to change values
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using GDApp;
namespace GDLibrary
{
public class ControllerManager : GameComponent, IService
{
public GamePadState newState, oldState;
public PlayerIndex player;
public ControllerManager(Main game, PlayerIndex index)
: base(game)
{
this.player = index;
}
public override void Update(GameTime gameTime)
{
oldState = newState;
newState = GamePad.GetState(player);
base.Update(gameTime);
}
public bool IsFirstButtonPress(Buttons button)
{
if (oldState.IsButtonUp(button) && newState.IsButtonDown(button))
return true;
return false;
}
public bool IsButtonDown(Buttons button)
{
return newState.IsButtonDown(button);
}
public bool IsButtonUp(Buttons button)
{
return newState.IsButtonUp(button);
}
#region Thumbsticks
//Right
//Magnitude of right stick in right direction
public float RightStickRight()
{
if (newState.ThumbSticks.Right.X <= 0)
return 0;
else
return newState.ThumbSticks.Right.X;
}
//Magnitude of right stick in left direction
public float RightStickLeft()
{
if (newState.ThumbSticks.Right.X >= 0)
return 0;
else
return System.Math.Abs(newState.ThumbSticks.Right.X);
}
//Magnitude of right stick in upward direction
public float RightStickUp()
{
if (newState.ThumbSticks.Right.Y >= 0)
return 0;
else
return System.Math.Abs(newState.ThumbSticks.Right.Y);
}
//Magnitude or right stick in downward direction
public float RightStickDown()
{
if (newState.ThumbSticks.Right.Y <= 0)
return 0;
else
return newState.ThumbSticks.Right.Y;
}
//Left
//Magnitude of left stick in right direction
public float LeftStickRight()
{
if (newState.ThumbSticks.Left.X <= 0)
return 0;
else
return newState.ThumbSticks.Left.X;
}
//Magnitude of left stick in left direction
public float LeftStickLeft()
{
if (newState.ThumbSticks.Left.X >= 0)
return 0;
else
return System.Math.Abs(newState.ThumbSticks.Left.X);
}
//Magnitude of left stick in upward direction
public float LeftStickUp()
{
if (newState.ThumbSticks.Left.Y >= 0)
return 0;
else
return System.Math.Abs(newState.ThumbSticks.Left.Y);
}
//Magnitude or right stick in downward direction
public float LeftStickDown()
{
if (newState.ThumbSticks.Left.Y <= 0)
return 0;
else
return newState.ThumbSticks.Left.Y;
}
#endregion
public bool RightStickCentered()
{
if (newState.ThumbSticks.Right.X.Equals(0) && newState.ThumbSticks.Right.Y.Equals(0))
{
return true;
}
return false;
}
public bool LeftStickCentered()
{
if (newState.ThumbSticks.Left.X.Equals(0) && newState.ThumbSticks.Left.Y.Equals(0))
{
return true;
}
return false;
}
public Vector2 RightStick()
{
return this.newState.ThumbSticks.Right;
}
public Vector2 LeftStick()
{
return this.newState.ThumbSticks.Left;
}
public bool LeftStickMoved()
{
if (!oldState.ThumbSticks.Left.X.Equals(newState.ThumbSticks.Left.X) || !oldState.ThumbSticks.Left.Y.Equals(newState.ThumbSticks.Left.Y))
{
return true;
}
return false;
}
public bool RightStickMoved()
{
if (!oldState.ThumbSticks.Right.X.Equals(newState.ThumbSticks.Right.X) || !oldState.ThumbSticks.Right.Y.Equals(newState.ThumbSticks.Right.Y))
{
return true;
}
return false;
}
}
}
Creating managers:
this.controller1Manager = new ControllerManager(this, PlayerIndex.One);
Components.Add(controller1Manager);
IServiceContainer.AddService(typeof(ControllerManager), controller1Manager);
this.controller2Manager = new ControllerManager(this, PlayerIndex.Two);
Components.Add(controller2Manager);
IServiceContainer.AddService(typeof(ControllerManager), controller2Manager);
Changing values:
if (this.index.Equals(PlayerIndex.One))
{
this.localManager = Controller1Manager;
}
else if (this.index.Equals(PlayerIndex.Two))
{
this.localManager = Controller2Manager;
Controller2Manager.player = index;
}
This issue has been resolved by passing a ControllerManager into the PlayerObject in place of a PlayerIndex. The reason all of the controllerManagers seemed to have their variables changed at once was their inclusion in the IServiceContainer shown in the edits above. IServiceContainer stores only one instance of a given type (It's a Dictionary) so only the most recent addition was being saved.