cómo hacer un juego online v2

34
¿Cómo crear un juego Online (TCP) en Game Maker Studio? Por: Black_Cat usuario de ComunidadGM.org Espero que este tutorial les sea de mucha ayuda. Decidí lanzar una version 2 porque la anterior era muy genérica sobre qué eventos se debían utilizar para manejarlo. Esta vez me tomé el doble de trabajo en hacerlo, porque quiero que sea lo más claro posible. Esto de hacer un tutorial sobre juegos Online viene precisamente de que yo quería hacer uno y no encontraba material en español o bien documentado. Así espero que lo aprovechen ahora que se los traigo :D Aclaro que es bastante laborioso hacer un juego de este tipo, puesto que hay que manejar el envío y recepción de datos, en paralelo con las acciones que requiera nuestro juego multijugador. Dije laborioso pero no imposible :) Nota: Este tutorial se enfoca específicamente para juegos que utilicen el protocolo TCP. Antes de comenzar: Es sumamente importante que conozcas algunos conceptos antes de comenzar: ¿Que es TCP? http://juegosluzplateada.blogspot.com.ar/2013/03/3-que-significa- tcp.html Conceptos basicos: Buffers, sockets, puertos. (Ignoren la parte de la 39DLL) . http://juegosluzplateada.blogspot.com.ar/2013/03/1-presentacion- 39dll-y-conceptos-basico.html Bien, antes que nada, les aconsejo que antes de empezar, lean los apartados en el manual de Game Maker que comprenden (o como mucho la lista de funciones) :

Upload: danielpipa

Post on 12-Jan-2016

229 views

Category:

Documents


0 download

DESCRIPTION

asdasdas as dasd fas das f ffasdas das dasd asdas das asd as dasdas das dasd

TRANSCRIPT

Page 1: Cómo Hacer Un Juego Online V2

¿Cómo crear un juego Online (TCP) en Game Maker Studio?

Por: Black_Cat usuario de ComunidadGM.org

Espero que este tutorial les sea de mucha ayuda. Decidí lanzar una version 2 porque la anterior era muy genérica sobre qué eventos se debían utilizar para manejarlo.Esta vez me tomé el doble de trabajo en hacerlo, porque quiero que sea lo más claro posible.

Esto de hacer un tutorial sobre juegos Online viene precisamente de que yo quería hacer uno y no encontraba material en español o bien documentado. Así espero que lo aprovechen ahora que se los traigo :D

Aclaro que es bastante laborioso hacer un juego de este tipo, puesto que hay que manejar el envío y recepción de datos, en paralelo con las acciones que requiera nuestro juego multijugador. Dije laborioso pero no imposible :)

Nota: Este tutorial se enfoca específicamente para juegos que utilicen el protocolo TCP.

Antes de comenzar:Es sumamente importante que conozcas algunos conceptos antes de

comenzar:

¿Que es TCP? http://juegosluzplateada.blogspot.com.ar/2013/03/3-que-significa-tcp.html

Conceptos basicos: Buffers, sockets, puertos. (Ignoren la parte de la 39DLL). http://juegosluzplateada.blogspot.com.ar/2013/03/1-presentacion-39dll-y-conceptos-basico.html

Bien, antes que nada, les aconsejo que antes de empezar, lean los apartados en el manual de Game Maker que comprenden (o como mucho la lista de funciones) :

DS LISTS DS MAPS BUFFERS

Aunque la iremos viendo de apoco, asó no hay problema.

Hagamos un vistazo rápido de lo que tratan:

Las DS LIST, las listas, son bastantes útiles a la hora de almacenar información. Como has de suponer en forma de lista, por ejemplo como cuando ves una receta: 200 de manteca, 2 huevos , 1 taza de leche...etc. Si, así, de esa forma es cómo se almacena.

Page 2: Cómo Hacer Un Juego Online V2

Este tipo de estructura nos facilitará guardarlos en un orden, por ejemplo, guardar los nombres de los jugadores que vayan entrando en nuestra partida.

Por comodidad usaremos las funciones que ya vienen integradas en Game Maker Studio para listas, en vez, de usar arrays (arreglos o vectores).

Los DS MAPS, los mapas, que también se les suele llamar diccionarios.Este es un tipo de dato que guarda los datos como un diccionario. Por ejemplo:Gato: animal peludo que dice Miau

Aquí “Gato” es la entrada y “animal peludo que dice miau” es el dato que contiene la entrada “Gato”. Este tipo de dato es muy útil e imprescindible. Luego veremos porqué.

Buffers, es un concepto también esencial para enviar paquetes entre nuestro servidor y los clientes conectados a este.

Por ejemplo si necesitamos enviar un puntaje o algunas coordenadas, es necesario que se guarden y se envíen. En la vida real equivaldría a mandar un paquete a un familiar, colocas la dirección a donde va a enviarse, metes en el paquete un par de galletas de la abuela y se lo envías. Exactamente así es como lo implementaremos.

Creando el servidor

Para poder comunicarnos con otros clientes es necesario un paso a través de un servidor:

“La información, o datos, no pueden ser transferidos de cliente a cliente. Es decir, primero deben pasar a través del Servidor para luego llegar a otro cliente. Observa entonces, que de otra manera, habrían lineas que cruzasen entre clientes “.http://juegosluzplateada.blogspot.com.ar/2013/03/3-que-significa-tcp.html

Es importante que hayas leído las entradas del blog al principio del post, que mencionan conceptos básicos! Si no las has leído, te doy otra oportunidad.

