Video Game Controllers

How to use Minter? I need VirtualStick and TapAction.

I can’t get this to copy on my iPad no matter what method I use. Can someone email it to Posterous.com? It would be much appreciated.

Have same problem with copying file!

Try alex812.posterous.com/controllers

Great work!
Maybe i had to add this to Monster :smiley: :slight_smile:

Thanks! This is definitely useful.

Can somebody Post it in posterous? Cant manage to Download it

Look up a few comments - Alex812a posted it.

I’ve put a downloadable package of the latest code here: http://npryce-codea.posterous.com/controllers

And examples are here: https://github.com/npryce/codea-controllers

Thanks, I used it in my Hunter game.

It’s fantastic. Thank you, Nat.

I can’t figure out how to use it to make a dual stick shooter :frowning: I’m having a hard time to make it fire. Can someone help me?

Great code but I am a little confused. How code would look like if I want 2 stick in singleplayer mode??

Have a look at: https://github.com/npryce/codea-controllers/blob/master/MultiplayerExample.lua

This uses two sticks to control two player characters but you could make one stick move the player and one stick control the shooting direction.

Wow, this is so cool… can this be added as a native feature of the Codea app (in a future update)?

Thank you @nat. I wish there was an example for each of you great controllers in a single main file… Do you plan to do one? Or do you want me to do one? (i’ll have to do it anyway if i want to understand how your tools work)

I did have a big demo program that used lots of different controllers but I think it was rather confusing. So I wrote several small demos instead. More demo programs would be good but I think keeping them very very tiny is better, so that the use of the controllers is not obscured by the rest of the program.

Ok. I forgot to mention: i love your idea of a controller that appears only where needed (the touch position) and when needed: it doesn’t spoil the screen pixels then. Even their look is that way: minimum drawing, very elegant. Great job.

Hello all. I have modified Nat’s controllers to gain in FPS by drawing the controlls in an image and drawing them as a sprite. But i ran into a bug i cannot find the source: the first drawing i do by setContext(myImage) is ok but the next ones are distorded in the x axis (not the y!). I cannot find the mistake i made. Is is a codea bug? It seems to be linked to the displayMode. Thanks for your help.



--# Main
-- Moving a sprite with a VirtualStick

function setup()
    displayMode(FULLSCREEN)
    pos = vec2(WIDTH/2, HEIGHT/2)
    steer = vec2(0,0)
    speed = 400 -- pixels per second
        
    controller1 = VirtualStick {
        moved = function(v) steer = v end,
        released = function(v) steer = vec2(0,0) end
    }
    
    controller1:activate({x0=0.5,x1=1,y0=0,y1=0.25,name="moving stick1"})
    


    controller2 = VirtualStick {
        moved = function(v) steer = v end,
        released = function(v) steer = vec2(0,0) end
    }
    
    controller2:activate({x0=0.5,x1=1,y0=0.25,y1=0.50,name="moving stick 2"})

    controller3 = VirtualStick {
        moved = function(v) steer = v end,
        released = function(v) steer = vec2(0,0) end
    }
    
    controller3:activate({x0=0.5,x1=1,y0=0.5,y1=0.75,name="moving stick 3"})
                                    
    allControllers = All({controller1,controller2,controller3,controller4})
end

function draw()
    background(131, 228, 73, 255)
    
    pos = pos + steer*speed*DeltaTime
    
    sprite("Planet Cute:Character Boy", pos.x, pos.y)
    allControllers:draw()
end
function touched(touch)
    allControllers:touched(touch)
end
    
--# Controller
-- Base class for controllers

Controller = class()

function Controller:activate(input)
    local x0 = input.x0 or 0
    local x1 = input.x1 or 1
    local y0 = input.y0 or 0
    local y1 = input.y1 or 1
    self.cx = (x0+x1)/2 *WIDTH
    self.cy = (y0+y1)/2 *HEIGHT
    self.wx = (x1-x0)/2 *WIDTH
    self.wy = (y1-y0)/2 *HEIGHT
    self.name = input.name
end

function Controller:demo(timeout)
    if ElapsedTime<timeout then
        
    pushStyle() pushMatrix()
    strokeWidth(1)
    rectMode(RADIUS)
    fill(255, 16, 0, 123)
    rect(self.cx,self.cy,self.wx,self.wy)
    fill(255, 255, 255, 255)
    fontSize(20)
    text("region of activation of",self.cx,self.cy+20)
    text(self.name,self.cx,self.cy - 20)
    popStyle() popMatrix()
    end
