# Is there an algorithm for a Joystick class that happens to be engine/framework independent?

I need to be able to get the given input's distance inside the joystick, bounded within -100% and 100% and an unbounded version of it, get the position of where the mouse's position projects to the joystick's ring and get the angle from the joystick to the given input that also wouldn't need unit conversion since all of the inputs would always be in within terms of a given unit.

I happen to have written an algorithm that does exactly that!

import kotlin.math.*

/**
* As long as all your input is in X units, you shouldn't need any kind of conversion
*
* If you don't want the joystick to gravitate towards its origin position after some time,
* use the UI viewport's units instead of game viewport's units (as the UI viewport's camera doesnt move/gravitate)
*/
data class Joystick (var initialX: Float, var initialY: Float, var radius: Float = 1f){
var active = false
var x = 0f; var y = 0f; var degree = 0f; var stickDistPercentageX = 0f; var stickDistPercentageY = 0f
var distanceX = 0f; var distanceY = 0f; var realStickDistPercentageX = 0f; var realStickDistPercentageY = 0f
var cachedValues = Array(9){1f}.apply {
this[0] = x
this[1] = y
this[2] = degree
this[3] = stickDistPercentageX
this[4] = stickDistPercentageY
this[5] = distanceX
this[6] = distanceY
this[7] = realStickDistPercentageX
this[8] = realStickDistPercentageY
}

fun updateValues(mouseX: Float, mouseY: Float) : Array<Float>
{
var distanceY = mouseY - initialY
var distanceX = mouseX - initialX
if(distanceX == 0f || distanceY == 0f) return cachedValues
// not sure if I should keep this check, the atan2 function seems to function normally despite such inputs anyway.
var degree = atan2(distanceY,distanceX)

var outerCircleHalfR = sqrt(distanceX*distanceX+distanceY*distanceY)

var realStickDistPercentageX = cos(degree) *  abs(outerCircleHalfR/radius)
var realStickDistPercentageY = sin(degree) *  abs(outerCircleHalfR/radius)
var stickDistPercentageX = cos(degree) *  abs(outerCircleHalfR/radius).coerceIn(-1f, 1f)
var stickDistPercentageY = sin(degree) *  abs(outerCircleHalfR/radius).coerceIn(-1f, 1f)

var x = cos(degree) * radius
var y = sin(degree) * radius

this.x = x
this.y = y
this.degree = degree
this.stickDistPercentageX = stickDistPercentageX
this.stickDistPercentageY = stickDistPercentageY
this.distanceX = distanceX
this.distanceY = distanceY
this.realStickDistPercentageX = realStickDistPercentageX
this.realStickDistPercentageY = realStickDistPercentageY

return cachedValues
}
}


and an example usage:

if(joystick.active) {
run joy@
{
joystick.updateValues(unProjectMouse.x, unProjectMouse.y)
joystick.stickDistPercentageX * delta * IMV * 10,
joystick.stickDistPercentageY * delta * IMV * 10, 0f)
if(!DEBUG) return@joy
uiDebug.begin(ShapeRenderer.ShapeType.Line)
uiDebug.color = Color.YELLOW
uiDebug.end();uiDebug.begin(ShapeRenderer.ShapeType.Filled)
uiDebug.color = Color.WHITE
uiDebug.color = Color.CYAN
uiDebug.circle(joystick.initialX + joystick.x, joystick.initialY + joystick.y, joystick.radius/5, 50)
}
}

override fun touchDown(screenX: Int, screenY: Int, pointer: Int, button: Int): Boolean {
centralScreen.joystick.initialX = centralScreen.unProjectMouse.x
centralScreen.joystick.initialY = centralScreen.unProjectMouse.y
centralScreen.joystick.active = true
return false
}

override fun touchUp(screenX: Int, screenY: Int, pointer: Int, button: Int): Boolean {
centralScreen.joystick.active = false
return false
}


A screenshot of an illustration of the algorithm:

Also if you input a negative number for the radius, luckily, nothing breaks, and the only side effect (as far as I observed) is that the stick and the dot of the joystick happens to be in the direction opposite to the direction of the mouse

Now I'm sure that a lot of this code needs some explanation, so there we go.

First of all we call the updateValues function with the unprojected positions of the mouse to the viewport (The units of the InitialX, InitialY, radius, and the arguments of the updateValues function need to be consistent).

For those that don't know what Unproject is, (Non-LibGDX users I mean) | If you know it, skip this part.

Say the "screen" is 100PX wide and 100 PX tall and the camera in the program has a worldWidth of 5, which means that 1 unit equals 20 Pixels on the screen. and say, the camera is at the position, x,y: 5,5, the unproject function first converts the mouse positions into the internal units of the camera (the viewport), then adds the camera offset (how far away from the origin is it, AKA the position) and there you have your unprojected value!

The stickDistPercentage value of the joystick is how far away it is from the radius in percentages, the value ranges from -1 (-100%) to +1 (100%). Since it is within -1 to +1, you can directly use it in multiplicative operations without any further conversion of it. The realStickDistPercentage is the same as this, just that it is not in the bounds of -100% to 100%

The IMV stands for InverseMouseMovement (just realized that I named it mouse movement); either -1 or 1, pull or push basically.

Line 11 draws the maximum bounds of the joystick. 50 stands for how circle-y it should look, it is outside our topic.

Line 14 draws the "stick" of the joystick that has a radius of Joystick.radius/3

Line 17 draws the dot on the ring itself.

The x, y is the dot in the ring of the circle, it isn't necessarily mandatory for a joystick but I kept it here because it might be useful in different cases.

The degree variable is the degree between the initial position and the given input position basically, don't know how to further describe it.

The distance is the distance between the initial position and the given input position

• Please use Stack Exchange's built in image posting/hosting instead of third party image links. Over time, third party links tend to break. Refer to the bottom of the formatting help page for more info on how to post images here. Commented Jan 25, 2022 at 17:18