Creating a Blackberry Game - Part 5
Let's See Our Game
Now that our logic is done, we have a few holes in the program where calls are made to graphics and sound processing. By isolating (for the most part, as much as our program allows) graphics and sound processing from the rest of the program, it offers for a little more portability. In general, this isn't too big of an issue in Java since Java itself is cross platform, but even so we run into areas where certain libraries might be available for one setup and not for others (for example, our program in which we use Blackberry specific libraries, wouldn't be valid in a non-Blackberry environment). For systems where processor speed and memory are in ample supply (which are most systems these days - as compared to an MCU or retro environment when you're dealing with 64K of RAM), it's a good practice to isolate things out like this - it makes porting a lot easier (Think Windows -> Mac & Linux, or PS3 -> XBox & Wii, that kind of thing).
Anyway, game programming philosophy aside, let's dig into the graphics functionality of GFX. GFX is actually fairly simple since most of the low level graphic processing is done by the Blackberry itself. We don't have a lot of ways to get close to the hardware, which is a good and bad thing - but for us here, the built in graphics functionality of the Blackberry is good enough and saves us work.
Besides some initialization routines, the GFX class consists mainly of the process method, which takes a handle to the graphics of the current screen from Gameplay. This handle indirectly allows us to write to the screen memory through simple graphic methods like drawBitmap and drawText. The overall algorithm is to first write the background (aligned properly, it moves every update) to the screen, then draw all objects, then the score and our hero's health meter. There is some math for doing coordinate and dimension calculations, but everything is fairly straightforward.
GFX.java
package com.synthdreams.GalacticBlast; import net.rim.device.api.system.Bitmap; import net.rim.device.api.system.Display; import net.rim.device.api.ui.*; import java.util.Vector; // The GFX class is our game's graphics engine with all the drawing routines class GFX { int _backPos, _backSpeed; // Background position and speed Bitmap _backgroundBM; // The bitmap for the background Bitmap _healthBM; // The bitmap for the health meter Font _gameFont; // The font used for drawing score and lives GFX() { // First see if we can get the BBCondensed font at size 16 for our game font. // If not, we just go with the default, but all Blackberrys should have this font. try { _gameFont = FontFamily.forName("BBCondensed").getFont(FontFamily.SCALABLE_FONT,16); } catch (Exception e) { } // Load the health bitmap from health.png _healthBM = Bitmap.getBitmapResource("health.png"); } // Get current background position public int getBackPos() { return _backPos; } // Method that sets the bitmap for the background and sets the scroll speed public void initBackground(String passBackground, int passSpeed) { _backgroundBM = Bitmap.getBitmapResource(passBackground); _backPos = 0; _backSpeed = passSpeed; } // Primary function for the graphics engine, draws all the objects, text, health, etc public void process(Graphics passGraphics, Vector passObjects, int passScore, int passLives) { // Draw our background at the correct position passGraphics.drawBitmap(0,0, Graphics.getScreenWidth(), Graphics.getScreenHeight(), _backgroundBM, 0, _backPos); // Move our background. _backPos -= _backSpeed; //If we're at the beginning of our bitmap, reset to end if (_backPos < 0) { _backPos = _backgroundBM.getHeight() - _backSpeed; } // If we're within screenheight of the end of our bitmap, we need to draw another copy to fill in the blank if (_backgroundBM.getHeight() - _backPos < Graphics.getScreenHeight()+_backSpeed) { passGraphics.drawBitmap(0, _backgroundBM.getHeight() - _backPos - _backSpeed, Graphics.getScreenWidth(), Graphics.getScreenHeight(), _backgroundBM, 0, 0); } // Now draw each of our objects to the screen for (int lcv = 0 ; lcv < passObjects.size() ; lcv++) { passGraphics.drawBitmap(((OBJ) passObjects.elementAt(lcv)).getX(), ((OBJ) passObjects.elementAt(lcv)).getY(), ((OBJ) passObjects.elementAt(lcv)).getBitmap().getWidth(), ((OBJ) passObjects.elementAt(lcv)).getBitmap().getHeight(), ((OBJ) passObjects.elementAt(lcv)).getBitmap(), 0, 0); } // Draw score String zeroPad; // We want to pad the score with 0s zeroPad = ""; if (passScore < 10000) zeroPad += "0"; if (passScore < 1000) zeroPad += "0"; if (passScore < 100) zeroPad += "0"; if (passScore < 10) zeroPad += "0"; // Draw score and lives in white with the game font passGraphics.setColor(0xFFFFFF); passGraphics.setFont(_gameFont); passGraphics.drawText("Score: " + zeroPad + passScore, Graphics.getScreenWidth()-93, 2); passGraphics.drawText("Lives: " + passLives, 7, Graphics.getScreenHeight() - 20); //Draw health, with width dependent on hero's life if (((OBJ) passObjects.elementAt(0)).getLife() > 0) passGraphics.drawBitmap(7, 7, _healthBM.getWidth() * ((OBJ) passObjects.elementAt(0)).getLife() / 5, _healthBM.getHeight(), _healthBM, 0, 0); } }
As can be seen, nothing major happening here. The only weird part is how we draw the background. We basically have a long bitmap that scrolls in the background. When it hits the end of the bitmap, it needs to repeat it. It does this by continuing to scroll the original bitmap, and then filling in the whitespace with a second bitmap, placed flush to the first bitmap. Since we're dealing with random stars, its hard to make out any seams on the screen. If you were doing something with a more ordered/textured background, you'd need to make sure the top of the bitmap flowed well with the bottom of the bitmap.
Graphics are done - you should be able to disable your audio calls and get a playable game at this point! It should look something like this (only with your graphics, of course)

Articles and Posts