Dofus Battle Arena

Jeu de plateau 3D (inspire de Dofus)

a marqué ce sujet comme résolu.

Bonjour,

je voudrais essayer de faire un petit jeu avec Unity et je voudrais savoir quel est le mieux entre animer les models dans Blender au moment de la modelisation ou bien l'animer dans Unity apres importation ? D'ailleurs au passage quel est le meilleur format de fichier pour exporter un model Blender dans Unity ? .blend ? .fbx ?

Mon projet s'apparenterait donc a un jeu de plateau 3D pour tu versus. En gros chaque joueur controlera un ou plusieur personnages (differentes classes seront disponible sinon c'est pas drole), pourra les bouger sur le plateau de case en case, faire des actions telles qu'attaquer, lancer un sort le tout limite par un certaine nombre de point d'action et de mouvement pas tour car oui le jeu sera en tour par tour.

Pourquoi ce projet ? Car j'ai toujours trouve la dimension strategique de Dofus assez bien foutu et surtout tres sous estimee. En effet ce jeu beneficie d'une image assez peu flatteuse de par un partie de ses utilisateurs qui sont jeune mais ce jeu est bien plus complique qu'il n'y parait croyez moi. Alors certes dans les premiers niveaux et contre les premiers boss c'est assez simple mais il suffit de regarder la difficulte des derniers donjons sortis pour se rendre compte qu'un simple gamin de 10 ans qui fonce tete baissee ne les passera jamais.

Ainsi c'est toute cette dimension strategique que j'aimerai implanter dans mon jeu. Pour le moment ca se resumera donc a un petit jeu dans lequel j'essaye de reproduire cela.

Merci d'avance

Edit Arius: déplacement du sujet :)

EDIT : Premier screenshot du jeu. Pas de texture et modeles provenant de l'asset store (sauf la maison que j'ai faite) mais c'est surtout pour pouvoir tester toutes les fonctionnalites implementees et surtout le Astar

lien vers demo youtube

Bon du coup j'ai commence a implementer le deplacement avec des cubes comme joueurs ( deplacement case par case avec alternance de joueur). Du coup autre question : mon script qui gere les deplacements de chaque joueur se trouve dans la MainCamera et je passe des references aux joueurs en variable public. Est-ce acceptable ou est il plus propre d'affecter un script de deplacement sur chacun des cubes afin d'eviter les variables publiques ? (code dans la balise suivante)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
using UnityEngine;
using System.Collections;

public class CubeController : MonoBehaviour {
  Vector3 movement;
  private int currentTour = 0;
  public GameObject player1;
  public GameObject player2;
  // Use this for initialization
  void Start () {
      movement = new Vector3 (0f, 0f, 0f);
      currentTour = 1;
  
  }
  
  // Update is called once per frame
  void Update () {
      if (currentTour % 2 == 1) 
      {
                      if (Input.GetKeyDown (KeyCode.UpArrow)) {
                              movement = new Vector3 (0f, 0f, 1f);
                              player1.transform.position += movement;
                              currentTour += 1;
                              Debug.Log (currentTour);
                      }
                      if (Input.GetKeyDown (KeyCode.DownArrow)) {
                              movement = new Vector3 (0f, 0f, -1f);
                              player1.transform.position += movement;
                              currentTour += 1;
                              Debug.Log (currentTour);
                      }
                      if (Input.GetKeyDown (KeyCode.LeftArrow)) {
                              movement = new Vector3 (-1f, 0f, 0f);
                              player1.transform.position += movement;
                              currentTour += 1;
                              Debug.Log (currentTour);
                      }
                      if (Input.GetKeyDown (KeyCode.RightArrow)) {
                              movement = new Vector3 (1f, 0f, 0f);
                              player1.transform.position += movement;
                              currentTour += 1;
                              Debug.Log (currentTour);
                      }
      }
      else if (currentTour % 2 == 0) 
      {
                      if (Input.GetKeyDown (KeyCode.UpArrow)) {
                          movement = new Vector3 (0f, 0f, 1f);
                          player2.transform.position += movement;
                          currentTour += 1;
                          Debug.Log (currentTour);
                      }
                      if (Input.GetKeyDown (KeyCode.DownArrow)) {
                          movement = new Vector3 (0f, 0f, -1f);
                          player2.transform.position += movement;
                          currentTour += 1;
                          Debug.Log (currentTour);
                      }
                      if (Input.GetKeyDown (KeyCode.LeftArrow)) {
                          movement = new Vector3 (-1f, 0f, 0f);
                          player2.transform.position += movement;
                          currentTour += 1;
                          Debug.Log (currentTour);
                      }
                      if (Input.GetKeyDown (KeyCode.RightArrow)) {
                          movement = new Vector3 (1f, 0f, 0f);
                          player2.transform.position += movement;
                          currentTour += 1;
                          Debug.Log (currentTour);
                      }
      }
  
  }
}

Bonjour,

je voudrais essayer de faire un petit jeu avec Unity et je voudrais savoir quel est le mieux entre animer les models dans Blender au moment de la modelisation ou bien l'animer dans Unity apres importation ? D'ailleurs au passage quel est le meilleur format de fichier pour exporter un model Blender dans Unity ? .blend ? .fbx ?

Merci d'avance

MeliMelo

Bonjour,

