Activitats

Estats del joc

L’objectiu d’aquesta activitat és practicar els conceptes apresos en els continguts tot fent canvis en les classes InputHandler, GameScreen, entre altres.

En aquesta activitat afegirem tres estats del joc:

  • READY: estat d’espera, el joc no començarà fins que l’usuari faci clic a la pantalla.
  • RUNNING: el joc s’està executant, els asteroides es mouen i podem desplaçar la nau espacial.
  • GAMEOVER: el joc ha acabat, mostrem el missatge de “Game Over” i si fem clic passem a l’estat READY.
Figura Imatge dels tres estats del joc

Tota la gestió dels estats la farem des de GameScreen ja que és des d’on gestionem tots els objectes i fem les crides a actualitzar i dibuixar de l'Stage.

Creem un enum amb els estats del joc per facilitar la comprensió del codi i el primer que farem és eliminar la variable gameover ja que no serà necessària.

L'enum pot ser el següent:

  1. // Els estats del joc
  2. public enum GameState {
  3.  
  4. READY, RUNNING, GAMEOVER
  5.  
  6. }

Serà necessària també una variable private GameState currentState; que inicialitzarem en el constructor a GameState.READY.

Canviem completament el mètode render, esborrem tot el seu contingut i afegim el següent codi:

  1. @Override
  2. public void render(float delta) {
  3.  
  4. // Dibuixem tots els actors de l'stage
  5. stage.draw();
  6.  
  7. // Depenent de l'estat del joc farem unes accions o unes altres
  8. switch (currentState) {
  9.  
  10. case GAMEOVER:
  11. updateGameOver(delta);
  12. break;
  13. case RUNNING:
  14. updateRunning(delta);
  15. break;
  16. case READY:
  17. updateReady();
  18. break;
  19.  
  20. }
  21.  
  22. //drawElements();
  23.  
  24. }

Sempre dibuixarem (draw) els objectes que existeixin a l'stage però no els actualitzarem (act()) en tots els casos. Això ho decidirem en cadascun dels estats del joc. Fem un switch de l’estat actual i fem la crida a 3 mètodes que haurem de crear: private void updateGameOver( float delta), private void updateRunning(float delta) i private void updateReady(); en els dos primers mètodes necessitem la variable delta ja que hem de desplaçar els objectes però en el cas de l’estat READY no és necessària.

Començarem per l’estat READY.

READY

Modifiquem el text del GlyphLayout que havíem definit al constructor, canviant “GameOver” per “Are you\nready?” quedant el codi:

  1. // Iniciem el GlyphLayout
  2. textLayout = new GlyphLayout();
  3. textLayout.setText(AssetManager.font, "Are you\nready?");

Amb “\n” fem un salt de línia.

A updateReady() simplement hem de dibuixar aquest text, i ho farem amb l’ajuda de l’objecte batch. Farem que el text surti centrat per pantalla per tant necessitarem el width i el height del layout.

El mètode updateReady() quedarà de la següent manera:

  1. private void updateReady() {
  2.  
  3. // Dibuixem el text al centre de la pantalla
  4. batch.begin();
  5. AssetManager.font.draw(batch, textLayout, (Settings.GAME_WIDTH / 2) - textLayout.width / 2, (Settings.GAME_HEIGHT / 2) - textLayout.height / 2);
  6. batch.end();
  7.  
  8. }

Si executem el joc, veurem que apareix la nau espacial i el text centrat en la pantalla però no podrem fer res més, haurem de canviar d’estat quan fem clic a la pantalla i crear el mètode updateRunning.

Per canviar l’estat de la pantalla quan hi fem clic necessitarem fer canvis en la classe GameScreen abans de passar a la classe InputHandler. Crearem el getter i el setter de currentState des del menú Code/Generate, quedant com a resultat:

  1. public GameState getCurrentState() {
  2. return currentState;
  3. }
  4.  
  5. public void setCurrentState(GameState currentState) {
  6. this.currentState = currentState;
  7. }

Després caldrà anar a la classe InputHandler i allà haurem de canviar l’estat del joc de READY a RUNNING quan l’usuari faci clic a la pantalla. Per fer-ho cal posar el següent codi al mètode touchDown:

  1. @Override
  2. public boolean touchDown(int screenX, int screenY, int pointer, int button) {
  3.  
  4. switch (screen.getCurrentState()) {
  5.  
  6. case READY:
  7. // Si fem clic comencem el joc
  8. screen.setCurrentState(GameScreen.GameState.RUNNING);
  9. break;
  10. case RUNNING:
  11. previousY = screenY;
  12.  
  13. stageCoord = stage.screenToStageCoordinates(new Vector2(screenX, screenY));
  14. Actor actorHit = stage.hit(stageCoord.x, stageCoord.y, true);
  15. if (actorHit != null)
  16. Gdx.app.log("HIT", actorHit.getName());
  17. break;
  18. }
  19.  
  20. return true;
  21. }

