Aprende a crear juegos en HTML5 Canvas

domingo, 1 de marzo de 2015

Rectángulos y el ratón

Anteriormente aprendimos como realizar la interacción entre un círculo y el puntero; ahora aprenderemos como realizar esa interacción misma interacción entre un rectángulo y el puntero.

Antes de empezar, conoceremos la función "contains", que verifica si un rectángulo contiene otro rectángulo:
    Rectangle.prototype.contains = function (rect) {
        if (rect !== undefined) {
            return (this.x < rect.x &&
                this.x + this.width > rect.x + rect.width &&
                this.y < rect.y &&
                this.y + this.height > rect.y + rect.height);
        }
    };
Como podrás darte cuenta, esta función es bastante similar a la función "intersects", pero compara los mismos lados en lugar de los opuestos, y solo retorna verdadero en caso que el rectángulo recibido esté completamente dentro del rectángulo que lo evalúa, por lo que resulta poco usable en la mayoría de los casos de juegos. Es por ello que no habíamos visto esta función antes.

Sin embargo, con los ajustes necesarios, de forma similar a como hicimos anteriormente a la función "distance" en los círculos, si creamos ancho y alto predefinido al objeto que recibimos en caso de no tenerlos, podemos evaluar también si el rectángulo contiene un punto de coordenadas:
    Rectangle.prototype.contains = function (rect) {
        if (rect !== undefined) {
            var rectWidth = rect.width || 0,
                rectHeight = rect.height || 0;
            return (this.x < rect.x &&
                this.x + this.width > rect.x + rectWidth &&
                this.y < rect.y &&
                this.y + this.height > rect.y + rectHeight);
        }
    };
Para probar que el código funciona correctamente, utilizaremos el mismo código que creamos de arrastrar y soltar círculos, reemplazando en esta ocasión los círculos por rectángulos:
        for (i = 0; i < 5; i += 1) {
            draggables.push(new Rectangle(random(canvas.width), random(canvas.height), 20, 20));
        }