Pour l'export de manière general, il est plus avantageux d'utiliser un format prévu à cet effet comme le FBX. De plus, un moteur le gèrera surement mieux qu'un format propre à un logiciel de modélisation 3D que ce soit à la fois au niveau de la taille que de la performance.

Par exemple, un .blend embarquera tout ce qu'un fbx possède mais en y ajoutant toutes les informations propre à Blender et elles ne sont pas toutes utilisables dans le moteur. Bien qu'Unity supporte ces formats (Blender, Maya, …) il ne faut pas les voir comme des méthodes de production mais plutôt comme du test rapide ou du dépannage.

Ensuite pour ce qui est de l'animation d'un mesh directement dans Unity, il faut plus voir ca comme un outils suplementaire (pour l'insertion d'event par exemple) mais pas comme un moyen de production. Une animation directement créer dans blender t'apportera bien plus de confort et de possibilités.

Du coup autre question : mon script qui gere les deplacements de chaque joueur se trouve dans la MainCamera et je passe des references aux joueurs en variable public. Est-ce acceptable ou est il plus propre d'affecter un script de deplacement sur chacun des cubes afin d'eviter les variables publiques ?

MeliMelo

Oui et non, les deux sont viables tout dépend de la dimension que prendra ton jeu. Dans ton cas tu peux considérer que ta camera est en quelque sorte le maitre du jeu c'est elle qui a tout pouvoir et qui va gérer chaque tour. On peut la tagger comme étant le "GameController".

Ensuite tout gérer dans la camera n'est pas une bonne idée en soit car tu vas vite te retrouver avec un code centraliser et peu maniable. Rien ne t'empêche par la suite de mettre un script sur chaque joueur et d'appeler leurs fonctions via la camera par exemple. Le tout étant de trouver le juste milieu. Tout n'a pas besoin de script mais on ne peut pas tout faire avec un ou deux scripts.

Pour ce qui est de la communication entre les objets, si j'ai bien compris c'est le fait de les mettre en public qui te dérange. Et bien oui je n'aime pas ca non plus, ca oblige à remplir tout les champs correctement du coté éditeur et ca peut vite devenir embarrassant (trop de champs public, object instancier pendant le jeu, etc…). Pour palier à ca, je te conseille de récupérer les objets avec lesquelles tu vas communiquer directement dans le start de ton script.

Par exemple :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
public class CubeController : MonoBehaviour
{
    private GameObject player1;
    private GameObject player2;

    private void Start()
    {
        this.player1 = GameObject.FindGameObjectsWithTag("Player1");
        this.player2 = GameObject.FindGameObjectsWithTag("Player2");
    }
}

Note: Ne pas oublier bien sûr de tagger les Players correctement.

+2 -0

Ok merci pour tous ces conseils précieux. Je suis aussi passé par les tags ensuite parce que comme tu dis mettre en public suggère d'être sûr de bien remplir les champs dans l'éditeur ensuite et j'aime pas trop ça. Du coup si j'ai bien compris l'idéal serai de laisser une partie du script dans la main camera comme par exemple la partie qui gère les tours (permettre à tel ou tel joueur de jouer) et par contre de mettre les fonctions de contrôle propre aux objets dans un script leur étant directement rattaché ?

Maintenant que tu le dis et que je l'écris ça me parait en effet bien plus propre. Pour la dimension de mon jeu, je suis pour l'instant dans l'optique de faire un petit jeu genre dofus arena mais en local (2 joueurs sur le même PC qui se passent la souris ou le clavier à chaque tour). Donc en gros déplacement style jeu de plateau case par case, puis ensuite combat au tour par tour avec gestion des lignes de vue en fonction de l'endroit où se trouvent les 2 joueurs, si jamais un élément du décors obstrue la vue, alors les joueurs ne peuvent pas se toucher,etc…

Ca me semble être un projet assez intéressant pour voir les différents aspect d'Unity tant au niveau code qu'animation, etc … Pensez-vous que c'est un projet réalisable ou c'est tout simplement trop ambitieux ? (mon but n'étant pas d'atteindre le niveau graphique de dofus arena ni même toutes ses subtilités de gameplay)

Du coup si j'ai bien compris l'idéal serai de laisser une partie du script dans la main camera comme par exemple la partie qui gère les tours (permettre à tel ou tel joueur de jouer) et par contre de mettre les fonctions de contrôle propre aux objets dans un script leur étant directement rattaché ?

MeliMelo

Oui voila c'est exactement ca.

Sinon pour la faisabilité de ton projet. Je pense que oui c'est en effet réalisable mais que comme pas mal de projet ce qui déterminera le plus sa finalité ce sera le niveau de qualité que tu souhaites atteindre.

Tu pourras surement réaliser le jeu en soit sans trop de problèmes. Il n'y a pas de notion trop compliquer dans ce que tu me présentes sauf peut être la gestion de la vue. Je ne connais pas trop Dofus mais si par "Vue" tu sous entends une sorte de brouillard de guerre (RTS) alors ca risque de se compliquer légèrement mais rien d'insurmontable.

Tu verras que dans la création d'un jeu tout avance très vite au début. On obtient rapidement des résultats encouragent mais quand vient le moment du polish et du debug ca avancera 100 fois moins vite et c'est assez démoralisant a la longue. Mais malgré tout ce sont des étapes indispensables si tu as un minimum d'ambition pour ton jeu.

