Développement d’un jeu de plateforme 2D avec libGdx en MVC – Partie 1

Introduction

IMPORTANT!! Cet article est basé et traduit à partir de l’article original d’Impaler. Dans cet article, nous allons étudier une méthode de développement d’un jeu vidéo de type plateforme 2D avec libGdx. Le jeu sera développé en se basant sur un modèle de développement MVC. Le but principal de l’article est d’apprendre quelques concepts de base incluant les suivants :
  • Comment créer un jeu 2D simple de type plateforme
  • Comment développer un jeu en suivant une architecture MVC
  • Comment utiliser les graphiques 2D d’OpenGL sans connaître OpenGL
  • Comment déployer un jeu pour le bureau et sur la plateforme Android
  • Comment ajouter du son

Étape pour créer un jeu

  1. Avoir une idée du jeu
  2. Faire des maquettes sur papier pour avoir une idée de quoi aura l’air le jeu.
  3. Analyser l’idée de façon itérative, i.e. revenir sur des idées de départ pour les améliorer tout en avançant pour terminer avec ce que contiendra la première version du jeu.
  4. Sélectionner une technologie et débuter le prototype.
  5. Débuter la programmation et la création des ressources pour le jeu.
  6. Développer itérativement, i.e. jouer, tester, améliorer et recommencer jusqu’à obtention du résultat désiré.
  7. Polir et distribuer!

Le concept du jeu

Étant donné que c’est un projet qui doit se faire dans un court laps de temps, on limite le temps alloué au processus de développement pour mettre plus de temps sur la technologie et la science pour faire des jeux. Le jeu développé pour cet article est basé sur Star Guard de Vacuum Flowers. Téléchargez le jeu et faites des tests, cela vous permettra d’avoir une idée globale du projet. Le concept du jeu est de guider le héros (Bob) à travers les différents niveaux en éliminant les ennemis et en évitant tout ce qui essaie de le tuer. Les contrôles seront relativement simples soient les flèches avec les touches Z et X qui seront respectivement utilisées pour se déplacer, sauter et tirer. Le plus longtemps que la touche « sauter » sera maintenue le plus haut le personnage sautera. On pourra changer la direction dans les airs ainsi que tirer. On verra plus tard comment transférer ces contrôles sur la plateforme Android.

Démarrez Eclipse

Nous allons utiliser la plateforme libGdx sous Eclipse. Pourquoi libGdx et non XNA ou autres plateformes? Tout simplement par qu’il est « simple », gratuit et multiplateformes. Il permet aussi de nous abstraire plusieurs notions complexes qui nous entraveraient dans le développement.

Générer les projets

La première étape sera de télécharger libGdx. Ensuite, il suffira de créer un nouveau projet libGdx avec l’utilitaire gdx-setup-ui.jar. Ce dernier se retrouve dans le dossier racine de libGdx. Une fois l’application gdx-setup-ui.jar lancée, la fenêtre ci-contre devrait s’afficher.Screen Shot 2016-04-11 at 15.05.53 Figure 1 : Page de démarrage et de configuration de projet Voici ce que chaque section signifie :
  • Configuration : Zone de configuration granulaire du projet
    • Name : Nom du projet qui sera utilisé dans Eclipse
    • Package : Nom du package de déploiement. Il doit être unique pour chaque projet. C’est ce nom qui permet de définir uniquement une application dans Google Play. Cela permet entre autre d’envoyer des mises à jour du jeu dans Google Play. D’ordre général, le nom du package est de la forme d’un URL inversé. Dans mon cas, je vais utiliser « ca.collegeshawinigan.bobgame ».
    • Game Class : Nom de la classe définissant le jeu. Toujours débuter le nom d’une classe avec une lettre majuscule. C’est une bonne pratique Java.
    • Destination : Dossier dans lequel les projets seront générés. Je suggère de créer un dossier avec le nom du projet, car le setup génère un minimum de deux dossiers soient « nomProjet » et « nomProjet-android ». Ainsi, il sera plus facile à retrouver dans l’arborescence des dossiers.
    • Android SDK : Dossier dans lequel se trouve le  android Studio Development Kit, à télécharger vers le lien suivant.
  • Sub Projects : Types de projet à générer. Sélectionnez les projets que vous désirez générer. Pour plus de faciliter de développement, je suggère au moins la version Desktop. Ainsi, il ne sera pas nécessaire de toujours déployer le projet sur la plateforme Android pour faire du débogage.
    • Si vous sélectionnez la version HTML, assurez-vous d’avoir le Google Web Toolkit installé sur votre système.
    • Si vous sélectionnez la version iOS… bon je ne suis pas un fan iOS donc je ne peux répondre pour cette partie! 😉
  • Extensions : On sélectionne les librairies tierce-partie dans cette section.
    • Le bouton « show Third Party Extensions » permet d’afficher plus de librairies. Il suffit de les sélectionner puis d’appuyer sur « Save ».
    • Dans notre cas, nous aurons besoins de la librairie « Tools » et « box2d ».
      Figure 3 : Page des librairies tierce-partie
      Figure 2 : Page des librairies tierce-partie
  • Advanced Settings : il suffit d’appuyer sur « Advanced » pour y accéder
    • Maven Mirror Url : TO DO.
    • IDEA : génère les fichier pour un projet sous Intellij IDEA.
    • Eclipse : génère les fichiers pour un projet sous Eclipse. Nous choisirons cette option pour BobGame.
    • Offline Mode : permet de pas forcer le téléchargement des dépendances pour les librairies.
    • Pour sauvegarder  les options, il faut appuyer sur le bouton « Save ».
      Figure 3 : Page des réglages avancées
      Figure 3 : Page des réglages avancées
  •  Generation : pour lancer la génération, il

    il ne suffit que de cliquer sur  « Generate »!

    Figure 4 : Page principal avec génération terminée

Importer dans Eclipse

Après avoir généré les projets, il faudra les importer dans Eclipse.
  1. File à Import…
  2. General… à Existing Projects into Workspace
  3. Select root directory ß Votre projet
  4. Les projets générés devrait se retrouver dans la section « Projects »
  5. Terminer
  6. Les dossiers des projets devraient se retrouver dans le panneau d’explorateur de package qui se situe à gauche

Développement

Le projet du jeu se retrouve dans le package « bobgame ». Les autres projets servent de projet de configuration pour les plateformes spécifiques. Par exemple, la dimension de l’écran pour le desktop ou encore la configuration des différents senseurs pour les plateformes Android.

Les ressources

