← Volver al listado de tecnologías

Control Táctil, Gestos y Multitouch

Por: Artiko
defoldtouchgestosmultitouchmobileinput

Control Táctil, Gestos y Multitouch

Los controles táctiles son la interfaz principal en dispositivos móviles. Esta guía te enseñará a implementar un sistema completo de gestos y multitouch en Defold.

Fundamentos del Input Táctil

1. Configuración Básica de Touch

Input Binding Setup

# game.input_binding
[key_trigger]
input = KEY_SPACE
action = jump

[mouse_trigger]
input = MOUSE_BUTTON_LEFT
action = touch

[touch_trigger]
input = TOUCH_MULTI
action = touch_multi

[text]
input = TEXT
action = text_input

Touch Manager Base

-- touch_manager.script
local M = {}

function init(self)
    msg.post(".", "acquire_input_focus")

    -- Estado del touch
    self.touches = {}
    self.touch_count = 0
    self.max_touches = 10

    -- Configuración de gestos
    self.gesture_config = {
        tap_threshold = 30,          -- Distancia máxima para tap
        tap_time_threshold = 0.3,    -- Tiempo máximo para tap
        hold_time_threshold = 0.5,   -- Tiempo mínimo para hold
        swipe_threshold = 50,        -- Distancia mínima para swipe
        swipe_velocity_threshold = 500, -- Velocidad mínima para swipe
        pinch_threshold = 20,        -- Distancia mínima para pinch
        rotation_threshold = 15      -- Ángulo mínimo para rotación (grados)
    }

    -- Estados de gestos
    self.gesture_state = {
        is_swiping = false,
        is_pinching = false,
        is_rotating = false,
        is_holding = false
    }

    print("Touch Manager initialized")
end

function M.add_touch(self, touch_id, x, y)
    local touch_data = {
        id = touch_id,
        start_pos = vmath.vector3(x, y, 0),
        current_pos = vmath.vector3(x, y, 0),
        previous_pos = vmath.vector3(x, y, 0),
        start_time = socket.gettime(),
        last_move_time = socket.gettime(),
        velocity = vmath.vector3(0, 0, 0),
        is_active = true
    }

    self.touches[touch_id] = touch_data
    self.touch_count = self.touch_count + 1

    -- Notificar touch start
    msg.post("main:/controller", "touch_start", {
        touch_id = touch_id,
        position = touch_data.start_pos,
        touch_count = self.touch_count
    })

    return touch_data
end

function M.update_touch(self, touch_id, x, y)
    local touch = self.touches[touch_id]
    if not touch then return nil end

    touch.previous_pos = vmath.vector3(touch.current_pos)
    touch.current_pos = vmath.vector3(x, y, 0)

    -- Calcular velocidad
    local current_time = socket.gettime()
    local dt = current_time - touch.last_move_time
    if dt > 0 then
        local displacement = touch.current_pos - touch.previous_pos
        touch.velocity = displacement / dt
    end
    touch.last_move_time = current_time

    -- Notificar touch move
    msg.post("main:/controller", "touch_move", {
        touch_id = touch_id,
        position = touch.current_pos,
        velocity = touch.velocity
    })

    return touch
end

function M.remove_touch(self, touch_id)
    local touch = self.touches[touch_id]
    if not touch then return nil end

    touch.is_active = false
    self.touch_count = self.touch_count - 1

    -- Notificar touch end
    msg.post("main:/controller", "touch_end", {
        touch_id = touch_id,
        position = touch.current_pos,
        duration = socket.gettime() - touch.start_time,
        touch_count = self.touch_count
    })

    local touch_data = touch
    self.touches[touch_id] = nil

    return touch_data
end

return M

Gestos Básicos

1. Tap y Double Tap

Tap Detector

-- tap_detector.lua
local M = {}

function M.new()
    return {
        last_tap_time = 0,
        last_tap_pos = vmath.vector3(0, 0, 0),
        tap_count = 0,
        double_tap_window = 0.5,
        tap_threshold = 30
    }
end

