Jump to content

  • Log In with Google      Sign In   
  • Create Account






Pong v2

Posted by timgranstrom, 17 April 2013 · 443 views

pong v2 update changes java slick2d game development experience learning source code
PONG v2 - Getting there
-------------------------------------------------------------------------------------------------------------------------------------------------------------------
CHANGE-NOTES
-------------------------------------------------------------------------------------------------------------------------------------------------------------------
So there are alot of changes between this version and the first version of the game, so bare with me on this!

The game has been completely revamped, it is now object orientated for starters!
Multiple classes has been made where I deemed it appropriate. Such as, I've made an abstract entity and obstacle class.
And since I've made more classes to hold each specific object etc, the variables from V1 has been added to be held in it's appropriate
class instead of being available from the main game class.

I've also completely scrapped the old and (what I think) highly inefficient collision detection.. And replaced it with a simple and more
appropriate collision detection system for this type of simple game.

The keylistener has also been changed to be used in a faster and cleaner way.


Comments are available pretty much everywhere in the source code.

You can follow my topic here where you also can read on the obstacles I faced and how I resolved them.


-------------------------------------------------------------------------------------------------------------------------------------------------------------------
STATUS
-------------------------------------------------------------------------------------------------------------------------------------------------------------------
It currently containts the following:
  • Object orientation.
  • keep variables(data) and methods(behavior) of an object isolated within its own class.
  • Better input control/implementation
  • better and more general collission detection
  • Movable Paddle
  • Moving Ball (randomly generated at start where it will move first)
  • score keeping (Now counts as a score when you hit the ball with your paddle)
  • accelerating velocity of ball and paddle based on number of times ball hits something (Paddle or walls) (removed for the time being.)
What it's missing as far as I can imagine right now (Listed in order I believe it should be added):
  • Object orientation (Everything is basicly just in one class atm)
  • keep variables(data) and methods(behavior) of an object isolated within its own class. (added thanks to warnexus)
  • Better input control/implementation
  • better and more general collission detection
  • Sound (Added recently)
  • enemy ai
-------------------------------------------------------------------------------------------------------------------------------------------------------------------
Pictures:
-------------------------------------------------------------------------------------------------------------------------------------------------------------------
Pong V2 pic1

Pong V2 pic3

Pong V2 pic2





-------------------------------------------------------------------------------------------------------------------------------------------------------------------
Code:
-------------------------------------------------------------------------------------------------------------------------------------------------------------------
GameSetup (basicly the window that the game is in)
package engine;

import org.newdawn.slick.AppGameContainer;
import org.newdawn.slick.GameContainer;
import org.newdawn.slick.SlickException;
import org.newdawn.slick.state.StateBasedGame;

public class GameSetup extends StateBasedGame {
	private static int width = 720;
	private static int height = (width / 12 * 9);
	public GameSetup(String name) {
		 super("Main Game");
		
	}
	
	public static void main(String args[]) {
        AppGameContainer app;
        try {
            app = new AppGameContainer(new GameSetup("Pong"));
            app.setDisplayMode(width, height, false);
            app.start();
        } catch (SlickException ex) {
            ex.printStackTrace();
        }
    }

	@Override
	public void initStatesList(GameContainer gc) throws SlickException {
		this.addState(new PongGame());
		
	}

}


PongGame (The actual game)
package engine;


import org.newdawn.slick.Color;
import org.newdawn.slick.GameContainer;
import org.newdawn.slick.Graphics;
import org.newdawn.slick.Input;
import org.newdawn.slick.KeyListener;
import org.newdawn.slick.SlickException;
import org.newdawn.slick.state.BasicGameState;
import org.newdawn.slick.state.StateBasedGame;

import engine.entities.Ball;
import engine.entities.Paddle;
import engine.walls.Wall;



public class PongGame extends BasicGameState implements KeyListener {
	private boolean gameStarted = false;
	private String start = "                Pong V2\nA self-educational game by Tim Granström\n\n\n       Press SPACE to start game.";
	private int width;
	private int height;
	private float ballX = 600.0f;
	private float ballY = 150.0f;
//---------------------------------------------------------------------
//          NEW STUFF
//---------------------------------------------------------------------
	private Paddle paddle;
	private Ball ball;
	private Wall wallUp;
	private Wall wallDown;
	private Wall wallRight;
	private CollisionDetection collision;
	boolean key_space = false;
	

