Hachaisse le site

19 décembre 2013

Tutorial pour Unity 3D (4)

Classé dans : Article, Informatique, Tutoriel — admin @ 12 h 07 min

Troisième partie du tutorial : http://raphp.fr/blog/?p=312

Création du noyau du jeu

Notre jeu de carte démarre correctement, mais avant de tourner les cartes, il faudrait déjà avoir les bonnes cartes dans nos paquets !

Il nous faut donc ajouter une simulation de jeu de carte. J’ai écrit rapidement une simulation de paquet, de mélange et de donne de cartes.

Sûrement pas le plus élaboré mais c’est suffisant.

Nous allons créer 2 scripts c# :

Card.cs

public class Card {

public enum CardType

{

Heart = 1,

Spade = 2,

Diamond = 3,

Club = 4

}

public Card(CardType ct, int v )

{

Type = ct;

Value = v;

}

public CardType Type;

public int Value;

}

CardsGame.cs

public class CardsGame {

// Heart, Club, Spade, Diamond

protected Card[]Sorted = new Card[52];

ArrayList MixedCards = new ArrayList();

ArrayList Trash = new ArrayList();

public CardsGame () {

int iSorted = 0;

for( int c = 1; c <= 4; c++)

{

Card.CardType cardtype = (Card.CardType)c;

for(int i = 1; i <= 13; i++ )

{

Sorted[iSorted++] = new Card(cardtype, i);

}

}

}

public void FirstMix()

{

//Card[]Mixed = new Card[52];

System.Random r = new System.Random(new System.Random().Next(0,5000));

ArrayList al = new ArrayList();

MixedCards = new ArrayList();

Trash = new ArrayList();

for(int i = 0; i < 52; i++ )

al.Add(Sorted[i]);

Debug.Log(« Nombre de carte à mélanger :  » + al.Count);

//int iMixed = 0;

while(al.Count > 0)

{

int random = r.Next(0,al.Count-1);

Debug.Log(« random :  » + random);

Card find = (Card)al[random];

Debug.Log(« Carte :  » + find.Type +  » –  » + find.Value);

MixedCards.Add(al[random]);

al.RemoveAt(random);

}

}

public void Mix()

{

Debug.Log(« Tri des cartes »);

if(Trash.Count > 0 && MixedCards.Count > 0)

{

while(MixedCards.Count > 0 )

{

Trash.Add(MixedCards[0]);

MixedCards.RemoveAt(0);

}

}else{

Debug.Log(« Pas de poubelle, on recupere les cartes tries »);

Trash = new ArrayList();

for(int i = 0; i < 52; i++ )

{

//Debug.Log(Sorted[i].Value);

Trash.Add(Sorted[i]);

//Card c = (Trash[i] as Card);

//Debug.LogError(« Carte dans la poubelle a la position  » + i +  » :  » + c.Value);

}

}

//Card[]Mixed = new Card[52];

System.Random r = new System.Random(new System.Random().Next(0,5000));

ArrayList al = new ArrayList();

MixedCards = new ArrayList();

//Trash = new ArrayList();

for(int i = 0; i < 52; i++ )

{

Card c = (Trash[i] as Card);

if( c == null )

{

Debug.LogError(« Pas de carte dans la poubelle a la position  » + i);

}

Debug.Log( c.Value);

al.Add(Trash[i]);

}

Debug.Log(« Nombre de carte à mélanger :  » + al.Count);

//int iMixed = 0;

while(al.Count > 0)

{

int random = r.Next(0,al.Count-1);

Debug.Log(« random :  » + random);

Card find = (Card)al[random];

Debug.Log(« Carte :  » + find.Type +  » –  » + find.Value);

MixedCards.Add(al[random]);

al.RemoveAt(random);

}

}

public Card NextCard()

{

Card c = null;

if( MixedCards.Count > 0 )

{

c = (Card)MixedCards[0];

MixedCards.RemoveAt(0);

}

return c;

}

public void AddTrash(Card c)

{

Trash.Add(c);

}

}

Pour notre jeu de bataille, chaque joueur doit garder la carte plutôt que la remettre dans le paquet d’origine alors on ajoute cette classe :

PlayerCards.cs

public class PlayerCards