Note: Polish est un terme du jeu vidéo (mais pas que) qui signifie "Finissions". C'est une étape de peaufinage des petits details et de l'augmentation en qualité du résultat.

+0 -0

OK merci pour les conseils. Nan pour ce qui concerne la vue je pensais plus à si un joueur est d'un côté d'un caillou et l'autre de l'autre côté par exemple, le caillou fait obstacle donc il ne peut pas lancer de sort sur son adversaire. Les cases derrière le caillou seraient donc grisées pour signifier que l'action est impossible sur cette case.

OK merci pour les conseils. Nan pour ce qui concerne la vue je pensais plus à si un joueur est d'un côté d'un caillou et l'autre de l'autre côté par exemple, le caillou fait obstacle donc il ne peut pas lancer de sort sur son adversaire. Les cases derrière le caillou seraient donc grisées pour signifier que l'action est impossible sur cette case.

MeliMelo

Ok si ce n'est que ca aucun problème !

N'hésite pas à faire un post dans la catégorie "Vos projets" quand il sera suffisamment avancé. Personnellement il m'intéresse et je serai ravi de suivre son évolution.

Bonne continuation. ;)

+0 -0

ok merci pour l'interet que tu portes a on projet et pour tes precieux conseils. Malheureusement j'ai peu de temps libre en ce moment donc le projet risque de prendre du temps a developper mais compte sur moi pour te le faire savoir quand il y aura des avances interessantes ;) Du coup maintenant une question me trotte dans la tete : pour le plateau de jeu, mieux vaut le faire a partir de cubes aplatis modelisant chacun une case ou plutot prendre un plan ? J'avoue que pour l'instant je vois pas trop, la seule chose qui pourrait me faire me decider c'est que si je veux faire des relief ce sera a base de cube empiles pour rester dans l'esprit casual et dans ce cas la peut-etre qu'un plateau a base de cube serait plus interessant …

Du coup je prends tous les avis qui passent :) Au passage voila mon nouveau code pour le controller si ca interesse et si vous voyez encore des choses a ameliorer ou factoriser.

CubeController.cs :

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
using UnityEngine;
using System.Collections;

public class CubeController : MonoBehaviour {
  Vector3 movement;                               //Vector3 used to |apply movement to players
  private GameObject[] environmentElements;       //Array of GOs to get environment elements |like obstacles from the scene
  private bool allowMov;                          //Boolean to determine if we |allow the movement or not depending on obstacles
  private bool didMove;                           //Boolean used to give |information on whether or not the player moved

  // Use this for initialization
  void Start () {

      movement = new Vector3 (0f, 0f, 0f);
      didMove = false;

      //Get GOs with tag "EnvElement" from the scene and store them into an array
      environmentElements = GameObject.FindGameObjectsWithTag ("EnvElement");

  
  }

  //Check if the position on the left is free or not
  public bool checkLeft ()
  {
      allowMov = true;
      //Set the movement vector for moving left
      movement = new Vector3 (-1f, 0f, 0f);

      //Check if any envElement is on the targetted position if yes then don't allow the movement
      foreach(GameObject envElement in environmentElements)
      {
          if(envElement.transform.position == new Vector3(this.transform.position.x + |movement.x, 0.5f,  this.transform.position.z + movement.z))
              allowMov = false;
      }
      Debug.Log("allowMov : " + allowMov);
      //Call the applyMove function and get the result if the movement has been applied or not and |store it into didMove boolean
      didMove = applyMove (allowMov);
      Debug.Log ("didMove : " + didMove);
      return didMove;

  }

  //Check if the position on the right is free or not
  public bool checkRight ()
  {
      allowMov = true;
      movement = new Vector3 (1f, 0f, 0f);
      foreach(GameObject envElement in environmentElements)
      {
          if(envElement.transform.position == new Vector3(this.transform.position.x + |movement.x, 0.5f,  this.transform.position.z + movement.z))
              allowMov = false;
      }
      Debug.Log("allowMov : " + allowMov);
      
      didMove = applyMove (allowMov);
      Debug.Log ("didMove : " + didMove);
      return didMove;
      
  }

  //Check if the position on the up is free or not
  public bool checkUp ()
  {
      allowMov = true;
      movement = new Vector3 (0f, 0f, 1f);
      foreach(GameObject envElement in environmentElements)
      {
          if(envElement.transform.position == new Vector3(this.transform.position.x + |movement.x, 0.5f,  this.transform.position.z + movement.z))
              allowMov = false;
      }
      Debug.Log("allowMov : " + allowMov);
      
      didMove = applyMove (allowMov);
      Debug.Log ("didMove : " + didMove);
      return didMove;
      
  }

  //Check if the position on the down is free or not
  public bool checkDown ()
  {
      allowMov = true;
      movement = new Vector3 (0f, 0f, -1f);
      foreach(GameObject envElement in environmentElements)
      {
          if(envElement.transform.position == new Vector3(this.transform.position.x + |movement.x, 0.5f,  this.transform.position.z + movement.z))
              allowMov = false;
      }
      Debug.Log("allowMov : " + allowMov);
      
      didMove = applyMove (allowMov);
      Debug.Log ("didMove : " + didMove);
      return didMove;
      
  }

  //Apply the movement if the allowMov boolean is true
  bool applyMove(bool allowMov)
  {
      if (allowMov == true) 
      {
          this.transform.position += movement;
          allowMov = false;
          return true;
      } 
      else 
      {
          return false;
      }
  }
}