	//Creates the objects for the game.
	public void init(GameContainer gc, StateBasedGame sbg)
			throws SlickException {
		gc.setTargetFrameRate(60);
		width = gc.getWidth();
		height = gc.getHeight();
		paddle = new Paddle(5,150,7,55,gc);
		ball = new Ball(ballX,ballY,15,15);
		wallUp = new Wall(0,0, width, 8);
		wallDown = new Wall(0, height - 8, width, 8);
		wallRight = new Wall(width - 8, 0, 8, height);
		collision = new CollisionDetection();

		
	}

	//Renders all the objects in the game
	public void render(GameContainer gc, StateBasedGame sbg, Graphics g)
			throws SlickException {
		
		g.setColor(Color.white);
		g.drawString(start, (width/2)-175, (height/2)-60);

		
//---------------------------------------------------------------------
//      NEW STUFF
//---------------------------------------------------------------------
		wallUp.render(g);
		wallDown.render(g);
		wallRight.render(g);
		paddle.render(g);
		ball.render(g);
	}
	
	
	//Check what key is pressed
	public void keyPressed(int key, char c) {
		
        if (key == Input.KEY_SPACE) {
            key_space = true;
        }
    }
	
	public void keyReleased(int key, char c){
		if (key == Input.KEY_SPACE) {
            key_space = false;
        }
	}

	

	public void update(GameContainer gc, StateBasedGame sbg, int delta)
			throws SlickException {
		
		if(!gameStarted){
		if (key_space == true) {
			ball.resetBall();
			start = "";
			gameStarted = true;
			
		}
		}
		if(gameStarted){
			
		//checks if the paddle collides with the upper and lower wall.
		paddle.checkCollisionDown(wallDown);
		paddle.checkCollisionUp(wallUp);
		paddle.update(delta);
		
		// check if ball collide with paddle
		if (collision.collidesWith(ball, paddle)) {
			ball.invGoingLeft();
		}
		
		// check if ball collide on right
		if (collision.collidesWith(ball, wallRight)) {
			ball.invGoingLeft();
		}
		// check if ball collide down
		if(collision.collidesWith(ball, wallDown)){
			ball.invGoingDown();
		}
		
		// check if ball collide up
		if(collision.collidesWith(ball, wallUp)){
			ball.invGoingDown();
		}
		
		ball.update(delta); //updates the ball coordinates etc. NOTE: important to update AFTER collision has been checked.
		
		// check if ball has passed the paddle. If true, resets the game.
		if(ball.getX()<0){
			paddle.getPlayer().newGame();
			ball.resetBall();
			gameStarted = false;
		}
		}

	}

	@Override
	public int getID() {
		return 0;
	}

}



Entity (Abstract entity class that other "entity" classes can implement)
package engine.entities;

import org.newdawn.slick.Graphics;
import org.newdawn.slick.geom.Rectangle;

public abstract class Entity {

	//x & y = x-coord & y-coord in game, width & height = width & height of the entity.
	protected float x,y;
	protected float velocity = 0.3f;
	protected int width,height;
	private boolean removed = false;
	protected Rectangle rect; //Rectangle for possible collision detection
	
	public Entity(float x, float y){
		this.x = x;
		this.y = y;
	}
	
	public void update(int delta){
		
	}
	
	public void render(Graphics g){
		
	}

	public Rectangle getRect(){
		return this.rect;
	}
	
	//Get width of entity
	public int getWidth(){
		return this.width;
	}
	//Get height of entity
	public int getHeight(){
		return this.height;
	}

	//get x-coord
	public float getX(){
		return this.x;
	}
	//get y-coord
	public float getY(){
		return this.y;
	}

	//Get velocity of entity
	public float getVelocity(){
		return this.velocity;
	}
	
	//Set remove-flag for an entity
	public void remove(){
		this.removed = true;
	}
	
	//Check the remove-flag for an entity
	public boolean checkRemoved(){
		return this.removed;
	}
}



