Dans ce tutorial, nous allons créer une application Multi-Utilisateurs permettant de déplacer une sorte d'avatar sur l'écran.
Vous pouvez directement observer le résultat de ce tutorial
en ligne.
Dans un premier temps, nous allons nous occuper de la partie serveur toujours basée sur le serveur de
ce tutorial. Nos explications seront en php puisque il s'agit du langage le plus rependu.
L'équivalent en perl est aussi disponible au bas de cette page. Globalement, le perl et le php sont assez proches pour pouvoir passer de l'un a l'autre facilement.
Voici donc la base de notre serveur :
<?php
$mainsock = socket_create(AF_INET, SOCK_STREAM, 0);
socket_set_option($mainsock, SOL_SOCKET,SO_REUSEADDR, 1);
socket_bind($mainsock, "127.0.0.1", 22222) or die('Could not bind to address');
socket_listen($mainsock);
///////////////////
$clients=Array();
$compteur=0;
echo("En attente d'un client !\n");
while(true){
$toread=Array();
array_push($toread,$mainsock);
for ($i=0;$i<count($clients);$i++){ // pour tous les clients
array_push($toread,$clients[$i]["SOCKET"]);
}
socket_select($toread,$a=null,$a=null,$a=null);
if(in_array($mainsock, $toread)){// le mainsock est dans le tableau $toread.
// c'est notre mainsock donc un nouveau client
$sock=socket_accept($mainsock);
echo("Arrivée d'un nouveau client !\n");
$compteur++;
$nb=count($clients);
$clients[$nb]["SOCKET"]=$sock;
$clients[$nb]["UID"]=$compteur;
//////////////////////////////////////////////////Arrivée d'un client
//////////////////////////////////////////////////
}else{
// c'est un client qui dit quelque chose
for ($i=0;$i<count($clients);$i++){ // on cherche le client
if(in_array($clients[$i]["SOCKET"], $toread)) { // celui la est dans le tableau toread
$input = socket_read($clients[$i]["SOCKET"], 1024);
if($input==null){
/// deconnection du client !
for ($j=0;$j<count($clients);$j++){ // on le cherche dans le tableau
if($clients[$j]==$clients[$i]){ // trouvé
//////////////////////////////////////////////////Perte d'un client
//////////////////////////////////////////////////
echo("Deconnection du client ".$clients[$j]["UID"]."\n");
array_splice($clients,$j,1); // on le retire du tableau
$i--;
}
}
}else{
//////////////////////////////////////////////////Message d'un client
//////////////////////////////////////////////////
}
}
}
}
}
function sendToAll($sauf,$msg){
global $clients;
for($i=0;$i<count($clients);$i++){ // pour tous les clients sauf celui qui envoie
if($sauf!=$clients[$i]["UID"]){
socket_write($clients[$i]["SOCKET"],$msg.chr(0));
}
}
}
php?>
Dans le code ci-dessus nous avons mis en évidence (séparés par des ////) les trois zones dans lesquelles nous allons travailler. Ces trois zones représentent respectivement : la gestion d'arrivée d'un client, de déconnection et de réception de message.
A l'initialisation, le client se connecte, choisis (aléatoirement pour simplifier) la couleur de son avatar, transmet au serveur le pseudo choisis par l'internaute ainsi que sa position (initialement définie de façon aléatoire).
Dans notre cas, on ne fait rien quand le client se connecte. On va attendre qu'il soit "initialisé" avec son nom, sa couleur et sa position pour en informer les autres clients. Nous utiliserons alors la fonction déclarée
sendToAll().
Nous allons donc traiter le message des clients. Chaque client nous envoie un message quand il s'initialise avec son nom, sa couleur et sa position ou quand il se déplace à l'écran.. Pour faire la distinction entre chaque commande, chaque message d'un client serra composé de l'action à faire suivie par des paramètres et le tout séparé par un caractère spécial (nous choisissons le signe "=").
Il nous faut d'abord décider du format des messages envoyés par un client. Voici un exemple des messages que le serveur peut recevoir :
INIT=1256=toto=152=168Le client transmettra donc toutes ses informations quand il serra prêt à être affiché sur l'écran des autres. Ces informations seront enregistrées par le serveur sous forme de variables dans l'objet client afin que si un autre se connecte, on puisse lui transmettre toutes les informations des autres clients déjà en ligne. Le premier paramètre (1256) est donc la couleur de l'avatar. Le second (toto) est son pseudo. Le troisième et quatrième sont la sa position en X et Y.
CLICK=10=208Un client viens de cliquer sur la scène pour se déplacer. Les deux paramètres sont la position en X et Y.
Ensuite les messages que le serveur peut envoyer aux clients :
NEWONE=56=12556=toto=208=158On informe le client qu'un nouveau viens d'arriver. Le premier paramètre est son numéro "UID". Le second est sa couleur. Le troisième est son pseudo. Le quatrième et le cinquième sont sa position en X et Y.
CLICK=56=208=158On informe les clients qu'un client viens de cliquer. Le premier paramètre est son numéro "UID". Le second et le troisième sont sa position en X et Y.
LOSTONE=56On informe les clients qu'un client viens de se déconnecter. Le premier et unique paramètre est le numéro "UID" du client en question.
Vous remarquerez que l'on utilise souvent le "UID". L'intérêt est d'être sur que chaque client à un "UID" unique contrairement au pseudo sur lequel nous ne ferons pas de vérification ici. La bande passante est aussi économisée puisque le "UID" est généralement plus court. Les informations transmises son donc plus rapides.
Le message du client se trouve donc dans la variable
$input. Nous allons décomposer cette variable afin d'en connaître les composants et tester de quelle action il s'agit :
$comp=split("=", $input);
if($comp[0]=="INIT"){
}else if($comp[0]=="CLICK"){
}
Nous avons ici un léger problème. Comme expliqué dans
ce post , flash utilise un byte 0 pour terminer ses messages. Ce caractère de fin se retrouve donc dans notre variable
$input. D'une manière générale, nous allons retirer tous caractères de fin envoyé par les clients. Pour cela nous allons utiliser la fonction php
trim() :
$input=trim($input);
$comp=split("=", $input);
if($comp[0]=="INIT"){
}else if($comp[0]=="CLICK"){
}
Il ne nous reste plus qu'a traiter chaque action. L'action "INIT" doit dans l'ordre, transmettre au client la liste de tous les autres connectés, mémoriser les données dans les variables du client, transmettre aux autres l'information d'arrivée de ce client. L'action "CLICK" doit modifier les variables de positions du client et transmettre ses nouvelles coordonnées aux autres clients.
$input=trim($input);
$comp=split("=", $input);
if($comp[0]=="INIT"){
//// on envoie la liste de ceux deja connectés
for($k=0;$k<count($clients);$k++){ // tous les clients
if($clients[$k]["INIT"]){ // seulement s'il est initialisé
socket_write($clients[$i]["SOCKET"],"NEWONE=".$clients[$k]["UID"]."=".$clients[$k]["COLOR"]."=".$clients[$k]["PSEUDO"]."=".$clients[$k]["X"]."=".$clients[$k]["Y"].chr(0));
}
}
//// on mémorise les variables de ce client
$clients[$i]["INIT"]=true; // on mémorise que ce client est initialisé
$clients[$i]["COLOR"]=$comp[1];
$clients[$i]["PSEUDO"]=$comp[2];
$clients[$i]["X"]=$comp[3];
$clients[$i]["Y"]=$comp[4];
/// on informe les autres
sendToAll($clients[$i]["UID"],"NEWONE=".$clients[$i]["UID"]."=".$clients[$i]["COLOR"]."=".$clients[$i]["PSEUDO"]."=".$clients[$i]["X"]."=".$clients[$i]["Y"]);
}else if($comp[0]=="CLICK"){
$clients[$i]["X"]=$comp[1];
$clients[$i]["Y"]=$comp[2];
sendToAll($clients[$i]["UID"],"CLICK=".$clients[$i]["UID"]."=".$clients[$i]["X"]."=".$clients[$i]["Y"]);
}
Nous devons aussi penser à informer tous les clients si l'un d'entre nous se déconnecte. Donc dans la zone "Perte d'un client" nous placerons le code qui permet d'envoyer l'information à tous le monde q'un client s'est déconnecté. Pour savoir lequel, nous y joindrons le numéro "UID" du client en question :
sendToAll($clients[$i]["UID"],"LOSTONE=".$clients[$i]["UID"]);
Il nous faut légèrement modifier la fonction
sendToAll() afin qu'elle n'envoie de message q'au clients déjà initialisés :
function sendToAll($sauf,$msg){
global $clients;
for($i=0;$i<count($clients);$i++){ // pour tous les clients sauf celui qui envoie
if($sauf!=$clients[$i]["UID"] && $clients[$i]["INIT"]){
socket_write($clients[$i]["SOCKET"],$msg.chr(0));
}
}
}
Le code du serveur est donc :
<?php
$mainsock = socket_create(AF_INET, SOCK_STREAM, 0);
socket_set_option($mainsock, SOL_SOCKET,SO_REUSEADDR, 1);
socket_bind($mainsock, "127.0.0.1", 22222) or die('Could not bind to address');
socket_listen($mainsock);
///////////////////
$clients=Array();
$compteur=0;
echo("En attente d'un client !\n");
while(true){
$toread=Array();
array_push($toread,$mainsock);
for ($i=0;$i<count($clients);$i++){ // pour tous les clients
array_push($toread,$clients[$i]["SOCKET"]);
}
socket_select($toread,$a=null,$a=null,$a=null);
if(in_array($mainsock, $toread)){// le mainsock est dans le tableau $toread.
// c'est notre mainsock donc un nouveau client
$sock=socket_accept($mainsock);
echo("Arrivée d'un nouveau client !\n");
$compteur++;
$nb=count($clients);
$clients[$nb]["SOCKET"]=$sock;
$clients[$nb]["UID"]=$compteur;
//////////////////////////////////////////////////Arrivée d'un client
//////////////////////////////////////////////////
}else{
// c'est un client qui dit quelque chose
for ($i=0;$i<count($clients);$i++){ // on cherche le client
if(in_array($clients[$i]["SOCKET"], $toread)) { // celui la est dans le tableau toread
$input = socket_read($clients[$i]["SOCKET"], 1024);
if($input==null){
/// deconnection du client !
for ($j=0;$j<count($clients);$j++){ // on le cherche dans le tableau
if($clients[$j]==$clients[$i]){ // trouvé
//////////////////////////////////////////////////Perte d'un client
sendToAll($clients[$i]["UID"],"LOSTONE=".$clients[$i]["UID"]);
//////////////////////////////////////////////////
echo("Deconnection du client ".$clients[$j]["UID"]."\n");
array_splice($clients,$j,1); // on le retire du tableau
$i--;
}
}
}else{
//////////////////////////////////////////////////Message d'un client
$input=trim($input);
$comp=split("=", $input);
if($comp[0]=="INIT"){
//// on envoie la liste de ceux deja connectés
for($k=0;$k<count($clients);$k++){ // tous les clients
if($clients[$k]["INIT"]){ // seulement s'il est initialisé
socket_write($clients[$i]["SOCKET"],"NEWONE=".$clients[$k]["UID"]."=".$clients[$k]["COLOR"]."=".$clients[$k]["PSEUDO"]."=".$clients[$k]["X"]."=".$clients[$k]["Y"].chr(0));
}
}
//// on mémorise les variables de ce client
$clients[$i]["INIT"]=true; // on mémorise que ce client est initialisé
$clients[$i]["COLOR"]=$comp[1];
$clients[$i]["PSEUDO"]=$comp[2];
$clients[$i]["X"]=$comp[3];
$clients[$i]["Y"]=$comp[4];
/// on informe les autres
sendToAll($clients[$i]["UID"],"NEWONE=".$clients[$i]["UID"]."=".$clients[$i]["COLOR"]."=".$clients[$i]["PSEUDO"]."=".$clients[$i]["X"]."=".$clients[$i]["Y"]);
}else if($comp[0]=="CLICK"){
$clients[$i]["X"]=$comp[1];
$clients[$i]["Y"]=$comp[2];
sendToAll($clients[$i]["UID"],"CLICK=".$clients[$i]["UID"]."=".$clients[$i]["X"]."=".$clients[$i]["Y"]);
}
//////////////////////////////////////////////////
}
}
}
}
}
function sendToAll($sauf,$msg){
global $clients;
for($i=0;$i<count($clients);$i++){ // pour tous les clients sauf celui qui envoie
if($sauf!=$clients[$i]["UID"] && $clients[$i]["INIT"]){
socket_write($clients[$i]["SOCKET"],$msg.chr(0));
}
}
}
php?>
A ce stade le serveur est prêt. Nous allons maintenant nous occuper du client flash. Pour faire simple, nous utiliserons une seule image clé qui contiendra le code et affichera notre interface. Nous allons reprendre le code
de ce tutorial :
var socket = new XMLSocket()
socket.connect("127.0.0.1", 22222)
socket.onConnect = function(success){
}
socket.onData=function(msg){
}
Vous trouverez le fla (fla_etape_1.fla) à cette étape avec les éléments graphiques déjas nommés et installés en bas de la page. Nous allons indiquer dans le
onConnect() si la connection s'est bien effectuée ou non :
socket.onConnect = function(success){
if(success){
txt_info.text="Connecté sur le port 22222 !"
}else{
txt_info.text="Impossible de se connecter au port 22222 !";
}
}
De la même façon, nous allons alerter l'internaute si la connection est perdue :
socket.onClose = function(){
txt_info.text="Connection perdue !"
}
Nous allons déterminer la couleur de ce client. Nous utiliserons la fonction
random() :
var selfColor=random(0xFFFFFF);
Dans un premier temps, l'internaute doit choisir son pseudo. Nous allons alors directement accéder au clip
choix_pseudo pour en programmer l'évènement de validation. Quand l'internaute clique sur "Entrer", il faut tester q'il ait bien tapé quelque chose dans le champ de texte. Si c'est le cas, on demande au serveur de l'initialiser et on rend invisible le clip
choix_pseudo :
choix_pseudo.bt_valide.onRelease=function(){
if(choix_pseudo.txt_pseudo.text.length>0){
choix_pseudo._visible=false
var Xpos=random(550)
var Ypos=random(400)
socket.send("INIT="+selfColor+"="+escape(choix_pseudo.txt_pseudo.text)+"="+Xpos+"="+Ypos)
}
}
Notez la présence de la fonction
escape(). Imaginez ce qui se passe si le client désire se nommer "=-moi-=". Le signe "=" du pseudo entrerait en conflit avec notre signe "=" choisis pour séparer les variables. Ainsi on est sur que le message est toujours bien formaté.
A cet instant, le serveur va nous renvoyer la liste de tous les clients connectés. Il nous faut donc nous préparer à les recevoir et à les mémoriser. Nous allons d'abord initialiser un tableau qui contiendra la liste de tous les clients. Chaque client sera en fait un objet qui contiendra toutes les variables propres à ce client :
var clients=new Array();
Pour simplifier, nous allons créer une fonction qui permettra simplement de créer un client. Cette fonction recevra le "UID", la couleur, le pseudo et la position du client. Elle en créera alors l'avatar et ajoutera l'objet au tableau
clients. Pour créer l'avatar, nous utiliserons la fonction de flash
attachMovie(). Nous pourrions utiliser le "UID" du client comme étant le niveau de profondeur du clip, mais nous choisissons de créer une variable
compteur qui s'incrémentera à chaque nouveau clients. Cette variable donnera aussi le niveau de profondeur des croix (indicateur de click de souris défini plus loin) :
var compteur=0;
function addClient(uid,couleur,pseudo,xpos,ypos){
compteur++;
/// le client
var obj=new Object();
obj.UID=uid
obj.COLOR=couleur
obj.PSEUDO=pseudo
obj.X=xpos
obj.Y=ypos
clients.push(obj)
/// l'avatar
obj.CLIP=attachMovieClip("avatar","avatar_"+compteur,compteur);
obj.CLIP._x=xpos
obj.CLIP._y=ypos
obj.CLIP.txt_pseudo.text=pseudo
var acol=new Color(obj.CLIP.cercle)
acol.setRGB(couleur);
}
Quand on s'initialise, le serveur nous renvoie la liste de tous les clients sauf nous même. Il faut donc que l'on se crée notre propre avatar. N'ayant pas de UID, nous allons considérer que notre avatar porte le numéro 0. Plus tard, si l'on désire accéder à notre propre avatar, il suffit d'accéder au premier client du tableau
clients ou de cherche celui dont le "UID" est de 0 :
choix_pseudo.bt_valide.onRelease=function(){
if(choix_pseudo.txt_pseudo.text.length>0){
choix_pseudo._visible=false
var Xpos=random(550)
var Ypos=random(400)
socket.send("INIT="+selfColor+"="+escape(choix_pseudo.txt_pseudo.text)+"="+Xpos+"="+Ypos)
addClient(0,selfColor,choix_pseudo.txt_pseudo.text,Xpos,Ypos)
}
}
Comme nous avons une fonction
addClient(), il nous faut aussi une fonction
removeClient() pour supprimer l'avatar et retirer le client du tableau
clients. Pour cette fonction, il suffit de lui passer le numéro "UID" du client à retirer :
function removeClient(uid){
/// on cherche le client
for(var i=0;i<clients.length;i++){
if(clients[i].UID==uid){
clients[i].CLIP.removeMovieClip()
clients.splice(i,1);
break;
}
}
}
Maintenant nous allons donner la possibilité au client de se déplacer. Nous allons utiliser le clip "fond" pour intercepter les évènements souris. Le plus simple ici est de créer une fonction
userClick() qui serra appelée quand l'un des clients distant ou l'internaute lui même clique sur la scène. Cette fonction placera une petite croix de la couleur du client (qui ne reste que quelques secondes) sur la scène et mettra à jour les variables de positions dans l'objet du client :
function userClick(uid,xpos,ypos){
/// on cherche le client
for(var i=0;i<clients.length;i++){
if(clients[i].UID==uid){
clients[i].X=xpos
clients[i].Y=ypos
compteur++;
var croix=attachMovie("croix","croix_"+compteur,compteur);
croix._x=xpos
croix._y=ypos
var ccoul=new Color(croix);
ccoul.setRGB(clients[i].COLOR);
}
}
}
Quand on clique sur le fond de la scène, il faut alors appelée cette fonction en donnant un "UID" de 0 (comme expliqué plus haut quand on crée l'avatar) et envoyer l'info au serveur :
fond.onRelease=function(){
userClick(0,_xmouse,_ymouse);
socket.send("CLICK="+_xmouse+"="+_ymouse);
}
Notez alors, que la main de la souris apparaît sur le fond de la scène. Pour éviter cela quand on est pas encore "initialisé", nous allons rendre le clip fond invisible au début du swf et nous le rendrons visible que l'ors que l'internaute aura entré son pseudo :
fond._visible=false
choix_pseudo.bt_valide.onRelease=function(){
if(choix_pseudo.txt_pseudo.text.length>0){
choix_pseudo._visible=false
var Xpos=random(550)
var Ypos=random(400)
socket.send("INIT="+selfColor+"="+escape(choix_pseudo.txt_pseudo.text)+"="+Xpos+"="+Ypos)
fond._visible=true
}
}
Nous allons maintenant mettre en place le moteur de mouvement. Il s'agit en fait d'un gestionnaire
onEnterFrame qui parcours la liste des clients et qui déplace le clip de l'avatar jusqu'a la position X et Y définie dans l'objet client :
onEnterFrame=function(){
for(var i=0;i<clients.length;i++){
if(clients[i].CLIP._x>clients[i].X){clients[i].CLIP._x--}
if(clients[i].CLIP._x<clients[i].X){clients[i].CLIP._x++}
if(clients[i].CLIP._y>clients[i].Y){clients[i].CLIP._y--}
if(clients[i].CLIP._y<clients[i].Y){clients[i].CLIP._y++}
}
}
Nous allons maintenant nous occuper de ce que le serveur nous envoie. Comme pour le serveur, il nous faut d'abord décomposer le message afin d'en connaître l'action :
socket.onData=function(msg){
var comp=msg.split("=");
if(comp[0]=="NEWONE"){
}else if(comp[0]=="LOSTONE"){
}else if(comp[0]=="CLICK"){
}
}
Il suffit maintenant d'appeler les fonctions déjà existantes pour chaque action :
socket.onData=function(msg){
var comp=msg.split("=");
if(comp[0]=="NEWONE"){
addClient(comp[1],comp[2],unescape(comp[3]),comp[4],comp[5]);
}else if(comp[0]=="LOSTONE"){
removeClient(comp[1]);
}else if(comp[0]=="CLICK"){
userClick(comp[2],comp[3],comp[1]);
}
}
Remarquez ici la présence de la fonction
unescape(). Souvenez-vous que pour être sur que le choix hasardeux du pseudo d'un client ne perturbe pas le format de nos messages, nous utilisions la fonction
escape(). Pour récupérer le pseudo au bon format, nous utilisons donc l'inverse qui est bien
unescape().
Et voila :-)
Au final, le code sur la première clé ressemble à ceci :
var compteur=0;
var clients=new Array();
var socket = new XMLSocket()
var selfColor=random(0xFFFFFF);
fond._visible=false
socket.connect("127.0.0.1", 22222)
socket.onConnect = function(success){
if(success){
txt_info.text="Connecté sur le port 22222 !"
}else{
txt_info.text="Impossible de se connecter au port 22222 !";
}
}
socket.onClose = function(){
txt_info.text="Connection perdue !"
}
socket.onData=function(msg){
var comp=msg.split("=");
if(comp[0]=="NEWONE"){
addClient(comp[1],comp[2],unescape(comp[3]),comp[4],comp[5]);
}else if(comp[0]=="LOSTONE"){
removeClient(comp[1]);
}else if(comp[0]=="CLICK"){
userClick(comp[1],comp[2],comp[3]);
}
}
choix_pseudo.bt_valide.onRelease=function(){
if(choix_pseudo.txt_pseudo.text.length>0){
choix_pseudo._visible=false
var Xpos=random(550)
var Ypos=random(400)
socket.send("INIT="+selfColor+"="+escape(choix_pseudo.txt_pseudo.text)+"="+Xpos+"="+Ypos)
addClient(0,selfColor,choix_pseudo.txt_pseudo.text,Xpos,Ypos)
fond._visible=true
}
}
function removeClient(uid){
/// on cherche le client
for(var i=0;i<clients.length;i++){
if(clients[i].UID==uid){
clients[i].CLIP.removeMovieClip()
clients.splice(i,1);
break;
}
}
}
function addClient(uid,couleur,pseudo,xpos,ypos){
compteur++;
/// le client
var obj=new Object();
obj.UID=uid
obj.COLOR=couleur
obj.PSEUDO=pseudo
obj.X=xpos
obj.Y=ypos
clients.push(obj)
/// l'avatar
obj.CLIP=attachMovie("avatar","avatar_"+compteur,compteur);
obj.CLIP._x=xpos
obj.CLIP._y=ypos
obj.CLIP.txt_pseudo.text=pseudo
var acol=new Color(obj.CLIP.cercle)
acol.setRGB(couleur);
}
function userClick(uid,xpos,ypos){
/// on cherche le client
for(var i=0;i<clients.length;i++){
if(clients[i].UID==uid){
clients[i].X=xpos
clients[i].Y=ypos
compteur++;
var croix=attachMovie("croix","croix_"+compteur,compteur);
croix._x=xpos
croix._y=ypos
var ccoul=new Color(croix);
ccoul.setRGB(clients[i].COLOR);
}
}
}
fond.onRelease=function(){
userClick(0,_xmouse,_ymouse);
socket.send("CLICK="+_xmouse+"="+_ymouse);
}
onEnterFrame=function(){
for(var i=0;i<clients.length;i++){
if(clients[i].CLIP._x>clients[i].X){clients[i].CLIP._x--}
if(clients[i].CLIP._x<clients[i].X){clients[i].CLIP._x++}
if(clients[i].CLIP._y>clients[i].Y){clients[i].CLIP._y--}
if(clients[i].CLIP._y<clients[i].Y){clients[i].CLIP._y++}
}
}
Vous pouvez télécharger le fla (fla_etape_2.fla) ou directement observer le résultat de ce tutorial
en ligne.