CountTour.cs :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
using UnityEngine;
using System.Collections;

public class CountTour : MonoBehaviour {

  private int tour = 0;               //tour number
  private GameObject player1;         //GO containing player1
  private GameObject player2;         //GO containing player2
  private GameObject[] players;       //array of GOs used to get players from the scene thanks to |tags
  private CubeController playerScript;
  private bool didMove;

  // Use this for initialization
  void Start () {
      tour = 1;
      didMove = false;

      //Look in the scene for objects with tag "Player"
      players = GameObject.FindGameObjectsWithTag ("Player");

      //then put them into their own GO
      player1 = players [0] as GameObject;
      player2 = players [1] as GameObject;
  
  }
  
  // Update is called once per frame
  void Update () {

      if (tour % 2 == 1) 
      {
          if (Input.GetKeyDown (KeyCode.UpArrow)) {
              playerScript = player1.GetComponent<CubeController>();
              didMove = playerScript.checkUp();
              if(didMove)
                  tour += 1;
          }
          if (Input.GetKeyDown (KeyCode.DownArrow)) {
              playerScript = player1.GetComponent<CubeController>();
              didMove = playerScript.checkDown();
              if(didMove)
                  tour += 1;
          }
          if (Input.GetKeyDown (KeyCode.LeftArrow)) {
              playerScript = player1.GetComponent<CubeController>();
              didMove = playerScript.checkLeft();
              if(didMove)
                  tour += 1;
          }
          if (Input.GetKeyDown (KeyCode.RightArrow)) {
              playerScript = player1.GetComponent<CubeController>();
              didMove = playerScript.checkRight();
              if(didMove)
                  tour += 1;
          }
      }
      else if (tour % 2 == 0) 
      {
          if (Input.GetKeyDown (KeyCode.UpArrow)) {
              playerScript = player2.GetComponent<CubeController>();
              didMove = playerScript.checkUp();
              if(didMove)
                  tour += 1;
          }
          if (Input.GetKeyDown (KeyCode.DownArrow)) {
              playerScript = player2.GetComponent<CubeController>();
              didMove = playerScript.checkDown();
              if(didMove)
                  tour += 1;
          }
          if (Input.GetKeyDown (KeyCode.LeftArrow)) {
              playerScript = player2.GetComponent<CubeController>();
              didMove = playerScript.checkLeft();
              if(didMove)
                  tour += 1;
          }
          if (Input.GetKeyDown (KeyCode.RightArrow)) {
              playerScript = player2.GetComponent<CubeController>();
              didMove = playerScript.checkRight();
              if(didMove)
                  tour += 1;
          }
      }
  
  }
}

EDIT : en fait je viens de me rendre compte que mon systeme de detection de collision ne marche que sur les objets ayant des unites precises et ne prenant pas plus d'une case sur la plateau donc va falloir que je retravaille ca car par exemple la j'ai ajoute une maison qui fait 2 cases de longueur en X et du coup mes players peuvent passer dans la partie qui depasse sur la 2eme case…

Je serais plus pour les cubes aplatis car plus flexible comme tu l'as dis avec le relief par exemple. Pour le plan attention, il y deux désavantages. On pourrait croire qu'il n'y a qu'une face et donc plus performant mais manque de bol, sur un Unity le plan est subdivisé… Je ne sais pas trop pourquoi d'ailleurs. Et deuxièmement, un plan n'a pas d'épaisseur et tes faces ne seront visibles que dans un sens (voir les faces et les normales).

Sinon au niveau du code dans ton "Update" tu pourrais rendre tout ca plus clean. Imaginons que tu ais un 3ème ou 4ème joueurs, comment feras tu ? On ne va tout meme pas ajouter un 3ème et 4ème bloc de "if".

Et passe plutôt sur des "else if" pour le check des touches car tu n'as pas de "return" en cas de succès donc si on appuie sur plus d'une touche à la fois on passera N tours d'un coups !

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
void Update()
{
      if (tour % 2 == 1)
              playerScript = player1.GetComponent<CubeController>();
      else
              playerScript = player2.GetComponent<CubeController>();

      if (Input.GetKeyDown(KeyCode.UpArrow))
      {
              if (playerScript.checkUp())
                  tour++;
      }
      else if (Input.GetKeyDown (KeyCode.DownArrow))
      {
              if (playerScript.checkDown())
                  tour++;
      }
      else if (Input.GetKeyDown (KeyCode.LeftArrow))
      {
              if (playerScript.checkLeft())
                  tour++;
      }
      else if (Input.GetKeyDown (KeyCode.RightArrow))
      {
              if (playerScript.checkRight())
                  tour++;
      }
}
+0 -0

Ok merci beaucoup pour tes conseils, j'ai applique ce que tu m'as dit et c'est vrai que ca clairifie beaucoup le code.

Sinon j'ai fait une grosse factorisation de mon code du controller et du check des collisions. Maintenant je passe par un raycast et je cree le ray en fonction de la direction passe a la fonction donc en gros : une seule fonction pour controller toutes les directions et ensuite on check une collision avec le raycasthit.collider.tag

Franchement j'y connaissais rien aux raycasts et je pensais que ca allait etre la galere mais finalement c'est plutot simple d'utilisation. Il me manque plus qu'a comprendre comment faire un raycast en fonction du curseur de la souris.