{

public ArrayList Pack

{

get;

private set;

}

public ArrayList Win

{

get;

private set;

}

public PlayerCards()

{

Pack = new ArrayList();

Win = new ArrayList();

}

public void AddPack(Card c)

{

Pack.Add(c);

}

public void AddWin(Card c)

{

Win.Add(c);

}

public Card GetCard()

{

Card c = null;

if( Pack.Count > 0 )

{

c = (Card)Pack[0];

Pack.RemoveAt(0);

}

return c;

}

public void MixWinToPack()

{

if( Pack.Count > 0 )

{

Debug.Log(« Impossible de remettre dans le paquet tant qu’il y a encore des cartes à jouer »);

return;

}

//Card[]Mixed = new Card[52];

System.Random r = new System.Random(new System.Random().Next(0,5000));

ArrayList al = new ArrayList();

Debug.Log(« Nombre de carte à mélanger :  » + Win.Count);

while(Win.Count > 0)

{

int random = r.Next(0,Win.Count-1);

Debug.Log(« random :  » + random);

Card find = (Card)Win[random];

Debug.Log(« Carte :  » + find.Type +  » –  » + find.Value);

Pack.Add(al[random]);

Win.RemoveAt(random);

}

}

}

Celle-ci pourra simuler le paquet de cartes à jouer et la pile de cartes gagnées contre l’adversaire.

Ensuite on se servira de notre script de GUI comme noyau.

On ajoute le membre suivant pour gérer un paquet de carte :

CardsGame mCardsPack;

Et pour gérer les cartes des joueurs :

PlayerCards Player1_Cards;

PlayerCards Player2_Cards;

On complète la méthode Start qui va initialiser les listes.

void Start () {

Debug.Log(« MyGUI – Start »);

Player1_Cards = new PlayerCards();

Player2_Cards = new PlayerCards();

Player1_3DCardsWin = new ArrayList();

Player2_3DCardsWin = new ArrayList();

Player1_3DCardsPack = new ArrayList();

Player2_3DCardsPack = new ArrayList();

}

Maintenant on ajoute la méthode de départ d’un jeu :

void NewGame()

{

mCardsPack = new CardsGame();

mCardsPack.FirstMix();

Player1_Cards = new PlayerCards();

Player2_Cards = new PlayerCards();

for(int i = 0; i < 26 ; i++)

{

Card c1 = mCardsPack.NextCard();

Player1_Cards.AddPack(c1);

Card c2 = mCardsPack.NextCard();

Player2_Cards.AddPack(c2);

}

RedrawCards(1);

RedrawCards(2);

}

La simulation de la donne : dans une bataille on distribue une carte chacun jusqu’à la fin des 52 cartes.

Ensuite on crée une pile de carte pour chaque joueur.

Ainsi que la méthode qui permet de créer les cartes dans la scène :

void RedrawCards(int player)

{

if( player == 1 )

{

for(int i = 0; i < Player1_3DCardsPack.Count; i++ )

Destroy((GameObject)Player1_3DCardsPack[i]);

Player1_3DCardsPack.Clear();

for(int i = 0; i < Player1_3DCardsWin.Count; i++ )

Destroy((GameObject)Player1_3DCardsWin[i]);

Player1_3DCardsWin.Clear();

for(int i = 0; i < Player1_Cards.Pack.Count; i++ )

Add3DCard(PlayerOne, ref Player1_3DCardsPack, -0.1f, « PlayerOne_Pack » + i);

for(int i = 0; i < Player1_Cards.Win.Count; i++ )

Add3DCard(PlayerOne, ref Player1_3DCardsWin, -0.3f, « PlayerOne_Win » + Player1_3DCardsWin.Count, « PlayerOneWin »);

}

if( player == 2 )

{

for(int i = 0; i < Player2_3DCardsPack.Count; i++ )

Destroy((GameObject)Player2_3DCardsPack[i]);

Player2_3DCardsPack.Clear();

for(int i = 0; i < Player2_3DCardsWin.Count; i++ )

Destroy((GameObject)Player2_3DCardsWin[i]);

Player2_3DCardsWin.Clear();

for(int i = 0; i < Player2_Cards.Pack.Count; i++ )

Add3DCard(PlayerTwo, ref Player2_3DCardsPack, +0.1f, « PlayerTwo_Pack » + i);

for(int i = 0; i < Player2_Cards.Win.Count; i++ )

Add3DCard(PlayerTwo, ref Player2_3DCardsWin, +0.3f, « PlayerTwo_Win » + Player2_3DCardsWin.Count, « PlayerTwoWin »);

}

}

Et on complète notre bouton démarrer avec la donne :

if( GUI.Button(new Rect(10f,MenuY += MenuHeight + MenuBlank,MenuWidth,MenuHeight), « Démarrer une partie ») )

{

mMenuStep = MenuStep.InGame;

mGameStep = GameStep.PlayerOneBeforeTurn;

PlayerOne.transform.rotation = Quaternion.Euler(0,0,0) ;                                                PlayerTwo.transform.rotation = Quaternion.Euler(0,0,0);

NewGame();

}

