En julio de 2026 recibí la propuesta de parte de Julián Di Pietro para comenzar a desempeñarme como docente adjunto de la asignatura Música y Sonido para Videojuegos en la Tecnicatura Universitaria en Desarrollo de Videojuegos de la Universidad Nacional Raúl Scalabrini Ortiz (UNSO) en San Isidro. Este nuevo espacio de trabajo abrió también una serie de preguntas sobre cómo acompañar el proceso de aprendizaje de los estudiantes con herramientas que trascendieran el tiempo del aula y que, al mismo tiempo, pudieran ser reutilizadas por otras personas interesadas en estos temas.
De esa inquietud nació el Laboratorio de Sonido y Música para Videojuegos, un proyecto que busca desarrollar diferentes aplicaciones web con fines educativos, construidas utilizando tecnologías abiertas como HTML5, CSS3 y JavaScript, para explorar distintos aspectos del desarrollo de videojuegos desde una perspectiva práctica.

La primera herramienta de esta serie propone un ejercicio sencillo: un pequeño videojuego estilo plataformero en el que es posible cargar archivos de audio propios —música de fondo y efectos sonoros— para observar cómo cambia la experiencia del juego al modificar esos recursos. A partir de esta mecánica simple, la aplicación permite que cada estudiante experimente con distintos recursos sonoros incorporando su propia música de fondo y efectos de sonido, observando de manera inmediata cómo esas decisiones modifican la experiencia de juego. De este modo, el laboratorio busca ayudarnos en la exploración, la comparación y la reflexión sobre el papel que desempeñan la música y el diseño sonoro en la construcción de una experiencia interactiva.
Quienes deseen profundizar en su funcionamiento encontrarán un ejemplo completo de integración entre HTML5, CSS3 y JavaScript, con la implementación de un videojuego que incorpora el uso del elemento Canvas, la carga dinámica de archivos de audio, la reproducción multimedia en el navegador, la detección de colisiones, la física básica y el manejo de eventos de teclado y controles táctiles, entre otros conceptos propios del desarrollo de videojuegos.
Sin embargo, el objetivo del proyecto no es únicamente desarrollar materiales para una asignatura determinada. La intención es construir un conjunto de recursos abiertos que puedan ser utilizados, adaptados y ampliados por cualquier persona interesada en aprender, enseñar o experimentar con estas tecnologías. Por ese motivo decidí que el proyecto tenga una filosofía open source. Considero que el conocimiento adquiere mayor valor cuando puede compartirse, discutirse y transformarse colectivamente. Publicar el código fuente y documentar el proceso de desarrollo no responde solamente a una decisión técnica, sino también a una forma de entender la producción académica y educativa: generar materiales que puedan convertirse en punto de partida para nuevas experiencias, en lugar de constituirse como productos cerrados.
Este artículo forma parte de esa decisión. Más que presentar un resultado terminado, busca compartir el proceso de desarrollo y poner a disposición de la comunidad tanto la aplicación como la documentación de su funcionamiento. Mi intención es que cualquier docente pueda utilizar estos materiales en sus clases; que estudiantes puedan analizarlos para comprender cómo están construidos; y que colegas, amigos o desarrolladores encuentren en ellos una base sobre la cual crear nuevas herramientas, corregir errores, proponer mejoras o desarrollar proyectos completamente diferentes.
Con ese propósito, a continuación presentaré entonces un recorrido técnico por el software, describiendo paso a paso su estructura, las decisiones de diseño adoptadas y el funcionamiento de cada uno de sus componentes. Más que una explicación del resultado final, se trata de una invitación a recorrer el código, comprender su lógica y, si así lo desean, utilizarlo como punto de partida para nuevas experiencias y desarrollos propios. Estoy convencido de que muchas de las mejores iniciativas educativas nacen cuando el conocimiento circula libremente entre quienes enseñan, investigan, programan y aprenden. En un contexto en el que las tecnologías web permiten desarrollar aplicaciones cada vez más potentes sin depender de software propietario, compartir el código deja de ser un gesto accesorio para convertirse en una práctica pedagógica.
El Laboratorio de Sonido y Música para Videojuegos, entonces, es apenas el primer paso de un proyecto más amplio. La idea es continuar desarrollando nuevos laboratorios dedicados a diferentes áreas del desarrollo de videojuegos y la programación, manteniendo siempre el mismo espíritu: crear herramientas abiertas, documentarlas con detalle y ponerlas a disposición de la comunidad para que puedan ser reutilizadas, adaptadas y enriquecidas colectivamente. Ojalá estas búsquedas resulten útiles para otras personas y, sobre todo, sirvan como invitación a desarrollar nuevas ideas. Si este trabajo logra que un estudiante se anime a modificar una línea de código, que un docente adapte una actividad para su clase o que un colega encuentre un punto de partida para una herramienta diferente, entonces habrá cumplido con el propósito con el que fue concebido.
Sin más preámbulos, a continuación, les comparto el desgloce completo de esta primer instancia del proyecto:

