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;
    }
}

 

Share
  • 15
    Shares
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  

Event system: tutorial for Unity3D & C#

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

Today I’ll start presenting you the single most useful piece of code that I’ve ever written. Since writing it I used it in every single project I’ve made, however simple. Even in game jams.
I’m speaking of an Event System. A class with no other purpose than to let other objects communicate with each other, with as little overhead as possible. And it’s implemented with delegates.

And it’s way better than SendMessage, if you ask me.

event_driven_programming

What’s an Event System?

Those of you who already know, just skip this paragraph. Still with me? good. The main problem you’ll have with SendMessage is that it needs to use a method’s name as an input. So the object sending the message not only needs to know what method should be invoked by the receiver, but also his name. In a case sensitive way. Want to change that name? too bad, the IDE won’t change the string for you. You’ll have to remember every single one of those and do it by yourself… or never change a function name ever again. Or remove one.

Sounds bad? it is. It’s a maintenance nightmare. Plus, it uses strings. Fuck strings. Strings are evil. They use your memory, trigger your garbage collector and spit on your mother. Never use them unless at gunpoint… and even then think twice about it and instead use an enum.ToString() if you can.

So what’s the solution? we need something more like this: you need to send a message between objects (or scripts) when a “thing” happens. So the scripts in the other object react to the “thing”. The caller script doesn’t need to know who or how will do anything in reaction to the “thing”. It just needs to shout out “Hey! I have a thing here!” and eventually how big the “thing” is. For instance, “Hey! I have a 3.5 Damage here!”. Then the health script subtracts the damage and the particle script spills blood everywhere. But the collider doesn’t need to know that. The “thing” is an Event. Shouting is a Broadcast. The reactions are called Callbacks or Handlers.

But this is nothing I invented, you can just study it here or here, or even here.

What’s a Delegate?

As before, if you know already, just skip ahead. Delegates are something I wish they did teach me in university, because they are just awesome. To put it bluntly: in C# you can treat functions as if they were variables and pass them as an argument to other functions. This means that you can change behaviours of an object at runtime. Or have an object hold and call a function without it even knowing what it does. Absolute decoupling. A maintenance heaven.

Before you can use a delegate, you first need to declare its type. Which means telling the compiler which return type and what arguments will be assigned to it. Not its name. Not its content. Just the signature structure. Of course, if you use as a signature something like:

public delegate object nameFunct(object o);

you can then pass anything and get anything (but say goodbye to compiler type-checks).

After you have declared a delegate type, you can then declare a delegate variable and assign to it a function with a matching signature. Just like you would do with any other variable. If you still need to delve deeper you can go here or here.

Get on with it!

gowi-2

Now, if you have seen my portfolio the event system there can be quite intimidating, but don’t worry, we won’t be doing that version right now. We’ll start with version 0.0.3 while that one is 1.0.1 [edit: this was before optimizing for the last part of the tutorial]. That makes about 100 rows of code and 20 headaches of difference.

Before we get to the main class, the event dispatcher, let’s define some stuff we’ll use there.

public delegate void gameEventHandler(eventArgExtend e);

This will be our event signature. Nothing to return because we don’t want the event sender to have anything to do with the receivers. The event type is this thing here:

public class eventArgExtend : System.EventArgs { }

Why didn’t I use directlySystem.EventArgs? Because in the future I may want to add something between those parentheses and when it happens it will cost me nothing to do so. Had I used directly that type it could require more work to do it.

Now, let’s get to something more interesting: defining the events themselves. To do so I’ll use something infinitely superior to strings: enums.

public enum eventChannels
{
    inGame
}
public enum inGameChannelEvents
{
    thing
}

Now we’ll need a comfortable way to pass this information to the dispatcher. For this I just created a class with a static function:

public class ChannelEnums
{
    public static Dictionary<eventChannels, System.Array> getChannelEnumList()
    {

        Dictionary<eventChannels, System.Array> enumChannelEventList = new Dictionary<eventChannels, System.Array>();
        enumChannelEventList.Add(eventChannels.inGame, System.Enum.GetValues(typeof(inGameChannelEvents)));
        return enumChannelEventList;
    }
}

Notice: adding a channel requires to add a new enum type, and there is no automated way to set a link between an enum value ineventChannelsand an other enumtype. So for each channel you need to add, you’ll have to write a new row of code like the one just before thereturn.

What we did here is to create a static function for the dispatcher. The function will recovery every event channel and every corresponding array of values, so that we can initialize the list of listeners in the dispatcher. All in the nice form of a Dictionary.

Now let’s get to the Event System

Our event system needs to be usable at EVERY stage of game play. This includes the Awake function of the first objects to be present in the first scene. Which means that I can’t use the Awake function to initialize the event system, or I would get in a race condition. That’s fucked up. But years of Unity3D ninjutsu allowed me to discover a precious thing: default values are initialized before Awake, and you can get them with static functions.

