Multitouch and 2D camera (zoom)

Hello,
I’ve got two questions about Codea.
First, how exactly does Multitouch work? I know that this has already been asked several times on the forums but I still don’t get it. How can I create arrows or a virtual joystick for movement and, for example, a jump button that can be touched at the same time?

Is there a way to implement a 2D camera that doesn’t only scroll like in a platform but can zoom in or out like
in some strategy games?

Thanks in advance for any help! :slight_smile:

@Leon For multitouch, you will need to use the Touch ID. Each touch creates a unique id number that can be used to determine which touch is doing something. As for zooming in or out, that can also be done. If you’re using sprites, just modify the width and height parameters to increase or decrease the Sprite size to make it look like you’re zooming in or out.

The code for the mutlitouch example:

--# Main


function setup()
    print("This example tracks multiple touches and colors them based on their ID")
    
    -- keep track of our touches in this table
    touches = {}
end

-- This function gets called whenever a touch
--  begins or changes state
function touched(touch)
    if touch.state == ENDED then
        -- When any touch ends, remove it from
        --  our table
        touches[touch.id] = nil
    else
        -- If the touch is in any other state 
        --  (such as BEGAN) we add it to our
        --  table
        touches[touch.id] = touch
    end
end

function draw()
    background(0, 0, 0, 255)
    
    for k,touch in pairs(touches) do

        -- Use the touch id as the random seed
        math.randomseed(touch.id)

        -- This ensures the same fill color is used for the same id
        fill(math.random(255),math.random(255),math.random(255))
        
        -- Draw ellipse at touch position
        ellipse(touch.x, touch.y, 100)
    end
end

the local variable touch in the touched function:
To understand multitouch, you must first understand what the variable touch (or whatever you named it) is. It stores information such as the x and y position, the speed the touch is moving, and more, but the most important thing to understand for multitouch is that each touch has a unique ID. You can access the id of a touch with touch.id.

in setup:

  1. It prints something into the output.
  2. It creates a table named touches that has nothing in it. This will be used later on to keep track of all the fingers touching the screen.

in touched

  1. It checks if the touch has ended. If it has, it will remove the item at the index of touch.id in touches.
  2. If it hasn’t ended, it will take all the touch information, and put it in touches at the index of the touch ID.

in draw

  1. It draws the background to overlap the last frame.
  2. if there is anything in touches, then it goes through all the values stored in touches. the following steps are what happens inside the loop.
  3. It sets the seed for math.random to whatever the id of the touch it is currently at is.
  4. It sets the fill colour to a random colour. (this colour will always stay the same for each touch because we’ve set the seed)
  5. we draw an ellipse at the x and y postition of the touch.

I hoped that helped. I tried to be as clear as possible.

Thanks :slight_smile:
As far as zooming is concerned I thought of an alternative to this. But if this is how I am supposed to do, it I’ll stick with it.
Thanks Kolosso for your detailed example! How would I transfer this to buttons? :expressionless:

@Leon If you have an alternative to zooming and it works better, then use it. I don’t know if there’s a best way, it all depends on what and how you’re displaying something.

@Leon Here’s an example I already had that allows the shoot and direction buttons to be pressed at the same time. Not sure if you can use this, but it might give you an idea.

function setup()
    rectMode(CENTER)
    dir=""
    stab=""
    btn={}
    table.insert(btn,buttons(300,300,100,50,"UP"))
    table.insert(btn,buttons(300,100,100,50,"DOWN"))
    table.insert(btn,buttons(200,200,100,50,"LEFT"))
    table.insert(btn,buttons(400,200,100,50,"RIGHT"))
    table.insert(btn,buttons(300,400,100,50,"SHOOT"))
end

function draw()
    background(40, 40, 50)
    for a,b in pairs(btn) do
        b:draw()
        if b.pressed then
            if b.text=="SHOOT" then
                stab=b.text
            elseif dir=="" then
                dir=b.text
            end
        end
    end
    fill(255)
    text(dir,WIDTH/2,HEIGHT-50)
    text(stab,WIDTH/2,HEIGHT-100)
end

function touched(t)
    for a,b in pairs(btn) do
        b:touched(t)
    end
end

buttons=class()

function buttons:init(x,y,w,h,txt)
    self.x=x
    self.y=y
    self.w=w
    self.h=h
    self.text=txt
    self.left=x-w/2
    self.right=x+w/2
    self.bottom=y-h/2
    self.top=y+h/2
    self.pressed=false  
    self.id=0 
end