Como les mencionaba, este primer proyecto permite al usuario cargar sus propios archivos de audio desde la computadora o dispositivo móvil para reemplazar la música y los efectos sonoros del juego sin necesidad de modificar el código fuente.
Además de servir como un videojuego simple, el proyecto funciona como un laboratorio educativo para comprender conceptos fundamentales del desarrollo de videojuegos en JavaScript. El jugador debe recorrer el escenario, recolectar tres monedas y evitar tocar al enemigo. Si consigue todas las monedas obtiene la victoria; si colisiona con el enemigo, la partida termina.
Tecnologías utilizadas
El proyecto está desarrollado íntegramente del lado del cliente.
- HTML5
- CSS3
- JavaScript (ES6)
- Canvas API
- Audio API
No utiliza frameworks ni bibliotecas externas para la lógica del juego.
Estructura general del proyecto
El proyecto se divide en tres archivos principales.
index.html
Contiene toda la estructura visual de la página:
- Título.
- Panel de carga de sonidos.
- Botones.
- Controles táctiles.
- Canvas del videojuego.
- Avisos para el usuario.
Su función es crear la interfaz que utilizará el jugador.
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Laboratorio Sonoro</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="style.css">
<link rel="shortcut icon" type="image/x-icon" href="https://facundosuarez.com/facuicono.ico">
</head>
<body>
<div class="container">
<header>
<h1>Laboratorio de Sonido y Música para Videojuegos</h1>
</header>
<div id="panel">
<div class="control">
<label>Música de fondo</label>
<input type="file" id="musicFile" accept="audio/*">
</div>
<div class="control">
<label>Sonido de salto</label>
<input type="file" id="jumpFile" accept="audio/*">
</div>
<div class="control">
<label>Sonido de moneda</label>
<input type="file" id="coinFile" accept="audio/*">
</div>
<div class="control">
<label>Sonido Game Over</label>
<input type="file" id="gameOverFile" accept="audio/*">
</div>
<div class="control">
<label>Sonido Victoria</label>
<input type="file" id="winFile" accept="audio/*">
</div>
<div class="buttons">
<button id="startMusic">
▶ Reproducir Música
</button>
<button id="stopMusic">
■ Detener Música
</button>
<button id="restartGame">
↻ Reiniciar Juego
</button>
</div>
</div>
<div id="touchControls">
<button class="touchBtn" id="btnUp">▲</button>
<div class="touchRow">
<button class="touchBtn" id="btnLeft">◀</button>
<button class="touchBtn" id="btnRight">▶</button>
</div>
</div>
<canvas id="game" width="900" height="500"></canvas>
<p style="margin-top:15px; color:#555; font-size:14px; font-family:Arial, sans-serif;">
<strong>Aviso:</strong> Si estás jugando desde un navegador web, es posible que las flechas del teclado también desplacen la página y que la barra espaciadora active algún botón de la interfaz. Si eso ocurre, utilizá las teclas <strong>W</strong>, <strong>A</strong>, <strong>S</strong> y <strong>D</strong> para moverte y saltar durante la partida.
</p>
</div>
<script src="script.js"></script>
</body>
</html>
Podés leer el código completo desde Google Chrome entrando acá: view-source:https://www.facundosuarez.com/artesonoro/laboratoriosonoro/index.html
style.css
Se encarga completamente del aspecto visual.
Entre sus responsabilidades se encuentran:
- Diseño del fondo.
- Panel con efecto Glassmorphism.
- Botones.
- Responsive Design.
- Estilos del Canvas.
- Estilos para dispositivos móviles.
- Controles táctiles.
Toda la apariencia del proyecto está concentrada en este archivo.
*{
margin:0;
padding:0;
box-sizing:border-box;
}
body{
font-family:"Poppins",sans-serif;
background:
linear-gradient(
135deg,
#020617,
#0f172a,
#1e293b
);
color:white;
display:flex;
justify-content:center;
padding:40px;
min-height:100vh;
}
/* ===========================
CONTENEDOR
=========================== */
.container{
width:min(1200px,95vw);
}
/* ===========================
HEADER
=========================== */
header{
text-align:center;
margin-bottom:35px;
}
header h1{
font-size:2.5rem;
font-weight:700;
letter-spacing:1px;
margin-bottom:10px;
text-shadow:
0 5px 20px rgba(0,0,0,.5);
}
header p{
color:#cbd5e1;
font-size:1rem;
}
/* ===========================
PANEL DE AUDIO
=========================== */
#panel{
display:flex;
flex-wrap:wrap;
justify-content:center;
align-items:flex-end;
gap:20px;
margin-bottom:35px;
padding:30px;
background:
linear-gradient(
145deg,
rgba(255,255,255,.12),
rgba(255,255,255,.05)
);
backdrop-filter:blur(20px);
border-radius:24px;
border:
1px solid rgba(255,255,255,.15);
box-shadow:
0 25px 60px rgba(0,0,0,.45);
}
/* ===========================
CONTROLES DE ARCHIVOS
=========================== */
.control{
display:flex;
flex-direction:column;
justify-content:flex-end;
align-items:center;
gap:10px;
width:200px;
}
.control label{
text-align:center;
min-height:42px;
display:flex;
align-items:center;
justify-content:center;
font-size:.85rem;
color:#e2e8f0;
font-weight:500;
}
/* ===========================
INPUT ARCHIVOS
=========================== */
input[type=file]{
width:200px;
height:42px;
padding:8px;
border:none;
border-radius:12px;
background:#f8fafc;
color:#334155;
cursor:pointer;
font-family:"Poppins",sans-serif;
font-size:12px;
transition:.25s;
}
input[type=file]:hover{
transform:
translateY(-3px);
box-shadow:
0 8px 20px rgba(0,0,0,.25);
}
/* ===========================
BOTONES
=========================== */
.buttons{
width:100%;
display:flex;
justify-content:center;
gap:18px;
margin-top:10px;
}
button{
height:45px;
padding:0 28px;
border:none;
border-radius:12px;
cursor:pointer;
font-size:15px;
font-weight:600;
color:white;
transition:.25s;
}
button:hover{
transform:
translateY(-3px);
}
/* ===========================
BOTÓN PLAY
=========================== */
#startMusic{
background:#22c55e;
box-shadow:
0 0 20px rgba(34,197,94,.35);
}
#startMusic:hover{
background:#16a34a;
}
/* ===========================
BOTÓN STOP
=========================== */
#stopMusic{
background:#ef4444;
box-shadow:
0 0 20px rgba(239,68,68,.35);
}
#stopMusic:hover{
background:#dc2626;
}
/* ===========================
BOTÓN REINICIAR
=========================== */
#restartGame{
background:#3b82f6;
box-shadow:
0 0 20px rgba(59,130,246,.35);
}
#restartGame:hover{
background:#2563eb;
}
/* ===========================
CANVAS
=========================== */
canvas{
display:block;
margin:auto;
width:100%;
max-width:900px;
border-radius:20px;
border:
4px solid rgba(255,255,255,.18);
background:#87ceeb;
image-rendering:pixelated;
box-shadow:
0 30px 70px rgba(0,0,0,.55);
}
/* ===========================
AVISO
=========================== */
.notice{
margin-top:20px;
text-align:center;
color:#cbd5e1;
font-size:.9rem;
line-height:1.7;
}
.notice strong{
color:white;
}
/* ===========================
RESPONSIVE
=========================== */
@media(max-width:1100px){
.control{
width:180px;
}
input[type=file]{
width:180px;
}
}
@media(max-width:700px){
#panel{
flex-direction:column;
align-items:center;
}
.buttons{
flex-direction:column;
align-items:center;
}
}
/* ===========================
CONTROLES TÁCTILES
=========================== */
#touchControls{
display:none;
margin:25px auto;
width:220px;
user-select:none;
}
.touchRow{
display:flex;
justify-content:space-between;
margin-top:12px;
}
.touchBtn{
width:90px;
height:90px;
border:none;
border-radius:18px;
font-size:34px;
background:#334155;
color:white;
box-shadow:0 10px 25px rgba(0,0,0,.35);
}
.touchBtn:active{
transform:scale(.95);
background:#2563eb;
}
/* Mostrar sólo en celulares */
@media (max-width:900px){
#touchControls{
display:flex;
flex-direction:column;
align-items:center;
}
}
Podés acceder al código completo acá: https://www.facundosuarez.com/artesonoro/laboratoriosonoro/style.css
script.js
Es el núcleo del proyecto.
Acá se implementa toda la lógica del videojuego:
- movimiento
- gravedad
- colisiones
- sonidos
- puntuación
- victoria
- derrota
- dibujo del escenario
- animación
// =======================================================
// LABORATORIO DE AUDIO PARA VIDEOJUEGOS
// DESARROLLADO POR FACUNDO SUÁREZ EN JULIO DE 2026
// =======================================================
// ===========================
// CANVAS
// ===========================
const canvas = document.getElementById("game");
const ctx = canvas.getContext("2d");
// ===========================
// IMÁGENES
// ===========================
const playerImage = new Image();
playerImage.src = "assets/player_juli_idle.png";
const coinImage = new Image();
coinImage.src = "assets/coin.png";
const enemyImage = new Image();
enemyImage.src = "assets/enemy.png";
// ===========================
// AUDIOS
// ===========================
let music = null;
let jumpSound = null;
let coinSound = null;
let gameOverSound = null;
let winSound = null;
// ===========================
// ELEMENTOS HTML
// ===========================
const musicInput = document.getElementById("musicFile");
const jumpInput = document.getElementById("jumpFile");
const coinInput = document.getElementById("coinFile");
const gameOverInput = document.getElementById("gameOverFile");
const winInput = document.getElementById("winFile");
const startMusicButton = document.getElementById("startMusic");
const stopMusicButton = document.getElementById("stopMusic");
const restartButton = document.getElementById("restartGame");
// Controles táctiles
const btnLeft = document.getElementById("btnLeft");
const btnRight = document.getElementById("btnRight");
const btnUp = document.getElementById("btnUp");
// ===========================
// PERSONAJE
// ===========================
const player = {
x:100,
y:350,
width:64,
height:64,
vx:0,
vy:0,
speed:4,
jumpForce:15,
onGround:false,
facing:1
};
// ===========================
// ENEMIGO
// ===========================
const enemy = {
x:550,
y:356,
width:64,
height:64,
vx:2
};
// ===========================
// MONEDAS
// ===========================
const coins = [
{
x:250,
y:360,
size:64,
taken:false
},
{
x:450,
y:280,
size:64,
taken:false
},
{
x:700,
y:360,
size:64,
taken:false
}
];
// ===========================
// ESTADO DEL JUEGO
// ===========================
let score = 0;
let gameOver = false;
let gameWon = false;
// ===========================
// MUNDO
// ===========================
const gravity = 0.75;
const floorY = 420;
// ===========================
// TECLADO
// ===========================
const keys = {};
window.addEventListener("keydown",(e)=>{
keys[e.key]=true;
});
window.addEventListener("keyup",(e)=>{
keys[e.key]=false;
});
// ===========================
// CARGA DE AUDIOS
// ===========================
// Música
musicInput.addEventListener("change",(e)=>{
const file=e.target.files[0];
if(!file) return;
music=new Audio(URL.createObjectURL(file));
music.loop=true;
});
// Salto
jumpInput.addEventListener("change",(e)=>{
const file=e.target.files[0];
if(!file) return;
jumpSound=new Audio(URL.createObjectURL(file));
});
// Moneda
coinInput.addEventListener("change",(e)=>{
const file=e.target.files[0];
if(!file) return;
coinSound=new Audio(URL.createObjectURL(file));
});
// Game Over
gameOverInput.addEventListener("change",(e)=>{
const file=e.target.files[0];
if(!file) return;
gameOverSound=new Audio(URL.createObjectURL(file));
});
// Victoria
winInput.addEventListener("change",(e)=>{
const file=e.target.files[0];
if(!file) return;
winSound=new Audio(URL.createObjectURL(file));
});
// ===========================
// BOTONES DE MÚSICA
// ===========================
startMusicButton.addEventListener("click",()=>{
if(!music){
alert("Primero elegí un archivo de música.");
return;
}
music.play();
});
stopMusicButton.addEventListener("click",()=>{
if(!music) return;
music.pause();
music.currentTime=0;
});
restartButton.addEventListener("click", resetGame);
// ===========================
// COLISIONES
// ===========================
function collision(a,b){
return(
a.x < b.x + b.width &&
a.x + a.width > b.x &&
a.y < b.y + b.height &&
a.y + a.height > b.y
);
}
// ===========================
// CONTROLES
// ===========================
function controls(){
player.vx=0;
// Izquierda
if(
keys["ArrowLeft"] ||
keys["a"] ||
keys["A"]
){
player.vx=-player.speed;
player.facing=-1;
}
// Derecha
if(
keys["ArrowRight"] ||
keys["d"] ||
keys["D"]
){
player.vx=player.speed;
player.facing=1;
}
// Salto
if(
(
keys[" "] ||
keys["ArrowUp"] ||
keys["w"] ||
keys["W"]
)
&&
player.onGround
){
player.vy=-player.jumpForce;
player.onGround=false;
if(jumpSound){
jumpSound.currentTime=0;
jumpSound.play();
}
}
}
// ===========================
// FÍSICA
// ===========================
function physics(){
player.vy+=gravity;
player.x+=player.vx;
player.y+=player.vy;
// Piso
if(player.y+player.height>=floorY){
player.y=floorY-player.height;
player.vy=0;
player.onGround=true;
}
// Borde izquierdo
if(player.x<0){
player.x=0;
}
// Borde derecho
if(player.x+player.width>canvas.width){
player.x=canvas.width-player.width;
}
}
// ===========================
// ACTUALIZAR ENEMIGO
// ===========================
function updateEnemy(){
enemy.x += enemy.vx;
if(enemy.x < 450 || enemy.x > 750){
enemy.vx *= -1;
}
}
// ===========================
// ACTUALIZAR MONEDAS
// ===========================
function updateCoins(){
for(const coin of coins){
if(coin.taken) continue;
if(
collision(
player,
{
x:coin.x,
y:coin.y,
width:coin.size,
height:coin.size
}
)
){
coin.taken = true;
score++;
if(coinSound){
coinSound.currentTime = 0;
coinSound.play();
}
}
}
}
// ===========================
// COMPROBAR VICTORIA
// ===========================
function checkWin(){
if(score >= 3 && !gameWon){
gameWon = true;
if(winSound){
winSound.currentTime = 0;
winSound.play();
}
if(music){
music.pause();
}
}
}
// ===========================
// COMPROBAR GAME OVER
// ===========================
function checkGameOver(){
if(gameOver) return;
if(collision(player,enemy)){
gameOver = true;
if(gameOverSound){
gameOverSound.currentTime = 0;
gameOverSound.play();
}
if(music){
music.pause();
}
}
}
function resetGame(){
player.x = 100;
player.y = 350;
player.vx = 0;
player.vy = 0;
player.onGround = false;
enemy.x = 550;
enemy.vx = 2;
score = 0;
gameOver = false;
gameWon = false;
for(const coin of coins){
coin.taken = false;
}
if(music){
music.currentTime = 0;
music.play();
}
}
// ===========================
// DIBUJAR FONDO
// ===========================
function drawBackground(){
// Cielo
ctx.fillStyle = "#87CEEB";
ctx.fillRect(
0,
0,
canvas.width,
canvas.height
);
// Piso
ctx.fillStyle = "#5dbb63";
ctx.fillRect(
0,
floorY,
canvas.width,
canvas.height-floorY
);
}
// ===========================
// DIBUJAR MONEDAS
// ===========================
function drawCoins(){
for(const coin of coins){
if(coin.taken) continue;
ctx.drawImage(
coinImage,
coin.x,
coin.y,
coin.size,
coin.size
);
}
}
// ===========================
// DIBUJAR ENEMIGO
// ===========================
function drawEnemy(){
ctx.drawImage(
enemyImage,
enemy.x,
enemy.y,
enemy.width,
enemy.height
);
}
// ===========================
// DIBUJAR PERSONAJE
// ===========================
function drawPlayer(){
ctx.save();
if(player.facing === 1){
ctx.drawImage(
playerImage,
player.x,
player.y,
player.width,
player.height
);
}else{
ctx.translate(
player.x + player.width,
player.y
);
ctx.scale(-1,1);
ctx.drawImage(
playerImage,
0,
0,
player.width,
player.height
);
}
ctx.restore();
}
// ===========================
// HUD
// ===========================
function drawHUD(){
ctx.fillStyle = "black";
ctx.font = "20px Arial";
ctx.fillText(
"Movimiento: WASD o Flechas",
20,
30
);
ctx.fillText(
"Salto: Espacio / W / ↑",
20,
60
);
ctx.fillText(
"Monedas: " + score + "/3",
20,
90
);
if(gameWon){
ctx.fillStyle = "green";
ctx.font = "48px Arial";
ctx.fillText(
"¡GANASTE!",
260,
220
);
}
if(gameOver){
ctx.fillStyle = "red";
ctx.font = "48px Arial";
ctx.fillText(
"GAME OVER",
230,
220
);
}
}
// ===========================
// DIBUJADO GENERAL
// ===========================
function draw(){
drawBackground();
drawCoins();
drawEnemy();
drawPlayer();
drawHUD();
}
// ===========================
// UPDATE
// ===========================
function update(){
if(gameOver || gameWon){
return;
}
controls();
physics();
updateEnemy();
updateCoins();
checkWin();
checkGameOver();
}
// ===========================
// GAME LOOP
// ===========================
function gameLoop(){
update();
draw();
requestAnimationFrame(gameLoop);
}
// ===========================
// ESPERAR CARGA DE IMÁGENES
// ===========================
let imagesLoaded = 0;
function imageLoaded(){
imagesLoaded++;
if(imagesLoaded === 3){
console.log("Todas las imágenes cargadas.");
gameLoop();
}
}
playerImage.onload = imageLoaded;
coinImage.onload = imageLoaded;
enemyImage.onload = imageLoaded;
playerImage.onerror = ()=>{
console.error("No se encontró assets/player_juli_idle.png");
};
coinImage.onerror = ()=>{
console.error("No se encontró assets/coin.png");
};
enemyImage.onerror = ()=>{
console.error("No se encontró assets/enemy.png");
};
function bindTouch(button,key){
button.addEventListener("touchstart",(e)=>{
e.preventDefault();
keys[key]=true;
});
button.addEventListener("touchend",(e)=>{
e.preventDefault();
keys[key]=false;
});
button.addEventListener("touchcancel",(e)=>{
e.preventDefault();
keys[key]=false;
});
}
bindTouch(btnLeft,"ArrowLeft");
bindTouch(btnRight,"ArrowRight");
bindTouch(btnUp,"ArrowUp");
Podés acceder al código completo acá: https://www.facundosuarez.com/artesonoro/laboratoriosonoro/script.js
Funcionamiento general del juego
Cuando la página termina de cargar ocurre el siguiente proceso:
- Se crean todas las variables.
- Se cargan las imágenes.
- Se preparan los audios.
- Se registran los eventos del teclado.
- Se esperan los archivos de sonido elegidos por el usuario.
- Cuando las tres imágenes terminan de cargarse comienza el Game Loop.
A partir de ese momento el juego se actualiza aproximadamente 60 veces por segundo.
Explicación del HTML
Header
Muestra el título principal del laboratorio.
<header>
<h1>Laboratorio de Sonido y Música para Videojuegos</h1>
</header>
Su única función es presentar el proyecto.
Panel de sonidos
Está formado por cinco controles.
Cada uno permite seleccionar un archivo de audio diferente.
Los archivos pueden ser:
- MP3
- WAV
- OGG
- cualquier formato compatible con el navegador
Cada selector se encuentra asociado a un elemento diferente del juego.
Ejemplo:
<input type=»file» id=»musicFile»>
Cuando el usuario selecciona un archivo, JavaScript lo convierte en un objeto Audio.
Botones
Existen tres botones principales.
Reproducir música
Empieza la reproducción del archivo cargado como música de fondo («le da play»).
Detener música
Pausa la reproducción y vuelve el audio al segundo cero.
Reiniciar juego
Restaura completamente el estado inicial del videojuego.
Canvas
Es la superficie donde ocurre todo el juego.
<canvas id=»game» width=»900″ height=»500″></canvas>
Acá se dibujan:
- jugador
- enemigo
- monedas
- fondo
- HUD
- mensajes de victoria
Controles táctiles
Para dispositivos móviles se incorporó un pequeño teclado con botones táctiles.
Estos botones simulan las flechas del teclado utilizando eventos touchstart y touchend, permitiendo jugar desde celulares sin modificar la lógica existente.
Explicación del CSS
El archivo CSS organiza los estilos en bloques claramente separados.
Reset
*{
margin:0;
padding:0;
box-sizing:border-box;
}
Elimina márgenes automáticos del navegador para lograr un comportamiento uniforme.
Body
Define:
- fuente
- fondo degradado
- centrado del contenido
- altura mínima
También utiliza Flexbox para centrar todo el contenido.
Container
Limita el ancho máximo del proyecto evitando que el contenido se estire demasiado en pantallas grandes.
Header
Configura:
- tamaño del título
- sombras
- separación
Panel
Utiliza Glassmorphism mediante:
- transparencia
- desenfoque
- bordes semitransparentes
- sombras
Inputs
Cada selector de archivos tiene:
- bordes redondeados
- animación al pasar el mouse
- colores claros
Botones
Cada botón posee:
- color propio
- sombra
- transición
- efecto hover
Canvas
El canvas recibe:
- borde redondeado
- sombra
- borde translúcido
Responsive
Mediante Media Queries el diseño se adapta automáticamente a celulares.
Controles táctiles
Solo se muestran en pantallas pequeñas.
Los botones poseen:
- tamaño grande
- esquinas redondeadas
- cambio de color al presionar
Explicación del JavaScript
El archivo JavaScript está dividido en secciones.
Canvas
const canvas=document.getElementById(«game»);
const ctx=canvas.getContext(«2d»);
Se obtiene el contexto 2D donde posteriormente se dibuja todo.
Imágenes
Se crean tres objetos Image.
- jugador
- moneda
- enemigo
Cada uno carga un archivo PNG.
Audios
Inicialmente todos valen:
null
Luego se reemplazan por objetos Audio cuando el usuario selecciona un archivo.
Elementos HTML
Se almacenan referencias a todos los botones e inputs para poder manipularlos desde JavaScript.
Jugador
El jugador se representa mediante un objeto.
Contiene:
- posición
- velocidad
- tamaño
- dirección
- fuerza de salto
- estado del suelo
Toda la física depende de estas variables.
Enemigo
También es un objeto.
Se mueve automáticamente entre dos posiciones.
Cuando alcanza un extremo cambia el sentido de movimiento.
Monedas
Las monedas se almacenan dentro de un arreglo.
Cada moneda tiene:
- posición
- tamaño
- estado (tomada o no)
Cuando el jugador la toca desaparece.
Estado del juego
Se utilizan tres variables principales.
- score
- gameOver
- gameWon
Estas controlan el flujo completo de la partida.
Gravedad
La gravedad incrementa constantemente la velocidad vertical del jugador.
Esto produce el efecto de caída.
Piso
Cuando el personaje alcanza el piso:
- deja de caer
- su velocidad vertical pasa a cero
- puede volver a saltar
Teclado
Se utiliza un objeto llamado:
keys
Cada tecla presionada se guarda dentro de ese objeto.
Por ejemplo:
keys[«ArrowLeft»]=true;
Cuando se libera la tecla:
keys[«ArrowLeft»]=false;
Este enfoque permite detectar varias teclas al mismo tiempo.
Controles táctiles
Los botones del celular modifican exactamente el mismo objeto keys.
Por esa razón, tanto el teclado físico como la pantalla táctil utilizan la misma lógica de movimiento y pueden convivir sin conflictos.
Carga de audios
Cada input escucha el evento:
change
Cuando el usuario selecciona un archivo ocurre:
- Se obtiene el archivo.
- Se crea una URL temporal.
- Se construye un objeto Audio.
- Se guarda para utilizarlo durante la partida.
Controles
La función controls() interpreta el estado del objeto keys.
Dependiendo de las teclas presionadas:
- mueve al jugador
- cambia su dirección
- realiza el salto
- reproduce el sonido correspondiente
Física
La función physics() actualiza:
- gravedad
- movimiento
- límites del escenario
- contacto con el suelo
Colisiones
La función collision() compara los rectángulos de dos objetos.
Si sus áreas se superponen devuelve:
true
En caso contrario devuelve:
false
Esta función se reutiliza para monedas y enemigo.
Monedas
Cada cuadro se verifica si el jugador toca alguna moneda.
Si ocurre:
- desaparece
- aumenta el puntaje
- reproduce sonido
Enemigo
El enemigo se mueve automáticamente.
Al llegar a un extremo invierte su velocidad.
Victoria
Cuando el puntaje llega a tres:
- se detiene la música
- se reproduce el sonido de victoria
- aparece el mensaje «¡GANASTE!»
Game Over
Si el jugador toca al enemigo:
- termina la partida
- se detiene la música
- se reproduce el sonido correspondiente
- aparece «GAME OVER»
Reinicio
La función resetGame() devuelve el juego a su estado inicial.
Se restauran:
- jugador
- enemigo
- monedas
- puntaje
- variables de estado
Dibujo
Existen funciones específicas para dibujar cada elemento.
- drawBackground()
- drawCoins()
- drawEnemy()
- drawPlayer()
- drawHUD()
Esta división mejora la organización y facilita el mantenimiento del código.
Update
La función update() ejecuta toda la lógica del juego.
Su orden es:
- Leer controles.
- Aplicar física.
- Mover enemigo.
- Actualizar monedas.
- Verificar victoria.
- Verificar derrota.
Draw
La función draw() dibuja el estado actual del juego.
Cada cuadro limpia y vuelve a dibujar todos los elementos.
Game Loop
Es el corazón del videojuego.
Se ejecuta mediante:
requestAnimationFrame()
Este método sincroniza la animación con la frecuencia de actualización del navegador, proporcionando un movimiento fluido y eficiente.
El ciclo es el siguiente:
- Actualizar lógica.
- Dibujar escena.
- Solicitar el siguiente cuadro.
Este proceso se repite continuamente hasta que el usuario cierra la página.
Comentarios Finales
Este proyecto integra en una única aplicación numerosos conceptos fundamentales del desarrollo de videojuegos en JavaScript. A través de una arquitectura sencilla y modular, combina la manipulación del DOM, la API Canvas, la gestión de eventos de teclado y táctiles, la reproducción de audio, la detección de colisiones y un ciclo de animación basado en requestAnimationFrame().
Además de cumplir con el objetivo de ofrecer un videojuego funcional, el laboratorio constituye una herramienta didáctica para comprender cómo interactúan las distintas tecnologías web en la construcción de aplicaciones interactivas. Su estructura modular facilita futuras ampliaciones, como la incorporación de nuevos niveles, enemigos, animaciones, efectos visuales o mecánicas de juego, convirtiéndolo en una base sólida para proyectos de mayor complejidad.
La foto de portada de este artículo fue tomada de https://www.laizquierdadiario.com/local/cache-vignettes/L720xH461/arton53773-4d38c.jpg?1785356366 en Julio de 2026
