Dino Run - Control por Gestos
Un juego estilo Chrome Dino controlado con gestos usando Teachable Machine y ml5.js
Este es un juego de dinosaurio corredor donde debes saltar sobre los obstáculos. En lugar de usar el teclado, el juego se controla mediante gestos capturados por tu cámara web usando un modelo de Teachable Machine.
Instrucciones:
- Haz clic en el botón “Iniciar Juego”
- Permite el acceso a tu cámara web cuando se te solicite
- Para saltar, muestra una mano abierta con la palma hacia la cámara y los dedos apuntando hacia arriba (gesto “Jump”)
- Si el modelo no detecta el gesto correcto, el dinosaurio no saltará
- Evita los obstáculos (árboles) para acumular puntos
- El juego termina cuando chocas con un obstáculo
Note
Este juego utiliza un modelo pre-entrenado de Teachable Machine. Asegúrate de tener buena iluminación para mejor detección de gestos.
Código Completo
// Game objects
let dino;
let obstacles = [];
let groundY = 300;
// ML5 variables
let classifier;
let video;
let movementLabel = '';
// Game variables
let count = 0;
let gameOver = false;
let gameStarted = false;
let countdown = 3;
let countdownTimer = 0;
let lastObstacleFrame = 0;
let minObstacleDistance = 80;
// Images
let imgDino;
let imgTree;
let imgExplosion;
function preload() {
imgDino = loadImage("../data/images/dino_run/dino.png");
imgTree = loadImage("../data/images/dino_run/tree.png");
imgExplosion = loadImage("../data/images/dino_run/explosion.png");
classifier = ml5.imageClassifier('https://teachablemachine.withgoogle.com/models/Ij_wIPp5Q/model.json');
}
function setup() {
createCanvas(800, 400);
video = createCapture(VIDEO);
video.size(160, 120);
video.hide();
dino = new Dino(groundY);
obstacles.push(new Obstacle(groundY));
countdownTimer = millis();
classifyVideo();
}
function classifyVideo() {
if (!gameOver) {
classifier.classify(video, gotResults);
}
}
function gotResults(error, results) {
if (error) {
console.error(error);
return;
}
if (results && results.length > 0) {
movementLabel = results[0].label;
}
setModelGesture();
classifyVideo();
}
function setModelGesture() {
if (movementLabel === 'Jump') {
dino.jump();
}
}
function draw() {
background(220);
// Countdown logic
if (!gameStarted && !gameOver) {
let elapsed = millis() - countdownTimer;
countdown = 3 - floor(elapsed / 1000);
if (countdown <= 0) {
gameStarted = true;
}
}
// Update and draw the dinosaur
dino.update();
dino.show();
// Display score
textSize(32);
textAlign(LEFT, TOP);
fill(0);
text(`Points: ${count}`, 10, 10);
// Display hit points (hearts)
textSize(20);
let hearts = '';
for (let i = 0; i < dino.getHitPoints(); i++) {
hearts += '❤️ ';
}
text(`HP: ${hearts}`, 10, 50);
// Display gesture
textSize(20);
text(`Gesture: ${movementLabel}`, 10, 80);
// Show countdown
if (!gameStarted && !gameOver && countdown > 0) {
fill(255, 165, 0);
textSize(72);
textAlign(CENTER, CENTER);
text(countdown, width/2, height/2);
}
// Spawn obstacles with proper spacing
if (gameStarted && !gameOver) {
let framesSinceLastObstacle = frameCount - lastObstacleFrame;
let randomInterval = random(minObstacleDistance, minObstacleDistance + 40);
if (framesSinceLastObstacle >= randomInterval) {
obstacles.push(new Obstacle(groundY));
lastObstacleFrame = frameCount;
}
}
// Update and draw each obstacle
for (let i = obstacles.length - 1; i >= 0; i--) {
if (gameStarted) {
obstacles[i].update();
}
obstacles[i].show();
// Check collision
if (gameStarted && obstacles[i].hits(dino) && !gameOver) {
let isGameOver = dino.hit();
if (isGameOver) {
gameOver = true;
}
obstacles.splice(i, 1);
continue;
}
// Remove offscreen obstacles
if (obstacles[i].offscreen()) {
if (gameStarted) {
count++;
}
obstacles.splice(i, 1);
}
}
// Draw the ground line
stroke(0);
line(0, groundY, width, groundY);
// Draw webcam feed in top right corner
push();
translate(width - 165, 5);
stroke(0);
strokeWeight(3);
fill(255);
rect(0, 0, 160, 120);
image(video, 0, 0, 160, 120);
pop();
// Show game over message
if (gameOver) {
fill(0);
textSize(48);
textAlign(CENTER, CENTER);
text("GAME OVER", width/2, height/2 - 30);
textSize(24);
text(`Total Points: ${count}`, width/2, height/2 + 20);
text(`Total Jumps: ${dino.getTotalJumps()}`, width/2, height/2 + 50);
}
}
// Dino class
class Dino {
constructor(groundY) {
this.r = 50;
this.x = 50;
this.groundY = groundY;
this.y = groundY - this.r;
this.vy = 0;
this.gravity = 1.5;
this.totalJumps = 0;
this.isDead = false;
this.hitPoints = 3;
this.invulnerable = false;
this.invulnerableTimer = 0;
}
jump() {
if (this.y === this.groundY - this.r) {
this.totalJumps++;
this.vy = -28;
}
}
update() {
this.y += this.vy;
this.vy += this.gravity;
if (this.y > this.groundY - this.r) {
this.y = this.groundY - this.r;
this.vy = 0;
}
if (this.invulnerable && millis() - this.invulnerableTimer > 2000) {
this.invulnerable = false;
}
}
hit() {
if (!this.invulnerable && !this.isDead) {
this.hitPoints--;
if (this.hitPoints <= 0) {
this.dead();
return true;
} else {
this.invulnerable = true;
this.invulnerableTimer = millis();
return false;
}
}
return false;
}
dead() {
this.isDead = true;
}
show() {
if (this.invulnerable && frameCount % 10 < 5) {
push();
tint(255, 100, 100);
if (this.isDead) {
image(imgExplosion, this.x, this.y, this.r, this.r);
} else {
image(imgDino, this.x, this.y, this.r, this.r);
}
pop();
} else {
if (this.isDead) {
image(imgExplosion, this.x, this.y, this.r, this.r);
} else {
image(imgDino, this.x, this.y, this.r, this.r);
}
}
}
getTotalJumps() {
return this.totalJumps;
}
getHitPoints() {
return this.hitPoints;
}
}
// Obstacle class
class Obstacle {
constructor(groundY) {
this.w = 20;
this.h = random(40, 80);
this.x = width;
this.groundY = groundY;
this.y = groundY - this.h;
this.speed = 3;
}
update() {
this.x -= this.speed;
}
offscreen() {
return this.x < -this.w;
}
show() {
image(imgTree, this.x, this.y, this.w, this.h);
}
hits(dino) {
return (
dino.x < this.x + this.w &&
dino.x + dino.r > this.x &&
dino.y < this.y + this.h &&
dino.y + dino.r > this.y
);
}
}