public class eventHandlerManager : MonoBehaviour
{
    static Dictionary<eventChannels, Dictionary<Enum, gameEventHandler>> ListenerFunctions = initializeDicts();

What’s thatinitializeDicts? we’ll see later. For now just look at the type of ListenerFunctions: it’s a dictionary of dictionaries. It allows us to index gameEventHandler delegates first by channel and then by event.

Now we can write the key functions of the dispatcher: one to raise an event, one to add a delegate as listener, one to remove it.

public static void Broadcast( eventChannels evType,Enum ev,eventArgExtend e) 
	{
		ListenerFunctions[evType][ev](e);
	}
	
	public static void AddListener(eventChannels evType,Enum ev, gameEventHandler eventListener)
	{
		ListenerFunctions[evType][ev]+=eventListener;
	}
	public static void RemoveListener(eventChannels evType,Enum ev, gameEventHandler eventListener)
	{
		ListenerFunctions[evType][ev]-=eventListener;
	}

Why are they all static? because that way you don’t need to get the other classes to find the instance. Yes, this is limiting: you can’t have a damage event that only gets notified to one character. But for something more selective we’ll need to make a lot of changes, for the time being just add an identifier as field in the argument. Then have the receivers check that value. We’ll get back to that in future tutorials.

This is all that’s needed on the outside to use this event handler. Other classes that want to react to an event just need to declare a function that matches thegameEventHandlersignature and then invoke theaddListenerfunction giving that function as the last argument (without parentheses), and do the same for removal like this:

eventHandlerManager.AddListener(eventChannels.inGame, inGameChannelEvents.thing, onThing);

The broadcast use is even simpler:

eventHandlerManager.Broadcast(eventChannels.inGame, inGameChannelEvents.thing, new eventArgExtend());

So, now we’re almost done. There is just one last detail for the magic to fully work: how do we initialize and cleanListenerFunctions?

    public void OnDestroy()
    {
        ListenerFunctions = initializeDicts();
    }

    static Dictionary<eventChannels, Dictionary<Enum, gameEventHandler>> initializeDicts()
    {
        Dictionary<eventChannels, Array> enumChannelEventList = ChannelEnums.getChannelEnumList();
        Dictionary<eventChannels, Dictionary<Enum, gameEventHandler>> result = new Dictionary<eventChannels, Dictionary<Enum, gameEventHandler>>();
        foreach (var val in (eventChannels[])Enum.GetValues(typeof(eventChannels)))
        {
            result.Add(val, new Dictionary<Enum, gameEventHandler>());
            foreach (var ev in enumChannelEventList[val])
            {
                result[val].Add((Enum)ev, new gameEventHandler(delegate (eventArgExtend e) { }));
            }
        }
        return result;
    }

Wait a minute: did I initialize ListenerFunctions on destroy? Yes, because that is a static field. It will survive to the existence of the eventHandlerManager instance. And since that should only happen on a scene load, I can be sure that nothing should stay in the dictionary and that the new scene has a ready new clean slate to work on when the first Awake is called.

The initialization works by first getting the enum data from the static function we defined before in ChannelEnums, then using that data to initialize every field of the dictionary with an empitygameEventHandler; this way we can later add the Handlers with a+=instead of checking for a null field every time.

That’s all folks!

Not really. Actually there is a lot more that we can add to this class, mainly for debugging purposes, plus the “local” version of the event dispatcher that I mentioned before. But this tutorial is already huge, so maybe it’s better to deal with that stuff 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 System;
using System.Collections.Generic;
using UnityEngine;

public class eventHandlerManager : MonoBehaviour
{
    public static Dictionary<eventChannels, Dictionary<Enum, gameEventHandler>> ListenerFunctions = initializeDicts();

    public static void Broadcast(eventChannels evType, Enum ev, eventArgExtend e)
    {
        ListenerFunctions[evType][ev](e);
    }

    public static void AddListener(eventChannels evType, Enum ev, gameEventHandler eventListener)
    {
        ListenerFunctions[evType][ev] += eventListener;
    }
    public static void RemoveListener(eventChannels evType, Enum ev, gameEventHandler eventListener)
    {
        ListenerFunctions[evType][ev] -= eventListener;
    }

    public void OnDestroy()
    {
        ListenerFunctions = initializeDicts();
    }

