In multiple answers it was stated that Update is called once every frame and shouldn't be used for physics update, it should however be used for input or you might miss important events.
The problem that arises now is what if I ise Update to influence a physics object?
That for example on a mouse release, some balls start moving and spinning.
void Update
{
if (Input.GetMouseButtonUp(0))
{
ball.getComponent<Rigidbody>().AddForce(vector);
ball.getComponent<Rigidbody>().AddTorque(vector2);
}
}
To achieve optimal performance you should split the code.
Inside Update you get the input and store it somewhere.
Inside FixedUpdate you calculate physics.
In the specific case you mentioned the code will become:
bool mouseUp = false;
void Update()
{
mouseUp = Input.GetMouseButtonUp(0);
}
void FixedUpdate()
{
if (mouseUp)
{
ball.getComponent<Rigidbody>().AddForce(vector);
ball.getComponent<Rigidbody>().AddTorque(vector2);
mouseUp = false;
}
}
-------------
EDIT (after derHugo and Wouter Vandenputte comments)
In some cases FixedUpdate might be called multiple times a frame. So it is safer to reset the value after it's usage. In the example case by adding mouseUp = false.
Related
Even the official documentation has borderline insane recommendations to solve what is probably one of the most common UI/3D interaction issues:
If I click while the cursor is over a UI button, both the button (via the graphics raycaster) and the 3D world (via the physics raycaster) will receive the event.
The official manual:
https://docs.unity3d.com/Packages/com.unity.inputsystem#1.2/manual/UISupport.html#handling-ambiguities-for-pointer-type-input essentially says "how about you design your game so you don't need 3D and UI at the same time?".
I cannot believe this is not a solved problem. But everything I've tried failed. EventSystem.current.currentSelectedGameObject is sticky, not hover. PointerData is protected and thus not accessible (and one guy offered a workaround via deriving your own class from Standalone Input Module to get around that, but that workaround apparently doesn't work anymore). The old IsPointerOverGameObject throws a warning if you query it in the callback and is always true if you query it in Update().
That's all just mental. Please someone tell me there's a simple, obvious solution to this common, trivial problem that I'm just missing. The graphics raycaster certainly stores somewhere if it's over a UI element, right? Please?
I've looked into this a fair bit and in the end, the easiest solution seems to be to do what the manual says and put it in the Update function.
bool pointerOverUI = false;
void Update()
{
pointerOverUI = EventSystem.current.IsPointerOverGameObject();
}
Your frustration is well founded: there are NO examples of making UI work with NewInput which I've found. I can share a more robust version of the Raycaster workaround, from Youtube:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.InputSystem;
using UnityEngine.UI;
/* Danndx 2021 (youtube.com/danndx)
From video: youtu.be/7h1cnGggY2M
thanks - delete me! :) */
public class SCR_UiInteraction : MonoBehaviour
{
public GameObject ui_canvas;
GraphicRaycaster ui_raycaster;
PointerEventData click_data;
List<RaycastResult> click_results;
void Start()
{
ui_raycaster = ui_canvas.GetComponent<GraphicRaycaster>();
click_data = new PointerEventData(EventSystem.current);
click_results = new List<RaycastResult>();
}
void Update()
{
// use isPressed if you wish to ray cast every frame:
//if(Mouse.current.leftButton.isPressed)
// use wasReleasedThisFrame if you wish to ray cast just once per click:
if(Mouse.current.leftButton.wasReleasedThisFrame)
{
GetUiElementsClicked();
}
}
void GetUiElementsClicked()
{
/** Get all the UI elements clicked, using the current mouse position and raycasting. **/
click_data.position = Mouse.current.position.ReadValue();
click_results.Clear();
ui_raycaster.Raycast(click_data, click_results);
foreach(RaycastResult result in click_results)
{
GameObject ui_element = result.gameObject;
Debug.Log(ui_element.name);
}
}
}
So, just drop into my "Menusscript.cs"?
But as a pattern, this is terrible for separating UI concerns. I'm currently rewiring EVERY separately-concerned PointerEventData click I had already working, and my question is, Why? I can't even find how it's supposed to work: to your point there IS no official guide at all around clicking UI, and it does NOT just drop-on-top.
Anyway, I haven't found anything yet which makes new input work easily on UI, and definitely not found how I'm going to sensibly separate Menuclicks from Activityclicks while keeping game & ui assemblies separate.
Good luck to us all.
Unity documentation for this issue with regard to Unity.InputSystem can be found at https://docs.unity3d.com/Packages/com.unity.inputsystem#1.3/manual/UISupport.html#handling-ambiguities-for-pointer-type-input.
IsPointerOverGameObject() can always return true if the extent of your canvas covers the camera's entire field of view.
For clarity, here is the solution which I found worked best (accumulated from several other posts across the web).
Attach this script to your UI Canvas object:
public class CanvasHitDetector : MonoBehaviour {
private GraphicRaycaster _graphicRaycaster;
private void Start()
{
// This instance is needed to compare between UI interactions and
// game interactions with the mouse.
_graphicRaycaster = GetComponent<GraphicRaycaster>();
}
public bool IsPointerOverUI()
{
// Obtain the current mouse position.
var mousePosition = Mouse.current.position.ReadValue();
// Create a pointer event data structure with the current mouse position.
var pointerEventData = new PointerEventData(EventSystem.current);
pointerEventData.position = mousePosition;
// Use the GraphicRaycaster instance to determine how many UI items
// the pointer event hits. If this value is greater-than zero, skip
// further processing.
var results = new List<RaycastResult>();
_graphicRaycaster.Raycast(pointerEventData, results);
return results.Count > 0;
}
}
In class containing the method which is handling the mouse clicks, obtain the reference to the Canvas UI either using GameObject.Find() or a public exposed variable, and call IsPointerOverUI() to filter clicks when over UI.
Reply to #Milad Qasemi's answer
From the docs you have attached in your answer, I have tried the following to check if the user clicked on a UI element or not.
// gets called in the Update method
if(Input.GetMouseButton(0) {
int layerMask = 1 << 5;
// raycast in the UI layer
RaycastHit2D hit = Physics2D.Raycast(Camera.main.ScreenToWorldPoint(Input.mousePosition), Vector2.zero, Mathf.Infinity, layerMask);
// if the ray hit any UI element, return
// don't handle player movement
if (hit.collider) { return; }
Debug.Log("Touched not on UI");
playerController.HandlePlayerMovement(x);
}
The raycast doesn't seem to detect collisions on UI elements. Below is a picture of the Graphics Raycaster component of the Canvas:
Reply to #Lowelltech
Your solution worked for me except that instead of Mouse I used Touchscreen
// Obtain the current touch position.
var pointerPosition = Touchscreen.current.position.ReadValue();
An InputSytem is a way to receive new inputs provided by Unity. You can't use existing scripts there, and you'll run into problems like the original questioner. Answers with code like "if(Input.GetMouseButton(0)" are invalid because they use the old system.
I wrote a script that writes Input.mousePosition into a file on every frame. The idea is that I want to identify which button on screen the player is trying to click before actually clicking, from the speed of the mouse basically. However, I ran into data like this:
(1113.0, 835.0, 0.0)
(1113.0, 835.0, 0.0)
(1113.0, 835.0, 0.0)
(1126.0, 835.0, 0.0)
Basically on one frame the x position is one value, a couple of frames later it's changed, but in the middle there is no gradation. While my mouse movement was continuous, if I'm to believe Unity, in the example above I hovered on 1 pixel for 3 frames then jumped 13 pixels to the right in one frame. Why is this? Is there any code to get the actual frame by frame position of the mouse?
EDIT:
Vector2 _lastPosition;
StreamWriter _mouseData;
// Start is called before the first frame update
void Start()
{
_mouseData = new StreamWriter(File.Open("sdata.txt", FileMode.Create));
}
// Update is called once per frame
void FixedUpdate()
{
_mouseData.WriteLine(Input.mousePosition.ToString());
if (Input.GetMouseButtonDown(0))
{
_mouseData.WriteLine("CLICK\n\n");
}
_lastPosition = Input.mousePosition;
}
void OnDestroy()
{
_mouseData.Close();
}
EDIT 2:
I changed the code to the following:
void FixedUpdate()
{
_mouseData.WriteLine(Vector2.SqrMagnitude(new Vector2(Input.GetAxis("Mouse X"), Input.GetAxis("Mouse Y"))));
if (Input.GetMouseButtonDown(0))
{
_mouseData.WriteLine("CLICK\n\n");
}
}
Now I'm still getting output that's 50% 0-es and non-0 values are sprinkled in on every second row. Exceptions: a few rows where actual values are supposed to be still contain random 0-es. Now, I'm not super concerned about getting less frequent than 1/frame data, but there's no way to distinguish between these false 0-es and actual 0-es when the mouse is not moving, which is an issue.
I cannot find out from your question but I am guessing that you use the FixedUpdate() method which is unreliable in this situation. Update() is advised to use for calls that you want to execute once per frame.
EDIT:
Also, note that it is recommended that you set your application's framerate to a realistic number since it is unlimited by default (on desktop) and your app could be running with so many FPS that it is faster than how often you can sample your mouse input.
You can set the framerate using: Application.targetFrameRate = 60;
Aside from this problem it is generally a good idea to set your framerate to save yourself some headaches. (This is specifically true if you develop for mobile platforms and test on your desktop.)
First of all, I'm quite noob with animator and animation systems in unity.
What I'm trying to achieve (and I was trying with Animator component) is a randomized attack only while I keep my mouse button pressed on the enemy and which completes the execution of the attack clip that is playing even if i release the button meanwhile.
I tried adding my 2 attack animations to a list and play it, with something like
anim.Play(Random.Range(0, list.Count))
...but I don'tknow if the problem is that while I keep pressed one animation cancels the other or what.
Therefore I prefer to ask, because I'm probably doing things in the wrong way.
Thank you.
Yes the issue is probably what you said: You have to wait until one Animation finished before starting a new one otherwise you would start a new animation every frame.
You could use a Coroutine (also check the API) to do that.
Ofcourse the same thing could be implemented also only in Update without using a Coroutine but most of the times that becomes really cluttered and sometimes even more complicated to handle. And there is not really any loss or gain (regarding performance) in simply "exporting" it to a Coroutine.
// Reference those in the Inspector or get them elsewhere
public List<AnimationClip> Clips;
public AnimationClip Idle;
private Animator _anim;
// A flag to make sure you can never start the Coroutine multiple times
private bool _isAnimating;
private void Awake()
{
_anim = GetComponent<Animator>();
}
private void Update()
{
if(Input.GetMouseButtonDown(0)
{
// To make sure there is only one routine running
if(!_isAnimating)
{
StartCoroutine(RandomAnimations());
}
}
// This would immediately interrupt the animations when mouse is not pressed anymore
// uncomment if you prefer this otherwise the Coroutine waits for the last animation to finish
// and returns to Idle state afterwards
//else if(Input.GetMouseButtonUp(0))
//{
// // Interrupts the coroutine
// StopCoroutine (RandomAnimations());
//
// // and returns to Idle state
// _anim.Play(Idle.name);
//
// // Reset flag
// _isAnimating = false;
//}
}
private IEnumerator RandomAnimations()
{
// Set flag to prevent another start of this routine
_isAnimating = true;
// Go on picking clips while mouse stays pressed
while(Input.GetMouseButton(0))
{
// Pick random clip from list
var randClip = Clips[Random.Range(0, Clips.Count)];
// Switch to the random clip
_anim.Play(randClip.name);
// Wait until clip finished before picking next one
yield return new WaitForSeconds(randClip.length);
}
// Even if MouseButton not pressed anymore waits until the last animation finished
// then returns to the Idle state
// If you rather want to interrupt immediately if the button is released
// skip this and uncomment the else part in Update
_anim.Play(Idle.name);
// Reset flag
_isAnimating = false;
}
Note that this way of randomizing does not provide things like "Don't play the same animation twice in a row" or "Play all animations before repeating one".
If you want this checkout this answer to a very similar question. There I used a randomized list to run through so there are no doubles
I'm struggling to find an elegant solution to what seems to be a fairly simple problem.
In Update() some code executes in state1, and when if (Input.GetMouseButtonDown(0)) is clicked, some more code is executed and the state is changed to state2.
However, state2 also has a if (Input.GetMouseButtonDown(0)) which is being triggered by the original if (Input.GetMouseButtonDown(0)) from state1 as the change happens quickly, as Input.GetMouseButtonDown(0) can return true for several frames.
I've tried using booleans, and using Time.deltaTime() but I'm having issues with ensuring that the first mouse click doesn't trigger the second.
If anyone has any ideas or suggestions, I'd be very grateful.
Thanks
I think what you're looking for is edge detection. Essentially you check a condition for a change in state. While the boolean is the same as the last time you asked it you don't do anything but once it changes you execute some code.
//global variable
bool lastState = false;
//inside a method
bool newState = Input.GetMouseButtonDown(0);
if(newState != lastState)
{
lastState = newState;
//do stuff
}
I have a grid-based game in which I programmed my movement script to move my game objects cell by cell. To achieve the cell by cell movement that I want, I had to use coroutines.
Here's a pseudo-code snippet of my code:
private Coroutine currentCoroutine;
public void Move(Vector3 velocity)
{
currentCoroutine = StartCoroutine(MoveCoroutine(velocity));
}
private IEnumerator MoveCoroutine(Vector3 velocity)
{
Vector3 endPoint;
Vector3 nextVelocity;
if(velocity == Vector3.Right)
{
endPoint = rightEndPoint;
// object should move left in the next coroutine
nextVelocity = Vector3.Left;
}
else
{
endPoint = leftEndPoint;
// object should move right in the next coroutine
nextVelocity = Vector3.Right;
}
while(currentPos != endPoint)
{
currentPos += velocity
yield return new WaitForSeconds(movementDelay);
}
currentCoroutine = StartCoroutine(MoveCoroutine(nextVelocity));
}
Basically what this does is move my object left and right. If it reaches the left edge already, I make it go right and vice-versa. I call the Move() from another script.
This code is working fine for me. However, I am not sure if starting a new coroutine inside a coroutine is safe, like what I did here. Are there any consequences when writing coroutines like this? I'm still getting used to the concept of coroutines.
Thanks
Starting a new coroutine at the end of a coroutine is safe. By the way, I noticed that your yield statement is only called inside a while loop. If there's any chance the while loop won't run at least once, you'll have an error; all coroutines must execute a yield statement.