Tutorial Drag & Drop: mover objetos en nuestro juego
Muy buenas. Hoy os traigo un nuevo tutorial para poder mover objetos en nuestro juego con el ratón. Os cuento como se me ocurrió la idea: para mi hija de casi dos años y medio suelo descargarme juegos y apps infantiles, y muchos de los juegos son para asociar figuras y formas. Arrastras un triángulo en el hueco correspondiente, o formas un puzzle con figuras separadas, etc. Todo este tipo de acciones es muy común si piensas crear un juego para niños. Así que he buscado en el foro principal de YoyoGames ejemplos que pueden funcionar y os enseño un proyecto sencillo que podéis usar para este tipo de proyecto.
Creación de sprites básicos
Creamos un proyecto nuevo, yo lo he llamado Example Drag n Drop. La intención es crear dos objetos diferentes que tendrán algunas diferencias en el comportamiento. Así que creamos un triángulo y un cuadrado. Lo llamamos spr_triangle_solid y spr_square_solid. Aquí están:
Dos detalles a informar: centrar su posición X/Y, y para el triángulo, elegir la máscara como Precise collision checking, para que su máscara sea la correcta (ahora veremos porqué).
Ahora creamos otros dos sprites, que serán hacia dónde arrastraremos las figuras: el objetivo no es solo mover los objetos, sino que vaya a un destino en concreto. Así que tendremos los sprites spr_triangle y spr_square.
Si os fijáis, los sprites son parecidos al anterior, solo que tiene el contorno del triángulo y del cuadrado. Ahora vamos a crear los objetos
Creamos objeto figura
Cuando aprendemos a programar en cualquier lenguaje, principalmente en C o Java, para explicar la herencia y las funciones virtuales, que no entraremos en ese tema, suelen poner el mismo ejemplo: objeto Figura y objetos que heredan de figura (objeto triángulo, objeto cuadrado, objeto círculo). Pues vamos a utilizar lo mismo.
Creamos el objeto obj_parent_solid, que será el que tendrá el código principal. Vamos a crear un evento Step, y añadimos la acción Execute code. Explicamos las dos partes de código que vamos a añadir.
Preparar variables para arrastrar
Antes de nada hay que preparar las variables para que funcione el Drag&Drop. Añadimos este código:
///DRAG OBJECT //Start drag if (mouse_check_button_pressed(mb_left) and position_meeting(mouse_x, mouse_y, id)) { with (obj_parent_solid) { selected = false; } selected = true; drag_x = mouse_x - x; drag_y = mouse_y - y; }
En la primera condición hay dos funciones: mouse_check_button_pressed()
y position_meeting()
. Vamos a analizar cada una de estas funciones.
La función mouse_check_button_pressed()
detecta si el botón izquierdo del ratón ha sido pulsado, y lo detecta UNA ÚNICA vez. Esto es importante, ya que al estar en el evento Step no nos interesa que entre dentro del if
cada vez.
Con position_meeting()
detecta si un objeto está en la posición X/Y que le pasamos. En este caso, le pasamos las coordenadas del ratón con las variables de sistema mouse_x y mouse_y, y el objeto le pasamos la palabra id para que pille solamente la instancia (acepta objetos, instancias, etc).
Dentro de la condición, lo que hacemos es desactivar todos los objetos que hay en la room, con la variable selected dentro de with, y activar el objeto actual. Usamos las variables drag_x y drag_y para tener una referencia de cuanto hay que arrastrar.
Mientras estamos moviendo
Ahora que hemos preparado las variables, necesitamos que el objeto se mueva con el ratón. Añadimos el código siguiente:
//During drag if (selected == true and mouse_check_button(mb_left)) { x = mouse_x - drag_x; y = mouse_y - drag_y; } else { //End drag selected = false; }
En la condición if
detecta que el ratón se sigue pulsando, vemos que la función es diferente a la anterior, además de usar la variable selected. Si cumplimos la condición, cambiamos la posición con las variables x/y y las variables de drag. Sino, es que ya no tiene el botón izquierdo pulsado y hacemos que selected sea falso.
Crear variables al iniciar el objeto
Antes de probar el objeto, hay que inicializar la variable selected en el evento Create. Añadimos el evento y arrastramos la acción Set Variable. En variable ponemos selected y en value ponemos false.
¿Porque lo hacemos con selected y no con drag_x y drag_y?
Muy fácil, porque estamos usando la variable selected sin que estemos le hayamos dado un valor. ¿Dónde? En el segundo if
. Si no entra en el primer if
, al ir al segundo if todavía no existe la variable, así que nos dará un error nada más empezar (en realidad cuando se ejecuta el primer Step. Lo suyo sería declarar todas las variables que vamos a usar y que tendrán algún valor dentro de todo el objeto en el evento Create, pero en este caso solo es suficiente con selected.
Crear objetos heredados
Ya tenemos el objeto preparado para que pueda moverse. Ahora podemos crear nuevos objetos que hereden del anterior, poner en parent el objeto obj_parent_solid y que tenga un sprite para que podamos verlo. Por ejemplo, yo he creado el objeto obj_triangle_solid con su sprite spr_triangle_solid.
Creamos una room nueva y añadimos el objeto del triángulo para probar como funciona.
Siguiente paso: arrastrarlo a un punto específico
Ahora vamos a añadir una funcionalidad más: no nos interesa que simplemente se pueda mover, sino que lo moveremos a un punto específico. Todo esto lo programaremos en el objeto padre para que lo hereden todos los que podamos crear.
Cambios en el objeto padre
Añadimos una acción nueva al evento Create, que será Set Variable. Creamos la variable obj_magnet y le damos el valor noone. De esta manera, si no usamos esta variable no pasará nada.
También creamos otra variable, que llamaremos distance y le damos un valor de 30. Esta variable será la referencia de lo cerca que está el objeto destino.
Ahora vamos al evento Step y modificamos el segundo if
, para que quede de la siguiente manera:
//During drag if (selected == true and mouse_check_button(mb_left)) { x = mouse_x - drag_x; y = mouse_y - drag_y; } else { //End drag selected = false; with (obj_magnet) { if (point_distance(other.x, other.y, x, y) < other.distance) { other.x = x; other.y = y; } } }
Explicamos el código añadido: comprobamos que exista una instancia del objeto final, gracias a with
. Luego comprobamos la distancia que hay entre los dos objetos, gracias a la función point_distance()
. Como veis, le pasamos el X/Y de un objeto (other) y del otro (en este caso obj_magnet). Si la distancia es más pequeña que la que hemos puesto en la variable, le ponemos el mismo X/Y.
¿Porqué hacemos esto? Porque sino es muy difícil acertar a nivel de píxel los dos objetos en el mismo sitio. Así que hacemos un pequeño efecto imán, que podemos modificar la distancia fácilmente con la variable creada.
Fijaros que la comprobación la hacemos cuando soltamos el botón del ratón, si queremos que lo haga en el momento que se acerque, solo falta cambiar el código y ponerlo dentro del if
en vez de dentro del else
.
Crear objeto imán y modificaciones en el objeto triángulo
Creamos el objeto destino. Lo llamamos obj_triangle, y le asignamos el sprite que creamos al principio, spr_triangle. Ahora tenemos que decirle a nuestro objeto obj_triangle_solid cuál es el objeto asociado. Tenemos que añadir un evento Create, y primero de todo le añadimos la acción Call Parent Event. Recomiendo siempre añadir esta acción si el objeto hereda de otro, por si tenemos en el objeto padre código en el mismo evento.
Ahora añadimos una acción Set Variable, dónde en variable ponemos obj_magnet y en value ponemos obj_triangle. De esta manera, sobreescribimos la variable obj_magnet por el valor que nos interesa, y podemos crear infinitos objetos, si no quieres infinitos, muchos objetos ^_^, sólo añadiendo estas dos acciones. es la “magia” de la herencia de objetos.
Ahora podéis añadir el objeto obj_triangle a la room y probad el resultado.
Otra funcionalidad nueva: que vuelva a su posición origen
Ahora ya podemos arrastrar objetos con el ratón, tal y como lo hemos hecho también funciona en dispositivos móviles y tablets. También podemos anclarlos a un lugar, como si fueran las piezas de un puzzle. Ahora añadimos otra funcionalidad nueva: vamos a hacer que si no está anclado en el objeto destino, que vuelva a su posición original.
La opción fácil sería almacenar su posición X/Y inicial, y sino está en el punto que nos interesa, darle ese valor. Pero el movimiento sería muy brusco, aparecería de repente dónde estaba, así que vamos a hacer que se mueva hasta allí usando una función de Game Maker.
Uso de las variables xprevious e yprevious
Antes de nada, vamos a hablar de dos variables que ya existen en un objeto cualquiera. Ya deberemos conocer la variable x y la variable y, pero también existen la variable xprevious y la variable yprevious. Tal y como dicen en la ayuda, almacenan su posición justo antes de un evento Step, y nos puede interesar para saber y calcular ciertas cosas (por ejemplo, cuánto se ha movido). ¿Nos serviría estas variables? Pues no.
Lo del evento Step es importante saberlo, porque significa que cuando ha acabado el evento, la variable xprevious
valdrá x
, y lo mismo con yprevious = y
. Como nosotros modificamos el valor de X/Y en cada Step, el valor de xprevious/yprevious no lo podemos mirar al final, cuando soltamos el botón del ratón. Pero se arregla fácilmente creando unas variables nosotros.
Nuevo objeto cuadrado
Empezamos con el objeto más sencillo, el objeto cuadrado que será el destino para arrastrar. Creamos un nuevo objeto obj_square, asociándole el sprite spr_square.
Ahora creamos otro objeto que llamamos obj_square_solid, que tendrá el sprite spr_square_solid que hemos creado anteriormente y heredará de obj_parent_solid (el botón de menú de la sección Parent).
En su evento Create añadiremos lo mismo que tenemos en obj_triangle_solid, una acción Call parent event y una acción Set variable, pero ahora obj_magnet tendrá de value obj_square.
Ahora añadimos dos acciones más para crear variables: una variable xinitial, que en value pondremos x, y otra variable yinitial = y.
Ahora añadimos un nuevo evento End step. Como el objeto padre no tiene el mismo evento, no haría falta añadir la acción Call parent event, pero si se añade no pasa nada ni nos dará ningún error (y así nos aseguramos si hay cambios en el futuro del objeto padre). Añadimos una acción Execute Code con el siguiente código:
///RETURN TO ORIGIN if (selected == false and xinitial != x and point_distance(obj_magnet.x, obj_magnet.y, x, y) > 0) { move_towards_point(xinitial, yinitial, 50); } if point_distance(xinitial, yinitial, x, y) < 50 { speed = 0; x = xinitial; y = yinitial; }
En la primera condición if
, con la variable selected == false
comprobamos si el objeto lo hemos soltado (por eso usamos el evento End Step. También comprobamos que el objeto esté en el destino, si no es así, usamos la función move_towards_point()
para mover el objeto a donde estaba en el principio, usando una velocidad de 50 y las variables xinitial/yinitial.
Las funciones que mueven objetos con velocidad, normalmente usan la variable speed
, no son precisas, es decir, si queremos que vaya al mismo punto X/Y es complicado que pase exactamente por el mismo punto. Por eso hemos añadido el segundo if
: calcula si el objeto está muy cerca de su posición inicial y, si es así, ponemos la velocidad a 0 (MUY IMPORTANTE, sino intentará moverse todo el rato, haciendo un efecto de parpadeo incómodo), y lo ajustamos a su posición X/Y.
Ahora añadimos los dos objetos nuevos a la room y probamos cómo funciona.
Conclusión
Espero que hayamos aprendido algo útil con este ejemplo. Como siempre, dejo el proyecto de este tutorial para mover objetos en Game Maker para que lo podáis descargar. Y como siempre, podéis preguntar vuestras dudas y dejar vuestra opinión en los comentarios. ¡Hazlo! Para mí es muy útil de cara a futuros tutoriales y contenidos. ¡Nos vemos!
hola David,
me parece muy instructiva tu pagina, pero quiza te sirva de consejo, yo aveces no tengo tiempo para leer todos los articulos y me gustaria tener la posibilidad de todos los articulos que publicas bajarmelos a la tablet bien en pdf o epud, como me he podido bajar el Guía de acciones Drag&Drop en código
GML.
espero que este consejo te ayude, y videos hay muchos en la red que aunque no sean tuyos sirven muy bien, sobre todo a personas como yo que acabamos de empezar
un saludo
y muchas gracias
marta
Hola Marta,
si me das un poco de tiempo, en breve podré hacer un recopilatorio que te puede interesar.
Y sobre los vídeos, me lo apunto como propósito para el año próximo, se que ayuda bastante para aprender.
¡Nos vemos!
David
disculpa quisiera saber como puedo hacer para que el juego sepa si movió todas las partes hasta el punto indicado, por ejemplo en el caso de que este armando el cuerpo de un personaje como saber si el usuario termino de armarlo
Hola Carlos,
yo haria una variable contador en el objeto destino, en ese caso obj_magnet, si tienes claro el total de elementos. SI tienes elementos optativos, como por ejemplo la posibilidad de usar un escudo o no, quizás el control se haría de otra manera, con una lista, o añadiendo un botón de “ACABADO”.
David
Hola una pregunta ¿Por qué con el obj_square no puedo poner multiples como en el obj_triangle por que solo se pega en uno y en el resto se regresa?
Entiendo que el problema lo tienes en obj_magnet, al poner un objeto todos tendrán de referencia ese objeto, no cada instancia por separado. Prueba de hacer los cambios y en obj_square poner la instancia.
Hola, se me presenta un problema el cual quisiere me ayudaran, y es que al crear mas de una instancia del objeto no funciona, y se me ha creado un caos al intentar solucionarlo.
Hola Arley,
en el ejemplo utilizo objetos diferentes y funciona bien así. Si solo quieres mover objetos te debe de funcionar, pero si quieres colocarlos en un sitio final, cuando usamos obj_magnet, seguramente no funciona con varias instancias del mismo objeto.
Una posible solución es que en cada objeto le asocies una instancia de obj_magnet, y cambiar la parte del código dónde pone
with (obj_magnet) {
por la variable que tiene la instancia creada.
David
deberías poner un video para ejemplificar el resultado del tutorial, solo es una sugerencia.
Tengo pendiente poner un video explicativo de todos los tutoriales que tengo, a versi puedo sacar más tiempo!!
Hola David 🙂 tengo una inquietud, en este tipo de juegos “Drag And Drop”, como podrías hacer para pasar de nivel. Ejm: un juego que seleccione todos los similares a una muestra. Pero como sabrías que es el último.
Gracias.
Mírate las acciones de Drag&Drop relacionada con rooms.
http://docs2.yoyogames.com/source/_build/3_scripting/2_drag_and_drop_reference/room_actions/index.html
Para pasar de nivel, tienes la acción Go to Next Room, para ir a la siguiente que hay en el árbol de recursos, aunque también tienes la de Go to Room para elegir una en concreto.
También tienes la acción If Room is Last, para controlar si es la última. una opción sería programar Que si es la última habitación, vuelva al menú, o a la primera room.