function M.process_tap(detector, touch_data, gesture_config)
    local current_time = socket.gettime()
    local duration = current_time - touch_data.start_time
    local distance = vmath.length(touch_data.current_pos - touch_data.start_pos)

    -- Verificar si es un tap válido
    if duration <= gesture_config.tap_time_threshold and distance <= gesture_config.tap_threshold then

        -- Verificar double tap
        local time_since_last_tap = current_time - detector.last_tap_time
        local distance_from_last_tap = vmath.length(touch_data.current_pos - detector.last_tap_pos)

        if time_since_last_tap <= detector.double_tap_window and
           distance_from_last_tap <= detector.tap_threshold then

            detector.tap_count = detector.tap_count + 1

            if detector.tap_count >= 2 then
                -- Double tap detectado
                msg.post("main:/controller", "double_tap", {
                    position = touch_data.current_pos
                })
                detector.tap_count = 0
                return "double_tap"
            end
        else
            detector.tap_count = 1
        end

        detector.last_tap_time = current_time
        detector.last_tap_pos = vmath.vector3(touch_data.current_pos)

        -- Programar verificación de tap simple
        timer.delay(detector.double_tap_window, false, function()
            if detector.tap_count == 1 then
                msg.post("main:/controller", "tap", {
                    position = detector.last_tap_pos
                })
                detector.tap_count = 0
            end
        end)

        return "tap"
    end

    return nil
end

return M

2. Swipe Gestures

Swipe Detector

-- swipe_detector.lua
local M = {}

M.DIRECTIONS = {
    UP = "up",
    DOWN = "down",
    LEFT = "left",
    RIGHT = "right",
    UP_LEFT = "up_left",
    UP_RIGHT = "up_right",
    DOWN_LEFT = "down_left",
    DOWN_RIGHT = "down_right"
}

function M.new()
    return {
        is_swiping = false,
        swipe_start_pos = vmath.vector3(0, 0, 0),
        swipe_direction = vmath.vector3(0, 0, 0)
    }
end

function M.start_swipe(detector, touch_data)
    detector.is_swiping = true
    detector.swipe_start_pos = vmath.vector3(touch_data.start_pos)
end

function M.update_swipe(detector, touch_data, gesture_config)
    if not detector.is_swiping then return nil end

    local distance_vector = touch_data.current_pos - detector.swipe_start_pos
    local distance = vmath.length(distance_vector)

    if distance >= gesture_config.swipe_threshold then
        detector.swipe_direction = vmath.normalize(distance_vector)

        -- Calcular velocidad
        local velocity_magnitude = vmath.length(touch_data.velocity)

        if velocity_magnitude >= gesture_config.swipe_velocity_threshold then
            local direction = M.get_swipe_direction(detector.swipe_direction)

            msg.post("main:/controller", "swipe", {
                direction = direction,
                direction_vector = detector.swipe_direction,
                distance = distance,
                velocity = velocity_magnitude,
                start_pos = detector.swipe_start_pos,
                end_pos = touch_data.current_pos
            })

            return direction
        end
    end

    return nil
end

function M.end_swipe(detector, touch_data, gesture_config)
    if not detector.is_swiping then return nil end

    local distance_vector = touch_data.current_pos - detector.swipe_start_pos
    local distance = vmath.length(distance_vector)

    detector.is_swiping = false

    if distance >= gesture_config.swipe_threshold then
        detector.swipe_direction = vmath.normalize(distance_vector)
        local direction = M.get_swipe_direction(detector.swipe_direction)

        msg.post("main:/controller", "swipe_end", {
            direction = direction,
            direction_vector = detector.swipe_direction,
            distance = distance,
            start_pos = detector.swipe_start_pos,
            end_pos = touch_data.current_pos
        })

        return direction
    end

    return nil
end

function M.get_swipe_direction(direction_vector)
    local x, y = direction_vector.x, direction_vector.y
    local angle = math.atan2(y, x) * 180 / math.pi

    -- Normalizar ángulo a 0-360
    if angle < 0 then angle = angle + 360 end

    -- Determinar dirección basada en ángulo
    if angle >= 337.5 or angle < 22.5 then
        return M.DIRECTIONS.RIGHT
    elseif angle >= 22.5 and angle < 67.5 then
        return M.DIRECTIONS.UP_RIGHT
    elseif angle >= 67.5 and angle < 112.5 then
        return M.DIRECTIONS.UP
    elseif angle >= 112.5 and angle < 157.5 then
        return M.DIRECTIONS.UP_LEFT
    elseif angle >= 157.5 and angle < 202.5 then
        return M.DIRECTIONS.LEFT
    elseif angle >= 202.5 and angle < 247.5 then
        return M.DIRECTIONS.DOWN_LEFT
    elseif angle >= 247.5 and angle < 292.5 then
        return M.DIRECTIONS.DOWN
    elseif angle >= 292.5 and angle < 337.5 then
        return M.DIRECTIONS.DOWN_RIGHT
    end

    return M.DIRECTIONS.RIGHT