    static Dictionary<eventChannels, Dictionary<Enum, gameEventHandler>> initializeDicts()
    {
        Dictionary<eventChannels, Array> enumChannelEventList = ChannelEnums.getChannelEnumList();
        Dictionary<eventChannels, Dictionary<Enum, gameEventHandler>> result = new Dictionary<eventChannels, Dictionary<Enum, gameEventHandler>>();
        foreach (var val in (eventChannels[])Enum.GetValues(typeof(eventChannels)))
        {
            result.Add(val, new Dictionary<Enum, gameEventHandler>());
            foreach (var ev in enumChannelEventList[val])
            {
                result[val].Add((Enum)ev, new gameEventHandler(delegate (eventArgExtend e) { }));
            }
        }
        return result;
    }
}

public enum eventChannels
{
    inGame
}

public enum inGameChannelEvents
{
    thing
}

public class eventArgExtend : System.EventArgs { }

public delegate void gameEventHandler(eventArgExtend e);

public class ChannelEnums
{
    public static Dictionary<eventChannels, System.Array> getChannelEnumList()
    {

        Dictionary<eventChannels, System.Array> enumChannelEventList = new Dictionary<eventChannels, System.Array>();
        enumChannelEventList.Add(eventChannels.inGame, System.Enum.GetValues(typeof(inGameChannelEvents)));
        return enumChannelEventList;
    }
}

 

Share
  • 33
    Shares
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  

Scroll to select: image gallery tutorial

 

 

Every now and then in a UI you need the user to select an option among many, there are several ways to do so, one method could be the good’ole command line, another could be the use of toggles in a group (or radio button in Android/HTML environment), but that’s not nearly as cool as just looking at the thing you want and it being already selected. So today we’ll implement a scroll to select UI interface in unity3d.

We’ll do this in the image gallery that we’ve been building here for quite a while, here I’ll also give a unified version of the code with all of the features in it.

The idea is to define an area where the selection happens, so that whatever image happens to be in the area gets selected automatically. Of course the snapping behaviour works well with this but it’s not essential, since we are defining an area, not a single position.

Scroll to select area: let’s build it

It will go something like this:

select a ninja by scrolling a gallery of images
Don’t move and select, but select by moving

Let’s get into the code. First we’ll need a new animation curve and a place to store the selected image information:

    public AnimationCurve selectionArea;
    public RectTransform selected;

We’ll use a very narrow spike function, it’s supposed to only be over .75 in the selection area (with respect to the alpha we used all along).

Narrow spike function
Make it larger to have a larger area of selection. Caos will ensue if two images can be both in the area at a time

And to complete the opera at the very end of our refresh image foreach loop we’ll insert this

        if (selectionArea.Evaluate(transformedPosition) >= 0.75f)
        {
            selected = img;
        }

This way only the image that’s been scrolled to the center is in the range for which the selectionArea curve evaluates to more than .75, which means only that image gets selected. It’s important to use a very narrow area so that only one image at time can get selected.

And that’s all for the scroll to select part of this tutorial!

This one was very easy, wasn’t it? So, at long last we are over this whole guide and you now have a fully functional scroll-to-select image gallery that looks fucking awesome! Of course there’s a new tutorial coming soon, don’t lose anything, join my newsletter.

A recap to put EVERYTHING in one place for you to copy-paste:

    public AnimationCurve orderFactor;
    public AnimationCurve positionCurve;
    public AnimationCurve zoomCurve;
    public AnimationCurve selectionArea;
    public RectTransform selected;

    public RectTransform[] imgset;
    public float rotationSensibility = 1f; //how sensible is to swipe input
    protected float alphaStep = 1f; //will host the image-to-image distance.
    public float swipeFieldWidth = 300f;//Total distance from leftmost position to rightmost one
    [SerializeField]    protected float alpha; //the parameter around wich all things revolve
    protected float lastFrameAlpha; //last frame's alpha value
    public CatchSwipe inputSource; //A module to handle input that gives us the net x-axis swipe value
    Dictionary<RectTransform, float> alphaoff = new Dictionary<RectTransform, float>();//a dictionary to store all image's offsets

    [Header("inertia Parameters")]
    public bool useInertia;
    protected float inertia;
    public float inertialDampening = 0.9f; 
    public float inertiaPersistenceFactor = 10f;

    [Header("Magnetizaiton Parameters")]
    public bool doMagnetize = true;
    public float magnetizationMaxForceFactor = 0.09f;
    public float magnetizationRangeAlphaStepFraction = 0.5f;
    protected float magneticForce;

    public virtual void Awake()
    {
        if (useInertia)
            doMagnetize = false;
    }

    public virtual void Start()
    {
        alphaInitialization();
        refreshImages();
    }

    protected virtual void alphaInitialization()
    {
        alpha = 1;
        if (imgset.Length > 1)
            alphaStep = 1f / (imgset.Length - 1f);
        float minAlpha = -(imgset.Length - 1f) * alphaStep / 2f;
        alphaoff.Clear();
        foreach (var item in imgset)
        {
            alphaoff.Add(item, minAlpha);
            minAlpha += alphaStep;
        }
        lastFrameAlpha = alpha;
    }

