← Volver al listado de tecnologías
Control Táctil, Gestos y Multitouch
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
- Botones: mínimo 44pt (iOS) / 48dp (Android)
- Elementos interactivos: separación mínima de 8pt
- Zonas de alcance cómodo del pulgar
2. Feedback Inmediato
- Respuesta visual instantánea (<100ms)
- Feedback háptico en acciones importantes
- Estados visuales claros (normal, pressed, disabled)
3. Manejo de Errores
- Cancelación de gestos si el dedo sale del área
- Timeouts para gestos incompletos
- Fallbacks para gestos no reconocidos
4. Optimización
- Limitar eventos de input por frame
- Usar object pooling para eventos frecuentes
- Debounce de gestos rápidos
Esta implementación completa te permite crear controles táctiles profesionales y gestos intuitivos para cualquier tipo de juego móvil.