Paddle (The Paddle class which implements the abstract Entity class and also listenes for key-inputs)
package engine.entities;

import org.newdawn.slick.Color;
import org.newdawn.slick.GameContainer;
import org.newdawn.slick.Graphics;
import org.newdawn.slick.Input;
import org.newdawn.slick.KeyListener;
import org.newdawn.slick.geom.Rectangle;

import engine.CollisionDetection;
import engine.walls.Wall;

public class Paddle extends Entity implements KeyListener {

	private float yd;
	private Color paddleColor = Color.white;
	private Player player;
	private Input input;
	private int dirUp = -1; //Paddle direction
	private int dirDown = 1; //Paddle direction
	private boolean key_up = false;
	private boolean key_down = false;
	private CollisionDetection collides;
	private boolean collideUp = false;
	private boolean collideDown = false;
	
	//Constructor.
	public Paddle(float x, float y, int width, int height, GameContainer gc){
		super(x,y);
		this.width = width;
		this.height = height;
		this.player = new Player(gc);
		input = gc.getInput();
		this.input.addKeyListener(this); //Add a keylistener to the object.
		this.rect = new Rectangle(this.x, this.y, this.width, this.height); //creates a rectangle for collision detection
		collides = new CollisionDetection();
	}
	
	public Player getPlayer(){
		return player;
	}
	
	public void checkCollisionUp(Wall w){
		if(collides.collidesWith(this, w)){
			this.collideUp = true;
		}else{
			this.collideUp = false;
		}
	}
	
	public void checkCollisionDown(Wall w){
		if(collides.collidesWith(this, w)){
			this.collideDown = true;
		}else{
			this.collideDown = false;
		}
	}
	

		//Updates the actual position of the player
		private void movement(){
			this.y += this.yd;
			this.rect.setLocation(this.x, this.y); //updates the paddle rectangle for correct collision detection.
		}

	
	//set new height of paddle
	protected void setHeight(int height){
		this.height = height;
	}
	
	//set new width of paddle
	protected void setWidth(int width){
		this.width = width;
	}
	
	
	//renders the paddle graphics.
	public void render(Graphics g){
		this.movement();
		player.render(g);
		g.setColor(this.paddleColor);
		g.fillRect(this.x, this.y, this.width, this.height);
	}

		
	public void update(int delta) {
		
		// move paddle up
		if (key_up) {
			if(this.collideUp == false){
				this.y += dirUp * velocity * delta;
			}else{
				
			}
			
		}
		// move paddle down
		if (key_down) {
			if(this.collideDown == false){
				this.y += dirDown * velocity * delta;
			}else{
				
			}
			
		}

	}

	//check Keyboard input if key pressed
	@Override
	public void keyPressed(int key, char c) {
		
		
		if (key == Input.KEY_W || key == Input.KEY_UP) {
			
           this.key_up = true;
           
        } else if (key == Input.KEY_S || key == Input.KEY_DOWN) {
        	
        	this.key_down = true;
        }

    }

	//check Keyboard input if key released
	@Override
	public void keyReleased(int key, char c) {
		
		if (key == Input.KEY_W || key == Input.KEY_UP) {
	           this.key_up = false;
	        } else if (key == Input.KEY_S || key == Input.KEY_DOWN) {
	        	this.key_down = false;
	        }
	
}

	@Override
	public void inputEnded() {
		
	}


	@Override
	public void inputStarted() {
	}


	@Override
	public boolean isAcceptingInput() {
		return true;
	}


	@Override
	public void setInput(Input input) {
	}

}



Player (Player class that keeps track of scores etc)
package engine.entities;

import org.newdawn.slick.GameContainer;
import org.newdawn.slick.Graphics;

public class Player{

	private int score = 0;
	private int bestScore = 0;
	private int width,height;
	private String scoreBoard = "Current Score: ";
	private String scoreBestBoard = "Best Score: ";
	
	
	public Player(GameContainer gc) {
		height = 10;
		width = gc.getWidth()/8;
		
		
	}
	
	//render the score board.
	public void render(Graphics g){
		g.drawString(scoreBoard+score, width, height);
		g.drawString(scoreBestBoard+bestScore, width*(3), height);
	}
	
	public void addScore(){
		score++;
	}
	
