Imagine some standard Unity UI ScrollView. You grab it with your mouse and start dragging it so it is scrolling with your mouse. How do I programmatically make it stop scrolling while mouse button is still down? Is there any way to make ScrollRect think that user is released mouse button?
Quite old question, but I found exactly this same problem and no clean solution. My problem was to have scrollRect with items which I can scroll left/right but when user select one item and drag it up then scrollRect should cancel scrolling and allow user to drag item up. I achieved this by temporary setting ScrollRect.enable to false, and after few ms set it back to true.
Here is part of mu code which do this. Maybe somebody find this useful.
void Update()
{
if (state == DragState.Started) {
dragStart = Input.mousePosition;
state = DragState.Continued;
}
if (state == DragState.Continued) {
Vector3 pos = Input.mousePosition;
if (pos.y - dragStart.y > 80) {
scrollRect.enabled = false;
state = DragState.None;
Invoke("ReenableScroll", 0.01f);
}
}
}
private void ReenableScroll() {
scrollRect.enabled = true;
}
Its not easy to make the EventSystem think something has happend while it hasn't. I believe its much easier to implement your own ScrollRect logic and parse the mouse input yourself
You can call ScrollRect's OnEndDrag() method with artificial PointerEventData.
var forceEndDragData = new PointerEventData(EventSystem.current)
{
button = PointerEventData.InputButton.Left
};
_scrollRect.OnEndDrag(forceEndDragData);
Related
I wanted my UI to not resize when user is still resizing the game (holding click in the window border) and only when the user has released the mouse the resize event will trigger.
I have tried to achieve it on Unity but so far I only able to detect windows size change, which my script checked every 0.5 second and if detected change it will resize the UI. But of course resizing everything caused a heavy lag, so resizing every 0.5 second is not a good option but resizing every 1 second is not a good idea either because 1 second is considered too long.
The question might be too broad but I have specified the problem as small as possible, how do I detect if user is still resizing the window? And how do I detect if user has stopped resizing the window (stop holding click at window border)?
You can't tell when someone stops dragging a window, unless you want to code a low level solution for ever desktop environment and every operating system.
Here's what worked for me with any MonoBehavior class, using the OnRectTransformDimensionsChange event:
public class StackOverflow : MonoBehaviour
{
private const float TimeBetweenScreenChangeCalculations = 0.5f;
private float _lastScreenChangeCalculationTime = 0;
private void Awake()
{
_lastScreenChangeCalculationTime = Time.time;
}
private void OnRectTransformDimensionsChange()
{
if (Time.time - _lastScreenChangeCalculationTime < TimeBetweenScreenChangeCalculations)
return;
_lastScreenChangeCalculationTime = Time.time;
Debug.Log($"Window dimensions changed to {Screen.width}x{Screen.height}");
}
}
I have some good news - sort of.
When the user resizes the window on Mac or PC,
Unity will AUTOMATICALLY re-layout everything.
BUT in fact ONLY when the user is "finished" resizing the Mac/PC window.
I believe that is what the OP is asking for - so the good news, what the OP is asking for is quite automatic.
However. A huge problem in Unity is Unity does not smoothly resize elements as the user is dragging the mouse to expand the Mac/PC window.
I have never found a solution to that problem. (A poor solution often mentioned is to check the size of the window every frame; that seems to be about the only approach.)
Again interestingly, what the OP mentions
" ..and if window has stopped resizing .."
is automatically done in Unity; in fact do nothing to achieve that.
I needed something like this for re generating a line chart, but as it has too many elements, it would be heavy to do it on every update, so I came up with this, which for me worked well:
public class ToRunOnResize : MonoBehaviour
{
private float screenWidth;
private bool screenStartedResizing = false;
private int updateCounter = 0;
private int numberOfUpdatesToRunXFunction = 15; // The number of frames you want your function to run after, (usually 60 per second, so 15 would be .25 seconds)
void Start()
{
screenWidth = Screen.width; // Identifies the screen width
}
private void Update()
{
if (Screen.width != screenWidth) // This will be run and repeated while you resize your screen
{
updateCounter = 0; // This will set 0 on every update, so the counter is reset until you release the resizing.
screenStartedResizing = true; // This lets the application know when to start counting the # of updates after you stopped resizing.
screenWidth = Screen.width;
}
if (screenStartedResizing)
{
updateCounter += 1; // This will count the updates until it gets to the numberOfUpdatesToRunXFunction
}
if (updateCounter == numberOfUpdatesToRunXFunction && screenStartedResizing)
{ // Finally we make the counter stop and run the code, in my case I use it for re-rendering a line chart.
screenStartedResizing = false;
// my re-rendering code...
// my re-rendering code...
}
}
}
This question already has answers here:
Detect mouse clicked on GUI
(3 answers)
Closed 4 years ago.
Description
Hi Guys need your help I faced problems with mouse clicks which pass through UI panel in Unity, that is, I have created pause menu and when I click Resume button, the game gets unpaused and the player plays Attack animation which is undesirable.What I want is when I click Resume button, Attack animation should not be played. The same problem if I just click on panel not necessarily a button and the more I click on UI panel the more Attack animation is played after I exit pause menu. Moreover, I have searched for solutions to this issue and was suggeted to use event system and event triggers but since my knowledge of Unity is at beginner level I could not properly implement it. Please guys help and sorry for my English if it is not clear)) Here is the code that I use:
The code:
using UnityEngine;
using UnityEngine.EventSystems;
public class PauseMenu : MonoBehaviour {
public static bool IsPaused = false;
public GameObject pauseMenuUI;
public GameObject Player;
private bool state;
private void Update() {
//When Escape button is clicked, the game has to freeze and pause menu has to pop up
if (Input.GetKeyDown(KeyCode.Escape)) {
if (IsPaused) {
Resume();
}
else {
Pause();
}
}
}
//Code for Resume button
public void Resume() {
//I was suggested to use event system but no result Attack animation still plays once I exit pause menu
if (EventSystem.current.IsPointerOverGameObject()) {
Player.GetComponent<Animator>().ResetTrigger("Attack");
}
pauseMenuUI.SetActive(false);
Time.timeScale = 1f;
IsPaused = false;
}
//this method is responsible for freezing the game and showing UI panel
private void Pause() {
pauseMenuUI.SetActive(true);
Time.timeScale = 0f;
IsPaused = true;
}
//The code for Quit button
public void QuitGame() {
Application.Quit();
}
}
Im not sure if i understood your problem, but it sounds like somewhere in your code you start an attack when the player does a left click.
Now your problem is that this code is also executed when the player clicks on a UI element, for example in this case the Resume button?
You tried to fix this problem, by resetting the attack trigger of the animator, i think it would be a better solution to prevent the attack from starting instead of trying to reset it later.
EventSystem.current.IsPointerOverGameObject() returns true if the mouse is over an UI element.
So you can use it to modify your code where you start your attack:
... add this check in your code where you want to start the attack
if(EventSystem.current.IsPointerOverGameObject() == false)
{
// add your code to start your attack
}
...
Now your attack will only start if you are not over a UI element
I'm writing a script to put on a button that will detect a drag direction to move a player
void OnGUI()
{
if (buttonRect.Contains(Event.current.mousePosition))
{
if (Event.current.type == EventType.MouseDown)
{
buttonPressed = true;
}
if (Event.current.type == EventType.MouseUp)
{
buttonPressed = false;
}
}
if (buttonPressed && Event.current.type == EventType.MouseDrag)
{
}
}
If this script was to be placed on a button, how could I get the buttons bounds as a rectangle?
Also, If anyone has a better solution for controling movement by drag I would be open for suggestions.
You can implement your own Visual Joystick by using the Unity new event callback functions such as OnBeginDrag OnDrag and the OnEndDrag function. There is a already made Visual Joystick package out there for Unity, so implementing your own is like reinventing the wheel.
All you have to do is to import the CrossPlatformInputManager package from Unity's UnityStandardAssets then use CrossPlatformInputManager.GetAxis("Horizontal") and CrossPlatformInputManager.GetAxisRaw("Horizontal") to read the direction of the image/thumb.
To make one from scratch, you can flow this video tutorial.
If you want a mobile solution that is really accurate and natural like a physical joystick, take a look at this asset:
Ultra Precise D-Pad Joystick
I'm creating a jetpack (mobile) controller where left joystick is used to control forward and backward movements and right joystick is used to control rotation and upward movements. What I want is player to go upwards whenever user is touching the right joystick even if horizontal && vertical axes both return zero. So if there is a finger on the right joystick player goes up similar to GetButton or GetKey(some keycode).
Hope this helps somebody in the future:
I found out there is OnPointerUp and OnPointerDown methods that can be used to check if joystick is pressed or not. Easiest way for me to use those were to change a few things in Standard Assets > Utility > Joystick.cs. This is how those methods look like after my modifications:
public void OnPointerUp(PointerEventData data)
{
transform.position = m_StartPos;
UpdateVirtualAxes(m_StartPos);
if (data.rawPointerPress.name == "MobileJoystick_right") {
rightJoystickPressed = false;
}
}
public void OnPointerDown(PointerEventData data) {
if (data.pointerEnter.name == "MobileJoystick_right") {
rightJoystickPressed = true;
}
}
So basically I just added the If-statements. Now I can access the rightJoystickPressed boolean from any other script to check if joystick is being pressed even if it is not moved.
I have a mouse listener. It has some code to respond to mouseUp and mouseDown events. This works correctly.
However, as soon as I add a DragSource, my mouseDown event is no longer delivered -- until I release the mouse button!
This is trivial to reproduce - below is a simple program which contains a plain shell with just a mouse listener and a drag listener. When I run this (on a Mac), and I press and hold the mouse button, nothing happens - but as soon as I release the mouse button, I instantly see both the mouse down and mouse up events delivered. If I comment out the drag source, then the mouse events are delivered the way they should be.
I've searched for others with similar problems, and the closest I've found to an explanation is this:
https://bugs.eclipse.org/bugs/show_bug.cgi?id=26605#c16
"If you hook drag detect, the operating system needs to eat mouse events until it determines that you have either dragged or not."
However, I don't understand why that's true -- why must the operating system eat mouse events to determine if I have a drag or not? The drag doesn't start until I have a mouse -move- event with the button pressed.
More importantly: Can anyone suggest a workaround? (I tried dynamically adding and removing my drag source when the mouse is pressed, but then I couldn't get drag & drop to function properly since it never saw the initial key press - and I can't find a way to programmatically initiate a drag.)
Here's the sample program:
package swttest;
import org.eclipse.swt.dnd.DND;
import org.eclipse.swt.dnd.DragSource;
import org.eclipse.swt.dnd.DragSourceEvent;
import org.eclipse.swt.dnd.DragSourceListener;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseListener;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
public class SwtTest {
public static void main(String[] args) {
final Display display = new Display();
final Shell shell = new Shell(display);
shell.addMouseListener(new MouseListener() {
public void mouseUp(MouseEvent e) {
System.out.println("mouseUp");
}
public void mouseDown(MouseEvent e) {
System.out.println("mouseDown");
}
public void mouseDoubleClick(MouseEvent e) {
System.out.println("mouseDoubleClick");
}
});
DragSourceListener dragListener = new DragSourceListener() {
public void dragFinished(DragSourceEvent event) {
System.out.println("dragFinished");
}
public void dragSetData(DragSourceEvent event) {
System.out.println("dragSetData");
}
public void dragStart(DragSourceEvent event) {
System.out.println("dragStart");
}
};
DragSource dragSource = new DragSource(shell, DND.DROP_COPY | DND.DROP_MOVE);
dragSource.addDragListener(dragListener);
shell.pack();
shell.open();
while (!shell.isDisposed()) {
if (!display.readAndDispatch())
display.sleep();
}
display.dispose();
}
}
To answer your specific question about why this happens -- on Cocoa we don't consider a drag to have started until the mouse has moved a few pixels. This ensures against 'accidental' drags if you're sloppy with the clicks. On Linux and Win32 the window toolkit can do the drag detection. If you just hold down the button the detection times out and the mouse down is delivered. On Cocoa we have no time out, which is why nothing happens until the drag is detected or a mouse up happens.
That's a lot of detail, but the conclusion is that the behavior is inconsistent, and we should always be able to deliver the mouse down immediately, without waiting for the drag detection to complete.
I don't see a workaround, since this is happening before the Control sees the event.
See this bug which has patches for win32, gtk and cocoa SWT.
I had faced the same problem and found a solution. Once you attach a DragSource to your custom widget, the event loop will be blocked in that widget's mouse down hook and will eat mouse move events to detect a drag. (I've only looked into the GTK code of SWT to find this out, so it may work a little differently on other platforms, but my solution works on GTK, Win32 and Cocoa.) In my situation, I wasn't so much interested in detecting the mouse down event right when it happened, but I was interested in significantly reducing the drag detection delay, since the whole purpose of my Canvas implementation was for the user to drag stuff. To turn off the event loop blocking and built-in drag detection, all you have to do is:
setDragDetect(false);
In my code, I am doing this before attaching the DragSource. As you already pointed out, this will leave you with the problem that you can't initiate a drag anymore. But I have found a solution for that as well. Luckily, the drag event generation is pure Java and not platform specific in SWT (only the drag detection is). So you can just generate your own DragDetect event at a time when it is convenient for you. I have attached a MouseMoveListener to my Canvas, and it stores the last mouse position, the accumulated drag distance and whether or not it already generated a DragDetect event (among other useful things). This is the mouseMove() implementation:
public void mouseMove(MouseEvent e) {
if (/* some condition that tell you are expecting a drag*/) {
int deltaX = fLastMouseX - e.x;
int deltaY = fLastMouseY - e.y;
fDragDistance += deltaX * deltaX + deltaY * deltaY;
if (!fDragEventGenerated && fDragDistance > 3) {
fDragEventGenerated = true;
// Create drag event and notify listeners.
Event event = new Event();
event.type = SWT.DragDetect;
event.display = getDisplay();
event.widget = /* your Canvas class */.this;
event.button = e.button;
event.stateMask = e.stateMask;
event.time = e.time;
event.x = e.x;
event.y = e.y;
if ((getStyle() & SWT.MIRRORED) != 0)
event.x = getBounds().width - event.x;
notifyListeners(SWT.DragDetect, event);
}
}
fLastMouseX = e.x;
fLastMouseY = e.y;
}
And that will replace the built-in, blocking drag detection for you.