Bien, vamos a lo que en verdad nos interesa, para que toda la magia surja, obviamente necesitamos al servidor, para ello contamos con la funcion:

network_create_server(type, port, max_client);

Esta función es utilizada para crear un nuevo servidor.La funcion devolverá un Identificador Unico (de ahora en mas ID), si se pudo crear correctamente el servidor. Es importante que la guardemos en una variable para luego usarla como referencia en otras funciones.Ahora, en el caso de que no se pueda crear o haya ocurrido algo anormal entonces devolverá un valor menor a 0.

Parametros:

type: es el tipo de protocolo que vamos a usar para transferir datos, aquí pueden ir 3 constantes incluidas ya en Game Maker:

network_socket_tcp : crea un socket web usando TCP network_socket_udp: crea un socket web usando UDP

Page 3: Cómo Hacer Un Juego Online V2

network_socket_bluethoot: crea un socket tipo bluethoot(Actualmente no soportado).

Port: numero de puerto donde alojar al servidor.Max_client: numero de clientes máximos permitidos en el juego.

Entonces, si queremos iniciar nuestro servidor, colocamos la siguiente instrucción (Recuerde que el tutorial está enfocado unicamente al uso de TCP):

global.servidor = network_create_server(network_socket_tcp, 48050, 10);

Aqui decimos que queremos iniciar un servidor con protocolo TCP, que estará alojado en el puerto numero 48050 y podrá recibir un máximo de 10 conexiones simultaneas, o que es lo mismo, 10 jugadores.

Esto bien podría colocarse en algún botón. Para este ejemplo usamos un evento create en un objeto limpio al que llamamos obj_servidor.

Así almacenamos el resultado en nuestra variable “global.servidor”, recordemos que dará un valor menor que 0 si ocurrió un error al crear o un numero identificador si salió todo bien.Sería sensato verificar si hubo algún error o no.

Page 4: Cómo Hacer Un Juego Online V2

NOTA: es importante saber que, si se crea un a conexión, ésta debe eliminarse en algun momento, sino quedará abierta y los hackers rusos de Anonimau5 podrían robar las fotos de nuestra gorda tia Jimenita.Para ello disponemos de la funcion:

network_destroy(servidor_que_queremos_destruir);

donde servidor_que_queremos_destruir es el ID del servidor que queremos eliminar.Entonces agregamos, por ejemplo, al finalizar el juego, esta instrucción, debidamente adaptada:

-La disponibilidad de nuestro puerto 48050

A la hora de crear el servidor puede que el puerto que quisieramos utilizar este ocupado, por lo que a la primera no se iniciaría, y no importase cuantas veces abramos el servidor, si el puerto está ocupado, no se iniciará ni por lloriqueos ni por berrinches... entonces vamos a tener que usar otro.

Esto es sencillo porque podemos pedir todos los que quieramos. Somos unos niños malcriados y si no nos ceden el puerto que queremos pedimos otro diferente, el caso es ganar nuestro caramelo.Para pedir un puerto libre podemos usar un ciclo condicionado:

var puerto = 1024; // porque 1024 y no 48050? Lee las entradas al blog donde explica los "puertos registrados"... ultima oportunidad si no leiste el blog

global.servidor = network_create_server(network_socket_tcp, puerto, 10);

while(global.servidor <0) //mientras no se pueda crear un servidor{ puerto++; //pedimos otro puerto para ver si esta libre global.servidor = network_create_server(network_socket_tcp, puerto, 10); //intentamos conectar}

Page 5: Cómo Hacer Un Juego Online V2

Así tarde o temprano vamos a obtener un puerto libre.

Ya con esto podemos comenzar a conectar clientes. :)

El Cliente

Ahora llega el turno de los jugadores, o los clientes. Para crear uno contamos con la función:

network_create_socket(type);

Esta función es utilizada para crear un nuevo cliente, que se comunicará con el servidor para enviar y recibir paquetes. Debemos asignar el tipo de cliente que queremos crear y esta función nos devolverá un ID unico si se pudo crear el cliente o un numero menor a 0 si hubo algun error.

Argumentos:

Type: es el tipo de cliente a crear, y puede ser alguna de estas constantes que ya trae Game Maker:

network_socket_tcp : crea un socket web usando TCP network_socket_udp: crea un socket web usando UDP network_socket_bluethoot: crea un socket tipo bluethoot(Actualmente no

soportado).

Entonces si queremos crear un cliente usamos:

global.cliente = network_create_socket(network_socket_tcp);

En nuestro ejemplo creamos un objeto limpio y en el evento create colocamos dicha instrucción.

Entonces verificamos si se pudo crear el cliente. Ante cualquier error, cerramos el juego.

Recordemos que debemos destruir cualquier conexión que hagamos, para no dejar puerto abiertos ni nada raro. Nadie quiere olvidar puertas abiertas en su casa, o si?

Para ello usamos “networ_destroy(conexion_que_queremos_destruir);”

Page 6: Cómo Hacer Un Juego Online V2

Bien, bien, bien :)Ya hasta este punto tenemos un servidor y cuanto menos un cliente. Ahora, hay que conectarlos para comenzar a pasar datos y recibirlos.

Entonces, conectaremos al cliente con el servidor y nunca al revés.

Para ello contamos con la siguiente funcion:

network_connect(socket, url, port);

Agumentos:

socket: la ID del socket que va a conectarse.url: direccion IP o URL a donde queremos conectarnos. (Deben introducirse en forma de cadena. Por ejemplo: “128.66.122.21” o “comunidadgm.org” [notese las comillas necesarias] o bien se puede convertir a cadena con la funcion string(...))

