Integrating the Inventory System into a Mobile game

Several students have tried to integrate our Inventory system into mobile games and found that the drag and drop interface simply didn’t work. You could drag the item but when you released, the item would dissappear.

I’ve been promising to look into this for a while, and after hours of experimentation and pouring through logcats, I believe I have found the issue and have a solution. The solution didn’t present itself until I realized that all button presses in the game were not blocking clicks into the game itself. In otherwords, EventSystem.current.IsPointerOverGameObject() was always returning false…

When dealing with the UI in general, in a mobile game, you have to supply EventSystem.current.IsPointerOverGameObject() with the handle of the touch that you’re checking. If you give it no parameter, it assumes you mean the mouse cursor, which of course does not exist in a mobile game. Events like GetMouseButtonDown() and GetMousebuttonUp() are simulated in mobile, but the Event System doesn’t convert this information for IsPointerOverGameObject().

So we’ll start with the solution for buttons and UI in general… in PlayerController.cs, in the method InteractWithUI()

        private bool InteractWithUI()
        {
            if (Input.GetMouseButtonUp(0))
            {
                isDraggingUI = false;
            }
            if (EventSystem.current.IsPointerOverGameObject())
            {
                if (Input.GetMouseButtonDown(0))
                {
                    isDraggingUI = true;
                }
                SetCursor(CursorType.UI);
                return true;
            }
            if (isDraggingUI)
            {
                return true;
            }
            return false;
        }

We’re going to leave most of this alone. Let’s let Unity simulate the MouseButtonUp and MouseButtonDown for now. It’s the check for EventSystem that we’re interested in.

#if UNITY_ANDROID
            if(Input.touchCount>0 && EventSystem.current.IsPointerOverGameObject(Input.touches[0].fingerId))
#elif UNITY_IOS
            if(Input.touchCount>0 && EventSystem.current.IsPointerOverGameObject(Input.touches[0].fingerId))
#else
            if (EventSystem.current.IsPointerOverGameObject())
#endif

What this does is instruct the compiler to use a different if statement depending on the device being targetted. Note: Neither UNITY_ANDROID or UNITY_IOS will be valid compiler tags if their respective modules are not installed in your version of Unity! For the remainder of this lesson, I will only be using the UNITY_ANDROID tag, but the above reference demonstrats the use of both.
Unfortunately, there is no UNITY_MOBILE directive, so we have to do a separate line for each directive.

On to our Inventory system… In my game which I just converted to Android, dragging an inventory item from Inventory to anywhere causes the item to dissappear. Not only that, but it drops some cryptic hard to track down null reference exceptions in the LogCat which would crash your game if not in developer mode…

Scouring through the DragItem.CS, I noticed another IsPointerOverGameObject reference in OnEndDrag

if(!EventSystem.current.IsPointerOverGameObject())
{
     container = parentCanvas.GetComponent<IDragDestination<T>>();
}
else
{
     container = GetContainer(eventData);
}

My first attempt was to simply use the same fix I used in PlayerController, but this time, it did not work, I was still getting a null reference. It turns out that when the EndDrag has happened, there are no touches in Input.touches… I got more null references…

My final solution was to make a bool isOverGameObject; and make a modification to OnDrag

        void IDragHandler.OnDrag(PointerEventData eventData)
        {
            transform.position = eventData.position;
#if UNITY_ANDROID
            isOverGameObject = EventSystem.current.IsPointerOverGameObject(Input.touches[0].fingerId);
#endif
        }

This is so we can use this bool in the EndDrag (the frame immediately before EndDrag will have isOverGameObject set properly)

#if UNITY_ANDROID
            if(!isOverGameObject)
#else
            if (!EventSystem.current.IsPointerOverGameObject())
#endif

Now, when you drag and drop, OnDrag should differentiate between being over a UI element or not.

6 Likes

Awesome post Brian!

i couldnt get last part of it by end drag ?just writing 2 if below each other?can you please write whole end drag script please ?

