VirtualRelativeJoystick

From GiderosMobile

VirtualRelativeJoystick Class

This is a VirtualRelativeJoystick Class for Gideros by @snooks and @antix (https://forum.gideros.rocks/discussion/7171/virtual-onscreen-controls-gamepad-does-anybody-want-to-share-theirs).

In my projects, I usually create a "classes" folder and put all my classes in it. You can create the "classes" folder, then create a new file which you can call "VirtualRelativeJoystick.lua" and copy the below code for that Class.

The Onscreen joystick only appears when the lower left or right quadrant of the screen is touched, you can change its parameters like size and color, plus it has functions you can call, etc...

-- VirtualRelativeJoystick for Gideros
--
-- Onscreen joystick that only appears when lower left quadrant
-- of screen is touched
--

VirtualRelativeJoystick = Core.class(Sprite)

function VirtualRelativeJoystick:init(options)
	-- default options
	self.x = 100
	self.y = 100
	self.outerRadius = 100
	self.padColor = 0xaaaadd
	self.knobColor = 0xaaddaa
	self.onPressed = nil
	self.onDragged = nil
	self.onReleased = nil

	self.left = false -- displays joystick on right side of the screen by default

	self.xpos = 0
	self.ypos = 0
	self.strength = 0
	self.angle = 0

	self.enabled = false

	-- set user supplied options
	if options then
		for key, value in pairs(options) do self[key]= value end
	end

	self.outerCircle = self:getNewCircle(self.outerRadius, self.padColor, false)
	self:addChild(self.outerCircle)
	self.innerNubbin = self:getNewCircle(self.outerRadius * .5, self.knobColor, true)
	self:addChild(self.innerNubbin)

	self:setPosition(self.x, self.y)
	self:setVisible(false)

	self:addEventListener(Event.TOUCHES_BEGIN, self.onTouchesBegin, self)
	self:addEventListener(Event.TOUCHES_MOVE, self.onTouchesMove, self)
	self:addEventListener(Event.TOUCHES_END, self.onTouchesEnd, self)
end

function VirtualRelativeJoystick:onTouchesCancel(e)
	self:onTouchesEnd(e)
end

function VirtualRelativeJoystick:onTouchesBegin(e)
	local tx, ty = e.touch.x, e.touch.y
	local mx, my = application:getContentWidth() * 0.5, application:getContentHeight() * 0.5

	if self.left then 
		if tx > mx or ty < my then return end
	else
		if tx < mx or ty < my then return end
	end

	self.controlTouchId = e.touch.id
	self:setPosition(tx, ty)
	self:setVisible(true)
	self.enabled = true -- enable control

	if self.onPressed then self.onPressed() end -- run code

	e:stopPropagation()
end

function VirtualRelativeJoystick:onTouchesMove(e)
	local cos, sin, sqrt, atan2 = math.cos, math.sin, math.sqrt, math.atan2
	if not self.enabled then return end
	if e.touch.id == self.controlTouchId then
		local x, y = self:globalToLocal(e.touch.x, e.touch.y)
		local radius = self.outerRadius
		local distance = sqrt(x * x + y * y) >< radius -- limit distance to outer radius
		-- normalized strength for use with angle
		local strength = (distance >< radius) / radius
		local angle = ^>atan2(y, x) + 90

		local ra = ^< self.angle
		y = -distance * cos(ra)
		x = distance * sin(ra)
		self.innerNubbin:setPosition(x, y)

		self.xpos, self.ypos = x / radius, -y / radius

		if self.onPressed then
			self.onDragged(angle, distance, strength) -- run code
		end

		self.angle, self.distance, self.strength = angle, distance, strength

		e:stopPropagation()
	end
end

function VirtualRelativeJoystick:onTouchesEnd(e)
	if not self.enabled then return end
	if e.touch.id == self.controlTouchId then
		self.innerNubbin:setPosition(0, 0)
		self.xpos, self.ypos = 0, 0
		self:setVisible(false)

		self.enabled = false
		if self.onReleased then self.onReleased() end -- run code

		e:stopPropagation()
	end
	self.controlTouchId = nil
end

function VirtualRelativeJoystick:getNewCircle(r, color, fill)
	local p = Path2D.new()
	p:setSvgPath(("M %s 0 a %s %s 0 0 0 %s 0 a %s %s 0 0 0 %s 0 Z"):format(-r, r, r, 2 * r, r, r, -2 * r))
	p:setLineThickness(10) -- Outline width
	p:setFillColor(color, fill and .6 or .0) --Fill color
	p:setLineColor(color, .6) --Line color
	return p
end

Demo

In this demo you will control a tank using the VirtualRelativeJoystick Class. The tank: speed is relative to the position of the joystick, shoots when you release your finger. The demo works on desktop, mobile and html5 too!

You can copy (or better write) the code below under the VirtualRelativeJoystick Class itself or in any other file (the main.lua file for example).

--!NEEDS:classes/VirtualRelativeJoystick.lua

--
-- virtual joystick DEMO
--
application:setBackgroundColor(0x6c6c6c)
local options = {}
options.padColor = 0xff0000
options.knobColor = 0x00ff00
options.outerRadius = 64
options.left = true -- activate the joystick on the left side of the screen
local vrj = VirtualRelativeJoystick.new(options)
-- player1 sprite
local player1 = Sprite.new()
local player1body = Pixel.new(0xffff00, 1, 64, 64)
local player1head = Pixel.new(0x00ff00, 1, 32, 32)
local player1debug = Pixel.new(0xff0000, 1, 8, 8)
player1head:setPosition(16, (-64+32))
player1debug:setPosition(32-4, 32-4)
player1:addChild(player1body)
player1:addChild(player1head)
player1:addChild(player1debug)
player1:setAnchorPoint(0.5, 0.5)
-- postion
player1:setPosition(application:getContentWidth()/2, application:getContentHeight()/2)
-- order
stage:addChild(player1)
stage:addChild(vrj)

-- bullets
local bullets = {}
function Bullet(angle, x, y)
	local bullet=Pixel.new(0x00ffff, 2, 16, 16)
	bullet:setAnchorPoint(0.5, 0.5)
	bullet:setRotation(angle)
	bullet.x, bullet.y = x, y
	bullet:setPosition(bullet.x, bullet.y)
	bullet.dx, bullet.dy = math.cos(^<angle), math.sin(^<angle)
	bullet.speed = 5
	bullet.timer = 60
	stage:addChild(bullet)
	bullets[#bullets + 1] = bullet
end

-- joystick functions
vrj.onPressed = function()
--	print("joystick pressed")
end
vrj.onDragged = function()
--	print("joystick dragged")
end
vrj.onReleased = function()
--	print("joystick released")
	Core.asyncCall(Bullet, vrj.angle-90, player1:getPosition())
end

-- game loop
local x, y = player1:getPosition()
local playerspeed = 4
stage:addEventListener(Event.ENTER_FRAME, function(e)
	-- player1
	if vrj.enabled then
--		print(vrj.angle, vrj.distance, vrj.strength)
		x += math.cos(^<(vrj.angle-90)) * vrj.strength * playerspeed
		y += math.sin(^<(vrj.angle-90)) * vrj.strength * playerspeed
		player1:setRotation(vrj.angle)
		player1:setPosition(x, y)
	end
	-- bullets
	for v = #bullets, 1, -1 do
		bullets[v].x += bullets[v].dx * bullets[v].speed
		bullets[v].y += bullets[v].dy * bullets[v].speed
		bullets[v]:setPosition(bullets[v].x, bullets[v].y)
		bullets[v].timer -= 1
		if bullets[v].timer < 0 then
			stage:removeChild(bullets[v])
			table.remove(bullets, v)
		end
	end
end)

Touch and drag the lower left quadrant of the screen to move the player.

See also

TntVirtualPad