end

return M

3. Long Press (Hold)

Hold Detector

-- hold_detector.lua
local M = {}

function M.new()
    return {
        hold_timer = nil,
        is_holding = false,
        hold_position = vmath.vector3(0, 0, 0)
    }
end

function M.start_hold_detection(detector, touch_data, gesture_config)
    detector.hold_position = vmath.vector3(touch_data.start_pos)

    detector.hold_timer = timer.delay(gesture_config.hold_time_threshold, false, function()
        -- Verificar que el toque sigue activo y en la misma posición
        local distance = vmath.length(touch_data.current_pos - detector.hold_position)

        if distance <= gesture_config.tap_threshold then
            detector.is_holding = true

            msg.post("main:/controller", "hold_start", {
                position = detector.hold_position
            })

            -- Iniciar hold continuo
            M.start_continuous_hold(detector, touch_data)
        end
    end)
end

function M.start_continuous_hold(detector, touch_data)
    -- Enviar eventos de hold cada 0.1 segundos
    detector.hold_timer = timer.delay(0.1, true, function()
        if detector.is_holding then
            msg.post("main:/controller", "hold_continue", {
                position = touch_data.current_pos,
                duration = socket.gettime() - touch_data.start_time
            })
        end
    end)
end

function M.cancel_hold(detector)
    if detector.hold_timer then
        timer.cancel(detector.hold_timer)
        detector.hold_timer = nil
    end

    if detector.is_holding then
        msg.post("main:/controller", "hold_end")
        detector.is_holding = false
    end
end

return M

Gestos Multitouch

1. Pinch to Zoom

Pinch Detector

-- pinch_detector.lua
local M = {}

function M.new()
    return {
        is_pinching = false,
        initial_distance = 0,
        current_distance = 0,
        scale_factor = 1,
        center_point = vmath.vector3(0, 0, 0)
    }
end

function M.start_pinch(detector, touch1, touch2)
    detector.is_pinching = true
    detector.initial_distance = vmath.length(touch1.current_pos - touch2.current_pos)
    detector.current_distance = detector.initial_distance
    detector.scale_factor = 1
    detector.center_point = (touch1.current_pos + touch2.current_pos) * 0.5

    msg.post("main:/controller", "pinch_start", {
        center = detector.center_point,
        initial_distance = detector.initial_distance
    })
end

function M.update_pinch(detector, touch1, touch2)
    if not detector.is_pinching then return nil end

    detector.current_distance = vmath.length(touch1.current_pos - touch2.current_pos)
    detector.scale_factor = detector.current_distance / detector.initial_distance
    detector.center_point = (touch1.current_pos + touch2.current_pos) * 0.5

    msg.post("main:/controller", "pinch_update", {
        scale_factor = detector.scale_factor,
        center = detector.center_point,
        distance = detector.current_distance
    })

    return detector.scale_factor
end

function M.end_pinch(detector)
    if not detector.is_pinching then return nil end

    detector.is_pinching = false

    msg.post("main:/controller", "pinch_end", {
        final_scale = detector.scale_factor,
        center = detector.center_point
    })

    return detector.scale_factor
end

return M

2. Rotation Gesture

Rotation Detector

-- rotation_detector.lua
local M = {}

function M.new()
    return {
        is_rotating = false,
        initial_angle = 0,
        current_angle = 0,
        rotation_delta = 0,
        total_rotation = 0,
        center_point = vmath.vector3(0, 0, 0)
    }
end

function M.start_rotation(detector, touch1, touch2)
    detector.is_rotating = true
    detector.initial_angle = M.calculate_angle(touch1.current_pos, touch2.current_pos)
    detector.current_angle = detector.initial_angle
    detector.total_rotation = 0
    detector.center_point = (touch1.current_pos + touch2.current_pos) * 0.5

    msg.post("main:/controller", "rotation_start", {
        center = detector.center_point,
        initial_angle = detector.initial_angle
    })
end

