# Restricting joystick within a radius of center

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

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)

-

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;
}
``````
-
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) – Ryan Speets Mar 8 '11 at 1:12