	public void clearScore(){
		score = 0;
	}
	
	public void newGame(){
		if (score>bestScore){
			bestScore = score;
			score = 0;
		}
		score = 0;
	}

}



Ball (The Ball class which implements the abstract Entity class)
package engine.entities;

import java.util.Random;

import org.newdawn.slick.Color;
import org.newdawn.slick.Graphics;
import org.newdawn.slick.geom.Rectangle;

public class Ball extends Entity {
	private Random randomBool; // random object
	public boolean goLeft, goDown = true; // direction ball is traveling in.
	private int dirUp = -1; // ball direction go up
	private int dirDown = 1; // ball direction go down
	private int dirLeft = -1; // Paddle direction
	private int dirRight = 1; // Paddle direction
	private float startX; // original start position X
	private float startY; // original start position Y

	private Color ballColor = Color.blue;

	private float xd, yd;

	public Ball(float x, float y, int width, int height) {
		super(x, y);
		this.startX = x;
		this.startY = y;
		this.width = width;
		this.height = height;
		this.rect = new Rectangle(this.x, this.y, this.width, this.height);
		randomBool = new Random();
		this.resetBall();

	}

	// Updates the actual position of the ball. (Moves the ball in other words)
	private void movement() {

		this.x += this.xd;
		this.y += this.yd;
		this.rect.setLocation(this.x, this.y); //updates the ball rectangle for correct collision detection.
	}

	@Override
	public void update(int delta) {

		if (this.goLeft == true) {
			this.xd = dirLeft * this.velocity * delta;
		} else if (this.goLeft == false) {
			this.xd = dirRight * this.velocity * delta;
		}
		if (this.goDown == true) {
			this.yd = dirDown * this.velocity * delta;
		} else if (this.goDown == false) {
			this.yd = dirUp * this.velocity * delta;
		}

	}

	public void resetBall() {
		this.xd = 0;
		this.yd = 0;
		this.x = startX;
		this.y = startY;
		this.goLeft = randomBool.nextBoolean();
		this.goDown = randomBool.nextBoolean();
	}

	// sets the y-coord to something.
	public void setY(float y) {
		this.y = y;
	}

	// sets the x-coord to something.
	public void setX(float x) {
		this.x = x;
	}

	// renders the actual ball entity graphics.
	public void render(Graphics g) {
		this.movement();
		g.setColor(ballColor);
		g.fillOval(this.x, this.y, this.width, this.height);
	}

	// set new height of ball
	protected void setHeight(int height) {
		this.height = height;
	}

	// set new width of ball
	protected void setWidth(int width) {
		this.width = width;
	}

	// Inverses the horizontal direction the ball is going. For example: if
	// goLeft = true, will become, goLeft = false.
	public void invGoingLeft() {
		this.goLeft = !this.goLeft;
	}

	// Inverses the vertical direction the ball is going. For example: if goDown
	// = true, will become, goDown = false.
	public void invGoingDown() {
		this.goDown = !this.goDown;
	}

	// check if ball is moving to the left. If false, it means the ball is
	// moving to the right.
	public boolean isGoingLeft() {
		return this.goLeft;
	}

	// check if ball is moving down. If false, it means the ball is moving up.
	public boolean isGoingDown() {
		return this.goDown;
	}

}



Obstacle (Abstract obstacle class that other "obstacle" classes can implement)
package engine.walls;

import org.newdawn.slick.Graphics;
import org.newdawn.slick.geom.Rectangle;

public abstract class Obstacle {
	protected float x,y;
	protected int width,height;
	protected boolean removed = false;
	protected Rectangle rect;
	
	public Obstacle(float x,float y){
		this.x = x;
		this.y = y;
	}
	
	public void render(Graphics g){
		
	}
	
	public Rectangle getRect(){
		return rect;
	}
	
		//Get width of obstacle
		public int getWidth(){
			return this.width;
		}
		//Get height of obstacle
		public int getHeight(){
			return this.height;
		}

		//get x-coords
		public float getX(){
			return this.x;
		}
		//get y-coords
		public float getY(){
			return this.y;
		}
		
		//Set remove-flag for an obstacle
		public void remove(){
			this.removed = true;
		}
		