function M.update_rotation(detector, touch1, touch2)
    if not detector.is_rotating then return nil end

    local new_angle = M.calculate_angle(touch1.current_pos, touch2.current_pos)
    detector.rotation_delta = M.normalize_angle(new_angle - detector.current_angle)
    detector.current_angle = new_angle
    detector.total_rotation = detector.total_rotation + detector.rotation_delta
    detector.center_point = (touch1.current_pos + touch2.current_pos) * 0.5

    msg.post("main:/controller", "rotation_update", {
        rotation_delta = detector.rotation_delta,
        total_rotation = detector.total_rotation,
        center = detector.center_point,
        current_angle = detector.current_angle
    })

    return detector.rotation_delta
end

function M.end_rotation(detector)
    if not detector.is_rotating then return nil end

    detector.is_rotating = false

    msg.post("main:/controller", "rotation_end", {
        total_rotation = detector.total_rotation,
        center = detector.center_point
    })

    return detector.total_rotation
end

function M.calculate_angle(pos1, pos2)
    local dx = pos2.x - pos1.x
    local dy = pos2.y - pos1.y
    return math.atan2(dy, dx) * 180 / math.pi
end

function M.normalize_angle(angle)
    while angle > 180 do angle = angle - 360 end
    while angle < -180 do angle = angle + 360 end
    return angle
end

return M

Sistema de Gestos Integrado

1. Gesture Manager Principal

Complete Gesture System

-- gesture_manager.script
local touch_manager = require "main.touch_manager"
local tap_detector = require "main.tap_detector"
local swipe_detector = require "main.swipe_detector"
local hold_detector = require "main.hold_detector"
local pinch_detector = require "main.pinch_detector"
local rotation_detector = require "main.rotation_detector"

function init(self)
    msg.post(".", "acquire_input_focus")

    -- Inicializar detectores
    self.tap_detector = tap_detector.new()
    self.swipe_detector = swipe_detector.new()
    self.hold_detector = hold_detector.new()
    self.pinch_detector = pinch_detector.new()
    self.rotation_detector = rotation_detector.new()

    -- Inicializar touch manager
    touch_manager.init(self)

    -- Estado del sistema
    self.active_gestures = {}

    print("Gesture Manager initialized")
end

function on_input(self, action_id, action)
    if action_id == hash("touch_multi") then
        local touch_id = action.id or 1

        if action.pressed then
            -- Nuevo toque
            local touch_data = touch_manager.add_touch(self, touch_id, action.x, action.y)

            -- Iniciar detección de hold
            hold_detector.start_hold_detection(self.hold_detector, touch_data, self.gesture_config)

            -- Iniciar detección de swipe
            swipe_detector.start_swipe(self.swipe_detector, touch_data)

            -- Verificar gestos multitouch
            self:check_multitouch_gestures()

        elseif action.released then
            -- Fin del toque
            local touch_data = touch_manager.remove_touch(self, touch_id)

            if touch_data then
                -- Procesar tap
                tap_detector.process_tap(self.tap_detector, touch_data, self.gesture_config)

                -- Finalizar swipe
                swipe_detector.end_swipe(self.swipe_detector, touch_data, self.gesture_config)

                -- Cancelar hold
                hold_detector.cancel_hold(self.hold_detector)

                -- Finalizar gestos multitouch
                self:end_multitouch_gestures()
            end

        else
            -- Movimiento del toque
            local touch_data = touch_manager.update_touch(self, touch_id, action.x, action.y)

            if touch_data then
                -- Actualizar swipe
                swipe_detector.update_swipe(self.swipe_detector, touch_data, self.gesture_config)

                -- Actualizar gestos multitouch
                self:update_multitouch_gestures()
            end
        end

        return true
    end

    return false
end

function check_multitouch_gestures(self)
    if self.touch_count >= 2 then
        local touches = {}
        for _, touch in pairs(self.touches) do
            table.insert(touches, touch)
        end

        if #touches >= 2 then
            local touch1, touch2 = touches[1], touches[2]

            -- Iniciar pinch si no está activo
            if not self.pinch_detector.is_pinching then
                pinch_detector.start_pinch(self.pinch_detector, touch1, touch2)
            end

            -- Iniciar rotación si no está activa
            if not self.rotation_detector.is_rotating then
                rotation_detector.start_rotation(self.rotation_detector, touch1, touch2)
            end
        end
    end
end