Si al fer clic estem a l’estat READY passarem a l’estat RUNNING i si estem en aquest darrer tindrem la funcionalitat que teníem abans. Guardem la posició de la Y per saber en quina direcció ha estat arrossegat el dit i comprovarem si s’ha fet clic en algun actor. Si tornem a executar i fem clic en la pantalla desapareixerà el text però no es mourà res, d’això s’encarregarà l’estat RUNNING.

RUNNING

Quan el joc està en funcionament hem d’actualitzar tots els elements i per tant haurem de fer la crida al mètode stage.act(delta). A més, haurem de controlar si es produeix alguna col·lisió i en cas de produir-se, haurem de:

  • Reproduir el so de l’explosió
  • Eliminar l'actor Spacecarft
  • Canviar el text del layout a “Game Over :'(”
  • Canviar l’estat actual a GAMEOVER

El codi del mètode serà el següent:

  1. private void updateRunning(float delta) {
  2. stage.act(delta);
  3.  
  4. if (scrollHandler.collides(spacecraft)) {
  5. // Si hi ha hagut col·lisió: reproduïm l'explosió i posem l'estat a GameOver
  6. AssetManager.explosionSound.play();
  7. stage.getRoot().findActor("spacecraft").remove();
  8. textLayout.setText(AssetManager.font, "Game Over :'(");
  9. currentState = GameState.GAMEOVER;
  10. }
  11. }

Si l’executem podrem començar el joc i jugar-lo fins que col·lideixin amb un asteroide. En aquell moment desapareixerà la nau i tots els asteroides pararan.

GAMEOVER

Una vegada hem col·lidit, hem de continuar desplaçant els asteroides i mostrarem el missatge que hem definit abans. A més, hem de dibuixar l’explosió i incrementar la variable explosionTime.

El codi del mètode updateGameOver() serà:

  1. private void updateGameOver(float delta) {
  2. stage.act(delta);
  3.  
  4. batch.begin();
  5. AssetManager.font.draw(batch, textLayout, (Settings.GAME_WIDTH - textLayout.width) / 2, (Settings.GAME_HEIGHT - textLayout.height) / 2);
  6.  
  7. // Si hi ha hagut col·lisió: reproduïm l'explosió i posem l'estat a GameOver
  8. batch.draw(AssetManager.explosionAnim.getKeyFrame(explosionTime, false), (spacecraft.getX() + spacecraft.getWidth() / 2) - 32, spacecraft.getY() + spacecraft.getHeight() / 2 - 32, 64, 64);
  9. batch.end();
  10.  
  11. explosionTime += delta;
  12.  
  13. }

La funcionalitat del joc ja és completa però seria interessant poder començar a jugar de nou des de l’estat GAMEOVER. Per fer-ho, tornem al mètode touchDown d'InputHandler i afegim en el switch el cas de GAMEOVER:

  1. // Si l'estat és GameOver tornem a iniciar el joc
  2. case GAMEOVER:
  3. screen.reset();
  4. break;

Si l’estat és GAMEOVER reiniciem la pantalla. En la classe GameScreen haurem de crear el mètode reset() que:

  • Posarà el text d’inici amb el nom de l’aplicació i el Ready?.
  • Cridarà als mètodes reset() d'spacecraft i d'ScrollHandler. El mètode reset() d'spacecraft la posarà en la posició inicial, amb la direcció STRAIGHT i reiniciarà el rectangle de col·lisions. El reset de la classe ScrollHandler cridarà al reset de tots els asteroides assignant-los una nova posició: el primer tindrà Settings.GAME_WIDTH i la resta els afegirem a la cua de l’anterior amb la separació definida a Settings.
  • Canviarà l’estat a READY.
  • Afegirà la nau a l'stage.
  • Inicialitzarà la variable explosionTime.

El mètode reset() de GameScreen serà:

  1. public void reset() {
  2.  
  3. // Posem el text d'inici
  4. textLayout.setText(AssetManager.font, "Are you\nready?");
  5.  
  6. // Cridem als restart dels elements
  7. spacecraft.reset();
  8. scrollHandler.reset();
  9.  
  10. // Posem l'estat a READY
  11. currentState = GameState.READY;
  12.  
  13. // Afegim la nau a l'stage
  14. stage.addActor(spacecraft);
  15.  
  16. // Posem a 0 les variables per controlar el temps jugat i l'animació de l'explosió
  17. explosionTime = 0.0f;
  18.  
  19. }