PS : dis moi si tu es interesse par mon code, savoir si je poste les updates ou pas mais bon la vue que ca reste essentiellement la meme chose juste la methode qui differe, je poste pas.

EDIT : j'ai rajoute une petite partie a mon fonction de movement qui fait que le model s'aligne dans la direction dans laquelle il bouge, ca fait une ligne en fait mais je me suis bien pris la tete avec les Quaternions car je n'avais jamais aborde cette notion alors qu'au final j'en ai meme pas eu besoin. Mais du coup si vous avez des explications sur cette classe je suis preneur car meme avec la doc ca reste tres flou dans ma tete. Et pour le creer un Ray avec le curseur de la souris c'est tout con :

1
ray = Camera.mainCamera.ScreenPointToRay (Input.mousePosition);

Et si au passage vous avez des bons tutos sur la creation de particule systems … Parce que tout ce que j'ai pu trouver c'est des trucs tres basics or sur l'Asset Store il y a des trucs totalement ouf or il me faudra des animations pour les sorts de mes persos (pour l'instant j'en suis pas la mais c'est toujours bon d'avoir des references)

Et oui vive les raycast !

Pour info il y a aussi d'autre méthode en plus du Start() Update(). Certaines sont très utiles et d'autre moins. Il y a par exemple :

  1. FixedUpdate() = utile pour la physique mais pas que.
  2. OnMouseDown() = c'est exactement se que tu as fais. C'est une fonction appelé si on click sur l'objet. Mais attention ! Je la trouve assez restreinte. Il faut impérativement un collider actif sur l'objet (ca pas le choix même avec un raycast classique) et il ne faut pas non plus que le click soit intercepter par une autre collider (ce n'est pas un RaycastAll())… Mais pour pallier à ca va voir du coté des layers ou fais tout simplement un RaycastAll().

Apres tu as une armée de fonction pour les collisions et les trigger mais je te laisse voir ca par toi même.

Sinon pour le système de particule je suis loin d'être une expert mais je peux tout de même te conseiller de ne pas essayer de faire une effet avec un seul "Emiter". Prenons le cas simple d'un petit feu. C'est un Emiter pour le centre du feu, un autre pour la partie plus haute et encore un autre pour la fumée. Si tu commences en sachant déjà ca et comment structuré ton effet, tu as déjà gagner un temps précieux. Après il n'y a pas de secret, il faut pratiquer. Commence déjà par voir comment faire des Emiters qui suivent un certain mouvement comme un "springs". Une fois que tu auras compris ca, ce n'est plus que de l'habillage avec des textures et la pas trop le choix faut être bon en graph (c'est la ou je me suis arrêté ^^ ).

+0 -0

Haha ok ok

Je connais certaines autres fonction telles que : FixedUpdate, Awake, LateUpdate, etc mais faudrait que je me renseigne sur les differences avec les fonctions traditionnelles car pour l'instant je dois avouer que c'est assez flou dans ma tete.

Je suis pas sur d'avoir tout compris a ton explication sur le OnMouseDown mais donc il faut que je face attention aux colliders et aux layers c'est bien ca ? Tu parles de fait que la fonction s'arrete au premier collider mais ca c'est pour le OmMouseDown ou le ScreenPointToRay ?

De toute facons la seule chose qui m'interesse a la souris c'est que le joueur puisse cliquer sur une case particuliere du terrain pour deplacer son personnage donc au final je pense que tout la partie decor ou tout de mon le sol sera dans un layer particulier et ouai j'ai vu que dans la fonction Physics.Raycast on pouvait passer le nom d'un layer en parametre donc ca risque d'etre tres pratique :)

EDIT : J'ai donc essaye de m'atteler au highlighting des differentes cases de mon plateau de jeu quand la souris est au dessus mais je me heurte a un soucis. Comme evoque plus tot dans le sujet, mon plateau est compose de cubes (21*20 donc 420 cubes definissent mon plateau) tous contenus dans un prefab. J'ai donc passe quelques heure a tenter de faire cela avec le Raycast mais mon gros soucis avec le raycast est que je peux savoir quand la souris est sur un cube mais pas vraiment quand elle ne l'est plus. Enfin a la base voila le script que je voulais appliquer :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
        ray = Camera.main.ScreenPointToRay (Input.mousePosition);
        if (Physics.Raycast (ray, out hit))
        {
            if (hit.collider.tag == "Ground") 
            {
                hit.collider.renderer.material = highlightMaterial;
                Debug.Log (hit.point);
            }
        }
        else
        {
            Debug.Log ("Raycase failed");
        }

Mais comme dit, je n'ai pas trouve le moyen de detecter quand la souris sortait d'un cube pour aller sur un autre… J'ai donc pense a utilise le OnMouseOver qui lui marche tres bien mais la le gros soucis c'est que si je le met sur mon prefab, le OnMouseOver va se declencher tant que je serai sur l'ensemble du terrain … Du coup je l'ai applique a chaque cubes qui composent mon plateau :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
using UnityEngine;
using System.Collections;

public class GroundScript : MonoBehaviour {

    void OnMouseOver()
    {
        renderer.material.color = Color.red;
    }

    void OnMouseExit()
    {
        renderer.material.color = Color.white;
    }
}

Mais la ce qui me derange enormement c'est le fait de devoir attacher le script a chacun des cubes, c'est a dire a 420 … Je trouve ca vraiment pas optimise donc je voulais savoir si vous aviez des pistes de reflexion pour pourquoi pas utiliser le premier script mais surtout pour detecter quand le raycast de la souris n'intersecte plus le collider du cube ?

D'ailleurs je viens de me rendre compte que vu que le script est utilise sur les 420 cubes, il y impossible de creer une fonction Update dans ce script sans faire ramer tout le programme…

Haha mince je viens de me rendre compte qu'avec ce systeme de click to move je vais devoir me taper du path finding … Le bonheur total.

Enfin ca fera pas de mal de se replonger la dedans mais je dois avouer que c'est pas ce qui me donne le plus de plaisir.

Du coup dans mes souvenirs algo de pathfinding = A* est ce que je me souviens bien ou y a t-il plus interessant dans mon cas ? L'idee etant que le joueur aura un certain nombre de points de mouvement pas tour. Par exemple s'il a 3pts de mouvement, il pourra se deplacer de 3 cases mais s'il clique sur une case a 3 de distance de la pos actuelle il va falloir trouver un chemin jusqu'a celle si possible (sans obstacles donc).

Je dois avouer que mes souvenirs sont flous la dessus, ce dont je me souviens c'est avoir utilise l'algo de A* et aussi la distance de Manhattan et justement elle va m'etre tres utile :) (en fait ca fait plaisir de se rendre compte que ce qu'on a appris en cours est parfois utile haha )

Salut,

Oui un A* est à privilégier dans ce cas ci. Unity possède un système de pathfinding appeler NavMesh mais qui est au moins 100 fois plus complexe qu'un A*. Donc je te le déconseille dans pour ce type de jeu. Et plus globalement un A* est presque toujours la meilleur solution pour un pathfinding par case.

Ensuite pour ton problème de highlight. En effet le coup des 420 scripts ce n'est pas possible… Plus globalement pour revenir à ta petite fonction du début. Pourquoi ne pas tout simplement faire ton Raycast une seule fois par la camera. Si elle trouve une case elle la met en surbrillance et la stock en tant que case active mais qui se elle avait déjà une case en surbrillance de stocker dans ce case elle la remet en état normale au préalable ? Et évidement si le Raycast ne trouve rien on désactive la case active si il y en a une.

Qu'est-ce que tu en dis ? C'est tout con et très léger.

Un truc comme ca :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
        ray = Camera.main.ScreenPointToRay (Input.mousePosition);
        if (Physics.Raycast (ray, out hit))
        {
            if (hit.collider.tag == "Ground") 
            {
                if (this.currentCase != null)
                      this.currentCase.renderer.material = basicMaterial;
                this.currentCase = hit.collider.gameObject;
                this.currentCase.renderer.material = highlightMaterial;
                Debug.Log (hit.point);
            }
            else //Dans ton exemple il y a un risque que ca passe ici !
            {
                 this.currentCase.renderer.material = basicMaterial;
                 this.currentCase = null;
            }
        }
        else
        {
            this.currentCase.renderer.material = basicMaterial;
            this.currentCase = null;
            Debug.Log ("Raycase failed");
        }
+0 -0

Hello,

j'ai pas trop compris .. tu mets le scripts dans la camera ? et du coup comment recuperes tu currentCase ? Le truc c'est que tous mes cubes qui composent mon Ground sont dans un empty histoire de pas me retrouver avec une hierarchie degueu. Du coup le Raycast ne marche pas puisqu'il detecte le collider de l'empty non ? Pour le reste faudra que je regarde parce que j'ai pas tout capte la et je dois y aller ^^ je te dis ce que j'en pense mais ca m'a l'air pas mal.

Dans l'ideal ton Empty agit comme un dossier et n'a rien d'autre qu'un Transform (pos, rot ,scale) donc pas de collider sauf si tu lui en rajoute un toi même.

Ensuite pour optimiser le truc au mieux il faudrait comme tu l'avais dis plus haut passer par un layer propre aux cases. Comme ca le Raycast n'interceptera rien d'autre que des cases.

Ensuite pour la localisation, oui tu peux très bien mettre ce bout de code dans la camera ou dans ton gestionnaire de tour. Au choix du moment que tu n'as 420 scripts c'est comme tu veux.

Pour récupérer la currentCase c'est tout simplement via le Raycast. Il va te retourner un RaycastHit qui est le résultat. Dans ce hit tu as plusieurs infos comme le point de collision mais surtout le collider ! Ensuite via ce collider tu peux comme tu avais fais récupérer et modifier le renderer de l'objet a qui appartenait le collider mais aussi tout simplement le GameObject lui même. Tu regarderas bien mon exemple c'est ce que j'ai fais. Je te laisse prendre le temps de regarder tout ca tranquillement, tu verras ce n'est pas sorcier.

+0 -0

Ha oui oui mais ça j'ai bien compris comment gérer le collider détecté mais le problème c'est que je n'ai pas encore assimilé comment tu fais pour savoir que la souris n'est plus sur cette case justement (pour la repasser en default material). Mais bon je vais prendre ton bout de code et je ferai des tests je vais bien comprendre ;)

