Forum rules - please read before posting.

Mobile: "Aftertouch" being read as a dragging cursor

edited January 2020 in Technical Q&A

Hi all! I think this is one for Chris.
I am trying to work with something that I don't know if it's a bug or expected behaviour.
I am working on a mobile game. Both my hotspot and interaction menu have an appear type of: on interaction.
My interaction menu (a 9-verb box) pops up in the center of the screen when a hotspot receives a single tap. The hotspot label pops up as well in the lower center of the screen. This is my desired behaviour.
Problem is, no matter how quick you remove your finger, it is interpreted as a finger dragging / retaining the cursor "in place" and the hotspot label updates as well. So if during those miliseconds, the spot you touched happens to have one of the 9 verbs now, the hotspot label will update as if you were actively hovering over / dragging your finger on that verb when you didn't intend to.

Example.

Let's say there's a flower. You touch it and you should get the pop up menu with 9 available interactions and a hotspot label reading "flower".
So you get the interaction menu, but it just so happens the verb "push" is at that same spot you touched. So as soon as you touch the flower, you get the interaction menu plus the label: "Push flower", which is not desired behaviour.

This is weird because I have Touch screen as the input method and "Moving touch drags cursor" is disabled (I actually don't see a difference at all when enabling / disabling this, other than my inventory not opening properly when drag is enabled).
I think if I could actually disable the drag cursor completely, this will be resolved. So basically have all touch be read as a single tap.

In the meantime my workaround is to reposition my interaction menu so that it pops up in the lower section of the screen, where I don't have hotspots. But I would really like to solve this so that my menu can pop up in the center without any interference.

Any ideas?

Thanks in advance!!!

Comments

  • Hi, same problem to me with the AC menu. When i click on Language it change when touch and change when my finger left the screen.

    My solution is:
    Try to switch in INPUT METHOD: Mouse and keyboard (and not TouchScreen). On iOS and Android it solve the question of aftertouch but if anyone have a best help, please, write below.

  • Thank you, @voceradio. Unfortunately, switching to Mouse and keyboard in an android build adds undesirable behaviour for me. So I'm hoping there is a way to register single taps as such.

  • I'm unclear about exactly what's going on. Does the Hotspot label only update when you begin dragging, or does it update just by virtue of the touch being held when the Interaction menu appears?

    Is your Interaction menu rendered by AC, or Unity UI? AC menus will react to the cursor position at all times. I'm not sure about how Unity UI works in such a situation.

    The cursor position, in touch-screen builds, can be overridden through use of the InputTouchPositionDelegate - see the Manual's "Remapping inputs" chapter for more.

    It should be possible, through use of both this and the OnHotspotSelect event, to reset the cursor position when a Hotspot is selected, and keep it reset until the touch is release.

    Something like this:
    https://paste.ofcode.org/uHPfZ7GFgs8XijtLJ6SXiK

  • edited January 2020

    Does the Hotspot label only update when you begin dragging, or does it update just by virtue of the touch being held when the Interaction menu appears?

    I don't have to drag. It happens if I tap as well. It's like taps are being interpreted as tapping and "holding" (dragging). Since dragging behaves like hovering a mouse over the interaction buttons, it updates the label if coincidentally the cursor is there. Mind you, even though "Moving touch drags cursor" is turned off, I can drag my cursor around during gameplay as well. It would be great to disable it altogether.

    Is your Interaction menu rendered by AC, or Unity UI? AC menus will react to the cursor position at all times. I'm not sure about how Unity UI works in such a situation.

    Unity UI.

    It should be possible, through use of both this and the OnHotspotSelect event, to reset the cursor position when a Hotspot is selected, and keep it reset until the touch is release.

    Something like this:
    https://paste.ofcode.org/uHPfZ7GFgs8XijtLJ6SXiK

    I just added this script and tested on android. I'll try to explain exactly what happens.

    1) When I tap the hotspot, I get the interaction menu as intended, and the hotspot label appears how it's supposed to (going back to my example: you tap the flower, you get the menu + the label "flower").

    However:

    2) When I tap the interaction I want, I don't get the hotspot + interaction label ("Pick up flower") --I even have a script to delay it for a second and it's not showing.
    3) More importantly, once you tap on a hotspot, the game becomes mostly unresponsive to taps whether you're tapping a hotspot or something else:

    If I tapped on "talk to man", it opens the conversation, but then doesn't respond to taps.
    Same thing with other interactions, I can just tap one, it follows through, but then it ignores further taps.
    I can tap the menu button, but then it does not respond when tapping options.
    I can tap the inventory button, it responds, but shows the label for the last hotspot instead of current inventory item.

  • Ah - my mistake. The InputTouchPositionDelegate is only called if the Touch Count is non-zero.

    Try this:
    https://paste.ofcode.org/37shJzKrn3qaECL6qpBXyhU

  • Amazing, Chris. Tested it on android and it works. Thanks a lot!

  • Hi Chris, I'm just bumping this from the other thread. This is the solution that somehow is no longer working after fixing the other issue with combining items

    (You advised me to create an eventsystem prefab with a OptionalMouseInputModule component)

    After I did that, the cursor does not reset position after touching hotspot.

    Between the two issues this is minor, because it does not affect gameplay, but probably you have an idea as to why this happens?

    Thanks in advance!!

  • I'm getting a 404 on the code I wrote above. I don't have a backup of it - can you share it below?

  • Also, what is the result of using the following instead of OptionalMouseInputModule:

    https://paste.ofcode.org/325S6xeUbcNfL6x9Z8cdbpQ

  • edited March 2020

    Sorry Chris, I got tied up with work this past week and didn't get a chance to go back to this. (Now it looks like with the coronavirus situation I will be out of work for a while, so more time for my game!). I wanted to try the code but it's no longer available. Do you still have it? I will also post the code you were asking about. Thanks a lot!!!

  • Sorry, I didn't realise those would expire so soon.

    OK, first one:

    using UnityEngine;
    using UnityEngine.EventSystems;
    namespace AC
    {
    
        public class CustomTouchInputModule : StandaloneInputModule
        {
    
            public override void Process ()
            {
                if (!eventSystem.isFocused)
                    return;
    
                bool usedEvent = SendUpdateEventToSelectedObject ();
    
                ProcessTouchEvents ();
    
                if (eventSystem.sendNavigationEvents)
                {
                    if (!usedEvent)
                        usedEvent |= SendMoveEventToSelectedObject ();
    
                    if (!usedEvent)
                        SendSubmitEventToSelectedObject ();
                }
            }
    
    
            private bool ProcessTouchEvents ()
            {
                for (int i = 0; i < input.touchCount; ++i)
                {
                    Touch touch = input.GetTouch (i);
    
                    if (touch.type == TouchType.Indirect)
                        continue;
    
                    bool released;
                    bool pressed;
                    var pointer = GetTouchPointerEventData (touch, out pressed, out released);
    
                    ProcessTouchPress (pointer, pressed, released);
    
                    if (!released)
                    {
                        ProcessMove (pointer);
                        ProcessDrag (pointer);
                    }
                    else
                        RemovePointerData (pointer);
                }
                return input.touchCount > 0;
            }
    
    
    
            protected new PointerEventData GetTouchPointerEventData (Touch input, out bool pressed, out bool released)
            {
                PointerEventData pointerData;
                var created = GetPointerData (input.fingerId, out pointerData, true);
    
                pointerData.Reset ();
    
                pressed = created || (input.phase == TouchPhase.Began);
                released = (input.phase == TouchPhase.Canceled) || (input.phase == TouchPhase.Ended);
    
                Vector2 inputPosition = KickStarter.playerInput.GetMousePosition ();
    
                if (created)
                    pointerData.position = inputPosition;
    
                if (pressed)
                    pointerData.delta = Vector2.zero;
                else
                    pointerData.delta = inputPosition - pointerData.position;
    
                pointerData.position = inputPosition;
    
                pointerData.button = PointerEventData.InputButton.Left;
    
                if (input.phase == TouchPhase.Canceled)
                {
                    pointerData.pointerCurrentRaycast = new RaycastResult ();
                }
                else
                {
                    eventSystem.RaycastAll (pointerData, m_RaycastResultCache);
    
                    var raycast = FindFirstRaycast (m_RaycastResultCache);
                    pointerData.pointerCurrentRaycast = raycast;
                    m_RaycastResultCache.Clear ();
                }
                return pointerData;
            }
    
        }
    
    }
    
  • Second one:

    using UnityEngine.EventSystems;
    using UnityEngine;
    
    namespace AC
    {
    
        public class CustomTouchInputModule3 : StandaloneInputModule
        {
    
            private bool allowMouseInput = true;
            private readonly MouseState m_MouseState = new MouseState ();
    
    
            public bool AllowMouseInput
            {
                get
                {
                    return allowMouseInput;
                }
                set
                {
                    allowMouseInput = value;
                }
            }
    
    
            protected void Update ()
            {
                AllowMouseInput = !CanDirectlyControlMenus ();
            }
    
    
            protected virtual bool CanDirectlyControlMenus ()
            {
                if (KickStarter.settingsManager != null && KickStarter.settingsManager.inputMethod == InputMethod.TouchScreen)
                {
                    return false;
                }
    
                if ((KickStarter.stateHandler.gameState == GameState.Paused && KickStarter.menuManager.keyboardControlWhenPaused) ||
                    (KickStarter.stateHandler.gameState == GameState.DialogOptions && KickStarter.menuManager.keyboardControlWhenDialogOptions) ||
                    (KickStarter.stateHandler.IsInGameplay () && KickStarter.playerInput.canKeyboardControlMenusDuringGameplay))
                {
                    return true;
                }
                return false;
            }
    
    
            protected override MouseState GetMousePointerEventData (int id = 0)
            {
                if (KickStarter.settingsManager == null || KickStarter.settingsManager.inputMethod == InputMethod.MouseAndKeyboard)
                {
                    return base.GetMousePointerEventData (id);
                }
    
                if (KickStarter.settingsManager.inputMethod == InputMethod.TouchScreen)
                {
                    return GetTouchEventData (id);
                }
    
                PointerEventData leftData;
                var created = GetPointerData (kMouseLeftId, out leftData, true);
    
                leftData.Reset ();
    
                Vector2 pos = KickStarter.playerInput.GetMousePosition ();
                if (created)
                {
                    leftData.position = pos;
                }
    
                leftData.delta = pos - leftData.position;
                leftData.position = pos;
                leftData.scrollDelta = Input.mouseScrollDelta;
                leftData.button = PointerEventData.InputButton.Left;
                eventSystem.RaycastAll (leftData, m_RaycastResultCache);
                RaycastResult raycast = FindFirstRaycast (m_RaycastResultCache);
                leftData.pointerCurrentRaycast = raycast;
                m_RaycastResultCache.Clear ();
    
                if (raycast.isValid && KickStarter.menuManager.autoSelectValidRaycasts && !CanDirectlyControlMenus ())
                {
                    KickStarter.playerMenus.EventSystem.SetSelectedGameObject (raycast.gameObject);
                }
    
                PointerEventData rightData;
                GetPointerData (kMouseRightId, out rightData, true);
                CopyFromTo (leftData, rightData);
                rightData.button = PointerEventData.InputButton.Right;
    
                PointerEventData middleData;
                GetPointerData (kMouseMiddleId, out middleData, true);
                CopyFromTo (leftData, middleData);
                middleData.button = PointerEventData.InputButton.Middle;
    
                PointerEventData.FramePressState leftClickState = PointerEventData.FramePressState.NotChanged;
                if (KickStarter.settingsManager.inputMethod == InputMethod.TouchScreen)
                {
                    if (Input.touchCount == 1)
                    {
                        TouchPhase phase = Input.GetTouch (0).phase;
                        switch (phase)
                        {
                            case TouchPhase.Began:
                                leftClickState = PointerEventData.FramePressState.Pressed;
                                break;
    
                            case TouchPhase.Canceled:
                                leftClickState = PointerEventData.FramePressState.Released;
                                break;
    
                            case TouchPhase.Ended:
                                leftClickState = PointerEventData.FramePressState.PressedAndReleased;
                                break;
    
                            default:
    
                                break;
                        }
                    }
                }
                else
                {
                    if (KickStarter.playerInput.InputGetButtonDown ("InteractionA"))
                    {
                        leftClickState = PointerEventData.FramePressState.Pressed;
                    }
                    else if (KickStarter.playerInput.InputGetButtonUp ("InteractionA"))
                    {
                        leftClickState = PointerEventData.FramePressState.Released;
                    }
                }
    
                PointerEventData.FramePressState rightClickState = PointerEventData.FramePressState.NotChanged;
                if (KickStarter.playerInput.InputGetButtonDown ("InteractionB"))
                {
                    rightClickState = PointerEventData.FramePressState.Pressed;
                }
                else if (KickStarter.playerInput.InputGetButtonUp ("InteractionB"))
                {
                    rightClickState = PointerEventData.FramePressState.Released;
                }
    
                m_MouseState.SetButtonState (PointerEventData.InputButton.Left, leftClickState, leftData);
                m_MouseState.SetButtonState (PointerEventData.InputButton.Right, rightClickState, rightData);
                m_MouseState.SetButtonState (PointerEventData.InputButton.Middle, StateForMouseButton (2), middleData);
    
                return m_MouseState;
            }
    
    
            private MouseState GetTouchEventData (int id)
            {
                // Populate the left button...
                PointerEventData leftData;
                var created = GetPointerData (kMouseLeftId, out leftData, true);
    
                leftData.Reset ();
    
                if (created)
                    leftData.position = input.mousePosition;
    
                //Vector2 pos = input.mousePosition;
                Vector2 pos = KickStarter.playerInput.GetMousePosition ();
                if (Cursor.lockState == CursorLockMode.Locked)
                {
                    // We don't want to do ANY cursor-based interaction when the mouse is locked
                    leftData.position = new Vector2 (-1.0f, -1.0f);
                    leftData.delta = Vector2.zero;
                }
                else
                {
                    leftData.delta = pos - leftData.position;
                    leftData.position = pos;
                }
                leftData.scrollDelta = input.mouseScrollDelta;
                leftData.button = PointerEventData.InputButton.Left;
                eventSystem.RaycastAll (leftData, m_RaycastResultCache);
                var raycast = FindFirstRaycast (m_RaycastResultCache);
                leftData.pointerCurrentRaycast = raycast;
                m_RaycastResultCache.Clear ();
    
                // copy the apropriate data into right and middle slots
                PointerEventData rightData;
                GetPointerData (kMouseRightId, out rightData, true);
                CopyFromTo (leftData, rightData);
                rightData.button = PointerEventData.InputButton.Right;
    
                PointerEventData middleData;
                GetPointerData (kMouseMiddleId, out middleData, true);
                CopyFromTo (leftData, middleData);
                middleData.button = PointerEventData.InputButton.Middle;
    
                m_MouseState.SetButtonState (PointerEventData.InputButton.Left, StateForMouseButton (0), leftData);
                m_MouseState.SetButtonState (PointerEventData.InputButton.Right, StateForMouseButton (1), rightData);
                m_MouseState.SetButtonState (PointerEventData.InputButton.Middle, StateForMouseButton (2), middleData);
    
                return m_MouseState;
            }
    
    
            public override void Process ()
            {
                //if (KickStarter.settingsManager.inputMethod == InputMethod.TouchScreen) {base.Process (); return;}
    
                bool usedEvent = SendUpdateEventToSelectedObject ();
    
                if (eventSystem.sendNavigationEvents)
                {
                    if (!usedEvent)
                    {
                        usedEvent |= SendMoveEventToSelectedObject ();
                    }
    
                    if (!usedEvent)
                    {
                        SendSubmitEventToSelectedObject ();
                    }
                }
    
                if (allowMouseInput)
                {
                    ProcessMouseEvent ();
                }
            }
    
        }
    
    }
    

    That said, I'm not 100% sure this is the direction to take. I'll see if it's possible to solve the other thread's issue without resorting to a custom module.

Sign In or Register to comment.

Howdy, Stranger!

It looks like you're new here. If you want to get involved, click one of these buttons!

Welcome to the official forum for Adventure Creator.