Restricting joystick within a radius of center

Posted by Phil on Game Development See other posts from Game Development or by Phil
Published on 2011-03-05T22:48:40Z Indexed on 2011/03/05 23:33 UTC
Read the original article Hit count: 394

Filed under:
|
|
|

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!

© Game Development or respective owner

Related posts about c#

Related posts about unity