The compiler directives (#if UNITY_ANDROID, #else, and #endif) make it so that the resulting code will only really consider one of the two if statements.

Here is the completed OnEndDrag method:

        void IEndDragHandler.OnEndDrag(PointerEventData eventData)
        {
            transform.position = startPosition;
            GetComponent<CanvasGroup>().blocksRaycasts = true;
            transform.SetParent(originalParent, true);

            IDragDestination<T> container;
#if UNITY_EDITOR
           ///this change was added to accomodate playing in the editor while the Build settings are set to Android.
            if(!isOverGameObject)
#elif UNITY_ANDROID
            if(!isOverGameObject)
#else
                if (!EventSystem.current.IsPointerOverGameObject())
#endif
            {
                container = parentCanvas.GetComponent<IDragDestination<T>>();
            }
            else
            {
                container = GetContainer(eventData);
            }

            if (container != null)
            {
                DropItemIntoContainer(container);
            }
        }

thanks alot brian you are the best ,yesterday i did the same but i put else in wrong place i will try yours as soon as im home ,hope it works :slight_smile:

I tried using this solution on mobile (android) with the new input system from Unity and it did not work for some reason. The problem I encountered was that the container was returning null when GetContainer was called on OnEndDrag.

When I called GetContainer during OnDrag, the container was returning the actual IDestination so i moved the call populate the container to OnDrag and everything finally worked.

This solution is not geared for the new input system. I was able to use this solution with the new input system only by allowing both the new and old input systems to work (in Build Settings).

I understand and I appreciate your post because it helped me find the solution to my problem.

I posted to provide some information to other who may have the same problem.

1 Like

Hi Brian. Thanks again for this. You should also tag it with tips-and-tricks.

I’m curious why you added UNITY_EDITOR there. It seems to make no difference since it executes the same exact code. This is what I had to do (in PlayerController.cs) but I imagine I would do the same in OnEndDrag. When it’s in the Unity Editor (regardless of build setting), my assumption is the player has a mouse and keyboard and we’d want to make sure the original code executes.

I’m curious if I am not considering some use case or if you have typo.

        private bool InteractWithUI()
        {
            if (Input.GetMouseButtonUp(0))
            {
                isDraggingUI = false;
            }
#if UNITY_ANDROID && !UNITY_EDITOR
            if(Input.touchCount>0 && EventSystem.current.IsPointerOverGameObject(Input.touches[0].fingerId))
#elif UNITY_IOS && !UNITY_EDITOR
            if(Input.touchCount>0 && EventSystem.current.IsPointerOverGameObject(Input.touches[0].fingerId))
#else
            if (EventSystem.current.IsPointerOverGameObject())
#endif
            // rest of code continues.
        }

This is correct. Within the editor, unless you are using the Device Manager (which wasn’t a viable thing when I wrote this post!) and have the new Input system with settings for the mouse to simulate touch (also, not a viable thing when I wrote this post), simply using #if UNITY_ANDROID wouldn’t work because touch didn’t work within the Editor (still doesn’t, but it can be simulated in the Device Manager).

The problem with the original code was that it was performing flawlessly in the Editor but completely failing when the game was built into an APK. My initial attempts to fix the system were stymied by the fact that once I put in the changes (before checking for UNITY_EDITOR, it no longer worked in the editor but would work in the APK.

Thanks Brian I will try an end to end build with my changes and see if I have any misbehavior. The game builds so slowly in cloud build so the charges rack up quickly. I try and limit my number of ios builds. I unfortunately don’t have an android device (besides the Amazon Fire tablet which fails on the most basic Android build). I have to imagine a preprocessor directive with two conditions should do the trick.

I noticed you made mention of the new input system just now but your recent post but earlier you said this (below). Is the big change the presence of Device Manager? I’ve really tried to avoid the new input system for the time being.

The new input system allows a remapping of Mouse pointer activity to simulate touches within an Input Debugger. This functionality wasn’t in the original input system. What this meant was that IsPointerOverGameObject() was returning false on the #if UNITY_ANDROID blocks within the Editor itself.

#ReasonsIDontDevelopForIOS

1 Like

Yeah. They don’t make it easy.

In my case, the ios product adoption is too high for me to ignore and the Amazon Tablets don’t run Unity (despite major press and signs of collaboration years ago). I rented a mac mini for 3 months so let’s see how that goes.

I don’t own any iOS devices to test games on, let alone a Mac… Note that as technology advances, older Macs quit being able to run the software to build an iOS game. It’s not horrible, but I once had an idea of buying a 6 year old used Mac, and somebody pointed out that the specs wouldn’t handle building to iOS, thankfully before I bought it.

100% familiar with this problem. For folks watching this thread you want to look up the version of XCode needed to build for iOS. Then lookup the MacOS version that is needed to run that version of XCode. Then lookup what Apple products still run that OS. You want to make sure you are at least one generation ahead of the oldest generation since the oldest generation can get wiped off the supported list tomorrow. In practice this means going with no more than ~4 years old. I rented a Mac Mini M1 (2020).

Also note that Unity seems to builds slowly on all but the latest mac hardware.

Privacy & Terms