Nos joueurs ont désormais leurs paquets de cartes et une pile de cartes gagnantes.

Remarquons que je n’ai pas changé le tag du paquet, le 5ème paramètre n’est pas présent, il prend donc la valeur définie par défaut dans la méthode, c’est-à-dire « », et donc ne change pas le tag de l’objet.

Alors si on clique sur le paquet, on aura le même effet que celui de cliquer sur la carte visible. Mais pas sur le paquet de victoire.

Pour retirer la carte du paquet lorsqu’on prend une carte, j’ai modifié la méthode OnClick :

case « PlayerOneCard »:

if( playerturn==1 )

{

Debug.Log(« Click : PlayerOneCard! »);

OnClickCard(1);

PlayerOne.renderer.enabled = true;

}

Break ;

case « PlayerTwoCard »:

if( playerturn==2 )

{

Debug.Log(« Click : PlayerTwoCard! »);

OnClickCard(2);

PlayerTwo.renderer.enabled = true;

}

break;

Et en ajoutant :

void OnClickCard(int player)

{

if( player==1 )

{

mGameStep = GameStep.PlayerOneTurning;

Remove3DCard(ref Player1_3DCardsPack);

}

if( player==2 )

{

mGameStep = GameStep.PlayerTwoTurning;

Remove3DCard(ref Player2_3DCardsPack);

}

}

Cependant, les cartes ne représentent toujours pas les vraies cartes !

Alors on va affecter la texture de la bonne carte lorsqu’on tourne notre carte.

J’ajoute les membres suivant pour connaître la carte jouée actuellement de chaque côté :

Card Player1_CurrentCard, Player2_CurrentCard;

Je modifie de suite ma méthode OnClickCard :

void OnClickCard(int player)

{

if( player==1 )

{

mGameStep = GameStep.PlayerOneTurning;

Remove3DCard(ref Player1_3DCardsPack);
Player1_CurrentCard= Player1_Cards.GetCard();

UpdateCurrentCard(PlayerOne, Player1_CurrentCard);

}

if( player==2 )

{

mGameStep = GameStep.PlayerTwoTurning;

Remove3DCard(ref Player2_3DCardsPack);
Player2_CurrentCard= Player2_Cards.GetCard();

UpdateCurrentCard(PlayerTwo, Player2_CurrentCard);

}

}

Et j’ajoute celle-ci :

void UpdateCurrentCard(GameObject go, Card c)

{

string resource = «  »;

Debug.Log(c.Type +  » –  » + c.Value);

string val = c.Value.ToString();

if( c.Value == 1 )

val = « A »;

else if( c.Value == 11 )

val = « J »;

else if( c.Value == 12 )

val = « Q »;

else if( c.Value == 13 )

val = « K »;

else

{

}

switch( c.Type )

{

case Card.CardType.Club:

resource = val + « c »;

break;

case Card.CardType.Diamond:

resource = val + « d »;

break;

case Card.CardType.Heart:

resource = val + « h »;

break;

case Card.CardType.Spade:

resource = val + « s »;

break;

}

if( !string.IsNullOrEmpty(resource) )

{

Debug.Log(« Chargement texture :  » + resource);

Object res = Resources.Load(resource);

if( res != null )

{

Debug.Log(« Recuperation texture :  » + resource);

Material newMat = Resources.Load(resource, typeof(Material)) as Material;

if( newMat != null )

{

Debug.Log(« Attribution texture :  » + resource);

go.renderer.material = newMat;

}else{

Debug.LogError(« Impossible de retrouver la ressources :  » + resource);

}

}

else{

Debug.LogError(« Impossible de retrouver la ressources :  » + resource);

}

}

}

Alors attention, j’ai dû copier intégralement le répertoire des matériaux des cartes dans un autre répertoire « Assets\ Resources\ »

Donc tous les matériaux présent dans les sous-répertoires « Assets\BGA\Poker Pack\Models\Card\Materials\ » sont copiés dans le même sous-répertoire « Assets\ Resources\ »

Si je fais ceci, c’est que la fonction = Resources.Load() recherche par défaut dans ce répertoire.

Donc ici

Material newMat = Resources.Load(« 2c », typeof(Material)) as Material;

Ira charger le matériau « Assets\Ressources\2c.mat »

On a plus qu’à changer l’apparence d’un objet en passant par son renderer :

go.renderer.material = newMat;

Maintenant notre jeu ressemble à ceci lorsque l’on tourne nos cartes :

La finalisation du jeu

Je ne vais pas détailler la suite du code, il n’y a plus rien qui ne concerne Unity3D.

