Better Games (Physics, Animation, Inheritance)

Define a Game Object with a Position and Collision Box

public class GameObj { protected int x = 0; protected int y = 0; protected int width = 2; // Collision box width protected int height = 2; // Collision box height public GameObj (int x, int y, int width, int height) { this.x = x; this.y = y; this.width = width; this.height = height; } public int getX() { return this.x; } public int getY() { return this.y; } public int getWidth() { return this.width; } public int getHeight() { return this.height; } }

- The collision box information will later be accessed to check if this object has collided with another one

Make a Sprite Class to use with animated game objects

public class Sprite { private BufferedImage spriteSheetImage; private int frameWidth, frameHeight, frameCount, frameIndex, x, y; public Sprite (BufferedImage spriteSheetImage, int frameWidth, int frameHeight, int frameCount) { try { // A sprite sheet image should contain evenly spaced areas containing different frames of the animation) this.spriteSheetImage = spriteSheetImage; this.frameWidth = frameWidth; // Width of each sprite frame/image in the sprite sheet this.frameHeight = frameHeight; // Height of each sprite frame/image in the sprite sheet this.frameCount = frameCount; // The number of frames/images in the sprite sheet this.frameIndex = 0; // To keep track of which frame of the animation is in focus } catch (Exception e) { e.printStackTrace(); } } public void update() { frameIndex += 1; if (frameIndex == frameCount) frameIndex = 0; } public BufferedImage getFrame() { return spriteSheetImage.getSubimage( frameWidth*frameIndex, 0, frameWidth, frameHeight); } }

- See the animations lesson for more information about this.

- Import java.awt.image.BufferedImage

Define a Movable Object that can be Updated and Detect Collisions

public class Movable extends GameObj { protected Sprite sprite; protected float xVelocity = 0; protected float yVelocity = 0; protected float xAccel = 0; protected float yAccel = 1; protected boolean grounded = false; public Movable(Sprite sprite, int x, int y, int width, int height) { super(x, y, width, height); this.sprite = sprite; } public void update (ArrayList<GameObj> solids ) { this.sprite.update(); this.xVelocity += xAccel; this.yVelocity += yAccel; int collisionCheck = 0; this.grounded = false; if (xVelocity != 0 || yVelocity != 0) { for (GameObj other : solids) { if (this != other) { collisionCheck = this.willCollide(other); // If collision above if (collisionCheck == 1) { this.yVelocity = 0; this.y = other.y + this.height/2 + other.height/2; } // If collision below else if (collisionCheck == 3) { this.yVelocity = 0; this.y = other.y - this.height/2 - other.height/2; this.grounded = true; } // If collision on right if (collisionCheck == 2) { this.xVelocity = 0; this.x = other.x - this.width/2 - other.width/2; } // If collision on left else if (collisionCheck == 4) { this.xVelocity = 0; this.x = other.x + this.width/2 + other.width/2; } // If collision is diagonal else if (collisionCheck == 5) { this.xVelocity = 0; this.yVelocity = 0; break; } } } this.x += xVelocity; this.y += yVelocity; } } public boolean checkCollision(GameObj other) { return Math.abs(this.x - other.x) < (this.width/2 + other.width/2) && Math.abs(this.y - other.y) < (this.height/2 + other.height/2); } public int willCollide(GameObj other) { if (Math.abs(this.x + this.xVelocity - other.x) < (this.width/2 + other.width/2) && Math.abs(this.y + this.yVelocity - other.y) < (this.height/2 + other.height/2)) { if (Math.abs(this.y - other.y) < (this.height/2 + other.height/2)) { if (this.xVelocity > 0) return 2; // Collision on right else return 4; // Collision on left } else if (Math.abs(this.x - other.x) < (this.width/2 + other.width/2)) { if (this.yVelocity < 0) return 1; // Collision above else return 3; // Collision below } else return 5; // Diagonal collision } return 0; // No collision } public void setXVel(int setTo) { this.xVelocity = setTo; } public void setYVel(int setTo) { this.yVelocity = setTo; } public Sprite getSprite() { return this.sprite; } }

- Import java.util.ArrayList

Define a Barrier Class to be Used as Solid Walls and Platforms

public class Barrier extends GameObj { public Barrier (int tlX, int tlY, int width, int height) { super(tlX + width/2, tlY + height/2, width, height); } }

- This allows us to make a collidable object with an easy way to specify the dimensions, it takes the top left corner coordiantes as parameters

Define a Nonplayer character to add to the game

public class PhysGameNPC extends Movable { public PhysGameNPC(Sprite sprite, int x, int y, int width, int height) { super(sprite, x, y, width, height); changeDirection(); } public void update (ArrayList<GameObj> solids) { super.update(solids); if (Math.random() < .1) changeDirection(); } public void changeDirection() { this.xVelocity = -2 + (int)(Math.random()*5); } }

- This can just move around, but you can program it do other things to make the game more interesting

Define a Hero to be Controlled by the User

public class PhysGameHero extends Movable{ public PhysGameHero(Sprite sprite, int x, int y, int width, int height) { super(sprite, x, y, width, height); } }

- The game code will add controls to it

Define the Game Code to Put All of the Parts Together

public class PhysicsGame { private PhysGameHero player1; private ArrayList<PhysGameNPC> npcs; private ArrayList<Barrier> barriers; private ArrayList<GameObj> solids; public PhysicsGame () { try { npcs = new ArrayList<PhysGameNPC>(); barriers = new ArrayList<Barrier>(); solids = new ArrayList<GameObj> (); // Create player 1 BufferedImage heroImage = ImageIO.read(new File("demoImage.png")); Sprite heroSprite = new Sprite(heroImage,64,64,1); player1 = new PhysGameHero(heroSprite,200,300,64,64); player1.setXVel(0); player1.setYVel(0); solids.add(player1); // Create NPCs BufferedImage spriteSheetImage = ImageIO.read(new File("demoSprite.png")); Sprite sprite = new Sprite(spriteSheetImage,64,64,4); PhysGameNPC npc1 = new PhysGameNPC(sprite,80,330,64,64); npcs.add(npc1); solids.add(npc1); // Create walls and platforms addBarrier(0,0,30,400); // Left Wall addBarrier(30,370,370,30); // Floor addBarrier(370,0,400,400); // Right Wall addBarrier(250,270,150,30); // Platform 1 addBarrier(0,190,150,30); // Platform 2 } catch (Exception e) { e.printStackTrace(); } } public void update () { // Update the NPCs (position and sprite frame) and check for collisions for (PhysGameNPC obj : npcs) { obj.update(solids); } // Update player 1 and check for collisions player1.update(solids); } public void display(Graphics2D g) { // Display player 1 g.drawImage(player1.getSprite().getFrame(), player1.getX() - player1.getWidth()/2, player1.getY() - player1.getHeight()/2, null); // Display NPCs for (PhysGameNPC obj : npcs) { g.drawImage(obj.getSprite().getFrame(), obj.getX() - obj.getWidth()/2, obj.getY() - obj.getHeight()/2, null); } // Display barriers g.setPaint(Color.black); for (Barrier obj : barriers) { g.fillRect( obj.getX() - obj.getWidth()/2, obj.getY() - obj.getHeight()/2, obj.getWidth(),obj.getHeight()); } } public void onLeftKeyPress() { player1.setXVel(-6); } public void onRightKeyPress() { player1.setXVel(6); } public void onSpaceKeyPress() { if (player1.grounded) { player1.setYVel(-16); } } public void addBarrier(int topLeftX, int topLeftY, int width, int height) { Barrier toAdd = new Barrier(topLeftX, topLeftY, width, height); barriers.add(toAdd); solids.add(toAdd); } public PhysGameHero getPlayer1() {return this.player1;} public ArrayList<PhysGameNPC> getNPCs() {return this.npcs;} public ArrayList<Barrier> getBarriers() {return this.barriers;} public ArrayList<GameObj> getSolids() {return this.solids;} }

- Import these: java.awt.Color, java.awt.Graphics2D, java.awt.image.BufferedImage, java.io.File, java.util.ArrayList, javax.imageio.ImageIO

Define a Panel to Connect the Controls and Display to the Game

public class PhysGamePanel extends JPanel implements ActionListener { PhysicsGame game; public PhysGamePanel(PhysicsGame game) { this.game = game; try { this.setBackground(Color.blue); Timer timer = new Timer (50, this); timer.start(); } catch (Exception e) { e.printStackTrace(); } PressRightAction pressRightAction = new PressRightAction(); this.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT,0, false), "pressRight"); this.getActionMap().put("pressRight", pressRightAction ); PressLeftAction pressLeftAction = new PressLeftAction(); this.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT,0, false), "pressLeft"); this.getActionMap().put("pressLeft", pressLeftAction ); PressSpaceAction pressSpaceAction = new PressSpaceAction(); this.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE,0, false), "pressSpace"); this.getActionMap().put("pressSpace", pressSpaceAction ); } @Override public void actionPerformed (ActionEvent event) { game.update(); repaint(); } public void paint (Graphics gra) { Graphics2D g = (Graphics2D) gra; super.paint(g); game.display(g); } public class PressRightAction extends AbstractAction { @Override public void actionPerformed (ActionEvent ae) { game.onRightKeyPress(); } } public class PressLeftAction extends AbstractAction { @Override public void actionPerformed (ActionEvent ae) { game.onLeftKeyPress(); } } public class PressSpaceAction extends AbstractAction { @Override public void actionPerformed (ActionEvent ae) { game.onSpaceKeyPress(); } } }

- Import these from java.awt: Color, Graphics, Graphics2D, event.ActionEvent, event.ActionListener, event.KeyEvent

- Import these from javax.swing: AbstractAction, JPanel, KeyStroke, Timer

Completed