port: puerto donde esta alojado el servidor en la direccion url.

Como ya habíamos creado nuestro cliente asi que en socket vamos a utilizarlo con global.cliente.La IP debe ser la dirección a donde queremos conectarnos, la IP de nuestro amigo que hizo la partida. Y port el numero de puerto donde está alojada, el cual está especificado en el momento en el que se creó el servidor. Recordemos que se hizo esto con 48050. Luego si no se pudo conectar, devuelve un valor menor que 0. Nota: puedes utilizar “127.0.0.1” para conectar con varios clientes dentro del mismo dispositivo.

Entonces, estoy en mi computadora, abro el servidor, que ya sabemos como hacerlo, y deseo para probar si conecta, entonces puedo utilizar “127.0.0.1”.

Nos debería quedar un código como el siguiente:

Page 7: Cómo Hacer Un Juego Online V2

network_connect(global.cliente, "127.0.0.1", 48050);

Nosotros vamos a colocarlo en el evento create de nuestro obj_cliente, mientras verificamos el caso de que no podamos conectarnos :

Todo esto debe bastar para crear un servidor, un cliente y conectarlo. :D Pero ahora vienen temas mas importantes si quieres que todo marche a la perfección, así que si estás cansado, toma un vaso de tu bebida favorita, que la cosa ahora se pone más densa...

Eventos asíncronos

- What the... hell? :S- Tranquilo Brother, deja que te explique...

Los eventos asíncronos son algunos de los eventos que pueden ser gatillados dentro de una instancia y que pueden contener códigos y acciones.

Gatillar se considera, lanzar o activar un evento, cuando ocurre algo en especial o se cumplen determinadas condiciones o cuando se llama a una función asíncrona, por ejemplo: get_string_async(...).

Estos famosos eventos asíncronos están disponibles como eventos dentro de un objeto. Vamos, donde estan “create”, “step”, “alarm”...

Page 8: Cómo Hacer Un Juego Online V2

Nosotros vamos a utilizar 2 de esos, a saber:

Dialog Networking

Es muuuuy importante saber que dentro de estos eventos se crea un ds_map llamado async_load y es de uso exclusivo dentro de dichos eventos. Para manipularlos es imprescindible saber usar las ds_maps, pero sino, vamos a explicarte algunas funciones útiles.También es importante saber que como cada evento asíncrono tiene su async_load estos, tienen entradas diferentes dependiendo a que evento pertenecen.

Ejemplo corto de como usar un evento asíncrono.(OPCIONAL).

Rápidamente, creamos un nuevo proyecto en blanco, limpio. Creamos un objeto sin nombre, y vamos al evento create.

Allí vamos a ejecutar una función asíncrona para que veamos la funcionalidad de esto, para ello usaremos get_string_async(...)

get_string_async(str,def);

Esta función nos pedirá una cadena o un texto y devolverá una respuesta en forma de numero.

Page 9: Cómo Hacer Un Juego Online V2

str: es el cartel que se mostrará como mensajedef: es la cadena o el texto por defecto que estará puesto al mostrar el cartel.

Así: la función muestra un cartel con el texto str con la respuesta def por defecto.

Nos debería quedar algo así:

Esta función dispara el evento asíncrono dialog. Y como sabemos, allí, dentro de ese evento se crea un ds_map, llamado async_load que tendrá entradas especiales.

En nuestro caso, es un cartel que pide nuestro nombre.

Entonces, vamos al evento asíncrono dialog y comencemos:Primero, nuestro mapa async_load, tiene 3 diferentes entradas (o palabras):

id: es el valor numérico que fue devuelto a la función que ejecutó el evento. En nuestro caso debería ser el mismo valor que contiene la variable respuesta.

status: contiene un solo valor a la vez, de dos posibles. True si se ha presionado el boton “OK” del cartel. O False si se ha presionado “Cancel”. Se debe tener en cuenta que no todos las plataformas proveen una opción de cancelar.

result: contiene la cadena ingresada. O si no se ha ingresado nada, entonces una cadena vacia: “”.

Bien, ya pedimos nuestro nombre en el evento create. Luego de que ingresemos un texto, la función gatillará el evento asíncrono dialog.Allí se crea, especialmente nuestro mapa async_load, listo para operar.

Así, vamos a nuestro evento. Allí vamos a pedir el valor de result que debería ser una cadena ingresada por el usuario. Para pedir este valor, usamos la funcion:

Page 10: Cómo Hacer Un Juego Online V2

ds_map_find_value(map, key);

Esta función devuelve el valor contenido en key del mapa map. Recuerda que al inicio del tutorial, dijimos que los mapas también se conocen como diccionarios. Vuelvamos al ejemplo:

Diccionario de animalesGato: animal que dice Miau

Si nosotros quisiéramos obtener el valor de la key “gato”, de nuestro mapa (o diccionario) llamado diccionario de animales, deberíamos hacer:

var definicion;definicion = ds_map_find_value(DiccionarioDeAnimales, gato);

guardamos el valor devuelto, y nuestra variable definicion debería contener una cadena que dice: “animal que dice miau”.

Entonces, si quisieramos guardar y leer la cadena que ingresa el usuario a través de la funcion get_string_async(...) deberíamos leer la key “result”.

Luego, verificamos por algún valor y si es el que esperábamos, operamos de una u otra manera.

Dejo un ejemplo simple:

Luego, para leer los demás apartados como “id” o “status” se opera de la misma manera. :)

Page 11: Cómo Hacer Un Juego Online V2

--------Fin del corto tutorial opcional sobre como usar eventos asíncronos-----