function update_multitouch_gestures(self)
    if self.touch_count >= 2 then
        local touches = {}
        for _, touch in pairs(self.touches) do
            table.insert(touches, touch)
        end

        if #touches >= 2 then
            local touch1, touch2 = touches[1], touches[2]

            -- Actualizar pinch
            if self.pinch_detector.is_pinching then
                pinch_detector.update_pinch(self.pinch_detector, touch1, touch2)
            end

            -- Actualizar rotación
            if self.rotation_detector.is_rotating then
                rotation_detector.update_rotation(self.rotation_detector, touch1, touch2)
            end
        end
    end
end

function end_multitouch_gestures(self)
    if self.touch_count < 2 then
        -- Finalizar pinch
        if self.pinch_detector.is_pinching then
            pinch_detector.end_pinch(self.pinch_detector)
        end

        -- Finalizar rotación
        if self.rotation_detector.is_rotating then
            rotation_detector.end_rotation(self.rotation_detector)
        end
    end
end

Controles de Juego Virtuales

1. Virtual Joystick

Joystick Virtual

-- virtual_joystick.gui_script
function init(self)
    self.joystick_base = gui.get_node("joystick_base")
    self.joystick_knob = gui.get_node("joystick_knob")

    -- Configuración
    self.is_active = false
    self.touch_id = nil
    self.base_position = gui.get_position(self.joystick_base)
    self.max_distance = 50  -- Radio máximo del joystick
    self.dead_zone = 0.1    -- Zona muerta

    -- Estado actual
    self.current_vector = vmath.vector3(0, 0, 0)
    self.normalized_vector = vmath.vector3(0, 0, 0)
    self.magnitude = 0

    -- Inicialmente oculto
    gui.set_enabled(self.joystick_base, false)
end

function on_input(self, action_id, action)
    if action_id == hash("touch_multi") then
        local touch_id = action.id or 1

        if action.pressed then
            -- Verificar si el toque está en la zona del joystick
            if self:is_in_joystick_area(action.x, action.y) or not self.is_active then
                self:start_joystick(touch_id, action.x, action.y)
            end

        elseif action.released and touch_id == self.touch_id then
            self:end_joystick()

        elseif touch_id == self.touch_id and self.is_active then
            self:update_joystick(action.x, action.y)
        end

        return self.is_active
    end

    return false
end

function start_joystick(self, touch_id, x, y)
    self.is_active = true
    self.touch_id = touch_id

    -- Posicionar joystick en el punto de toque
    self.base_position = vmath.vector3(x, y, 0)
    gui.set_position(self.joystick_base, self.base_position)
    gui.set_position(self.joystick_knob, self.base_position)

    -- Mostrar joystick
    gui.set_enabled(self.joystick_base, true)

    -- Animación de aparición
    gui.set_scale(self.joystick_base, vmath.vector3(0.5, 0.5, 1))
    gui.animate(self.joystick_base, gui.PROP_SCALE, vmath.vector3(1, 1, 1),
               gui.EASING_OUTBACK, 0.2)

    msg.post("main:/controller", "joystick_start")
end

function update_joystick(self, x, y)
    local touch_pos = vmath.vector3(x, y, 0)
    local offset = touch_pos - self.base_position
    local distance = vmath.length(offset)

    -- Limitar distancia al radio máximo
    if distance > self.max_distance then
        offset = vmath.normalize(offset) * self.max_distance
        distance = self.max_distance
    end

    -- Calcular posición del knob
    local knob_pos = self.base_position + offset
    gui.set_position(self.joystick_knob, knob_pos)

    -- Calcular vectores normalizados
    self.magnitude = distance / self.max_distance
    if self.magnitude > self.dead_zone then
        self.normalized_vector = vmath.normalize(offset)
        self.current_vector = self.normalized_vector * self.magnitude
    else
        self.normalized_vector = vmath.vector3(0, 0, 0)
        self.current_vector = vmath.vector3(0, 0, 0)
        self.magnitude = 0
    end

    -- Enviar estado del joystick
    msg.post("main:/controller", "joystick_update", {
        vector = self.current_vector,
        normalized = self.normalized_vector,
        magnitude = self.magnitude
    })
end