Il est important de comprendre que lorsque l’on utilise libGdx, les ressources sont partagés à partir du projet Android d’où l’obligation de générer le projet Android. Les ressources se retrouvent dans le dossier « Assets » à l’intérieur du projet Android. On y retrouvera entre autres les images, les sons et les textures.

Créer le jeu

Il est important de comprendre le concept d’une application de type jeu vidéo. Le principe est très simple. Il s’agit d’une méthode d’initialisation, d’une boucle infinie et d’une méthode de terminaison. On peut retrouver plus d’information sur l’architecture des jeux ici et de la boucle infinie ici. LibGdx permet de simplifier le tout en générant le code de base telle une pièce de théatre. Dans notre cas, nous allons définir les scènes (niveaux), les acteurs, leurs rôles et comportements. Cependant la chorégraphie sera réalisée par le joueur. Donc pour configurer notre jeu, on doit réaliser les étapes suivantes :
  1. Démarrer l’application.
  2. Charger les images et sons en mémoire.
  3. Générer les niveaux avec les acteurs et les comportements.
  4. Laisser le contrôle au joueur.
  5. Créer le moteur qui va gérer l’interaction entre le joueur et le jeu basée sur les actions reçues de la manette (ou clavier ou souris).
  6. Déterminer le moment où le jeu prend fin.
  7. Terminer le spectacle.
package ca.collegeshawinigan.bobgame;

import com.badlogic.gdx.ApplicationListener;

public class BobGame implements ApplicationListener {

	@Override
	public void create() {
	}

	@Override
	public void dispose() {
	}

//Ne mettre cette méthode que si elle est implémentée !
	//@Override
	//public void render() {
	//}

	@Override
	public void resize(int width, int height) {
	}

	@Override
	public void pause() {
	}

	@Override
	public void resume() {
	}

}
  • La méthode Create permet de charger les différentes ressources nécessaires au jeu.
  • La méthode Resize est exécutée à chaque fois que l’écran est redimensionné.
  • La méthode Pause est exécutée lorsque le système sort de l’application pour effectuer d’autres tâches. Par exemple, recevoir un appel téléphonique sur les téléphone Android
  • La méthode Resume est exécutée lorsque l’application a été mis en pause et revient comme application ayant le focus.
  • La méthode Dispose est exécutée à la fin de l’exécution. Principalement utilisé pour détruire les objets qui ont été instanciés.
  • La méthode Render est le coeur du jeu. C’est la méthode qui fait office de boucle principale du jeu. Ainsi toute la logique et le rendu du jeu se fait à l’intérieur de cette méthode.

Les acteurs

Commençons avec le jeu. La première étape sera d’avoir un monde où le personnage pourra se promener. Le monde est composé de niveaux et chaque niveau est composé d’un terrain. Le terrain n’est rien de plus que des blocs desquelles le personnage ne peut traverser au travers. Pour le moment, identifier les acteurs et les entités n’est pas une tâche complexe. Nous avons le personnage (Bob) et les blocs qui forment le monde. Si vous avez essayé Star Guard, on constate que Bob peut avoir quelques états. Lorsque l’on ne touche à rien Bob est inactif (idle). Il peut aussi se déplacer (move) dans les deux directions et il peut sauter (jump). De plus, lorsqu’il est mort (dead), il ne peut plus rien faire. Ainsi, on vient d’identifier quatre états.
  • Idle : Lorsqu’il ne bouge ou saute pas ET est envie.
  • Moving : Déplacement de gauche ou droite à une vitesse constante.
  • Jumping : Déplacement en hauteur de gauche ou droite.
  • Dead : Invisible et regénération
Les Blocs représentent les autres acteurs. Pour simplifier le jeu, nous n’avons que des blocs. Les niveaux consistent en des blocs disposés dans un espace 2D. Pour simplifier le problème, nous allons utiliser une grille. Figure 5 : Jeu actuel Figure 6 : Notre jeu Nous avons maintenant un monde défini en blocs. Il faut définir un système de mesure, car nous ne fonctionnons pas en pixel. Pour simplifier le système de mesure, nous allons déterminer que chaque bloc possède une dimension de 1 unité en largeur et hauteur. Bob a la taille d’une demi-unité. Pour un comparatif par rapport à notre monde, disons que 1 unité équivaut à 4 mètres ainsi Bob mesure 2 m (grand gaillard!). Un comparatif avec le monde réel est important, car cela va nous permettre de comprendre les différentes notions de physique. Maintenant définissons le monde. Notre personnage principal est Bob. La classe Bob.java ressemblera au code suivant.
package ca.collegeshawinigan.bobgame.model;

import com.badlogic.gdx.math.Rectangle;
import com.badlogic.gdx.math.Vector2;

public class Bob {

	public enum State {
		IDLE, WALKING, JUMPING, DYING
	}

	static final float SPEED = 2f;	// unité par seconde
	static final float JUMP_VELOCITY = 1f;
	static final float SIZE = 0.5f; // Demi unité

	Vector2 	position = new Vector2();
	Vector2 	acceleration = new Vector2();
	Vector2 	velocity = new Vector2();
	Rectangle 	bounds = new Rectangle();
	State		state = State.IDLE;
	boolean		facingLeft = true;

	public Bob(Vector2 position) {
		this.position = position;
		this.bounds.height = SIZE;
		this.bounds.width = SIZE;
		this.bounds.x = this.position.x;
		this.bounds.y = this.position.y;
	}
}
  • Dans un jeu, l’énumération des états sert à simplifier la compréhension du code lors du développement.
  • position est la position de Bob dans le monde. Ainsi les coordonnées seront exprimées en utilisant celle du monde.
  • acceleration est l’accélération en XY lorsque Bob saute.
  • velocity est la vitesse de déplacement de Bob qui est continuellement calculée.
  • bounds représente les limites de Bob.
  • state est l’état actuellement de Bob.
  • facingLeft indique que Bob fait face à gauche.
  • Quelques constantes sont définies dans le haut de la classe. Celles-ci seront utilisées pour calculer la mécanique de Bob.
En plus du personnage, nous devons définir les blocs qui vont servir à construire le monde. La classe Block.java ressemblera à ceci :
package ca.collegeshawinigan.bobgame.model;

import com.badlogic.gdx.math.Rectangle;
import com.badlogic.gdx.math.Vector2;

public class Block {

	static final float SIZE = 1f;

	Vector2 	position = new Vector2();
	Rectangle 	bounds = new Rectangle();