El reset() de la classe Spacecraft:

  1. public void reset() {
  2.  
  3. // La posem a la posició inicial i a l'estat normal
  4. position.x = Settings.SPACECRAFT_STARTX;
  5. position.y = Settings.SPACECRAFT_STARTY;
  6. direction = SPACECRAFT_STRAIGHT;
  7. collisionRect = new Rectangle();
  8. }

I el reset() d'ScrollHandler:

  1. public void reset() {
  2.  
  3. // Posem el primer asteroide fora de la pantalla per la dreta
  4. asteroids.get(0).reset(Settings.GAME_WIDTH);
  5. // Calculem les noves posicions de la resta d'asteroides
  6. for (int i = 1; i < asteroids.size(); i++) {
  7.  
  8. asteroids.get(i).reset(asteroids.get(i - 1).getTailX() + Settings.ASTEROID_GAP);
  9.  
  10. }
  11. }

I amb això ja tenim el joc amb els estats acabats.

Actions

L’objectiu d’aquesta activitat és el de practicar la creació de noves pantalles de joc i les accions que podem aplicar als actors.

Crearem una nova pantalla anomenada SplasScreen. Aquesta classe serà l’encarregada de crear la càmera i el viewport, que passarem conjuntament amb el batch a la pantalla GameScreen. Per facilitar la redacció de l’activitat, farem la gestió del clic a la pantalla des del mètode render de la SplashScreen. El codi serà el següent:

  1. @Override
  2. public void render(float delta) {
  3. ...
  4. if (Gdx.input.isTouched()) {
  5. game.setScreen(new GameScreen(stage.getBatch(), stage.getViewport()));
  6. dispose();
  7. }
  8. ...
  9. }

La variable game del codi anterior és l’objecte SpaceRace.

Creeu aquesta nova pantalla que haurà de contenir:

  • Un actor que serà un objecte de tipus image amb el TextureRegion d'AssetManager.background.
  • Un container amb una label que tindrà de text “SpaceRace”. El text haurà d’escalar-se fins a 1.5 vegades la seva mida en un segon i tornar a la seva mida original en un segon de manera infinita, és a dir, creixerà i es reduirà eternament. El text haurà d’estar al centre de la pantalla.
  • Un objecte image del TextureRegion d'AssetManager.spacecraft que sortirà per l’esquerra de la pantalla i desapareixerà per la part dreta de manera infinita. La imatge haurà de sortir a continuació del label.
Figura Els objectes en moviment

El primer que haurem de fer és crear la classe SplashScreen a la secció d’screens. Fem clic a screens amb el botó dret i seleccionem new/Java class i li posem de nom SplashScreen. Fem que aquesta implementi Screen i solucionem l’error amb Implement methods.

Definim quatre variables d’instància:

  1. private Stage stage;
  2. private SpaceRace game;
  3.  
  4. private Label.LabelStyle textStyle;
  5. private Label textLbl;