function end_joystick(self)
    self.is_active = false
    self.touch_id = nil

    -- Animar knob de vuelta al centro
    gui.animate(self.joystick_knob, gui.PROP_POSITION, self.base_position,
               gui.EASING_OUTQUAD, 0.2)

    -- Animar desaparición
    gui.animate(self.joystick_base, gui.PROP_SCALE, vmath.vector3(0.5, 0.5, 1),
               gui.EASING_INQUAD, 0.2, 0, function()
        gui.set_enabled(self.joystick_base, false)
    end)

    -- Reset valores
    self.current_vector = vmath.vector3(0, 0, 0)
    self.normalized_vector = vmath.vector3(0, 0, 0)
    self.magnitude = 0

    msg.post("main:/controller", "joystick_end")
end

function is_in_joystick_area(self, x, y)
    if not self.is_active then return true end  -- Permitir inicio en cualquier lugar

    local distance = vmath.length(vmath.vector3(x, y, 0) - self.base_position)
    return distance <= self.max_distance * 2  -- Área ampliada para facilitar uso
end

2. Botones Virtuales

Virtual Button

-- virtual_button.gui_script
function init(self)
    self.button_node = gui.get_node("button")
    self.button_label = gui.get_node("button_label")

    -- Estado del botón
    self.is_pressed = false
    self.touch_id = nil

    -- Configuración
    self.button_id = "action_button"  -- ID único del botón
    self.repeat_rate = 0.1           -- Para botones de repetición
    self.repeat_timer = nil

    -- Escalas para feedback visual
    self.normal_scale = vmath.vector3(1, 1, 1)
    self.pressed_scale = vmath.vector3(0.9, 0.9, 1)
end

function on_input(self, action_id, action)
    if action_id == hash("touch_multi") then
        local touch_id = action.id or 1

        if action.pressed then
            if gui.pick_node(self.button_node, action.x, action.y) then
                self:press_button(touch_id)
                return true
            end

        elseif action.released and touch_id == self.touch_id then
            self:release_button()
            return true

        elseif touch_id == self.touch_id and self.is_pressed then
            -- Verificar si el toque sale del botón
            if not gui.pick_node(self.button_node, action.x, action.y) then
                self:cancel_button()
            end
            return true
        end
    end

    return false
end

function press_button(self, touch_id)
    if self.is_pressed then return end

    self.is_pressed = true
    self.touch_id = touch_id

    -- Feedback visual
    gui.animate(self.button_node, gui.PROP_SCALE, self.pressed_scale,
               gui.EASING_OUTQUAD, 0.1)

    -- Feedback háptico (si está disponible)
    if sys.get_sys_info().system_name == "iPhone OS" then
        -- iOS haptic feedback
        msg.post("main:/controller", "haptic_feedback", {type = "light"})
    end

    -- Enviar evento de presión
    msg.post("main:/controller", "button_pressed", {
        button_id = self.button_id
    })

    -- Configurar repetición si es necesario
    if self.repeat_rate > 0 then
        self.repeat_timer = timer.delay(self.repeat_rate, true, function()
            if self.is_pressed then
                msg.post("main:/controller", "button_repeat", {
                    button_id = self.button_id
                })
            end
        end)
    end
end

function release_button(self)
    if not self.is_pressed then return end

    self.is_pressed = false
    self.touch_id = nil

    -- Cancelar repetición
    if self.repeat_timer then
        timer.cancel(self.repeat_timer)
        self.repeat_timer = nil
    end

    -- Feedback visual
    gui.animate(self.button_node, gui.PROP_SCALE, self.normal_scale,
               gui.EASING_OUTBACK, 0.2)

    -- Enviar evento de liberación
    msg.post("main:/controller", "button_released", {
        button_id = self.button_id
    })
end

function cancel_button(self)
    if not self.is_pressed then return end

    self.is_pressed = false
    self.touch_id = nil

    -- Cancelar repetición
    if self.repeat_timer then
        timer.cancel(self.repeat_timer)
        self.repeat_timer = nil
    end

    -- Feedback visual
    gui.animate(self.button_node, gui.PROP_SCALE, self.normal_scale,
               gui.EASING_OUTQUAD, 0.2)

    -- Enviar evento de cancelación
    msg.post("main:/controller", "button_cancelled", {
        button_id = self.button_id
    })
end

Optimización y Performance

1. Object Pooling para Touch Events

Touch Event Pool

-- touch_event_pool.lua
local M = {}