		//Check the remove-flag for an obstacle
		public boolean checkRemoved(){
			return this.removed;
		}
}



Wall (The Wall class which implements the abstract Obstacle class)
package engine.walls;

import org.newdawn.slick.Color;
import org.newdawn.slick.Graphics;
import org.newdawn.slick.geom.Rectangle;
public class Wall extends Obstacle{

	private Color wallColor = Color.white;
	
	
	public Wall(float x, float y, int width, int height) {
		super(x, y);
		this.height = height;
		this.width = width;
		this.rect = new Rectangle(this.x,this.y,this.width,this.height); //creates a rectangle for collision detection
	}


	//Change height of wall.
	public void setHeight(int height){
		this.height = height;
	}
	//Change width of wall.
	public void setWidth(int width){
		this.width = width;
	}
	
	public void render(Graphics g){
		g.setColor(wallColor);
		g.fillRect(this.x, this.y, this.width, this.height);
	}
}



CollisionDetection (System that checks if entities collide with obstacles etc)
package engine;

import engine.entities.Ball;
import engine.entities.Paddle;
import engine.walls.Wall;

public class CollisionDetection {
	
	
	//Check if ball and player entity intersect.
	public boolean collidesWith(Ball b, Paddle p){
		boolean collided = b.getRect().intersects(p.getRect());
		if(collided){
			p.getPlayer().addScore();
			return collided;
		}else{
			return collided;
		}
	}
	
	//Check if ball and player entity intersect.
		public boolean collidesWith(Ball b, Wall w){
			boolean ja = b.getRect().intersects(w.getRect());
			return ja;
		}
		
		//Check if ball and player entity intersect.
		public boolean collidesWith(Paddle p, Wall w){
			return p.getRect().intersects(w.getRect());
		}
	
}




-------------------------------------------------------------------------------------------------------------------------------------------------------------------
Downloads:
-------------------------------------------------------------------------------------------------------------------------------------------------------------------
Eclipsefile is attached.


-------------------------------------------------------------------------------------------------------------------------------------------------------------------
Comments:
-------------------------------------------------------------------------------------------------------------------------------------------------------------------
Feel free to comment and ask questions, make suggestions or whatever you want!

Thank you :)

Attached Files






Advice: Take advantage of polymorphism.
Keep up the good work, not quite a charizard yet!

thanks for the advice! Will do!

 

How and where do you think I should use polymorphism? I was considering for the Entities(duh), but then I thought that I had to typecast alot and wasn't sure how to implement it in a smart way..

 

EDIT:

I found a good way to make use of polymorphism! In the collision Detection, I now do a check for "Entity" instead of, for example "Ball" or "Paddle".

 

This saves alot of code right away :)

Thank you again for the advice!

Thats how I was going to suggest you should use your polymorphism, sorry for taking so long to reply! Wish this thing had notifications or something.
Another point to remember is to perhaps have an array/vector of entities and call thier all respestive updates and draw calls so
 

instead of:

  wallUp.render(g); 
wallDown.render(g); 
wallRight.render(g); 
paddle.render(g); 
ball.render(g); 

 

 

Try something like
 

 

Entity m_entities[10];
for(int i = 0; i < 10; ++i)
{  
    m_entites.draw(g);
 }
 

 

Obviously use a vector if you know what that is instead of a array. And put it into its own type of class called for example "EntityManager".

Sorry if this code doesnt compile havnt writen java in a few years :)

yes I was thinking of doing something similar like that!

 

Though I'm probably gonna use an ArrayList instead since it is faster and newer then vectors smile.png

 

thanks for the feedback!

 

------------EDIT---------

 

I was thinking of doing this but then realised that this would result in many extra checks in the updates etc because of the for-loops etc!

 

Am I wrong about this? I was mainly thinking this since the update method will have to access the objects aswell.

I think you need to rearrange your update loop. Maybe rethink it all together.
There are many calls in there which are not needed. For example you should check for collisions somewhere else, and then pass it back to the objects that are effected. Its a cleaner solution

April 2014 »

S M T W T F S
  12345
6789101112
1314151617 18 19
20212223242526
27282930   

Recent Entries

Recent Comments

PARTNERS