Cómo añadir atajos de teclado personalizados
Este tutorial fue escrito en febrero de 2026, para la v2 del motor.
Los jugadores tienen opiniones fuertes sobre los controles. Algunos juran por WASD, otros necesitan las teclas de flecha, y mucha gente quiere usar un mando. Un buen sistema de atajos permite a los jugadores configurar las cosas a su manera — y recuerda sus elecciones entre sesiones.
Vamos a construir un sistema de atajos completo: una capa de abstracción sobre la entrada directa, una forma de capturar nuevos atajos y persistencia con localStorage.
Entendiendo la API de entrada
Floaty nos da dos funciones para la entrada de teclado. btn(key) devuelve true en cada fotograma mientras una tecla está pulsada — úsala para acciones continuas como el movimiento. btnp(key) devuelve true solo en el primer fotograma de una pulsación — úsala para acciones discretas como saltar o seleccionar en un menú.
Veamos cómo funcionan:
// in your draw() or update() function
if (btn('ArrowUp')) {
player.y -= 1; // move up while held
}
if (btnp('z')) {
player.jump(); // trigger once per press
}
El parámetro key corresponde a los valores de KeyboardEvent.key de JavaScript: 'ArrowUp', 'ArrowDown', 'z', 'x', 'Enter', ' ' (espacio), etc.
Esto funciona bien. Pero codificar los nombres de las teclas en todo tu juego hace que reasignarlas sea una pesadilla.
Creando un mapa de entrada
En lugar de llamar a btn('ArrowUp') directamente, definimos un mapeo de nombres de acciones a teclas. Luego comprobamos las acciones por nombre — las teclas reales se configuran en un solo lugar:
const keybinds = {
up: 'ArrowUp',
down: 'ArrowDown',
left: 'ArrowLeft',
right: 'ArrowRight',
jump: 'z',
action: 'x',
};
function input(action) {
return btn(keybinds[action]);
}
function inputPressed(action) {
return btnp(keybinds[action]);
}
// in draw() or update()
if (input('up')) {
player.y -= 1;
}
if (inputPressed('jump')) {
player.jump();
}
Esta es la base de todo sistema de atajos. La lógica de tu juego usa nombres semánticos como 'jump' y 'action', mientras que el objeto keybinds maneja la traducción. ¿Quieres cambiar saltar de Z a Espacio? Actualiza una sola línea.
Consejo: Usa nombres de acciones que describan lo que sucede, no qué tecla lo hace. 'jump' es mejor que 'z_key' — sigue siendo significativo incluso después de reasignar.
Capturando nuevos atajos
Ahora viene la parte divertida. Para permitir a los jugadores reasignar teclas en tiempo de ejecución, necesitamos capturar la siguiente pulsación y asignarla a una acción. Establecemos un indicador que señale qué acción está esperando una nueva tecla, y luego escuchamos el siguiente evento keydown:
let waitingForKey = null; // which action we're rebinding
function startRebind(action) {
waitingForKey = action;
}
function captureKey(event) {
if (waitingForKey) {
keybinds[waitingForKey] = event.key;
waitingForKey = null;
}
}
// attach this to a DOM element or check in update()
window.addEventListener('keydown', captureKey);
Cuando waitingForKey está establecido, la siguiente tecla pulsada se convierte en el nuevo atajo para esa acción. Una interfaz típica de reasignación muestra una lista de acciones — cuando el jugador hace clic en una, llamas a startRebind() con el nombre de esa acción, muestras "Pulsa una tecla..." y esperas.
Resulta que este patrón funciona para prácticamente cualquier sistema de configuración de entrada.
Guardando y cargando atajos
Los jugadores esperan que sus ajustes persistan. Nadie quiere reasignar sus controles cada vez que abre el juego. Usamos localStorage para guardar el objeto de atajos como JSON, y cargarlo de nuevo al iniciar el juego:
const STORAGE_KEY = 'my-game-keybinds';
function saveKeybinds() {
localStorage.setItem(STORAGE_KEY, JSON.stringify(keybinds));
}
function loadKeybinds() {
const saved = localStorage.getItem(STORAGE_KEY);
if (saved) {
Object.assign(keybinds, JSON.parse(saved));
}
}
// call loadKeybinds() in init() to restore settings
// call saveKeybinds() after any rebind
Llama a loadKeybinds() en tu función init() para que los jugadores que vuelven obtengan sus controles personalizados inmediatamente. Llama a saveKeybinds() después de cada reasignación para que los cambios persistan.
Juntándolo todo
Vamos a conectarlo todo. Aquí hay una demo funcional con un cuadrado móvil y un menú de reasignación dentro del juego. Las teclas de flecha navegan el menú por defecto, Enter inicia la reasignación de la acción seleccionada. El cuadrado del jugador se mueve usando las teclas que estén asignadas actualmente:
engine.scope(({ start, cls, rectfill, text, btn, btnp }) => {
const keybinds = {
up: 'ArrowUp',
down: 'ArrowDown',
left: 'ArrowLeft',
right: 'ArrowRight',
action: 'z',
};
const player = { x: 60, y: 60 };
let waitingForKey = null;
let menuIndex = 0;
const actions = ['up', 'down', 'left', 'right', 'action'];
function input(action) {
return btn(keybinds[action]);
}
function inputPressed(action) {
return btnp(keybinds[action]);
}
function loadKeybinds() {
const saved = localStorage.getItem('demo-keybinds');
if (saved) Object.assign(keybinds, JSON.parse(saved));
}
function saveKeybinds() {
localStorage.setItem('demo-keybinds', JSON.stringify(keybinds));
}
function init() {
loadKeybinds();
}
function draw() {
cls(0);
if (waitingForKey) {
text('Press a key for:', 20, 50, 7);
text(waitingForKey.toUpperCase(), 20, 60, 10);
return;
}
// player movement
if (input('up')) player.y -= 1;
if (input('down')) player.y += 1;
if (input('left')) player.x -= 1;
if (input('right')) player.x += 1;
// clamp to screen
player.x = Math.max(0, Math.min(120, player.x));
player.y = Math.max(0, Math.min(120, player.y));
// draw player
rectfill(player.x, player.y, player.x + 7, player.y + 7, 11);
// draw keybind menu
for (let i = 0; i < actions.length; i++) {
const action = actions[i];
const y = 4 + i * 10;
const color = i === menuIndex ? 10 : 6;
text(`${action}: ${keybinds[action]}`, 4, y, color);
}
// menu navigation
if (btnp('ArrowDown')) menuIndex = (menuIndex + 1) % actions.length;
if (btnp('ArrowUp')) menuIndex = (menuIndex - 1 + actions.length) % actions.length;
if (btnp('Enter')) {
waitingForKey = actions[menuIndex];
}
}
// capture new keybind
window.addEventListener('keydown', (e) => {
if (waitingForKey) {
keybinds[waitingForKey] = e.key;
saveKeybinds();
waitingForKey = null;
}
});
start({ sprites: {}, sounds: {}, init, draw, target });
});
Los atajos se guardan en localStorage automáticamente después de cada cambio, así que refrescar la página mantiene tus controles personalizados.
Tómate un tiempo para jugar con la demo. Reasigna algunas teclas, refresca la página y comprueba que tus cambios se mantienen. Prueba a asignar la misma tecla a múltiples acciones y mira qué pasa.
Para ir más allá
- Restaurar valores predeterminados — añade un botón que restaure el objeto
keybindsoriginal y limpie localStorage - Detección de conflictos — avisa a los jugadores si asignan la misma tecla a múltiples acciones
- Soporte de mando — extiende la función
input()para comprobar tanto el teclado como los botones del mando - Múltiples perfiles — permite a los jugadores cambiar entre esquemas de control (ej. "teclado", "una mano")