Ouai en fait pour le Ground j'avais parlé plus haut de le mettre dans un layer dédié mais j'ai totalement zappé ^^ je vais faire ça, ça devrait simplifier grandement le traitement. Pour ce qui est des NavMesh j'avais vu quelques cas où ils les utilisent mais en général c'est pour du temps réel, là c'est du tour par tour donc comme tu m'as confirmé par la peine de s’embêter avec ça. Un simple A* devrait faire l'affaire.

Pour ce qui est du collider sur l'Empty je comprends bien que ce n'est qu'un folder au final mais je sais plus pourquoi je l'avais rajouté, ça devait être pour un truc avant mais de toutes façons je ne l'utilise pas donc il va virer, finalement le Raycast passera tout seul. J'implémente ça ce week end.

En tous cas merci beaucoup pour ton implication et tes précieux conseils.

Ok merci Loptr pour ton aide sur ce script. Ca marche nikel. J'ai cependant du ajouter quelques petits test if histoire de pas me taper des erreurs de null reference mais sinon dans les grandes lignes c'est ce que tu m'avais conseille.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
        //create a ray with the mouse position
        ray = Camera.main.ScreenPointToRay (Input.mousePosition);
        if (Physics.Raycast (ray, out hit))
        {
            //if the ray touch the ground
            if (hit.collider.tag == "Ground") 
            {
                //if an other case was already highlighted then put it back to default material
                if (this.currentCase != null)
                    this.currentCase.renderer.material = basicMaterial;

                //set the new current case highlighted position
                this.currentCase = hit.collider.gameObject;
                this.currentCase.renderer.material = highlightMaterial;
                mousePositionOver = new Vector3(hit.point.x, 0.5f, hit.point.z);
                Debug.Log (hit.point);
            }
            else //if the raycast does not touch the ground
            {
                Debug.Log("Raycast no Ground");
                //set the current case position back to null and reset the material of the previous one
                if (this.currentCase != null)
                {
                    this.currentCase.renderer.material = basicMaterial;
                    this.currentCase = null;
                }
                mousePositionOver = new Vector3 (666f, 666f, 666f);
            }
        }
        else //if the raycast does not hit anything
        {
            //set the current case position back to null and reset the material of the previous one
            if (this.currentCase != null)
            {
                this.currentCase.renderer.material = basicMaterial;
                this.currentCase = null;
            }
            mousePositionOver = new Vector3 (666f, 666f, 666f);
            Debug.Log ("Raycase failed");
        }


        if (tour % 2 == 1) 
        {
            playerScript = player1.GetComponent<CubeController>();

        }
        else if (tour % 2 == 0) 
        {
            playerScript = player2.GetComponent<CubeController>();

        }

        if (Input.GetMouseButtonDown (0) && mousePositionOver != new Vector3 (666f, 666f, 666f))
        {
            Debug.Log(mousePositionOver);
            didMove = playerScript.moveTo (mousePositionOver);
            Debug.Log(player1.transform.position);
            if(didMove)
                tour += 1;
        }

