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:

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
        );
    }
}