Il suffit de modifier le code pour gérer la reprise du paquet gagnant pour le remettre dans le paquet de jeu, de gérer la victoire, je rappelle que mon objet Player1_CurrentCard est de type Card, il suffit de tester le .Value pour connaître la puissance de la carte (transformer le 1 (as) en 14 (pour être plus fort que le roi 13).

Ne vous en faites pas, voici un ZIP contenant le projet complet pour vous aider ;)

media.visyr.ch/Unity3D/Tuto01/Tuto01HSVersion.zip

Et je rappelle l’exemple en live :

http://media.visyr.ch/Unity3D/Tuto01/

Par contre, par respect envers le créateur des modèles de cartes à 2$, je ne vous fournis pas les objets. Du moins pas totalement. Même si c’est libre commercialement, je ne suis pas sûr que cela soit autorisé de donner les objets eux-mêmes.

Je ne vous laisse que les cœurs copiés en carreaux, piques et trèfles. Donc vous aurez 4 x 2 de cœurs, 4 x 3 de cœurs et ainsi de suite.

Si vous souhaitez vraiment terminer le jeu, achetez ses modèles ou créez les. Personnellement un petit don de 2$ ne fait de mal à personne.

Je termine donc ce chapitre sur la création d’un .exe ou une application Android :

Donc sur un build « executable », dans PlayerSettings nous avons ce genre d’info :

J’ai choisi une icône présente dans le projet, à vous de mettre la vôtre.

J’ai mis mon nom de société et mon nom de produit.

Vous pouvez définir la résolution par défaut, plein écran ou non.

Il y a des détails impossibles à modifier à moins d’être en pro, comme la gestion de directx11 par exemple.

Pour un .exe, c’est assez simple.

Pour une version Android, il faut faire attention.

Techniquement, avant de vous lancer dans une app Android, vous devez vérifier la version qui vous intéresse !

En fait la technologie pour Android a évolué et continuera dans ce sens.

Cela devient trop technique pour moi, mais certains téléphones/tablettes ne sont compatibles qu’avec une technologie ArmV6 ou ArmVX, ma tablette Asus tf201 est en ArmV7 par exemple.

Ici Unity3D v4.0 et plus ne supporte que la techno ArmV7 !

Donc tout va bien pour moi.

Mais si je devais ou voulais commercialement être ouvert à tout je devrais faire du ArmV6 également, et autre.

Or… Il faut utiliser Unity3D 3.0 et plus. Heureusement la licence est compatible peu importe la version du logiciel.

Donc il faudra jongler sur deux (ou plus) installations différentes et copier le projet de l’un vers l’autre. Vous ne devriez pas rencontrer de gros problème de compatibilité, sauf si vous utilisez mecanim par exemple… Uniquement sur v4.

L’angoisse hein ?

Bref, il faut être prudent sur notre demande ou notre objectif.

Donc le playersettings de l’Android ressemble lui à ceci :

Le début est pareil, mais dans Other Settings, ça deviens « sympa ».

Comme Android est avant tout du Java, il répond à certaines normes de celui-ci.

Il faut mettre le package de java. Com.Company…

Chaque projet java est défini de la sorte. Donc on y met en général le site web privé ou pro en inversé.

Dans mon cas je mettrais ch.visyr.battlecard. Je n’ai pas testé, mais sauf erreur en java c’est minuscule. Donc je vous recommande d’éviter la majuscule comme le montre l’exemple.

Ensuite l’API, lié à la version d’Android.  Exemple Ice Cream Sandwich est la version Android 4.0 API Level 14 et plus.

Donc là encore ne vous limitez pas à une partie seulement, si vous pouvez mettre le moins c’est mieux. Mais c’est vous qui savez quelle version vous arrivez à supporter et voulez supporter.

Le device Filter, donc ici uniquement la v7 alors qu’en v3 on a le choix.

Après il y a des choix sur l’installation, préférence pour carte sd externe ou interne, les types de droits requis pour le web, etc.

Donc voilà, vous pouvez compiler votre jeu très facilement, il suffit de faire attention à l’objectif et les produits qui le supporterons.

Dans mon cas d’application professionnelle, le téléphone d’un collègue m’oblige à utiliser la version 3.5.7 pour compiler. Mais aucun problème de compatibilité,  normal, c’est pratiquement que du Gui et de l’accès Web pour interroger un webservice. Qui fera l’objet je pense d’un prochain tutorial.

Je n’ai pas de Mac pour tester une compilation Mac, et je m’en excuse. Mais un mec dont son passe-temps était de faire du directx en c# sur visual studio et qui vous ferait un tuto pour un Mac… Soyons d’accord. C’est n’importe quoi.

Pour discuter de l’article, c’est ici : http://raphp.fr/fofo/viewtopic.php?f=2&t=2339

Propulsé par WordPress