Unity3D Joystick Script


Here we are again. 3 cups of coffee later and another script conversion. I’ve taken the time to convert the Penelope Joystick.cs JavaScript script into C-Sharp. Why would someone do this? I did it because I am getting tired of anything that is not strongly typed in Unity3D. If you are writing your engine in JavaScript, you’re probably making variables with or without typos that don’t have the scope you thought they did and worse, you might have a 6-8 hour hot date with your code when you can’t figure out why it’s crashing due to some strange “type assumptions”. Don’t get me wrong, hot dates are a blast, especially when they aren’t with your computer.

In my usual way, here is the Joystick script converted over into C-Sharp.

May 17, 2011: Oh one more thing, I’m posting this early, call it jumping the gun, I haven’t tested it yet. I’ll be testing in the next 24 hours.

View Code CSHARP
using UnityEngine;
 
/**
 * File: MPJoystick.cs
 * Author: Chris Danielson of (monkeyprism.com)
 * 
// USED TO BE: Joystick.js taken from Penelope iPhone Tutorial
//
// Joystick creates a movable joystick (via GUITexture) that 
// handles touch input, taps, and phases. Dead zones can control
// where the joystick input gets picked up and can be normalized.
//
// Optionally, you can enable the touchPad property from the editor
// to treat this Joystick as a TouchPad. A TouchPad allows the finger
// to touch down at any point and it tracks the movement relatively 
// without moving the graphic
*/
 
[RequireComponent(typeof(GUITexture))]
public class MPJoystick : MonoBehaviour
{
	class Boundary {
		public Vector2 min = Vector2.zero;
		public Vector2 max = Vector2.zero;
	}
 
	private static MPJoystick[] joysticks;					// A static collection of all joysticks
	private static bool enumeratedJoysticks = false;
	private static float tapTimeDelta = 0.3f;				// Time allowed between taps
 
	public bool touchPad;
	public Vector2 position = Vector2.zero;
	public Rect touchZone;
	public Vector2 deadZone = Vector2.zero;						// Control when position is output
	public bool normalize = false; 							// Normalize output after the dead-zone?
	public int tapCount;	
 
	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;
	private Rect defaultRect;								// Default position / extents of the joystick graphic
	private Boundary guiBoundary = new Boundary();			// Boundary for joystick graphic
	private Vector2 guiTouchOffset;						// Offset to apply to touch input
	private Vector2 guiCenter;							// Center of joystick
 
	void Start() {
		gui = (GUITexture)GetComponent(typeof(GUITexture));
 
		defaultRect = gui.pixelInset;
		defaultRect.x += transform.position.x * Screen.width;// + gui.pixelInset.x; // -  Screen.width * 0.5;
    	defaultRect.y += transform.position.y * Screen.height;// - Screen.height * 0.5;
 
		transform.position = Vector3.zero;
 
		if (touchPad) {
			// If a texture has been assigned, then use the rect ferom the gui as our touchZone
			if ( gui.texture )
				touchZone = defaultRect;
		} else {	
			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 Vector2 getGUICenter() {
		return guiCenter;
	}
 
	void Disable() {
		gameObject.active = false;
		//enumeratedJoysticks = false;	
	}
 
	private void ResetJoystick() {
		gui.pixelInset = defaultRect;
		lastFingerId = -1;
		position = Vector2.zero;
		fingerDownPos = Vector2.zero;	
	}
 
	private 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();
	}
 
	void Update() {
		if (!enumeratedJoysticks) {
			// Collect all joysticks in the game, so we can relay finger latching messages
			joysticks = (MPJoystick[])FindObjectsOfType(typeof(MPJoystick));	
			enumeratedJoysticks = true;
		}
 
		int count = Input.touchCount;
 
		if ( tapTimeWindow > 0 )
			tapTimeWindow -= Time.deltaTime;
		else
			tapCount = 0;
 
		if ( count == 0 )
			ResetJoystick();
		else
		{
			for(int i = 0; i < count; i++) {
				Touch touch = Input.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) {
						//gui.color.a = 0.15;
						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++;
					else {
						tapCount = 1;
						tapTimeWindow = tapTimeDelta;
					}
 
					// Tell other joysticks we've latched this finger
					//for (  j : Joystick in joysticks )
					foreach (MPJoystick 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
						Rect r = gui.pixelInset;
						r.x =  Mathf.Clamp( guiTouchPos.x, guiBoundary.min.x, guiBoundary.max.x );
						r.y =  Mathf.Clamp( guiTouchPos.y, guiBoundary.min.y, guiBoundary.max.y );		
						gui.pixelInset = r;
					}
 
					if (touch.phase == TouchPhase.Ended || touch.phase == TouchPhase.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	
		var absoluteX = Mathf.Abs( position.x );
		var 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 );
		}
 
	}
 
}

, , , , ,

  1. #1 by Mithos on November 10th, 2011

    Hi,

    So I made a GUITexture and draged the script on to it and it said:

    “Can’t add script behaviour joystick. The scripts file name does not match the name of the class defined in the script!”

    No matter what I name. So maybe I am not implementing it correctly.

    Maybe some tips on how to implement otherwise it looks good.

    And thank you very much for taking the time and effort to make this, I much appreciate it.

    Yours sincerely

    Mithos Annar

  2. #2 by Chris Danielson on November 11th, 2011

    MIthos,
    What folder did you drag the script into? Also, double check that you named the script “MPJoystick”. See if that works for you. -chris

  3. #3 by Mithos on November 12th, 2011

    Hey, thanks for the reply!

    Ok, so I made a GUI Texture, named it MPJoystick, tried to drag the script on, and it gave me that error. Also I named the script MPJoystick.

    Is it because I have the free trial?

    I don’t know why its doing that… but the script looks really good.

  4. #4 by Mithos on November 12th, 2011

    OH LOL….

    you’re not going to believe this, I dragged it onto the camera and the script was on both the camera and the GUITexture.
    Also I think you should know that it works perfectly.

    Once again thank you very much for writing that in c.

    R+

  5. #5 by Michel on February 27th, 2012

    Hi, appreciate your script.

    I am new to the Unity3d and JS.

    Can you please tell me how to use the script. I have been trying for many times. It doesnt work for me. But i know it is my problem.

    Appreciate your help! i am a student, learning making joystick for sch project.

  6. #6 by Talirris Smith on October 2nd, 2012

    Thanks for the C# code, I’m new to unity and this was very helpful!

  7. #7 by lief on January 7th, 2013

    I’ve implement that but it don’t work.
    because the player don’t move.
    Can you tell me how Can i implement with the 3rd controller person script? (that I use for PC).
    i already have the prefab

(will not be published)