Closed, but will come back.

Hi everyone,

As you may have noticed, it’s been a while since my last tutorial and it will be some time before I start writing again.
Why this? well, because of major life changes.

In january I was facing 7-8 interview processes simultaneously AND preparing my portfolio for GDC (where I intended to look for further opportunities). Of these interview processes, 3 ended up with a success, the last of which the day before taking my plane to GDC. I had the luxury to choose between an ocean simulator in Norway, with an office near the polar circle and the mighty fjords, a remote job with a team of friendly fellow italians and … wait for it … CD PROJEKT RED!!!

Of course I’m in the mighty fjords, right?
No, oddly enough I didn’t go crazy for the shock and was able to accept the work with what is in my (still not contractually obliged and uninformed) opinion THE BEST developer in the world right now. I’ll start working there on April 3rd and I’m now in the process of relocating to Warsaw.

As you can understand, my situation is quite hectic right now and I barely had the time to restore this website for the n-th time (advice: never pick cloudatcost as an hosting) and write this post.

I don’t plan to stop writing tutorials, if anything I’ll try to expand on that even further following an opportunity that came in during GDC to have a collaboration with IGDA on the topic of tutorials about design patterns in unity. For now there’s nothing guaranteed, I’ll se where the rabbit hole leads.

So, I apologize for the stop (and for the frequent downtimes of the website), but as you can see, there was a good reason for it.
See you soon!

  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  

Custom drawer in unity3d to pick an enum – event tutorial followup

  • Part 1: basics event system
  • Part 2: event picker drawer for in-editor usage [you are here]
  • Part 3: debugging with platform dependant compilation
  • Part 4: multiple dispatchers
  • Part 5: optimization

For today’s tutorial we’re going to make something that’s really comfortable to use: a dropdown menu to select an event. We’ll do that by creating anEventPickerclass and a custom drawer by estending thePropertyDrawerclass thatUnityEnginekindly gives us.

First, well’need aEventPickerclass. This class will actually hold the data inside the game, and will not in any way define the editor UI. It’s the data for which we’re going to define a Custom Drawer in Unity3d, not the drawer itself.

custom drawer in unity3d
let’s make a custom drawer

The EventPicker class

To function with the Event system we defined in last tutorial we’ll need for it the ability to read which event channel and which specific event have been selected. It also needs to allow us to identify the specific type of enum to which each channel will be associated with, so that later the custom drawer can use this information to draw the appropriate dropdown menu. Therefore we’ll need at least :

  • one field to store the selected channel
  • one field to store the selected event for each channel
  • one property to give access to the actual currently selected event to any class that will actually use the event picker.
using UnityEngine;

[System.Serializable]
public class EventPicker
{
    public const string channelVarName = "channel";
    public eventChannels channel;

    public inGameChannelEvents inGame;
    public menuChannelEvents menu;

    public System.Enum Selected
    {
        get
        {
            switch (channel)
            {
                case eventChannels.menu: return menu;
                case eventChannels.inGame: return inGame;
                default:
                    Debug.LogError("EventPicker was passed an unimplemented channel!!! " + channel.ToString());
                    break;
            }
            return channel;
        }
    }


}

Sadly, this means that each time a channel is added inChannelEnumsclass, it will need to be added here too. Also, since we’re going to need a Drawer, we don’t have the freedom to choose whatever variable names we want to pick since it relies on getting that name as a string.

String-run-fools
A string! Run, you fools!

We could solve this problem with C# reflection but that’s a deep topic that I’d rather address another time. For now, let’s just use a strong naming convention and have the the event-storage variables use the same name as the channel, while we addchannelVarNameto keep thechannelvariable name as a constant string field near the variable whose name it stores, so that whenever we may need to rename it we can just as easily copy-paste that name inside the string.

TheSelectedproperty is just a switch that returns the value of the field described by the content of thechannelvariable. So that we have a fast way to get the selected event from theEventPicker.

The actual Custom Drawer

The Custom Drawer must have this declaration:

