UPDATE (16/04/2018): Updated the UI script to work correctly even after screen resolution/orientation changes runtime
Hi there,
I’d like to share with you a simple code to simulate a 2D Steering Wheel for use in (mostly) mobile racing games. There are two versions of the code and you only need to use one of them:
- Using new UI system
I used C# for this version. You need to create a UI Image containing the wheel sprite anywhere in your canvas. Do not add an Event Trigger component to the Image if possible (otherwise, I don’t know what will happen). Make sure that the pivot of the Image is set correctly (i.e. changing the Rotation-Z value of the Image does not rotate it awkwardly).
After everything is set, add the script below as a component to any GameObject you want and then give the steering wheel Image as value to the UI_Element variable via Inspector. And finally, test it**!**
NOTE: Use GetClampedValue() to get a value in range [-1,1] (both inclusive), and GetAngle() to get the angle of the steering wheel directly (not recommended).
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
using UnityEngine.Events;
using System.Collections;
public class SteeringWheel : MonoBehaviour
{
public Graphic UI_Element;
RectTransform rectT;
Vector2 centerPoint;
public float maximumSteeringAngle = 200f;
public float wheelReleasedSpeed = 200f;
float wheelAngle = 0f;
float wheelPrevAngle = 0f;
bool wheelBeingHeld = false;
public float GetClampedValue()
{
// returns a value in range [-1,1] similar to GetAxis("Horizontal")
return wheelAngle / maximumSteeringAngle;
}
public float GetAngle()
{
// returns the wheel angle itself without clamp operation
return wheelAngle;
}
void Start()
{
rectT = UI_Element.rectTransform;
InitEventsSystem();
}
void Update()
{
// If the wheel is released, reset the rotation
// to initial (zero) rotation by wheelReleasedSpeed degrees per second
if( !wheelBeingHeld && !Mathf.Approximately( 0f, wheelAngle ) )
{
float deltaAngle = wheelReleasedSpeed * Time.deltaTime;
if( Mathf.Abs( deltaAngle ) > Mathf.Abs( wheelAngle ) )
wheelAngle = 0f;
else if( wheelAngle > 0f )
wheelAngle -= deltaAngle;
else
wheelAngle += deltaAngle;
}
// Rotate the wheel image
rectT.localEulerAngles = Vector3.back * wheelAngle;
}
void InitEventsSystem()
{
// Warning: Be ready to see some extremely boring code here :-/
// You are warned!
EventTrigger events = UI_Element.gameObject.GetComponent<EventTrigger>();
if( events == null )
events = UI_Element.gameObject.AddComponent<EventTrigger>();
if( events.triggers == null )
events.triggers = new System.Collections.Generic.List<EventTrigger.Entry>();
EventTrigger.Entry entry = new EventTrigger.Entry();
EventTrigger.TriggerEvent callback = new EventTrigger.TriggerEvent();
UnityAction<BaseEventData> functionCall = new UnityAction<BaseEventData>( PressEvent );
callback.AddListener( functionCall );
entry.eventID = EventTriggerType.PointerDown;
entry.callback = callback;
events.triggers.Add( entry );
entry = new EventTrigger.Entry();
callback = new EventTrigger.TriggerEvent();
functionCall = new UnityAction<BaseEventData>( DragEvent );
callback.AddListener( functionCall );
entry.eventID = EventTriggerType.Drag;
entry.callback = callback;
events.triggers.Add( entry );
entry = new EventTrigger.Entry();
callback = new EventTrigger.TriggerEvent();
functionCall = new UnityAction<BaseEventData>( ReleaseEvent );//
callback.AddListener( functionCall );
entry.eventID = EventTriggerType.PointerUp;
entry.callback = callback;
events.triggers.Add( entry );
}
public void PressEvent( BaseEventData eventData )
{
// Executed when mouse/finger starts touching the steering wheel
Vector2 pointerPos = ( (PointerEventData) eventData ).position;
wheelBeingHeld = true;
centerPoint = RectTransformUtility.WorldToScreenPoint( ( (PointerEventData) eventData ).pressEventCamera, rectT.position );
wheelPrevAngle = Vector2.Angle( Vector2.up, pointerPos - centerPoint );
}
public void DragEvent( BaseEventData eventData )
{
// Executed when mouse/finger is dragged over the steering wheel
Vector2 pointerPos = ( (PointerEventData) eventData ).position;
float wheelNewAngle = Vector2.Angle( Vector2.up, pointerPos - centerPoint );
// Do nothing if the pointer is too close to the center of the wheel
if( Vector2.Distance( pointerPos, centerPoint ) > 20f )
{
if( pointerPos.x > centerPoint.x )
wheelAngle += wheelNewAngle - wheelPrevAngle;
else
wheelAngle -= wheelNewAngle - wheelPrevAngle;
}
// Make sure wheel angle never exceeds maximumSteeringAngle
wheelAngle = Mathf.Clamp( wheelAngle, -maximumSteeringAngle, maximumSteeringAngle );
wheelPrevAngle = wheelNewAngle;
}
public void ReleaseEvent( BaseEventData eventData )
{
// Executed when mouse/finger stops touching the steering wheel
// Performs one last DragEvent, just in case
DragEvent( eventData );
wheelBeingHeld = false;
}
}
- Using legacy GUI (using OnGUI) system
Script supports both PC (& Linux & MAC) and mobile platforms. You can fetch the value of the steering wheel using the GetAngle() function (see comments). If you would like to change the position of the steering wheel on screen, all you need to do is to change the value of wheelPosition in the Start() function.
Here is the Javascript code:
#pragma strict
public var maximumAngle : float = 500f; // Maximum angle the steering wheel can rotate
public var wheelSize : float = 256f; // Wheel's width (and height) as pixel
public var deltaPivot : Vector2 = Vector2.zero; // If wheel not rotates around its center, this variable allows tweaking the pivot point
public var wheelFreeSpeed : float = 200f; // Degrees per second the wheel rotates when released
public var wheelTexture : Texture2D; // Wheel texture
private var wheelAngle : float; // Wheel's angle in degrees
private var wheelBeingHeld : boolean; // Whether or not the steering wheel is being held
private var wheelPosition : Rect; // Wheel's position on screen
private var wheelCenter : Vector2; // Wheel's center on screen coordinates (not Rect coordinates)
private var wheelTempAngle : float; // A necessary variable
function Start()
{
// Initialize variables and calculate wheel's position on screen
wheelBeingHeld = false;
wheelPosition = new Rect( Screen.width - wheelSize - 75, Screen.height - wheelSize - 75, wheelSize, wheelSize );
wheelCenter = new Vector2( wheelPosition.x + wheelPosition.width * 0.5f, Screen.height - wheelPosition.y - wheelPosition.height * 0.5f );
wheelAngle = 0f;
}
// Returns the angle of the steering wheel. Can be used to rotate a car etc.
// Resulting value is always between -1.0 (inclusive) and 1.0 (inclusive)
// Resulting value will be positive while the wheel is rotated to the right
// Resulting value will be negative while the wheel is rotated to the left
public function GetAngle()
{
return wheelAngle / maximumAngle;
}
// Draw the steering wheel on screen
function OnGUI()
{
// Uncomment the line below to see the bounds of the wheel
// GUI.Box( wheelPosition, "" );
var theMatrix : Matrix4x4 = GUI.matrix;
GUIUtility.RotateAroundPivot( wheelAngle, wheelPosition.center + deltaPivot );
GUI.DrawTexture( wheelPosition, wheelTexture );
GUI.matrix = theMatrix;
}
#if UNITY_EDITOR || UNITY_STANDALONE || UNITY_WEBPLAYER
/*
** This code is executed on Unity Editor, Windows, MAC, Linux and Web Player only
*/
function Update()
{
// If the wheel is currently being held
if( wheelBeingHeld )
{
var mousePosition : Vector2;
// Find the mouse position on screen
mousePosition = Input.mousePosition;
var wheelNewAngle : float = Vector2.Angle( Vector2.up, mousePosition - wheelCenter );
// If mouse is very close to the steering wheel's center, do nothing
if( Vector2.Distance( mousePosition, wheelCenter ) > 20f )
{
if( mousePosition.x > wheelCenter.x )
wheelAngle += wheelNewAngle - wheelTempAngle;
else
wheelAngle -= wheelNewAngle - wheelTempAngle;
}
// Make sure that the wheelAngle does not exceed the maximumAngle
if( wheelAngle > maximumAngle )
wheelAngle = maximumAngle;
else if( wheelAngle < -maximumAngle )
wheelAngle = -maximumAngle;
wheelTempAngle = wheelNewAngle;
// If user releases the mouse, release the wheel
if( Input.GetMouseButtonUp( 0 ) )
wheelBeingHeld = false;
}
else // If wheel is not being held
{
// If user clicks on the wheel, update the status
if( Input.GetMouseButtonDown( 0 ) && wheelPosition.Contains( new Vector2( Input.mousePosition.x, Screen.height - Input.mousePosition.y ) ) )
{
wheelBeingHeld = true;
wheelTempAngle = Vector2.Angle( Vector2.up, Input.mousePosition - wheelCenter );
}
// If the wheel is rotated and not being held, rotate it to its default angle (zero)
if( !Mathf.Approximately( 0f, wheelAngle ) )
{
var deltaAngle : float = wheelFreeSpeed * Time.deltaTime;
if( Mathf.Abs( deltaAngle ) > Mathf.Abs( wheelAngle ) )
{
wheelAngle = 0f;
return;
}
if( wheelAngle > 0f )
wheelAngle -= deltaAngle;
else
wheelAngle += deltaAngle;
}
}
}
#else
/*
** This code is executed on mobile platforms only
*/
private var touchId : int = -1; // The finger holding the wheel
function Update()
{
// If the wheel is currently being held
if( wheelBeingHeld )
{
var touchPosition : Vector2;
// Find the finger position on screen
for( var t : Touch in Input.touches )
{
if( t.fingerId == touchId )
{
touchPosition = t.position;
// If finger exists no more, release the wheel
if( t.phase == TouchPhase.Ended || t.phase == TouchPhase.Canceled )
{
wheelBeingHeld = false;
}
}
}
var wheelNewAngle : float = Vector2.Angle( Vector2.up, touchPosition - wheelCenter );
// If touch is very close to the steering wheel's center, do nothing
if( Vector2.Distance( touchPosition, wheelCenter ) > 20f )
{
if( touchPosition.x > wheelCenter.x )
wheelAngle += wheelNewAngle - wheelTempAngle;
else
wheelAngle -= wheelNewAngle - wheelTempAngle;
}
// Make sure that the wheelAngle does not exceed the maximumAngle
if( wheelAngle > maximumAngle )
wheelAngle = maximumAngle;
else if( wheelAngle < -maximumAngle )
wheelAngle = -maximumAngle;
wheelTempAngle = wheelNewAngle;
}
else // If wheel is not being held
{
// If a finger touches the wheel, update the status
for( var t : Touch in Input.touches )
{
if( t.phase == TouchPhase.Began )
{
if( wheelPosition.Contains( new Vector2( t.position.x, Screen.height - t.position.y ) ) )
{
wheelBeingHeld = true;
wheelTempAngle = Vector2.Angle( Vector2.up, t.position - wheelCenter );
touchId = t.fingerId;
}
}
}
// If the wheel is rotated and not being held, rotate it to its default angle (zero)
if( !Mathf.Approximately( 0f, wheelAngle ) )
{
var deltaAngle : float = wheelFreeSpeed * Time.deltaTime;
if( Mathf.Abs( deltaAngle ) > Mathf.Abs( wheelAngle ) )
{
wheelAngle = 0f;
return;
}
if( wheelAngle > 0f )
wheelAngle -= deltaAngle;
else
wheelAngle += deltaAngle;
}
}
}
#endif
If you’d like to see it (the legacy version) in action, here is an example scene in which you control a car using the steering wheel.
Hope you like it. If you have a question, please ask…
Have a nice day!