Touchscreen Steering Wheel Rotation Example (Mouse Supported)

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!

15 Likes

Finally found it… Have been looking for this for almost a day… going to use it and tweak it according to my needs… will let you know how it works out for me…

I’m glad that you liked it. Thanks for giving it a try.

This Script is very helpful, i am working on car parking game when i build this for android my steering wheel positon and scaling is disturb. please help.

Are you attaching the script to a GUI Texture? If so, don’t do this because script uses OnGUI function to manually draw wheel texture instead of using the GUI Texture. However GUI Texture idea seems really interesting. Maybe I can give GUI Texture support, too.

If your issue is not about attaching script to a GUITexture, please check whether your wheel texture’s dimensions are equal (width = height). When you are sure that dimensions are equal, you can scale the wheel using the wheelSize variable and change its position manually by changing the “wheelPosition” in the Start function.

Please let me know if you have anymore issues. Thank you for your interest in this script :slight_smile:

Yes its works thanks… I have one more issue this game have multi touch event so the problem is when I use this script for rotating my car the slider don’t work. I am attaching image and also wheel rotation script .

1704114--107209--Untitled-2.jpg
1704114–107210–WheelRotation.cs (4.42 KB)

You are using “GUI.VerticalSlider” to create the slider. However, unfortunately, none of the GUI elements (like buttons, toggles, sliders etc.) support multi-touch.

As far as I observed, when there are two fingers on the screen, the middle point of these two touches’ position is considered as the touch position by the Unity GUI system. Multi-touch can only be achieved using Input.touches (and sometimes fingerId). Steering wheel supports multi-touch but I think you can’t change the default behaviour of Unity sliders, which don’t use Input.touches and are expecting only one touch on the screen at a time.

How to solve this problem? I can’t think of anything other than creating your own slider class from scratch.

if I attach the script to a cube…how do I rotate the cube with this script?

When you give the wheelTexture property its value (a steering wheel texture), it will be shown on the screen (in Game View). Then you can access the script and use its GetAngle() function to rotate the cube. GetAngle() returns the angle of the wheel (negative if you turn it left, positive if you turn it right). You may use Physics.AddForce (or possibly AddForceAtPosition) to rotate the cube.

Can i attach another GuiTexture as a button for speed of car in your wheel rotation script.

if( wheelBeingHeld )
{
foreach(Touch t in Input.touches )
{
if( t.fingerId == touchId )
{
touchPosition = t.position;
if( t.phase == TouchPhase.Ended || t.phase == TouchPhase.Canceled )
{
wheelBeingHeld = false;
}
}
}
float wheelNewAngle = Vector2.Angle( Vector2.up, touchPosition - wheelCenter);

if( Vector2.Distance( touchPosition, wheelCenter ) > 20f )
{
if( touchPosition.x > wheelCenter.x )
wheelAngle += wheelNewAngle - wheelTempAngle;
else
wheelAngle -= wheelNewAngle - wheelTempAngle;
}

if( wheelAngle > maximumAngle )
wheelAngle = maximumAngle;
else if( wheelAngle < -maximumAngle )
wheelAngle = -maximumAngle;

wheelTempAngle = wheelNewAngle;
}
else
{
foreach( Touch touch in Input.touches ){
if( touch.phase == TouchPhase.Began)
{
if( wheelPosition.Contains( new Vector2( touch.position.x, Screen.height - touch.position.y ) ) )
{
wheelBeingHeld = true;
wheelTempAngle = Vector2.Angle( Vector2.up, touch.position - wheelCenter );
touchId =touch.fingerId;
}
}

if (touch.phase == TouchPhase.Began && speed.HitTest (touch.position)) {
speedupcar+=Time.deltaTime;
}
else if (touch.phase == TouchPhase.Ended){
speedupcar-=Time.deltaTime;
}
}
if( !Mathf.Approximately( 0f, wheelAngle ) )
{
float deltaAngle = wheelFreeSpeed * Time.deltaTime;

if( Mathf.Abs( deltaAngle ) > Mathf.Abs( wheelAngle ) )
{
wheelAngle = 0f;
return;
}
if( wheelAngle > 0f )
wheelAngle -= deltaAngle;
else
wheelAngle += deltaAngle;
}
}

It’s better that you create another script for speed handling. For example you can try this one (haven’t tested it):

using UnityEngine;

public class YOUR_SCRIPT_NAME : MonoBehaviour
{
   private int fingerId = -1;
   public GUITexture speed;
   public float speedupcar;

   void Update()
   {
     bool texturePressed = false;
     
     foreach( Touch t in Input.touches )
     {
       // don't allow more than one fingers on the texture
       if( !texturePressed )
       {
         if( fingerId == -1 && t.phase == TouchPhase.Began && speed.HitTest( t.position ) )
         {
           fingerId = t.fingerId;
           speedupcar += Time.deltaTime;
           texturePressed = true;
         }
         else if( t.fingerId == touchId )
         {
           speedupcar += Time.deltaTime;
           texturePressed = true;
         }
       }
     }
     
     if( !texturePressed )
       fingerId = -1;
   }
}

Dude…thanks for a great script

This script Work For me Thanks For The Help.

there is one more issue. when i resize my game window in unity why this steering is out of the window. and when i use this script the wheel position is fix but it did’t rotate.
void OnGUI()
{

Vector2 ratio = new Vector2 (Screen.width / originalWidth, Screen.height / originalHeight);
Matrix4x4 guiMatrix = Matrix4x4.identity;
guiMatrix.SetTRS (new Vector3 (1, 1, 1), Quaternion.identity, new Vector3 (ratio.x, ratio.y, 1));
GUI.matrix = guiMatrix;

Matrix4x4 theMatrix = GUI.matrix;
GUIUtility.RotateAroundPivot( wheelAngle, wheelPosition.center + deltaPivot );
GUI.DrawTexture( wheelPosition, wheelTexture );
GUI.matrix = theMatrix;
GUI.matrix = Matrix4x4.identity;
}
}


I’m not sure what your script does (I’m not good at matrices). However I know why the wheel got out of the screen. The position of the wheel is only calculated at the Start function for better performance. Normally, your screen size shouldn’t change run-time in a real device. So, I think it is OK like this. If you resize the window and then start the game, wheel should be fine.

Thanks dear its really helpful for me :slight_smile:

This is great thanks so much for sharing this with us!!

Thanks dear. :slight_smile:

Just One question. How can I access Getangle() function into my C# Code. I want it here.
FrontLeftWheel.steerAngle =

If your SteeringWheel script is in a GameObject called “WheelObject”, you should type this code:

FrontLeftWheel.steerAngle = GameObject.Find( "WheelObject" ).GetComponent<SteeringWheel>().GetAngle();

If both scripts are in the same GameObject, you can simply use this code:

FrontLeftWheel.steerAngle = GetComponent<SteeringWheel>().GetAngle();

If your compiler gives an error like SteeringWheel is unknown identifier, then try putting SteeringWheel script in a folder called “Plugins” (source: http://answers.unity3d.com/questions/48874/accessing-javascript-variable-from-c-and-vice-vers.html)

For better performance, you can cache SteeringWheel component in a variable and use this variable to access the script (it is faster). Id est:

private SteeringWheel wheelVariable;

void Start() {
wheelVariable = GetComponent<SteeringWheel>();
}
FrontLeftWheel.steerAngle = wheelVariable.GetAngle();

Note that GetAngle() function returns the angle of the steering wheel in degrees. If steerAngle variable expects a value between 0 and 1, then change the GetAngle() function like this:

return wheelAngle / maximumAngle;