	public Block(Vector2 pos) {
		this.position = pos;
		this.bounds.width = SIZE;
		this.bounds.height = SIZE;
		this.bounds.x = this.position.x;
		this.bounds.y = this.position.y;
	}
}
Les blocs ne sont rien de plus que des rectangles qui seront placés dans le monde. On utilisera les blocs pour générer le terrain. Il n’y aura qu’une seule règle. Rien ne peut les pénétrer.
À propos des Vector2 dans libGdx
Remarquez que l’on utilise le type Vector2. Cela nous permet de nous simplifier la vie pour effectuer des calculs de positionnement et de déplacement.
 

Le système de coordonnées et les unités

À l’instar du monde réel, le monde des jeux possède des dimensions. Pensez à une maison d’une étage. Il y a une largeur, une hauteur et une profondeur. Dans notre cas, nous n’avons besoins que de deux dimensions, ainsi nous ne nous préoccupons pas de la profondeur. Si une pièce a une dimension de 5 mètres en largeur et 3 mètres en hauteur, on peut la décrire dans le système métrique. Il est facile d’imaginer une table 1 mètre de large et 1 mètre de haut au milieu. Nous ne pouvons pas passer au travers la table, pour la traverser, nous aurions besoin de sauter au-dessus d’elle, marcher 1 mètre et sauter. Nous pouvons utiliser plusieurs tables pour créer une pyramide et créer des dessins bizarres dans la chambre. Dans notre monde, le monde représente la chambre, les blocs représente la table et les unités représente les mètres dans le monde réel. Si nous courrons 10 km/h, cela se traduit à 2.7778 m/s (10 * 1000 / 3600). Si on traduit dans le monde de Bob, cela sera l’équivalent de 2.78 unités/s. Observez le diagramme suivant qui représente les boîtes limitrophes et Bob dans le système de coordonnées du monde. coordinate-system   Les carrés rouges sont les boîtes limitrophes des blocs. Le carré vert est la boîte limitrophe de Bob. Les cases vides représentent l’air. La grille est juste pour la référence. C’est le monde dans lequel nous allons créer nos simulations. L’origine du système de coordonnées est au fond à gauche. Ainsi, marcher à gauche à 10 km/h indique que la position de Bob devra diminuer à 2,7 u/s. Notez que l’accès aux membres dans les classes est celui définit par le packaging d’Eclipse. Il faudra définir les modèles dans un package différent que l’on nommera Model. On doit créer les méthodes accesseurs (getters et setters) pour accéder aux membres à partir du moteur.

Exercices

  • Créer les packages suivant pour votre projet :
    • com.votreDomaine.nomJeu.controller
    • com.votreDomaine.nomJeu.model
    • com.votreDomaine.nomJeu.screens
    • com.votreDomaine.nomJeu.view
  • Créer les getters et setters pour les propriétés des classes Bob et Bloc
    • Dans le menu contextuel du code dans Eclipse, il y a une méthode qui simplifie grandement le temps passé à coder. Il suffit de sélectionner Source –> Générer les Getter et Setters. Ensuite, on sélectionne ce que l’on désire générer.

Créer le monde

Dans un premier temps, nous allons « hard-coder » le monde avoir un aperçu rapide. Le monde sera de 10 x 7. On placera Bob et les blocs de la méthode ci-contre. 100113_1709_Dveloppemen5.png La classe World.java va ressemble à ceci.
package ca.collegeshawinigan.bobgame.model;

import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.utils.Array;

public class World {

	/** Les blocs qui composent le monde **/
	Array<Block> blocks = new Array<Block>();
	/** Notre héro!! **/
	Bob bob;

	// Getters -----------
	public Array<Block> getBlocks() {
		return blocks;
	}
	public Bob getBob() {
		return bob;
	}
	// --------------------

	public World() {
		createDemoWorld();
	}

	private void createDemoWorld() {
		bob = new Bob(new Vector2(7, 2));

		for (int col = 0; col < 10; col++) {
 			blocks.add(new Block(new Vector2(col, 0))); 			 
			blocks.add(new Block(new Vector2(col, 6)));
			if (col > 2)
				blocks.add(new Block(new Vector2(col, 1)));
		}

		blocks.add(new Block(new Vector2(9, 2)));
		blocks.add(new Block(new Vector2(9, 3)));
		blocks.add(new Block(new Vector2(9, 4)));
		blocks.add(new Block(new Vector2(9, 5)));

		blocks.add(new Block(new Vector2(6, 3)));
		blocks.add(new Block(new Vector2(6, 4)));
		blocks.add(new Block(new Vector2(6, 5)));
	}
}
Cette classe n’est qu’un conteneur pour les différentes entités du monde. Présentement, les entités sont Bob et les blocs. Dans le constructeur les blocs sont ajoutés dans un tableau et Bob est créé. Je sais que ce n’est pas une bonne méthode de programme, mais ce n’est que pour des fins de démonstration rapide. Il faut se souvenir que l’origine est représentée par le coin inférieur gauche.

Créer le jeu et afficher le monde

Pour dessiner le monde sur l’écran, nous avons besoin un écran pour lui et dire à ce dernier de dessiner le monde. Dans libGdx, il y a une classe appelé Game et nous allons redéfinir la classe principale du jeu pour hériter de la classe Game fournit par libGdx au lieu d’être un ApplicationListener.
À propos des Écrans (Screens)
Un jeu est constitué de plusieurs écrans. Même dans notre jeu, nous aurons 3 écrans. L’écran de départ (Start), l’écran de jeu (Play) et l’écran de la partie terminée (Game Over). Chaque écran ne s’occupe que de son monde et ne connaît pas le contenu des autres écrans. Par exemple, l’écran de départ contiendra le menu d’options Jouer et Quitter. Il possède deux éléments (boutons) et s’occupe du clic/touché de ces éléments. Il dessine en continue ces deux éléments, jusqu’à ce qu’un des deux boutons soit cliqué. Si Jouer est cliqué, l’écran avise le jeu qu’il doit charger l’écran de jeu et décharger l’écran de départ. L’écran de jeu s’occupe à calculer et à rendre le jeu en soi jusqu’à ce que la fin du jeu arrive. À ce moment, le jeu avise la boucle principale d’afficher le partie terminée et décharge l’écran de jeu. L’écran de Game Over ne sert qu’à afficher le score final et bouton de redémarrage de jeu.
Pour l’instant, redéfinissons le code du jeu principale et créons l’écran principal. Nous allons ignorer l’écran de départ et de partie terminée. La classe GameScreen.java
package ca.collegeshawiniga.bobgame.screens;

import com.badlogic.gdx.Screen;

public class GameScreen implements Screen {

	@Override
	public void render(float delta) {
		// TODO Auto-generated method stub
	}

	@Override
	public void resize(int width, int height) {
		// TODO Auto-generated method stub
	}