A noter que lorsque je set mon mousePositionOver a (666f, 666f, 666f) c'est une valeur par defaut car apparemment on ne peut pas set les variables vector3 a null, a moins que j'ai rate quelque chose (au passage du coup mon script pour le Ground ne sert plus a rien, ce qui n'est vraiment pas plus mal car 420 instance de ce script c'est pas tres opti).

Je vais maintenant essayer de m'attaquer au A*. Je pense que ca va me prendre un peu plus de temps que les scripts precedents donc il y aura sans doute moins d'update que la semaine derniere.

EDIT : en fait j'avais pas remarque mais le hit.point ne correspond pas puisqu'il transmet directement les coordonnees du points de collision et non celles de l'objet rencontre donc j'ai changer les hit.point par des hit.collide.transform.position du coup un ligne change :

1
mousePositionOver = new Vector3(hit.collider.transform.position.x, 0.5f, hit.collider.transform.position.z);

EDIT 2 : au fait je viens d'y penser mais

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
if (tour % 2 == 1) 
        {
            playerScript = player1.GetComponent<CubeController>();

        }
        else if (tour % 2 == 0) 
        {
            playerScript = player2.GetComponent<CubeController>();

        }

ce serait pas mieux de le mettre dans la fonction start ? Enfin pas le test sur le tour mais d'instancier directement les scripts dedans, ca permettrait de pas le refaire a chaque update. Du coup si je prends une liste de scripts plutot que 2 scripts separe, et que pour l'appel en fonction des tours j'utilise le modulo 2 comme index ca devrait marcher non ? En fait je suis persuade que ca marche mais la question est : est-ce bien la bonne demarche a suivre ?

Petite update pour dire que j'ai vire le controle du deplacement au clavier car dans le vrai jeu il n'est possible que de se deplacer en point & click et puis comme la camera sera peu-etre en vue de diagonale par rapport a la map, les fleches n'ont plus vraiment de sens (je garde quand meme en commentaire pour pouvoir m'en inspirer ou le remettre en place si jamais je trouve qu'au final c'est utile).

ce serait pas mieux de le mettre dans la fonction start ? Enfin pas le test sur le tour mais d'instancier directement les scripts dedans, ca permettrait de pas le refaire a chaque update. Du coup si je prends une liste de scripts plutot que 2 scripts separe, et que pour l'appel en fonction des tours j'utilise le modulo 2 comme index ca devrait marcher non ? En fait je suis persuade que ca marche mais la question est : est-ce bien la bonne demarche a suivre ?

MeliMelo

Salut,

Ah oui totalement c'est une très bonne idée ! Je dirais même que j'aurais du mal à trouver plus performant. ^^

