Monday, April 14, 2014

Making Breakout for JAVA

This is the game we will be making.

Breakout is a simple paddle-and-ball game developed in the 70s by Steve Wozniak, and designed by Steve Jobs. It features a simple paddle, deflecting a ball into an array of bricks. It is an incredibly simple game, and thus is usually the first or second game, alongside Pong, that is developed when getting accustomed to a new library or language. Since the game uses the same mechanics as Pong, we can reuse much of our earlier Pong code in order to make this game.

An Improved Entity Manager Class

In the last game, Pong, we used a simple Entity Manager that could manage all of our Entities and handle the drawing and logic of each and every one. In this game, the Entity class is not much different save for a few new overloaded constructors and variables that allow us to add color to our blocks individually, while also giving them points.

package _breakout;

import java.awt.Rectangle;
import java.util.ArrayList;
import java.util.List;

public abstract class Entity {

protected int x;
protected int y;
protected double dx;
protected double dy;
protected int height;
protected int width;
protected float colorRed, colorGreen, colorBlue;
protected int points;
protected static List<Entity> entities = new ArrayList<Entity>();
protected static List<Entity> garbage = new ArrayList<Entity>();
protected Rectangle bounds = new Rectangle();
public Entity(int x, int y, int height, int width) {
entities.add(this);
this.x = x;
this.y = y;
this.height = height;
this.width = width;
this.dx = 0;
this.dy = 0;
this.colorRed = 1.0f;
this.colorGreen = 1.0f;
this.colorBlue = 1.0f;
}
public Entity(int x, int y, int height, int width, float colorRed, float colorGreen, float colorBlue) {
entities.add(this);
this.x = x;
this.y = y;
this.height = height;
this.width = width;
this.dx = 0;
this.dy = 0;
this.colorRed = colorRed;
this.colorGreen = colorGreen;
this.colorBlue = colorBlue;
}
public Entity(int x, int y, int height, int width, float colorRed, float colorGreen, float colorBlue, int points) {
entities.add(this);
this.x = x;
this.y = y;
this.height = height;
this.width = width;
this.dx = 0;
this.dy = 0;
this.colorRed = colorRed;
this.colorGreen = colorGreen;
this.colorBlue = colorBlue;
this.points = points;
}

public int getX() {
return x;
}

public void setX(int x) {
this.x = x;
}

public int getY() {
return y;
}

public void setY(int y) {
this.y = y;
}

public double getDx() {
return dx;
}

public void setDx(double dx) {
this.dx = dx;
}

public double getDy() {
return dy;
}

public void setDy(double dy) {
this.dy = dy;
}

public int getHeight() {
return height;
}

public void setHeight(int height) {
this.height = height;
}

public int getWidth() {
return width;
}

public void setWidth(int width) {
this.width = width;
}
public void update(int delta){
}
public void draw(){
}
public void logic(){
}
public void destroy(){
garbage.add(this);
}

public boolean intersect(Entity e) {
return this.bounds.intersects(e.bounds);
}

}

As you can see, we also updated the destroy method. I apologize for the inclusion of an inferior method in the Pong code. Since we didn't use it at all, I didn't notice that it was calling to destroy a list element during an iteration (our update method iterates through the list of entities, so we cannot simply destroy them when we please). In this method, when we call for an entity to be destroyed, it is added to a garbage list of Entities. At the end of every update, the Entity Index checks itself against the garbage list and removes any entities that are queued for removal, then the garbage list is cleared. 

 New Ball Logic

Since the ball is the only entity on the screen that is constantly interacting with different elements on the screen, I decided to add most of the game logic into the ball's logic section. Now, for the initialization, I created two Entity lists: One for the collection of bricks, and one for the brick's own seperate garbage collection:

bricks = new ArrayList<Entity>();
bricksGarbage = new ArrayList<Entity>();

The reason I did this is so the ball would have an easier time checking for collisions on the bricks, since I only have to iterate over the bricks-ball intersections and check if it intersects with any of the stored coordinates in that list. This also means I have to have a seperate garbage list to destroy all of the collision coordinates or the ball would bounce off of random thin air.

//collision detection
for(Entity f : bricks){
if(ball.intersect(f)){
point += f.points;
f.destroy();
bricksGarbage.add(f);
ball.setDy(-dy);
ball.setDx(rand.nextInt(50)>25?(Math.abs(dx) + 0.03):-(Math.abs(dx) + 0.03));
}
        }

An interesting thing here is the ball's new setDx() method. Much like in the original Breakout!, the ball's speed will increase as it collides with more and more bricks, making the game harder as you reach the end. Instead of opting for a completely static Dynamic X like I did with Pong, here we use the Math.abs() function so we can get the current dx and manipulate that instead. We could go back to the Pong-styled setDy(), but this makes it feel like the player has more control over the ball with their paddle.