Bien, bien continuemos... hasta ahora solo podemos crear un servidor y conectarnos remotamente. Este ejemplo no nos sirve si necesitamos, como cliente, meter la IP de nuestro amigo que vive en España, o que vive en Chile.Es decir que colocar a secas “127.0.0.1” y compilar un cliente (o varios con diferentes direcciones IP) no nos sirve.

Lo mismo para el puerto, puede que 48050 no esté disponible siempre, y cambie para el servidor, entonces compilarlo con dicho puerto no nos conectaría nunca.Vamos a verlo por parte:

Para pedir una dirección vamos a usar la siguiente función:

get_string_async(str,def);

Esta función nos pedirá una cadena o un texto y devolverá una respuesta en forma de numero.

str: es el cartel que se mostrará como mensajedef: es la cadena o el texto por defecto que estará puesto al mostrar el cartel.

Recordemos que la dirección para network_connect(...) debe ser una cadena.

Entonces operamos de la siguiente manera, lo único que debe estar en nuestro evento create del objeto cliente es la creacion del socket cliente:

global.cliente = network_create_socket(network_socket_tcp); //Creamos al cliente

if (global.cliente < 0) game_end(); //Si no se puede crear el cliente, cerramos el juego

Adicionalmente vamos a necesitar algunas variables para almacenar la IP que ingresará el usuario como así también el numero de puerto, además de una variable para verificar si la conexión se realizó con éxito:

conexion = -1; // Variable para verificar si se conectó exitosamenteIP_A_CONECTARSE = ""; // ip a donde queremos conectarnosPUERTO_DEL_SERVIDOR = 0; // numero de puerto donde esta el servidorrta_cadena = -1; //variable adicional para saber el motivo de gatilladorta_entero = -1; // variable adicional para saber el motivo de gatillado

global.cliente = network_create_socket(network_socket_tcp); //Creamos al cliente

if (global.cliente < 0) game_end(); //Si no se puede crear el cliente, cerramos el juego

Page 12: Cómo Hacer Un Juego Online V2

Para una mayor comodidad vamos a asignarle a nuestro obj_cliente un sprite con un cartelito de cliente, como simulando un boton:

Luego agregamos la parte que pedimos una dirección para poder conectarnos.Esto lo haremos desde un evento de mouse left key pressed, para que cada vez que presionemos en nuestro boton obj_cliente, nos pida una IP, y un numero de puerto.

Aqui pedimos una IP y guardamos el resultado numerico en “rta_cadena” para saber el motivo por el cuál se ejecuta nuestro evento asíncrono dialog.

Guardamos los valores en “rta_cadena” y “rta_numero” porque ambas funciones gatillan el mismo evento y es necesario csaber uando ocurre cada uno. Si es por pedir una cadena o si es por pedir un numero.

Así al hacer click se pide una cadena con get_string_async(...), e inmediatamente esta función llama al evento asíncrono dialog. Allí debemos operar el resultado de async_load.

Entonces para saber que vinimos desde la funcion string_async, le

Page 13: Cómo Hacer Un Juego Online V2

preguntamos a async_load si esto es asi, de la siguiente manera:

(Estamos ahora dentro del evento Dialog).

var llamada;

llamada = ds_map_find_value(async_load,"id");

if (llamada == rta_cadena)….

Aquí obtenemos el ID de la función que llamó a dialog y preguntamos si esa funcion es la que pide la IP (o la cadena). Si es así, entonces leemos la IP que ingresó el usuario.

if (llamada == rta_cadena)IP_A_CONECTARSE = ds_map_find_value(async_load, "result");

Eso bastaría para obtener la IP...Luego, el programa vuelve donde se hizo la llamada en el evento create y ejecuta la instrucción get_integer_async(...) que vuelve a llamar al evento dialog, entonces debemos hacer una distinción entre las llamadas.

Para ello, preguntamos si “llamada” es igual a la id de la función que nos trajo. Es decir si “llamada” es igual al valor de get_integer_async(...).Entonces preguntamos, dentro del evento dialog:

if (llamada == rta_entero) // Si llegamos por pedir un entero

Si llegamos por dicha función entonces deberíamos almacenar el numero de puerto:

if (llamada == rta_entero) PUERTO_DEL_SERVIDOR = ds_map_find_value(async_load, "value");

ahora nuestro evento dialog debería lucir así:

Page 14: Cómo Hacer Un Juego Online V2

es importante notar que cuando pedimos el valor ingresado por el usuario en get_integer_async(...) se usa VALUE en vez de RESULT como lo haríamos con get_string_async(...).

Ya con eso, el programa, luego de ejecutar el evento asíncrono dialog, vuelve al evento create donde ejecuta network_connect(...) con los nuevos parametros, si no se puede conectar entonces volverá a pedir una IP y un puerto.

Luego en caso de que nos conectemos nos moveremos al room de juego, para ello solo bastaría verificar en un evento step si la conexión se realizó con exito:

if (conexión >= 0)room_goto(room_juego);

Y eso es absolutamente todo lo que necesitas para conectar un cliente a un servidor... largo no? XD

Ahora vamos a acomodarlo todo un poco...Primero que nada, crearemos un objeto que llamaremos obj_boton_server. Y le asignaremos un sprite de boton para el server.

Este nuevo objeto, obj_boton_server, deberá llevar un evento de mouse_key_pressed que al presionarlo, nos llevará a un room al que crearemos y llamaremos “room_servidor”, donde tendrá únicamente al obj_server que ya habíamos programado.

Luego, creamos un nuevo room llamado “room_menu” para poner los dos botones creados:

