Question:
I am trying to program an Endless Running style video game in Android Studio with libGDX and I have encountered a problem in which I have been stalled for a long time, once the game detects the collision, the game goes to the "Game Over" state but I am not able to bring it back to startup state to restart the game. Another bug I've run into has been that I can't make the coins disappear when the character collides with them, although this is a minor bug.
package es.rodriguez.dinojump;
import com.badlogic.gdx.ApplicationAdapter;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
import com.badlogic.gdx.math.Intersector;
import com.badlogic.gdx.math.Rectangle;
import com.sun.org.apache.bcel.internal.generic.RETURN;
public class Game extends ApplicationAdapter {
SpriteBatch batch;
//Fondos
Texture background;
Texture background2;
int bg2altura = 200;//altura del suelo
//Dinosaurio
Texture[] dinos;
float velocity = -30;
float gravity = 2;
float impulse = -30;
float dinoY;
int dinoAnim = 0;
int salto = 0;
Rectangle dinoR;
//Bomba y moneda
Texture bomb;
Rectangle bombR;
Texture coin;
Rectangle coinR;
float objectVelocity = 10;
int numberOfObjects = 4;
float[] objectX = new float[numberOfObjects];
float distanceBetweenObjects;
GameState gameState = GameState.TAP_TO_PLAY;
//private ShapeRenderer shrend;
//private boolean debug;
@Override
public void create() {
batch = new SpriteBatch();
SoundManager.loadSFX();
background = new Texture("bg.png");
background2 = new Texture("bg2.jpg");
//Dino
dinos = new Texture[5];
dinos[0] = new Texture("frame-1.png");
dinos[1] = new Texture("frame-2.png");
dinos[2] = new Texture("frame-3.png");
dinos[3] = new Texture("frame-4.png");
dinos[4] = new Texture("dizzy-1.png");
dinoY = (bg2altura);
dinoR = new Rectangle(dinos[0].getWidth() / 2F, dinoY, dinos[0].getWidth() / 2, dinos[0].getHeight() / 2);
//Bombas y monedas
bomb = new Texture("bomb.png");
bombR = new Rectangle();
coin = new Texture("coin.png");
coinR = new Rectangle();
distanceBetweenObjects = Gdx.graphics.getWidth() * 3 / 4;
for (int i = 0; i < numberOfObjects; i++) {
objectX[i] = Gdx.graphics.getWidth() + i * distanceBetweenObjects;
}
/*
//Para testear
shrend = new ShapeRenderer();
debug = true;*/
}
@Override
public void render() {
Gdx.gl.glClearColor(1, 0, 0, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
switch(gameState) {
case TAP_TO_PLAY:
if (Gdx.input.justTouched()) {
gameState = GameState.PLAY;
salto += 1;
SoundManager.play(SFX.JUMP);
}
break;
case PLAY:
SoundManager.play(SFX.GAME);
for (int i = 0; i < numberOfObjects; i++) {
if (objectX[i] < -bomb.getWidth()) {
objectX[i] = objectX[i] + numberOfObjects * distanceBetweenObjects;
}
objectX[i] = objectX[i] - objectVelocity;
}
if (Gdx.input.justTouched()) {
if (salto < 2) { //
salto += 1;
SoundManager.play(SFX.JUMP);
velocity = impulse;
}
} else {
velocity = velocity + gravity;
dinoR.y -= velocity;
}
//Para que no pase para abajo del suelo
if (dinoR.y < bg2altura) {
dinoR.y = bg2altura;
salto = 0;
}
//Animar el dinosaurio
if (dinoAnim == 3) {
dinoAnim = 0;
} else {
dinoAnim += 1;
}
break;
case GAME_OVER:
if (dinoR.overlaps(bombR)) {
gameState = gameState.TAP_TO_PLAY; //falta reiniciar juego
}
if(dinoR.overlaps(coinR)){
SoundManager.play(SFX.SCORE);
}
//Falta reiniciar juego
break;
}
batch.begin();
batch.draw(background, 0, 0, Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
batch.draw(background2, 0, 0, Gdx.graphics.getWidth(), bg2altura);
batch.draw(dinos[dinoAnim], dinoR.x, dinoR.y,dinos[0].getWidth()/2, dinos[0].getHeight()/2);
for (int i = 0; i < 4; i++) {
bombR = new Rectangle(objectX[i], bg2altura, bomb.getWidth()/2, bomb.getHeight()/2);
batch.draw(bomb, bombR.x, bombR.y, bomb.getWidth(), bomb.getHeight());
coinR = new Rectangle(objectX[i], bg2altura+coin.getHeight()*3,coin.getWidth()/2, coin.getHeight()/2);
batch.draw(coin, coinR.x, coinR.y, coinR.width, coinR.height);
if (dinoR.overlaps(bombR)) {
SoundManager.play(SFX.HIT);
dinos[dinoAnim] = dinos[4];
SoundManager.hitSFX.dispose();
SoundManager.gameSFX.stop();
gameState = gameState.GAME_OVER; //falta reiniciar juego
}else if(dinoR.overlaps(coinR)){
SoundManager.play(SFX.SCORE);
//Falta contador de monedas, arreglar o sonido
}
}
batch.end();
}
@Override
public void dispose() {
batch.dispose();
background.dispose();
background2.dispose();
// shrend.dispose();
}
}
The code can be improved but I am starting.
Answer:
Alternative 1
You can encapsulate all the code you have in that class and then restart by calling new Game()
me explain …
It would consist of 3 different classes
-
Assets
which will handle allDisposables
. Separating this will avoid creating them every time we callnew Game()
-
GameManager
this is the main class, which extendsApplicationAdapter
this class will be in charge of creating a new game and rendering it. -
Game
which will handle all the logic of the game.
Assets
public class Assets {
public Texture background;
public Texture background2;
public Texture dinos = new Texture[5];
public Texture bomb;
public Texture coin;
public Texture shrend;
public Assets(){
SoundManager.loadSFX();
background = new Texture("bg.png");
background2 = new Texture("bg2.jpg");
dinos[0] = new Texture("frame-1.png");
dinos[1] = new Texture("frame-2.png");
dinos[2] = new Texture("frame-3.png");
dinos[3] = new Texture("frame-4.png");
dinos[4] = new Texture("dizzy-1.png");
bomb = new Texture("bomb.png");
coin = new Texture("coin.png");
shrend = new ShapeRenderer();
}
public void dispose(){
for(Texture t: dinos)
t.dispose();
background.dispose();
background2.dispose();
bomb.dispose();
coin.dispose();
shrend.dispose();
}
}
GameManager
public class GameManager extends ApplicationAdapter { // OJO ESTA ES LA CLASE PRINCIPAL, LA UNICA QUE EXTIENDE `ApplicationAdapter`
private Assets assets;
private batch;
@Override
public void create() {
batch = new SpriteBatch();
assets = new Assets();
game = new Game(assets);
}
@Override
public void render() {
Gdx.gl.glClearColor(1, 0, 0, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
game.render(batch);
if(game.gameState == GameState.GAME_OVER)
game = new Game(assets);// aqui reseteas el juego
}
@Override
public void dispose() {
batch.dispose();
assets.dispose();
}
}
Game
public class Game {
//Fondos
int bg2altura = 200;//altura del suelo
float velocity = -30;
float gravity = 2;
float impulse = -30;
float dinoY;
int dinoAnim = 0;
int salto = 0;
Rectangle dinoR;
Rectangle bombR;
Rectangle coinR;
float objectVelocity = 10;
int numberOfObjects = 4;
float[] objectX = new float[numberOfObjects];
float distanceBetweenObjects;
public GameState gameState = GameState.TAP_TO_PLAY;
private Assets assets;
//private ShapeRenderer shrend;
//private boolean debug;
public Game(Assets assets) {
this.assets = assets;
dinoY = (bg2altura);
dinoR = new Rectangle(assets.dinos[0].getWidth() / 2F, dinoY, assets.dinos[0].getWidth() / 2, assets.dinos[0].getHeight() / 2);
//Bombas y monedas
bombR = new Rectangle();
coinR = new Rectangle();
distanceBetweenObjects = Gdx.graphics.getWidth() * 3 / 4;
for (int i = 0; i < numberOfObjects; i++) {
objectX[i] = Gdx.graphics.getWidth() + i * distanceBetweenObjects;
}
/*
//Para testear
shrend = new ShapeRenderer();
debug = true;*/
}
public void render(SpriteBatch batch) {
switch(gameState) {
case TAP_TO_PLAY:
if (Gdx.input.justTouched()) {
gameState = GameState.PLAY;
salto += 1;
SoundManager.play(SFX.JUMP);
}
break;
case PLAY:
SoundManager.play(SFX.GAME);
for (int i = 0; i < numberOfObjects; i++) {
if (objectX[i] < -bomb.getWidth()) {
objectX[i] = objectX[i] + numberOfObjects * distanceBetweenObjects;
}
objectX[i] = objectX[i] - objectVelocity;
}
if (Gdx.input.justTouched()) {
if (salto < 2) { //
salto += 1;
SoundManager.play(SFX.JUMP);
velocity = impulse;
}
} else {
velocity = velocity + gravity;
dinoR.y -= velocity;
}
//Para que no pase para abajo del suelo
if (dinoR.y < bg2altura) {
dinoR.y = bg2altura;
salto = 0;
}
//Animar el dinosaurio
if (dinoAnim == 3) {
dinoAnim = 0;
} else {
dinoAnim += 1;
}
break;
// ESTO ES REDUDANTE, ahora `GameManager` se encarga de este caso
//case GAME_OVER:
// if (dinoR.overlaps(bombR)) {
// gameState = gameState.TAP_TO_PLAY; //falta reiniciar juego
// }
// if(dinoR.overlaps(coinR)){
// SoundManager.play(SFX.SCORE);
// }
//Falta reiniciar juego
// break;
}
batch.begin();
batch.draw(assets.background, 0, 0, Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
batch.draw(assets.background2, 0, 0, Gdx.graphics.getWidth(), bg2altura);
batch.draw(assets.dinos[dinoAnim], dinoR.x, dinoR.y,assets.dinos[0].getWidth()/2, assets.dinos[0].getHeight()/2);
for (int i = 0; i < 4; i++) {
bombR = new Rectangle(objectX[i], bg2altura, assets.bomb.getWidth()/2, assets.bomb.getHeight()/2);
batch.draw(assets.bomb, bombR.x, bombR.y, assets.bomb.getWidth(), assets.bomb.getHeight());
coinR = new Rectangle(objectX[i], bg2altura+assets.coin.getHeight()*3,assets.coin.getWidth()/2, assets.coin.getHeight()/2);
batch.draw(assets.coin, coinR.x, coinR.y, coinR.width, coinR.height);
if (dinoR.overlaps(bombR)) {
SoundManager.play(SFX.HIT);
assets.dinos[dinoAnim] = dinos[4];
SoundManager.hitSFX.dispose();
SoundManager.gameSFX.stop();
gameState = gameState.GAME_OVER; //falta reiniciar juego, ya no.
}else if(dinoR.overlaps(coinR)){
SoundManager.play(SFX.SCORE);
//Falta contador de monedas, arreglar o sonido
}
}
batch.end();
}
}
Grades:
-
case GAME_OVER
is now redundant, this case is handled byGameManager
-
new Rectangle()
in render is a bad idea, creating this object at 60 times per second can be overwhelming for the Heap, instead you can usebombR.set(...);
Alternative 2
Another and probably the simplest but less elegant way is to reset all variables to their original state.
public void reset(){
//aqui reinicias todas las variables a su estado original.
dinoY = (bg2altura);
... etc ect
}
and it would look like this …
public class Game extends ApplicationAdapter {
SpriteBatch batch;
//Fondos
Texture background;
Texture background2;
int bg2altura = 200;//altura del suelo
//Dinosaurio
Texture[] dinos;
float velocity = -30;
float gravity = 2;
float impulse = -30;
float dinoY;
int dinoAnim = 0;
int salto = 0;
Rectangle dinoR;
//Bomba y moneda
Texture bomb;
Rectangle bombR;
Texture coin;
Rectangle coinR;
float objectVelocity = 10;
int numberOfObjects = 4;
float[] objectX = new float[numberOfObjects];
float distanceBetweenObjects;
GameState gameState;
//private ShapeRenderer shrend;
//private boolean debug;
@Override
public void create() {
batch = new SpriteBatch();
SoundManager.loadSFX();
background = new Texture("bg.png");
background2 = new Texture("bg2.jpg");
dinos = new Texture[5];
dinos[0] = new Texture("frame-1.png");
dinos[1] = new Texture("frame-2.png");
dinos[2] = new Texture("frame-3.png");
dinos[3] = new Texture("frame-4.png");
dinos[4] = new Texture("dizzy-1.png");
bomb = new Texture("bomb.png");
coin = new Texture("coin.png");
reset();
/*
//Para testear
shrend = new ShapeRenderer();
debug = true;*/
}
public void reset(){
//TODO todas las variables que cambien se resetean al estado original.
gameState = GameState.TAP_TO_PLAY;
velocity = -30;
gravity = 2;
impulse = -30;
dinoY;
dinoAnim = 0;
salto = 0;
bg2altura = 200;
dinoY = (bg2altura);
dinoR = new Rectangle(dinos[0].getWidth() / 2F, dinoY, dinos[0].getWidth() / 2, dinos[0].getHeight() / 2);
bombR = new Rectangle();
coinR = new Rectangle();
distanceBetweenObjects = Gdx.graphics.getWidth() * 3 / 4;
for (int i = 0; i < numberOfObjects; i++) {
objectX[i] = Gdx.graphics.getWidth() + i * distanceBetweenObjects;
}
}
@Override
public void render() {
Gdx.gl.glClearColor(1, 0, 0, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
switch(gameState) {
case TAP_TO_PLAY:
if (Gdx.input.justTouched()) {
gameState = GameState.PLAY;
salto += 1;
SoundManager.play(SFX.JUMP);
}
break;
case PLAY:
SoundManager.play(SFX.GAME);
for (int i = 0; i < numberOfObjects; i++) {
if (objectX[i] < -bomb.getWidth()) {
objectX[i] = objectX[i] + numberOfObjects * distanceBetweenObjects;
}
objectX[i] = objectX[i] - objectVelocity;
}
if (Gdx.input.justTouched()) {
if (salto < 2) { //
salto += 1;
SoundManager.play(SFX.JUMP);
velocity = impulse;
}
} else {
velocity = velocity + gravity;
dinoR.y -= velocity;
}
//Para que no pase para abajo del suelo
if (dinoR.y < bg2altura) {
dinoR.y = bg2altura;
salto = 0;
}
//Animar el dinosaurio
if (dinoAnim == 3) {
dinoAnim = 0;
} else {
dinoAnim += 1;
}
break;
case GAME_OVER:
if (dinoR.overlaps(bombR)) {
gameState = gameState.TAP_TO_PLAY; //falta reiniciar juego
}
if(dinoR.overlaps(coinR)){
SoundManager.play(SFX.SCORE);
}
//Falta reiniciar juego
break;
}
batch.begin();
batch.draw(background, 0, 0, Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
batch.draw(background2, 0, 0, Gdx.graphics.getWidth(), bg2altura);
batch.draw(dinos[dinoAnim], dinoR.x, dinoR.y,dinos[0].getWidth()/2, dinos[0].getHeight()/2);
for (int i = 0; i < 4; i++) {
bombR = new Rectangle(objectX[i], bg2altura, bomb.getWidth()/2, bomb.getHeight()/2);
batch.draw(bomb, bombR.x, bombR.y, bomb.getWidth(), bomb.getHeight());
coinR = new Rectangle(objectX[i], bg2altura+coin.getHeight()*3,coin.getWidth()/2, coin.getHeight()/2);
batch.draw(coin, coinR.x, coinR.y, coinR.width, coinR.height);
if (dinoR.overlaps(bombR)) {
SoundManager.play(SFX.HIT);
dinos[dinoAnim] = dinos[4];
SoundManager.hitSFX.dispose();
SoundManager.gameSFX.stop();
reset();//TODO falta reiniciar juego, ya NO.
}else if(dinoR.overlaps(coinR)){
SoundManager.play(SFX.SCORE);
//Falta contador de monedas, arreglar o sonido
}
}
batch.end();
}
@Override
public void dispose() {
batch.dispose();
background.dispose();
background2.dispose();
// shrend.dispose();
}
}
Remove coins
A more organized idea to stop drawing or rendering the coins would be using OOP
You create a Coin
class, in the Game
class you make an Array<Coin>
that contains all the coins, with a loop you draw all the coins of that Array<Coin>
as soon as the player collides with a coin, you remove the coin from the coins.removeValue(coin,false);
array coins.removeValue(coin,false);
public class Coin extends Rectangle{ //Nota que extiende la clase Rectangle para que podamos utilizar .overlaps y las variable x,y,width,height para dibujar
public Texture texture;
public Coin(Texture texture,float x,float y,float width, float height){
this.texture = texture;
set(x,y,width,height);
}
public void render(SpriteBatch batch){
batch.draw(texture,x,y,width,height);
}
}
public class Game{
...
...
Array<Coin> coins = new Array<Coin>();
public Game(){
...
...
//a~ade las monedas que quieras
coins.add(new Coin(assets.coin,0,0,10,10));
coins.add(new Coin(assets.coin,100,0,10,10));
coins.add(new Coin(assets.coin,200,0,10,10));
}
public void render(){
...
batch.begin();
for(Coin coin: coins){
coin.render(batch); //dibujas la moneda
if(dinoR.overlaps(coin)){
//se ha capturado una moneda
monedasCapturadas++;
coins.removeValue(coin,false); //quitas la moneda del array
}
}
batch.end();
}
}