	@Override
	public void show() {
		// TODO Auto-generated method stub
	}

	@Override
	public void hide() {
		// TODO Auto-generated method stub
	}

	@Override
	public void pause() {
		// TODO Auto-generated method stub
	}

	@Override
	public void resume() {
		// TODO Auto-generated method stub
	}

	@Override
	public void dispose() {
		// TODO Auto-generated method stub
	}
}
Astuces!
Dans Eclipse, on peut générer des classes automatiquement. Il suffit de cliquer avec le bouton de droite sur le projet et ajouter une classe. Dans la fenêtre de nouvelle classe, on peut faire hériter le code d’une classe interface en ajoutant la classe interface dans la zone Interface.Dans le cas du GameScreen, on fera hériter celle-ci de l’interface Screen.
La classe du jeu principal dans mon cas BobGame devient très simple.
package ca.collegeshawinigan.bobgame;

import ca.collegeshawinigan.bobgame.screens.GameScreen;
import com.badlogic.gdx.Game;

public class BobGame extends Game {

	@Override
	public void create(){
		setScreen(new GameScreen());
	}

}
  • GameScreen implémente l’interface Screen qui agit très similairement à ApplicationListener, mais possède deux méthodes de plus.
  • show() : Cette méthode est appelée lorsque le jeu principal rend cet écran actif.
  • hide() : Cette méthode est appelée lorsque le jeu principal affiche un autre écran.
BobGame a seulement une méthode d’implémentée. Il s’agit de create() qui ne fait qu’activer la nouvelle instance de GameScreen. En d’autres mots, il le crée, appelle show() et ensuite appelle la méthode render() à chaque cycle. GameScreen est maintenant notre nouveau centre d’intérêt pour les prochaines étapes, car ce sera où le jeu prendra place. Rappelez-vous que la boucle principale est render(). Pour avoir quelques choses à afficher, il faut créer le monde. Pour l’instant, nous allons créer le monde dans show() car il n’y a rien qui ne peut interrompre notre jeu. Pour l’instant, GameScreen n’est montré que lorsque le jeu démarre. On va ajouter deux membres à la classe GameScreen et implémenter la méthode render(float delta).
private World world;
private WorldRenderer renderer;

/** Autre code **/

@Override
public void render(float delta) {
	Gdx.gl.glClearColor(0.1f, 0.1f, 0.1f, 1);
	Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
	renderer.render();
}

/** Autre code **/
  • La propriété world est l’instance du monde qui contient les blocs et Bob.
  • La propriété renderer est la classe qui va rendre le monde sur l’écran. Elle sera décrite ci-bas.
Créez la classe WorldRenderer.  
package ca.collegeshawinigan.bobgame.view;

import ca.collegeshawinigan.bobgame.model.Block;
import ca.collegeshawinigan.bobgame.model.World;
import ca.collegeshawinigan.bobgame.model.Bob;

import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer.ShapeType;
import com.badlogic.gdx.math.Rectangle;

public class WorldRenderer {

	private World world;
	private OrthographicCamera cam;

	/** Pour fin de débogage **/
	ShapeRenderer debugRenderer = new ShapeRenderer();

	public WorldRenderer(World world) {
		this.world = world;
		this.cam = new OrthographicCamera(10, 7);
		this.cam.position.set(5, 3.5f, 0);
		this.cam.update();
	}

	public void render() {
		// Rendu des blocs
		debugRenderer.setProjectionMatrix(cam.combined);
		debugRenderer.begin(ShapeType.Filled);
		for (Block block : world.getBlocks()) {
			Rectangle rect = block.getBounds();
			float x1 = block.getPosition().x;
			float y1 = block.getPosition().y;
			debugRenderer.setColor(new Color(1, 0, 0, 1));
			debugRenderer.rect(x1, y1, rect.width, rect.height);
		}
		// Rendu de Bob
		Bob bob = world.getBob();
		Rectangle rect = bob.getBounds();
		float x1 = bob.getPosition().x;
		float y1 = bob.getPosition().y;
		debugRenderer.setColor(new Color(0, 1, 0, 1));
		debugRenderer.rect(x1, y1, rect.width, rect.height);
		debugRenderer.end();
	}
}
WorldRenderer n’a qu’un seul but soit de vérifier l’état actuel du monde et de le dessiner. Il n’y a qu’une seule méthode public (render())qui est appelé par la boucle principale de GameScreen. Le renderer a besoin du monde pour extraire l’information de celui-ci. C’est la raison pour laquelle il est en paramètre dans le constructeur. Lors du rendu, la première étape consiste à dessiner les blocs et ensuite Bob. Dans le cas présent, les blocs et Bob sont représentés par des primitives de OpenGL. Pour dessiner ces primitives avec OpenGL, cela demande des connaissances de la librairie. Cependant libGdx permet de nous abstraire de ces notions avec la classe ShapeRenderer. Voici les points importants à connaître :
  • On déclare le monde (world) en tant que propriété.
  • On déclare un objet OrthographicCamera. Nous allons utiliser cette caméra pour visualiser le monde d’un point de vue orthographique. Actuellement le monde est très petit et a taille d’une écran, mais éventuellement les niveaux seront plus grands et Bob se déplacera à travers. Nous allons devoir déplacer la caméra avec Bob. On pourrait faire l’analogie avec une caméra dans la vie réelle. Si vous désirez en savoir plus sur la projection orthographique, vous pouvez cliquez ici.
  • La classe ShapeRenderer sera utilisée pour dessiner des primitives. Cette classe permet de dessiner des primitives tel des cercles, rectangles, lignes, etc.
  • Dans le constructeur, nous instancions la caméra pour une vue de 10 x 7 (le monde actuel). Cela signifie qu’en remplissant l’écran de 10 blocs en largeur et 7 blocs en hauteur, on prendra tout l’espace de la vue de la caméra. Important : Il ne faut pas perdre la notion que le jeu est de résolution indépendante. Ainsi si l’écran est de 480 x 320, cela signifie que 480 pixels (px) représentent 10 unités donc chaque bloc aura 48 px de largeur et 48.7 px de hauteur. Dans une écran 1080P, les blocs auraient 192 px x 154.3 px.
  • La ligne #23 indique à la caméra que l’on désire fixer le centre de la caméra sur le point (5, 3.5) de notre monde. L’image qui suit montre le concept.