obj_boton_server obj_cliente

que ambos tienen un sprite para hacerlos simular un boton.

Page 15: Cómo Hacer Un Juego Online V2

El juego debería verse de esta manera, estructuralmenteYo eh pintado los rooms para identificarlos :

-Aceptando nuevos clientes y paquetes de datos.Ahora veremos el segundo evento asíncrono: Networking

Cada vez que el juego intercambia datos en una conexión se ejecuta el evento asíncrono Networking.Es decir, cada vez que el servidor, por ejemplo, recibe a un cliente, o cuando se desconecta, se ejecuta. Así mismo sucede cuando se envían datos o reciben, ya sea en un cliente o un servidor.

La manera de operar este evento es igual a la que forma en que se hizo con el evento dialog, es decir, depuraremos para saber la razón de dicha ejecución.

En nuestro evento dialog, tuvimos que depurar para saber si el evento se ejecutaba por usar get_string_async(...) o si se ejecutaba por utilizar get_integer_async(...).

En este caso debemos depurar para saber si se ejecutó por recepción de datos o por si se ha conectado un nuevo cliente u otro motivo.

Es importante, saber que este mapa async_load (recordemos que dijimos anteriormente, que todos los eventos asíncronos contaban con uno) tiene entradas especiales a determinadas acciones y otras comunes, entre un grupo

Page 16: Cómo Hacer Un Juego Online V2

de acciones.

Entonces, como dijimos el evento asíncrono networking se puede gatillar por 3 razones:

Se conectó un cliente Se desconectó un cliente Recibimos datos

Hay entradas o keys o palabras que son comunes para estos tres motivos de gatillado y son:

type: esta entrada (o palabra) contendrá alguno de los siguientes valores, los cuales son constantes que trae Game maker, ya definidas.

Dichos valores reflejan el motivo por el cual se ejecuta el evento networking y son:

◦ network_type_conect: el evento fue gatillado por una conexión entrante.

◦ network_type_disconnect: el evento fue gatillado por una desconexión.

◦ network_type_data: el evento fue gatillado por datos entrantes.

id: el ID del socket que está recibiendo el evento.En los casos en los que se reciben datos, “id” es el socket de quién envia dichos datos, es decir, el numero de socket de quien envia el paquete de informacion(id del cliente emisor).

ip: es la dirección IP del socket anterior (en formato de cadena). port: es el número de puerto donde está alojado el cliente, dentro de la

IP anterior.-Entradas adicionales para cada “async_load” dependiendo el motivo

Como dije anteriormente se agregan entradas (o keys o palabras) adicionales dependiendo del motivo por el cual se gatilla el evento asíncrono networking.

Si, se ejecuta por:

Conexión o desconexión: cuando se conecta o desconecta un cliente, se agrega una key adicional a nuestro diccionario async_load y es:

◦ socket: esta entrada contendrá el número de socket que se conectó o desconectó. Útil por ejemplo si queremos retirar un jugador de la lista o agregarlo.

Por recepción de datos: cuando se reciben datos se agregan dos keys adicionales a nuestro diccionario async_load y son:◦ buffer: este es un identificador de buffer unico, el cual es generado

por el evento. Es un buffer dinámico, con alineacion a 1 byte y es creado para almacenar el paquete que envía el socket. A la final del evento asñincrono, este buffer es eliminado. (Explicaremos más de buffers en un momento).

◦ size: contiene el tamaño en bytes del buffer recibido.

Page 17: Cómo Hacer Un Juego Online V2

Bien, hora de trabajar con la recepción de clientes y datos.Vamos al evento asíncrono network, dentro de nuestro servidor(el obj_servidor), puesto que queremos ver si se ha conectado algún cliente.

Cómo sabemos, primero debemos determinar la razón por la cual se podría gatillar nuestro evento, para ello vamos a hacer la lectura de nuestro mapa async_load para determinar el motivo y para ello vamos a usar una conveniente estructura switch para ir filtrando o depurando.

Como sabemos, la entrada (o key) “type” contiene la razón o el motivo de la ejecución del evento networking, entonces sería lógico primero obtener el valor y luego preguntar:

var MOTIVO;MOTIVO = ds_map_find_value(async_load, "type");

switch (MOTIVO){ case network_type_connect: //Se conectó un cliente break; case network_type_disconnect: //Se desconectó un cliente break; case network_type_data: //Recibimos datos break; }

Así entonces podremos operar los datos recibidos, más las entradas adicionales por cada motivo, de manera conveniente para realizar algunas acciones, como enlistar jugadores, quitarlos de una lista, reenviar datos, etc...

Ahora, vamos a suponer que se nos conecta un nuevo cliente a nuestro servidor. Entonces, se activará el evento asíncrono, se obtendrá un valor y almacenará en la variable “MOTIVO”, luego esta será evaluada y entrará en el caso de los clientes conectados, o sea, network_type_connect.

Nosotros vamos a enlistar a los jugadores que vayan llegando. Pero solo es con fines del ejemplo, se puede hacer lo que al programador se le antoje.Bien, para ello vamos a necesitar el uso de las ds_list funciones nativas de Game Maker Studio para crear listas dinámicas. Para ello contamos con la función:

ds_list_create();

la cual nos crea una lista dinámica y nos devuelve un ID único que nos servirá para hacer referencia a dicha lista en caso de que la necesitemos para otras

Page 18: Cómo Hacer Un Juego Online V2

funciones.Convenientemente vamos a crear nuestra lista en el evento create, de la siguiente forma:

lista_jugadores = ds_list_create();

Entonces así nos aseguramos de guardar el ID de nuestra lista en “lista_jugadores” para luego poder referirnos a ella.

Ahora que tenemos una lista creada, bastaría llenarla con los jugadores que vayan entrando a la sesión. Entonces volvemos al evento asíncrono donde tenemos el switch, y dentro del case donde se verifica la conexión de un cliente, usaríamos una función para agregar un elemento a nuestra lista “lista_jugadores”, la cual es:

ds_list_add(id_lista, valor);

Esta función nos permite agregar un valor a la lista, ya sea en formato de numero o de cadena. Siempre se agrega al final.

Argumentos:

id_lista: ID de la lista en donde queremos agregar un nuevo valor. valor: es el nuevo valor (un numero o una cadena) que queremos

agregar.

Más concretamente, agrega a la lista id_lista el nuevo valor value al final.

Entonces dentro de nuestro caso para nuevos clientes conectados, deberíamos tener algo como esto:

case network_type_connect: var cliente = ds_map_find_value( async_load , "socket" ); ds_list_add(lista_jugadores, cliente);

break;

Con esto decimos que, cada vez que se conecte un cliente, leeremos el socket de dicho cliente y almacenaremos su número en nuestra lista “lista_jugadores”.Así iremos almacenando a todos los que vayan entrando a nuestra partida.

Si todo marcha bien, deberíamos haber llegado a algo como esto:

Page 19: Cómo Hacer Un Juego Online V2

Para ir vaciando la lista en caso de desconexión, deberíamos hacer el mismo proceso:1-Obtener el socket 2-Modificar la lista

Para quitar un valor de la lista, contamos con la función:

ds_list_delete(id_list, pos);

con esta función borramos en la lista id_list el valor en la posición pos.

Argumentos: id: ID de la lista en donde queremos borrar un valor. pos: posición del valor que queremos quitar.

El único “problema” que tendríamos es que los jugadores se conectan en un orden específico, al momento de desconectarse no lo harían en el orden inverso. Me refiero que el último en entrar no sería necesariamente el primero en salir. Por lo que no sabríamos la posición exacta de un valor en la lista. Para lidiar con esto contamos con la siguiente función:

Page 20: Cómo Hacer Un Juego Online V2

ds_list_find_index(id_list, val)

esta función nos devuelve la posición de val dentro de la lista id_list. Si no está dentro de la lista devolverá -1

Argumentos:

id_list: lista en donde queremos operar val: es el valor que buscaremos dentro de la lista

Entonces primero deberíamos obtener el socket que se desconectó, luego debemos encontrar la posición donde está dicho socket y finalmente eliminar el socket de la lista.

case network_type_disconnect:

var SOCKET = ds_map_find_value( async_load , "socket" ); var POSICION = ds_list_find_index( lista_jugadores , SOCKET );

if (POSICION != -1)ds_list_delete(lista_jugadores, POSICION);

break;

Ya con esto cubrimos la necesidad de tener un registro con nuestros clientes, luego se pueden operar convenientemente si se desea. Por ejemplo dibujar una lista con los jugadores conectados. O pedir sus nombres y también almacenarlos. Todo depende lo que quiera hacer el programador.Cubrimos también el hecho de que un jugador se desconecte, ya fuera por el motivo que sea, se le cayó internet, su madre le apagó el modem para que vaya a comprar las tortillas, o lo raptaron los extraterrestres. Game Maker nos asegura que si se pierde la conexión, este enviará a nuestro servidor una señal de desconexión.

Ahora viene la parte máaaas emocionante :DLa transmisión de datos... sigue leyendo brother, que ahora más que nada necesitas más esfuerzo.

- Buffers

-Qué es un buffer, se come?-A decir verdad, no, no se comen... :(

“Un buffer es un espacio de memoria que guarda datos temporalmente.”Es es mi definición. Si quieres otra, por ahí está el blog que recomendé leer, que contiene los conceptos estos que vinimos viendo desde hace rato :)

Bien, este es un tema importante e imprescindible puesto que con estos

Page 21: Cómo Hacer Un Juego Online V2

buffers vamos a transmitir los datos, así que daré una explicación breve.

Game Maker Studio tiene la posibilidad de crear buffers de diferentes tipos, para luego llenarlos con información. Existen varios tipos, nosotros veremos algunos:

Buffer fijo: una vez que se crea dicho buffer con un determinado tamaño, al llenarse no pueden agregarse mas datos.

Buffer dinámico: Una vez que se llena el buffer, si ha llegado a su limite, éste se expandirá hasta ocupar el espacio requerido

Buffer envolvente: una vez que se llega al limite del tamaño de dicho buffer, éste vuelve al principio para sobre escribir los datos que ya hayan, hasta ocupar el espacio faltante.

Nosotros en este tutorial ocuparemos buffers dinamicos para evitarnos mucho trabajo y algunos problemas.

Nota: Existe lo que se llama Alineación de bytes, el cual no veremos y cuando lo necesitemos, siempre usaremos el valor de 1, lo cual siempre nos funcionará y será suficiente.

Los buffers, como son espacios de memoria creados, los usaremos para colocar valores y enviarlos como paquetes de datos a otros clientes, a través del servidor.La estructura por convención que vamos a utilizar en este tutorial para redactar un buffer, será:

-identificador de paquete-contenido (o datos)

Es decir que para enviar un paquete, éste constará primero de un “identificador de paquete” que será un numero entero y luego de eso podremos escribir los datos que queramos, ya sean cadenas, coordenadas, nombres, etc.

Esta estructura es necesaria puesto que el servidor y los clientes necesitarán saber que trae dentro del paquete y en qué orden.El Identificador le ayudará a saber a cada parte, qué clase de paquete se espera recibir.

Un ejemplo sencillo de un paquete sería el siguiente:

1x jugadory jugador

donde 1 es el identificador de paquete, es decir, cuando el servidor reciba un paquete con un número 1, sabrá internamente que espera recibir un paquete de coordenadas, en el orden de X primero y Y segundo.

Otro ejemplo sería el siguiente:

Page 22: Cómo Hacer Un Juego Online V2

2nombre jugador

donde 2 es el identificador de paquete, es decir, el servidor al recibir un paquete con del numero 2 sabrá que este contiene un nombre.

Es importante recalcar que los identificadores de paquetes deben ser UNICOS. Esto es, no pueden haber dos paquetes con identificadores iguales, sino el servidor o el cliente no sabrán que clase de paquetes le están llegando y se confundirían.

Entonces, para enviar datos al servidor (o a un cliente) es necesario crear un buffer y llenarlo con datos. Para ello contamos con la siguiente función:

buffer_create(size, type, alignament);

Esta función crea un buffer y devuelve un ID para hacer referencia a dicho buffer.

Argumentos:

size: el tamaño del buffer en bytes type: el tipo de buffer que queremos crear, pueden ser algunas de estas

constantes definidas en Game Maker:◦ buffer_fixed : buffer de tamaño fijo◦ buffer_grow : buffer dinámicos◦ buffer_wrap: buffer envolvente◦(Recuerde que ya los habíamos explicado)

alignament: la alineacion de bytes (Nosotros dijimos que utilizaríamos 1 en cualquier caso).

Así para crear un paquete con datos, podríamos colocar:

paquete = buffer_create(64,buffer_grow,1);

Con esto decimos que queremos crear un buffer, con un tamaño de 64 bytes, del tipo dinámico. Una vez creado dicho paquete debemo llenarlo con los datos que quermos enviar. Entonces comenzamos a escribir con la funcion:

buffer_write(buffer,type,value)

Esta función puede ser usada para escribir datos en nuestro buffer recién creado. Los datos que escribas deben ir en concordancia con el tipo (type) que se le ha pasado como parámetro. Es decir, NO intentes escribir una cadena si esperas recibir un numero entero, deben coincidir.

Argumentos:

buffer: buffer donde vamos a escribir los datos.

Page 23: Cómo Hacer Un Juego Online V2

type: tipo y tamaño del dato que vamos a escribir. Game Maker Studio cuenta con diversas constantes que reprensentan muchos tipos de datos, voy a listar solo algunas para más, busca en el manual en el apartado de “buffer_write”. Algunas son:

◦ buffer_u8 = un entero de 8 bits sin signo. Esto quiere decir que el valor tiene que ir de 0 a 255

◦ buffer_s8 = un entero de 8 bits con signo. Esto quiere decir que el valor a escribir debe ir de -128 a 127 (el 0 se considera positivo)

◦ buffer_u16 = Un entero de 16bits sin signo. El valor a escribir debe ir de 0 a 65.535

◦ buffer_s16 = Un entero de 16 bits con signo. El valor a escribir debe ir de -32.768 a 32.767

◦ buffer_string = Una cadena de cualquier tamaño.

value: valor a escribir.

-Escribiendo y leyendo un buffer

Algo importante que se debe saber es que cuando se lee o se escribe dentro de un buffer, hay como un indice dentro de este que indica la posición donde se terminó de escribir (o leer).Es como dejar un marcador dentro de un libro, comienzas el libro, lees 5 paginas y dejas el marcador allí, luego la siguiente vez que vuelves a leer el libro este marcador avanzará desde donde te quedaste.

Cada tipo de buffer tiene si tamaño en bytes, dejo una tabla de los ya mencionados, para más, véase el manual.

TIPO DE DATO ESPACIO EN BYTES

buffer_u8 1

buffer_s8 1

buffer_u16 2

buffer_s16 2

buffer_string *N/A

*Las cadenas tienen un trato especial y es que al momento de terminar de escribirlas se las agrega un 0, que significa NULO o fin de cadena.

Entonces, supongamos que tengo un buffer de 16 bits que está totalmente vacío.Este indice o marcador, se encontrará en la posición 0.Ahora bien, escribimos un dato del tamaño y tipo “buffer_u8”, entonces, el indice se moverá hasta la posición donde terminamos de escribir (o leer), asi entonces, el indice estará en la posición 1 de 16.Supongamos que vuelvo a escribir un dato, esta vez del tamaño “buffer_s16”, entonces el indice se moverá desde 1, unas 2 posiciones adicionales (porque es lo que ocupan los buffers_u8 y buffers_s16), así entonces, el indice estará en

Page 24: Cómo Hacer Un Juego Online V2

la posicion 3.Así será consecutivamente mientras escribamos (o leamos) un dato dentro del buffer.

Entonces, sería necesario contar con una función que nos permitiera mover libremente dicho indice o marcador, para por ejemplo, leer el buffer desde el incio.Por suerte contamos con:

buffer_seek(buffer, base, offset);

Esta funcion mueve el indice de “buffer”, desde la posición base, offset lugares adicionales.

Argumentos:

buffer: el buffer donde queremos mover el indice base: posición base desde donde nos moveremos. Tambien se aceptan

las siguientes constantes definidas dentro de Game Maker Studio:◦ buffer_seek_start = el comienzo del buffer◦ buffer_seek_relative = La posición actual del indice◦ buffer_seek_end = el final del buffer

offset: lugares adicionales desde la posición base.

Luego una función extra, que nos servirá de ayuda será:

buffer_tell(buffer);

Esta función devuelve el valor de donde se encuentra el indice de buffer actualmente.

Argumentos:

buffer: buffer del cual queremos obtener la posición de su indice de escritura/lectura.

- Armando un buffer con datos o paquete de datos

Pfff, tanto lio... para un juego :-[

Primero vamos a crear un paquete, recordando nuestra convención de como se armaba uno:

1- identificador del paquetes2- datos que queremos enviar

Así por ejemplo, si quremos que nuestro cliente envíe un paquete al servidor (o viceversa), primero deberíamos escribir un número para identificar el paquete y seguido, los datos:

Page 25: Cómo Hacer Un Juego Online V2

var paquete;

paquete = buffer_create(16, buffer_grow, 1); // creamos un paquete de 16 bytes

buffer_seek(paquete, buffer_seek_start, 0); //Movemos el indice de escritura al inicio del paquete

buffer_write(paquete, buffer_u8, 10); // escribimos el identificador del paquete

buffer_write(paquete, buffer_s8, x);//Escribimos en “paquete” el valor de nuestra xbuffer_write(paquete, buffer_s8, y); //escribimos en “paquete” el valor de nuestra y Ya con esto creamos nuestro paquete, que tiene la siguiente estructura:

10coordenada Xcoordenada Y

Siendo 10 el identificador de datos, que puede ser cualquier numero que deseemos, siempre que recordemos que significa esto.

Es como si dijéramos implicitamente: “Este paquete contiene galletas” pero en vez de eso ponemos un 10, ( o cualquier numero). Entonces al momento de recibirlos el servidor con solo ver el número 10 sabe que ahí dentro hay galletas, porque es la convención que adoptamos para numerar a los paquetes.

Bien, ahora con el paquete listo, debemos enviarlo al servidor... para ello contamos con la siguiente funcion:

network_send_packet(socket, buffer, size);

Esta función sirve para enviar nuestro paquete buffer del tamaño size por medio de socket.Si se envió bien el paquete entonces devolverá el numero de bytes enviados sino devolverá un valor menor a 0.

Argumentos:

socket: socket que usaremos para enviar el paquete. buffer: buffer o paquete que queremos enviar. size: tamaño de nuestro buffer (en bytes).

Bien, una vez sabiendo esto, nos quedaría algo como:

var paquete;paquete = buffer_create(16, buffer_grow, 1); // creamos el paquete

Page 26: Cómo Hacer Un Juego Online V2

buffer_seek(paquete, buffer_seek_start, 0); //Movemos el indice de escritura al inicio del paquete

buffer_write(paquete, buffer_u8, 10); // escribimos en “paquete” un valor que va de 0 a 255 (notese que usamos buffer_u8), y el valor que escribimos alli es 10

buffer_write(paquete, buffer_s8, x);//Escribimos en “paquete” el valor de nuestra xbuffer_write(paquete, buffer_s8, y); //escribimos en “paquete” el valor de nuestra y

var tamanio = buffer_tell(paquete); //Esto nos devolverá la posición donde se escribo el ultimo dato, por lo tanto, el tamaño final del paquete.

network_send_packet(global.cliente, paquete, tamanio);// enviamos el paquete a través de nuestro socket cliente

Y ya con esto por fin, le pudimos enviar un dato al servidor! :DAh, pero nuestro servidor no sabe como interpretar cada paquete :O pues ahora, vamos a trabajar ese aspecto.

–Interpretando los paquetes de nuestros clientes

Para interpretar los paquetes de nuestros clientes, es necesario que vayamos a nuestro evento asíncrono en la parte que detecta los paquetes entrantes dentro del switch que discriminas los motivos de ejeución: case network_type_data:

// ESTE APARTADO NOS INTERESA!!!break;

Dentro del caso network_type_data, es necesario que que interpretemos que clase de paquete nos llegó, para ello usaremos un switch anidado.

case network_type_data:var paquete_recibido = ds_map_find_value(async_load,

“buffer”); //obtenemos el buffer que nos envia un cliente

buffer_seek(paquete_recibido, buffer_seek_start, 0); //nos aseguramos de comenzar a leer desde el inicio.

Var ID_PAQUETE = buffer_read(paquete_recibido, buffer_u8);

Page 27: Cómo Hacer Un Juego Online V2

//leemos el indice del paquete que nos enviaron

switch(ID_PAQUTE) //Evaluamos el indice de dicho paquete{

case 10: //nos dice que recibimos un paquete de coordenadas//Leemos las coordenadas, como se ve, tengan en cuenta que

se leen en el orden en el cual están definidas en el paquete del cliente.

player1_x = buffer_read(paquete_recibido, buffer_s8);player1_y = buffer_read(paquete_recibido, buffer_s8);

break;

}break;

Ya con esto el servidor puede procesar los datos que envió el cliente. En este caso los guarda dentro de las variables player1_x y player1_y. Que pueden ser interpretadas de cualquier forma que se desee dentro del programa.

Si quisiéramos enviar datos del servidor al cliente, es exactamente el mismo proceso:

-Crear un paquete

Colocarle una ID y los datos

-Enviarla al cliente

y finalmente, el cliente debería procesar los datos como crea conveniente.

Eso es todo... muuuuuuuuuuuy extenso, pero bueno! Es lo que se requiere para hacer un juego en linea TCP.

Espero que les haya servido. Y si desean compartir el tutorial, bienvenido sea! Los créditos son agradecidos.

Cualquier pregunta es bienvenida!

Black_Cat