Y evaluamos la interacción entre el arrastrable y el puntero al presionar el ratón:
                if (draggables[i].contains(pointer)) {
Al probar nuestro código de nuevo, podremos usar con éxito el arrastrar y soltar rectángulos. Notarás que al arrastrar un objeto, este no se ubica al centro respecto al cursor, si no a la esquina superior izquierda. Esto se puede corregir fácilmente sumando la mitad del alto y el ancho del rectángulo al asignar su nueva posición, pero en la siguiente entrega, haremos cambios que simplificarán mucho tareas como esta.

La interacción entre rectángulos y el ratón es importante en los juegos, además de evaluar casos de interacción dentro de un juego, para crear también botones interactivos en las pantallas del mismo.

Código final:

[Canvas not supported by your browser]
/*jslint bitwise: true, es5: true */
(function (window, undefined) {
    'use strict';
    var canvas = null,
        ctx = null,
        lastPress = null,
        lastRelease = null,
        mouse = {x: 0, y: 0},
        pointer = {x: 0, y: 0},
        dragging = null,
        draggables = [],
        i = 0,
        l = 0;
    
    function Rectangle(x, y, width, height) {
        this.x = (x === undefined) ? 0 : x;
        this.y = (y === undefined) ? 0 : y;
        this.width = (width === undefined) ? 0 : width;
        this.height = (height === undefined) ? this.width : height;
    }
    
    Rectangle.prototype.contains = function (rect) {
        if (rect !== undefined) {
            var rectWidth = rect.width || 0,
                rectHeight = rect.height || 0;
            return (this.x < rect.x &&
                this.x + this.width > rect.x + rectWidth &&
                this.y < rect.y &&
                this.y + this.height > rect.y + rectHeight);
        }
    };
    
    Rectangle.prototype.intersects = function (rect) {
        if (rect !== undefined) {
            return (this.x < rect.x + rect.width &&
                this.x + this.width > rect.x &&
                this.y < rect.y + rect.height &&
                this.y + this.height > rect.y);
        }
    };
    
    Rectangle.prototype.fill = function (ctx) {
        if (ctx !== undefined) {
            ctx.fillRect(this.x, this.y, this.width, this.height);
        }
    };
    
    function enableInputs() {
        document.addEventListener('mousemove', function (evt) {
            mouse.x = evt.pageX - canvas.offsetLeft;
            mouse.y = evt.pageY - canvas.offsetTop;
        }, false);
        
        document.addEventListener('mouseup', function (evt) {
            lastRelease = evt.which;
        }, false);
        
        canvas.addEventListener('mousedown', function (evt) {
            evt.preventDefault();
            lastPress = evt.which;
        }, false);
        
        canvas.addEventListener('touchmove', function (evt) {
            evt.preventDefault();
            var t = evt.targetTouches;
            mouse.x = t[0].pageX - canvas.offsetLeft;
            mouse.y = t[0].pageY - canvas.offsetTop;
        }, false);
        
        canvas.addEventListener('touchstart', function (evt) {
            evt.preventDefault();
            lastPress = 1;
            var t = evt.targetTouches;
            mouse.x = t[0].pageX - canvas.offsetLeft;
            mouse.y = t[0].pageY - canvas.offsetTop;
        }, false);
        
        canvas.addEventListener('touchend', function (evt) {
            lastRelease = 1;
        }, false);
        
        canvas.addEventListener('touchcancel', function (evt) {
            lastRelease = 1;
        }, false);
    }
    
    function random(max) {
        return ~~(Math.random() * max);
    }

    function paint(ctx) {
        // Clean canvas
        ctx.fillStyle = '#000';
        ctx.fillRect(0, 0, canvas.width, canvas.height);
        
        // Draw rectangles
        ctx.fillStyle = '#00f';
        for (i = 0, l = draggables.length; i < l; i += 1) {
            draggables[i].fill(ctx);
        }
        
        // Debug pointer position
        ctx.fillStyle = '#0f0';
        ctx.fillRect(pointer.x - 1, pointer.y - 1, 2, 2);
        
        // Debug dragging rectangle
        ctx.fillStyle = '#fff';
        ctx.fillText('Dragging: ' + dragging, 0, 10);
    }
        
    function act() {
        // Set pointer to mouse
        pointer.x = mouse.x;
        pointer.y = mouse.y;
        
        // Limit pointer into canvas
        if (pointer.x < 0) {
            pointer.x = 0;
        }
        if (pointer.x > canvas.width) {
            pointer.x = canvas.width;
        }
        if (pointer.y < 0) {
            pointer.y = 0;
        }
        if (pointer.y > canvas.height) {
            pointer.y = canvas.height;
        }
        
        if (lastPress === 1) {
            // Check for current dragging rectangle
            for (i = 0, l = draggables.length; i < l; i += 1) {
                if (draggables[i].contains(pointer)) {
                    dragging = i;
                    break;
                }
            }
        } else if (lastRelease === 1) {
            // Release current dragging rectangle
            dragging = null;
        }
        
        // Move current dragging rectangle
        if (dragging !== null) {
            draggables[dragging].x = pointer.x;
            draggables[dragging].y = pointer.y;
        }
    }

    function run() {
        window.requestAnimationFrame(run);
        act();
        paint(ctx);
        
        lastPress = null;
        lastRelease = null;
    }
    
    function init() {
        // Get canvas and context
        canvas = document.getElementById('canvas');
        ctx = canvas.getContext('2d');
        canvas.width = 200;
        canvas.height = 300;
        
        // Create draggables
        for (i = 0; i < 5; i += 1) {
            draggables.push(new Rectangle(random(canvas.width), random(canvas.height), 20, 20));
        }
        
        // Start game
        enableInputs();
        run();
    }
    
    window.addEventListener('load', init, false);
}(window));

No hay comentarios.:

Publicar un comentario