Aprende a crear juegos en HTML5 Canvas

domingo, 14 de julio de 2013

Arrastra y suelta

Ya van varias veces que me preguntan como hacer un Drag&Drop. Como veo es una petición muy popular, y usada en muchos juegos, he decidido agregar esta entrada para explicar como realizar esta técnica, que, aunque parece complicada, en realidad es bastante sencilla. Y como es costumbre en este blog, haremos esta tarea aun más sencilla de ser posible. Comenzaremos declarando las nuevas variables que necesitaremos:
        lastPress = null,
        lastRelease = null,
        mouse = {x: 0, y: 0},
        pointer = {x: 0, y: 0},
        dragging = null,
        draggables = [],
La variable lastPress ya es conocida para nosotros. Agregamos un lastRelease que nos indicará el momento en que soltamos el ratón, un arreglo draggables que contendrá todos nuestros elementos arrastrables, y una variable dragging que nos indicará el elemento que estamos arrastrando en este momento, cuyo valor será nulo en caso de no arrastrar ninguno.

Quizá lo que resulta más llamativo, es la forma en que se declara la variable "mouse", con varios valores en el interior de unas llaves. Esta es la forma de declarar un objeto de forma directa, que sería similar a crear una función de tipo pseudo-clase y asignársela posteriormente. Sin embargo, en vez de hacer esto lo he hecho de forma directa en esta ocasión para crear un punto de coordenadas, ya que solo estoy almacenando un objeto con dos variables y sin funciones, y resulta más rápido hacerlo de esta forma que crear una función solo para ello. También he creado una segunda variable similar llamada "pointer", que almacenará una versión personalizable de la posición del ratón, de forma similar como se hacía con la función "player" anteriormente, para mantener siempre visible dentro del juego, aun cuando el ratón no lo esté.

Para que pueda haber interacción entre círculos y puntos, es necesario modificar ligeramente la función "distance" en nuestra función "circle" de esta forma:
    Circle.prototype.distance = function (circle) {
        if (circle !== undefined) {
            var dx = this.x - circle.x,
                dy = this.y - circle.y,
                circleRadius = circle.radius || 0;
            return (Math.sqrt(dx * dx + dy * dy) - (this.radius + circleRadius));
        }
    };
Notarás que agregamos una variable llamada "circleRadius" a la que se le asigna el radio del círculo que mandamos, o cero si no la encuentra. Como los puntos de coordenadas solo contienen posición X y Y, cuando se mande uno de estos para comparar su distancia de un círculo, se le asignará un radio temporal de cero, y de esta forma no efectuará un error al intentar sumar su radio al del círculo actual, pudiendo obtener la distancia entre el círculo y el punto de forma exitosa.

Reutilizando parte del código Distancia entre círculos, modificaremos la función enableInputs para agregar las nuevas acciones, así como actualizar mousemove para el nuevo objeto que contiene la posición del ratón. De igual forma que al presionar un botón del mouse se almacena el valor en lastPress, se hará lo mismo al soltar el botón con lastRelease:
    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);
    }
Nota que a diferencia de mousedown, el escucha del mouseup es asignado a document. Esto es por que, en caso que el botón del ratón sea liberado fuera del canvas, aun deseamos dejar de ejecutar cualquier acción que pudiera haber empezado dentro del mismo.

Ahora crearemos cinco círculos arrastrables dentro de la función init, justo después de iniciar nuestro canvas, asignándole posiciones al azar dentro del área de nuestro juego:
        // Create draggables
        for (i = 0; i < 5; i += 1) {
            draggables.push(new Circle(random(canvas.width), random(canvas.height), 10));
        }
En la función act, después de mover la posición del ratón, veremos si el botón izquierdo del ratón acaba de ser presionado. De ser así, analizaremos cada uno de los elementos dentro del arreglo draggable, para ver si el cursor está posicionado sobre uno de los objetos arrastrables y en caso de encontrar uno, este será asignado a la variable dragging:
        if (lastPress === 1) {
            // Check for current dragging circle
            for (i = 0, l = draggables.length; i < l; i += 1) {
                if (draggables[i].distance(pointer) < 0) {
                    dragging = i;
                    break;
                }
            }
        }