+0 -0

Ok tres bien j'ai mis ca en place et ca marche nikel.

De plus j'ai rajoute une petite condition dans le test du raycast pour anticiper la partie "points de mouvement". En gros si le point d'impact est a plus de 3 cases du player alors on highlight pas la case et du coup impossible de se deplacer jusqu'a cette case.

Le 3 sera remplace par les points de mouvement effectif du joueur plus tard mais c'etait juste pour mettre une limite parce que la le joueur pouvait partir a l'autre bout de la map en 1 coup sans complexe. A noter que le pathfinding n'est toujours pas code pour le moment, les deplacements s'effectuent a coup de teleportation.

EDIT : Bon voila je viens de commencer mon algo de Astar et je voudrais savoir ce que vous en pensiez (si c'est totalement degueulasse ou pas) :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class AstarScript : MonoBehaviour {
    private Vector3 currentPosition;
    private Vector3 arrivalPosition;
    private List<Vector3> openList;
    private List<Vector3> closeList;

    // Use this for initialization
    void Start () {
        openList = new List<Vector3> ();
        closeList = new List<Vector3>();

    }

    // Update is called once per frame
    void Update () {

    }

    public void init (Vector3 currPosition, Vector3 arrPosition)
    {
        currentPosition = currPosition;
        arrivalPosition = arrPosition;

        openList.Add(currentPosition);
        closeList.Clear();

        Astar ();
    }

    private void Astar()
    {
        int cheapestElementIndex;
        int cheapestElementCost;

        bool checkBool = computeCosts (out cheapestElementIndex, out cheapestElementCost);

        closeList.Add (openList [cheapestElementIndex]);
        openList.RemoveAt (cheapestElementIndex);
    }

    private bool computeCosts (out int cheapestIndex, out int cheapestCost)
    {
        int h;              //heuristic from element to arrivalPostion
        int g;              //cost of movement from currentPosition to element
        int f;              //cost of path from currentPosition to arrivalPosition going through element

        for (int i = 0; i < openList.Count; i++)
        {
            //heuristic is equal to manhattan distance
            h = (int) Mathf.Abs(arrivalPosition.x - openList[i].x) + Mathf.Abs(arrivalPosition.z - openList[i].z);
            //corst of movement if equal to 1 since every movement have the same weight (might change later)
            g = 1;
            //cost of path is equal to addition of heuristic and cost of movement
            f = h + g;
            //if it is the first element we check or if it is cheaper than previous ones
            if(cheapestCost == null || f < cheapestCost)
            {
                //then we store its cost and its index
                cheapestCost = f;
                cheapestIndex = i;
            }
        }
    }
}

a savoir que pour ma fonction computeCosts je suis pas bien sur de l'utilite de renvoyer un bool mais j'ai vu que c'etait pas mal utilise sur les fonctions avec des parametres out … (j'avoue ne pas trop maitriser cet aspect du c#) De meme pour les List c'est a peut pret le premier programme dans lequel je les utilise en C# et deja en C++ je maitrisais pas vraiment le concept alors bon :/

EDIT 2 : bon apparemment je n'ai pas pige le concept des parametres out puisque je me tape une erreur Assets/Scripts/AstarScript.cs(60,28): error CS0269: Use of unassigned out parameter `cheapestCost'

De plus j'ai un soucis avec mon cast sur l'attribution de la valeur a h : Assets/Scripts/AstarScript.cs(54,25): error CS0266: Cannot implicitly convert type float' toint'. An explicit conversion exists (are you missing a cast?)

J'avoue ne pas comprendre puisque justement je fais le cast …

EDIT 3 : bon ok pour le cast je suis un boloss, j'ai remplace le cast (int) en System.Convert.ToInt32() et ca marche nikel. Je savais pas que ce type de cast ne marchait pas en C# mais bon je le saurai pour la prochaine fois. Par contre toujours pas de solution pour l'autre erreur

EDIT 4 : toujours aussi con … fallait juste instancier les variables out dans la fonction tout simplement …

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
  private void Astar()
  {
      int cheapestElementIndex;
      int cheapestElementCost;

      bool checkBool = computeCosts (out cheapestElementIndex, out cheapestElementCost);

      closeList.Add (openList [cheapestElementIndex]);
      openList.RemoveAt (cheapestElementIndex);
  }

  private bool computeCosts (out int cheapestIndex, out int cheapestCost)
  {
      int h;              //heuristic from element to arrivalPostion
      int g;              //cost of movement from currentPosition to element
      int f;              //cost of path from currentPosition to arrivalPosition going |through element
      cheapestCost = System.Convert.ToInt32(Mathf.Infinity);
      cheapestIndex = System.Convert.ToInt32(Mathf.Infinity);

      for (int i = 0; i < openList.Count; i++)
      {
          //heuristic is equal to manhattan distance
          h = System.Convert.ToInt32(Mathf.Abs(arrivalPosition.x - openList[i].x) + |Mathf.Abs(arrivalPosition.z - openList[i].z));
          //corst of movement if equal to 1 since every movement have the same weight (might |change later)
          g = 1;
          //cost of path is equal to addition of heuristic and cost of movement
          f = h + g;
          //it is cheaper than previous ones
          if(f < cheapestCost)
          {
              //then we store its cost and its index
              cheapestCost = f;
              cheapestIndex = i;
          }
      }
      return true;
  }

Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte