Wiki
Clone wikiCore / TouchPad
TouchPad
Description
A TouchPad class written in lua that can be used in Codea.
For an example of how to use, i've included the Main.lua i used for testing at the bottom.
See it in action
Youtube TouchPad Demo
Usage
Create Instance of either TouchPadCircle or TouchPadRect
myTouchPad = TouchPadCircle(vec2(80,110), 150)
Fields
-- Set Colors myTouchPad.fillColor = color(186, 186, 166,158) myTouchPad.latchColor = color(216, 216, 216, 47) -- size of the thumb stick myTouchPad.latchSize = 126 -- if true the control will only render if touch is detected myTouchPad.drawOnTouch = false -- use sprites to draw the controls myTouchPad.touchSprite = "SPRITE_NAME" myTouchPad.backgroundSprite = "SPRITE_NAME" -- treat touchPad as a button myTouchPad.isButton = false -- delegates myTouchPad.BeganDelegate = CALL_BACK_FUNCTION myTouchPad.MovingDelegate = CALL_BACK_FUNCTION myTouchPad.EndedDelegate = CALL_BACK_FUNCTION
Classes
TouchPad.lua Base Class
TouchPad = class()
-- base init, set some defaults for member variables
function TouchPad:init(position, size)
self.pos = position
self.size = size
-- colors to use if not sprite
self.fillColor = color(186, 186, 166,158)
self.latchColor = color(216, 216, 216, 47)
-- size of the thumb stick
self.latchSize = 126
-- if true the control will only render if touch is detected
self.drawOnTouch = false
-- use sprites to draw the controls
self.touchSprite = nil
self.backgroundSprite = nil
-- treat touchPad as a button
self.isButton = false
-- delegates
self.BeganDelegate = nil
self.MovingDelegate = nil
self.EndedDelegate = nil
-- used too keep track of which touch is being used
self.latchTouch = nil
end
function TouchPad:draw()
--implement in subclass
assert(false, "override draw function in subclass")
end
function TouchPad:getDrawPos()
-- implement in subclass
assert(false, "override getDrawPos function in subclass")
end
-- respond to touch events and notify
-- delegates of event
function TouchPad:touched(touch)
-- Codea does not automatically call this method
if self.latchTouch == nil then
if touch.state == BEGAN then
if self.drawOnTouch then
self.latchTouch = touch
self.pos = vec2(touch.x, touch.y)
self:notifyDelegate(self.BeganDelegate)
return true
elseif self:hit(vec2(touch.x,touch.y)) then
self.latchTouch = touch
self:notifyDelegate(self.BeganDelegate)
return true
end
end
elseif self.latchTouch ~= nil and touch.id == self.latchTouch.id then
if touch.state == MOVING then
self.latchTouch = touch
self:notifyDelegate(self.MovingDelegate)
return true
elseif touch.state == ENDED then
self.latchTouch = nil
if self.drawOnTouch then
-- hide touchpad off screen
self.pos = vec2(-500,-500)
end
self:notifyDelegate(self.EndedDelegate)
return false
end
end
return false
end
-- let delegate know about events
function TouchPad:notifyDelegate(del)
if del then
del(self)
end
end
function TouchPad:direction()
-- implement in subclass
assert(false, "override direction function in subclass")
end
function TouchPad:hit(p)
-- implement in subclass
assert(false, "override hit function in subclass")
end
-- keep val between min and max
function TouchPad:clamp(val, min, max)
if val > max then
return max
elseif val < min then
return min
end
return val
end
TouchPadCircle.lua
-- TouchPadCircle inherits from TouchPad
TouchPadCircle = class(TouchPad)
-- calls the base class init and defines a radius
function TouchPadCircle:init(position, size)
TouchPad.init(self, position, size, shape)
self.radius = self.size/2
end
-- draw TouchPadCircle
function TouchPadCircle:draw()
-- do not draw with no latch
if self.drawOnTouch and self.latchTouch == nil then
return
end
if self.backgroundSprite then
sprite(self.backgroundSprite,self.pos.x, self.pos.y, self.size)
else
fill(self.fillColor)
strokeWidth(1)
ellipse(self.pos.x, self.pos.y, self.size)
end
if self.latchTouch then
local drawPos = self:getDrawPos()
if self.touchSprite ~= nil then
sprite(self.touchSprite,drawPos.x, drawPos.y)
else
fill(self.latchColor)
ellipse(drawPos.x, drawPos.y, self.latchSize)
end
end
end
-- returns vec2 in the position to draw where
-- the finger is touching. Value is clamped in
-- circle with some overlap
function TouchPadCircle:getDrawPos()
if self.isButton then
return self.pos
end
if self.touchSprite ~= nil then
return self:clampCircle(vec2(self.latchTouch.x,self.latchTouch.y),
self.pos,(self.radius - spriteSize(self.touchSprite)/4))
else
return self:clampCircle(vec2(self.latchTouch.x,self.latchTouch.y),
self.pos,(self.radius - self.latchSize/4))
end
end
-- reports the offset from the center from -1 to 1
function TouchPadCircle:direction()
if self.latchTouch then
local maxX = self.size/2 - self.latchSize/4
if self.touchSprite then
maxX = self.size/2 - spriteSize(self.touchSprite) / 4
end
local xPos = self.latchTouch.x - self.pos.x
xPos = self:clamp(xPos/maxX, -1, 1)
local yPos = self.latchTouch.y - self.pos.y
yPos = self:clamp(yPos/maxX, -1, 1)
fill(255, 255, 255, 255)
textMode(CORNER)
text("pos" .. xPos .. "," .. yPos,
self.pos.x - self.size/2, self.pos.y + self.size/2 + 5)
return vec2(xPos, yPos)
end
end
-- checks if the point is inside the circle
function TouchPadCircle:hit(p)
distSquared = math.pow((p.x - self.pos.x), 2) + math.pow((p.y - self.pos.y),2)
if distSquared < math.pow(self.radius,2) then
return true
end
return false
end
-- if touch is beyond the circle this clamps
-- the value to the nearest point along the
-- circumference
function TouchPadCircle:clampCircle(pos,center,radius)
distSquared = math.pow((pos.x - center.x), 2) + math.pow((pos.y - center.y),2)
if distSquared < math.pow(radius,2) then
return pos
else
-- get hypotenuse
hyp = math.sqrt(math.pow((pos.x - center.x), 2) + math.pow((pos.y - center.y),2))
cosA = (pos.x - center.x) / hyp
x = cosA * radius + center.x
sinA = (pos.y - center.y) / hyp
y = sinA * radius + center.y
return vec2(x,y)
end
end
TouchPadRect.lua
-- TouchPadRect inherits from TouchPad
TouchPadRect = class(TouchPad)
-- draw TouchPadRect
function TouchPadRect:draw()
-- do not draw with no latch
if self.drawOnTouch and self.latchTouch == nil then
return
end
if self.backgroundSprite then
sprite(self.backgroundSprite,self.pos.x, self.pos.y, self.size.x, self.size.y)
else
fill(self.fillColor)
strokeWidth(2)
rectMode(CENTER)
rect(self.pos.x,self.pos.y,self.size.x,self.size.y)
end
if self.latchTouch then
local drawPos = self:getDrawPos()
if self.touchSprite ~= nil then
if self.isButton then
sprite(self.touchSprite,drawPos.x, drawPos.y)
else
sprite(self.touchSprite,drawPos.x, drawPos.y)
end
else
fill(self.latchColor)
ellipse (drawPos.x, drawPos.y, self.latchSize)
end
end
end
-- returns vec2 in the position to draw where
-- the finger is touching. Value is clamped in
-- rect with some overlap
function TouchPadRect:getDrawPos()
if self.isButton then
return self.pos
end
if self.touchSprite ~= nil then
return vec2(
self:clamp(self.latchTouch.x,
(self.pos.x - self.size.x/2 + spriteSize(self.touchSprite)/4),
(self.pos.x + self.size.x/2 - spriteSize(self.touchSprite)/4)),
self:clamp(self.latchTouch.y,
(self.pos.y - self.size.y/2 + spriteSize(self.touchSprite)/4),
(self.pos.y + self.size.y/2 - spriteSize(self.touchSprite)/4))
)
else
return vec2(
self:clamp(self.latchTouch.x,
(self.pos.x -self.size.x/2 + self.latchSize/4),
(self.pos.x + self.size.x/2 - self.latchSize/4)),
self:clamp(self.latchTouch.y,
(self.pos.y - self.size.y/2 + self.latchSize/4),
(self.pos.y + self.size.y/2 - self.latchSize/4))
)
end
end
-- reports the offset from the center from -1 to 1
function TouchPadRect:direction()
if self.latchTouch then
local maxX = self.size.x/2 - self.latchSize/4
local maxY = self.size.y/2 - self.latchSize/4
if self.touchSprite then
maxX = self.size.x/2 - spriteSize(self.touchSprite) / 4
maxY = self.size.y/2 - spriteSize(self.touchSprite) / 4
end
local xPos = self.latchTouch.x - self.pos.x
xPos = self:clamp(xPos/maxX, -1, 1)
local yPos = self.latchTouch.y - self.pos.y
yPos = self:clamp(yPos/maxY, -1, 1)
fill(255, 255, 255, 255)
textMode(CORNER)
text("pos" .. xPos .. "," .. yPos,
self.pos.x - self.size.x/2, self.pos.y + self.size.y/2)
return vec2(xPos, yPos)
end
end
-- checks if the point is inside the rect
function TouchPadRect:hit(p)
if p.x > self.pos.x - self.size.x/2 and p.x < self.pos.x + self.size.x/2
and p.y > self.pos.y - self.size.y /2 and p.y < self.pos.y + self.size.y/2 then
return true
end
return false
end
Main.lua Example
--Main
-- Use this function to perform your initial setup
function setup()
-- keep track of objects that may react to touches
touchPads = {}
-- testing variables
iparameter("TouchPadShape", 1, 2, 1)
iparameter("TouchPadSpriteEnabled",0, 1, 0)
shape = circle
spriteEnabled = false
-- sprite character positions
spriteHeroPos1 = vec2(WIDTH/4, HEIGHT/2)
spriteHeroDirection1 = vec2(0, 0)
spriteMoveSpeed1 = 3
spriteHeroPos2 = vec2(WIDTH - WIDTH/4, HEIGHT/2)
spriteHeroDirection2 = vec2(0, 0)
spriteMoveSpeed2 = 3
end
-- This function gets called once every frame
function draw()
background(0, 0, 0, 255)
-- test sprite
spriteHeroPos1 = spriteHeroPos1 + spriteHeroDirection1 * spriteMoveSpeed1
spriteHeroPos2 = spriteHeroPos2 + spriteHeroDirection2 * spriteMoveSpeed2
sprite("Planet Cute:Character Cat Girl", spriteHeroPos1.x, spriteHeroPos1.y)
sprite("Planet Cute:Character Horn Girl", spriteHeroPos2.x, spriteHeroPos2.y)
-- Do your drawing here
setTouchPadShape()
setTouchPadSprite()
touchPad1:draw()
touchPad2:draw()
touchPad3:draw()
touchPad4:draw()
drawLabels()
end
-- draw some labels on screen to identify
-- the TouchPads and give some feedback
function drawLabels()
fill(255, 255, 255, 255)
fontSize(18)
textMode(CENTER)
if touchPad3.latchTouch then
text("Two Dirctional pads latched",WIDTH/2,HEIGHT - 20)
elseif touchPad2.latchTouch then
text("Touch anywhere for a second Directional Pad",WIDTH/2,HEIGHT - 20)
else
text("Touch anywhere for a Directional Pad",WIDTH/2,HEIGHT - 20)
end
text("Directional Pad",80,20)
text("Button",WIDTH - 80,20)
end
-- call touched functions in order of priority
-- touch pads return true if they are latched onto the touch
function touched(touch)
for i,v in ipairs(touchPads) do
if v:touched(touch) then
return
end
end
end
-- testing function for switching touch pad shapes
-- when the TouchPadShape parameter changes
function setTouchPadShape()
if TouchPadShape == 1 and shape ~= "circle" then
shape = "circle"
loadTouchPadShape()
elseif TouchPadShape == 2 and shape ~= "rect" then
shape = "rect"
loadTouchPadShape()
end
end
-- testing function to switch sprites on and off
-- when the TouchPadSpriteEnabled parameter changes
function setTouchPadSprite()
if TouchPadSpriteEnabled == 0 and spriteEnabled then
spriteEnabled = false
loadTouchPadShape()
elseif TouchPadSpriteEnabled == 1 and spriteEnabled == false then
spriteEnabled = true
loadTouchPadShape()
end
end
-- testing function that determines which TouchPads
-- to instantiate
function loadTouchPadShape()
if TouchPadShape == 1 then
shape = "circle"
setTouchPadShapeCircle()
elseif TouchPadShape == 2 then
shape = "rect"
setTouchPadShapeRect()
end
setTouchPadDelegates()
setTouchPadAwareness()
end
-- instantiate circle shaped TouchPads
-- with or without sprites
function setTouchPadShapeCircle()
if TouchPadSpriteEnabled == 0 then
touchPad1 = TouchPadCircle(vec2(80,110), 150)
touchPad2 = TouchPadCircle(vec2(-500,-500), 150)
touchPad2.drawOnTouch = true
touchPad3 = TouchPadCircle(vec2(-500,-500), 150)
touchPad3.drawOnTouch = true
touchPad4 = TouchPadCircle(vec2(WIDTH - 80,110), 150)
touchPad4.isButton = true
else
touchPad1 = TouchPadCircle(vec2(80,110), 150)
touchPad1.touchSprite = "SpaceCute:Star"
touchPad1.backgroundSprite = "SpaceCute:Planet"
touchPad2 = TouchPadCircle(vec2(-500,-500), 150)
touchPad2.drawOnTouch = true
touchPad2.touchSprite = "SpaceCute:Star"
touchPad2.backgroundSprite = "SpaceCute:Planet"
touchPad3 = TouchPadCircle(vec2(-500,-500), 150)
touchPad3.drawOnTouch = true
touchPad3.touchSprite = "SpaceCute:Star"
touchPad3.backgroundSprite = "SpaceCute:Planet"
touchPad4 = TouchPadCircle(vec2(WIDTH - 80,110), 150)
touchPad4.isButton = true
touchPad4.touchSprite = "SpaceCute:Star"
touchPad4.backgroundSprite = "SpaceCute:Planet"
end
end
-- instantiates rect shaped TouchPads
-- with or without sprites
function setTouchPadShapeRect()
if TouchPadSpriteEnabled == 0 then
touchPad1 = TouchPadRect(vec2(80,110), vec2(150,150))
touchPad2 = TouchPadRect(vec2(-500,-500), vec2(150,150))
touchPad2.drawOnTouch = true
touchPad3 = TouchPadRect(vec2(-500,-500), vec2(150,150))
touchPad3.drawOnTouch = true
touchPad4 = TouchPadRect(vec2(WIDTH - 80,110), vec2(150,150))
touchPad4.isButton = true
else
touchPad1 = TouchPadRect(vec2(80,125), vec2(101,171))
touchPad1.touchSprite = "Planet Cute:Chest Open"
touchPad1.backgroundSprite = "Planet Cute:Chest Closed"
touchPad2 = TouchPadRect(vec2(-500,-500), vec2(101,171))
touchPad2.drawOnTouch = true
touchPad2.touchSprite = "Planet Cute:Chest Open"
touchPad2.backgroundSprite = "Planet Cute:Chest Closed"
touchPad3 = TouchPadRect(vec2(-500,-500), vec2(101,171))
touchPad3.drawOnTouch = true
touchPad3.touchSprite = "Planet Cute:Chest Open"
touchPad3.backgroundSprite = "Planet Cute:Chest Closed"
touchPad4 = TouchPadRect(vec2(WIDTH - 80,125), vec2(101,171))
touchPad4.isButton = true
touchPad4.touchSprite = "Planet Cute:Chest Open"
touchPad4.backgroundSprite = "Planet Cute:Chest Closed"
end
end
-- creates a list of objects that need to respond
-- to touches. In order of priority. The drawOnTouch
-- objects go last.
function setTouchPadAwareness()
for i=1,table.maxn(touchPads) do
table.remove(touchPads)
end
table.insert(touchPads, touchPad1)
table.insert(touchPads, touchPad4)
table.insert(touchPads, touchPad2)
table.insert(touchPads, touchPad3)
end
-- let touchPads know which function to
-- call as delegates for touch events
function setTouchPadDelegates()
touchPad4.BeganDelegate = touchBegan
touchPad1.MovingDelegate = moveSprite
touchPad2.MovingDelegate = moveSprite
touchPad3.MovingDelegate = moveSprite
touchPad1.EndedDelegate = touchEnded
touchPad2.EndedDelegate = touchEnded
touchPad3.EndedDelegate = touchEnded
end
-- receive touch began events
function touchBegan(touchPad)
if touchPad == touchPad4 then
spriteHeroPos1 = vec2(WIDTH/4, HEIGHT/2)
spriteHeroPos2 = vec2(WIDTH - WIDTH/4, HEIGHT/2)
end
end
-- receive touch moving events
function moveSprite(touchPad)
if touchPad == touchPad1 then
spriteHeroDirection1 = touchPad:direction()
elseif touchPad == touchPad2 then
spriteHeroDirection2 = touchPad:direction()
elseif touchPad == touchPad3 then
spriteHeroDirection1 = touchPad:direction()
end
end
-- receive touch ended events
function touchEnded(touchPad)
if touchPad == touchPad1 then
spriteHeroDirection1 = vec2(0,0)
elseif touchPad == touchPad2 then
spriteHeroDirection2 = vec2(0,0)
elseif touchPad == touchPad3 then
spriteHeroDirection1 = vec2(0,0)
end
end
Updated