410 likes | 580 Views
Cosc 4755. . lcdui.game package. Game package. Introduced in MIDP 2.0 Provides 5 classes to help develop games GameCanvas , which extends Canvas Layer Super class for sprite and TiledLayer Abstract class to provide visual element in a game Sprite
E N D
Cosc 4755 .lcdui.game package
Game package • Introduced in MIDP 2.0 • Provides 5 classes to help develop games • GameCanvas, which extends Canvas • Layer • Super class forsprite and TiledLayer • Abstract class to provide visual element in a game • Sprite • An Image or collection of images for animation • TiledLayer • Used to great an image from a few smaller images • LayerManager • Used to manage multiple layers and render each layer in the correct order.
A short game design primer. • A game or animation is built on an animation loop. • Instance variables of “objects” are updated • User interaction may change “objects” • Depends on game state (Is user playing, demo mode, etc). • Code then draws/paints/repaints the game screen • Code loop sleeps for a short period of time, which controls the game rate.
Basic outline of a game • MIDlet, which declares an object. • It’s an extension of GameCanvas • And implements Runnable • A thread. • Likely has an exit command too. public class GameMIDlet extends MIDlet { MyGameCanvasgCanvas; Display display; public GameMIDlet() { gCanvas = new MyGameCanvas(); gCanvas.addCommand(new Command("Exit", Command.EXIT, 0)); gCanvas.setCommandListener(new CommandListener() { public void commandAction(Command c, Displayable s) { gCanvas.stop(); notifyDestroyed(); } }); } public void startApp() { display = Display.getDisplay(this); gCanvas.start(); display.setCurrent(gCanvas); } public void pauseApp() {} public void destroyApp(boolean unconditional) { gCanvas.playgame = false; //end Thread. } }
GameCanvas class • This is the part of the code responsible for the “animation loop” • Including get user input class MyGameCanvas extends GameCanvas implements Runnable{ public booleanplaygame; private myColor Color = new myColor(); public MyGameCanvas() { super(true); //required by GameCanvas } public void run() { Graphics g = getGraphics(); while (playgame) { // Verify game state, “move” objects, etc… checkGameState(); // get user input checkUserInput(); // update screen render(g); //control refresh rate try { Thread.sleep(30); //sleep 30 Milliseconds } catch (Exception e) {} } }
GameCanvas class (2) • Methods to do the work. public void render(Graphics g) { //draw in off screen buffer //draw white background g.setColor(Color.WHITE); g.fillRect(0, 0, getWidth(), getHeight()); //this call paints off screen buffer to the screen flushGraphics(); } public void start() { //initialization playgame = true; // start animation loop thread Thread t = new Thread(this); t.start(); //calls run method } public void stop() { playgame = false; } public void checkGameState(){ } public void checkUserInput() { } }
GameCanvas Detail • GameCanvas is subclass of Canvas • So all those methods are available • Provides double buffering to produce smooth transitions of objects moving on the screen. • We don’t write a paint method, instead we create a “render” or updateScreen method to draw on the off screen buffer. • flushGraphics() method moves the off screen buffer to the screen buffer • Which is why we don’t create a paint method.
GameCanvas Detail (2) • Provides a method to get user input • getKeyStates() • Returns a bitwise variable of all game keys pressed since it was last called. • Based on keys defined by Canvas • 1 pressed, 0 not pressed. • DOWN_PRESSED, UP_PRESSED, RIGHT_PRESSED, LEFT_PRESSED, FIRE_PRESSED, GAME_A_PRESSED, GAME_B_PRESSED, GAME_C_PRESSED, and GAME_D_PRESSED
getKeysStats() example intkeyState = getKeyStates(); if ((keyState & LEFT_PRESSED) != 0) { //left key pressed } else if ((keyState & RIGHT_PRESSED) != 0) { // right key pressed } • User pressed two keys at once? if ( ( (keyState & LEFT_PRESSED) !=0) && ((keyState & UP_PRESSED) !=0) ) { //User pressed both and left and up key. }
Starting our game. • Add game constants and draw the board private myShip ship; //game images Image iShip, iShot, iAlien; //Game characteristics constants public static final int GAME_WIDTH = 192; public static final int GAME_HEIGHT = 192; //center the game on the screen. public final int GAME_ORIGIN_X = (getWidth() - GAME_WIDTH)/2; public final int GAME_ORIGIN_Y = (getHeight() - GAME_HEIGHT)/2; public final int WATER_BOUNDARY_LEFT = GAME_ORIGIN_X + 32; public final int WATER_BOUNDARY_RIGHT = GAME_ORIGIN_X + GAME_WIDTH - 32; public void render(Graphics g) { //draw in off screen buffer //draw white background g.setColor(Color.WHITE); g.fillRect(0, 0, getWidth(), getHeight()); g.setColor(Color.BLACK); drawBackground(g); g.drawImage(iShip, ship.x, ship.y, 0); //this call paints off screen buffer to the screen flushGraphics(); } public void drawBackground(Graphics g){ g.drawRect(GAME_ORIGIN_X, GAME_ORIGIN_Y, GAME_WIDTH, GAME_HEIGHT); g.drawLine(WATER_BOUNDARY_LEFT, GAME_ORIGIN_Y, WATER_BOUNDARY_LEFT, GAME_ORIGIN_Y+GAME_HEIGHT); g.drawLine(WATER_BOUNDARY_RIGHT, GAME_ORIGIN_Y, WATER_BOUNDARY_RIGHT, GAME_ORIGIN_Y+GAME_HEIGHT); }
Starting our game (2) • User movement public void checkUserInput() { // get the state of keys intkeyState = getKeyStates(); // determines which way to move and changes if((keyState & LEFT_PRESSED) != 0) { ship.left(); } else if((keyState & RIGHT_PRESSED) != 0) { ship.right(); } else if((keyState & UP_PRESSED) != 0) { ship.up(); } else if((keyState & DOWN_PRESSED) != 0) { ship.down(); } } • For the moment we ignore the ship class and come back to it later. • First though, we need some color on this board.
Backgrounds with TiledLayer • TiledLayer always us to create backgrounds with small images, then tell it how to place them. • We can divide the board in 32x32 pixels, or 6x6 cells for 192x192 pixels. • And use only 2 small pictures. • Column 0 and 5 as first 32x32 • The middle 4 columns as the second 32x32 part.
Code for TiledLayer public void createBackground() throws IOException { //get the background image backGroundimg = Image.createImage("/background1.png"); //build the tiledlayer object backGround = new TiledLayer(6,6,backGroundimg,32,32); // array that specifies what image goes where int[] cells = { 1, 2, 2, 2, 2, 1, //cliff, water, water, water, water, cliff 1, 2, 2, 2, 2, 1, //cliff, water, water, water, water, cliff 1, 2, 2, 2, 2, 1, //cliff, water, water, water, water, cliff 1, 2, 2, 2, 2, 1, //cliff, water, water, water, water, cliff 1, 2, 2, 2, 2, 1, //cliff, water, water, water, water, cliff 1, 2, 2, 2, 2, 1 //cliff, water, water, water, water, cliff }; // set the background with the images for (inti = 0; i < cells.length; i++) { int column = i % 6; int row = (i - column)/6; backGround.setCell(column, row, cells[i]); } // set the location of the background backGround.setPosition(GAME_ORIGIN_X, GAME_ORIGIN_Y); }
Code for TiledLayer (2) • Add code to Start() public void start() { //initialization playgame = true; try { createBackground(); // start animation thread Thread t = new Thread(this); t.start(); } catch (IOException ex) { ex.printStackTrace(); } } • Add to drawBrackGround method public void drawBackground(Graphics g){ g.drawRect(GAME_ORIGIN_X, GAME_ORIGIN_Y, GAME_WIDTH, GAME_HEIGHT); g.drawLine(WATER_BOUNDARY_LEFT, GAME_ORIGIN_Y, WATER_BOUNDARY_LEFT, GAME_ORIGIN_Y+GAME_HEIGHT); g.drawLine(WATER_BOUNDARY_RIGHT, GAME_ORIGIN_Y, WATER_BOUNDARY_RIGHT, GAME_ORIGIN_Y+GAME_HEIGHT); //And paint in the tilelayer. backGround.paint(g); } • Except we do this easier with the layout manager class.
LayoutManager • Both Sprite and TiledLayer classes extend the Layer • Instead of having to individual paint each layer, we add Sprites and TiledLayer to a LayoutManager in the correct order and then have it render the image for us. • We can add and remove items from a Layout manager, as well as access the items in the manager.
LayoutManager • “Correct Order”? • Think of the layoutManager like a queue of items • Append(X), append(Y), then X will be draw “on top” of Y. • We can always append with an index, Where 0 is head of the queue. • Append(y), append(x,0). X will still be draw on of Y • Append(y), append(x,0), append(z,0), Draw z on top of x, which is drawn on top of y. • You want a background layer to be last!
Sprite class • The Sprite class allows to number things • First it takes an Image and size of the image in the constructor • It then allows us to add us to add it to a Layer • Provides • CollidesWith methods • SetTransform method ( Mirror and rotate 90, 180, 270 degrees) • Animation, similar tiledLayerobjects.
Change the code • Add the layermanagerand change ship to sprite. • We can now remove drawbackground() method public MyGameCanvas() { super(true); //required by GameCanvas //suppresses normal key events, for "key pressed, key released, key repeated" try { iShip = Image.createImage("/ship.png"); sShip = new Sprite(iShip,iShip.getWidth(),iShip.getHeight()); //iShot = Image.createImage("/shot.png"); } catch (IOExceptionio) { System.err.println(io.toString());} } public void start() { //initialization playgame = true; try { //create a layer manager manager = new LayerManager(); //and ship to it manager.append(sShip); //create and add the backgroud createBackground(); manager.append(backGround); // start animation thread Thread t = new Thread(this); t.start(); } catch (IOException ex) { ex.printStackTrace(); } }
Change the code • Render function is very simple now. public void render(Graphics g) { //draw in off screen buffer //draw white background g.setColor(Color.WHITE); g.fillRect(0, 0, getWidth(), getHeight()); g.setColor(Color.BLACK); manager.paint(g, 0, 0); //draw all the layers. //this call paints off screen buffer to the screen flushGraphics(); }
Extending the sprite class • Since we are going to have the layermanager kept track of the aliens and shots, we going to make things a little simpler and extend the sprite class with info we need. • To extend the class, we’ll need to deal with the constructor we use. • Sprite(Image image, intframeWidth, intframeHeight) • Very useful for handling the aliens and firing.
MyShip sprite class class myShip extends Sprite { intimageHeight, imageWidth; intboardx, boardy, boardh, boardw; myShip(Image i, intfw, intfh, intmx, int my, intbx, int by, intbw, intbh){ super(i,fw,fh); //call the sprite constructor //now the rest of our stuff setPosition(mx, my); imageHeight = i.getHeight(); imageWidth = i.getWidth(); boardx = bx; boardy = by; boardw = bw; boardh = bh; } } • Kept the ship from outside the borders. void left() { int x = Math.max(getX() -1, boardx); setPosition(x, getY()); } void right() { int x = Math.min(getX() +1, boardw - imageWidth); setPosition(x, getY()); } void up() { int y = Math.max(getY() -1,boardy); setPosition(getX(), y); } void down() { int y = Math.min(getY() +1, boardh - imageHeight); setPosition(getX(), y); }
Adding aliens • First create an alien and shot class that extends the sprite class • When new aliens or firing happens we add it to the layer manager. • Both aliens and shot class have information about how the move • The checkGameState() function cycles through all the objects in the layout manager, telling them to move and then checks for collisions. • It will also decide when to create more aliens
CheckGameState() if (numAliens ==0) { int x = myRandom.nextInt(WATER_BOUNDARY_RIGHT - WATER_BOUNDARY_LEFT - iAlien.getWidth()); manager.insert(new myAlien(iAlien,iAlien.getWidth()/2,iAlien.getHeight(),WATER_BOUNDARY_LEFT + x, GAME_ORIGIN_Y, this),0); numAliens++; } else if (numAliens<maxAliens) { if (myRandom.nextInt(100)>97) { int x = myRandom.nextInt(WATER_BOUNDARY_RIGHT - WATER_BOUNDARY_LEFT - iAlien.getWidth()); manager.insert(new myAlien(iAlien,iAlien.getWidth()/2,iAlien.getHeight(),WATER_BOUNDARY_LEFT + x, GAME_ORIGIN_Y, this),0); numAliens++; } } //update all movement. for(inti=0; i<(manager.getSize() -2); i++) { if (manager.getLayerAt(i) instanceofmyAlien ) { ma = (myAlien) manager.getLayerAt(i); if (ma.moveme()) {//if moved off the board. manager.remove(ma); numAliens--; } } else if (manager.getLayerAt(i) instanceofmyShot) { ms = (myShot) manager.getLayerAt(i); if (ms.moveme()) { //moved off the board. manager.remove(ms); numShots--; } } }
and finally shooting • In checkUserInput() • Look for FIRE_PRESSED • When pressed, generate a “shot” and add it to the layer manager. • Layer manager now controls the shot, just like an alien class. • Hopefully we hit an alien before they collide with our ship.
Firing code • In checkUserInput() add } else if ((keyState & FIRE_PRESSED) != 0){ manager.insert(new myShot(iShot,iShot.getWidth()/3,iShot.getHeight(),ship.getX()+4, ship.getY()-iShot.getHeight(), this),0); numShots ++; }
Shot class class myShot extends Sprite { intimageHeight, imageWidth; intboardx, boardy, boardh, boardw; MyGameCanvas parent; myShot(Image i, intfw, intfh, intmx, int my, MyGameCanvasmyParent) { super(i,fw,fh); parent = myParent; setPosition(mx, my); imageHeight = i.getHeight(); imageWidth = i.getWidth(); boardx = parent.WATER_BOUNDARY_LEFT; boardy = parent.GAME_ORIGIN_Y; boardw = parent.WATER_BOUNDARY_RIGHT; boardh = parent.GAME_ORIGIN_Y + parent.GAME_HEIGHT; } booleanmoveme() { nextFrame(); int y = getY() -1; if (y < boardy) { //leave the screen return true; } setPosition (getX(),y); return false; } }
Collisions • Sprite has collidesWidth() method. • Loop through and figure out if things have collided with each other. • CollidesWidth(Sprite S, booleanpixellevel) • Pixellevel true • By pixel if two non opaque pixels collide • false • By simple bounds checking • Collisions with the ship: for(inti=0; i<(manager.getSize() -2); i++) { if (ship.collidesWith((Sprite)manager.getLayerAt(i), true)) { manager.remove(manager.getLayerAt(i)); manager.remove(ship); gameover =true; break; } }
The rest of the collisions for(inti=0; i<(manager.getSize() -2); i++) { //note last two items are the ship and background, don’t test them. obj = (Sprite) manager.getLayerAt(i); for(int j=i+1; j<(manager.getSize()-2); j++) { if (obj.collidesWith((Sprite)manager.getLayerAt(j), true)) { if (objinstanceofmyAlien) { //polymorphism, figure out if alien or shot numAliens--; } else { numShots--; } if (manager.getLayerAt(j) instanceofmyAlien){ numAliens--; } else { numShots--; } score += 1; manager.remove(manager.getLayerAt(j)); manager.remove(obj); break; } } }
Scoring • Every game should have a score. • Add a new variable to called score. • Add to it every time there is a collision • Two aliens colliding only counts once • Add to render g.drawString("Score: "+score, GAME_ORIGIN_X,GAME_ORIGIN_Y-g.getFont().getHeight(),0);
Game Over! • Again, add a gameoverboolean variable • Add to render if(gameover) { g.drawString("GAME OVER", GAME_ORIGIN_X + GAME_WIDTH/2, GAME_ORIGIN_Y + GAME_HEIGHT/2, g.TOP|g.HCENTER); } • Add an if statement to animation loop if (!gameover) { // Verify game state, "move" objects, etc... checkGameState(); // get user input checkUserInput(); // update screen }
For lastly. • Of course, while this is a “complete” game, but • The aliens should be able to shot at the ship. • The aliens tend to collide with each other. • And randomly head down the screen. • More lives: • add a variable and in the collision code, check to see if the ship still has lived should continue. • But it was intended to show you all how to use the gameCanvas. • For the complete source code, check the example page.
Blackberry notes • “Game” worked as is with blackberries with keyboard. • Left is A key, right is the S key, R is up, F is down and Fire is space on the Tour, but varied between Blackberries
Blackberry notes (2) • With the Storm… • No keyboard • Need to work with touchevents • virtual key worked though, but had to move the gamescreen up.
Storm & Storm 2 • To work with TouchEvents, • Use net.rim.device.api.lcdui.game, which inherits game. Declare as BlackBerryGameCanvas and use the touchEvent() method. • Valid Events • DOWN - Finger touch the screen • UP - Finger(s) lifted from screen • MOVE Finger drag or slide on screen • CANCEL - Overriding system event, interrupting touch sequence • GESTURE - Gesture detected, there is a method called getGesture() cite above • UNCLICK - Finger releases from depressed screen until feedback is felt • CLICK - Finger presses screen until feedback is felt
Storm & Storm 2 (2) • But touch to move is not great. • To show the full power of this battle station…. • Accelerometer Sensor! • net.rim.device.api.system.AccelerometerSensor.* • Use click TouchEvent for firing and accelerometer to move. • A Note: Blackberry MIDLet and application are incompatible in many ways. So if this was rewritten to be a application, you would not be able to use the (BlackBerry)GameCanvas, since it won’t display. • MIDlets can call many of the applications functions though.
Accelerometer Sensor • With applications, implement an interface • With MIDlet, call it directly. import net.rim.device.api.system.AccelerometerSensor.*; import net.rim.device.api.system.Application; private short[] xyz = new short[ 3 ]; //for accelerometer data private Channel accChannel; //for the accelerometer channel //open accelerometer channel accChannel = AccelerometerSensor.openRawDataChannel( Application.getApplication() ); //read the data accChannel.getLastAccelerationData( xyz ); //close the channel. accChannel.close();
Accelerometer Sensor (2) • X axis runs from east (negative values) to west (positive values); • Y axis runs from north (negative values) to south (positive values); and • Z axis runs from the front of the BlackBerry device (negative values) to the back of the device (positive values). //left right first. if (xyz[0] > 0) { ship.left(); } else if(xyz[0] <0) { ship.right(); } //up and down if(xyz[1] > 600) { //allow for about a 45% tilt ship.down(); } else if(xyz[1] <400) { ship.up(); } See AccelerometerSensor API for more Information.
Storm Notes. • Screen flipping between portrait and landscape is a problem. • To stop it, need to use setAcceptableDirections( int), but it’s a “privileged” command and you need a Blackberry key to sign the app in order for it to work.
Andorid? • We'll leave this to another lecture.
References • Java Docs for RIM api, jsr118 api’s • Useful tutorials • http://www.java.net/author/vikram-goyal • So useful information: • Math & Physics http://www.gamedev.net/reference/list.asp?categoryid=28 • Essential Math for Games Programmers • http://www.essentialmath.com/ • 2D Game Physics 101 • http://www.rodedev.com/tutorials/gamephysics/
Q A &