Creem el constructor, on necessitarem rebre l’objecte SpaceRace per gestionar el canvi de pantalla. En el constructor crearem la càmera i el viewport i traurem el codi de la classe GameScreen. El constructor quedarà de la següent manera:

  1. public class SplashScreen implements Screen {
  2.  
  3. private Stage stage;
  4. private SpaceRace game;
  5.  
  6. private Label.LabelStyle textStyle;
  7. private Label textLbl;
  8.  
  9. public SplashScreen(SpaceRace game) {
  10.  
  11. this.game = game;
  12.  
  13. // Creem la càmera de les dimensions del joc
  14. OrthographicCamera camera = new OrthographicCamera(Settings.GAME_WIDTH, Settings.GAME_HEIGHT);
  15. // Posant el paràmetre a true configurem la càmera perquè
  16. // faci servir el sistema de coordenades Y-Down
  17. camera.setToOrtho(true);
  18.  
  19. // Creem el viewport amb les mateixes dimensions que la càmera
  20. StretchViewport viewport = new StretchViewport(Settings.GAME_WIDTH, Settings.GAME_HEIGHT, camera);
  21.  
  22. // Creem l'stage i assginem el viewport
  23. stage = new Stage(viewport);
  24.  
  25.  
  26. }

Primer crearem el fons i l’agregarem a l'stage amb la línia:

  1. stage.addActor(new Image(AssetManager.background));

Per crear l’etiqueta i el contenidor, afegim al final del mètode:

  1. textStyle = new Label.LabelStyle(AssetManager.font, null);
  2. textLbl = new Label("SpaceRace", textStyle);
  3.  
  4. Container container = new Container(textLbl);
  5. container.setTransform(true);
  6. container.center();
  7. container.setPosition(Settings.GAME_WIDTH / 2, Settings.GAME_HEIGHT / 2);

El contenidor ja estarà centrat respecte la pantalla així que no és necessari restar a la pantalla les seves dimensions per obtenir la coordenada correcta. Les accions que li assignarem seran les següents:

  1. container.addAction(Actions.repeat(RepeatAction.FOREVER, Actions.sequence(Actions.scaleTo(1.5f, 1.5f, 1), Actions.scaleTo(1, 1, 1))));
  2. stage.addActor(container);

Repetirem contínuament l’escalat a 1.5f en 1 segon i una vegada fet, el tornarem a escalar a la seva mida original durant 1 segon. Finalment afegirem el container a l’objecte stage.

Hem de fer que els actors es dibuixin i s’actualitzin, per això editarem el mètode render i afegim les següents línies:

  1. stage.draw();
  2. stage.act(delta);

Si anem a la classe SpaceRace i modifiquem el setScreen per executar la nova pantalla: setScreen(new SplashScreen(this)); i executem el projecte veurem el fons de pantalla amb el text “SpaceRace” canviant la seva mida de manera constant.

Per afegir la nau espacial, crearem l’objecte Image i posarem com a coordenada Y la meitat de la pantalla més l’alçada de l’etiqueta:

  1. Image spacecraft = new Image(AssetManager.spacecraft);
  2. float y = Settings.GAME_HEIGHT / 2 + textLbl.getHeight();

Hem de fer que es mogui contínuament d’esquerra a dreta així que li aplicarem les accions Actions.repeat, Action.sequence i Action.moveTo:

  1. spacecraft.addAction(Actions.repeat(RepeatAction.FOREVER, Actions.sequence(Actions.moveTo(0 - spacecraft.getWidth(), y), Actions.moveTo(Settings.GAME_WIDTH, y, 5))));

En la seqüència:

  1. Posem la nau fora de la pantalla restant-li a 0 l’amplada de la nau i en la coordenada Y.
  2. La fem moure fins l’ample de la pantalla en 5 segons.

Si l’afegim amb stage.addActor(spacecraft); i executem el projecte ja tindrem la imatge i l’etiqueta en moviment.

Ja sols ens queda gestionar el canvi de pantalla entre SplashScreen i GameScreen. Afegim el següent codi al mètode render:

  1. if (Gdx.input.isTouched()) {
  2. game.setScreen(new GameScreen(stage.getBatch(), stage.getViewport()));
  3. dispose();
  4. }

I modificarem el constructor de GameScreen perquè quedi de la següent manera:

  1. public GameScreen(Batch prevBatch, Viewport prevViewport) {
  2.  
  3. // Iniciem la música
  4. AssetManager.music.play();
  5.  
  6. // Creem el ShapeRenderer
  7. shapeRenderer = new ShapeRenderer();
  8.  
  9. // Creem l'stage i assginem el viewport
  10. stage = new Stage(prevViewport, prevBatch);
  11.  
  12. batch = stage.getBatch();
  13.  
  14. // Creem la nau i la resta d'objectes
  15. spacecraft = new Spacecraft(Settings.SPACECRAFT_STARTX, Settings.SPACECRAFT_STARTY, Settings.SPACECRAFT_WIDTH, Settings.SPACECRAFT_HEIGHT);
  16. scrollHandler = new ScrollHandler();
  17.  
  18. // Afegim els actors a l'stage
  19. stage.addActor(scrollHandler);
  20. stage.addActor(spacecraft);
  21. // Donem nom a l'Actor
  22. spacecraft.setName("spacecraft");
  23.  
  24. // Iniciem el GlyphLayout
  25. textLayout = new GlyphLayout();
  26. textLayout.setText(AssetManager.font, "Are you\nready?");
  27.  
  28. currentState = GameState.READY;
  29.  
  30. // Assignem com a gestor d'entrada la classe InputHandler
  31. Gdx.input.setInputProcessor(new InputHandler(this));
  32.  
  33. }

S’ha eliminat la creació del viewport i la càmera i hem definit l’objecte stage amb el batch i viewport que enviem des de la classe SplashScreen.

Si executem el joc el primer que ens sortirà serà la pantalla SplashScreen i quan fem clic passarem a l’estat READY de GameScreen i podrem jugar tal com havíem fet fins ara.

Anar a la pàgina anterior:
Desenvolupament d'un joc
Anar a la pàgina següent:
Exercicis d'autoavaluació