chapter 12: side scroller - general characteristics the...

58
Chapter 12: Side Scroller - General Characteristics The background moves past the screen, not the character The background and foreground images are usually wider than the screen width Background may be layered Background consists of multiple images instead of just one Give the illusion of depth Each layer scrolls at a different rate, ones closer to the front scrolling faster Usually implemented using tile maps Tiles can be simple bricks Can be animated ... Game perspective is usually a side view, but can be top view, isometric, ... 1

Upload: others

Post on 30-Sep-2020

5 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: Chapter 12: Side Scroller - General Characteristics The ...djmoon/gaming/gaming-notes/side-scroller.pdf{ Several editors exit for creating tile maps (see resource page) 1. Mappy Fairly

Chapter 12: Side Scroller - General Characteristics

• The background moves past the screen, not the character

• The background and foreground images are usually wider than the screen width

• Background may be layered

– Background consists of multiple images instead of just one

– Give the illusion of depth

– Each layer scrolls at a different rate, ones closer to the front scrolling faster

• Usually implemented using tile maps

– Tiles can be simple bricks

– Can be animated

– ...

• Game perspective is usually a side view, but can be top view, isometric, ...

1

Page 2: Chapter 12: Side Scroller - General Characteristics The ...djmoon/gaming/gaming-notes/side-scroller.pdf{ Several editors exit for creating tile maps (see resource page) 1. Mappy Fairly

Chapter 12: Side Scroller - Tile Maps

• When implement a scroller, the foreground and background images are gener-ally larger than the display area (usually MUCH larger)

• There are usually multiple levels, each of which has its own set of images

• There are several approaches than can be used for these images

1. Graphics API (OpenGL, java2D) to generate the graphics as needed

– This creates a lot of runtime overhead

– Not flexible - changes require modification to source code

2. Predefined images

– For large images, multiple layers, and many levels, this has high memoryrequirements

– Predefined images make collision detection very problematic

3. Tile map

– Map consists of a grid of tiles

– Each tile is an image

– Consequently, we can generate a large image from a small set imagesstored in memory

– This facilitates collision detection

∗ Can determine if an object will occupy a location in the grid that isalready occupied by a tile

• Components:

1. Configuration file

– This is not required

∗ Map could be hard-coded in program

∗ The configuration file provides flexibility by allowing you to edit thefile without touching the source code

– The file contains

(a) Representation of the map

(b) Usually a link to the tile images

2

Page 3: Chapter 12: Side Scroller - General Characteristics The ...djmoon/gaming/gaming-notes/side-scroller.pdf{ Several editors exit for creating tile maps (see resource page) 1. Mappy Fairly

Chapter 12: Side Scroller - Tile Maps (2)

– The map representation

∗ Each line represents one row of tiles

∗ Tiles are represented by single characters or character combinations

∗ If using a single character, the number of tiles is limited by the numberof characters (62 if consider only alphanumeric characters)

∗ If using multicharacter representation, use a comma-separated list

44 ,4,4, ,

4 ,4, , ,

, , , ,

1 or , , , ,1

3 , , , ,3

5 2 ,5, , ,2

11111 1,1,1,1,1

2. Internal representation of the tile map

(a) Single image

– When reading the configuration file, the tiles can be written into animage object to create a single image

– This makes scrolling easy, and facilitates modification

– This does not reduce storage or facilitate collision detection

(b) Array of tile images

– Scrolling more difficult, as must determine how much of tiles to dis-play for those that straddle the edges of the display area

– Facilitates collision detection

– Still requires large amount of storage

(c) Indexed tile map

– Tile images are stored in a structure that facilitates retrieval (array,hash table, etc.)

– The map contains references to the images (just like in the configu-ration file)

– Facilitates collision detection

– Reduces storage - only images of individual tiles are maintained

– Increases overhead for scrolling, as must perform lookup for each tilebefore drawing

3

Page 4: Chapter 12: Side Scroller - General Characteristics The ...djmoon/gaming/gaming-notes/side-scroller.pdf{ Several editors exit for creating tile maps (see resource page) 1. Mappy Fairly

Chapter 12: Side Scroller - Tile Maps (2)

• Creating tile maps

– Easy to make by hand

– Many tile sprites are available on the internet (see resource page)

– Several editors exit for creating tile maps (see resource page)

1. Mappy

∗ Fairly old

∗ Windows-based only

∗ Provides source code to load and display FMP maps for Allegro,OpenGL, DirectX, Java, ...

2. Tiled Map Editor (Tiled Qt)

∗ Windows, OS X, Ubuntu versions

∗ C/C++

∗ A Java version exists, but is not currently being maintained

4

Page 5: Chapter 12: Side Scroller - General Characteristics The ...djmoon/gaming/gaming-notes/side-scroller.pdf{ Several editors exit for creating tile maps (see resource page) 1. Mappy Fairly

Chapter 12: Side Scroller - Jumping Jack Game Overview

• The title screen shot

• Game play

– Jack must leap over obstacles while avoiding fireballs

– Jack controlled by arrow keys

5

Page 6: Chapter 12: Side Scroller - General Characteristics The ...djmoon/gaming/gaming-notes/side-scroller.pdf{ Several editors exit for creating tile maps (see resource page) 1. Mappy Fairly

Chapter 12: Side Scroller - Jumping Jack Game Overview (2)

• Components

1. The foreground consists of a tile map

– The map consists of Brick objects

– Each is represented by a GIF

– The bricks are managed by a BricksManager object

2. The background consists of three GIFs

– Each is represented by a Ribbon object

– They are managed by a RibbonsManager object

– These scroll past the JPanel

3. Jack is the player character, implemented by a sprite

– Jack jumps up and down under player control, but has no horizontalmotion

– His legs are animated via a strip image

4. The fireball is a hazard Jack must avoid, implemented by a sprite

– It has lateral velocity, but no associated animation loop

6

Page 7: Chapter 12: Side Scroller - General Characteristics The ...djmoon/gaming/gaming-notes/side-scroller.pdf{ Several editors exit for creating tile maps (see resource page) 1. Mappy Fairly

Chapter 12: Side Scroller - Jumping Jack Game Overview (3)

• Abbreviated class diagram:

– The framework is the same as used for BugRunner

– These classes (or variants) were discussed in Chapters 2, 3, 4, 5, and 11

7

Page 8: Chapter 12: Side Scroller - General Characteristics The ...djmoon/gaming/gaming-notes/side-scroller.pdf{ Several editors exit for creating tile maps (see resource page) 1. Mappy Fairly

Chapter 12: Side Scroller - Jumping Jack Game Overview (4)

• Detailed class diagram showing classes new to this game:

• The interesting aspects are involved in the sprite, ribbon, and brick classes

8

Page 9: Chapter 12: Side Scroller - General Characteristics The ...djmoon/gaming/gaming-notes/side-scroller.pdf{ Several editors exit for creating tile maps (see resource page) 1. Mappy Fairly

Chapter 12: Side Scroller - JumpingJack Class

• Code:

public class JumpingJack extends JFrame implements WindowListener

{

private static int DEFAULT_FPS = 30;

private JackPanel jp;

private MidisLoader midisLoader;

public JumpingJack(long period)

{ super("JumpingJack");

midisLoader = new MidisLoader();

midisLoader.load("jjf", "jumping_jack_flash.mid");

midisLoader.play("jjf", true); // repeatedly play it

Container c = getContentPane(); // default BorderLayout used

jp = new JackPanel(this, period);

c.add(jp, "Center");

addWindowListener( this );

pack();

setResizable(false);

setVisible(true);

}

public void windowActivated(WindowEvent e)

{ jp.resumeGame(); }

public void windowDeactivated(WindowEvent e)

{ jp.pauseGame(); }

public void windowDeiconified(WindowEvent e)

{ jp.resumeGame(); }

public void windowIconified(WindowEvent e)

{ jp.pauseGame(); }

public void windowClosing(WindowEvent e)

{ jp.stopGame();

midisLoader.close(); // not really required

}

public void windowClosed(WindowEvent e) {}

public void windowOpened(WindowEvent e) {}

public static void main(String args[])

{

long period = (long) 1000.0/DEFAULT_FPS;

new JumpingJack(period*1000000L); // ms --> nanosecs

}

} // end of JumpingJack class

9

Page 10: Chapter 12: Side Scroller - General Characteristics The ...djmoon/gaming/gaming-notes/side-scroller.pdf{ Several editors exit for creating tile maps (see resource page) 1. Mappy Fairly

Chapter 12: Side Scroller - JumpingJack Class (2)

• Game speed limited to 30FPS

• Game control managed using WindowListener

• Most movement expressed in terms of moveSize

– Defined in BricksManagerprivate final static double MOVE_FACTOR = 0.25; //BricksManager - for bricks

private int moveSize;

moveSize = (int)(imWidth * MOVE_FACTOR);

private double moveFactors[] = {0.1, 0.5, 1.0}; //RibbonsManager - for ribbons

– Movement rate per update computed in terms of 0.0 ≤ move factor ≤ 1.0and moveSize

∗ 0.0→ standing still

∗ 1.0→ max speed

– The fireball does not have a move size - it moves independently

10

Page 11: Chapter 12: Side Scroller - General Characteristics The ...djmoon/gaming/gaming-notes/side-scroller.pdf{ Several editors exit for creating tile maps (see resource page) 1. Mappy Fairly

Chapter 12: Side Scroller - JackPanel Class

• Globals:

public class JackPanel extends JPanel implements Runnable, ImagesPlayerWatcher

{

private static final int PWIDTH = 500; // size of panel

private static final int PHEIGHT = 360;

private static final int NO_DELAYS_PER_YIELD = 16;

private static final int MAX_FRAME_SKIPS = 5;

// image, bricks map, clips loader information files

private static final String IMS_INFO = "imsInfo.txt";

private static final String BRICKS_INFO = "bricksInfo.txt";

private static final String SNDS_FILE = "clipsInfo.txt";

private static final String[] exploNames ={"explo1", "explo2", "explo3"};

private static final int MAX_HITS = 20;

private Thread animator; // animation aspects

private volatile boolean running = false;

private volatile boolean isPaused = false;

private long period;

private JumpingJack jackTop;

private ClipsLoader clipsLoader;

// the sprites

private JumperSprite jack;

private FireBallSprite fireball;

// the managers

private RibbonsManager ribsMan;

private BricksManager bricksMan;

private long gameStartTime;

private int timeSpentInGame;

private volatile boolean gameOver = false;

private int score = 0;

private Graphics dbg; // off-screen rendering

private Image dbImage = null;

private boolean showHelp; // to display the title/help screen

private BufferedImage helpIm;

private ImagesPlayer explosionPlayer = null; // explosion-related

private boolean showExplosion = false;

private int explWidth, explHeight; // image dimensions

private int xExpl, yExpl; // coords where image is drawn

private int numHits = 0; // the number of times ’jack’ has been hit

11

Page 12: Chapter 12: Side Scroller - General Characteristics The ...djmoon/gaming/gaming-notes/side-scroller.pdf{ Several editors exit for creating tile maps (see resource page) 1. Mappy Fairly

Chapter 12: Side Scroller - JackPanel Class (2)

• Constructor:

public JackPanel(JumpingJack jj, long period)

{

jackTop = jj;

this.period = period;

setDoubleBuffered(false);

setBackground(Color.white);

setPreferredSize( new Dimension(PWIDTH, PHEIGHT));

setFocusable(true);

requestFocus(); // the JPanel now has focus, so receives key events

addKeyListener( new KeyAdapter() {

public void keyPressed(KeyEvent e)

{ processKey(e); }

});

// initialise the loaders

ImagesLoader imsLoader = new ImagesLoader(IMS_INFO);

clipsLoader = new ClipsLoader(SNDS_FILE);

// initialise the game entities

bricksMan = new BricksManager(PWIDTH, PHEIGHT, BRICKS_INFO, imsLoader);

int brickMoveSize = bricksMan.getMoveSize();

ribsMan = new RibbonsManager(PWIDTH, PHEIGHT, brickMoveSize, imsLoader);

jack = new JumperSprite(PWIDTH, PHEIGHT, brickMoveSize, bricksMan,

imsLoader, (int)(period/1000000L) ); // in ms

fireball = new FireBallSprite(PWIDTH, PHEIGHT, imsLoader, this, jack);

// prepare the explosion animation

explosionPlayer = new ImagesPlayer("explosion", (int)(period/1000000L),

0.5, false, imsLoader);

BufferedImage explosionIm = imsLoader.getImage("explosion");

explWidth = explosionIm.getWidth();

explHeight = explosionIm.getHeight();

explosionPlayer.setWatcher(this); // report animation’s end back here

// prepare title/help screen

helpIm = imsLoader.getImage("title");

showHelp = true; // show at start-up

isPaused = true;

// set up message font

msgsFont = new Font("SansSerif", Font.BOLD, 24);

metrics = this.getFontMetrics(msgsFont);

}

– brickMoveSize controls amount bricks move per update

– Initially, the help/introduction screen is displayed

∗ This is enabled by setting showHelp and isPaused to true

12

Page 13: Chapter 12: Side Scroller - General Characteristics The ...djmoon/gaming/gaming-notes/side-scroller.pdf{ Several editors exit for creating tile maps (see resource page) 1. Mappy Fairly

Chapter 12: Side Scroller - JackPanel Class (3)

• Fireball explosion handled here (see animation later)

– Note that fireball sprite not managed here

• gameRender() method (see animation later)

• Game control handled by processKey() via the arrow keys

public void processKey(KeyEvent e)

// handles termination, help, and game-play keys

{

int keyCode = e.getKeyCode();

// termination keys

if ((keyCode == KeyEvent.VK_ESCAPE) || (keyCode == KeyEvent.VK_Q) ||

(keyCode == KeyEvent.VK_END) ||

((keyCode == KeyEvent.VK_C) && e.isControlDown()) )

running = false;

// help controls

if (keyCode == KeyEvent.VK_H) {

if (showHelp) { // help being shown

showHelp = false; // switch off

isPaused = false;

}

else { // help not being shown

showHelp = true; // show it

isPaused = true; // isPaused may already be true

}

}

// game-play keys

if (!isPaused && !gameOver) {

// move the sprite and ribbons based on the arrow key pressed

if (keyCode == KeyEvent.VK_LEFT) {

jack.moveLeft();

bricksMan.moveRight(); // bricks and ribbons move the other way

ribsMan.moveRight();

}

else if (keyCode == KeyEvent.VK_RIGHT) {

jack.moveRight();

bricksMan.moveLeft();

ribsMan.moveLeft();

}

else if (keyCode == KeyEvent.VK_UP)

jack.jump(); // jumping has no effect on the bricks/ribbons

else if (keyCode == KeyEvent.VK_DOWN) {

jack.stayStill();

bricksMan.stayStill();

ribsMan.stayStill();

}

}

}

13

Page 14: Chapter 12: Side Scroller - General Characteristics The ...djmoon/gaming/gaming-notes/side-scroller.pdf{ Several editors exit for creating tile maps (see resource page) 1. Mappy Fairly

Chapter 12: Side Scroller - JackPanel Class (4)

– Three types of control:

1. Help

∗ H key toggles help screen∗ Game cannot be resumed if help screen displayed

public void resumeGame()

// called when the JFrame is activated / deiconified

{ if (!showHelp)

isPaused = false;

}

2. Play

∗ ← and → control direction

∗ ↑ jumps

∗ ↓ stays still

3. Termination

∗ Triggered by ESC, CTRL-C, END, Q key combinations

– If press two keys in sequence, can have Jack move while jumping, or jumpwhile moving

– To implement multiple key combinations, code must be modified

∗ Use Booleans to remember which keys are currently pressed

∗ Use both keyPressed() and keyReleased() methods in tandem

14

Page 15: Chapter 12: Side Scroller - General Characteristics The ...djmoon/gaming/gaming-notes/side-scroller.pdf{ Several editors exit for creating tile maps (see resource page) 1. Mappy Fairly

Chapter 12: Side Scroller - JackPanel Class (5)∗ Sample code:

private boolean leftKeyPressed = false;

private boolean rightKeyPressed = false;

private boolean upKeyPressed = false;

public JackPanel(JumpingJack jj, long period)

{

...

addKeyListener(new keyAdapter() {

public void keyPressed(KeyEvent e)

{processKeyPress(e); }

public void keyReleased(KeyEvent e)

{processKeyRelease(e); }

});

...

}

public void processKeyPress(KeyEvent e)

{

int keyCode = e.getKeyCode();

if (keyCode == KeyEvent.VK_LEFT) {

leftKeyPressed = true;

else if (keyCode == KeyEvent.VK_RIGHT) {

rightKeyPressed = true;

else if (keyCode == KeyEvent.VK_UP) {

upKeyPressed = true;

if (leftKeyPressed && upKeyPressed)

//up-left action

if (rightKeyPressed && upKeyPressed)

//up-right action

... //other key actions

}

public void processKeyRelease(KeyEvent e)

{

int keyCode = e.getKeyCode();

if (keyCode == KeyEvent.VK_LEFT) {

leftKeyPressed = false;

else if (keyCode == KeyEvent.VK_RIGHT) {

rightKeyPressed = false;

else if (keyCode == KeyEvent.VK_UP) {

upKeyPressed = false;

}

}

15

Page 16: Chapter 12: Side Scroller - General Characteristics The ...djmoon/gaming/gaming-notes/side-scroller.pdf{ Several editors exit for creating tile maps (see resource page) 1. Mappy Fairly

Chapter 12: Side Scroller - JackPanel Class (6)

• run() method and animation

– Mostly unchanged from previous versionspublic void run()

{

long beforeTime, afterTime, timeDiff, sleepTime;

long overSleepTime = 0L;

int noDelays = 0;

long excess = 0L;

gameStartTime = J3DTimer.getValue();

beforeTime = gameStartTime;

running = true;

while(running) {

gameUpdate();

gameRender();

paintScreen();

afterTime = J3DTimer.getValue();

timeDiff = afterTime - beforeTime;

sleepTime = (period - timeDiff) - overSleepTime;

if (sleepTime > 0) { // some time left in this cycle

try {

Thread.sleep(sleepTime/1000000L); // nano -> ms

}

catch(InterruptedException ex){}

overSleepTime = (J3DTimer.getValue() - afterTime) - sleepTime;

}

else { // sleepTime <= 0; the frame took longer than the period

excess -= sleepTime; // store excess time value

overSleepTime = 0L;

if (++noDelays >= NO_DELAYS_PER_YIELD) {

Thread.yield(); // give another thread a chance to run

noDelays = 0;

}

}

beforeTime = J3DTimer.getValue();

int skips = 0;

while((excess > period) && (skips < MAX_FRAME_SKIPS)) {

excess -= period;

gameUpdate(); // update state but don’t render

skips++;

}

}

System.exit(0); // so window disappears

}

16

Page 17: Chapter 12: Side Scroller - General Characteristics The ...djmoon/gaming/gaming-notes/side-scroller.pdf{ Several editors exit for creating tile maps (see resource page) 1. Mappy Fairly

Chapter 12: Side Scroller - JackPanel Class (7)

– gameUpdate() methodprivate void gameUpdate()

{

if (!isPaused && !gameOver) {

if (jack.willHitBrick()) { // collision checking first

jack.stayStill(); // stop jack and scenery

bricksMan.stayStill();

ribsMan.stayStill();

}

ribsMan.update(); // update background and sprites

bricksMan.update();

jack.updateSprite();

fireball.updateSprite();

if (showExplosion)

explosionPlayer.updateTick(); // update the animation

}

}

∗ Collision checking performed here

· If collision occurs, no update (i.e., no movement)

∗ Explosion updates handled here

17

Page 18: Chapter 12: Side Scroller - General Characteristics The ...djmoon/gaming/gaming-notes/side-scroller.pdf{ Several editors exit for creating tile maps (see resource page) 1. Mappy Fairly

Chapter 12: Side Scroller - JackPanel Class (8)

– gameRender() methodprivate void gameRender()

{

if (dbImage == null){

dbImage = createImage(PWIDTH, PHEIGHT);

if (dbImage == null) {

System.out.println("dbImage is null");

return;

}

else

dbg = dbImage.getGraphics();

}

dbg.setColor(Color.white);

dbg.fillRect(0, 0, PWIDTH, PHEIGHT);

ribsMan.display(dbg); // the background ribbons

bricksMan.display(dbg); // the bricks

jack.drawSprite(dbg); // the sprites

fireball.drawSprite(dbg);

if (showExplosion) // draw the explosion (in front of jack)

dbg.drawImage(explosionPlayer.getCurrentImage(), xExpl, yExpl, null);

reportStats(dbg);

if (gameOver)

gameOverMessage(dbg);

if (showHelp) // draw the help at the very front (if switched on)

dbg.drawImage(helpIm, (PWIDTH-helpIm.getWidth())/2,

(PHEIGHT-helpIm.getHeight())/2, null);

}

∗ The game elements are drawn back to front

∗ An explosion is drawn if warranted

∗ The help screen is drawn if requested

∗ Ribbon and brick drawing handled by respective managers

18

Page 19: Chapter 12: Side Scroller - General Characteristics The ...djmoon/gaming/gaming-notes/side-scroller.pdf{ Several editors exit for creating tile maps (see resource page) 1. Mappy Fairly

Chapter 12: Side Scroller - JackPanel Class (9)

– Explosions called by fireball sprite, but handled by JackPanel class (seeabove)public void showExplosion(int x, int y)

// called by fireball sprite when it hits jack at (x,y)

{ if (!showExplosion) { // only allow a single explosion at a time

showExplosion = true;

xExpl = x - explWidth/2; //\ (x,y) is the center of the explosion

yExpl = y - explHeight/2;

/* Play an explosion clip, but cycle through them.

This adds variety, and gets round not being able to

play multiple instances of a clip at the same time. */

clipsLoader.play( exploNames[numHits%exploNames.length], false);

numHits++;

}

}

∗ (x, y) represents the center of the image

· Placement based on upper left corner

· (xExpl, yExpl) computed for this

∗ Concurrent explosions not displayed - only one

∗ Sound clips will play multiple

– sequenceEnded() called at end of explosion looppublic void sequenceEnded(String imageName)

{

showExplosion = false;

explosionPlayer.restartAt(0); // reset animation for next time

if (numHits >= MAX_HITS) {

gameOver = true;

score = (int) ((J3DTimer.getValue() - gameStartTime)/1000000000L);

clipsLoader.play("applause", false);

}

}

∗ Checks to see if game is over

∗ Resets animation for next explosion

∗ Insures that game doesn’t terminate befor eexplosion sequence ended

19

Page 20: Chapter 12: Side Scroller - General Characteristics The ...djmoon/gaming/gaming-notes/side-scroller.pdf{ Several editors exit for creating tile maps (see resource page) 1. Mappy Fairly

Chapter 12: Side Scroller - RibbonsManager Class

• Code:

public class RibbonsManager

{

private String ribImages[] = {"mountains", "houses", "trees"};

private double moveFactors[] = {0.1, 0.5, 1.0};

private Ribbon[] ribbons;

private int numRibbons;

private int moveSize;

public RibbonsManager(int w, int h, int brickMvSz, ImagesLoader imsLd)

{

moveSize = brickMvSz;

numRibbons = ribImages.length;

ribbons = new Ribbon[numRibbons];

for (int i = 0; i < numRibbons; i++)

ribbons[i] = new Ribbon(w, h, imsLd.getImage( ribImages[i] ),

(int) (moveFactors[i]*moveSize) );

}

public void moveRight()

{ for (int i=0; i < numRibbons; i++)

ribbons[i].moveRight();

}

public void moveLeft()

{ for (int i=0; i < numRibbons; i++)

ribbons[i].moveLeft();

}

public void stayStill()

{ for (int i=0; i < numRibbons; i++)

ribbons[i].stayStill();

}

public void update()

{ for (int i=0; i < numRibbons; i++)

ribbons[i].update();

}

public void display(Graphics g)

{ for (int i=0; i < numRibbons; i++)

ribbons[i].display(g);

}

}

20

Page 21: Chapter 12: Side Scroller - General Characteristics The ...djmoon/gaming/gaming-notes/side-scroller.pdf{ Several editors exit for creating tile maps (see resource page) 1. Mappy Fairly

Chapter 12: Side Scroller - RibbonsManager Class (2)

• This class handles the backgrounds (ribbons)

• The moveSize variable represents the base movement amount per update

– This is set to the brick move size

– The moveFactors array holds the movement factors for the backgroundlayers

• The background image file names are stored in the ribImages array

• The background images are stored in the ribbons array

• The layers are stored from back to front in the arrays, and must be drawn inthis order

• The manager acts primarily as a router, calling movement, update, and displaymethods for each ribbon

21

Page 22: Chapter 12: Side Scroller - General Characteristics The ...djmoon/gaming/gaming-notes/side-scroller.pdf{ Several editors exit for creating tile maps (see resource page) 1. Mappy Fairly

Chapter 12: Side Scroller - Ribbon Class

• A ribbon image is preferrably as wide or wider than the panel in which it isdisplayed

– This will require at most two calls to draw()

22

Page 23: Chapter 12: Side Scroller - General Characteristics The ...djmoon/gaming/gaming-notes/side-scroller.pdf{ Several editors exit for creating tile maps (see resource page) 1. Mappy Fairly

Chapter 12: Side Scroller - Ribbon Class (2)

• Globals and constructor:

public class Ribbon

{

private BufferedImage im;

private int width; // the width of the image (>= pWidth)

private int pWidth, pHeight; // dimensions of display panel

private int moveSize; // size of the image move (in pixels)

private boolean isMovingRight; // movement flags

private boolean isMovingLeft;

private int xImHead;

public Ribbon(int w, int h, BufferedImage im, int moveSz)

{

pWidth = w; pHeight = h;

this.im = im;

width = im.getWidth(); // no need to store the height

if (width < pWidth)

System.out.println("Ribbon width < panel width");

moveSize = moveSz;

isMovingRight = false; // no movement at start

isMovingLeft = false;

xImHead = 0;

}

– xImHead locates left side of image

– isMovingRight/Left are direction flags set by movement methods

• Movement methods:

public void moveRight()

{ isMovingRight = true;

isMovingLeft = false;

}

public void moveLeft()

{ isMovingRight = false;

isMovingLeft = true;

}

public void stayStill()

{ isMovingRight = false;

isMovingLeft = false;

}

23

Page 24: Chapter 12: Side Scroller - General Characteristics The ...djmoon/gaming/gaming-notes/side-scroller.pdf{ Several editors exit for creating tile maps (see resource page) 1. Mappy Fairly

Chapter 12: Side Scroller - Ribbon Class (3)

• update() method

public void update()

{ if (isMovingRight)

xImHead = (xImHead + moveSize) % width;

else if (isMovingLeft)

xImHead = (xImHead - moveSize) % width;

}

– Adjusts xImHead

−panel width ≤ xImHead ≤ +panel width

• display() method

1. Handles drawing

2. Uses two coordinate systems:

(a) JPanel’s

(b) Image’s

– Both have same height

– These values passed to draw()private void draw(Graphics g, BufferedImage im, int dx1, int dx2, int sx1, int sx2)

{

g.drawImage(im, dx1, 0, dx2, pHeight, sx1, 0, sx2, pHeight, null);

}

– The proper x coordinate in the ribbon is based on xImHead, whichrepresents the location of the left side of the ribbon with respect to thepanel

−width ≤ xImHead ≤ width

24

Page 25: Chapter 12: Side Scroller - General Characteristics The ...djmoon/gaming/gaming-notes/side-scroller.pdf{ Several editors exit for creating tile maps (see resource page) 1. Mappy Fairly

Chapter 12: Side Scroller - Ribbon Class (4)

3. Five situations to consider:

(a) Case 1: Start up

– The ribbon starts at the left side of the panel

– This also applies to the situation where the ribbon has done a com-plete wrap-around

draw(g, im, 0, pWidth, 0, pWidth);

25

Page 26: Chapter 12: Side Scroller - General Characteristics The ...djmoon/gaming/gaming-notes/side-scroller.pdf{ Several editors exit for creating tile maps (see resource page) 1. Mappy Fairly

Chapter 12: Side Scroller - Ribbon Class (5)

(b) Case 2: Ribbon moving to right (Jack moving to left)

xImHead < pWidth

– The head and tail meet somewhere within the panel

draw(g, im, 0, xImHead, width-xImHead, width); // im tail

draw(g, im, xImHead, pWidth, 0, pWidth-xImHead); // im head

26

Page 27: Chapter 12: Side Scroller - General Characteristics The ...djmoon/gaming/gaming-notes/side-scroller.pdf{ Several editors exit for creating tile maps (see resource page) 1. Mappy Fairly

Chapter 12: Side Scroller - Ribbon Class (6)

(c) Case 3: Ribbon moving to right (Jack moving to left)

xImHead ≥ pWidth

– Both the head and tail fall outside the panel

draw(g, im, 0, pWidth, width-xImHead,

width-xImHead+pWidth); // im tail

27

Page 28: Chapter 12: Side Scroller - General Characteristics The ...djmoon/gaming/gaming-notes/side-scroller.pdf{ Several editors exit for creating tile maps (see resource page) 1. Mappy Fairly

Chapter 12: Side Scroller - Ribbon Class (7)

(d) Case 4: Ribbon moving to left (Jack moving to right)

xImHead ≥ pWidth− width

– Both the head and tail fall outside the panel

– This differs from case 3 because xImHead < 0

draw(g, im, 0, pWidth, -xImHead, pWidth-xImHead); // im body

28

Page 29: Chapter 12: Side Scroller - General Characteristics The ...djmoon/gaming/gaming-notes/side-scroller.pdf{ Several editors exit for creating tile maps (see resource page) 1. Mappy Fairly

Chapter 12: Side Scroller - Ribbon Class (8)

(e) Case 5: Ribbon moving to left (Jack moving to right)

xImHead < pWidth− width

– Comparable to case 2

draw(g, im, 0, width+xImHead, -xImHead, width); // im tail

draw(g, im, width+xImHead, pWidth, 0,

pWidth-width-xImHead); // im head

29

Page 30: Chapter 12: Side Scroller - General Characteristics The ...djmoon/gaming/gaming-notes/side-scroller.pdf{ Several editors exit for creating tile maps (see resource page) 1. Mappy Fairly

Chapter 12: Side Scroller - Ribbon Class (9)

• Code:

public void display(Graphics g)

{

if (xImHead == 0) // Case 1: draw im head at (0,0)

draw(g, im, 0, pWidth, 0, pWidth);

else if ((xImHead > 0) && (xImHead < pWidth)) { // Case 2: draw im tail at (0,0) and

// im head at (xImHead,0)

draw(g, im, 0, xImHead, width-xImHead, width); // im tail

draw(g, im, xImHead, pWidth, 0, pWidth-xImHead); // im head

}

else if (xImHead >= pWidth) // Case 3: draw im tail at (0,0)

draw(g, im, 0, pWidth,

width-xImHead, width-xImHead+pWidth); // im tail

else if ((xImHead < 0) && (xImHead >= pWidth-width)) //Case 4

draw(g, im, 0, pWidth, -xImHead, pWidth-xImHead); // im body

else if (xImHead < pWidth-width) { // Case 5: draw im tail at (0,0)

// and im head at (width+xImHead,0)

draw(g, im, 0, width+xImHead, -xImHead, width); // im tail

draw(g, im, width+xImHead, pWidth,

0, pWidth-width-xImHead); // im head

}

}

30

Page 31: Chapter 12: Side Scroller - General Characteristics The ...djmoon/gaming/gaming-notes/side-scroller.pdf{ Several editors exit for creating tile maps (see resource page) 1. Mappy Fairly

Chapter 12: Side Scroller - BricksManager Class

• The basic operations this class is responsible for:

1. Loading brick info

2. Creating brick structures

3. Moving the brick map

4. Drawing bricks

5. Handling Jack-related interactions

• Globals and constructor:

public class BricksManager

{

private final static String IMAGE_DIR = "Images/";

private final static int MAX_BRICKS_LINES = 15;

private final static double MOVE_FACTOR = 0.25;

private int pWidth, pHeight; // dimensions of display panel

private int width, height; // max dimensions of bricks map

private int imWidth, imHeight; // dimensions of a brick image

private int numCols, numRows;

private int moveSize;

private boolean isMovingRight; // movement flags

private boolean isMovingLeft;

private int xMapHead;

private ArrayList bricksList;

private ArrayList[] columnBricks;

private ImagesLoader imsLoader;

private ArrayList brickImages = null;

public BricksManager(int w, int h, String fnm, ImagesLoader il)

{

pWidth = w;

pHeight = h;

imsLoader = il;

bricksList = new ArrayList();

loadBricksFile(fnm);

initBricksInfo();

createColumns();

moveSize = (int)(imWidth * MOVE_FACTOR);

if (moveSize == 0) {

System.out.println("moveSize cannot be 0, setting it to 1");

moveSize = 1;

}

isMovingRight = false; // no movement at start

isMovingLeft = false;

xMapHead = 0;

}

31

Page 32: Chapter 12: Side Scroller - General Characteristics The ...djmoon/gaming/gaming-notes/side-scroller.pdf{ Several editors exit for creating tile maps (see resource page) 1. Mappy Fairly

Chapter 12: Side Scroller - BricksManager Class - Loading

• Loading brick info is accomplished by the loadBricksFile() method

• Component info is stored in file bricksInfo.txt

– This stores image data and map data// bricks information

s tiles.gif 5

// -----------

44444

222222222

111

2222

11111

444

444

22222 444 111

1111112222222 23333 2 33 44444444

00 000111333333000000222222233333 333 2222222223333301

00000000011100000000002220000000003300000111111222222234

// -----------

– Image data

∗ The image is a GIF strip tiles.gif

∗ This is read in the usual way

– Map data

∗ The map is represented by lines of digits

∗ Their positions represent where bricks are located, and the images usedfor the bricks

∗ Note that using digits is very limiting - you are limited to only ten stylesof bricks

32

Page 33: Chapter 12: Side Scroller - General Characteristics The ...djmoon/gaming/gaming-notes/side-scroller.pdf{ Several editors exit for creating tile maps (see resource page) 1. Mappy Fairly

Chapter 12: Side Scroller - BricksManager Class - Loading (2)

• Code:

private void loadBricksFile(String fnm)

{

String imsFNm = IMAGE_DIR + fnm;

System.out.println("Reading bricks file: " + imsFNm);

int numStripImages = -1;

int numBricksLines = 0;

try {

BufferedReader br = new BufferedReader( new FileReader(imsFNm));

String line;

char ch;

while((line = br.readLine()) != null) {

if (line.length() == 0) // ignore a blank line

continue;

if (line.startsWith("//")) // ignore a comment line

continue;

ch = Character.toLowerCase( line.charAt(0) );

if (ch == ’s’) // an images strip

numStripImages = getStripImages(line);

else { // a bricks map line

if (numBricksLines > MAX_BRICKS_LINES)

System.out.println("Max reached, skipping bricks line: " + line);

else if (numStripImages == -1)

System.out.println("No strip image, skipping bricks line: " + line);

else {

storeBricks(line, numBricksLines, numStripImages);

numBricksLines++;

}

}

}

br.close();

}

catch (IOException e)

{ System.out.println("Error reading file: " + imsFNm);

System.exit(1);

}

}

33

Page 34: Chapter 12: Side Scroller - General Characteristics The ...djmoon/gaming/gaming-notes/side-scroller.pdf{ Several editors exit for creating tile maps (see resource page) 1. Mappy Fairly

Chapter 12: Side Scroller - BricksManager Class - Loading (3)

• A new brick is created for each brick

• Code:

public class Brick

{

private int mapX, mapY; // indicies in the bricks map

private int imageID;

private BufferedImage image;

private int height; // of the brick image

private int locY;

public Brick(int id, int x, int y)

{

mapX = x; mapY = y;

imageID = id;

}

• storeBricks() stores one line of bricks

– The bricks are stored in an ArrayList (bricksList) in no particular order

– Code:private void storeBricks(String line, int lineNo, int numImages)

{

int imageID;

for(int x=0; x < line.length(); x++) {

char ch = line.charAt(x);

if (ch == ’ ’) // ignore a space

continue;

if (Character.isDigit(ch)) {

imageID = ch - ’0’; // we assume a digit is 0-9

if (imageID >= numImages)

System.out.println("Image ID " + imageID + " out of range");

else // make a Brick object

bricksList.add( new Brick(imageID, x, lineNo) );

}

else

System.out.println("Brick char " + ch + " is not a digit");

}

}

34

Page 35: Chapter 12: Side Scroller - General Characteristics The ...djmoon/gaming/gaming-notes/side-scroller.pdf{ Several editors exit for creating tile maps (see resource page) 1. Mappy Fairly

Chapter 12: Side Scroller - BricksManager Class - Loading (4)

– The following diagram shows the relevant variables associated with the brickmap

35

Page 36: Chapter 12: Side Scroller - General Characteristics The ...djmoon/gaming/gaming-notes/side-scroller.pdf{ Several editors exit for creating tile maps (see resource page) 1. Mappy Fairly

Chapter 12: Side Scroller - BricksManager Class - Initializing Brick DataStructures

• After bricksList is loaded, initBricksInfo is called

– Its job is to check the integrity of the brick map

1. Wider than panel

2. No gaps in first level

3. Etc.– Code:

private void initBricksInfo()

{

if (brickImages == null) {

System.out.println("No bricks images were loaded");

System.exit(1);

}

if (bricksList.size() == 0) {

System.out.println("No bricks map were loaded");

System.exit(1);

}

BufferedImage im = (BufferedImage) brickImages.get(0);

imWidth = im.getWidth();

imHeight = im.getHeight();

findNumBricks();

calcMapDimensions();

checkForGaps();

addBrickDetails();

}

• createColumns() stores the map on a column-by-column basis

– Each column stored as an ArrayList

– columnBricks is an ArrayList of the columns

– Bricks are not ordered in a column

36

Page 37: Chapter 12: Side Scroller - General Characteristics The ...djmoon/gaming/gaming-notes/side-scroller.pdf{ Several editors exit for creating tile maps (see resource page) 1. Mappy Fairly

Chapter 12: Side Scroller - BricksManager Class - Initializing Brick DataStructures (2)

– Code:private void createColumns()

{

columnBricks = new ArrayList[numCols];

for (int i=0; i < numCols; i++)

columnBricks[i] = new ArrayList();

Brick b;

for (int j=0; j < bricksList.size(); j++) {

b = (Brick) bricksList.get(j);

columnBricks[ b.getMapX() ].add(b); // bricks not stored in any order

}

}

37

Page 38: Chapter 12: Side Scroller - General Characteristics The ...djmoon/gaming/gaming-notes/side-scroller.pdf{ Several editors exit for creating tile maps (see resource page) 1. Mappy Fairly

Chapter 12: Side Scroller - BricksManager Class - Moving the Brick Map

• Moving the brick map is performed similarly to ribbons

public void moveRight()

{ isMovingRight = true;

isMovingLeft = false;

}

public void moveLeft()

{ isMovingRight = false;

isMovingLeft = true;

}

public void stayStill()

{ isMovingRight = false;

isMovingLeft = false;

}

public void update()

{

if (isMovingRight)

xMapHead = (xMapHead + moveSize) % width;

else if (isMovingLeft)

xMapHead = (xMapHead - moveSize) % width;

}

• xMapHead represents the left x coordinate of the brick map

−width ≤ xMapHead ≤ +width

38

Page 39: Chapter 12: Side Scroller - General Characteristics The ...djmoon/gaming/gaming-notes/side-scroller.pdf{ Several editors exit for creating tile maps (see resource page) 1. Mappy Fairly

Chapter 12: Side Scroller - BricksManager Class - Drawing the Brick Map

• There are three coordinate systems that must be contended with:

1. The panel’s

2. The brick map image’s

3. The indices of the bricks relative to the map representation

• The display() method:

public void display(Graphics g)

{

int bCoord = (int)(xMapHead/imWidth) * imWidth;

int offset; // offset is the distance

// between bCoord and xMapHead

if (bCoord >= 0)

offset = xMapHead - bCoord; // offset is positive

else // negative position

offset = bCoord - xMapHead; // offset is positive

if ((bCoord >= 0) && (bCoord < pWidth)) {

drawBricks(g, 0-(imWidth-offset), xMapHead, width-bCoord-imWidth); // bm tail

drawBricks(g, xMapHead, pWidth, 0); // bm start

}

else if (bCoord >= pWidth)

drawBricks(g, 0-(imWidth-offset), pWidth, width-bCoord-imWidth); // bm tail

else if ((bCoord < 0) && (bCoord >= pWidth-width+imWidth))

drawBricks(g, 0-offset, pWidth, -bCoord); // bm tail

else if (bCoord < pWidth-width+imWidth) {

drawBricks(g, 0-offset, width+xMapHead, -bCoord); // bm tail

drawBricks(g, width+xMapHead, pWidth, 0); // bm start

}

}

– xMapHead represents the left x coordinate of the brick map, as noted above

– bCoord represents the x coordinate of the left edge of the brick whose hor-izontal extent includes xMapHead

• The actual drawing is accomplished by drawBricks() (discussed later)

private void drawBricks(Graphics g, int xStart, int xEnd, int xBrick)

– xStart and xEnd represent left and right limits of the active drawing areain the panel

– xBrick represents the coordinate in the brick map that corresponds to xStart

– Bricks are drawn into the panel one column at a time

39

Page 40: Chapter 12: Side Scroller - General Characteristics The ...djmoon/gaming/gaming-notes/side-scroller.pdf{ Several editors exit for creating tile maps (see resource page) 1. Mappy Fairly

Chapter 12: Side Scroller - BricksManager Class - Drawing the Brick Map (2)

• There are four situations that must be addressed when drawing the brick map

1. Case 1: Map is moving toward the right, bCoord ≤ pWidth

0 ≤ xMapHeight ≤ pWidth

– Requires two calls to drawBricks()

– Complexity due to fact that the left edge of the panel most likely doesnot correspond to the left edge of a brick column

– The first call draws to the left side of the paneldrawBricks(g, 0-(imWidth-offset), xMapHead, width-bCoord-imWidth);

∗ It draws from 0− (imWidth− offset) to xMapHead in the panel

∗ It starts with the brick column at width− bCoord− imWidth– The second call draws to the right side of the panel

drawBricks(g, xMapHead, pWidth, 0);

∗ It draws from xMapHead to pWidth in the panel

∗ It starts with the brick column at 0

40

Page 41: Chapter 12: Side Scroller - General Characteristics The ...djmoon/gaming/gaming-notes/side-scroller.pdf{ Several editors exit for creating tile maps (see resource page) 1. Mappy Fairly

Chapter 12: Side Scroller - BricksManager Class - Drawing the Brick Map(3)

2. Case 2: Map is moving toward the right, bCoord > pWidth

– Requires a single draw

41

Page 42: Chapter 12: Side Scroller - General Characteristics The ...djmoon/gaming/gaming-notes/side-scroller.pdf{ Several editors exit for creating tile maps (see resource page) 1. Mappy Fairly

Chapter 12: Side Scroller - BricksManager Class - Drawing the Brick Map(4)

3. Case 3: Map is moving toward the left, bCoord > pWidth − width +imWidth

xMapHead, bCoord < 0

– Requires a single draw

42

Page 43: Chapter 12: Side Scroller - General Characteristics The ...djmoon/gaming/gaming-notes/side-scroller.pdf{ Several editors exit for creating tile maps (see resource page) 1. Mappy Fairly

Chapter 12: Side Scroller - BricksManager Class - Drawing the Brick Map(5)

4. Case 4: Map is moving toward the left, bCoord < pWidth − width +imWidth

– Requires two draws

• drawBricks() method

private void drawBricks(Graphics g, int xStart, int xEnd, int xBrick)

{

int xMap = xBrick/imWidth; // get the column position of the brick

// in the bricks map

ArrayList column;

Brick b;

for (int x = xStart; x < xEnd; x += imWidth) {

column = columnBricks[ xMap ]; // get the current column

for (int i=0; i < column.size(); i++) { // draw all bricks

b = (Brick) column.get(i);

b.display(g, x); // draw brick b at JPanel posn x

}

xMap++; // examine the next column of bricks

}

}

– xBrick (x coordinate of left edge of starting column of bricks to be drawn)is converted into an index (xMap) into the columnBricks array

– Individual bricks drawn by Brick.display()

– Only the x coordinate is passed since a brick’s y coordinate is fixed

43

Page 44: Chapter 12: Side Scroller - General Characteristics The ...djmoon/gaming/gaming-notes/side-scroller.pdf{ Several editors exit for creating tile maps (see resource page) 1. Mappy Fairly

Chapter 12: Side Scroller - BricksManager Class - JumperSprite-Related Methods

1. findFloor()

• Jack is initially placed in center of panel at xSprite

• Must determine his y coordinate so he is standing on a brick at that location

• xSprite is converted into a horizontal index into the brick map

• The height of the corresponding column is then calculated• Code:

public int findFloor(int xSprite)

{

int xMap = (int)(xSprite/imWidth); // x map index

int locY = pHeight; // starting y position (the largest possible)

ArrayList column = columnBricks[ xMap ];

Brick b;

for (int i=0; i < column.size(); i++) {

b = (Brick) column.get(i);

if (b.getLocY() < locY)

locY = b.getLocY(); // reduce locY (i.e. move up)

}

return locY;

}

2. Collisions

(a) With bricks moving left or right

• Handled by insideBrick()

• Called before a move is made• Code:

public boolean insideBrick(int xWorld, int yWorld)

{

Point mapCoord = worldToMap(xWorld, yWorld);

ArrayList column = columnBricks[ mapCoord.x ];

Brick b;

for (int i=0; i < column.size(); i++) {

b = (Brick) column.get(i);

if (mapCoord.y == b.getMapY())

return true;

}

return false;

}

44

Page 45: Chapter 12: Side Scroller - General Characteristics The ...djmoon/gaming/gaming-notes/side-scroller.pdf{ Several editors exit for creating tile maps (see resource page) 1. Mappy Fairly

Chapter 12: Side Scroller - BricksManager Class -JumperSprite-Related Methods (2)

• (xWorld, yWorld) represent the location Jack would be in if the movewere executed

• This coordinate is converted into (x, y) indices into the brick map byworldToMap()

– This returns a Point object in terms of (mapX,mapY )

– Since xWorld can extend beyond the map edges, it is converted tothe range [0, width]

– The y coordinate is adjusted to within the map’s vertical range– Code:

private Point worldToMap(int xWorld, int yWorld)

{

xWorld = xWorld % width; // limit to range (width to -width)

if (xWorld < 0) // make positive

xWorld += width;

int mapX = (int) (xWorld/imWidth); // map x-index

yWorld = yWorld - (pHeight-height); // relative to map

int mapY = (int) (yWorld/imHeight); // map y-index

if (yWorld < 0) // above the top of the bricks

mapY = mapY-1; // match to next ’row’ up

return new Point(mapX, mapY);

}

• The collision test simply checks to see if Jack’s corresponding y index isthe same as a brick’s in this column

• If a collision is detected, no movement takes place

45

Page 46: Chapter 12: Side Scroller - General Characteristics The ...djmoon/gaming/gaming-notes/side-scroller.pdf{ Several editors exit for creating tile maps (see resource page) 1. Mappy Fairly

Chapter 12: Side Scroller - BricksManager Class - JumperSprite-RelatedMethods (3)

(b) Collisions with the ceiling

• Must check that Jack’s head does not collide with bricks suspended fromthe top of the panel when jumping

• Vertical collision detection handled by checkBrickBase()public int checkBrickBase(int xWorld, int yWorld, int step)

{

if (insideBrick(xWorld, yWorld)) {

int yMapWorld = yWorld - (pHeight-height);

int mapY = (int) (yMapWorld/imHeight); // map y- index

int topOffset = yMapWorld - (mapY * imHeight);

int smallStep = step - (imHeight-topOffset);

return smallStep;

}

return step;

}

• If a collision is imminent, the step vertical distance will need to be ad-justed accordingly

smallStep = step− (imHeight− topOffset)

46

Page 47: Chapter 12: Side Scroller - General Characteristics The ...djmoon/gaming/gaming-notes/side-scroller.pdf{ Several editors exit for creating tile maps (see resource page) 1. Mappy Fairly

Chapter 12: Side Scroller - BricksManager Class - JumperSprite-RelatedMethods (4)

(c) Landing

• When Jack is falling, must detect when he lands

• This is handled similarly to the way head collisions are detected, but inthe opposite sense• Method checkBrickTop() handles this

public int checkBrickTop(int xWorld, int yWorld, int step)

{

if (insideBrick(xWorld, yWorld)) {

int yMapWorld = yWorld - (pHeight-height);

int mapY = (int) (yMapWorld/imHeight); // map y- index

int topOffset = yMapWorld - (mapY * imHeight);

int smallStep = step - topOffset;

return smallStep;

}

return step; // no change

}

• If a collision is imminent, the step vertical distance will need to be ad-justed accordingly

smallStep = step− topOffset

47

Page 48: Chapter 12: Side Scroller - General Characteristics The ...djmoon/gaming/gaming-notes/side-scroller.pdf{ Several editors exit for creating tile maps (see resource page) 1. Mappy Fairly

Chapter 12: Side Scroller - Brick Class

public class Brick

{

private int mapX, mapY; // indicies in the bricks map

private int imageID;

private BufferedImage image;

private int height; // of the brick image

private int locY; // the y- coordinate of the brick in the bricks ribbon

public Brick(int id, int x, int y)

{ mapX = x; mapY = y;

imageID = id;

}

public int getMapX()

{ return mapX; }

public int getMapY()

{ return mapY; }

public int getImageID()

{ return imageID; }

public void setImage(BufferedImage im)

{ image = im;

height = im.getHeight();

}

public void setLocY(int pHeight, int maxYBricks)

{ locY = pHeight - ((maxYBricks-mapY) * height); }

public int getLocY()

{ return locY; }

public void display(Graphics g, int xScr)

{ g.drawImage(image, xScr, locY, null); }

}

• Straightforward and simple

• Variations that could be implemented

1. Animated bricks

– Animation would need to be managed by ImagePlayer object(s)

– If used one per brick, would be expensive

– If used a single player to manage all bricks, would result in sameness inanimation

– Could create an animated brick subclass

2. Allow scrolling both horizontally and vertically

– This would require updating bricks’ y coordinates as the character moves

– BricksManager would need to be modified

48

Page 49: Chapter 12: Side Scroller - General Characteristics The ...djmoon/gaming/gaming-notes/side-scroller.pdf{ Several editors exit for creating tile maps (see resource page) 1. Mappy Fairly

Chapter 12: Side Scroller - FireballSprite Class

• State chart:

49

Page 50: Chapter 12: Side Scroller - General Characteristics The ...djmoon/gaming/gaming-notes/side-scroller.pdf{ Several editors exit for creating tile maps (see resource page) 1. Mappy Fairly

Chapter 12: Side Scroller - FireballSprite Class (2)

• The constructor calculates a random height for the fireball

• The updateSprite() method does the real work (see following code)

1. Has it collided with jack?

– Checked by hasHitJack()

– To make the collisions more realistic, the bounding rectangle used forcollision detection is a scaled-down version of Jack’s bounding rectangle

2. Has it reached the left edge of the panel?

– Checked by goneOffScreen()

– When it reaches the left edge, it is re-initialized

3. Mapping of the state chart to the code:

(a) move state → updateSprite()

(b) draw state → drawSprite()

50

Page 51: Chapter 12: Side Scroller - General Characteristics The ...djmoon/gaming/gaming-notes/side-scroller.pdf{ Several editors exit for creating tile maps (see resource page) 1. Mappy Fairly

Chapter 12: Side Scroller - FireballSprite Class (3)

• Code:

public class FireBallSprite extends Sprite

{

private static final int STEP = -10; // moving left

private static final int STEP_OFFSET = 2;

private JackPanel jp; // tell JackPanel about colliding with jack

private JumperSprite jack;

public FireBallSprite(int w, int h, ImagesLoader imsLd, JackPanel jp, JumperSprite j)

{

super( w, h/2, w, h, imsLd, "fireball");

this.jp = jp;

jack = j;

initPosition();

}

private void initPosition()

{

int h = getPHeight()/2 + ((int)(getPHeight() * Math.random())/2);

if (h + getHeight() > getPHeight())

h -= getHeight();

setPosition(getPWidth(), h);

setStep(STEP + getRandRange(STEP_OFFSET), 0); // move left

}

private int getRandRange(int x)

{ return ((int)(2 * x * Math.random())) - x; }

public void updateSprite()

{

hasHitJack();

goneOffScreen();

super.updateSprite();

}

private void hasHitJack()

{

Rectangle jackBox = jack.getMyRectangle();

jackBox.grow(-jackBox.width/3, 0); // make jack’s bounded box thinner

if (jackBox.intersects( getMyRectangle() )) { // jack collision?

jp.showExplosion(locx, locy+getHeight()/2);

initPosition();

}

}

private void goneOffScreen()

{

if (((locx+getWidth()) <= 0) && (dx < 0)) // off left and moving left

initPosition(); // start the ball in a new position

}

}

51

Page 52: Chapter 12: Side Scroller - General Characteristics The ...djmoon/gaming/gaming-notes/side-scroller.pdf{ Several editors exit for creating tile maps (see resource page) 1. Mappy Fairly

Chapter 12: Side Scroller - JumperSprite Class

• State chart:

52

Page 53: Chapter 12: Side Scroller - General Characteristics The ...djmoon/gaming/gaming-notes/side-scroller.pdf{ Several editors exit for creating tile maps (see resource page) 1. Mappy Fairly

Chapter 12: Side Scroller - JumperSprite Class (2)

– Once put in horizontal motion, the sprite continues until stopped by theplayer or it collides with a brick

– If it runs off a platform, it will continue its horizontal motion while falling

– There are three concurrent activities:

1. Horizontal motion

2. Vertical motion

3. Updating/drawing

– Rather than use separate threads for each, their implementation is inter-leaved

∗ This imposes a sequence that is not constrained by the state chart

• Constructor:

public class JumperSprite extends Sprite

{

private static double DURATION = 0.5; // secs

private static final int NOT_JUMPING = 0;

private static final int RISING = 1;

private static final int FALLING = 2;

private static final int MAX_UP_STEPS = 8;

private int period; // in ms; the game’s animation period

private boolean isFacingRight, isStill;

private int vertMoveMode;

private int vertStep; // distance to move vertically in one step

private int upCount;

private BricksManager brickMan;

private int moveSize; // obtained from BricksManager

private int xWorld, yWorld;

public JumperSprite(int w, int h, int brickMvSz, BricksManager bm, ImagesLoader imsLd, int p)

{

super(w/2, h/2, w, h, imsLd, "runningRight");

moveSize = brickMvSz;

brickMan = bm;

period = p;

setStep(0,0); // no movement

isFacingRight = true;

isStill = true;

locy = brickMan.findFloor(locx+getWidth()/2)-getHeight();

xWorld = locx; yWorld = locy; // store current position

vertMoveMode = NOT_JUMPING;

vertStep = brickMan.getBrickHeight()/2;

upCount = 0;

}

53

Page 54: Chapter 12: Side Scroller - General Characteristics The ...djmoon/gaming/gaming-notes/side-scroller.pdf{ Several editors exit for creating tile maps (see resource page) 1. Mappy Fairly

Chapter 12: Side Scroller - JumperSprite Class (3)

– (xWorld, yWorld) represent the sprite’s location

– Initially, Jack is still, facing right

– findFloor() is used to determine his vertical position, as discussed above

– Various variables are initialized

• Mapping of the state chart to the code:

1. Movement states → combined values of Booleans isFacingRight and isStill

2. notJumping, rising, and falling, states → integers 0, 1, 2, respectively

3. initialize state → constructor

4. [willhitbrickabove] state → updateRising()

5. [willhitbrickbelow] state → updateFalling()

6. update() implements transitions from a number of states

54

Page 55: Chapter 12: Side Scroller - General Characteristics The ...djmoon/gaming/gaming-notes/side-scroller.pdf{ Several editors exit for creating tile maps (see resource page) 1. Mappy Fairly

Chapter 12: Side Scroller - JumperSprite Class (4)

• Motion is handled by the methods moveLeft(), moveRight(), stayStill(), andjump()

– Each motion has a particular image associated with it– Code:

public void moveLeft()

{

setImage("runningLeft");

loopImage(period, DURATION); // cycle through the images

isFacingRight = false; isStill = false;

}

public void moveRight()

{

setImage("runningRight");

loopImage(period, DURATION); // cycle through the images

isFacingRight = true; isStill = false;

}

public void stayStill()

{

stopLooping();

isStill = true;

}

public void jump()

{

if (vertMoveMode == NOT_JUMPING) {

vertMoveMode = RISING;

upCount = 0;

if (isStill) { // only change image if the sprite is ’still’

if (isFacingRight)

setImage("jumpRight");

else

setImage("jumpLeft");

}

}

}

55

Page 56: Chapter 12: Side Scroller - General Characteristics The ...djmoon/gaming/gaming-notes/side-scroller.pdf{ Several editors exit for creating tile maps (see resource page) 1. Mappy Fairly

Chapter 12: Side Scroller - JumperSprite Class (5)

• Collision detection handled in JackPanel

– willHitBrick() called from within update()

– Horizontal collisions handled by JackPanel so other activities triggered bythe collision can be coordinated

– xTest represents the x position the sprite would be in if it were to be updated

– Vertical collisions handled in JumperSprite because they only affect thesprite

– Code:public boolean willHitBrick()

{

if (isStill)

return false; // can’t hit anything if not moving

int xTest; // for testing the new x- position

if (isFacingRight) // moving right

xTest = xWorld + moveSize;

else // moving left

xTest = xWorld - moveSize;

int xMid = xTest + getWidth()/2;

int yMid = yWorld + (int)(getHeight()*0.8); // use current y posn

return brickMan.insideBrick(xMid,yMid);

}

• updateSprite()

– Updates x coordinate– Code:

public void updateSprite()

{

if (!isStill) { // moving

if (isFacingRight) // moving right

xWorld += moveSize;

else // moving left

xWorld -= moveSize;

if (vertMoveMode == NOT_JUMPING) // if not jumping

checkIfFalling(); // may have moved out into empty space

}

if (vertMoveMode == RISING)

updateRising();

else if (vertMoveMode == FALLING)

updateFalling();

super.updateSprite();

}

56

Page 57: Chapter 12: Side Scroller - General Characteristics The ...djmoon/gaming/gaming-notes/side-scroller.pdf{ Several editors exit for creating tile maps (see resource page) 1. Mappy Fairly

Chapter 12: Side Scroller - JumperSprite Class (6)

– Calls checkIfFalling()private void checkIfFalling()

{

int yTrans = brickMan.checkBrickTop( xWorld+(getWidth()/2),

yWorld+getHeight()+vertStep, vertStep);

if (yTrans != 0) // yes it could

vertMoveMode = FALLING; // set it to be in falling mode

}

– Conditionally calls updateRising()

∗ Checks if sprite reaches maximum height via BricksManager.checkBrickBase()

∗ Transitions to falling state if it has∗ Code:

private void updateRising()

{ if (upCount == MAX_UP_STEPS) {

vertMoveMode = FALLING; // at top, now start falling

upCount = 0;

}

else {

int yTrans = brickMan.checkBrickBase(xWorld+(getWidth()/2), yWorld-vertStep, vertStep);

if (yTrans == 0) { // hit the base of a brick

vertMoveMode = FALLING; // start falling

upCount = 0;

}

else { // can move upwards another step

translate(0, -yTrans);

yWorld -= yTrans; // update position

upCount++;

}

}

}

– Conditionally calls updateFalling()

∗ Checks to see if has landed via BricksManager.checkBrickTop()∗ Calls finishJumping() if it has

private void finishJumping()

{

vertMoveMode = NOT_JUMPING;

upCount = 0;

if (isStill) { // change to running image, but not looping yet

if (isFacingRight)

setImage("runningRight");

else // facing left

setImage("runningLeft");

}

}

57

Page 58: Chapter 12: Side Scroller - General Characteristics The ...djmoon/gaming/gaming-notes/side-scroller.pdf{ Several editors exit for creating tile maps (see resource page) 1. Mappy Fairly

Chapter 12: Side Scroller - JumperSprite Class (7)∗ Code:

private void updateFalling()

{

int yTrans = brickMan.checkBrickTop(xWorld+(getWidth()/2), yWorld+getHeight()+vertStep,

vertStep);

if (yTrans == 0) // hit the top of a brick

finishJumping();

else { // can move downwards another step

translate(0, yTrans);

yWorld += yTrans; // update position

}

}

58