    protected virtual void Update()
    {
        updateAlpha();
        updateInertia();
        updateMagnetization();
        if (Mathf.Abs(lastFrameAlpha - alpha) > 0.001f)
        {
            refreshImages();
        }
        lastFrameAlpha = alpha;
    }
    protected virtual void updateAlpha()
    {
        alpha = Mathf.Clamp(
                alpha + (Mathf.Abs(inputSource.NetSwipe.x) > 0 ?
                    inputSource.NetSwipe.x * rotationSensibility
                    :
                    inertia + magneticForce), 
                0,
                1
                );
    }
    protected virtual void updateInertia()
    {
        if (useInertia)
        {
            inertia = Mathf.Abs(inputSource.NetSwipe.x) > 0 ? alpha - lastFrameAlpha : inertialDampening * inertia;
            if (Mathf.Abs(inertia) < rotationSensibility / inertiaPersistenceFactor)
                inertia = 0;
        }
    }


    protected virtual void updateMagnetization()
    {
        if ((Mathf.Abs(inputSource.NetSwipe.x) <= 0.001) && (doMagnetize))
        {
            float rest = alpha % alphaStep;
            if (rest < magnetizationRangeAlphaStepFraction * alphaStep)
            {
                if (rest < rotationSensibility)
                    magneticForce = -rest;
                else
                    magneticForce = -rest * magnetizationMaxForceFactor;
            }
            else if (rest > alphaStep * (1 - magnetizationRangeAlphaStepFraction))
            {
                if (rest < rotationSensibility)
                    magneticForce = rest;
                else
                    magneticForce = (alphaStep - rest) * magnetizationMaxForceFactor;
            }
        }
    }

    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])));
            if (selectionArea.Evaluate(transformedPosition) >= 0.75f)
            {
                selected = img;
            }
        }
        var orderByVal = orderingLayerList.OrderBy(kvp => kvp.Value);
        foreach (var item in orderByVal)
        {
            item.Key.SetAsLastSibling();
        }
    }

And this finally ends this lenghty tutorial. I hope you will find it useful 🙂
Tell me what you think about it and follow me on twitter for more useful stuff.

 

Share
  • 4
    Shares
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  

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();
        }
    }
Share
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  

Of men & scroll galleries: in-depth tutorial

In my last project I had to build a skin selection menu. I could have gone away by simply using a scrollrect, but who likes cool when you can have awesome?

I didn’t want just a scrollrect. I wanted Icons to change size depending on screen position. And to cover each other when too far away from the currently selected one. All with inertia and snap-to-option movement. Of course, no tap-to-select, whatever’s centered should be selected. In other words I wanted a scrolling gallery of images that looked and felt awesome where I could pick my ninja.

I also wanted a Lamborghini, but you can’t get THAT just by coding it.

Yet.

Let’s begin with some high-level concepts

So what to do? First I started with some math.
I had to figure out how to mathematecally handle all the movements, which functions to encode in unity so that at every frame I had exactly what I needed. Of course using math does not mean using calculus, if you remember this trick of mine.

So, mathematically speaking I wanted a bellshaped curve (AKA Gaussian) of zoom, and some easing for position on x axis.
All that must be controlled with a swipe, which means with a single parameter. What we have is a linear swipe, what we want is an eased movement and zoom.

An example of easing in animation
The upper picture represents a linear movement, the lower one an eased movement

The trick is not to think on what kind of formulas can give the result I need, but to just revert the functions that would do the trick graphically, which means a bell shaped curve for zoom and an s-shaped one for x position.
The mechanics will be very similar to spinning an invisible wheel, with our images are glued on its border and then watching from a small distance. You just increase linearly the rotation angle at its center and then you’ll see exactly the movement we need: big and fast picture in the middle, many others small and slow on the sides. We’ll call the rotation angle of the imaginary wheel our “alpha” angle.

Brace yourselves: code is coming

Let’s review the variables we’ll need. AnimationCurves first.

 public AnimationCurve positionCurve;
 public AnimationCurve zoomCurve;

These will be our “math” functions. Zoomcurve needs a bell-shape with peak in .5f, positioncurve it’s a regular ease-in ease-out.

public RectTransform[] imgset;

Here we’ll store the images to move. Actually their RectTransforms.

And then there’s some “constants”:

public float rotationSensibility = 1f; //how sensible is to swipe input
protected float alphaStep = 1f; //will host the image-to-image distance.
public float swipeFieldWidth = 300f;//Total distance from leftmost position to rightmost one

And finally some internal variables

[SerializeField] protected float alpha; //the parameter around wich all things revolve
protected float lastFrameAlpha; //last frame's alpha value
public CatchSwipe inputSource; //A module to handle input that gives us the net x-axis swipe value
Dictionary<RectTransform, float> alphaoff = new Dictionary<RectTransform, float>();//a dictionary to store all image's offsets

The eagle-eye view

So, our high-level code would be something like this:

    public virtual void Start()
    {
        alphaInitialization();
        refreshImages();
    }

    protected virtual void Update()
    {
        updateAlpha();
        if (Mathf.Abs(lastFrameAlpha - alpha) > 0.001f)
        {
            refreshImages();
        }
        lastFrameAlpha = alpha;
    }