Notarás que al asignar la posición del objeto a dragging, hacemos un break. Esto hará que se dejen de leer los demás elementos del for, con el motivo de que, en caso de que haya dos elementos arrastrables encimados uno sobre el otro, solo tome el primero de la lista, para así evitar que queden pegados varios elementos en el ratón.

A continuación, se analiza si, en caso contrario de haber presionado el botón izquierdo del ratón, este ha sido liberado, y en caso de ser así, dragging vuelve a tener un valor nulo:
        else if (lastRelease === 1) {
            // Release current dragging circle
            dragging = null;
        }
Por último, si dragging no tiene un valor nulo, entonces el elemento que está siendo arrastrado obtendrá la posición del ratón:
        // Move current dragging circle
        if (dragging !== null) {
            draggables[dragging].x = pointer.x;
            draggables[dragging].y = pointer.y;
        }
Para finalizar, dibujaremos los elementos en pantalla. He agregado una línea de texto que indica el elemento que está siendo arrastrado:
        // Draw circles
        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 circle
        ctx.fillStyle = '#fff';
        ctx.fillText('Dragging: ' + dragging, 0, 10);
De esta forma, hemos creado un sencillo Drag&Drop.

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 Circle(x, y, radius) {
        this.x = (x === undefined) ? 0 : x;
        this.y = (y === undefined) ? 0 : y;
        this.radius = (radius === undefined) ? 0 : radius;
    }
    
    Circle.prototype.distance = function (circle) {
        if (circle !== undefined) {
            var dx = this.x - circle.x,
                dy = this.y - circle.y,
                circleRadius = circle.radius || 0;
            return (Math.sqrt(dx * dx + dy * dy) - (this.radius + circleRadius));
        }
    };
    
    Circle.prototype.fill = function (ctx) {
        if (ctx !== undefined) {
            ctx.beginPath();
            ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2, true);
            ctx.fill();
        }
    };
    
    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);
    }
    
    function random(max) {
        return ~~(Math.random() * max);
    }
    
    function paint(ctx) {
        // Clean canvas
        ctx.fillStyle = '#000';
        ctx.fillRect(0, 0, canvas.width, canvas.height);
        
        // Draw circles
        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 circle
        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 circle
            for (i = 0, l = draggables.length; i < l; i += 1) {
                if (draggables[i].distance(pointer) < 0) {
                    dragging = i;
                    break;
                }
            }
        } else if (lastRelease === 1) {
            // Release current dragging circle
            dragging = null;
        }
        
        // Move current dragging circle
        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 Circle(random(canvas.width), random(canvas.height), 10));
        }
        
        // Start game
        enableInputs();
        run();
    }
    
    window.addEventListener('load', init, false);
}(window));