end

function Controller:check(touch)
    local goodZone = false
    if  math.abs((touch.x-self.cx))<self.wx
    and math.abs((touch.y-self.cy))<self.wy
    then
        goodZone = true
    end
    return goodZone
end

function Controller:draw()
    -- nothing
end

-- Utility functions

function touchPos(t)
    return vec2(t.x, t.y)
end

function clamp(x, min, max)
    return math.max(min, math.min(max, x))
end

function clampAbs(x, maxAbs)
    return clamp(x, -maxAbs, maxAbs)
end

function clampLen(vec, maxLen)
    if vec == vec2(0,0) then
        return vec
    else
        return vec:normalize() * math.min(vec:len(), maxLen)
    end
end

-- projects v onto the direction represented by the given unit vector
function project(v, unit)
    return v:dot(unit)
end

function sign(x)
    if x == 0 then
        return 0
    elseif x < 0 then
        return -1
    elseif x > 0 then
        return 1
    else
        return x -- x is NaN
    end
end

function doNothing()
end

--# Controller_VirtualStick

VirtualStick = class(Controller)

function VirtualStick:init(args)
    self.radius = args.radius or 100
    self.deadZoneRadius = args.deadZoneRadius or 25
    self.releasedCallback = args.released or doNothing
    self.steerCallback = args.moved or doNothing
    self.pressedCallback = args.pressed or doNothing
    -- pre-draw sprites
    self.base = self:createBase()
    self.stick = self:createStick()
end

function VirtualStick:createBase()
    local base = image(self.radius*2+6,self.radius*2+6)
        pushStyle() pushMatrix()
        ellipseMode(RADIUS)
        strokeWidth(1)
        stroke(255, 255, 255, 255)
        noFill()       
    setContext(base)
        background(0, 0, 0, 0)
        ellipse(base.width/2, base.height/2, self.radius, self.radius)
        ellipse(base.width/2, base.height/2, self.deadZoneRadius, self.deadZoneRadius)
    setContext()
        popMatrix() popStyle()
    return base
end

function VirtualStick:createStick()
    local base = image(56,56)
        pushStyle() pushMatrix()
        ellipseMode(RADIUS)
        strokeWidth(1)
        stroke(255, 255, 255, 255)
        noFill()       
    setContext(base)
        background(0, 0, 0, 0)
        ellipse(base.width/2, base.height/2, 0.75*25, 25)
    setContext()
        popMatrix() popStyle()
    return base
end

function VirtualStick:touched(t)
    local pos = touchPos(t)
    local goodZone = self:check(t)
    
    if t.state == BEGAN and self.touchId == nil and goodZone then
        self.touchId = t.id
        self.touchStart = pos
        self.stickOffset = vec2(0, 0)
        self.pressedCallback()
    elseif t.id == self.touchId then
        if t.state == MOVING then
            self.stickOffset = clampLen(pos - self.touchStart, self.radius)
            self.steerCallback(self:vector())
        elseif t.state == ENDED or t.state == CANCELLED then
            self:reset()
            self.releasedCallback()
        end
    end
end

function VirtualStick:vector()
    local stickRange = self.radius - self.deadZoneRadius
    local stickAmount = math.max(self.stickOffset:len() - self.deadZoneRadius, 0)
    local stickDirection = self.stickOffset:normalize()
    
    return stickDirection * (stickAmount/stickRange)
end

function VirtualStick:reset()
    self.touchId = nil
    self.touchStart = nil
    self.stickOffset = nil
end

function VirtualStick:draw()
    if self.name ~= nil then self:demo(10) end
    if self.touchId ~= nil then
        sprite(self.base,self.touchStart.x, self.touchStart.y)
        sprite(self.stick,
            self.touchStart.x+self.stickOffset.x, 
            self.touchStart.y+self.stickOffset.y)
    end
end



--# Controller_All
-- Forwards each touch event to all the controllers in the table
-- passed to the constructor

All = class(Controller)

function All:init(controllers)
    self.controllers = controllers
end

function All:touched(t)
    for _, c in pairs(self.controllers) do
        c:touched(t)
    end
end

function All:draw()
    for _, c in pairs(self.controllers) do
        c:draw()
    end
end

I don’t have time to check it right now, but the first thing I would do is take displayMode(FULLSCREEN) out of setup() and put it by itself at the top of your code. Weird bugs can happen when certain things are not at the very beginning of the code before setup().