1 <!DOCTYPE html>2 <html lang="es">3 <head>4 <meta charset="UTF-8">5 <title>Juego de Plataformas: Pelota Elástica</title>6 <style>7 body {8 margin: 0;9 padding: 0;10 background-color: #222;11 display: flex;12 justify-content: center;13 align-items: center;14 height: 100vh;15 color: white;16 font-family: 'Arial', sans-serif;17 overflow: hidden;18 }19 #gameContainer {20 position: relative;21 box-shadow: 0 0 20px rgba(0,0,0,0.5);22 }23 canvas {24 background: linear-gradient(to bottom, #87CEEB, #E0F7FA);25 border: 4px solid #444;26 border-radius: 4px;27 }28 #ui {29 position: absolute;30 top: 10px;31 left: 10px;32 font-size: 20px;33 font-weight: bold;34 color: #333;35 text-shadow: 1px 1px 0px white;36 }37 #gameOverScreen {38 position: absolute;39 top: 0; left: 0; width: 100%; height: 100%;40 background: rgba(0,0,0,0.85);41 display: none;42 flex-direction: column;43 justify-content: center;44 align-items: center;45 color: white;46 }47 button {48 padding: 10px 20px;49 font-size: 18px;50 cursor: pointer;51 background: #ff4757;52 border: none;53 color: white;54 border-radius: 5px;55 margin-top: 20px;56 }57 button:hover { background: #ff6b81; }58 </style>59 </head>60 <body>61 62 <div id="gameContainer">63 <div id="ui">Vidas: <span id="livesDisplay">3</span></div>64 <canvas id="gameCanvas" width="800" height="450"></canvas>65 66 <div id="gameOverScreen">67 <h1 id="statusText">GAME OVER</h1>68 <button onclick="resetGame(true)">Jugar de nuevo</button>69 </div>70 </div>71 72 <script>73 const canvas = document.getElementById('gameCanvas');74 const ctx = canvas.getContext('2d');75 const livesDisplay = document.getElementById('livesDisplay');76 const gameOverScreen = document.getElementById('gameOverScreen');77 const statusText = document.getElementById('statusText');78 79 // --- CONFIGURACIÓN DEL JUEGO ---80 const GRAVITY = 0.6;81 const FRICTION = 0.8;82 const SPEED = 1;83 const JUMP_FORCE = -14;84 85 // Estado del juego86 let gameRunning = true;87 let keys = { ArrowRight: false, ArrowLeft: false, ArrowUp: false };88 89 // --- EL JUGADOR (LA PELOTA) ---90 const player = {91 x: 100,92 y: 100,93 radius: 15,94 color: '#ff4757',95 vx: 0,96 vy: 0,97 isGrounded: false,98 lives: 3,99 // Propiedades de deformación (Squash & Stretch)100 scaleX: 1,101 scaleY: 1,102 103 update: function() {104 // Movimiento Horizontal105 if (keys.ArrowRight) this.vx += SPEED;106 if (keys.ArrowLeft) this.vx -= SPEED;107 108 this.vx *= FRICTION;109 this.x += this.vx;110 111 // Colisión Horizontal112 checkCollisions(this, 'x');113 114 // Movimiento Vertical115 this.vy += GRAVITY;116 this.y += this.vy;117 this.isGrounded = false; // Asumimos que cae hasta que se demuestre lo contrario118 119 // Colisión Vertical120 checkCollisions(this, 'y');121 122 // Salto123 if (keys.ArrowUp && this.isGrounded) {124 this.vy = JUMP_FORCE;125 this.isGrounded = false;126 // Efecto elástico al saltar (se estira verticalmente)127 this.scaleX = 0.7;128 this.scaleY = 1.3;129 }130 131 // Muerte por caída132 if (this.y > canvas.height + 50) {133 playerDie();134 }135 136 // --- Lógica de Deformación Visual ---137 // Recuperar forma original poco a poco138 this.scaleX += (1 - this.scaleX) * 0.1;139 this.scaleY += (1 - this.scaleY) * 0.1;140 141 // Deformación basada en velocidad (Estirarse al caer o subir rápido)142 if (!this.isGrounded) {143 // Cuanto más rápido vaya verticalmente, más se estira en Y144 const stretch = Math.abs(this.vy) * 0.03; 145 this.scaleY = 1 + stretch;146 this.scaleX = 1 - (stretch * 0.5); // Conservar volumen147 }148 },149 150 draw: function() {151 ctx.save();152 // Trasladar al centro de la pelota para escalar desde el centro153 ctx.translate(this.x, this.y);154 ctx.scale(this.scaleX, this.scaleY);155 156 // Dibujar la pelota157 ctx.beginPath();158 ctx.arc(0, 0, this.radius, 0, Math.PI * 2);159 ctx.fillStyle = this.color;160 ctx.fill();161 162 // Brillo (para que parezca 3D)163 ctx.beginPath();164 ctx.arc(-5, -5, 5, 0, Math.PI * 2);165 ctx.fillStyle = 'rgba(255,255,255,0.5)';166 ctx.fill();167 168 ctx.restore();169 }170 };171 172 // --- PLATAFORMAS ---173 // x, y, ancho, alto, tipo (static, moving), props extra174 let platforms = [];175 176 function initLevel() {177 platforms = [178 // Suelo inicial179 { x: 0, y: 350, w: 300, h: 40, type: 'static' },180 181 // Primer salto182 { x: 380, y: 280, w: 100, h: 20, type: 'static' },183 184 // Plataforma móvil horizontal185 { x: 550, y: 200, w: 120, h: 20, type: 'moving', dx: 2, minX: 550, maxX: 750 },186 187 // Plataforma alta188 { x: 850, y: 150, w: 100, h: 20, type: 'static' },189 190 // Suelo final lejos (hay un agujero grande antes)191 { x: 1050, y: 350, w: 400, h: 40, type: 'static' }192 ];193 }194 195 // --- CÁMARA ---196 // La cámara sigue al jugador suavemente197 let cameraX = 0;198 199 // --- FÍSICA Y COLISIONES ---200 function checkCollisions(p, axis) {201 for (let plat of platforms) {202 // Verificar solapamiento básico AABB (Axis-Aligned Bounding Box)203 // Consideramos la pelota como un cuadrado para las colisiones físicas204 if (p.x + p.radius > plat.x && 205 p.x - p.radius < plat.x + plat.w &&206 p.y + p.radius > plat.y && 207 p.y - p.radius < plat.y + plat.h) {208 209 if (axis === 'x') {210 // Chocar lateralmente211 if (p.vx > 0) p.x = plat.x - p.radius;212 if (p.vx < 0) p.x = plat.x + plat.w + p.radius;213 p.vx = 0;214 } else {215 // Chocar verticalmente216 if (p.vy > 0) { // Cayendo217 p.y = plat.y - p.radius;218 p.isGrounded = true;219 220 // Efecto de aplastamiento al aterrizar221 if (p.vy > 2) { // Solo si cae con fuerza222 p.scaleX = 1.4;223 p.scaleY = 0.6;224 }225 226 // Si es plataforma móvil, mover al jugador con ella227 if (plat.type === 'moving') {228 p.x += plat.dx;229 }230 } else if (p.vy < 0) { // Saltando y golpeando cabeza231 p.y = plat.y + plat.h + p.radius;232 }233 p.vy = 0;234 }235 }236 }237 }238 239 function updatePlatforms() {240 for (let plat of platforms) {241 if (plat.type === 'moving') {242 plat.x += plat.dx;243 // Rebotar entre límites244 if (plat.x > plat.maxX || plat.x < plat.minX) {245 plat.dx *= -1;246 }247 }248 }249 }250 251 function drawPlatforms() {252 for (let plat of platforms) {253 ctx.fillStyle = plat.type === 'moving' ? '#FFD700' : '#654321';254 // Bordes verdes si es suelo estático255 if(plat.type === 'static') ctx.strokeStyle = '#2E8B57';256 else ctx.strokeStyle = '#DAA520';257 258 ctx.lineWidth = 3;259 ctx.fillRect(plat.x, plat.y, plat.w, plat.h);260 ctx.strokeRect(plat.x, plat.y, plat.w, plat.h);261 }262 }263 264 // --- GESTIÓN DEL JUEGO ---265 function playerDie() {266 player.lives--;267 livesDisplay.innerText = player.lives;268 269 if (player.lives > 0) {270 // Respawn271 player.x = 100;272 player.y = 100;273 player.vx = 0;274 player.vy = 0;275 cameraX = 0; // Resetear cámara276 } else {277 gameRunning = false;278 statusText.innerText = "¡TE HAS QUEDADO SIN VIDAS!";279 gameOverScreen.style.display = 'flex';280 }281 }282 283 function resetGame(fullReset = false) {284 if (fullReset) {285 player.lives = 3;286 livesDisplay.innerText = 3;287 }288 player.x = 100;289 player.y = 100;290 player.vx = 0;291 player.vy = 0;292 cameraX = 0;293 gameRunning = true;294 gameOverScreen.style.display = 'none';295 initLevel();296 loop();297 }298 299 // --- BUCLE PRINCIPAL ---300 function loop() {301 if (!gameRunning) return;302 303 ctx.clearRect(0, 0, canvas.width, canvas.height);304 305 // Actualizar lógica306 player.update();307 updatePlatforms();308 309 // Actualizar cámara (seguimiento suave)310 // Mantiene al jugador en el tercio izquierdo de la pantalla311 let targetCamX = player.x - 200;312 // Limitar cámara para que no vaya hacia atrás del inicio313 if (targetCamX < 0) targetCamX = 0;314 cameraX += (targetCamX - cameraX) * 0.1;315 316 // Dibujar mundo relativo a la cámara317 ctx.save();318 ctx.translate(-cameraX, 0);319 320 drawPlatforms();321 player.draw();322 323 // Condición de victoria simple (llegar al final)324 if (player.x > 1400) {325 gameRunning = false;326 statusText.innerText = "¡HAS GANADO!";327 gameOverScreen.style.display = 'flex';328 }329 330 ctx.restore();331 332 requestAnimationFrame(loop);333 }334 335 // --- CONTROLES ---336 window.addEventListener('keydown', e => {337 if(e.code === 'ArrowRight') keys.ArrowRight = true;338 if(e.code === 'ArrowLeft') keys.ArrowLeft = true;339 if(e.code === 'ArrowUp') keys.ArrowUp = true;340 });341 342 window.addEventListener('keyup', e => {343 if(e.code === 'ArrowRight') keys.ArrowRight = false;344 if(e.code === 'ArrowLeft') keys.ArrowLeft = false;345 if(e.code === 'ArrowUp') keys.ArrowUp = false;346 });347 348 // Iniciar349 initLevel();350 loop();351 352 </script>353 </body>354 </html>Enlace
El enlace para compartir es:

