En el post anterior vimos cómo funcionaba el evento Draw del DrawingArea y cómo establecer algunas variables que afectaban a la forma de dibujar. Los métodos y propiedades de la clase Draw () y cómo disparar el evento usando un Timer. Pues bien, si nuestra intención es no sólo dibujar algo en un DA, sino además dotarlo de movimiento, parece lógico pensar que necesitamos disparar el evento Draw varias veces por segundo para conseguir «animar» los objetos. Con este artículo daremos por terminada la serie dedicada al estudio del evento Draw() y su funcionamiento, aunque continuaremos viendo nuevas posibilidades una vez comprendido el concepto.
El movimiento se demuestra… programando!
Si recordamos un poco de lo que estudiamos en «Ciencias Naturales» (si, si, ya tengo una edad…) el ojo humano tiene una característica que se llama persistencia que mantiene durante unos instantes las imágenes en la retina, permitiendo que si la sucesión de estas imágenes es lo suficientemente rápida, se fundan entre sí suavemente consiguiendo el efecto de animación. Es el principio fundamental de cine, que por razones históricas se determinó en 24 fotogramas por segundo, aunque también se puede apreciar sensación de movimiento a velocidades más lentas y por supuesto, también más rápidas.
Todo este rollo es para comprender mejor que nuestro DA va a necesitar refrescarse a un número alto de fotogramas por segundo si queremos que nuestros objetos cobren vida propia. Por lo tanto, vamos a emplear un refresco de 60fps (fps es el acrónimo de Frames Per Second, es decir, fotogramas por segundo) ya que es una velocidad estándar para los juegos y además coincide con el refresco vertical de nuestro monitor, en la mayoría de los casos. Para ello debemos usar un Timer con la propiedad Delay en un número de milisegundos que podemos calcular como el resultado de:
Que podemos redondear tranquilamente a 16 milisegundos.
Ahora bien, para que haya movimiento necesitamos tres cosas. Un punto inicial, que será la posición de nuestro objeto, un punto final que constituye el vector velocidad que será el punto donde se desplazará nuestro objeto. Y la tercera variable es el tiempo, que completa la ecuación por todos conocida. Es decir, que en una unidad de tiempo, nuestro objeto pasará de su posición inicial al punto que determine el vector velocidad (que lleva implícito de algún modo el sentido y la dirección del movimiento. Para entenderlo mejor, hay que pensar en nuestro drawing area como un juego de coordenadas con su origen en el punto (0, 0) que está situado en la esquina superior izquierda y que aumenta en el eje X hacia la derecha y en el eje Y hacia abajo. el punto de destino en el ejemplo es (5, 5) que determina la velocidad V. Por tanto, nuestro vector velocidad será (5, 5) porque es el punto que alcanzaremos en una unidad de tiempo:
De manera que para desplazar un punto por el espacio, sólo tenemos que sumarle la velocidad en cada ciclo de refresco del DA. Naturalmente para poder aplicarlo a nuestro código, lo tendremos que descomponer en sus dos componentes x, y. Lo vemos mejor con un poco de código comentado:
' Gambas class file Private posicion As New Integer[] Private velocidad As New Integer[] Public Sub _new() ' inicializamos las variables globales con valores ' enteros dentro de un array que representa los puntos x, y ' es decir, un punto en las coordenadas del DA posicion = [20, 20] velocidad = [2, 2] End Public Sub DrawingArea1_Draw() Draw.FillColor = Color.Red Draw.FillStyle = Fill.Solid Draw.Circle(posicion[0], posicion[1], 20) ' sumamos el vector velocidad a la posición en cada momento ' primero el componente X (que es el primer elemento del array) posicion[0] += velocidad[0] ' y luego el componente Y que es el segundo elemento del array posicion[1] += velocidad[1] End Public Sub Timer1_Timer() ' el timer tiene su propiedad Delay = 16 ' lo que equivale a 60 fps DrawingArea1.Refresh End |
El resultado de correr este código es un círculo rojo que se mueve desde un punto inicial (20, 20) hacia abajo y a la derecha a una velocidad de 120 píxeles por segundo en el eje X y lo mismo para el eje Y. Como el tiempo (el refresco) es constante e infinito, el círculo seguirá moviéndose aún fuera de la superficie del drawing area hasta el infinito ( en realidad, sólo hasta que paremos la ejecución de nuestro programa ;-))
¿Por qué he dicho tan tajantemente que nuestro círculo rojo se mueve a 120 píxeles/sec? Bueno, esto es cierto al menos para cada eje por separado, ya que nuestra velocidad es [2, 2], es decir 2 píxeles en cada eje que multiplicado por el total de ciclos de refresco en un segundo que son 60, da como resultado 120.
Divertido, ¿verdad?. Os dejo a vuestro albedrío jugar con los valores de velocidad, usando incluso números negativos que determinarán la dirección del movimiento y su magnitud. En próximas entregas, veremos cómo podemos detectar colisiones simples que junto con lo que hemos aprendido hasta ahora, nos permitirán armar un juego de Pong.
Hasta entonces, muchas gambas!
Te ha faltado Timer1.Start en Form_Open (por ejemplo) para que inicie el Timer. 😀
Es muy interesante el tema del DrawingArea.
Espero que te animes a mas tutoriales con este componente. 🙂
Gracias por el ejemplo.
Shell, efectivamente el timer lo puedes crear y manejar desde el código o a través del IDE. Es demasiado obvio como para explicarlo, ya dije que el contenido del blog no iba destinado a principiantes, a pesar de que lo que aquí se explica tampoco es que sea «rocket science»…
Shell:
Podría activar el Timer en _new(), o como supongo jguardon lo ha hecho poner la propiedad Enabled de Timer1 a True en el IDE.
Jguardon:
Yo quería hacer el pong en gambas, pero pss ya me lo ganaste; así que yo haré el blackjack, jejeje.
Saludos!
Ups, creía que fue un olvido.
Creo que os habéis explicado muy bien y que cualquiera puede entenderlo,
no es necesario un master. Ya se ira complicando. B-)
«Que podemos redondear tranquilamente a 16 milisegundos.»
Si el juego tiene mucho que redibujar (muchos objetos) y recalcular, este valor lo he subido a 40 milisegundos (24 fps) y sigue dando la impresión visual «de movimiento».