Which basically reads: first, initialize my shit and show it, then update my alpha and if it’s different than last frame, show the update .
It’s a simple behaviour.

Warning: this check will save a lot of cpu but may get some added effects not too smooth with extra slow movements, it’s up to you to decide your tradeoff.

Let’s see what those functions actually do.

 protected virtual void alphaInitialization()
    {
        alpha = 1;

We just start with the leftmost image at center.

        if (imgset.Length > 1)
            alphaStep = 1f / (imgset.Length - 1f);

Here we calculate the alphaStep if it needs to be less than one. We want the images to be equally spaced on the imaginary wheel border, so that we can calculate easily how they move.

        float minAlpha = -(imgset.Length - 1f) * alphaStep / 2f;
        alphaoff.Clear();
        foreach (var item in imgset)
        {
            alphaoff.Add(item, minAlpha);
            minAlpha += alphaStep;
        }

Then we calculate what the offset values are for our images. This will enable us to rule them all with just one ring… err… parameter, the “alpha” rotation value of the imaginary wheel. The middle image uses the base alpha value and the others will shift it with the offset. Simple huh?

        lastFrameAlpha = alpha;
    }

And lastly, we also initialize the lastFrameAlpha value.

protected virtual void updateAlpha()
    {
        alpha = Mathf.Clamp(alpha + inputSource.NetSwipe.x * rotationSensibility, 0, 1 );
    }

Now, this is what one would expect to be the complicated part… but actually it’s the simplest. When the user swipes he’s just spinning the wheel, nothing more. So we increase the alpha by exactly the amount he swipes times the sensibility.

And now let’s see where the magic actually happens!

    protected virtual void refreshImages()
    {
        foreach (var img in imgset)
        {
            float transformedPosition = positionCurve.Evaluate(alpha + alphaoff[img]);

HERE! did you see that? Here I changed the alpha value to a phisical position. Before we only knew how much the wheel would spin (alpha), then we got how exactly it was spinning for the image we’re checking (alpha + alphaoff[img]). The actual transformation would have been some good old math if we didn’t use an AnimationCurve to do it in our place.
Fuck that. I’m lazy and want the UI designer to be able to change how the slider behaves without touching MY code with his filthy fingers.
Our result is still normalized in a 0-1 scale. To get a position in the proper scale we need :

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

So, basically, by subtracting half swipeFieldWidth we have the results centered in 0, but then we’ll have that with transformedPosition==0, a position of (- swipeFieldWidth/2,0,0) will be assigned, and with transformedPosition==1,the result will of course be (swipeFieldWidth/2,0,0). The result is that the width of it all it’s exactly swipeFieldWidth.

            img.localScale = Vector3.one * (zoomCurve.Evaluate(transformedPosition));
        }
    }

And this is what gives the “zoom” effect: we just make the scale bigger the closer the image is to the center (which means transformedPosition==.5f )
This too of course will be done with an AnimationCurve so that we can thinker with results without having to change the code.

What we did so far should look something like this:

[note to self: never delete a gif again or you’ll have to go through hundreds of tweets to get it back]

I hope this will be enough for now. I also hope you would like to read how to add the other stuff I mentioned before like inertia and snapping. If so, join my newsletter to know when the next part of this guide is ready! [edit: next part is here]

And yes, of course I know you want to copy-paste this, so here’s all in one place:

    public AnimationCurve positionCurve;
    public AnimationCurve zoomCurve;
    public RectTransform[] imgset;
    public float rotationSensibility = 1f; //how sensible is to swipe input
    protected float alphaStep = 1f; //will host the image-to-image distance.
    public float swipeFieldWidth = 300f;//Total distance from leftmost position to rightmost one
    [SerializeField]    protected float alpha; //the parameter around wich all things revolve
    protected float lastFrameAlpha; //last frame's alpha value
    public CatchSwipe inputSource; //A module to handle input that gives us the net x-axis swipe value
    Dictionary<RectTransform, float> alphaoff = new Dictionary<RectTransform, float>();//a dictionary to store all image's offsets

    public virtual void Start()
    {
        alphaInitialization();
        refreshImages();
    }

    protected virtual void Update()
    {
        updateAlpha();
        if (Mathf.Abs(lastFrameAlpha - alpha) > 0.001f)
        {
            refreshImages();
        }
        lastFrameAlpha = alpha;

    }

 protected virtual void alphaInitialization()
    {
        alpha = 1;

        if (imgset.Length > 1)
            alphaStep = 1f / (imgset.Length - 1f);

        float minAlpha = -(imgset.Length - 1f) * alphaStep / 2f;
        alphaoff.Clear();
        foreach (var item in imgset)
        {
            alphaoff.Add(item, minAlpha);
            minAlpha += alphaStep;
        }

        lastFrameAlpha = alpha;
    }


protected virtual void updateAlpha()
    {
        alpha = Mathf.Clamp(alpha + inputSource.NetSwipe.x * rotationSensibility, 0, 1 );
    }

    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));
        }
    }

And here are the position curve:

position-curve

And zoom curve:

zoom-curve

Share
  • 18
    Shares
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  

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;
    }
}

 

Share
  • 45
    Shares
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  

Unity3d WebGL input mapping for Xbox controller

So, this weekend ludum dare 35 happened and I made my entry using xbox 360 controller as my intended imput source. Only one problem there: no existing input guide spoke of how to map buttons on the Xbox controller for unity3d webGL. And that was my target platform.

So, long story short, here’s what I wasted precious jam time looking for:

xbox controller unity3d webgl input map

xbox_360_controller-button-map

For xbox controller usually you would have the triggers on the back as a 3rd or 5th axis, instead on webGL they are mapped only as standard buttons.

As for the axis: left stick is X and Y axis (as usual), right stick is 3rd and 4th axis.

You can set them like this:

xbox controller unity axis input
xbox controller unity axis input

By the way, there’s a simple way to test these mappings on your own!
You can just use this script:

public class testbuttons : MonoBehaviour
{
    [SerializeField]
    Text t;
    // Update is called once per frame
    void Update()
    {
        t.text = "";
        for (int i = 0; i < 20; i++)
        {
            t.text += "Button " + i + "=" + Input.GetKey("joystick button " + i) + "| ";
        }
    }
}

Make an empty scene, add a UI>Text element and stick this script on it. Then run it and read what turns to true when any button is pressed on your controller.

As easy as pie… unless you are in a game jam frenzy, with severe sleep deprivation and panicking like I was… Want more tutorials like this? Join my newsletter! Want to tell me anything? Hit me up on twitter.

Share
  • 11
    Shares
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  

Validating email in C#

Input sanitation is a must when dealing with web services, but it’s also smart to avoid to waste precious seconds in registration procedures due to a wrong email address. That’s true in web development, that’s true in mobile apps, that’s just true on any digital platform.
So it’s best to validate email in unity3d too before any web service is called.

The variables

First we get the references via unity editor to catch both theInputFieldandButton.

    [SerializeField]
    InputField mail;
    [SerializeField]
    Button sendButton;

Then we create aRegexusing the mail validation pattern from .Net framework (be aware, it’s not perfect, but it’s good enough).

    System.Text.RegularExpressions.Regex mailValidator = new System.Text.RegularExpressions.Regex(@"^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?$");

TheRegexclass will provide us with a pattern-matching function that we can easily use to check if the pattern on which is built does or not match any input. The pattern will follow the regex syntax. Which by the way I found out not to be a regular expression language any more due to backreferences.

Validate email

Then, on eachUpdatewe first reset the button’s state, then we make it notinteractableagain only if any of the validation criteria is false. So only if the mail validation check DOES NOT recognize our string as a mail address it should make it notinteractable, but in the converse case it shouldn’t make itinteractablesince another validation criteria may fail independently.

    void Update()
    {
        sendButton.interactable = true;

        //other sanitation for other stuff

        if (!mailValidator.IsMatch(mail.text))
                sendButton.interactable = false;
    }

That’s all folks!

Really? really.
It’s not more than just 15 lines of code but it’s a big deal in reducing friction during your registration process for any service or game. If you like this kind of tutorial or just want to hear from me in the future, join my newsletter.

And here’s everything ready for copy-paste :

    [SerializeField]
    InputField mail;
    [SerializeField]
    Button sendButton;
    System.Text.RegularExpressions.Regex mailValidator= new System.Text.RegularExpressions.Regex(@"^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?$");
    void Update()
    {
        sendButton.interactable = true;

        //other sanitation for other stuff

        if (!mailValidator.IsMatch(mail.text))
                sendButton.interactable = false;
    }
Share
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  

Webservices in unity3d: Post, JSON and wait

Working with webservices in mobile apps is quite a common task,  but what if you are developing in Unity3d and need to send a Post request with JSON data? Well, there’s a quick and painless way of dealing with it, in less than 40 lines of code, complete of “wait…” screen management, from call to loggable answer.

First of all we’ll need a little coroutine and a JSON script you can get here.

The coroutine

It’s actually a very small one:

    IEnumerator afterConditionExecutor<T>(System.Action<T> del, T param, bool cond)
    {
        while (!cond)
        { yield return new WaitForEndOfFrame(); }
        del(param);
    }

This will just wait for our condition to be true, then execute the delegatedelgiving it the parameter it needs. We could just put here our wait screen control code, but you may want to customize it so we’ll handle that later (plus, this coroutine is so generally useful, you may want to put it in a library).

Sending the Post request