Déplacement de la caméra
Déplacement de la caméra
  • À chaque fois que l’on doit modifier un paramètre de la caméra, on doit mettre à jour celle-ci. En gros, on modifie les matrices internes de la caméra. LibGdx fait un très bon travail pour nous abstraire de toutes ces notions de calculs matriciels que l’on retrouve dans OpenGL (Croyez-moi ce ne sont pas que des petits calculs!).
  • Dans la méthode render(), on mets en accord les matrices de la caméra avec le renderer (dessinateur?). Ceci est nécessaire, car nous avons modifié les paramètres de la caméra.
  • On dit au renderer que l’on désire dessiner des rectangles.
  • Tel qu’indiqué précédemment, on dessine la série de blocs en premier lieu. Ainsi, on itère à travers le tableau de blocs. J’espère que vous n’avez pas oublié de créer vos getters et setters! 😉
  • Les blocs seront dessinés en rouge.
  • On indique au renderer de dessiner le rectangle.
  • Le même principe s’applique pour Bob sauf que l’on dessine un rectangle vert.
Nous devons maintenant ajouter le renderer et le monde au GameScreen. Modifiez GameScreen comme ceci :
package ca.collegeshawinigan.bobgame.screens;

import ca.collegeshawinigan.bobgame.model.World;
import ca.collegeshawinigan.bobgame.view.WorldRenderer;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Screen;
import com.badlogic.gdx.graphics.GL20;

public class GameScreen implements Screen {

	private World world;
	private WorldRenderer renderer;

	@Override
	public void show() {
		world = new World();
		renderer = new WorldRenderer(world);
	}

	@Override
	public void render(float delta) {
		Gdx.gl.glClearColor(0.1f, 0.1f, 0.1f, 1);
		Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
		renderer.render();
	}

	/** ... restant du code omis ... **/

}
  • La méthode render() possède 3 lignes soient la première pour indiquer la couleur du fond, la seconde pour effacer l’écran et la troisième pour lancer le rendu.
Il est maintenant possible de lancer l’application en mode Desktop et Android. Pour la version Android, nous allons désactiver quelques senseurs de manière à économiser de l’énergie de la batterie. C’est une question de bonne pratique.
package ca.collegeshawinigan.bobgame;

import android.os.Bundle;

import com.badlogic.gdx.backends.android.AndroidApplication;
import com.badlogic.gdx.backends.android.AndroidApplicationConfiguration;

public class AndroidLauncher extends AndroidApplication {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
		AndroidApplicationConfiguration config = new AndroidApplicationConfiguration();
		config.useAccelerometer = false;
		config.useCompass = false;
		config.useWakelock = true;
		config.useGL20 = true;
		initialize(new BobGame(), config);
    }
}
  • La propriété de configuration useWakeLock permet d’empêcher Android de tamiser l’écran s’il détecte une inactivité de la part de l’OS.
  • La propriété useGL20 permet, dans notre cas, de charger des images qui n’ont pas des dimensions de puissance de 2. De plus, nous allons devoir tester l’application sur un périphérique Android, car l’émulateur n’a pas OpenGL ES 2.0.
    • Il faudra effectuer la même modification dans le fichier Main.java du projet desktop.

Le patron MVC

Pour ceux qui suivent le cours avec moi, vous avez sûrement remarqué que l’on développait le jeu de manière différente. On ne fait plus des classes qui contiennent toute l’information, mais on subdivise les entités en tâche. Ce que l’on appelle le développement MVC qui tient pour Modèle-Vue-Contrôleur (Model-View-Controller). Cette méthode a des similarités avec le développement 3-tiers quoiqu’elles ne sont pas identique dans la façon de faire. Cette méthode de travail permet de faire du développement efficace au niveau de la maintenance et de la simplicité. Ce didacticiel explique le développement de jeux en utilisant le MVC.

Ajouter les images

Pour l’instant, le rendu fonctionne, mais on aimerait ajouter des images. Nous allons modifier le renderer (vue) pour qu’il dessine des images au lieu de rectangles. Dans OpenGL, afficher une image est un processus complexe. Il faut charger les images, les convertir en textures et ensuite les « mapper » sur des surfaces définies par de la géométrie. LibGdx comme dans son habitude, nous soustrait de toute cette complexe en nous permettant de charger les images et de les convertir en une seule ligne. Les projets libGdx accèdent aux ressources à l’intérieur du projet Android sous le dossier assets. Ainsi les ressources sont partagées entre les différents projets d’où l’obligation d’avoir le projet Android. Ainsi, je suggère de classer les ressources par catégorie, par exemple, les textures, les sons et les images devraient être à l’intérieur de sous-dossiers respectifs. Dans un premier temps, créez un dossier images à l’intérieur du dossier assets. On y stockera pour l’instant les deux images qui seront utilisées dans cette partie. Les deux images sont block.png et bob_01.png. Ces images sont disponibles ici. Dans le fichier téléchargé, plusieurs images de Bob sont disponibles. Ceux-ci seront utilisés lors de l’animation du personnage. La prochain étape consiste à faire un peu de ménage dans la classe WorldRenderer. Nous allons séparer le traçage des rectangles dans une méthode séparée et utilisée cette dernière pour le débogage. De plus, nous allons charger les textures et les rendre dans l’écran.
/** package et importations omises **/

public class WorldRenderer {

	private static final float CAMERA_WIDTH = 10f;
	private static final float CAMERA_HEIGHT = 7f;

	private World world;
	private OrthographicCamera cam;

	/** 
	 * ShapeRenderer permet de dessiner facilement les
	 * formes de base
	 * Sera utilisé pour des fins de débogage
	 * **/
	ShapeRenderer debugRenderer = new ShapeRenderer();

	/** Textures **/
	private Texture bobTexture;
	private Texture blockTexture;

	private SpriteBatch spriteBatch;
	private boolean debug = false;
	private int width;
	private int height;
	private float ppuX;	// pixels par unité pour X
	private float ppuY;

	public void setSize (int w, int h) {
		this.width = w;
		this.height = h;
		ppuX = (float)width / CAMERA_WIDTH;
		ppuY = (float)height / CAMERA_HEIGHT;
	}

	public WorldRenderer(World world, boolean debug) {
		this.world = world;
		this.cam = new OrthographicCamera(CAMERA_WIDTH, CAMERA_HEIGHT);
		this.cam.position.set(CAMERA_WIDTH / 2f, CAMERA_HEIGHT / 2f, 0);
		this.cam.update();
		this.debug = debug;
		spriteBatch = new SpriteBatch();
		loadTextures();
	}

	private void loadTextures() {
		bobTexture = new  Texture(Gdx.files.internal("images/bob_01.png"));
		blockTexture = new Texture(Gdx.files.internal("images/block.png"));
	}

	public void render() {
		spriteBatch.begin();
			drawBlocks();
			drawBob();
		spriteBatch.end();
		if (debug)
			drawDebug();
	}