[CustomPropertyDrawer(typeof(EventPicker))]
public class EventPickerDrawer : PropertyDrawer
{

So that unity can assign this drawer to the class that it’s intended to draw. It’s also important to understand that we won’t be writing something from scratch here. We’ll actually be extending aPropertyDrawerclass that already exists in unity. I won’t explain in depth what this class is or does, for that there is the documentation, but please notice we’re going to override two methods:OnGUIandGetPropertyHeight.

Let’s begin with the easy part:

public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
    {
        return base.GetPropertyHeight(property, label) * 3f + 5f;
    }

Now, what we’re doing here is basically telling unity to reserve a height equal to three times a normal variable plus an extra 5 pixels. This is because we’re going to put one header and then two dropdowns, and each one is going to use the height of one variable.

public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    {
        EditorGUI.PrefixLabel(position, label);
        Rect tempPos = position;
        tempPos.height = ROWHEIGHT;
        tempPos.y += ROWHEIGHT;
        tempPos.x += position.width * .4f;
        tempPos.width *= .6f;

TheOnGUIfunction actually tells unity what to draw in the inspector when it encounters anEventPickervariable. Here we start by telling it to draw a label which indicates the kind of objetc to which the data belongs and then declaring aRectwith the appropriate size and coordinates to store the rest of the UI elements.

        SerializedProperty channel = property.FindPropertyRelative(EventPicker.channelVarName);
        EditorGUI.PropertyField(tempPos, channel, GUIContent.none);

This bit of code recovers the data about thechannelvariable in theEventPickervia the serialization information (which means you can’t use this on anything that can’t be serialized), then draws new field with that information. Thechannelvariable is identified by its name, that we recover with the static string insideEventPicker.

        tempPos.y += ROWHEIGHT;
        eventChannels chosen = (eventChannels)channel.enumValueIndex;
        SerializedProperty selection = property.FindPropertyRelative(chosen.ToString());
        EditorGUI.PropertyField(tempPos, selection, GUIContent.none);

Here we increment the starting point of theRectso that Unity won’t draw the fields one over the other.

Then we proceed to get the value of the selected channel converted in itsenumform, so that we can then recover the serialization information for the appropriate variable by using theToStringmethod and benefitting from the strong naming convention used in theEventPickerclass.

That’s all folks

So, let’s recap: first you need to create the data container, but in a way that allows you to access all the names of the fields, then we can write another class that must be linked through aCustomPropertyDrawertag to the data class and that overrides thePropertyDrawerfunctions to draw our class the way we want it to.

Now you have a nice UI element that you can use to pick events for your Event System, without having to write it as a horrible string and then converting it. As usual down here there’s all the code in a copy-paste-friendly format. More on this event system is still coming next week. If you want to be sure not losing it, subscribe to my newsletter. For any feedback comments are below or you can just add me on twitter.

using UnityEngine;

[System.Serializable]
public class EventPicker
{
    public const string channelVarName = "channel";
    public eventChannels channel;

    public inGameChannelEvents inGame;
    public menuChannelEvents menu;

    public System.Enum Selected
    {
        get
        {
            switch (channel)
            {
                case eventChannels.menu: return menu;
                case eventChannels.inGame: return inGame;
                default:
                    Debug.LogError("EventPicker was passed an unimplemented channel!!! " + channel.ToString());
                    break;
            }
            return channel;
        }
    }
}

 

using UnityEditor;
using UnityEngine;

[CustomPropertyDrawer(typeof(EventPicker))]
public class EventPickerDrawer : PropertyDrawer
{
    protected virtual float ROWHEIGHT
    {
        get
        { return 18f; }
    }
    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    {
        EditorGUI.PrefixLabel(position, label);
        Rect tempPos = position;
        tempPos.height = ROWHEIGHT;
        tempPos.y += ROWHEIGHT;
        tempPos.x += position.width * .4f;
        tempPos.width *= .6f;

        SerializedProperty channel = property.FindPropertyRelative(EventPicker.channelVarName);
        EditorGUI.PropertyField(tempPos, channel, GUIContent.none);
        tempPos.y += ROWHEIGHT;

        eventChannels chosen = (eventChannels)channel.enumValueIndex;
        SerializedProperty selection = property.FindPropertyRelative(chosen.ToString());
        EditorGUI.PropertyField(tempPos, selection, GUIContent.none);

    }
    public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
    {
        return base.GetPropertyHeight(property, label) * 3f + 5f;
    }
}

 

  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  

Sorting layers in scroll galleries: in-depth tutorial

This tutorial is part of a series on how to construct an awesome-looking scroll gallery, with inertia, snapping, zoom and other stuff. Today we’ll deal with sorting layers in unity 3d.

How does sorting layers in unity work?

Last time we had seen how to set up a scroll gallery, with a nice zoom effect, but in the end the order of images is a fixed thing. Worse than ever, Unity3d in both Screen spaces Overlay and Camera doesn’t look at z-axis to decide what to draw on what, but at the order in hyerarchy; so if we want what’s bigger to also stay on top, we’ll need some trickery.

Let’s see the code!

Specifically well’ need to change our refreshImage function from last tutorial and to use another AnimationCurve:

    public AnimationCurve orderFactor;

With this value:

zig up, zag down - order function for sorting layers in unity
Just keep growing 0-0.5 and set “ping-pong” in the little gear-menu so that you get a constant decrease in 0.5-1 – order function for sorting layers in unity

Now, remember the refreshImage function? It was like this:

    protected virtual void refreshImages()
    {
        foreach (var img in imgset)
        {
            float transformedPosition = positionCurve.Evaluate(alpha + alphaoff[img]);
            img.localPosition = Vector3.right * (transformedPosition * swipeFieldWidth - (swipeFieldWidth / 2f));
            img.localScale = Vector3.one * (zoomCurve.Evaluate(transformedPosition));
        }
    }

So we’ll change it “a bit”.

 protected virtual void refreshImages()
    {
        List<KeyValuePair<RectTransform, float>> orderingLayerList = new List<KeyValuePair<RectTransform, float>>();
        foreach (var img in imgset)

We’ll use this list as a dictionary to hold information about the order value for each image. We won’t use a Dictionary since it cannot be trusted with keeping order.

At the end of the foreach cycle we then add:

            img.localScale = Vector3.one * (zoomCurve.Evaluate(transformedPosition));
            orderingLayerList.Add(new KeyValuePair<RectTransform, float>(img, orderFactor.Evaluate(alpha + alphaoff[img])));
        }

We just use the alpha+alphaoff of the image to get a point on the order function. Then record it in our list. After that, we just get our shit sorted out:

        var orderByVal = orderingLayerList.OrderBy(kvp => kvp.Value);

Wish it was that easy in real life…

From values to actual layer sorting

Now we can at last sort our hyerarchy in the order we have obtained, with a fast insertion sort approach. We just put as “last” every item starting from the lowest order value so that our highest values come out on top:

        foreach (var item in orderByVal)
        {
            item.Key.SetAsLastSibling();
        }

And that’s all.  We now have our gallery sorted out like we wanted, with the biggest images on the center and on top of the other images. Of course there still must be a lot of effects and we’ll need a selection mechanism, but that’s coming with next parts of this tutorial. Don’t lose them,  join my newsletter and I’ll keep you posted. [edit: next part is here]

Copy-paste from here:

    public AnimationCurve orderFactor;

    protected virtual void refreshImages()
    {
        List<KeyValuePair<RectTransform, float>> orderingLayerList = new List<KeyValuePair<RectTransform, float>>();
        foreach (var img in imgset)
        {
            float transformedPosition = positionCurve.Evaluate(alpha + alphaoff[img]);

            img.localPosition = Vector3.right * (transformedPosition * swipeFieldWidth - (swipeFieldWidth / 2f));

            img.localScale = Vector3.one * (zoomCurve.Evaluate(transformedPosition));
            orderingLayerList.Add(new KeyValuePair<RectTransform, float>(img, orderFactor.Evaluate(alpha + alphaoff[img])));
        }
        var orderByVal = orderingLayerList.OrderBy(kvp => kvp.Value);
        foreach (var item in orderByVal)
        {
            item.Key.SetAsLastSibling();
        }
    }
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  

Catching Swipes – a touch screen tutorial

One of the hurdles of dealing with touch screens is to handle finger tracking and boiling it down to a clean cut movement so that one can process gesture recognition.

Let’s look how to make a component that:

  1. only gets swipes in a specific screen area
  2. can detect multiple touches and store their individual details
  3. measures movement since last frame

Touch interface input – variables needed

For this we’ll need a couple of things in our data:

    public GameObject toCheck;
    Dictionary<int, Vector2> lastTouchPositionByFinger = new Dictionary<int, Vector2>();
    Vector2 lastClickPositionByMouse;
    Vector2 currentMousePos;
    [SerializeField]
    Vector2 netSwipe;
    public Vector2 NetSwipe
    {
        get { return netSwipe; }
    }

First of all we get a GameObject we’ll use to define the area in which the swipes shall be catched, it must be a UI element that can be Raycasted.
The next item will be a dictionary in which to store the current position of every single tracked finger. Also, since on Windows 10 computer touch screens are treated as mouse input we’ll need an extra place for mouse input position. And at last, of course, we have a property which will contain our movement since last frame inside the area.

Notice: there are up to 10 fingers and just one net movement. What we will do will be to have the total movement of the frame in this netSwipe variable, if you need to track the fingers independently, you will need to change this approach.

How to check if touch is over a UI element

So, how do we implement our first requisite? We just ask the event system if a finger is over the image (which of course can be made invisible by setting a 0 in its alpha channel).

    private static List<RaycastResult> tempRaycastResults = new List<RaycastResult>();

    public bool PointIsOverUI(Vector2 position)
    {

        var eventDataCurrentPosition = new PointerEventData(EventSystem.current);

        eventDataCurrentPosition.position = position;

        tempRaycastResults.Clear();

        EventSystem.current.RaycastAll(eventDataCurrentPosition, tempRaycastResults);
        foreach (var item in tempRaycastResults)
        {
            if (item.gameObject.GetInstanceID() == toCheck.GetInstanceID() && item.index == 0)
                return true;
        }
        return false;
    }

We’ll need an extra list for this function, to store all the results of the Raycast. At first we poll the EventSystem for a RaycastAll , then we go through every hit object and only if the object we hit is the one we’re looking to hit and only if there is no other object over it we give true as an answer. Otherwise we’re not hitting the right item and the touch should not be contributing to net swipe.

How to process a touch input in Unity3d

Now we can check if the touch should be counted or not, but that’s not enough. We also need to check for the touch phase in which we are and put tracking in place, let’s start with the Update function’s structure:

 void Update()
    {
        if (Input.touchCount > 0)
        {
            for (int i = 0; i < Input.touchCount; i++)
            {
                var touch = Input.GetTouch(i);
                switch (touch.phase)
                {
                    case TouchPhase.Began:
                        //stuff
                        break;
                    case TouchPhase.Moved:
                        //moar stuff
                        break;
                    case TouchPhase.Ended:
                        //stuff again
                        break;
                    case TouchPhase.Canceled:
                        //last stuff
                        break;
                    default:
                        break;
                }
            }
        }

    }

The first thing to check, of course, is if there are any touches at all. If there aren’t, then we don’t want to waste any resources. Then we cycle through each touch and do stuff appropriately.

Let’s get to the details. Before even starting our check we want to ensure that if no touches are active our netSwipe is reset to zero, so:

void Update()
    {
        netSwipe = Vector2.zero;
        if (Input.touchCount > 0)
        {

Getting to the actual stuff, when the touch begins and ends or gets cancelled we just want to add or remove it from our tracking accordingly, so we have:

                   case TouchPhase.Began:
                         lastTouchPositionByFinger.Add(touch.fingerId, touch.position);
                        break;

and:

                    case TouchPhase.Ended:
                        lastTouchPositionByFinger.Remove(touch.fingerId);
                        break;
                    case TouchPhase.Canceled:
                        lastTouchPositionByFinger.Remove(touch.fingerId);
                        break;

When the finger is moving, instead, is where the fun part happens:

                    case TouchPhase.Moved:
                        if (PointIsOverUI(touch.position))
                        {
                            netSwipe += touch.position - lastTouchPositionByFinger[touch.fingerId];
                            lastTouchPositionByFinger[touch.fingerId] = touch.position;
                        }
                        break;

First we check if the finger is over the area of interest, then we add its movement since last frame to our  netSwipe variable and update the finger’s position in our dictionary and that’s it! You now have also the tracking and net movement over the image.

Dealing with Windows 10

        if (Input.GetMouseButton(0))
        {
            currentMousePos = Input.mousePosition;
            if (PointIsOverUI(currentMousePos))
                netSwipe += (currentMousePos - lastClickPositionByMouse);

            lastClickPositionByMouse = currentMousePos;
        }

As I said before, on Windows 10 laptops touchscreens touches are read as mouse input. In that case we’re going to make an exception ad use a special treatment. The logic is still the same, but without the tracking of a finger touch. Simply put, if the button is down, then we check if it’s over the UI element we’re tracking and if so we use current position versus last frame’s one to contribute to netswipe.

That’s all folks!

Now go out there and do something awesome with that. I’ll post soon enough a tutorial that makes use of this component to control a gallery of sliding images. Join my newsletter to never miss my stuff and hit me up on Twitter for any remarks!

And of course here’s the script in a copypaste-friendly format:

using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;

public class CatchSwipe : MonoBehaviour
{

    public GameObject toCheck;
    Dictionary<int, Vector2> lastTouchPositionByFinger = new Dictionary<int, Vector2>();
    Vector2 lastClickPositionByMouse;
    Vector2 currentMousePos;
    [SerializeField]
    Vector2 netSwipe;
    public Vector2 NetSwipe
    {
        get { return netSwipe; }
    }

    void Update()
    {
        netSwipe = Vector2.zero;
        if (Input.GetMouseButton(0))
        {
            currentMousePos = Input.mousePosition;
            if (PointIsOverUI(currentMousePos))
                netSwipe += (currentMousePos - lastClickPositionByMouse);

            lastClickPositionByMouse = currentMousePos;
        }
        if (Input.touchCount > 0)
        {
            for (int i = 0; i < Input.touchCount; i++)
            {
                var touch = Input.GetTouch(i);
                switch (touch.phase)
                {
                    case TouchPhase.Began:
                        lastTouchPositionByFinger.Add(touch.fingerId, touch.position);
                        break;
                    case TouchPhase.Moved:
                        if (PointIsOverUI(touch.position))
                        {
                            netSwipe += touch.position - lastTouchPositionByFinger[touch.fingerId];
                            lastTouchPositionByFinger[touch.fingerId] = touch.position;
                        }
                        break;
                    case TouchPhase.Ended:
                        lastTouchPositionByFinger.Remove(touch.fingerId);
                        break;
                    case TouchPhase.Canceled:
                        lastTouchPositionByFinger.Remove(touch.fingerId);
                        break;
                    default:
                        break;
                }
            }
        }
    }


    private static List<RaycastResult> tempRaycastResults = new List<RaycastResult>();
    public bool PointIsOverUI(Vector2 position)
    {

        var eventDataCurrentPosition = new PointerEventData(EventSystem.current);

        eventDataCurrentPosition.position = position;

        tempRaycastResults.Clear();

        EventSystem.current.RaycastAll(eventDataCurrentPosition, tempRaycastResults);
        foreach (var item in tempRaycastResults)
        {
            if (item.gameObject.GetInstanceID() == toCheck.GetInstanceID() && item.index == 0)
                return true;
        }
        return false;
    }
}

 

  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •