Take the 2-minute tour ×
Game Development Stack Exchange is a question and answer site for professional and independent game developers. It's 100% free, no registration required.

I'm using Unity3d iOs and am using the example joysticks that came with one of the packages. It works fine but the area the joystick moves in is a rectangle which is unintuitive for my type of game. I can figure out how to see if the distance between the center and the current point is too far but I can't figure out how to constrain it to a certain distance without interrupting the finger tracking.

Here's the relevant code:

using UnityEngine;
using System.Collections;
public class Boundary 
{
public Vector2 min = Vector2.zero;
public Vector2 max = Vector2.zero;
}

public class Joystick : MonoBehaviour{ 
static private Joystick[] joysticks;                    // A static collection of all joysticks
static private bool enumeratedJoysticks=false;
static private float tapTimeDelta = 0.3f;               // Time allowed between taps

public bool touchPad;                                   // Is this a TouchPad?
public Rect touchZone;
public Vector2 deadZone = Vector2.zero;                     // Control when position is output
public bool normalize = false;                          // Normalize output after the dead-zone?
public Vector2 position;                                    // [-1, 1] in x,y
public int tapCount;                                            // Current tap count

private int lastFingerId = -1;                              // Finger last used for this joystick
private float tapTimeWindow;                            // How much time there is left for a tap to occur
private Vector2 fingerDownPos;
private float fingerDownTime;
private float firstDeltaTime = 0.5f;

private GUITexture gui;                             // Joystick graphic
private Rect defaultRect;                               // Default position / extents of the joystick graphic
private Boundary guiBoundary = new Boundary();          // Boundary for joystick graphic
public Vector2 guiTouchOffset;                      // Offset to apply to touch input
private Vector2 guiCenter;                          // Center of joystick

private Vector3 tmpv3;
private Rect tmprect;
private Color tmpclr;

public float allowedDistance;

public enum JoystickType 
{
    movement, rotation
}

public JoystickType joystickType;

public void Start()
{
    // Cache this component at startup instead of looking up every frame    
    gui = (GUITexture) GetComponent( typeof(GUITexture) );

    // Store the default rect for the gui, so we can snap back to it
    defaultRect = gui.pixelInset;   

    if ( touchPad )
    {
        // If a texture has been assigned, then use the rect ferom the gui as our touchZone
        if ( gui.texture )
            touchZone = gui.pixelInset;
    }
    else
    {               
        // This is an offset for touch input to match with the top left
        // corner of the GUI
        guiTouchOffset.x = defaultRect.width * 0.5f;
        guiTouchOffset.y = defaultRect.height * 0.5f;

        // Cache the center of the GUI, since it doesn't change
        guiCenter.x = defaultRect.x + guiTouchOffset.x;
        guiCenter.y = defaultRect.y + guiTouchOffset.y;

        // Let's build the GUI boundary, so we can clamp joystick movement
        guiBoundary.min.x = defaultRect.x - guiTouchOffset.x;
        guiBoundary.max.x = defaultRect.x + guiTouchOffset.x;
        guiBoundary.min.y = defaultRect.y - guiTouchOffset.y;
        guiBoundary.max.y = defaultRect.y + guiTouchOffset.y;
    }
}

public void Disable()
{
    gameObject.active = false;
    enumeratedJoysticks = false;
}

public void ResetJoystick()
{
    if (joystickType != JoystickType.rotation)
    {
        //Don't do anything if turret mode
        // Release the finger control and set the joystick back to the default position
        gui.pixelInset = defaultRect;
        lastFingerId = -1;
        position = Vector2.zero;
        fingerDownPos = Vector2.zero;

        if ( touchPad ){
            tmpclr  = gui.color;
            tmpclr.a = 0.025f;
            gui.color = tmpclr;
        }   
    }
    else {
                    //gui.pixelInset = defaultRect;
        lastFingerId = -1;
        position = position;
        fingerDownPos = fingerDownPos;

        if ( touchPad ){
            tmpclr  = gui.color;
            tmpclr.a = 0.025f;
            gui.color = tmpclr;
        }
    }
}

public bool IsFingerDown()
{
    return (lastFingerId != -1);
}

public void LatchedFinger( int fingerId )
{
    // If another joystick has latched this finger, then we must release it
    if ( lastFingerId == fingerId )
        ResetJoystick();
}

public void Update()
{   
    if ( !enumeratedJoysticks )
    {
        // Collect all joysticks in the game, so we can relay finger latching messages
        joysticks = (Joystick[])  FindObjectsOfType( typeof(Joystick) );
        enumeratedJoysticks = true;
    }   

    //CHeck if distance is over the allowed amount
    //Get centerPosition

    //Get current position

    //Get distance

    //If over, don't allow

    int count = iPhoneInput.touchCount;

    // Adjust the tap time window while it still available
    if ( tapTimeWindow > 0 )
        tapTimeWindow -= Time.deltaTime;
    else
        tapCount = 0;

    if ( count == 0 )
        ResetJoystick();
    else
    {
        for(int i = 0;i < count; i++)
        {
            iPhoneTouch touch = iPhoneInput.GetTouch(i);            
            Vector2 guiTouchPos = touch.position - guiTouchOffset;

            bool shouldLatchFinger = false;
            if ( touchPad )
            {               
                if ( touchZone.Contains( touch.position ) )
                    shouldLatchFinger = true;
            }
            else if ( gui.HitTest( touch.position ) )
            {
                shouldLatchFinger = true;
            }       

            // Latch the finger if this is a new touch
            if ( shouldLatchFinger && ( lastFingerId == -1 || lastFingerId != touch.fingerId ) )
            {

                if ( touchPad )
                {
                    tmpclr = gui.color;
                    tmpclr.a = 0.15f;
                    gui.color = tmpclr;

                    lastFingerId = touch.fingerId;
                    fingerDownPos = touch.position;
                    fingerDownTime = Time.time;
                }

                lastFingerId = touch.fingerId;

                // Accumulate taps if it is within the time window
                if ( tapTimeWindow > 0 )
                {
                    tapCount++;
                    print("tap" + tapCount.ToString());
                }
                else
                {
                    tapCount = 1;
                    print("tap" + tapCount.ToString());
                    //Tell gameobject that player has tapped turret joystick
                    if (joystickType == JoystickType.rotation) {
                        //TODO: Call!
                    }
                    tapTimeWindow = tapTimeDelta;
                }

                // Tell other joysticks we've latched this finger
                foreach ( Joystick j in joysticks )
                {
                    if ( j != this )
                        j.LatchedFinger( touch.fingerId );
                }                       
            }               

            if ( lastFingerId == touch.fingerId )
            {   
                // Override the tap count with what the iPhone SDK reports if it is greater
                // This is a workaround, since the iPhone SDK does not currently track taps
                // for multiple touches
                if ( touch.tapCount > tapCount )
                    tapCount = touch.tapCount;

                if ( touchPad )
                {   
                    // For a touchpad, let's just set the position directly based on distance from initial touchdown
                    position.x = Mathf.Clamp( ( touch.position.x - fingerDownPos.x ) / ( touchZone.width / 2 ), -1, 1 );
                    position.y = Mathf.Clamp( ( touch.position.y - fingerDownPos.y ) / ( touchZone.height / 2 ), -1, 1 );
                }
                else
                {                   
                    // Change the location of the joystick graphic to match where the touch is
                    tmprect = gui.pixelInset; 
                    tmprect.x = Mathf.Clamp( guiTouchPos.x, guiBoundary.min.x, guiBoundary.max.x );
                    tmprect.y = Mathf.Clamp( guiTouchPos.y, guiBoundary.min.y, guiBoundary.max.y );

                    //Check distance
                    float distance = Vector2.Distance(new Vector2(defaultRect.x, defaultRect.y), new Vector2(tmprect.x, tmprect.y));
                    float angle = Vector2.Angle(new Vector2(defaultRect.x, defaultRect.y), new Vector2(tmprect.x, tmprect.y));
                    if (distance < allowedDistance) {
                        //Ok
                        gui.pixelInset = tmprect;
                    }
                    else {
                                                 //This is where I don't know what to do...
                    }


                }

                if ( touch.phase == iPhoneTouchPhase.Ended || touch.phase == iPhoneTouchPhase.Canceled )
                    ResetJoystick();                    
            }           
        }
    }

    if ( !touchPad )
    {
        // Get a value between -1 and 1 based on the joystick graphic location
        position.x = ( gui.pixelInset.x + guiTouchOffset.x - guiCenter.x ) / guiTouchOffset.x;
        position.y = ( gui.pixelInset.y + guiTouchOffset.y - guiCenter.y ) / guiTouchOffset.y;
    }

    // Adjust for dead zone 
    float absoluteX = Mathf.Abs( position.x );
    float absoluteY = Mathf.Abs( position.y );

    if ( absoluteX < deadZone.x )
    {
        // Report the joystick as being at the center if it is within the dead zone
        position.x = 0;
    }
    else if ( normalize )
    {
        // Rescale the output after taking the dead zone into account
        position.x = Mathf.Sign( position.x ) * ( absoluteX - deadZone.x ) / ( 1 - deadZone.x );
    }

    if ( absoluteY < deadZone.y )
    {
        // Report the joystick as being at the center if it is within the dead zone
        position.y = 0;
    }
    else if ( normalize )
    {
        // Rescale the output after taking the dead zone into account
        position.y = Mathf.Sign( position.y ) * ( absoluteY - deadZone.y ) / ( 1 - deadZone.y );
    }


}

}

So the later portion of the code handles the updated position of the joystick thumb. This is where I'd like it to track the finger position in a direction it still is allowed to move (like if the finger is too far up and slightly to the +X I'd like to make sure the joystick is as close in X and Y as allowed within the radius)

Thanks for reading!

share|improve this question
add comment

1 Answer

up vote 4 down vote accepted

I'm not at all familiar with unity or iOS, however this sounds like a maths problem.

As your positions go from -1 to +1 all you need to do is see if the distance to the point is greater than 1.0 (i.e. outside a circle) and if it is normalize it so it's on the circle. Something like:

float distance = Mathf.Sqrt(position.x * position.x + position.y * position.y);
if (distance > 1.0f)
{
    position.x /= distance;
    position.y /= distance;
}
share|improve this answer
    
Thanks, I've never been good at maths unfortunately.. –  Phil Mar 6 '11 at 12:28
    
I get the distance to be 1.525879E-07 constantly.. any idea why? –  Phil Mar 6 '11 at 12:44
    
In fact it's probably because position.x seems to be -1.525679E-07... I'll try to figure our why. –  Phil Mar 6 '11 at 12:48
    
If that's in the centre, that'd make sense, as that pretty close to zero, roughly 0.0000001525679 (According to Wolfram Alpha) –  echeese Mar 8 '11 at 1:12
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.