Désolé d'avance car je vais devoir écrire un petit pavé, car j'ai des problèmes louches et assez compliqué à expliquer.
Je suis actuellement en train de développer un mini jeu en multijoueur. (Unity 2017.1)
Pour la partie multijoueur, j'utilise le Transport Layer API de Unity.
Mon architecture multijoueur est la suivante:
- Un joueur fait l'hôte
- Des joueurs s'y connecte
Mon 1er problème:
Quand l'hôte/serveur est exécuté dans l'éditeur de Unity:
- Un build du jeu exécuté sur la même machine PEUT s'y connecter via "localhost", "127.0.01"
- Un build du jeu exécuté sur une autre machine NE PEUT PAS s'y connecter via l'ip publique "85.0.xxx.xxx"
Si j'exécute l'hôte/serveur en tant que build (en dehors de Unity), les clients peuvent s'y connecter.
Cela règle mon problème de connexion, et vous me direz, je serais de toute façon dans ce cas lors que le jeu sera release, mais cela me créer un 2ème problème, très très bizarre....
Mon 2ème problème:
Quand l'hôte/serveur est exécuté dans l'éditeur de Unity, le serveur fonctionne nickel, hormis que l'on ne peut pas s'y connecter via l'adresse publique.
Quand l'hôte/serveur est exécuté en tant que build (en dehors de Unity), il y a des parties de codes qui ne sont pas exécutées...
Voici la fonction Start() qui pose apparement soucis:
Code : Tout sélectionner
void Start () {
// Initialisation du Transport Layer
NetworkTransport.Init();
// On récupère le GameManager et le SceneManager
kosGameManager = GameObject.Find("KosGameManager").GetComponent<KosGameManager>();
kosSceneManager = GameObject.Find("KosSceneManager").GetComponent<KosSceneManager>();
// On stock si on est Server ou client
IsServer = kosGameManager.IsHost;
// Si c'est un client on stock l'ip serveur à rejoindre
if (!IsServer)
{
ServerIP = kosGameManager.ServerIP;
}
// On défini la config
ConnectionConfig config = new ConnectionConfig();
unreliableChannelId = config.AddChannel(QosType.UnreliableSequenced);
reliableChannelId = config.AddChannel(QosType.Reliable);
HostTopology topology;
// DEBUG
TextKosDebug.text += "IsServer=" + IsServer + "\n";
if (IsServer)
{
// On défini la topology
topology = new HostTopology(config, 10);
// On créer l'host (on ouvre le socket)
HostId = NetworkTransport.AddHost(topology, ServerPort);
// Créer le joueur local et on récupère son transform
Transform localPlayerTransform = kosSceneManager.CreateLocalPlayer(new Vector3(20, 1.01f, 20));
// On est serveur, on s'ajoute comme client
ClientsList.Add(ClientConnectionId, new KosNetworkClient(ClientConnectionId, kosGameManager.PlayerName, localPlayerTransform));
// On modifie le gamestate
kosGameManager.KosGameState = KosGameManager.KosGameStates.Playing;
// DEBUG
TextKosDebug.text += "Server:HostId: "+ HostId + " , ClientsCount: "+ ClientsList.Count + "\n";
Debug.Log("KosNetwork: Serveur démarré avec client local spawné." + ClientsList.Count +"with connId: "+ ClientConnectionId);
}
else
{
// On défini la topology
topology = new HostTopology(config, 2);
// On créer l'host (on ouvre le socket)
HostId = NetworkTransport.AddHost(topology); // client, sans port, prendra le 1er libre
// On est client on demande la connection
byte error;
ClientConnectionId = NetworkTransport.Connect(HostId, ServerIP, ServerPort, 0, out error);
// On modifie le gamestate
kosGameManager.KosGameState = KosGameManager.KosGameStates.NetworkConnecting;
// DEBUG
TextKosDebug.text += "Client:ClientConnectionId: " + ClientConnectionId + " , ClientsCount: " + ClientsList.Count + "\n";
Debug.Log("KosNetwork: Client démarré avec demande de connexion envoyée à " + ServerIP + ":" + ServerPort + " ("+ ((NetworkError)error).ToString() + ")");
}
}
1. J'initialise NetworkTransport.
2. Je créer la config
3a. Si je suis serveur, j'ouvre un socket, je créer un joueur local, je le stock dans la liste de joueurs
3b. Si je suis joueur, j'ouvre un socket, je demande une connexion au serveur.
La partie qui pose problème est le 3a.
Ci qui se passe vraiment lors de l'exécution:
Le serveur ouvre un socket, puisque les joueurs arrivent à s'y connecter, envoyer et recevoir des messages.
Le serveur spawn son joueur local, car à l'exécution l'hôte voir la scène, s'il n'était pas spawné il n'aurait pas de caméra.
Le serveur n'a pas stocké son joueur local, car la liste est vide tant qu'aucun autre joueur ne s'y connecte, et qu'il ne s'envoie pas quand un joueur reçoit la liste des joueurs.
Donc ce que je trouve ultra bizarre, c'est que d'après mes debug, on dirait que certains lignes de code ne sont pas exécutées, alors qu'elle sont dans la même fonction et le même if etc...
Voilà, j'espère que quelqu'un à une chtite piste car là je commence à sérieusement péter un câble...
Merci les gens!
[EDIT]
Voici le fichier complet, pour plus d'infos.
[EDIT2]
C'est dommage, apparement un modo est repassé derrière moi plusieurs fois pour modifier la balise "quote" en "code".
Je mettais "quote" pour que le code soit lisible avec l'affichage de votre site, mais apparemment c'est mal
Ca parait quand même spécial un forum lié au développement avec l'impossibilité d'afficher du code correctement... (Perte de l'indentation et des retours chariot...)
Perso en relisant mon post affiché en l'état j'aurais aucune envie de tenter de "déchiffrer" le code poster dans la balise "code" pour aider le gars. Pourtant de base je suis le 1er à aider au besoin.
Arf t'façon rien de grave hein , et bizette les gens!
Code : Tout sélectionner
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.UI;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
public class KosNetworkManager : MonoBehaviour {
// Liste des types de messages
public enum KosNetworkMessageTypes
{
CreateNewPlayers,
DeletePlayers,
SetPlayerProfileInfos,
UpdatePlayersPosition,
}
// Liste des chanels
private int unreliableChannelId;
private int reliableChannelId;
// Id de l'ouverture du socket
private int HostId = -1;
// Id de connexion, -99 = client local
public int ClientConnectionId = -99;
// Port du serveur
private int ServerPort = 17777;
// Ip du serveur à rejoindre
private string ServerIP = "";
// Si on est client ou serveur
private bool IsServer = false;
// Références du gamemanager et scenemanager
private KosGameManager kosGameManager;
private KosSceneManager kosSceneManager;
// Liste des joueurs
private Dictionary<int, KosNetworkClient> ClientsList = new Dictionary<int, KosNetworkClient>();
// DEBUG
public Text TextKosDebug;
// Use this for initialization
void Start () {
// Initialisation du Transport Layer
NetworkTransport.Init();
// On récupère le GameManager et le SceneManager
kosGameManager = GameObject.Find("KosGameManager").GetComponent<KosGameManager>();
kosSceneManager = GameObject.Find("KosSceneManager").GetComponent<KosSceneManager>();
// On stock si on est Server ou client
IsServer = kosGameManager.IsHost;
// Si c'est un client on stock l'ip serveur à rejoindre
if (!IsServer)
{
ServerIP = kosGameManager.ServerIP;
}
// On défini la config
ConnectionConfig config = new ConnectionConfig();
unreliableChannelId = config.AddChannel(QosType.UnreliableSequenced);
reliableChannelId = config.AddChannel(QosType.Reliable);
HostTopology topology;
// DEBUG
TextKosDebug.text += "IsServer=" + IsServer + "\n";
if (IsServer)
{
// On défini la topology
topology = new HostTopology(config, 10);
// On créer l'host (on ouvre le socket)
HostId = NetworkTransport.AddHost(topology, ServerPort);
// Créer le joueur local et on récupère son transform
Transform localPlayerTransform = kosSceneManager.CreateLocalPlayer(new Vector3(20, 1.01f, 20));
// On est serveur, on s'ajoute comme client
ClientsList.Add(ClientConnectionId, new KosNetworkClient(ClientConnectionId, kosGameManager.PlayerName, localPlayerTransform));
// On modifie le gamestate
kosGameManager.KosGameState = KosGameManager.KosGameStates.Playing;
// DEBUG
TextKosDebug.text += "Server:HostId: "+ HostId + " , ClientsCount: "+ ClientsList.Count + "\n";
Debug.Log("KosNetwork: Serveur démarré avec client local spawné." + ClientsList.Count +"with connId: "+ ClientConnectionId);
}
else
{
// On défini la topology
topology = new HostTopology(config, 2);
// On créer l'host (on ouvre le socket)
HostId = NetworkTransport.AddHost(topology); // client, sans port, prendra le 1er libre
// On est client on demande la connection
byte error;
ClientConnectionId = NetworkTransport.Connect(HostId, ServerIP, ServerPort, 0, out error);
// On modifie le gamestate
kosGameManager.KosGameState = KosGameManager.KosGameStates.NetworkConnecting;
// DEBUG
TextKosDebug.text += "Client:ClientConnectionId: " + ClientConnectionId + " , ClientsCount: " + ClientsList.Count + "\n";
Debug.Log("KosNetwork: Client démarré avec demande de connexion envoyée à " + ServerIP + ":" + ServerPort + " ("+ ((NetworkError)error).ToString() + ")");
}
}
// Update is called once per frame
void Update()
{
// Id d'hôte (ouverture de socket)
int outHostId;
// Id de connection
int outConnectionId;
// Id de chanel
int outChannelId;
// Buffer
byte[] buffer = new byte[1024];
// Taille du buffer
int bufferSize = 1024;
// Taille reçue
int receiveSize;
// Byte pour le retour d'erreur
byte error;
// Pour le moment aucune idée si on peut avoir plusieur message à traiter par frame, on garde l'idée sous le coude
// TOCKECK
// While NetworkTransport.Receive != NetworkEventType.Nothing ???
NetworkEventType evnt = NetworkTransport.Receive(out outHostId, out outConnectionId, out outChannelId, buffer, bufferSize, out receiveSize, out error);
// On switch sur le type de message reçu
switch (evnt)
{
case NetworkEventType.Nothing: //1
break;
case NetworkEventType.ConnectEvent: //2
if (outHostId == HostId &&
outConnectionId == ClientConnectionId &&
(NetworkError)error == NetworkError.Ok)
{
// Je suis client et j'ai pu me connecter
OnClientConnection(outConnectionId);
}
else if((NetworkError)error == NetworkError.Ok)
{
// Je suis serveur et je reçois une nouvelle connection
OnServerConnection(outConnectionId);
}
else
{
Debug.Log("Connect event goes wrong");
}
break;
case NetworkEventType.DataEvent: //3
Stream stream = new MemoryStream(buffer);
BinaryFormatter formatter = new BinaryFormatter();
OnReceiveMessage(outConnectionId, formatter.Deserialize(stream) as string);
break;
case NetworkEventType.DisconnectEvent: //4
if (outHostId == HostId &&
outConnectionId == ClientConnectionId)
{
// Je suis client et je me fais déconnecter
OnClientDisconnection(outConnectionId);
}
else
{
// Je suis serveur et je reçois une déconnection
OnServerDisconnection(outConnectionId);
}
break;
default:
Debug.Log("Unhandled KosNetworkEvent: IsServer " + IsServer.ToString());
break;
}
}
// vraiment utile?
/*void OnApplicationQuit()
{
DisconnectNetwork();
}*/
// Si on détruit ce gameobject
void OnDestroy()
{
// On déconnecte tout
DisconnectNetwork();
}
// Fermeture des connexions et net
private void DisconnectNetwork()
{
// On supprime l'hôte et les connexions associées
NetworkTransport.RemoveHost(HostId);
// On ferme networktransport
NetworkTransport.Shutdown();
}
// Quand le serveur reçois une nouvelle connexion client
private void OnServerConnection(int connId)
{
TextKosDebug.text += "ServerConnection: ConnId=" + connId + "\n";
if (ClientsList.ContainsKey(connId))
{
Debug.Log("OnServerConnection: Le joueur existe déjà.");
}
else
{
Debug.Log("OnServerConnection: Nouvelle connexion (Id:" + connId + ")");
// On ajoute le joueur à la liste
ClientsList.Add(connId, new KosNetworkClient(connId));
}
}
// Quand le serveur reçois une déconnexion client
private void OnServerDisconnection(int connId)
{
TextKosDebug.text += "ServerDisconnection: ConnId=" + connId + "\n";
Debug.Log("OnServerDisconnection: Connexion fermée (Id:" + connId + ")");
// On supprime le joueur
RemovePlayer(connId);
}
// Quand le client se connecte sur le serveur
public void OnClientConnection(int connId)
{
TextKosDebug.text += "ClientConnection: ConnId=" + connId + "\n";
Debug.Log("OnClientConnection: Connecté au serveur (Id:" + connId + ")");
// Envoi de notre nom de joueur pour le profil
ClientSendMessage(KosNetworkMessageTypes.SetPlayerProfileInfos, kosGameManager.PlayerName);
}
// Quand le client se déconnecte du serveur
public void OnClientDisconnection(int connId)
{
TextKosDebug.text += "ClientDisconnection: ConnId=" + connId + "\n";
Debug.Log("OnClientDisconnection: Deconnecté du serveur (Id:" + connId + ")");
// On déconnecte tout
DisconnectNetwork();
// GOBACK à la scène du menu
kosGameManager.GoBackToMenuFromInGame();
}
// Quand on a reçu un message
public void OnReceiveMessage(int connId, string msg)
{
// On split le message par '|' , en 0 c'est le type de message, en 1 le message
string[] tmpInfos = msg.Split('|');
// On récupère le type de message
KosNetworkMessageTypes msgType = (KosNetworkMessageTypes)int.Parse(tmpInfos[0]);
// Si on est serveur
if(IsServer)
{
// Serveur
OnServerReceiveMessage(connId, msgType, tmpInfos[1]);
}
else
{
// Client
OnClientReceiveMessage(connId, msgType, tmpInfos[1]);
}
}
// Quand le serveur reçois un message
private void OnServerReceiveMessage(int connId, KosNetworkMessageTypes msgType, string msg)
{
switch (msgType)
{
case KosNetworkMessageTypes.SetPlayerProfileInfos:
Debug.Log("SetPlayerinfos: Called ("+ ClientsList.Count + ")");
TextKosDebug.text += "SetPlayerinfos: ConnId="+ connId + " , Name=" + msg +"\n";
// Si le joueur existe
if (ClientsList.ContainsKey(connId))
{
// On récupère le nom du joueur
ClientsList[connId].PlayerName = msg;
// On défini la position de spawn
Vector3 spawnPosition = new Vector3(20, 1.01f, 20);
// On spawn le joueur et on stock le transform et le coplayercontroller
ClientsList[connId].PlayerTransform = kosSceneManager.CreateNetPlayer(connId, spawnPosition);
ClientsList[connId].kosCoPlayerController = ClientsList[connId].PlayerTransform.GetComponent<KosCoPlayerController>();
// On envoi à tous les joueurs de créer le nouveau joueur
ServerSendMessage(KosNetworkMessageTypes.CreateNewPlayers,
connId + ";" + ClientsList[connId].PlayerName + ";" + spawnPosition.x + ":" + spawnPosition.y + ":" + spawnPosition.z);
// On envoi au nouveau joueur la liste de tous les joueurs (sans le compter lui)
ServerSendMessageTo(KosNetworkMessageTypes.CreateNewPlayers,
GetPlayersListWithoutNewPlayer(connId), connId);
Debug.Log("SetPlayerinfos: Joueur ajouté");
}
else
{
Debug.Log("SetPlayerinfos: Le joueur n'existe pas");
}
break;
case KosNetworkMessageTypes.UpdatePlayersPosition:
// On split la position
string[] plPos = msg.Split(':');
// On récupère la position
Vector3 plPosi = new Vector3(float.Parse(plPos[0]), float.Parse(plPos[1]), float.Parse(plPos[2]));
// On update la position du joueur sur le serveur
if (ClientsList.ContainsKey(connId))
{
ClientsList[connId].kosCoPlayerController.UpdatePosition(plPosi);
// Message à transmettre
string newMsg = connId + ";" + msg;
// On envoi le changement de position à tous les joueurs (Sauf le local et l'envoyeur)
ServerSendMessageExceptTo(KosNetworkMessageTypes.UpdatePlayersPosition, newMsg, connId);
}
break;
default:
Debug.Log("Le serveur a reçu un message non traité: " + msgType.ToString() + ":" + msg);
break;
}
}
// Quand le client reçois un message
private void OnClientReceiveMessage(int connId, KosNetworkMessageTypes msgType, string msg)
{
switch (msgType)
{
case KosNetworkMessageTypes.CreateNewPlayers:
TextKosDebug.text += "CreateNewPlayers: ConnId="+connId+" , msg: "+ msg + "\n";
// On split pour avoir la liste des joueurs
string[] newPInfos = msg.Split('/');
// On boucle sur la liste
for (int i = 0; i < newPInfos.Length; i++)
{
// On split les infos
string[] infos = newPInfos[i].Split(';');
// On récupère l'id de connexion
// Convert.ToInt32(yourString);
int pConnId = int.Parse(infos[0]);
// On récupère le nom du joueur
string pName = infos[1];
// On split la position
string[] pPos = infos[2].Split(':');
// On récupère la position
Vector3 pPosition = new Vector3(float.Parse(pPos[0]), float.Parse(pPos[1]), float.Parse(pPos[2]));
// Init de la variable du transform joueur
Transform pTransform;
if (pConnId == ClientConnectionId)
{
// On créer son propre joueur
pTransform = kosSceneManager.CreateLocalPlayer(pPosition);
// On modifie le gamestate
kosGameManager.KosGameState = KosGameManager.KosGameStates.Playing;
}
else
{
// On créer un nouveau joueur
pTransform = kosSceneManager.CreateNetPlayer(pConnId, pPosition);
}
// On ajoute le joueur
ClientsList.Add(pConnId, new KosNetworkClient(pConnId, pName, pTransform));
}
break;
case KosNetworkMessageTypes.DeletePlayers:
TextKosDebug.text += "CreateNewPlayers: ConnId=" + connId + " , msg: " + msg + "\n";
// On split pour avoir la liste des joueurs
string[] playersToDelete = msg.Split('/');
// On boucle sur la liste
for(int i = 0; i < playersToDelete.Length; i++)
{
// On récupère l'id de connexion
int connIdTodel = int.Parse(playersToDelete[i]);
// On supprime le joueur
RemovePlayer(connIdTodel);
}
break;
case KosNetworkMessageTypes.UpdatePlayersPosition:
// On split pour avoir la liste des joueurs
string[] playersPos = msg.Split('/');
// On boucle sur la liste
for (int i = 0; i < playersPos.Length; i++)
{
// Split pour séprarer l'id de connexion de la position
string[] plPosTmp = playersPos[i].Split(';');
// Récupère l'id de connexion
int tmpConnId = int.Parse(plPosTmp[0]);
// Split la position
string[] plPos = plPosTmp[1].Split(':');
// Défini la position
Vector3 plPosi = new Vector3(float.Parse(plPos[0]), float.Parse(plPos[1]), float.Parse(plPos[2]));
// Si le joueur existe
if(ClientsList.ContainsKey(tmpConnId))
{
// On update la position du joueur
ClientsList[tmpConnId].kosCoPlayerController.UpdatePosition(plPosi);
}
}
break;
default:
Debug.Log("Le client a reçu un message non traité: " + msgType.ToString() + ":" + msg);
break;
}
}
// Envoi d'un message par un client au serveur
public void ClientSendMessage(KosNetworkMessageTypes msgType, string msg)
{
byte error;
byte[] bytes = new byte[1024];
Stream stream = new MemoryStream(bytes);
BinaryFormatter formatter = new BinaryFormatter();
formatter.Serialize(stream, (int)msgType + "|" + msg);
int channelId = GetChanel(msgType);
NetworkTransport.Send(HostId, ClientConnectionId, channelId, bytes, bytes.Length, out error);
}
// Envoi d'un message par le serveur à tous les clients
public void ServerSendMessage(KosNetworkMessageTypes msgType, string msg)
{
byte error;
byte[] bytes = new byte[1024];
Stream stream = new MemoryStream(bytes);
BinaryFormatter formatter = new BinaryFormatter();
formatter.Serialize(stream, (int)msgType + "|" + msg);
int channelId = GetChanel(msgType);
foreach (KeyValuePair<int, KosNetworkClient> netClient in ClientsList)
{
if (netClient.Value.ConnectionId == -99)
{
// Client local du serveur
continue;
}
NetworkTransport.Send(HostId, netClient.Value.ConnectionId, channelId, bytes, bytes.Length, out error);
}
}
// Envoi d'un message par le serveur à un client
public void ServerSendMessageTo(KosNetworkMessageTypes msgType, string msg, int connId)
{
byte error;
byte[] bytes = new byte[1024];
Stream stream = new MemoryStream(bytes);
BinaryFormatter formatter = new BinaryFormatter();
formatter.Serialize(stream, (int)msgType + "|" + msg);
int channelId = GetChanel(msgType);
foreach (KeyValuePair<int, KosNetworkClient> netClient in ClientsList)
{
// L'id de connection correspond à l'id reçu
if(netClient.Value.ConnectionId == connId)
{
NetworkTransport.Send(HostId, netClient.Value.ConnectionId, channelId, bytes, bytes.Length, out error);
break;
}
}
}
// Envoi d'un message par le serveur à tous les clients sauf celui donné en paramètre
public void ServerSendMessageExceptTo(KosNetworkMessageTypes msgType, string msg, int connId)
{
byte error;
byte[] bytes = new byte[1024];
Stream stream = new MemoryStream(bytes);
BinaryFormatter formatter = new BinaryFormatter();
formatter.Serialize(stream, (int)msgType + "|" + msg);
int channelId = GetChanel(msgType);
foreach (KeyValuePair<int, KosNetworkClient> netClient in ClientsList)
{
if (netClient.Value.ConnectionId == -99 || netClient.Value.ConnectionId == connId)
{
// Client local du serveur
continue;
}
NetworkTransport.Send(HostId, netClient.Value.ConnectionId, channelId, bytes, bytes.Length, out error);
}
}
// Obtient l'id de chanel correspondant au type de message
private int GetChanel(KosNetworkMessageTypes msgType)
{
switch (msgType)
{
case KosNetworkMessageTypes.CreateNewPlayers:
return reliableChannelId;
case KosNetworkMessageTypes.DeletePlayers:
return reliableChannelId;
case KosNetworkMessageTypes.SetPlayerProfileInfos:
return reliableChannelId;
case KosNetworkMessageTypes.UpdatePlayersPosition:
return unreliableChannelId;
default:
return reliableChannelId;
}
}
// Retourne la liste des clients sauf celui indiqué, sous forme "connId;pName;pos.x:pos.y:pos.z/...."
private string GetPlayersListWithoutNewPlayer(int newPlayerConnId)
{
string pList = "";
int countor = 1;
foreach (KeyValuePair<int , KosNetworkClient> netClient in ClientsList)
{
if(newPlayerConnId == netClient.Value.ConnectionId)
{
continue;
}
pList += netClient.Value.ConnectionId +";"+ netClient.Value.PlayerName +";"+ netClient.Value.PlayerTransform.position.x +":"+ netClient.Value.PlayerTransform.position.y +":"+ netClient.Value.PlayerTransform.position.z;
if(countor < ClientsList.Count)
{
pList += "/";
}
countor++;
}
// Si le dernier de la liste était la connection à ne pas retourner on fini avec un slash en trop
if(pList.EndsWith("/"))
{
pList = pList.Substring(0, pList.Length - 1);
}
return pList;
}
// Supprime un joueur Internet
private void RemovePlayer(int connId)
{
// On supprime le gameobject du joueur
GameObject.Destroy(ClientsList[connId].PlayerTransform.gameObject);
// On supprime le joueur de la liste des joueurs
ClientsList.Remove(connId);
// Si on est serveur
if(IsServer)
{
// Envoie le message aux autres clients de supprimer le joueur
ServerSendMessageExceptTo(KosNetworkMessageTypes.DeletePlayers, connId.ToString(), connId);
}
}
// Transmet le mouvement du joueur
// S'il est client, au serveur
// S'il est serveur, aux clients
public void SendLocalPlayerMovementUpdate(Vector3 newPosition)
{
if(IsServer)
{
ServerSendMessage(KosNetworkMessageTypes.UpdatePlayersPosition, ClientConnectionId + ";" + newPosition.x + ":" + newPosition.y + ":" + newPosition.z);
}
else
{
ClientSendMessage(KosNetworkMessageTypes.UpdatePlayersPosition, newPosition.x +":"+ newPosition.y +":"+ newPosition.z);
}
}
}