28 comentarios:

  1. hola muy bueno tu aporte a este tema tengo una interrogante como podria hacer que en lugar de circulos sean imagenes gracias

    ResponderEliminar
    Respuestas
    1. En temas anteriores se veía diferentes técnicas para lograr esto mismo, y dos capitulos adelante, se hace esto mismo con el ejemplo presente. Básicamente, esta línea es la que se encarga de ello:

      ctx . drawImage (img , sx, sy,sw, sy,
      this. x - this.radius, this. y - this.
      rad ius , this.radius* 2 , this. radius*2
      ) ;

      Eliminar
  2. hola muy bueno tu aporte, tengo una duda, como puedo hacer para que al soltar el circulo este se siga moviendo? digamos, agarrarlo y tirarlo.. y que se mueve dependiendo de la fuerza con lo que lo haya tirado

    ResponderEliminar
    Respuestas
    1. Almacena la velocidad del objeto al soltarlo como la distancia entre sus últimas dos posiciones, así como su ángulo. Eso te dará la fuerza y dirección para el objeto al soltarle.

      Eliminar
  3. Hola para hacer un juego que sea tipo tetris pero que sean solo cuadros de 4 colores diferentes, y que estos a su vez al crear una linea de 3 colores iguales desaparezcan y que los cuadros al caer sean movidos por mouse???

    ResponderEliminar
    Respuestas
    1. Suena muy complejo lo que deseas hacer. Creo que deberías enfocarte en una tarea a la vez. Hay mucha información en Internet que podría ayudarte a conseguir lo que deseas. Quizá pueda facilitarte algo de esa información.

      Eliminar
  4. Hola como puedo hacer que al clickear una figura esta se pinte y muestre un mensaje por pantalla.

    Saludos.

    ResponderEliminar
    Respuestas
    1. ¿Te refieres a cambiar el relleno del color? Tan solo declara el color como una variable, y al hacer clic sobre la figura, cambia el valor de esta variable.

      Sobre el mensaje, no se que tienes exactamente en mente, pero quizá quieras ver el tema de mejoras ( http://juegos.canvas.ninja/2012/05/mejoras.html ). Es posible que el arreglo de mensajes te de una idea para que consigas lo que estás buscando.

      Eliminar
    2. la idea es la siguiente:
      tengo que dibujar una boca con todos sus dientes y cada diente tiene 5 partes, esto lo dibuje asi::

      context.beginPath();
      context.arc(xPos, Ypos, 7, Math.PI * 2, 0, true);
      context.stroke();

      context.beginPath();
      context.arc(xPos, Ypos, 15, Math.PI * 1, Math.PI * 0.5, true);
      context.arc(xPos, Ypos, 7, Math.PI * 0.5, Math.PI * 1, false);
      context.stroke();

      context.beginPath();
      context.arc(xPos, Ypos, 15, Math.PI * 1.5, Math.PI, true);
      context.arc(xPos, Ypos, 7, Math.PI * 1, Math.PI * 1.5, false);
      context.stroke();

      context.beginPath();
      context.arc(xPos, Ypos, 15, 0, Math.PI * 1.5, true);
      context.arc(xPos, Ypos, 7, Math.PI * 1.5, 0, false);
      context.stroke();

      context.beginPath();
      context.arc(xPos, Ypos, 15, Math.PI * 0.5, Math.PI * 2, true);
      context.arc(xPos, Ypos, 7, Math.PI * 2, Math.PI * 0.5, false);
      context.stroke();

      la idea es que al seleccionar una parte de cualquier diente , se pinte solo esa parte y en cuanto al mensaje la idea es que muestre por ejemplo: Pieza Dental 1 , parte 3.

      Eliminar
    3. Supongo que ya guardas el variables los valores de pieza dental y parte. Justo antes de context.stroke(), compara si estos valores son los que corresponden al que estás dibujando en ese momento, y si la condicion es cierta, haces un context.fill()

      if(dentalPiece == 1 && part == 3){
      context.fill();
      }
      context.stroke();

      Para el mensaje, solo necesitas un fillText:

      context.fillText('Pieza Dental ' + dentalPiece + ', parte ' + part, 80, 100);

      Eso deberá resolver ambos problemas que tienes.

      Eliminar
    4. lo que me complica y no entiendo bien como hacerlo es de guardar las variables (valores de pieza dental y parte) y después acceder a los valores de la pieza y parque que le hicieron click.

      Estoy recién metiéndome en esto y tus tutoriales me han sido de mucha ayuda se agradece demasiado el aporte.

      Eliminar
    5. ¿Cómo tienes almacenadas las partes de las piezas dentales? ¿Están en un arreglo como en el presente ejemplo? Eso es lo principal a saber para ver como se ha de hacer la lectura de colisiones y asignar sus valores correspondientes.

      Eliminar
    6. Lo estoy haciendo como en este ejemplo con un arreglo, el problema es que son 32 figuras ( dientes) que tienen 5 partes cada una.

      Eliminar
    7. Eso hace un total de 160 partes. ¿Las estás poniendo en un solo arreglo? ¿O estás usando un arreglo anidado?

      Eliminar
    8. esa es mi gran duda, cual seria la forma mas eficiente para hacerlo.
      acepto propuestas :)

      Eliminar
    9. Posiblemente sea más eficiente poner tus 160 piezas en un arreglo. Para revisar la colisión, sería algo así:

      if(lastPress == 1){
      for(var i = 0, l = dentalPieces; i < l; i++){
      if(player.distance(dentalPieces[i]) < 0){
      deltalPiece = ~~(i / 5);
      part = i % 5;
      break;
      }
      }
      }

      Eso devolverá los valores de la primer pieza que encuentre colisión al presionar el ratón.

      Eliminar
    10. ok, muchas gracias probaré eso , te estaré comentando como me fue.

      Eliminar
  5. me base en el ejemplo y logre manejar mis figuras pero aun no puedo identificar en cual parte del diente hicieron click, este es el código.
    (function(){
    'use strict';
    window.addEventListener('load', init, false);
    var canvas = null, context = null;
    var lastPress = null;
    var lastRelease = null;
    var mousex = 0, mousey = 0;
    var player = new Circle(0, 0, 1);
    var PiezasDentales = [];
    var texto = "Text";
    var canvas_x, canvas_y;
    var deltalPiece, part;

    function init() {
    canvas = document.getElementById('myCanvas');
    context = canvas.getContext('2d');
    canvas.width = 578;
    canvas.height = 550;

    //Lado Superior Derecho
    for (var i = 1; i < 6; i++)
    {
    PiezasDentales.push(new Diente(220, 55, 1, i));
    PiezasDentales.push(new Diente(250, 60, 2, i));
    PiezasDentales.push(new Diente(280, 70, 3, i));
    PiezasDentales.push(new Diente(295, 100, 4, i));
    PiezasDentales.push(new Diente(315, 130, 5, i));
    PiezasDentales.push(new Diente(330, 160, 6, i));
    PiezasDentales.push(new Diente(345, 190, 7, i));
    PiezasDentales.push(new Diente(350, 220, 8, i));

    //lado superior izquierdo
    PiezasDentales.push(new Diente(190, 55, 9, i));
    PiezasDentales.push(new Diente(160, 60, 10, i));
    PiezasDentales.push(new Diente(130, 70, 11, i));
    PiezasDentales.push(new Diente(110, 100, 12, i));
    PiezasDentales.push(new Diente(90, 130, 13, i));
    PiezasDentales.push(new Diente(75, 160, 14, i));
    PiezasDentales.push(new Diente(65, 190, 15, i));
    PiezasDentales.push(new Diente(55, 220, 16, i));

    //lado inferior izquierdo
    PiezasDentales.push(new Diente(55, 320, 17, i));
    PiezasDentales.push(new Diente(65, 350, 18, i));
    PiezasDentales.push(new Diente(75, 380, 19, i));
    PiezasDentales.push(new Diente(90, 410, 20, i));
    PiezasDentales.push(new Diente(110, 440, 21, i));
    PiezasDentales.push(new Diente(130, 470, 22, i));
    PiezasDentales.push(new Diente(160, 480, 23, i));
    PiezasDentales.push(new Diente(190, 485, 24, i));

    //lado inferior derecho
    PiezasDentales.push(new Diente(220, 485, 25, i));
    PiezasDentales.push(new Diente(250, 480, 26, i));
    PiezasDentales.push(new Diente(280, 470, 27, i));
    PiezasDentales.push(new Diente(295, 440, 28, i));
    PiezasDentales.push(new Diente(315, 410, 29, i));
    PiezasDentales.push(new Diente(330, 380, 30, i));
    PiezasDentales.push(new Diente(345, 350, 31, i));
    PiezasDentales.push(new Diente(350, 320, 32, i));
    }


    enableInputs();
    run();
    }

    function run() {
    requestAnimationFrame(run);
    act();
    Dibujar(context);
    }
    function act() {
    player.x = mousex;
    player.y = mousey;

    if (player.x < 0)
    player.x = 0;
    if (player.x > canvas.width)
    player.x = canvas.width;
    if (player.y < 0)
    player.y = 0;
    if (player.y > canvas.height)
    player.y = canvas.height;

    if (lastPress == 1) {
    for (var i = 0, l = PiezasDentales.length; i < l; i++) {
    if (player.distance(PiezasDentales[i]) < 0) {
    deltalPiece = ~~(i / 5);
    part = i % 5;
    alert("Pieza = " + PiezasDentales[i].pieza + " , Parte = " + PiezasDentales[i].parte + "\n Part:" + part + " deltalPiece:" + deltalPiece);
    break;
    }
    }
    } else if (lastRelease == 1)
    texto = null;

    }

    function Circle(x, y, radius) {
    this.x = (x == null) ? 0 : x;
    this.y = (y == null) ? 0 : y;
    this.radius = (radius == null) ? 0 : radius;
    }
    Circle.prototype.fill = function (ctx) {
    ctx.beginPath();
    ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2, true);
    ctx.stroke();
    ctx.fill();
    }

    ResponderEliminar
  6. Circle.prototype.distance = function (Diente) {
    if (Diente != null) {
    var dx = this.x - Diente.xPos;
    var dy = this.y - Diente.Ypos;
    return (Math.sqrt(dx * dx + dy * dy) - (this.radius + Diente.radio));
    }
    }

    function Diente(xPos, Ypos, pieza,parte, radio) {
    this.xPos = (xPos == null) ? 0 : xPos;
    this.Ypos = (Ypos == null) ? 0 : Ypos
    this.parte = (parte == null) ? 0 : parte;
    this.pieza = (pieza == null) ? 0 : pieza;
    if (this.parte > 1)
    {
    this.radio = 15;
    }else if(this.parte == 1)
    {
    this.radio = 7;
    }

    }

    Diente.prototype.fill = function (Diente) {
    if (this.parte == 1)
    {
    context.beginPath();
    context.arc(this.xPos, this.Ypos, this.radio, Math.PI * 2, 0, true);
    context.stroke();
    } else if (this.parte == 2)
    {
    context.beginPath();
    context.arc(this.xPos, this.Ypos, this.radio, Math.PI * 1, Math.PI * 0.5, true);
    context.arc(this.xPos, this.Ypos, 7, Math.PI * 0.5, Math.PI * 1, false);
    context.stroke();
    } else if (this.parte == 3)
    {
    context.beginPath();
    context.arc(this.xPos, this.Ypos, this.radio, Math.PI * 1.5, Math.PI, true);
    context.arc(this.xPos, this.Ypos, 7, Math.PI * 1, Math.PI * 1.5, false);
    context.stroke();
    } else if (this.parte == 4)
    {
    context.beginPath();
    context.arc(this.xPos, this.Ypos, this.radio, 0, Math.PI * 1.5, true);
    context.arc(this.xPos, this.Ypos, 7, Math.PI * 1.5, 0, false);
    context.stroke();
    } else if (this.parte == 5)
    {
    context.beginPath();
    context.arc(this.xPos, this.Ypos, this.radio, Math.PI * 0.5, Math.PI * 2, true);
    context.arc(this.xPos, this.Ypos, 7, Math.PI * 2, Math.PI * 0.5, false);
    context.stroke();
    }
    }

    function enableInputs() {
    canvas.addEventListener("mousedown", doMouseDown, doMouseDown, false);

    function doMouseDown(event) {
    canvas_x = event.pageX;
    canvas_y = event.pageY;
    texto = "X=" + canvas_x + " , " + "Y=" + canvas_y;
    event.preventDefault();
    lastPress = event.which;
    }

    document.addEventListener('mousemove', function (evt) {
    mousex = evt.pageX - canvas.offsetLeft;
    mousey = evt.pageY - canvas.offsetTop;
    }, false);
    document.addEventListener('mouseup', function (evt) {
    lastRelease = evt.which;
    }, false);

    }


    function Dibujar(context) {

    context.fillStyle = 'white';
    context.fillRect(0, 0, canvas.width, canvas.height);

    for (var i = 0, l = PiezasDentales.length; i < l; i++)
    PiezasDentales[i].fill(context);

    player.fill(context);

    context.fillStyle = 'black';
    context.fillText('Dragging: ' + texto, 0, 10);

    lastPress = null;
    lastRelease = null;
    }

    window.requestAnimationFrame = (function () {
    return window.requestAnimationFrame ||
    window.webkitRequestAnimationFrame ||
    window.mozRequestAnimationFrame ||
    function (callback) { window.setTimeout(callback, 17); };
    })();
    })();

    ResponderEliminar
    Respuestas
    1. Viendo ya tu código, puedo ver el detalle de como lo estás resolviendo. Habrá que hacer dos modificaciones:

      En donde revisas sí se está presionando la pieza, deben ser estos resultados:

      dentalPiece = PiezasDentales[i].pieza;
      part = PiezasDentales[i].parte;

      Para visualizar el seleccionado, agrega esto al final del prototipo fill:

      if(this.parte == part && this.pieza == dentalPiece){
      context.fillStyle = 'fc0';
      context.fill();
      }

      Con eso tu código quedará resuelto. Sin embargo, al probarlo, veras que hay un pequeño conflicto con las piezas, ya que estás comparando la colisión con el círculo completo. Es posible que necesites una segunda comparación cuadrada para resolver ese problema...

      Eliminar
    2. desde ya agradezco toda tu ayuda en cuanto a tutoriales y a responder todas las consultas, son de mucha ayuda.

      En cuanto al código lo de pintar la figura seleccionada he logrado resolverlo , pero aun queda un gran problemas que es tal cual tu lo mencionas , conflicto con las figuras que se clickean , no he podido dar con la solución , quisiera poder identificar correctamente cual es la figura a la que se le hace click no que pinte la primera que encuentre en el arreglo.

      Si das con la solución o tienes una idea que ayude a lograrlo te agradecería mucho que me la comentes.

      Saludos

      Eliminar
    3. Lo que yo te recomiendo es que uses cuadrados para las cuatro secciones de detrás. Dibuja el cuadrado normal y prueba las colisiones. Una vez que funcione, emular que es un cuarto de círculo será relativamente sencillo, tomando la esquina correspondiente como centro y el ancho como radio. ¡Suerte!

      Eliminar
    4. Estimado, te comento que logre el resultado que quería :) , así que agradezco tu buena disponibilidad de responder y ayudar .

      Quisiera preguntarte algo, la idea de esas PiezasDentales es que se puedan cargar con informacion proveniente de la Base de Datos , por decir un ejemplo una lista viene con la información de que la parte 2 de la pieza 6 este pintada.

      alguna idea para manejar ese mecanismo ?
      saludos.

      Eliminar
    5. ¡Que bueno saber que ha quedado finalmente lo que necesitabas!

      Para usar la información de la base de datos, depende de como obtengas la información. Si tu información es impresa directamente al HTML, puedes imprimir esta información directo en variables dentro de una etiqueta script y jalar la información de ahí.

      Si por el contrario, jalaras la información de forma apartr, es posible que el servidor imprima la información en un archivo de formato amigable como un JSON, y debas obtenerlo de forma asíncrona a través de un XHR.

      Eliminar
  7. Hola Karl, como podemos hacer zoom y que enfoque sobre el player? Se puede utilizar la rueda del ratón?

    Un saludo

    ResponderEliminar
    Respuestas
    1. Para enfocar sobre el jugador, necesitas una cámara, esto se aprende temas más adelante.

      Para hacer zoom, se hace escalando los objetos dentro del lienzo; la forma más sencilla seguramente es a través de un doble bufer que se vio temas atrás.

      Finalmente, la rueda del ratón por largo tiempo no tuvo un estándar. Estuve revisando y parece ser que finalmente los navegadores se han puesto de acuerdo con el evento 'wheel', leyendo deltaY para conocer el desplazamiento vertical. Sin embargo, dado que es un estándar reciente, es posible que algunos navegadores no-actualizados no lo soporten. Puedes conocer más de el evento en el siguiente enlace: https://developer.mozilla.org/en-US/docs/Web/Events/wheel

      Eliminar
    2. podrias hacer un ejemplo con http://juegos.canvas.ninja/2013/04/mundos-grandes-y-camara.html

      de zoom con la rueda. Es que me hace zoom pero en la esquina 0,0


      document.addEventListener('mousewheel', function (evt) {

      zoom();
      rueda = evt.wheelDelta;
      }, false);


      function zoom(){

      ctx.scale(1.002,1.002);

      }

      Eliminar
    3. Antes de un ctx.scale, debes hacer un ctx.translate al centro donde deseas hacer la transformación.

      Revisa los pasos aprendidos sobre traslación, rotación y escala en http://juegos.canvas.ninja/2013/12/rotacion-de-imagenes.html

      Eliminar