Let’s get to the meat of the issue, where the POST request is sent along with all our JSON data:

    public void sendMessage(JSONObject data, string url, bool addCookie, MonoBehaviour reqSource, System.Action<WWW> callBack)
    {
        Dictionary<string, string> headers = null;
        if (addCookie)
        {
            headers = new Dictionary<string, string>();
            headers.Add("Cookie", "SomeHeaderContent");
        }

        byte[] pData = Encoding.ASCII.GetBytes(data.ToString().ToCharArray());

        WWW www = new WWW(url,pData, headers);
        reqSource.StartCoroutine(afterConditionExecutor<T>(delegate (WWW mmm)
        {
            Debug.Log("sendMessage to: " + mmm.url + " returned: " + mmm.text);
            callBack(mmm);
        }, www, www.isDone);
    }

In the first part we prepare the request headers. Those are tipically the same across all webservice requests (eg.: login tokens) so it can be convenient to just generate them there and have a bool to control if they are added or not. Otherwise just add a parameter and pass it along from the source.

After that we encode ourJSONObjectin a byte array, so that is in the right format for theWWWobject constructor to pick up. When this constructor is called the webservice request will be sent as a POST http request, but we cannot just parse the reply right away because it won’t be ready. Even for whole seconds. Since it would be too long of a time to freeze our game/app, this call is conveniently Asynchronous. When is done,WWWclass will tell us by turning true thewww.isDoneflag (see reference docs here).

This is the reason why we start a coroutine on behalf of the message-sending class that will use our coroutine to wait until the proper moment and then call our callBack function.

Since there may be some standard error handling to do (eg: no internet connection) we do this inside a dedicated delegate that is passed as argument to the coroutine. Notice that theWWWargument of the delegate is named differently from the object we are just creating so that it links to the one being passed from the coroutine as an argument, this avoids a compilation error.

Let’s see a usage example:

    public void executeLogin(string username, string password)
    {
        JSONObject loginReqJS = new JSONObject();
        loginReqJS.AddField("username", username);
        loginReqJS.AddField("password", password);

        waitLayer.SetActive(true);
        sendMessage(loginReqJS, "http://your url/request/url", false, this, delegate (WWW www)
        {
            waitLayer.SetActive(false);
            Debug.Log("executeLogin to: " + www.url + " returned: " + www.text);
        });
    }

The first thing we do here is to prepare aJSONObjectwith the appropriate parameters for the situation, then we activate our “wait” UI element and finally call oursendMessagefunction.

Inside the delegate that will handle the server reply we also make our “wait” screen go away (non-standard error handling goes here, eg: wrong password), in this case we just log what’s been returned, but in real-life cases you’d transition from a login to your main menu.

This will produce the following http request:

POST /request/url HTTP/1.1
Cookie: "SomeHeaderContent"

{"username":"username","password":"password"}

And here you go. Hope you’ll find this tutorial useful. For more unity3d tutorials join my newsletter 😉

Here’s everything in a more copy-pastable format:

    IEnumerator afterConditionExecutor<T>(System.Action<T> del, T param, bool cond)
    {
        while (!cond)
        { yield return new WaitForEndOfFrame(); }
        del(param);
    }

    public void executeLogin(string username, string password)
    {
        JSONObject loginReqJS = new JSONObject();
        loginReqJS.AddField("username", username);
        loginReqJS.AddField("password", password);

        waitLayer.SetActive(true);
        sendMessage(loginReqJS, "http://your url/request/url", false, this, delegate (WWW www)
        {
            waitLayer.SetActive(false);
            Debug.Log("executeLogin to: " + www.url + " returned: " + www.text);
        });
    }

    public void sendMessage(JSONObject data, string url, bool addCookie, MonoBehaviour reqSource, System.Action<WWW> callBack)
    {
        Dictionary<string, string> headers = null;
        if (addCookie)
        {
            headers = new Dictionary<string, string>();
            headers.Add("Cookie", "SomeHeaderContent");
        }

        byte[] pData = Encoding.ASCII.GetBytes(data.ToString().ToCharArray());

        WWW www = new WWW(url, pData, headers);
        reqSource.StartCoroutine(afterConditionExecutor<T>(delegate (WWW mmm)
        {
            Debug.Log("sendMessage to: " + mmm.url + " returned: " + mmm.text);
            callBack(mmm);
        }, www, www.isDone);
    }

 

Share
  • 2
    Shares
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  

Snap effect in scroll area: in-depth tutorial

This tutorial is part of a series on how to construct an awesome-looking scroll gallery, with inertia, snap effect, zoom and other stuff. Now we will add a snap effect to it, or as I call it, a magnetic behaviour. When the scrolling gallery is left somewhere between two images, the behaviour will move the one closest to the center towards the middle by scrolling all the sliding gallery. Easier done than said, actually:

snap to center effect
The snap effect will look like this. Magnetic, isn’t it?

Let’s get on the snap effect parameters!

Let’s start with the parameters we’ll need to add to our script this time:

    [Header("Magnetizaiton Parameters")]
    public bool doMagnetize = true;
    public float magnetizationMaxForceFactor = 0.09f;
    public float magnetizationRangeAlphaStepFraction = 0.5f;
    protected float magneticForce;

The first one is just to be able to disable the whole behaviour in one click. The variable magneticForce, instead, will be used to pass the actual per-frame movement to perform, as caused by the snap behaviour, to our updateAlpha function (that will change accordingly).
As for magnetizationMaxForceFactor it will be a factor that defines the maximum movement that can be done in a single frame. And last but not least we have magnetizationRangeAlphaStepFraction, a variable that represents which range of the intermediate positions between two images should be a valid starting point for our snapping behaviour (0.5 being just everywhere and less will leave some resting areas in the middle of two images).

To the bat-functions!

We won’t need to change the start function, but our Update will need a fix:

    protected virtual void Update()
    {
        updateAlpha();
        updateMagnetization();
        if (Mathf.Abs(lastFrameAlpha - alpha) > 0.001f)
        {
            refreshImages();
        }
        lastFrameAlpha = alpha;
    }

We’ll have to make a call to update the magneticForce parameter we introduced before and it must to be done at every frame.

As for our UpdateAlpha we’ll check for input and use the magneticForce if and only if there is no ongoing action

    protected virtual void updateAlpha()
    {
        alpha = Mathf.Clamp(
                alpha + (Mathf.Abs(inputSource.NetSwipe.x) > 0 ?
                    inputSource.NetSwipe.x * rotationSensibility
                    :
                    magneticForce),
                0,
                1
                );
    }

See? Only when the absolute value of the horizontal swipe is exactly zero we’ll add our magneticForce instead of the input-driven value. And now let’s go for the main dish:

    protected virtual void updateMagnetization()
    {
        if ((Mathf.Abs(inputSource.NetSwipe.x) <= 0.001) && (doMagnetize))
        {
            float rest = alpha % alphaStep;
            if (rest < magnetizationRangeAlphaStepFraction * alphaStep)
            {
                if (rest < rotationSensibility)
                    magneticForce = -rest;
                else
                    magneticForce = -rest * magnetizationMaxForceFactor;

            }
            else if (rest > alphaStep * (1 - magnetizationRangeAlphaStepFraction))
            {
                if (rest < rotationSensibility)
                    magneticForce = rest;
                else
                    magneticForce = (alphaStep - rest) * magnetizationMaxForceFactor;
            }
        }
    }

First we check that there’s no input and that the magnetization flag is set to true, otherwise we’d only be wasting our CPU cycles. Then we get the difference between the current position and the nearest image thanks to the module operator and the alphaStep variable that gives us the distance between images.

Implementing the actual snap effect

Now we can distinguish two cases: either we need to snap to left, or we need to snap to right. But we also could be in the case that magnetizationRangeAlphaStepFraction is small enough to allow for a resting position between images and that shall be checked too. Once we decided this we’ll see if we’re so close that it’s better to just skip to the final destination (by using the whole rest value as our next magneticForce ) or if we should go move of a fraction of it (by multiplying for magnetizationMaxForceFactor ).

Well, now the slider looks way nicer but it still lacks an inertia option and the abilty to select stuff. You’ll have to wait a bit for that, until next part is ready. Join my newsletter, I’ll link it there! [edit: next part is here]

Here’s a copy-paste friendly recap of all the changes we made to the base script in last article

    [Header("Magnetizaiton Parameters")]
    public bool doMagnetize = true;
    public float magnetizationMaxForceFactor = 0.09f;
    public float magnetizationRangeAlphaStepFraction = 0.5f;
    protected float magneticForce;

    protected virtual void Update()
    {
        updateAlpha();
        updateMagnetization();
        if (Mathf.Abs(lastFrameAlpha - alpha) > 0.001f)
        {
            refreshImages();
        }
        lastFrameAlpha = alpha;
    }

    protected virtual void updateAlpha()
    {
        alpha = Mathf.Clamp(
                alpha + (Mathf.Abs(inputSource.NetSwipe.x) > 0 ?
                    inputSource.NetSwipe.x * rotationSensibility
                    :
                    magneticForce),
                0,
                1
                );
    }

    protected virtual void updateMagnetization()
    {
        if ((Mathf.Abs(inputSource.NetSwipe.x) <= 0.001) && (doMagnetize))
        {
            float rest = alpha % alphaStep;
            if (rest < magnetizationRangeAlphaStepFraction * alphaStep)
            {
                if (rest < rotationSensibility)
                    magneticForce = -rest;
                else
                    magneticForce = -rest * magnetizationMaxForceFactor;

            }
            else if (rest > alphaStep * (1 - magnetizationRangeAlphaStepFraction))
            {
                if (rest < rotationSensibility)
                    magneticForce = rest;
                else
                    magneticForce = (alphaStep - rest) * magnetizationMaxForceFactor;
            }
        }
    }

 

Share
  • 14
    Shares
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •