Myrne 6312

Un shooter en vue du haut et en coop, développé en 72h dans le cadre du 26e Ludum Dare

Song of the Myrne: What Lies Beneath

Un action-RPG en pixel-art

mercredi 28 novembre 2012

Tutorial: La mise en réseau sous Unity3D - Partie 4


Salut tout le monde !

Quatrième et dernière partie du tutorial. Si vous arrivez en cours de route, ça commence par-là.
Dans cette partie on va apprendre à déclencher un évènement sur le réseau. En l’occurrence l'activation d'un ascenseur.

Vous vous demandez peut-être pourquoi on ne se contente pas de synchroniser la position de l'ascenseur via une NetworkView. Si on fait ça, l'ascenseur ne saura pas s'il est en position haute ou en position basse. Le mouvement de l'ascenseur sera également plus fluide avec notre façon de faire et ça économisera de précieuses ressources réseau. Mais il y aurait bien entendu moyen d'obtenir le même résultat en jouant avec une NetworkView et en observant la position.y de l'ascenseur pour savoir s'il est en haut ou en bas.

Mise en place de la scène



On va améliorer un peu notre arène. Je vous laisse faire ça comme vous voulez mais en gros il faut une partie surélevée qu'on ne pourra atteindre qu'avec l'ascenseur.

Placez au moins un ascenseur (un simple cube plat) muni d'un levier/bouton ou truc quelconque pour l'activer. Je vous conseille de lui mettre un pied de support comme moi, ça nous permettra d’appeler l'ascenseur si on est en bas et lui en haut.

Voila à quoi ressemble ma map:



Et voila mon ascenseur pour ceux qui veulent:
lift.fbx

Si vous l'utilisez voila comment régler les options d'importation.


Désactivez aussi l'animation de l'objet. et placez une BoxCollider sur chacune des parties.

Bien, on va régler rapidement l'ascenseur:
  • Placez une BoxCollider en mode isTrigger qui englobe tout l'ascenseur (nacelle et pilier). C'est cette box qu'on activera.
  • Créez un nouveau script en javascript et nommez-le Lift.js
  • Assignez le script à votre/vos ascenseur(s)

Ça devrait ressembler à ça

On va aussi ajouter un composant essentiel à notre Cuby, un champs d'action. Voyez ça comme un espèce de bras virtuel, ça lui permettra d'activer les objets qui sont devant lui.
  • Créez un nouvel Empty GameObject et appelez-le "ActionField".
  • Ajoutez-lui une CollisionBox en mode isTrigger et donnez-lui une forme de lame verticale qui s'étendra devant Cuby. 
  • Attachez-là à Cuby.




Scripting


  • Ouvrez le script Lift.js dans votre éditeur de code
On va commencer par l'écrire sans tenir compte du réseau.

var down:boolean; //L'ascenseur est il en position basse ou en haut?
var latence:float = 0.1; //plus elle est grande, plus l'ascenseur ira lentement
private var isMoving:boolean; //si l'ascenseur est en mouvement, on ne peux pas l'activer

var upPosition:float; //coordonnée y de la position haut de l'ascenseur
var downPosition:float; //idem mais position basse


function OnSwitched()
{
if(!isMoving)
{
isMoving = true;
if(down)
{
//monte
while(transform.localPosition.y < upPosition)
{
yield new WaitForSeconds(latence);
transform.position.y += 0.1;
}
down = false;
}
else
{
//descend
while(transform.localPosition.y > downPosition)
{
yield new WaitForSeconds(latence);
transform.position.y -= 0.1;
}
down = true;
}
isMoving = false;
}
}

function OnTriggerStay(col:Collider)
{
if(col.transform.name == "ActionField")
{
if(col.transform.root.networkView.isMine) //Evite de pouvoir activer l'ascenseur si c'est l'ennemi qui est dans la bonne position pour l'activer
{
if(Input.GetKeyDown(KeyCode.E) || Input.GetKeyDown(KeyCode.Return))
    {
    OnSwitched();
    }
    }
    }
}

  • Configurez ensuite le script dans l'inspector. Voila ma config, les coordonnées seront très probablement différentes dans votre projet.


Normalement, votre ascenseur doit fonctionner en local ("E" ou "Enter" pour l'activer).

Quelques conseils:
    • Le script n'est pas très précis, c'est pourquoi la downPosition de l'ascenseur est différente de sa position de départ.
    • Le mouvement de l'ascenseur n'est pas du tout fluide (si on vous demande, dites que c'est pour un effet ascenseur antique^^). Ça peut s'arranger en jouant avec la variable latence et le changement de position (ex: transform.position.y -= 0.1; ).
    • Vous pouvez aussi utiliser la fonction transform.Translate plutôt que de changer directement la position.y

La partie réseau



Si vous avez testé le jeu en multi, vous avez du vous rendre compte que l'ascenseur ne s'active pas sur les autres clients.

On va remédier à ça très facilement.
  • Commencez par ajouter une NetworkView à votre ascenseur. Cette NetworkView ne doit rien observer ("none").
Pour que l'ascenseur se synchronise entre les clients on va utiliser ce qu'on appelle un RPC (Remote Procedure Call). Comme le nom l'indique, on va appeler (call) une fonction (une procédure) à distance (remote). Vous allez voir, c'est très facile.

On va d'abord préciser à Unity que notre fonction OnSwitched() est une RPC. Pour ça, on ajoute "@RPC" devant la déclaration de la fonction.


On va ensuite simplement changer la manière dont on appelle cette fonction.

Au lieu de:

if ( Input.GetKeyDown(KeyCode.E) || Input.GetKeyDown(KeyCode.Return))
{
 OnSwitched();
}

Tapez:

if ( Input.GetKeyDown(KeyCode.E) || Input.GetKeyDown(KeyCode.Return))
{
networkView.RPC("OnSwitched", RPCMode.All);
}

Rien de bien neuf ici, comme pour l'instanciation en réseau, le RPCMode détermine sur quel groupe de clients on doit appeler la fonction.

Chez moi les joueurs non-serveurs ne décollent pas avec l'ascenseur, je vous conseille donc de tout de même synchroniser la position (le Transform) de l'ascenseur avec la NetworkView. De cette façon, il n'y aura aucun problème et l'état up ou down de l'ascenseur sera bien synchronisé par notre RPC.

Voici le script final:

Cliquez pour le voir en grand

Vos ascenseurs devraient maintenant fonctionner comme prévu.


Conclusion



Comme promis, cette partie du tutorial était très courte. Les RPC sont simples a utiliser mais c'est un outil vraiment puissant que vous utiliserez très régulièrement.

On a passé en revue tous les éléments de base de la mise en réseau et vous devriez maintenant être aptes à réaliser vos propres petits jeux en réseau puisque vous savez désormais:
  • Synchroniser la position et l'orientation d'objets
  • Instancier des objets tels que les joueurs ou les projectiles sur le réseau
  • Appeler des fonctions locales à distance sur toutes les machines connectées
Vous remarquerez qu'il manque le quatrième pilier d'un jeu en réseau: la synchronisation de variables (ex: synchroniser le contenu de deux coffres dans un jeu où leur contenu est sélectionné au hasard en début de partie). Je ne maîtrise malheureusement pas assez le sujet que pour écrire un tutorial là dessus. Je vous donne quelques pistes un peu plus loin.

On a également créé un véritable jeu qui bien que très basique et à des années lumières d'un Unreal Tournament par exemple, pourra vous occuper un petit moment avec des amis. N'hésitez pas à le peaufiner et à y ajouter des features le temps de vous familiariser avec le réseau. Je serai ravi de voir ce que vous en faites donc n'hésitez surtout pas à poster vos versions modifiées dans les commentaires.

J'espère que ce tutorial vous a plût et que je ne vous ai pas donné trop de mauvais conseils. Je posterai des annexes si je fais des découvertes intéressantes.

Voila mon projet Unity terminé:
Télécharger


Aller plus loin



Je vous parlais de la synchronisation de variables, cela se fait via la fonction OnSerializeNetworkView().

Voila le passage de la documentation Unity parlant de ça. Et bien entendu voici le Network Reference Guide d'Unity où vous trouverez pas mal d'informations importantes.

Il ne me reste plus qu'à vous donner un dernier conseil: pratiquez ! Pratiquez, pratiquez et pratiquez encore. Rome ne s'est pas faites en un jour donc ne vous découragez pas si vous n'arrivez pas tout de suite aux résultats escomptés. Faire un jeu en réseau n'est pas la chose la plus simple du monde mais avec un peu de persévérance vous devriez y arriver.

N'hésitez pas non plus à demander de l'aide sur les forums où des gens plus qualifiés pourront vous aider. A ce propos je vous déconseille Unity Answers vu que vos messages doivent être validés pour apparaître  ce qui vous fera perdre un temps précieux. Personnellement je demande généralement de l'aide sur Reddit/r/Unity3D. La communauté est très sympa et généralement de bon conseil.

Bonne continuation :)


Tutorial: La mise en réseau sous Unity3D - Annexe 1

Salut tout le monde !

Désolé pour le retard pris dans mes publications du tutorial. Avant de passer à la partie 4 du tuto j'aimerais vous faire partager une petite découverte faite par Pascal Monnier, un grand merci à lui.

Si, contrairement à moi, vous avez testé le projet en réseau, vous aurez probablement constaté pas mal de problèmes de lags. Cela est dû, entre autre, au fait que nous déplaçons notre personnage en touchant directement à sa position plutôt qu'en lui appliquant des forces.

Du coup, sa position est envoyée régulièrement sur le réseau, mais entre chaque envoi, le personnage est tout simplement immobile tant qu'il ne reçoit pas de nouvelles instructions  Si un retard survient dans l'envoi ou que des packets se perdent sur le réseau, votre personnage recevra une position beaucoup plus éloignée que la sienne et fera un petit bond de téléportation. C'est moche et surtout ça ruine le gameplay.

Notez qu'on a pas ce problème avec les projectiles tirés puisqu'on synchronise leur rigidbody et donc leur vitesse. Entre deux reçus de données, le projectile continue donc son chemin.

Mais on va remédier à ce problème en "trichant" un peu. Entre deux reçus de position, on va tenter de deviner la prochaine position du player adverse, avant de la recevoir et de corriger les quelques imprécisions.

Ce processus s'appelle l'interpolation et vu qu'il est assez compliqué on va se contenter de copier/coller un script donné dans le tutorial réseau d'Unity. Je vous recommande la lecture de cet article pour mieux comprendre le principe. C'est expliqué de manière totalement accessible et en français donc ne vous en privez pas.

Je vous encourage aussi à lire le script et à essayer de le comprendre quand vous aurez le skill pour. Mais il y a de fortes chances que ce soit du chinois pour vous actuellement.

Vous pouvez le télécharger ici: NetworkRigidbody.cs

Je vous le copie en fin d'article au cas où le lien viendrait à mourir.

Je m'excuse pour la mise en page un peu moisie, j'ai copier/coller le truc du PDF Unity et évidemment c'est pas super.

La suite est simple. On va simplement placer le script sur notre "Player" et l'assigner à la NetworkView à la place du Transform de l'objet.


Et voila, en principe c'est bon :)

Une fois encore je n'ai pas le matériel pour tester ça sur un réseau donc faites moi savoir s'il manque une étape ou si ça ne fonctionne pas correctement.

Et comme promis voici le script. Attention, c'est à coller dans un script en C#:

______________________________________


using UnityEngine;
using System.Collections;

public class NetworkRigidbody : MonoBehaviour
{
public double m_InterpolationBackTime = 0.1;
public double m_ExtrapolationLimit = 0.5;
internal struct  State
  {
internal double timestamp;
internal Vector3 pos;
internal Vector3 velocity;
internal Quaternion rot;
internal Vector3 angularVelocity;
  }
  // We store twenty states with "playback" information
  State[] m_BufferedState = new State[20];
  // Keep track of what slots are used
  int m_TimestampCount;
  void OnSerializeNetworkView(BitStream stream, NetworkMessageInfo info) {
  // Send data to server
  if (stream.isWriting)
  {
Vector3 pos = rigidbody.position;
Quaternion rot = rigidbody.rotation;
Vector3 velocity = rigidbody.velocity;
Vector3 angularVelocity = rigidbody.angularVelocity;
stream.Serialize(ref pos);
stream.Serialize(ref velocity);
stream.Serialize(ref rot);
stream.Serialize(ref angularVelocity);
  }
  // Read data from remote client
else
{
  Vector3 pos = Vector3.zero;
  Vector3 velocity = Vector3.zero;
  Quaternion rot = Quaternion.identity;
  Vector3 angularVelocity = Vector3.zero;
  stream.Serialize(ref pos);
  stream.Serialize(ref velocity);
  stream.Serialize(ref rot);
  stream.Serialize(ref angularVelocity);
 
  // Shift the buffer sideways, deleting state 20
  for (int i = m_BufferedState.Length-1; i >= 1; i--)
  {
    m_BufferedState[i] = m_BufferedState[i-1];
  }
 
  // Record current state in slot 0
  State state;
  state.timestamp = info.timestamp;
  state.pos = pos;
  state.velocity = velocity;
  state.rot = rot;
  state.angularVelocity = angularVelocity;
  m_BufferedState[0] = state;
 
  // Update used slot count, however never exceed the buffer size
  // Slots aren't actually freed so this just makes sure the buffer is
  // filled up and that uninitalized slots aren't used.
  m_TimestampCount = Mathf.Min(m_TimestampCount + 1, m_BufferedState.Length);
  // Check if states are in order, if it is inconsistent you could reshuffel or
  // drop the out-of-order state. Nothing is done here
  for (int i=0;i   {
   if (m_BufferedState[i].timestamp < m_BufferedState[i+1].timestamp)
    Debug.Log("State inconsistent");
  }
  }
  }

 // We have a window of interpolationBackTime where we basically play
 // By having interpolationBackTime the average ping, you will usually use interpolation.
 // And only if no more data arrives we will use extra polation
 void Update () {
  // This is the target playback time of the rigid body
  double interpolationTime = Network.time - m_InterpolationBackTime;
 
  // Use interpolation if the target playback time is present in the buffer
  if (m_BufferedState[0].timestamp > interpolationTime)
  {
   // Go through buffer and find correct state to play back
   for (int i=0;i   {
    if (m_BufferedState[i].timestamp <= interpolationTime || i == m_TimestampCount-1)
    {
     // The state one slot newer (<100ms best="best" p="p" playback="playback" state="state" than="than" the="the">     State rhs = m_BufferedState[Mathf.Max(i-1, 0)];
     // The best playback state (closest to 100 ms old (default time))
     State lhs = m_BufferedState[i];
   
     // Use the time between the two slots to determine if interpolation is necessary
     double length = rhs.timestamp - lhs.timestamp;
     float t = 0.0F;
     // As the time difference gets closer to 100 ms t gets closer to 1 in
     // which case rhs is only used
     // Example:
     // Time is 10.000, so sampleTime is 9.900
     // lhs.time is 9.910 rhs.time is 9.980 length is 0.070
     // t is 9.900 - 9.910 / 0.070 = 0.14. So it uses 14% of rhs, 86% of lhs
     if (length > 0.0001)
      t = (float)((interpolationTime - lhs.timestamp) / length);
   
     // if t=0 => lhs is used directly
     transform.localPosition = Vector3.Lerp(lhs.pos, rhs.pos, t);
     transform.localRotation = Quaternion.Slerp(lhs.rot, rhs.rot, t);
     return;
    }
   }
  }
  // Use extrapolation
  else
  {
  State latest = m_BufferedState[0];

  float extrapolationLength = (float)(interpolationTime - latest.timestamp);
  // Don't extrapolation for more than 500 ms, you would need to do that carefully
  if (extrapolationLength < m_ExtrapolationLimit)
  {
   float axisLength = extrapolationLength * latest.angularVelocity.magnitude * Mathf.Rad2Deg;
   Quaternion angularRotation = Quaternion.AngleAxis(axisLength, latest.angularVelocity);
 
   rigidbody.position = latest.pos + latest.velocity * extrapolationLength;
   rigidbody.rotation = angularRotation * latest.rot;
   rigidbody.velocity = latest.velocity;
   rigidbody.angularVelocity = latest.angularVelocity;
    }
  }
  }
}

______________________________________

Nous voila armés pour la partie 4, hiaaarrr !


mardi 27 novembre 2012

Quick Review: Cargo Commander

Salut tout le monde !

Je sais ce que vous vous dites, oui il n'y a finalement pas eu de QR la semaine passée, je sais c'est scandaleux^^.

Pour me faire pardonner je vous ai dégoté un excellent jeu (c'est pas l'avis de jeuxvideo.com, ce qui prouve que j'ai sans doute raison^^) cette semaine, qui possède un gros potentiel de rejouabilité. Si vous savez lire un titre vous savez déjà qu'il s'agit de Cargo Commander^^.



Alors, de quoi s'agit-il? Ce jeu est assez original et si je devais lui donner un genre je dirais qu'il s'agit d'un "Roguelike-ish Gravity-Plateformer". Ne cherchez pas sur Wikipedia, je viens d’inventer ce terme.

Donc à la base il s'agit d'un plateformer... dans l'espace... avec pas mal d'éléments Rogue-like tels que la génération aléatoire des levels (rejouabilité !) et un genre de permadeath (sauf qu'on ne perd pas tout).

Vous saupoudrer le tout avec un bon humour à la Portal et vous obtenez Cargo Commander.


Gravity



Dans Cargo Commander vous incarnez un type chargé de récupérer du matériel (genre des pulls en poils de chats et autres nains de jardins) dans des conteneurs abandonnés dans l'espace.

Le schéma est toujours le même. Depuis votre cargo (un conteneur qui vous sert de maison) vous actionnez un gros aimant qui va attirer des conteneurs (qui détruiront à moitié votre maison en la percutant^^). Vous explorez ce conteneur puis vous vous lancer dans le vide et la non-gravité de l'espace en retenant votre respiration (on est badass ou on ne l'est pas) jusqu'au prochain conteneur que vous ouvrez avec votre laser (le décor est destructible) avant de le fouiller, etc.

Cliquez pour agrandir les images
Votre "charmante" petite maison

Au bout d'un moment un vortex s'ouvre et vous devrez alors rapidement rejoindre votre maison en retraversant les conteneurs qui s'effondrent autour de vous, c'est jouissif !

Bien entendu il y a généralement des monstres dans les conteneurs (et certains dans l'espace) qui ne vous faciliteront pas la tâche, surtout que les cartouches sont assez limitées (surtout au début).



Addictif


Ce jeu possède lui-aussi le syndrome du "allez, une dernière partie ne peux pas faire de mal". Ici aussi chaque partie vous fera progressivement évoluer et débloquer des améliorations pour votre maison ou votre personnage.

De plus, si le jeu n'offre malheureusement pas de mode multi, il y a par contre un bon système de scoring en place. Vous évoluez dans des zones, chaque zone est générée aléatoirement selon son nom et vous pouvez "créez" vos propres zones.

Chaque zone possède son propre tableau de scores, partagé sur internet. Ça signifie que vous pouvez par exemple vous mesurez à vos amis dans une zone pseudo-privée (elle est publique mais qui ira taper "Blqsfjdfhsq" comme nom de zone si vous ne lui dites pas de le faire. N'hésitez pas à venir dans la zone "Beldarak" d'ailleurs ;)

Lorsque vous terminez une zone, il faut trouver un laisser-passer de zone dans un conteneur spécial (un genre de puzzle à résoudre, arène à terminer, etc) qui apparaît au bout d'un moment. Vous pourrez alors en débloquer une nouvelle (avec de nouveaux objets à trouver). Vous aurez le choix entre une zone aléatoire populaire (où beaucoup de gens s’affrontent pour voir qui a la plus grosse), impopulaire (pour avoir une chance d'avoir la plus grosse), une zone de vos amis aléatoire, une secteur totalement aléatoire ou bien d'entrer un nom de zone pour vous mesurez à vos amis ou être tranquillou tout seul.

Un des conteneurs spéciaux


Un mode bonus qui est bien plus que ça


Après un certain temps vous débloquerez le mode "Voyage", ce mode est bien plus qu'un mode bonus puisqu'il rapproche énormément le jeu d'un Rogue-like.

Dans le mode voyage, il n'y a pas de vortex cassant les conteneurs (vous avez donc tout votre temps) et vous n'avez pas de maison. De nouveaux conteneurs arrivent au fur et à mesure de votre progression.



Ce qui est vraiment un coup de maître dans ce mode c'est qu'il fait avancer l'histoire (j'y reviens après), qu'il ne possède pas toutes les limitations de zones: tous les objets sont présents ainsi que tous les conteneurs (sinon chaque zone à ses propres conteneurs); mais qu'il n'est pourtant pas cheaté.

D'abord parce qu'il est plus difficile (vous devez trouver des ateliers, etc pour améliorer votre perso ou acheter des cartouches et de la vie), ensuite parce qu'au final la progression se fait à la même vitesse que si vous débloquiez des zones dans le mode normal.



Un vrai scénario


Si le gameplay est au final assez répétitif (comme dans tout roguelike), l'histoire, elle, progresse via des mails que vous recevez de votre femme (ça me rappelle un peu le film "Moon") et de vos patrons lors de vos progression (chaque nouvel objet trouvé fait avancer une barre d'xp).
Le tout est très drôle et pousse vraiment à avancer dans le jeu, ne fut-ce que pour voir les horribles dessins que vous envoie votre gamin :D

J'en profite pour parler de l'ambiance qui est vraiment bien foutue, on oscille tout le temps entre la musique country de notre maison à celle angoissante des conteneurs en passant par le silence de l'espace (une option permet d'ailleurs de ne pas avoir de son du tout quand vous êtes dans l'espace, pour plus de réalisme).

La direction artistique est impeccable et on ressent bien la solitude de l'espace

La musique est un peu répétitive à force mais je l'aime bien quand-même et on peut toujours la couper une fois le panneau de contrôle du vaisseau débloqué.



Go go l'acheter


Voila voila ;)

Je vous conseille de l'acheter rapidement, il est à 1,74 € pour les soldes Steam pour encore 2 petites heures, n'hésitez pas ça les vaut très largement. Et si vous arrivez trop tard bah il vaut encore le coût pour 7 euros. Sinon j'imagine qu'il finira bien par se retrouver en pay what you want sur IndiegameStand, je vous ferez savoir si c'est un jour le cas.

Pour les pirates (bande de vils), je pense que ça peut-être intéressant de le télécharger pour l'essayer mais seul le mode en ligne permet d'avancer dans l'histoire. Donc sortez votre porte-monnaie pour une fois^^.

Si je devais lui donner une note, je donnerais un bon 7/10 qui passe à 8 parce que je vais probablement y rejouer régulièrement.

Cargo Commander est disponible sur Steam.

Bon amusement et à la prochaine les ragondins.


mercredi 21 novembre 2012

Pas de QR et news sur SotM

Salut tout le monde.

Je sais que ça devient une habitude mais la Quick Review de cette semaine est reportée à un peu plus tard. Toujours la même raison, exam jeudi.

En principe après ça je devrais avoir un peu plus de temps pour faire tout ce qui est en retard: QR et fin du tutorial de mise en réseau sous Unity3D.

Je pourrai également me consacrer à plein temps à SotM pendant un moment (quelques semaines je pense). et je me suis d'ailleurs fixé une deadline pour sortir la démo. Rien de certain bien-sûr mais j'espère bien la sortir pour un concours de création dont l'échéance est le 29 novembre. Je ne promet pas qu'elle sortira bien en temps et en heure mais c'est mon objectif en tout cas^^.

En parlant de Song of the Myrne j'ai commencé à implémenter la gestion des sauvegardes et de la mémoire (histoire que les pnj n'oublient pas tout ce que vous leurs avez dit quand vous changez de scène avant de revenir^^. Il me reste encore a ajouter une attaque de base aux mobs (ils "chargent" déjà mais sans mettre de dégâts  et le core de la démo sera terminé. C'est à dire que je n'aurai plus qu'à écrire les dialogues/quêtes et construire un peu les maps qui ne sont pas générées aléatoirement (les villes et lieux importants en gros, il y en aura deux dans la démo).

Je m'amuse beaucoups en écrivant les dialogues et le jeu a "pris son identité". Ce seras définitivement du gros n'importe quoi bien drôle (du moins j'aime à le penser^^).

Tout ce qui est inventaire, équipement, magasins et ce genre de choses seront ajoutés après la démo (d'habitude l'inventaire est un des trucs que j'ajoute en premier et c'est le mega bordel, cette fois je préfère l'implémenter tardivement).

Ah oui, et bien-sûr la big news c'est qu'un internaute s'est proposé pour faire quelques dessins pour le jeu. Pour le moment je ne pense à rien d'autre qu'au menu et à un éventuel écran de fin mais il pourra bien-sûr m'aider pour des projets futurs s'il me supporte et qu'il est bien entendu d'accord (sinon j'ai une valisette avec toutes sortes d'instruments bien sympas dans ma cave, mais chuuut).

Je vous met le lien vers son blog où vous pourrez jeter un oeil à ses dessins que je trouve vraiment sublimes.

Je compte utiliser des dessins dans ce style-là:

Ça a été dessiné avant que je le rencontre et c'est déjà totalement dans le thème, ça promet^^

Vous ne vous en rendez sans doute pas compte mais vous avez évité la catastrophe, j'avais commencé à bosser sur un écran de fond de menu et moi, dès qu'on va plus haut que du 12x12 pixels je ne fais que des trucs moches :D

Voila voila, sinon pour revenir à la QR, j'ai testé Salvation Prophecy tout à l'heure et il avait l'air vraiment sympa jusqu'à ce que ma carte graphique crashe lamentablement. Je re-testerai quand j'aurai le temps pour voir si le problème est récurrent mais si vous avez l'occasion de l'essayer, n'hésitez pas, je trouvais ça assez sympa (mais bon, passé le tutorial j'ai pas pu jouer plus de 10 minutes).

Il est disponible sur Desura et une démo est également disponible.

Voila voila. Je vous dis à la prochaine.


mercredi 14 novembre 2012

Quick Review: Really Big Sky

Hello, and welcome again...

Ça devient une habitude mais cette fois la Quick Review du mardi est un mercredi, je suis trop un punk dedans ma tête lolilol comme, je l'espère, personne ne le dis.

J'ai un peu galérer à trouver un jeu pour aujourd'hui parce que je suis crevé et que je voulais un jeu simplissime (comprendre: simple à jouer mais aussi simple à décrire dans un test^^). Et mon choix s'est finalement porté sur "Really Big Sky".


Pour décrire le jeu en deux mots c'est un shooter horizontal. Mais qu'est-ce qu'il a de plus que les douze-mille shooters existants?, me direz-vous. Hé bien tout d'abord c'est très impoli de couper la parole, bande de mécréants, ensuite:
  • De l'aléatoire
Le jeu possède différentes phases qui arrivent aléatoirement: boss, planètes à traverser (indice parce que tout le monde galère la première fois: il suffit d'appuyer une seule fois sur clic droit pour passer en mode drill (forage) ), dinosaures...

C'est pas non plus un jeu infini comme peuvent l'être des Nethack et compagnie mais on prend toujours plaisir à revenir dessus de temps en temps.

Hou... le vilain boss qui prend tout l'écran !
  • RBS est fracking addictif
Le jeu typique du:
- 23h: "Oh, aller, une petite partie de plus ne peux pas faire de mal, ça ne dure jamais que 5 minutes".
- 5h du mat': "Pouuuuurquoiiiiii ai-je refait 10 000 partiiiiies?!!! Moooonde cruel !!"

Les quelques ingrédients expliquant le pourquoi du comment de votre damnation:
    • On collecte de l'xp en jeu pour améliorer un peu son vaisseau entre chaque partie
    • Une partie dure assez peu de temps (selon votre skill / vaisseau évidemment)
    • La mort est souvent frustrante ("tin, je viens de battre 4 boss d'affilé pour me faire tuer par un bête rocher T_T") ce qui pousse à rejouer, car c'est bien connu, une partie où vous mourrez bêtement ne compte pas comme une vraie partie (3e loi de la Procrastination).
  • La musique est cool et la voix off faisant quelques petits commentaires est vraiment sympathique (on a vite fait le tour des phrases mais ça reste sympa à entendre)
  • On ressent vraiment les progrès qu'on fait et ça, ça n'a pas de prix (non je ne ferai pas la blague Mastercard !!)
C'est joli mais faut pas être épileptique...

Conclusion


Really Big Sky est un jeu vachement addictif qui bien qu'on en ait vite fait le tour se laisse volontiers rejouer. Différents modes de jeu viennent rehausser l’intérêt mais le mode Classique reste pour moi le meilleur (ça et le mode avec seulement les boss). Pour ceux qui aiment spammer leurs amis on peut poster directement ses scores sur Facebook, Twitter et je ne sais plus quoi.

Je lui donne un score de 7.5/10 (oui maintenant je côte les jeux même si j'aime pas trop ce principe sinon les trucs vraiment awesome dont je parle passent inaperçus dans la masse^^).

Apparemment une version un peu mieux existe (BigSky Infinity) mais bien qu'elle utilise le même moteur et aussi les mêmes assets elle n'est sortie que sur Playstation et Vita, je ne félicite pas les devs sur ce point là :/

Je vous met le site officiel du studio de devs parce qu'ils ont l'air d'avoir fait pas mal de petites perles (que j'ai eu sur IndieGameStand mais que je n'ai pas encore eu l'occasion de tester).

Et bien sûr le lien vers le jeu sur Steam (qui est un peu cher, c'est moche que j'ai oublié de vous en parler quand il était sur IndieGameStand :'(  )

News: un peu de tout

Salut les loutres !
  • Comme j'ai du étudier aujourd'hui, la Quick review est reportée à demain.
  • Pour ceux qui attendent la suite et fin du tutorial réseau sous Unity3D je ne vous ai pas oublié, pas de panique. Je suis juste overbooké en ce moment mais je compte l'écrire vendredi ou un peu plus tard probablement. Désolé pour le délai.
Voila voila. Pour me faire pardonner je vous met un nouveau screen de Song of the Myrne, le jeu sur lequel je bosse.



Dans la version finale, la page de droite affichera un petit arbre de compétence. Il y en aura un par élément (Feu, Glace et Electricité) avec chacun quatre skills.

Pour chaque élément on a la puissance et la résistance. L'aile en bas à gauche représente la vitesse de cast (le délai entre chaque sort).

Chaque niveau donnera un point qui peut être dépensé n'importe où (sauf sur l'argent^^).

dimanche 11 novembre 2012

Tutorial: La mise en réseau sous Unity3D - Partie 3

Salut tout le monde !

Si vous êtes arrivés ici par hasard, vous aurez sans doute besoin de commencer par la partie 1, par-ici.

Troisième et avant-dernière partie du tutorial. Aujourd'hui on va apprendre à notre Cuby a tirer et on va transformer ce petit projet en vrai jeu jouable et presque amusant.

Au fait, dans la partie 2 je vous parlais pas mal d'un certain script Deplacement, je voulais en fait parler du script Cuby.js. J'ai corrigé le billet et j'espère que ça ne vous a pas trop embrouillé / dérangé, désolé.

Pew Pew



Ok, on va apprendre à Cuby, notre cube d'intervention spéciale, à tirer.
  • Commençons par créer un nouveau script "Weapon".
  • Ajoutez-lui la fonction Shoot(). 
On aurait pu créer cette fonction dans le script Cuby.js mais je préfère utiliser un script Weapon qu'on assignera à chaque arme différente pour les raisons suivantes:
    • Si on ajoute plusieurs types d'armes, ce seras beaucoup plus simple de configurer chaque arme avec un script comme ça. Par exemple si je crée une arbalète, je pourrai lui dire, dans l'inspector, d'utiliser des carreaux comme projectiles plutôt que des balles.
    • Ça évite d'encombrer le script Cuby.js qui dans le cas d'un gros jeu contiendrait déja pas mal de code lié à ses déplacements.
    • L'instanciation sera plus facilement configurable puisque ce script sera placé sur l'arme, et donc à la position d'où doit partir le projectile.
La première chose à faire va être d'instancier un projectile sur le réseau à la position de notre arme:
  • var objet:GameObject = Network.Instantiate(projectile, transform.position, transform.rotation, 0);
  • Déclarez la variable var projectile:GameObject; tout en haut du script.
Il ne reste plus qu'a donner une vitesse à notre projectile.
  • objet.rigidbody.velocity = transform.TransformDirection (Vector3.forward * 40);
Unity déconseille de toucher directement à la vélocité d'un objet et propose plutôt d'utiliser des AddForce mais puisque je suis un gros noob de la physique et que ce n'est pas un "vrai jeu" on va faire comme ça pour plus de facilité.

On va maintenant pouvoir configurer tout ça dans Unity. 
  • Sélectionnez le GameObject "Gun" sur votre Prefab "Player" et assignez-lui le script "Weapon".
  • Faites glisser le Prefab "Bullet" sur le champs Projectile dans l'inspector.


On va ensuite ajouter un RigidBody sur notre Bullet sans quoi elle ne subira pas la gravité et ne pourra pas être éjectée de l'arme.
  • Sélectionnez le Prefab "Bullet", cliquez sur Component/Physics/RigidBody et laissez tout par défaut.
  • Ajoutez-lui aussi une NetworkView. Et assignez le RigidBody plutôt que le Transform de l'objet à cette NetworkView Sinon les balles se déplaceront en laggant et ça fera tout foirer^^

En principe tout est prêt mais on doit encore appeler la fonction Shoot(). Retournez dans le script Weapon et ajoutez ceci dans la fonction Update():

if ( Input.GetKeyDown(KeyCode.Mouse0))
Shoot();
    N'oubliez pas la fonction Awake avec le code suivant pour éviter que votre adversaire ne tire aussi quand vous cliquez:

    if ( !networkView.isMine)
        Destroy(this);

    Votre code devrait maintenant ressembler à ça.


    Si vous lancez le jeu vous constaterez qu'il est désormais jouable ! En effet vous pouvez vous amusez à pousser l'adversaire en dehors de la map. C'est pas Crysis 3 mais c'est déjà plus amusant à jouer qu'un JRPG [/troll]

    Quelques idées d'amélioration:
    • Créer plusieurs arènes avec des obstacles, etc
    • Mettre un compteur de points qui augmente à chaque fois qu'un joueur tombe de la map
    • Faire respawn le joueur lorsqu'il tombe.
    • Ajouter une visée à la souris et des déplacements latéraux
    Bien, revenons-en à notre réseau, il nous reste encore quelques trucs à voir.

     Accéder aux objets en réseau



    Je ne sais pas vous, mais souvent, lorsque je code et que je désire accéder au joueur, je tape un truc du genre "GameObject.Find("Player")".

    Oubliez cette fonction ! En réseau vous ne l'utiliserez quasiment plus jamais. Pourquoi?

    Vous avez plusieurs joueurs sur votre map et ils ont en principe tous le même nom (l'objet Player en tout cas). Cette fonction va vous-en renvoyer un au hasard ou en tout cas de manière difficilement contrôlable.

    Je vais vous montrer une alternative et on en profitera pour régler un petit bug de notre jeu.

    Vous l'avez peut-être remarqué mais Cuby est vraisemblablement équipé d'un Jetpack puisqu'il peut voler lorsqu'on appuie sur la touche de saut alors que Cuby n'est pas au sol. C'est classe mais ça ne m'arrange pas.

    On ne le fera pas dans ce tuto mais disons que je veux ajouter un objet Jetpack ramassable sur la map. Je vais devoir empêcher Cuby de voler lorsqu'il n'a pas ce Jetpack. Pour ça on va devoir détecter si Cuby est au sol ou non.

    On pourrait simplement détecter si l'objet "Player" est en collision avec le sol mais pour ce tutorial et parce que bien souvent votre joueur ne tiendra pas en un seul bloc, on va plutôt utiliser un sous-objet "GroundAnalyst" qui se chargera de regarder si le joueur est au sol ou non.

    Je vous conseille d'ailleurs de toujours créer un objet vide "Player" qui aura ensuite un objet Modele3D attaché comme sous-objet, sinon tout changement de modèle sera difficilement faisable sans devoir reconfigurer tout le personnage.
    • Dans Unity, créez un nouvel Empty GameObject que vous nommerez "GroundAnalyst". 
    • Placez Cuby dans la scène et assignez-lui ce nouvel objet. 
    • Ajoutez une CollisionBox au GroundAnalyst et cochez la case "Is Trigger" afin qu'il passe à travers les objets.
    • Redimensionnez la box (shift + clic sur les carrés des faces) pour qu'elle soit un peu en-dessous de Cuby. De cette façon:


    • Créez un nouveau script "GroundAnalyst" que vous assignez au GroundAnalyst.
    • N'oubliez pas d'appliquer vos modifications au Prefab en appuyant sur le bouton "Apply" en haut a droite de l'inspector (en rouge sur l'image) avant de le supprimer de la scène.

    J'en ai profité pour rendre Cuby rebondissant (en bleu sur l'image), ce qui ajoutera un peu de challenge au jeu. Cette étape n'est pas du tout obligatoire et n'a rien à voir avec le réseau mais si vous voulez faire pareil voici les spécifications de mon Physic Material:


    Attaquons nous maintenant à notre GroundAnalyst. On va faire un petit changement dans le script Cuby.js pour commencer:
    • Déclarez la variable "var canJump : boolean;"
    • Vous pouvez aussi supprimer la variable "life" si vous l'avez puisque le jeu ne consiste finalement plus à tuer l'ennemi en tirant dessus (je trouve que le pousser en dehors du terrain est bien plus fun et original).
    • Changez ensuite le saut dans la fonction "Deplacement()":

    if ( Input.GetKeyDown(KeyCode.Space) && canJump)
    {
    rigidbody.AddForce(Vector3.up * 350);
    canJump = false;
    }

    au lieu de:

    if ( Input.GetKeyDown(KeyCode.Space))
    rigidbody.AddForce(Vector3.up * 350);



    Passons maintenant à notre script "GroundAnalyst":

    Il va simplement regarder s'il est en contact avec le sol, ce qui signifierais que le joueur peut sauter:

    function OnTriggerEnter(col:Collider)
    {
    if(col.gameObject.name == "Terrain") //si l'objet qui traverse le GroundAnalyst s'appelle "Terrain"
    {
    scriptCuby.canJump = true;
    }
    }
    • Déclarez la variable "private var scriptCuby:Cuby;" au-début du script.
    Il faut maintenant assigner un script à cette variable. Un réflexe de débutant pourrait-être de taper "scriptCuby = GameObject.Find("Player").GetComponent("Cuby");" dans la fonction Start().

    Mais rappelez-vous ce que j'ai dit plus haut, en faisant ça Unity va vous renvoyer le script Cuby d'un joueur. Peut-être le vôtre mais peut-être celui d'un autre... Cette méthode est donc à totalement bannir quand on code en réseau et qu'on tente d’accéder à un objet instancié.

    On va plutôt utiliser une ligne toute simple que vous connaissez peut-être déjà. Personnellement je l'ai apprise en cherchant une solution à mes problèmes réseaux lorsque je débutais:

    La solution est donc de taper dans la fonction OnTriggerEnter():
    • scriptCuby = transform.root.GetComponent("Cuby");
    Votre fonction doit ressembler à ça:

    function OnTriggerEnter(col:Collider)
    {
    if(col.gameObject.name == "Terrain")
    {
    scriptCuby = transform.root.GetComponent("Cuby");
    scriptCuby.canJump = true;
    }
    }

    Si vous testez le jeu ça devrais normalement fonctionner. Si vous avez rendu votre Cuby rebondissant plus haut, vous devriez pouvoir vous amuser à contrôler la hauteur de votre saut en appuyant sur espace au bon moment.

    Je reviens donc sur ce petit mot magique: "transform.root". En fait il vous renvoie tout simplement le Transform le plus haut de la hiérarchie de l'objet. Ici, notre objet "Player" donc puisqu'il est le parent le plus haut du GroundAnalyst. On aurait aussi pu utiliser "transform.parent" qui renvoie le parent direct de l'objet.

    Vous utiliserez souvent cette manière de procéder. Je pense d'ailleurs qu'elle est moins gourmande en ressources que GameObject.Find donc pensez-y aussi pour vos jeux solo.

    Vous remarquerez peut-être que notre jeu bug un peu parfois ce qui fait qu'on peut se retrouver au sol sans pouvoir sauter. Voici mon code qui contient un petit "workaround" pour contourner ce problème (un peu plus gourmand en mémoire).



    Il ne nous reste qu'une seule chose à voir mais ce sera pour la prochaine partie du tutorial.


    Conclusion



    On possède maintenant un vrai jeu jouable et qui fonctionne en réseau. Il vous manque encore un petit concept réseau à voir pour vraiment pouvoir vous lancer seuls mais cette lacune sera réglée après la prochaine et dernière partie du tutorial. N'hésitez pas à apportez des modifications au jeu pour le rendre plus intéressant.

    Si votre code ne fonctionne pas je vous invite à refaire cette partie en prenant éventuellement mon projet Unity que j'ai donné à la fin de la seconde partie comme base de départ. Si vous avez des questions ou des suggestions, les commentaires sont fait pour ça donc n'hésitez pas.

    Voila le projet Unity du jeu tel qu'il doit être à la fin de cette partie.

    A télécharger ici

    Avant de passer à la partie 4 du tuto, je vous conseille de faire un tour sur cet annexe où on réglera un problème de lags.




    samedi 10 novembre 2012

    Tutorial: La mise en réseau sous Unity3D - Partie 2

    Salut tout le monde.

    Aujourd'hui on va continuer ce qu'on a commencé hier et on va vraiment chipoter à des composants réseau d'Unity.

    Si vous avez loupé la première partie du guide c'est par-là que ça se passe.

    Attention, hier je vous avais dit d’appeler un de nos scripts "StartNet" mais dans le script Cuby.js que je vous ait donné je l'ai utilisé en tant que "StartNetwork". Renommez le donc en StartNetwork. J'ai corrigé le billet d'hier donc si vous lisez cet article plus tard que le 9/11/2012 pas de problème^^.

    Bien, on peut continuer.

    Etablissement d'une connexion

    • Toujours dans votre scène mainMenu, créez un nouveau GameObject vide (Create Empty)
    • Appelez le "NetworkMaster" et assignez-lui le script StartNetwork. 
    • Faites glisser ce nouvel objet dans les Prefabs puis supprimez-le de la scène
    • Sélectionnez maintenant votre caméra. Dans l'Inspector vous verrez que son script MainMenu demande un GameObject NetworkMaster. Ça tombe bien, on vient d'en créer un ! Faites le glisser des Prefabs vers l'endroit prévu dans l'inspector.

    Dans le script MainMenu, j'ai en fait précisé à Unity qu'il devait créer une nouvelle copie de NetworkMaster lorsqu'on appuie sur les boutons "Créer serveur" ou "Rejoindre serveur" du menu principal. Il configure ensuite ce Network Master selon qu'on soit serveur ou client. On a va maintenant configurer le script StartNetwork pour qu'il crée une connexion entre le serveur et les clients.
    • Ouvrez le script StartNetwork dans votre éditeur de code.
    • Supprimez la ligne #pragma strict (si vous codez en javascript). (Elle sert principalement quand on code sous iOS, xbox et ce genre de trucs et agit principalement comme un véritable générateur d'erreurs en mousse^^)
    • Avant la fonction Start() ajoutez une fonction Awake() et placez-y la ligne de code suivante: DontDestroyOnLoad(this);
    Cela évitera que le script et son GameObject ne soient détruits lors du changement de scène.
    • Tout en haut du script, déclarez les variables suivantes
      • var server : boolean;
      • var listenPort : int = 25000; //le port d'écoute du serveur
      • var remoteIP : String; //l'adresse IP du serveur auquel les clients se connecteront
    On a ensuite deux-trois trucs à changer dans notre script MainMenu.
    Tout d'abord, j'ai fait une erreur dans la première version de la partie 1 du tutorial. Donc si vous l'avez suivi hier, un truc à changer:
    • Trouvez la condition suivante: if(GUI.Button(Rect(10, 20, sizeButtonX, sizeButtonY), "Créer serveur"))
    • Dedans j'ai écris la ligne scriptStartNet.server = false; 
    • Changez-la par scriptStartNet.server = true;
    • Ajoutez la ligne scriptStartNet.listenPort = serverPort; juste après
    • La condition if(GUI.Button(Rect(10, 60, sizeButtonX, sizeButtonY), "Rejoindre serveur")) est quant à elle correcte mais on va y ajouter scriptStartNet.remoteIP = serverIP; et scriptStartNet.listenPort = serverPort;
    Votre script MainMenu doit maintenant ressembler à ceci:



    Revenons à notre script StartNetwork et écrivons sa fonction Start().

    Ce script gérant le serveur et les clients, on va commencer par une condition vérifiant si on est serveur ou client:

    if(server)
    {
    Network.InitializeServer(32, listenPort, false); //le false signifie qu'on utilise pas le Nat punchtrough. Je vous recommande la doc d'Unity pour en savoir plus
    // On préviens tous nos objets que le réseau est lancé
    for (var go in FindObjectsOfType(GameObject))
    go.SendMessage("OnNetworkLoadedLevel", SendMessageOptions.DontRequireReceiver);
    }

    La première ligne met réellement en place le serveur. La connexion est ouverte aux clients. Vous aurez probablement besoin d'ajouter le Nat Punchtrough lorsque vous créerez un serveur sur internet, cette fonction va permettre de passer outre certains pare-feu afin de ne pas avoir besoin d'ouvrir les ports manuellement sur sa box/modem.

    La boucle va envoyer un message à tous les GameObjects de la scène pour les avertir que le serveur est lancé.

    Ensuite, ajoutez les lignes suivantes:
    else
    {
    Network.Connect(remoteIP, listenPort);
    }

    Cela va connecter les clients au serveur.

    A ce stade vous devriez être en mesure de builder le jeu, lancer la version buildée et cliquez sur "Créer serveur" puis lancer le jeu dans l'éditeur, cliquer sur "Rejoindre serveur" et tout devrais se passer sans erreurs. Vous ne remarquerez sans doute rien de spécial et pourtant vous venez d'établir votre première connexion Server-Client sous Unity, toutes mes félicitations :)

    Comme je vous sens sceptiques vous pouvez aussi ajouter la fonction suivante:

    function OnPlayerConnected(player: NetworkPlayer)
    {
    if(server)
    {
    print("Connecté !");
    }
    }

    Buildez et lancer le jeu. Lancez-le aussi dans l'éditeur et cliquez sur "Créer serveur" dans l'éditeur puis sur "Rejoindre Serveur" dans la version buildée. Après un court instant  la console de l'éditeur devrait afficher "Connecté !". Il faut prendre le focus de la fenêtre contenant le serveur pour que le client réponde.

    Cette fonction sera lancée à chaque fois qu'un client se connectera au serveur, elle peut notamment être utilisée pour garder le compte des joueurs connectés. La fonction inverse existe également: OnPlayerdisconnected(player:NetworkPlayer)

    Je ne m'attarderai pas sur ce qu'est un NetworkPlayer parce que pour être honnête je ne maîtrise pas très bien ce concept. En gros c'est un peu ce qui relie un client et tous les objets qu'il instanciera sur le réseau.

    Votre code doit désormais ressembler à ceci.


    Lancer le niveau



    Il ne nous reste plus qu'a lancer le level une fois la connexion établie.

    On va commencer par ajouter la fonction suivante à notre script StartNetwork:

    function OnLevelWasLoaded()
    {
    if ( Application.loadedLevelName == "mainMenu")
    Destroy(this.gameObject);
    // Notify our objects that the level and the network are ready
    for (var go in FindObjectsOfType(GameObject))
    go.SendMessage("OnNetworkLoadedLevel", SendMessageOptions.DontRequireReceiver);
    }

    Cela va informer tous les objets de notre level au moment où il sera chargé qu'une connexion existe. On en profite également pour supprimer le NetworkMaster si on revient au menu principal afin d'éviter qu'il soit cloné à chaque fois.

    Revenons à notre fonction Start(). 
    • A la fin de celle-ci, ajoutez Application.LoadLevel("Level");
    Ceci lancera le level qu'on a créé dans la partie 1 (si vous lui avez donnez un autre nom que "Level", changez-le aussi dans le code). Le NetworkMaster doit normalement effectuer ce passage de level avec nous (comme précisé dans la fonction Awake() ).
    • Lancez le jeu pour voir si tout se passe comme prévu (n'oubliez pas d'ajouter le menu et le level dans la liste des niveaux à builder dans File/Build Settings, le menu en première position)
    Vous ne devriez pas avoir d'erreurs et être capable de vous connecter au serveur.

    Instancier les joueurs sur le réseau



    On va maintenant créer un Cuby (notre cube d’assaut) par joueur (serveur y compris).

    Dans la scène "Level", ajoutez un nouvel Empty GameObject. Appellez le "Spawn" et positionnez-le au-dessus du sol (pas trop haut mais pas trop bas non plus pour que Cuby ne spawn pas dans le sol). Taggez le "Spawn" et créez en plusieurs un peu partout, on fera apparaitre Cuby sur un Spawn aléatoire.

    De retour dans notre script StartNetwork() on va ajouter quelques lignes à la fin de la fonction OnLevelWasLoaded()
    • Ajoutez
      • spawners = GameObject.FindGameObjectsWithTag("Spawn");
      • var rand : int = Random.Range(0, spawners.length);
      • var spawn : GameObject = spawners[rand];
    Cela aura pour effet de regarder tous les spawn du niveau et d'en choisir un aléatoirement.

    On va ensuite ajouter une commande dont vous aurez souvent besoin lorsque vous coderez des jeux réseaux:
    • cubyInst = Network.Instantiate(cuby, spawn.transform.position, Quaternion.identity, 0);
    Elle fonctionne globalement comme Instantiate sauf qu'il y a une option en plus. Cette ligne de code va instancier le GameObject "cuby" en réseau à la position de spawn (le Spawn choisi aléatoirement) , orienté normalement (les rotations seront les mêmes que celles du préfab Player).

    Quelle est la différence avec la fonction Instantiate normale alors?, me direz-vous. En fait l’instanciation va se passer sur tous les clients connectés et sur le serveur et pas seulement sur la machine exécutant le jeu. Ça signifie que votre Cuby sera créé chez tout le monde. Avec une instanciation normale, seul vous seriez capable de voir votre perso. Peu pratique pour faire des headshots aux ennemis^^.

    Le dernier argument désigne le groupe de clients chez qui on instancie notre Player/Cuby. On va le laisser à 0 pour qu'il spawn chez tout le monde.

    Il ne reste plus qu'à déclarer les variables qu'on vient d'utiliser. 
    • Au début du script, déclarez:
      • private var spawners : GameObject[];
      • var cuby : GameObject;
      • var cubyInst : GameObject;
    Retournez sur Unity et assigner notre Prefab Player à la variable cuby en le faisant glisser dans l'inspector.


    Si vous lancez votre jeu (depuis le menu principal) et que vous créez un serveur, votre Cuby devrais spawner et arriver.. de travers... 
    • Maudissez les quaternions auquel je n'ai jamais rien compris puisqu'une rotation n'est pas censée être découpée en 4 valeurs, c'est contre nature...
    • Ajoutez cubyInst.transform.eulerAngles = Vector3(0, 0, 90); après cubyInst = Network.Instantiate(cuby, spawn.transform.position, Quaternion.identity, 0);
    On va également ajouter un petit délai au client avant qu'il n'instancie son Cuby pour lui laisser le temps de se connecter au serveur.

    Ajoutez:
    if(!server)
    yield WaitForSeconds(3);
    avant cubyInst = Network.Instantiate(cuby, spawn.transform.position, Quaternion.identity, 0);

    Relancez le jeu et ça devrais fonctionner.
    Remarque:
    Quand vous lancez client et serveur sur une seule machine vous remarquerez que rien ne se passe côté client si vous ne cliquez pas de temps en temps sur la fenêtre serveur. Cela est dû au fait qu'Unity met le jeu en pause quand vous perdez le focus de la fenêtre.
    • Pour régler ça, dans Unity, cliquez sur Edit/Project Settings/Player
    • Dépliez le panneau "Settings for PC and Mac Standalone"
    • Cochez finalement la case "Run in Background"
    Votre code doit ressembler à ça


    Vous pouvez maintenant lancer le jeu en tant que serveur et client (lancez le deux fois, créer un serveur et connectez-vous sur 127.0.0.1 avec le client

    Au moment où vous lancerez votre jeu, vous devriez normalement vous dire un truc du genre:

    Oh mais ça marche !
    ...Hey mais... ! Attends une minute... Beldir mais tu n'es qu'un sale charlatan !! Le joueur adverse est de travers et en plus il avance en même temps que moi et je ne vois pas ses déplacements à lui !!

    Pas de panique, tout cela est tout à fait normal à ce stade.

    Séparer le fonctionnement de nos objets et celui de ceux de l'adversaire



    Nous avons trois problèmes

    • Ce n'est pas vous qui avez instancié le joueur adverse, et vous ne lui avez donc pas dit de se tourner correctement. 
    • De plus le joueur adverse est exactement comme le votre. C'est à dire qu'il possède un script Cuby.js qui réagit donc à vos frappes de touches.
    • On ne voit pas les déplacements que l'adversaire effectue de son côté
    Nous allons tout de suite remédier au troisième problème et vous verrez que le premier se réglera en même temps. On verra ensuite comment empêcher le déplacement de l'ennemi quand on appuie sur nos touches à nous.
    • Dans Unity, sélectionnez votre Prefab Player et ajoutez lui une NetworkView (Component/Miscellaneous/Network View)
    Problème réglé ! La NetworkView va synchroniser la position et l'orientation de notre Cuby local et de notre Cuby tel qu'il apparaît sur l'écran des adversaires. N'hésitez pas à consulter la doc d'Unity pour en apprendre plus sur les NetworkView.

    Mais l'ennemi se déplace toujours en même temps que nous et la console spamme plein d'avertissements.
    Le spam est dû au fait qu'on ait deux caméras activées avec un AudioListener dessus dans notre scène. En plus de spammer la console cette seconde caméra consomme aussi beaucoup de mémoire.

    On va régler ça tout de suite.
    • Ouvrez le script Cuby et ajoutez les lignes suivantes dans la fonction Start():
    if ( !networkView.isMine) //si ce perso ne m'appartient pas
    {
    Destroy(cameraPlayer);
    this.enabled = false;
    }

    Cela va dire à Unity de désactiver ce script si la NetworkView associée à l'objet le contenant n'est pas à nous. Autrement dit, si l'objet n'a pas été instancié par nous sur le réseau.

    On en profite pour désactiver la caméra attachée aux Cuby ne nous appartenant pas.

    • Déclarez la variable var cameraPlayer : GameObject; dans le script et associez-y la caméra attachée à Cuby dans les Préfabs par glisser/déposer. 


    Je vous met le code qu'on vient de taper en même temps (ne faites pas attention à la variable "life" que j'ai finalement laissée tomber dans la partie 3^^).

    Conclusion



    Voila, c'est tout pour cette partie 2. Vous devriez maintenant être capable de vous promener dans le niveau et de connecter autant de joueurs que vous voulez. Votre jeu doit pouvoir fonctionner en réseau local mais aussi sur internet. S'il ne fonctionne pas sur internet, pensez à essayer d'activer le Nat punchtrough ou utiliser hamachi ;) (ou Tunngle qui est mieux, héhé).

    Retenez bien ce "networkView.isMine". Vous l'utiliserez extrêmement souvent. D'abord pour, comme ici, choisir quels scripts doivent être ou ne pas être activés sur un objet instancié sur le réseau, mais aussi pour plein d'autres raisons dont aucune ne me vient à l'esprit pour l'instant.

    Lorsque vous créez un script, demandez-vous toujours s'il sera oui ou non activé pour tout le monde ou seulement par celui qui l'instancie. Par exemple j'ai mis la vie de Cuby dans le script Cuby.js qu'on vient de désactiver pour tous les autres joueurs... Je sais ce que je fais et c'est pour vous montrer quelque chose plus tard mais il aurait peut-être été plus facile en conditions normales de créer un script InfoPlayer contenant ce genre d'informations d'états du joueur.

    Voila voila.

    Dans la prochaine partie du tutorial on verra comment gérer les tirs de nos Cuby (vous devriez déjà avoir une idée) et on rendra le jeu plus ou moins jouable.

    Si ça ne fonctionne pas chez vous, je vous recommande de relire tout mais si vraiment vous ne vous en sortez pas voila mon projet Unity, là où nous en sommes arrivés:

    Télécharger le fichier.

    A tout de suite dans la partie 3 !


    vendredi 9 novembre 2012

    Tutorial: La mise en réseau sous Unity3D - Partie 1

    [Edit - 02/03/2017] Salut lecteur !

    J'attire ton attention sur l'âge de ce tuto, qui est sorti fin 2012. Il y a peut-être encore du bon a en tirer mais je pense que dans l'ensemble il doit être très obsolète.

    Si tu parles anglais, je te conseillerais plutôt d'aller suivre un des tutos officiels Unity, comme celui-ci qui a l'air très bien fait... et si tu ne parles pas anglais et ben, va suivre des cours d'anglais^^

    Enfin, si tu choisis de quand-même suivre ceci, hé ben bonne lecture :)





    Hellow !

    Un billet bien différent de d'habitude aujourd'hui mais je trouve que les tutos sur ce sujet sont beaucoup trop rares. Je dois expliquer à un ami comment faire un jeu jouable en ligne avec Unity3D donc tant qu'à faire, autant mettre ça à disposition de tout le monde.

    Je précise que je suis loiiiiiiiin d'être un pro et que ce tuto n'est en rien une méthode optimale ou complète pour mettre son jeu en réseau, c'est plus à prendre comme une base pour se lancer. J'espère ne pas faire trop d'erreurs et ne pas vous donner de mauvais conseils, les commentaires sont là pour ça sinon ;)

    Je pars du principe que vous savez déjà utiliser Unity et que vous êtes plus ou moins à l'aise avec ses concepts de base telles que les Collision Box, les GameObjects, etc. Je code en javascript, normalement vous ne devriez pas avoir de mal à passer le code en C# si besoin est, désolé de ne pas le fournir :S

    Ce petit guide sera en plusieurs parties (probablement 3) puisque il prend pas mal de temps à écrire.

    Trève de blabla, c'est parti....

    Mise en place des prérequis



    Pas vraiment de réseau en soi dans cette première partie mais il faut bien un début à tout

    On va commencer par créer une scène de base pour nos tests.

    Vous pouvez aussi commencer directement dans un projet existant si le coeur vous en dit mais si c'est la première fois que vous vous essayez au jeu en réseau je vous conseille de démarrer un nouveau projet. Contrairement à ce qu'il m'est arrivé de lire à gauche et à droite, le réseau n'est pas du tout un simple élément qu'on ajoute sur un jeu fini (pas en tant que débutant en tout cas^^). Ça demande toute une méthodologie et une manière de penser le rapport entre nos objets différente.

    Vous êtes prêts? Bien

    Joueur et terrain

    • Créons d'abord un simple cube qu'on appellera "Player" (je code généralement en anglais puisque les noms de variables ne peuvent pas contenir d'accents). 
    • Ajoutez lui un RigidBody avec les options suivantes pour qu'il subisse la gravité sans rouler partout.




    Vous l'avez deviné ce cube représentera notre joueur (son petit nom c'est Cuby), placez la caméra derrière lui et attachez là au cube afin qu'elle suive le joueur lors de ses déplacements.
    • Créez un nouveau cube, nommez le "Gun" et attachez le à Cuby (votre cube-joueur donc).
    •  Positionnez et redimensionnez ce nouveau cube comme si c'était un bras de Cuby, ça représentera son arme. 
    • Supprimez le composant "Box Collider" de cette arme afin d'éviter que les munitions qu'on tirera plus tard ne "se cognent" contre l'arme au moment où elles sont tirées (on pourrait aussi leur dire d'ignorer les collisions avec cet objet en particulier si on a vraiment besoin d'une box sur l'arme mais je fais au plus simple).
    • Ensuite on va créer un nouveau cube pour le terrain, on l'appellera "Terrain" parce que nous sommes des gens originaux. Je donne à mon terrain les dimensions 40x1x40. Je vous conseille de lui ajouter un material de couleur ou une texture pour qu'on voit clairement la scène. Ajoutez aussi une directional light afin d'éclairer le niveau.

    Vous devriez obtenir quelque chose dans ce goût là.

    Cliquez sur les images pour les voir en taille réelle
    J'ai ajouté une texture au terrain pour bien voir les déplacements de mon cube mais c'est facultatif.

    Bien, on a notre joueur et il est paré pour la guerre. il ne manque plus que...

    Un peu d'action !


    Il va falloir apprendre à Cuby à se déplacer s'il veut survivre sur le champs de bataille.

    • Jusqu'ici il n'y a rien de propre au réseau donc contentez-vous d'attacher le script suivant à votre cube, il n'est pas très compliqué. Ce script s'appelle Cuby.js et contiendra quasiment tout le code gérant notre petit cube préféré (pour de plus gros projets, mieux vaut séparer le fonctionnement du perso en plusieurs scripts).


    Cuby.js

    private var vitesse : float = 0.05;

    function Start () {

    }

    function Update ()
    {
    Deplacement();
    }

    function Deplacement()
    {
    if ( Input.GetKey(KeyCode.Z) || Input.GetKey(KeyCode.UpArrow) )
    transform.Translate(Vector3.forward * vitesse);
    else if ( Input.GetKey(KeyCode.S) || Input.GetKey(KeyCode.DownArrow) )
    transform.Translate(Vector3.back * vitesse);
    if ( Input.GetKey(KeyCode.Q) || Input.GetKey(KeyCode.LeftArrow) )
    transform.Rotate(Vector3.left * 2);
    else if ( Input.GetKey(KeyCode.D) || Input.GetKey(KeyCode.RightArrow) )
    transform.Rotate(Vector3.right * 2);

    if ( Input.GetKeyDown(KeyCode.Space) ) //le saut
    rigidbody.AddForce(Vector3.up * 350);
    }


    • Vérifiez que votre cube se déplace bien comme prévu (avec les touches fléchées ou ZSQD et espace pour sauter).
    On ne va pas apprendre tout de suite à tirer à Cuby car cela demande une connexion au réseau. Laissons lui encore un peu son innocence de jeune cube.

    On va par contre déjà créer les balles qu'il tirera.

    • Créez une nouvelle sphère et dimensionnez-là comme bon vous semble. Je donne une taille de 0.2 à la mienne. Je la nomme également "Bullet".
    Puisque on créera une copie de cette balle à chaque fois que le joueur tirera, on va la transformer en Prefab.

    • Créez un nouveau dossier "Prefabs" dans votre projet et placez y votre balle (glissez-déposez de l'onglet Hierarchy vers Project). Une fois cela fait, vous pouvez supprimer la balle de la scène.
    Bien, il est temps de laisser Cuby se reposer.

    • Puisque chaque joueur devra instancier son propre Cuby, on va en faire un prefab, glissez-le à son tour dans le dossier Prefabs. Une fois cela fait supprimez le de votre scène.

    Votre projet doit maintenant ressembler à ceci:



    Sauvegardez si ce n'est pas déjà fait puis créez une nouvelle scène.

    Il va nous falloir...

    Un menu


    Ça peut paraître gadget mais au début où je bossais sur Cold World, j'avais tout d'abord construit le premier level sans menu, on arrivais directement sur la map avec l'interface de connexion. L'ajout d'un menu a été un véritable enfer puisque plus rien ne marchait et que je ne comprenais alors rien de rien à la mise en réseau sous Unity. Autant s'éviter des complications plus tard et directement créer le menu principal du jeu.

    Bref !

    • Créez un nouveau script que nous appellerons "MainMenu". Attachez-le à la caméra normalement déjà présente dans la scène. 
    • Créez également un script nommé "StartNetwork". N'y touchez pas pour l'instant.
    • A l'intérieur de MainMenu, copiez ceci:

    var networkMaster : GameObject; // Prefab

    private var instantiatedMaster : GameObject; //Prefab instancié
    private var scriptStartNet : StartNetwork;

    private var serverIP : String = "127.0.0.1";
    private var serverPort : int = 25000;

    function OnGUI()
    {
    var menuSizeX : int = 460;
    var menuSizeY : int = 115;
    var menuPosX : float = 20;
    var menuPosY : float = Screen.height/2 - menuSizeY/2;
    var mainMenu = new Rect(menuPosX, menuPosY, menuSizeX, menuSizeY);
    var sizeButtonX : int = 250;
    var sizeButtonY : int = 30;

    //Le menu de base
    GUI.BeginGroup(mainMenu, "");
    GUI.Box(Rect(0,0,menuSizeX, menuSizeY), "");

    //La demande de champs d'ip pour rejoindre un serveur
    serverIP = GUI.TextField(new Rect(sizeButtonX + 30, 60, 120, 30), serverIP, 40);

    ifGUI.Button(Rect(10, 20, sizeButtonX, sizeButtonY), "Créer serveur"))
    {
    //Création du serveur
    instantiatedMaster = Instantiate(networkMaster, Vector3.zero, Quaternion.identity);
    scriptStartNet = instantiatedMaster.GetComponent("StartNetwork");
    scriptStartNet.server = true;
    }
    if ( GUI.Button(Rect(10, 60, sizeButtonX, sizeButtonY), "Rejoindre serveur"))
    {
    //Rejoindre serveur
    instantiatedMaster = Instantiate(networkMaster, Vector3.zero, Quaternion.identity);
    scriptStartNet = instantiatedMaster.GetComponent("StartNetwork");
    scriptStartNet.server = false;
    }
    GUI.EndGroup();
    }

    Si vous lancez le script, vous devriez normalement voir ceci:



    Ne paniquez pas s'il y a des erreurs et que rien ne se passe lorsque vous cliquez sur les boutons, c'est tout à fait normal à ce stade.

    Bien. Je pense que tout est prêt ou presque. Dans la prochaine partie du guide, on va pouvoir s'attaquer à la partie réseau, oui oui, pour de vrai ! Ne vous en faites pas j'expliquerai rapidement les trucs propres au réseau du script que vous venez de copier.

    A demain pour la suite ;)

    PS: d'ici là j'essayerai de trouver un moyen d'afficher le code plus proprement sur le blog.

    La partie 2 est disponible par-ici.