	private void drawBlocks() {
		for (Block block : world.getBlocks()) {
			spriteBatch.draw(
				blockTexture, 
				block.getPosition().x * ppuX, 
				block.getPosition().y * ppuY, 
				Block.getSize() * ppuX, 
				Block.getSize() * ppuY);
		}
	}

	private void drawBob() {
		Bob bob = world.getBob();
		spriteBatch.draw(
			bobTexture, 
			bob.getPosition().x * ppuX, 
			bob.getPosition().y * ppuY, 
			Bob.getSize() * ppuX, 
			Bob.getSize() * ppuY);
	}

	private void drawDebug() {
		// Démarrage du renderer
		debugRenderer.setProjectionMatrix(cam.combined);
		debugRenderer.begin(ShapeType.Line);

		// render blocks
		for (Block block : world.getBlocks()) {
			Rectangle rect = block.getBounds();
			float x1 = block.getPosition().x ;
			float y1 = block.getPosition().y ;
			debugRenderer.setColor(new Color(1, 0, 0, 1));
			debugRenderer.rect(x1, y1, rect.width, rect.height);
		}
		// Rendre Bob
		Bob bob = world.getBob();
		Rectangle rect = bob.getBounds();
		float x1 = bob.getPosition().x ;
		float y1 = bob.getPosition().y ;
		debugRenderer.setColor(new Color(0, 1, 0, 1));
		debugRenderer.rect(x1, y1, rect.width, rect.height);
		debugRenderer.end();
	}
}
  • CAMERA_WIDTH et _HEIGHT : Constantes pour la dimension du viewport.
  • bobTexture et blockTexture : Textures qui seront dessinés.
  • spriteBatch : Système qui s’occupe du rendu des textures.
  • debug : Utiliser dans le constructeur lorsque l’on désirera afficher les contours des objets.
  • ppuX et ppuY sont les facteurs de multiplication pour ajuster les positions et dimensions des unités du jeu en pixel. Par exemple si l’écran a une dimension de 1920 x 1080, chaque bloc aura 192px x 153.4px soit (1920 / 10) x (1080 / 7).
  • width et height gardent la dimension de l’écran en pixel.
  • setSize est utilisé pour calculer le ratio de pixel par unité. Cette méthode est appelé à chaque fois qu’il y aura un redimensionnement de l’écran.
  • loadTextures() : Méthode utilisée pour charger les textures.
  • render() : On sépare la partie debug de la méthode de rendu principale.
  • spriteBatch démarre la procédure de rendu, ajoute à la pile les éléments à dessiner et ensuite lance la procédure de rendu. Plus d’info ici.
  • drawBlocks et drawBob sont similaires. Ils dessinent chacun de leurs éléments à la position et dimension indiqués multipliés par les facteurs de redimensionnement ppuX et ppuY.
Il faut mettre à jour GameScreen pour ajouter la procédure de redimensionnement et modifier l’appel au constructeur WorldRenderer pour respecter la nouvelle signature.
/** ... omis ... **/
	public void show() {
		world = new World();
		renderer = new WorldRenderer(world, true);
	}

	public void resize(int width, int height) {
		renderer.setSize(width, height);
	}
/** ... omis ... **/
L’application devrait ressembler à ceci sans le débogage.
Figure 5 : Jeu sans le debug mode
Figure 7 : Jeu sans le debugage activé
Avec le débogage activé.
Figure 8 : Jeu avec le debug mode
Essayez-le sur une plateforme Android pour voir comment il s’affiche.

Traiter les entrées sur PC et Android

Pour l’instant, le jeu ne fait qu’afficher Bob et les blocs donc rien d’extravagant. Pour en faire un jeu, nous devons traiter les entrées pour créer des actions basées sur ceux-ci. Les touches sur le PC seront relativement simple. Nous allons utiliser les flèches avec les lettres Z et X pour respectivement faire déplacer, sauter et tirer Bob. Pour l’Android nous allons devoir définir des zones de touches où on simulera l’appuie de touches. Pour continuer dans la veine du MVC, nous séparer les contrôles des modèles et des vues. Si ce n’est déjà fait, créez un package controller. Pour débuter, nous allons contrôler Bob par les touches. Pour jouer, nous allons devoir considérer 4 touches soient gauche, droite, sauter et tirer. Parce que l’on utilise deux types d’entrées (clavier et tactile), les événements devront être fournis à un processeur qui lancera les actions. Chaque action est lancé par un événement. Le déplacement à gauche est lancé après avoir appuyé sur la flèche de gauche ou une partie de l’écran est touchée. Le saut est lancé lorsque Z sera appuyé et ainsi de suite. Créez un contrôleur très simple appelé WorldController.
package ca.collegeshawinigan.bobgame.controller;

import java.util.HashMap;
import java.util.Map;

import ca.collegeshawinigan.bobgame.model.Bob;
import ca.collegeshawinigan.bobgame.model.Bob.State;
import ca.collegeshawinigan.bobgame.model.World;

public class WorldController {

	enum Keys {
		LEFT, RIGHT, JUMP, FIRE
	}

	private World 	world;
	private Bob 	bob;

	static Map<Keys, Boolean> keys = 
		new HashMap<WorldController.Keys, Boolean>();

	// Initialisation static du hashmap
	static {
		keys.put(Keys.LEFT, false);
		keys.put(Keys.RIGHT, false);
		keys.put(Keys.JUMP, false);
		keys.put(Keys.FIRE, false);
	};

	public WorldController(World world) {
		this.world = world;
		this.bob = world.getBob();
	}

	// ** Écran touchée ou touche appuyée *********** //

	public void leftPressed() {
		keys.get(keys.put(Keys.LEFT, true));
	}

	public void rightPressed() {
		keys.get(keys.put(Keys.RIGHT, true));
	}

	public void jumpPressed() {
		keys.get(keys.put(Keys.JUMP, true));
	}

	public void firePressed() {
		keys.get(keys.put(Keys.FIRE, false));
	}

	public void leftReleased() {
		keys.get(keys.put(Keys.LEFT, false));
	}

	public void rightReleased() {
		keys.get(keys.put(Keys.RIGHT, false));
	}

	public void jumpReleased() {
		keys.get(keys.put(Keys.JUMP, false));
	}

	public void fireReleased() {
		keys.get(keys.put(Keys.FIRE, false));
	}

	/** Méthode de mise à jour principale **/
	public void update(float delta) {
		processInput();
		bob.update(delta);
	}

