Estudio de UNET

Esta entrada, es un recordatorio personal, después de hablar con amigos y de hablarme tan bien de UNET (Yo solo toque NET en Unity 3.5, :S), me entro el gusanico de ver esta nueva tecnología, por lo que decidí ponerme a investigar, pero descubrí que hay muy poca información sobre esto.

Detallo en más profundidad, hay información, pero del High API. Lo que yo quería ver es información sobre el Low API y de esto hay poco, asi que decidí crear esta entrada para guardar como recuerdo este estudio, por si próximamente tengo que volver a mirarlo (soy un neurótico del guardado de datos xD).

También agradecer a Alejandro Rivas y Fernando Galán por la ayuda e info que me dieron con libros y artículos.

Conceptos necesarios

Un sistema de red en videojuegos implica que los participantes pueden jugar juntos a través de la conexión entre ellos por medio de una red, ya sea una red de área local o el Internet. Esto quiere decir que uno o varios clientes se conectan a un servidor, para intercambiar datos.


/img/c/unet.png
.

En esencia, un cliente le dice al servidor lo que esta realizando y el servidor remite la información para todos los clientes, sincronizando sus estados. Este método se realiza desde cada cliente, haciendo asi que todos los clientes sean sincronizados por el servidor.


/img/c/unet2.png
.

Se le suele llamar a un servidor, servidor dedicado, ya que solo efectúa el cambio de datos y no ejecuta ninguna instancia. Pero también existen otros tipos de servidores. Por ejemplo en Warcraft 3, cualquier cliente puede crear una partida desde su cliente, esto realmente es levantar un servidor para que otros jugadores puedan conectar con el como clientes, a la vez, el servidor también podrá ser un cliente.


/img/c/unet3.png
.

Incluso se puede implementar una capa más, comúnmente llamada matchmaker, que es la instancia de un servidor que congrega las partidas de otros servidores, de esta forma los clientes conectan con el servidor principal, que este a su vez conecta con los demás servidores.


/img/c/unet4.png
.

Empezamos el planteamiento

/img/c/unet5.png
.

Como siempre al final de la entrada dejo el link del repositorio en Github para los que quieran descargarlo. Doy por sentado que ya conocéis Unity y tenéis un conocimiento sobre su funcionamiento.

Lo primero que necesitaremos, sera un Manager para gestionar toda la información de nuestro servidor. Este Manager no puede desaparecer nunca, ya que si lo hace, el servidor y todas las conexiones se caerán.

Crearemos un objeto vació y le agregaremos el componente Network Manager que sera nuestro objeto para administrar la red.


Este componente nos proporcionara mucha funcionalidad necesaria que podríamos escribirla desde cero pero ya que esta y no buscamos un sistema mega optimizado vamos a usarlo. Este componente ya lleva un valor booleano**(Dont destroy on load)** para decirle si queréis que el objeto sea destruido o que no se destruya nunca. También dispone de otro valor**(Run in Background)** para decidir si se ejecutara en segundo plano, Unity si no tiene este valor activado, al salir de Unity o del juego, se queda sin el foco y por lo tanto se pausa, esto seria malo si queremos que nuestros clientes reciban la información a tiempo.

/img/c/unet6.png
.

Ahora vamos a agregarle un nuevo componente, llamado Network Manager HUD. Este componente nos facilita una interfaz con la funcionalidad necesaria para conectar con un servidor, levantar nuestro servidor o activar un matchmaker.


Si construimos ahora mismo la aplicación, veremos como podemos crear dos o tres instancias de Unity y con ello poder conectarlas entre ellas.

Ahora que ya tenemos un administrador de red, tenemos que ver en sus opciones spawn info, una opción de player prefab. Esto nos indica que este cliente tiene que instanciar algo. Pero no se refiere a algo físico, sino que tiene que tener una instancia de jugador en memoria. Digamos que la siguiente manera como vemos en la siguiente imagen.


/img/c/unet7.png
.

Cada jugador tiene que tener una presencia no física, es decir, pongamos el objeto que ponéis como spawn, es un objeto que posteriormente es destruido o desactivado, esto quitaría la presencia del cliente en el servidor, por que no tiene ninguna instancia. Por ello tenemos que realizar una buena praxis.

Creamos un objeto vació, que contendrá la información persistente del cliente/usuario y le agregaremos el componente network identity, que es un componente para obtener la identificación del objeto en la red. Luego crearemos su prefab y la agregaremos al prefab del spawn. Básicamente es un puntero a nuestro objeto de red.

Esto realiza más fácil la relación amor odio de nuestros clientes/servidor.


Representación Dramática

  • Cliente : El jugador 18 tiene que moverse 3 pixeles a la derecha
  • Servidor : Sincronizando …
  • Cliente : Gracias

/img/c/unet8.png
.
/img/c/unet9.png
.

Ahora tenemos un pequeño problema. Si comprobamos cuando iniciamos dos instancias o mas, cada jugador instancia el mismo objeto. Vamos a cambiar eso. He creado un script y se lo he agregado a nuestro prefab de player.

Empezaremos por sincronizar la instancia física del jugador, en nuestro caso un cubo con todas sus caras y vértices.

//                                  ┌∩┐(◣_◢)┌∩┐
//																				\\
// Player.cs (16/07/2018)														\\
// Autor: Antonio Mateo (.\Moon Antonio) 	antoniomt.moon@gmail.com			\\
// Descripcion:		Controller del jugador/Cliente.								\\
// Fecha Mod:		16/07/2018													\\
// Ultima Mod:		Version Inicial												\\
//******************************************************************************\\

#region Librerias
using UnityEngine;
#endregion

namespace MoonAntonio
{
	/// <summary>
	/// <para>Controller del jugador/Cliente.</para>
	/// </summary>
	public class Player : MonoBehaviour 
	{
		#region Variables Publicas
		/// <summary>
		/// <para>Prefab del cubo para instanciar.</para>
		/// </summary>
		public GameObject prefabCubo;
		#endregion

		#region Inicializadores
		/// <summary>
		/// <para>Inicializador de <see cref="Player"/>.</para>
		/// </summary>
		private void Start()
		{
			Instantiate(prefabCubo);
		}
		#endregion
	}
}

Solo tenéis que crear un gameobject, como el modelado o cualquier cubo como el nuestro y veréis su funcionamiento. Como vemos, esto es lo que realizaríamos si queremos crear un objeto en un single player, sin necesidad de ser multijugador, ahora vamos a realizar los cambios para poder crear la red.

Primero agregaremos using UnityEngine.Networking; y heredaremos nuestro script de NetworkBehaviour en vez de MonoBehaviour. Con esto lo que conseguimos es obtener acceso a la funcionalidad Net de Unity.

private void Start()// Inicializador de Player
{
	// Comprobamos si es el objeto local
	if (!isLocalPlayer)
	{
		// Este objeto es de otro jugador
		return;
	}

	// Solo instanciara el cubo si es Local. Es decir, si soy el cliente.
	Instantiate(prefabCubo);
}

De esta manera, si el objeto no es nuestro, no instanciaremos nada, ya que no es nuestra autoridad. Solo podemos darle autoridad sobre los clientes al servidor. Los demás clientes no tienen que tener autoridad sobre ningún cliente que no sea el suyo propio.


/img/c/unet10.png
.

Bien, ahora con todo esto, tenemos que aprender unos nuevos conceptos. ¿Por que la instanciacion debe de realizarse desde el cliente? En realidad no, debería realizarse desde el servidor. Vamos a ver esto en mas profundidad.

En el método Start(), donde teníamos la instanciacion de nuestra unidad, deberíamos borrarla y colocar lo siguiente.

// Decir al servidor que instancie nuestra unidad
CmdSpawnUnidad();

Simplemente indicamos que se llame a un método, lo interesante viene en ese método. Una nota antes de seguir, es ver que el prefijo del método es Cmd, esto es una decisión que se tomo por parte de Unity3D, no se bien por que, pero es un protocolo a seguir si no quieres que te de un error. Todos los métodos que sean llamados desde el servidor deberán llevar el prefijo Cmd.

/// <summary>
/// <para>Le dice al servidor que instancie la unidad.</para>
/// </summary>
[Command]
private void CmdSpawnUnidad()
{
	GInstantiate(prefabCubo);
}

Ahora en nuestro método, vemos como colocamos el atributo [Command], esto indica, que este método sera ejecutado por el servidor, en vez de por nuestro cliente. Por lo que creara el objeto y lo propagara hasta los clientes.

/img/c/unet11.png
.

Al ejecutar os dará un error, esto es por que nuestro objeto Unidad, no tiene un identificador de red, es decir el componente NetworkIdentity. Todos los objetos que van a ser modificados por la red, tienen que tener un identificador.

También todos los objetos tienen que ser agregados en Registered Spawnable Prefabs.


Después tenemos un atributo muy peculiar que puede ser agregado a las variables que necesiten que se sincronicen desde el servidor hasta los clientes. Este atributo es [SyncVar].

/// <summary>
/// <para>Nombre del player.</para>
/// </summary>
[SyncVar] public string nombre = "name";

Ahora tenemos que usar el atributo RPC, que lo que realiza es que cuando llama el servidor a este método, es ejecutado en todos los clientes. Cuando usamos este atributo, también hay que modificar el prefijo con Rpc.

/// <summary>
/// <para>Cambia el nombre del jugador.</para>
/// </summary>
/// <param name="newNombre"></param>
[ClientRpc]
public void RpcCambiarNombre(string newNombre)// Cambia el nombre del jugador
{
	nombre = newNombre;
}

De momento eso es lo principal para poder crear las conexiones entre clientes y servidores. Animo a todo el mundo a descubrir esta especie de magia que seguro que os encantara como al 100% de la gente que aprende y terminareis construyendo e investigando mas y mas. Un saludo.

PD: Hay mucha mas info y cosillas que explicar, pero eso sera otra historia.


GitHub
/img/ref.png
.