//bounds control
if(ball.getX() >= 640 || ball.getX() < 0){
ball.setDx(-dx);
} else if(ball.getY() <= 0){
ball.setDy(-dy);
} else if (ball.getY() >= 480){
gameStatus = gameState.WAITING;
playerLives -= 1;
System.out.println("You have " + playerLives + " lives left!");
System.out.println("You also have " + point + " points!");
paddle.setX(640>>1);
paddle.setY(460);
ball.setX((640>>1) + 25);
ball.setY(410);
ball.setDx(0);
}

As you can see here, I have also implemented Game States, which I will be going over in the main method. Here, our ball checks to see if it is past the point of no return. We then deduct a life from the player, print out their stats, then reset the ball and paddle's position while switching the current game state. It is a pretty simple addition.


Brick Creation Method

Because we are doing a brick-breaker game, we're gonna need some bricks. Fortunately, we can do this any number of ways, ranging to overcomplicated to incredibly rudimentary. I decided to go the over-complicated route:

private void createBlocks(){
for(int i = 1; i <= 6; i++){
for(int j = 1; j <= 4; j++){
if(j - 1 == 0){
bricks.add(new Box((100 * i) - 75, 5 + (35 * j), 30, 90, 1.0f, 0.0f, 0.0f, 100));
}
else if (j - 1 == 1){
bricks.add(new Box((100 * i) - 75, 5 + (35 * j), 30, 90, 1.0f, 1.0f, 0.0f, 750));
}
else if (j - 1 == 2){
bricks.add(new Box((100 * i) - 75, 5 + (35 * j), 30, 90, 0.0f, 1.0f, 1.0f, 25));
}
else bricks.add(new Box((100 * i) - 75, 5 + (35 * j), 30, 90, 0.5f, 1.0f, 0.5f, 5));
}
}
}

The first thing you may notice is the ridiculous formula I used to position the bricks. Essentially it can simplified to: (A * i) + X, (B * j) + Y, where X and Y are offsets. The reason I did this is strictly for aesthetic reasons only. I wasn't happy with the way the bricks looked when I used a multi-dimensional array, and I wanted to be able to offset the position of the bricks while still having control over the amount of pixels between each brick and the walls themselves. I literally over-complicated the heck out of this, but sometimes hackish solutions work out in the end.

This method simply loops through 6 columns and 4 rows and creates a new Brick depending on the row that it is in. Higher rows contain more points than lower rows, and each row has their own color of bricks. Like I said before, you can do the same thing using a multidimensional array, so not too much to see here.

Our New Main Game Loop

Since we added in gamestates, we need a way to manage those gamestates. To do that, we use our trusty game loop to manage the different states and key combinations that take us into each one. A game state is simply which point in our game hierarchy our game is currently in. For instance, we could have one level for the menu, the next one would be the overworld, and the third could be the credits screen. Each of these states have different code execution formats and thus we set up and enumerator that allows us to check if we are in any of these states.

//main loop
while(!Display.isCloseRequested()){
glClear(GL_COLOR_BUFFER_BIT);
//Input checking
if(Keyboard.isKeyDown(Keyboard.KEY_ESCAPE)){
Display.destroy();
System.exit(0);
}
if(Keyboard.isKeyDown(Keyboard.KEY_RETURN) 
&& gameStatus == gameState.WAITING){
gameStatus = gameState.PLAYING;
}
//Draw
drawAll(Entity.entities);
//Game State checking
if(gameStatus == gameState.PLAYING){
updateAll(getDelta(), Entity.entities);
}
if(gameStatus == gameState.WAITING 
|| gameStatus == gameState.END){
getDelta();
}
if(bricks.isEmpty() 
&& gameStatus == gameState.PLAYING){
gameStatus = gameState.WIN;
}
if(playerLives == 0 
&& gameStatus == gameState.WAITING){
playerLives--;
gameStatus = gameState.LOSE;
}
if(gameStatus == gameState.LOSE){
System.out.println("You Lose!");
gameStatus = gameState.END;
}
if(gameStatus == gameState.WIN){
System.out.println("You Win! Points: " + point);
gameStatus = gameState.END;
}
Display.update();
Display.sync(60);
}
Entity.entities.clear();
bricks.clear();
Display.destroy();
//destroy display

This isn't too interesting -- we are simply managing the state of our game here. One thing I do want to point out, though, is the fact that we only update our entities when we are playing the game, and if we aren't updating, we are still getting our delta. The reason we do this is because our delta function resets when we call getDelta(), but if we do not call it, then the delta will increasingly rise over time, and the next time we resume gameplay our entities will be all over the place because they technically were updating in the background. This would defeat the whole purpose of using delta, so we make sure to call it in every frame that we do not update.

That is Breakout! ladies and gentlemen. Since we completed Pong, many of the things we learned in that game were brought into this one. Likewise, we can bring many of the Breakout! elements into games such as Space Invaders, which I will covering next time, and even R-Type or Galaga. The Shoot-em'-up genre is heavily based on these mechanics so we could potentially make any shoot-em'-up we wanted to. However, I first want to go over Sprite animation, Audio managing, and improving our game states. So, until next time!

Remember, if you have any questions about the code, please do not be afraid to leave a comment requesting assistance. And as always, the source code can be downloaded from the link below:

No comments:

Post a Comment