	/** Modification des paramètres et de l'état de Bob selon les entrées **/
	private void processInput() {
		if (keys.get(Keys.LEFT)) {
			// Flèche de gauche
			bob.setFacingLeft(true);
			bob.setState(State.WALKING);
			bob.getVelocity().x = -Bob.getSpeed();
		}

		if (keys.get(Keys.RIGHT)) {
			// Flèche de droite
			bob.setFacingLeft(false);
			bob.setState(State.WALKING);
			bob.getVelocity().x = Bob.getSpeed();
		}

		// On immobilise Bob si les deux touches sont appuyées.
		if ((keys.get(Keys.LEFT) && keys.get(Keys.RIGHT)) ||
				(!keys.get(Keys.LEFT) && !(keys.get(Keys.RIGHT)))) {
			bob.setState(State.IDLE);
			// acceleration is 0 on the x
			bob.getAcceleration().x = 0;
			// horizontal speed is 0
			bob.getVelocity().x = 0;
		}
	}
}
  • On définit un énumération des actions de Bob.
  • On définit un nouvel HashMap de clés et de booléen. Ce HashMap sera utilisé pour garder l’état des touches.
  • On initialise statiquement chacune des touches à faux.
  • Pour chacune des touches, une fois appuyée ou relâchée, on met à jour l’action.
  • La méthode processInput permet de traiter les informations entrées par les touches. Dans le cas présent, nous modifions l’état et autres propriétés de Bob.
Il faut maintenant modifier Bob.java.
public static final float SPEED = 4f;	// unité par seconde

public void setState(State newState) {
	this.state = newState;
}

public void update(float delta) {
	position.mulAdd(velocity.cpy(),delta);
}
  • Nous n’avons que mis à jour la vitesse de déplacement de Bob.
  • De plus, la méthode setState a été ajoutée, car elle a été oubliée plus tôt.
  • La méthode update() permet de mettre à jour la position de Bob à l’aide du temps d’exécution du jeu. Cette méthode sera appelé par le WorldController. Nous utilisons velocity.tmp(), car elle permet de cloner l’objet (velocity) avec la même valeur et ensuite on multiplie ce vecteur avec le temps delta en seconde qui constitue généralement une fraction de seconde. On doit cloner l’objet, sinon on modifie l’objet velocity.
    • Exemple
    • position <– (2, 3)
    • velocity <– (4, 0)
    • delta <– 0.25
    • position <– velocity * delta + position <– (4, 0) * 0.25 + (2, 3)
    • position <– (5, 3)
Nous avons pratiquement tout, il ne reste qu’à appeler les bons événements au bon moment. LibGdx possède un système de traitement des entrées qui possèdent quelques méthodes callback. Étant donné que GameScreen représente l’écran pour jouer, il est sensé de lui faire hérité de la gestion des événements. Pour faire ainsi, nous ferons hérité InputProcessor à GameScreen. GameScreen mis à jour  
/** IMPORTATIONS OMISES **/

// On fait hérité GameScreen d'InputProcessor
public class GameScreen implements Screen, InputProcessor {

	private World world;
	private WorldRenderer renderer;
	private WorldController	controller;

	private int width, height;

	@Override
	public void show() {
		world = new World();
		renderer = new WorldRenderer(world, false);
		controller = new WorldController(world);
		Gdx.input.setInputProcessor(this);
	}

	@Override
	public void render(float delta) {
		Gdx.gl.glClearColor(0.1f, 0.1f, 0.1f, 1);
		Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);

		controller.update(delta);
		renderer.render();
	}

	@Override
	public void resize(int width, int height) {
		renderer.setSize(width, height);
		this.width = width;
		this.height = height;
	}

	@Override
	public void hide() {
		Gdx.input.setInputProcessor(null);
	}

	@Override
	public void pause() {
		// TODO Auto-generated method stub
	}

	@Override
	public void resume() {
		// TODO Auto-generated method stub
	}

	@Override
	public void dispose() {
		Gdx.input.setInputProcessor(null);
	}

	// * Méthode InputProcessor ***************************//

	@Override
	public boolean keyDown(int keycode) {
		if (keycode == Keys.LEFT)
			controller.leftPressed();
		if (keycode == Keys.RIGHT)
			controller.rightPressed();
		if (keycode == Keys.Z)
			controller.jumpPressed();
		if (keycode == Keys.X)
			controller.firePressed();
		return true;
	}

	@Override
	public boolean keyUp(int keycode) {
		if (keycode == Keys.LEFT)
			controller.leftReleased();
		if (keycode == Keys.RIGHT)
			controller.rightReleased();
		if (keycode == Keys.Z)
			controller.jumpReleased();
		if (keycode == Keys.X)
			controller.fireReleased();
		return true;
	}

	@Override
	public boolean keyTyped(char character) {
		// TODO Auto-generated method stub
		return false;
	}

	@Override
	public boolean touchDown(int x, int y, int pointer, int button) {
		if (x < width / 2 && y > height / 2) {
			controller.leftPressed();
		}
		if (x > width / 2 && y > height / 2) {
			controller.rightPressed();
		}
		return true;
	}

	@Override
	public boolean touchUp(int x, int y, int pointer, int button) {
		if (x < width / 2 && y > height / 2) {
			controller.leftReleased();
		}
		if (x > width / 2 && y > height / 2) {
			controller.rightReleased();
		}
		return true;
	}

	@Override
	public boolean touchDragged(int x, int y, int pointer) {
		// TODO Auto-generated method stub
		return false;
	}

	@Override
	public boolean mouseMoved(int x, int y) {
		// TODO Auto-generated method stub
		return false;
	}

	@Override
	public boolean scrolled(int amount) {
		// TODO Auto-generated method stub
		return false;
	}
}
  • On fait hérité GameScreen de InputProcessor
  • Les méthodes abstraites qui doivent être codées sont ajoutées.
  • Pour chaque touche qui est appuyé ou relâché, on gère l’action qui doit être effectuée.
  • Pour les méthodes touchUp et touchDown qui sont liées aux écrans tactiles, on sépare l’écran en 4. Le coin inférieur droit et gauche sont respectivement l’équivalent des touches droites et gauches.
Attention!
Étant donné que touchDragged n’est pas implanté, cela rend le jeu instable sur les plateformes Android.
L’exécution de l’application sur le bureau ou sur une machine Android montre le fonctionnement des contrôle. Il est bien de noter que l’on peut utiliser la souris sur le bureau pour simuler le touché. Ceci est dû aux méthodes touchXXX qui gèrent aussi la souris sur le bureau. Pour réparer ces méthodes, il suffit d’ajouter le code suivant au début des méthodes touchUp et touchDown.
if (!Gdx.app.getType().equals(ApplicationType.Android))
	return false;
  •  Ce code ne fait que retourner faux si l’application n’est pas roulée sur Android.