function M.new(initial_size)
    initial_size = initial_size or 20

    return {
        pool = {},
        active_events = {},
        pool_size = initial_size
    }
end

function M.get_event(pool, event_type, data)
    local event = table.remove(pool.pool)

    if not event then
        event = {
            type = "",
            data = {},
            timestamp = 0,
            processed = false
        }
    end

    -- Resetear evento
    event.type = event_type
    event.data = data or {}
    event.timestamp = socket.gettime()
    event.processed = false

    pool.active_events[event] = true

    return event
end

function M.return_event(pool, event)
    if pool.active_events[event] then
        pool.active_events[event] = nil

        -- Limpiar datos
        for k, v in pairs(event.data) do
            event.data[k] = nil
        end

        -- Devolver al pool si hay espacio
        if #pool.pool < pool.pool_size then
            table.insert(pool.pool, event)
        end
    end
end

return M

2. Debounce de Gestos

Gesture Debouncer

-- gesture_debouncer.lua
local M = {}

function M.new(debounce_time)
    return {
        debounce_time = debounce_time or 0.1,
        last_gesture_time = {},
        gesture_queue = {}
    }
end

function M.should_process_gesture(debouncer, gesture_type)
    local current_time = socket.gettime()
    local last_time = debouncer.last_gesture_time[gesture_type] or 0

    if current_time - last_time >= debouncer.debounce_time then
        debouncer.last_gesture_time[gesture_type] = current_time
        return true
    end

    return false
end

function M.queue_gesture(debouncer, gesture_type, data)
    table.insert(debouncer.gesture_queue, {
        type = gesture_type,
        data = data,
        timestamp = socket.gettime()
    })
end

function M.process_queue(debouncer)
    local current_time = socket.gettime()
    local processed = {}

    for i, gesture in ipairs(debouncer.gesture_queue) do
        if current_time - gesture.timestamp >= debouncer.debounce_time then
            msg.post("main:/controller", gesture.type, gesture.data)
            table.insert(processed, i)
        end
    end

    -- Remover gestos procesados
    for i = #processed, 1, -1 do
        table.remove(debouncer.gesture_queue, processed[i])
    end
end

return M

Testing y Debug

1. Touch Visualizer

Visual Debug

-- touch_visualizer.gui_script
function init(self)
    self.debug_enabled = sys.get_config("project.debug", "0") == "1"
    self.touch_indicators = {}
    self.gesture_trails = {}

    if self.debug_enabled then
        print("Touch Visualizer enabled")
    end
end

function on_message(self, message_id, message, sender)
    if not self.debug_enabled then return end

    if message_id == hash("touch_start") then
        self:create_touch_indicator(message.touch_id, message.position)

    elseif message_id == hash("touch_move") then
        self:update_touch_indicator(message.touch_id, message.position)

    elseif message_id == hash("touch_end") then
        self:remove_touch_indicator(message.touch_id)

    elseif message_id == hash("gesture_detected") then
        self:show_gesture_feedback(message.gesture_type, message.position)
    end
end

function create_touch_indicator(self, touch_id, position)
    local indicator = gui.new_box_node(position, vmath.vector3(30, 30, 0))
    gui.set_color(indicator, vmath.vector4(1, 0, 0, 0.7))

    self.touch_indicators[touch_id] = indicator

    -- Animación de aparición
    gui.set_scale(indicator, vmath.vector3(0.5, 0.5, 1))
    gui.animate(indicator, gui.PROP_SCALE, vmath.vector3(1, 1, 1),
               gui.EASING_OUTBACK, 0.2)
end

function update_touch_indicator(self, touch_id, position)
    local indicator = self.touch_indicators[touch_id]
    if indicator then
        gui.set_position(indicator, position)
    end
end

function remove_touch_indicator(self, touch_id)
    local indicator = self.touch_indicators[touch_id]
    if indicator then
        gui.animate(indicator, gui.PROP_SCALE, vmath.vector3(0, 0, 1),
                   gui.EASING_INQUAD, 0.2, 0, function()
            gui.delete_node(indicator)
        end)

        self.touch_indicators[touch_id] = nil
    end
end

Mejores Prácticas

1. Área Táctil Mínima

2. Feedback Inmediato

3. Manejo de Errores

4. Optimización

Esta implementación completa te permite crear controles táctiles profesionales y gestos intuitivos para cualquier tipo de juego móvil.