function buttons:draw()
    fill(255)
    if self.pressed then
        fill(255, 158, 0, 255)
    end
    rect(self.x,self.y,self.w,self.h)   
    fill(255,0,0) 
    text(self.text,self.x,self.y)
end

function buttons:touched(t)
    if t.state==BEGAN or t.state==MOVING and self.id==0 then
        if t.x>self.left and t.x<self.right and 
                    t.y>self.bottom and t.y<self.top then
            self.pressed=true
            self.id=t.id
        end
    end
    if t.state==MOVING and self.id==t.id then
        if t.x>self.left and t.x<self.right and 
                    t.y>self.bottom and t.y<self.top then
            self.pressed=true
        else
            self.pressed=false
            self.id=0
            dir=""
            stab=""
        end
    end
    if t.state==ENDED and self.id==t.id then
        self.pressed=false
        self.id=0
        dir=""
        stab=""
    end
end

You can also use the scale command to zoom

@Leon I made a two player tank game a few months ago. It has pretty good joystick controls, if you want to look at another example
https://gist.github.com/Dwiniflin/10c4a478e43b6d1532499df9439d9627

i have written a simplified multitouch handler. basically you copy the fallowing code into your game (in a separate tab) and it overrides codea’s default ?touched? method. you can then retrieve all touches from your current multitouch gesture/session like so:

?function touched(touch1, touch2, …)? or as a table ?function touched(…) touches={…}?

local touches = {}
local expiredTouches = 0
local gestureCountdown = .08 -- ADJUST!
local touchesAutoDispatcher
local dispatchTouches = touched

function touched(touch)
    -- Identify touch
    local gesture, uid = #touches > 0 and touches[1].initTime + gestureCountdown < ElapsedTime
    for r, t in ipairs(touches) do
        if touch.id == t.id then uid = r end
        touches[r].state = "RESTING"
    end
    
    -- Cache updates
    local rt = touches[uid] or {}
    local template = {
        id = rt.id or touch.id,
        state = touch.state,
        tapCount = CurrentTouch.tapCount,
        initTime = rt.initTime or ElapsedTime,
        duration = ElapsedTime - (rt.initTime or ElapsedTime),
        initX = rt.initX or touch.x,
        initY = rt.initY or touch.y,
        x = touch.x,
        y = touch.y,
        prevX = touch.prevX,
        prevY = touch.prevY,
        deltaX = touch.deltaX,
        deltaY = touch.deltaY,
        radius = touch.radius,
        radiusTolerance = touch.radiusTolerance,
        force = remapRange(touch.radius, 0, touch.radius + touch.radiusTolerance, 0, 1)
    }
    
    if uid then
        -- Update touches
        touches[uid] = template
        
        -- Dispatch touches
        if touch.state == ENDED then
            -- First touch expired while gesture still active (or waiting to get active)
            if expiredTouches == 0 then
                -- Gesture was waiting to get active
                if touchesAutoDispatcher then
                    -- Sync all touch states to BEGAN
                    -- Still dispatch the planed BEGAN state from Auto-Dispatch
                    for r, t in ipairs(touches) do
                        touches[r].state = BEGAN
                        touches[r].initX = t.x
                        touches[r].initY = t.y
                    end
                    dispatchTouches(table.unpack(touches))
                    
                    -- Cancel gesture!
                    tween.reset(touchesAutoDispatcher)
                    touchesAutoDispatcher = nil
                end
                
                -- Sync all touch states to ENDED
                for r, t in ipairs(touches) do
                    touches[r].state = ENDED
                end
                -- Dispatch ENDED
                dispatchTouches(table.unpack(touches))
            end
            
            -- Delete all touches when all expired
            expiredTouches = expiredTouches + 1
            if expiredTouches == #touches then
                touches = {}
                expiredTouches = 0
            end
        else
            -- Dispatch MOVING
            if not touchesAutoDispatcher and gesture and expiredTouches == 0 then
                dispatchTouches(table.unpack(touches))
            end
        end
    else
        -- Register touch
        -- Ignore new touches when gesture already active
        if not gesture and touch.state == BEGAN then
            table.insert(touches, template)
            uid = #touches
            
            -- Auto-Dispatch touches
            if uid == 1 then
                -- Dispatch BEGAN ... when gesture gets active
                touchesAutoDispatcher = tween.delay(gestureCountdown, function()
                    -- Sync all touch states to BEGAN
                    for r, t in ipairs(touches) do
                        touches[r].state = BEGAN
                        touches[r].initX = t.x
                        touches[r].initY = t.y
                    end
                    -- Dispatch BEGAN
                    dispatchTouches(table.unpack(touches))
                    touchesAutoDispatcher = nil
                end)
            end
        end
    end
end