L’application devrait maintenant permettre à faire déplacer Bob de gauche à droite. Évidemment, ce n’est que la première partie.   Espace de fin

Résumé

En résumé, nous avons pu affiché le décor et Bob. Ainsi que déplacer Bob de gauche à droite. Il reste beaucoup de chose à faire pour rendre le jeu plus intéressant. Peu à peu nous allons introduire différentes notions pour améliorer le jeu. Nous avons besoin d’effectuer les tâches suivantes :
  • Interaction avec le terrain collision et saut
  • Animation
  • Un niveau plus grand avec une caméra qui suit Bob
  • Ennemies et fusils
  • Sons
  • Améliorer les contrôles
  • Des écrans supplémentaires pour la fin de la partie et le début
  • et plus avec LibGdx!
Vos commentaires sont les bienvenus!
Note
La deuxième partie du didacticiel est disponible ici.
Mis à jour le 13/04/2016 par Rémi ROUSSEAU.

Publié

dans

par

Étiquettes :

Commentaires

10 réponses à “Développement d’un jeu de plateforme 2D avec libGdx en MVC – Partie 1”

  1. Avatar de John
    John

    Bonjour, je suis entrain de suivre ce tutoriel et je suis bloqué au WorldRenderer. J’ai des erreurs aux lignes suivantes:
    debugRenderer.begin(ShapeType.Rectangle);
    //J’ai l’erreur « Rectangle cannot be resolved or is not a field »

    for (Block block : world.getBlocks())
    // »Type missmatch: cannot convert from element type Object to Block

    Que puis-je faire pour régler celà?

  2. Avatar de Chnapy
    Chnapy

    @John
    Pour ton premier problème : remplace Rectangle par Filled.
    Pour le second :
    Dans la classe World, remplace ceci :
    Array blocks = new Array();
    Par ça :
    Array blocks = new Array();

    Et remplace le getter par ca :
    public Array getBlocks() {
    return blocks;
    }

  3. Avatar de Valentin
    Valentin

    Bonjour je suis ce tuto pour m’aider dans un projet,

    dans votre class Bob.java vous utilisez la method tmp():
    public void update(float delta) {
    position.add(velocity.tmp().mul(delta));
    }
    or celle ci a été supprimé de libgdx, et je ne trouve pas d’alternative pour la remplacer,

    pourriez vous m’aidez?

  4. Avatar de Kay
    Kay

    Un tutoriel des plus utiles…cependant je ne peux pas avancer. En effet le Getter de la classe World doit être faux puisque dans la classe WorldRenderer, la boucle ne fonctionne pas car : world.getBlocks()) n’est pas possible. Incompatibilité entre un éléments Block et un élément Object…

  5. Avatar de Kay
    Kay

    En réponse à mon précédent commentaire et pour aider ceux qui ont rencontré le même soucis, voici comment le corriger.

    Tout tourne autour de votre Array. En effet, il s’agit d’un type de classe générique. Il convient donc de préciser le type d’objet que notre Array doit contenir, ce que l’auteur de ce tutoriel n’a pas fait. Java pense donc qu’il s’agit d’un type Object, d’ou le soucis entre Bloc et Object…

    Dans votre classe World :

    Remplacez Array blocks = new Array(); par la chose suivante : Array blocks = new Array();

    Idem pour votre Getter, ce qui doit vous donner :

    public Array getBlocks() {
    return blocks;
    }

    Voilà. Vérifier dans votre classe WorldRenderer, normalement il n’y a plus aucun problème. J’en profite aussi pour préciser que si vous rencontrez des soucis au niveau des lignes qui comportent des « GL10 » c’est normal, c’est une fonction OpenGL qui n’existe plus. Remplacez le 10 par un 20 et tout est réglé.

  6. Avatar de Kay
    Kay

    Houlà je me suis emmêlé dans mes copiers collers de code. Non oubliez mon précédent message, voici la vraie solution :

    Dans votre classe World :

    Remplacez Array blocks = new Array(); par la chose suivante :
    Array blocks = new Array();

    Idem pour votre Getter, ce qui doit vous donner :

    public Array getBlocks() {
    return blocks;
    }

    En gros ça donne ce schéma :

    Array nomvariable = new Array();

  7. Avatar de Kay
    Kay

    D’accord ! Donc visiblement mon commentaire est automatiquement modifié pour je ne sais quelle raison dés que je modifie une ligne de code donné par ce tutoriel. Splendide. Messieurs dames, voici cette foutue correction :

    http://pastebin.com/ZbWYCsgq

    Bon sang. Bon courage !

  8. Avatar de Nicolas
    Nicolas

    Bonjour,

    Pour une raison inconnue, mis à part aujourd’hui (20150714), je n’ai jamais reçu de courriel pour m’avertir des nouveaux commentaires qui sont inclus dans mes articles…

    Désolé pour les soucis causés!

    Concernant ce tuto, comme vous pouvez le constater, je n’y ai pas touché depuis un certain temps. Je vais tenter de le mettre à jour prochainement.

    Peut-être que quelqu’un serait intéressé de le mettre à jour!

    En attendant, je vous invite à vous référer à l’article original.

  9. Avatar de Kay
    Kay

    Bonjour Nicolas ! Tutotriel néanmoins très utile même si actuellement un peu dépassé sur certains points (mais les soucis que nous rencontrons dans le code à cause de ce décalage sont assez simples à corriger). Mais il m’a été très utile quoi qu’il en soit ! Je vous encourage à l’update, à ce jour je n’en ai pas trouvé d’aussi complet sur le net. Enfin, pas en français en tout cas. Il en aidera plus d’un !

    Cordialement

  10. Avatar de Pat
    Pat

    Yo !

    Bon tuto !
    Devrait être mis à jour (je suis volontaire au cas ou… )

    @ tous ceux qui ont le problème du Array : bah, comme tout le monde l’a déjà dit, Array a évolué et nécessite désormais de préciser le type d’objet que va contenir le tableau lors de sa déclaration. (TIPs : utilisez le ctrl+space, ainsi que votre matière grise, quand vous déclarez votre tableau)

    @ Valentin : idem. évolution du langage (ici, de la classe Vector2).
    Tiens copain :
    // posZombie.add(velociteZombie.tmp().mul(pDelta));
    posZombie.mulAdd(velociteZombie, pDelta);

    @ Kay : protéger son site des injections est nécessaire de nos jours. Inutile, donc, de tenter d’insérer des balises de ce type :
    ‘inferieur’balise_name’supperieur’ dans un simple champ de saisie web…

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

*

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.