Looking for part 2?
Gameplay
Now that our initial menu switches control to the Gameplay class (by putting an instance of it on the screen stack – [gameplay extends FullScreen]), we need to actually define it! Gameplay takes care of high level game functionality by multitasking actions via threads.
One thread, “Refresher”, takes care of creating new objects, looping through existing objects and telling them to process themselves, then checks for collision detection, adds points gained to total score, tells the graphics engine to redraw the screen, etc. It is basically the primary heartbeat loop that ensures everything keeps updating properly.
The other thread is the EnemyAI thread, which is responsible for looping through all the objects and telling enemies to run their intelligence processing, which sets their movement and firing.
Please note – this is one of the areas where the Blackberry emulator seemed to differ from the real thing. On the emulator, thread processing would simply halt if no key was being pressed, while on the Blackberry it would have no issues. While this might be a fault of mine, it doesn’t change the fact that the emulator acted very much differently than the real thing.
Lastly, gameplay has keychar and navigationMovement overridden to process when the Escape or Spacebar is pressed, as well as when the trackball is moved.
GamePlay.java
package com.synthdreams.GalacticBlast;
import net.rim.device.api.ui.UiApplication;
import net.rim.device.api.ui.container.FullScreen;
import net.rim.device.api.ui.Graphics;
import net.rim.device.api.system.Characters;
import net.rim.device.api.system.Application;
import java.lang.Thread;
import java.lang.Math;
import java.util.*;
// Gameplay extends FullScreen only, it doesn't need the functionality of the
// MainScreen object. Either are screens though, and can be pushed onto the
// screen stack
// Special Note: For those familiar with the concept double buffering, supposedly
// the rim UI automatically does this. It seems to from what I can see, I
// notice no tearing or graphic anomalies.
// For those unfamiliar, if you blit (write) graphic data directly primary graphic
// buffer, it is out of sync with the refresh of the screen. This can cause
// tearing or other weird graphic issues, as half of the screen is still being shown
// where the objects were, and the other half where the objects are now. This
// is solved by first drawing to a back buffer, and then flipping the entire
// back buffer with the front during the screen's refresh. In java there are
// specific game-oriented screen classes to do this, but supposedly the RIM
// libs do this automatically.
public class GamePlay extends FullScreen
{
// These three objects are called from many places and are made public
public static GFX gfx; // Static object for graphics control
public static SND snd; // Static object for sound control
public static Random rndGenerator; // Static object for random numbers
Refresher _refresher; // Thread that continually refreshes the game
EnemyAI _enemyai; // Thread when the enemies think
Vector _objects; // A vector of all objects currently in play
boolean _active; // Flag if our game is currently active or not (eg did we lose)
int _score; // Player's current score
// Getters for active and score
boolean getActive() { return _active; }
int getScore() {return _score; }
// Refresh is a type of thread that runs to refresh the game.
// Its job is to make sure all the processing is called for each object, update the background,
// update score, check for end of game, etc. This is the main heartbeat.
private class Refresher extends Thread
{
// When the object is created it starts itself as a thread
Refresher()
{
start();
}
// This method defines what this thread does every time it runs.
public void run()
{
// Temporary variable that stores the score value of all
// the objects that were just cleaned (destroyed/removed)
// return a negative number if we died
int cleanReturn;
// This thread runs while the game is active
while (_active)
{
// Level population/processing, responsible for creating new enemies
processLevel();
// Perform physics by calling each object's process command. Objects
// are unique and control their own physics (e.g. a photon blast can move
// faster than our hero ship), so we loop through all our objects
// and call the process method on each.
for (int lcv = 0 ; lcv < _objects.size() ; lcv++)
{
((OBJ) _objects.elementAt(lcv)).process();
}
// Collision detection of objects. If they collide, this method
// will call the damage method of each object, which might lead
// to no life for the object
OBJ.collisionDetect(_objects);
// Clean up stuff that's gone (e.g. life of 0 and in
// destroyable state, eg explosion graphic), quit if we were destroyed
cleanReturn = OBJ.cleanObjects(_objects);
// If we didn't die, add the score, redraw, etc.
if (cleanReturn >= 0)
{
_score += cleanReturn;
// Now that all our processing is done, tell the graphics engine to
// redraw the screen through a call to invalidate (invalidates the screen
// and automatically causes a redraw)
invalidate();
}
else
{
// if we died, mark active as false.
_active = false;
}
try
{
// Attempt to sleep for 50 ms
this.sleep(50);
}
catch (InterruptedException e)
{
// Do nothing if we couldn't sleep, we don't care about exactly perfect
// timing.
}
}
}
}
// We have a separate thread that takes care of enemy AI. This allows us to control how
// quickly the enemies think outside of our main refresh thread. That way we can make
// dumb enemies that think much slower than the action happening around them, or smart
// enemies that think as fast.
private class EnemyAI extends Thread
{
EnemyAI()
{
start();
}
public void run()
{
// Just make sure the thread doesn't accidentally run when the game is over
while (_active)
{
// Loop through all the objects and call the think method, which
// controls the actions of that object. Technically, this call's
// the hero's think method as well, but Hero doesn't have the
// think method overridden from the parent's, which is just blank,
// so the hero does no automatic thinking.
for (int lcv = 0 ; lcv < _objects.size() ; lcv++)
{
if (_active)
((OBJ) _objects.elementAt(lcv)).think(_objects);
}
try
{
// Enemies think 5 times a second
this.sleep(1000/5);
}
catch (InterruptedException e)
{
// Do nothing, again we don't care if timing isn't exact
}
}
}
}
public GamePlay()
{
snd = new SND(); // Create sound engine
gfx = new GFX(); // Create graphics engine
rndGenerator = new Random(); // Create random number generator
_active = true; // Mark the game as active
_score = 0; // Start with a score of 0
// Set our background to stars.png with a speed of 3 pixels per refresh
gfx.initBackground("stars.jpg", 3);
// Start our music playing. Something odd I noticed with my Blackberry 8830,
// which may either be a bug or some functionality I'm missing from the
// sound routines, is the volume starts out very quiet the first time you
// start playing. If you stop the music playing and play it again, the volume
// is fine. This might not be true for all Blackberrys, but it doesn't hurt
// anything to start, stop, and start again, so that's what we've done here,
// just to solve the volume bug/misundertsanding.
snd.playMusic("music.mid");
snd.stopMusic();
snd.playMusic("music.mid");
// Create a new vector to hold all the active objects (hero, enemies, photons, etc)
_objects = new Vector();
// Our hero will always be the very first object in the vector.
_objects.addElement(new Hero(Graphics.getScreenWidth() / 2, Graphics.getScreenHeight() - 50));
// Create the refresher and enemy AI last, we want to make sure all our objects are setup first
// so the threads don't make use of uninitialized objects
_refresher = new Refresher();
_enemyai = new EnemyAI();
}
// Process level is responsible for new computer generated events in the game. In our
// case, the only one really is creating new enemies.
public void processLevel()
{
OBJ tempObject;
// Throw a new enemy in every 25 pixels. This can be made more complex if desired
// of course
if (gfx.getBackPos() % 25 == 0)
{
// Create a new enemy drone with the X coordinate somewhere between the two edges of the
// screen
tempObject = new EnemyDrone(rndGenerator.nextInt() % Graphics.getScreenWidth(), 0);
// Set the Y coordinate to above the screen, so it comes in from the top
tempObject.setY(tempObject.getY() - tempObject.getBitmap().getHeight() + 3);
// Add the enemy to the object vector
_objects.addElement(tempObject);
}
}
// This method is called when the invalidate method is called from the refresh thread.
// We have it passing the graphics object over to our graphics engine so our
// custom graphics routines can take care of any drawing necessary.
protected void paint(Graphics graphics)
{
gfx.process(graphics, _objects, _score, ((Hero)_objects.elementAt(0)).getLives());
}
// The keyChar method is called by the event handler when a key is pressed.
public boolean keyChar(char key, int status, int time)
{
boolean retVal = false;
switch (key)
{
// If escape is pressed, we set the game to inactive (quit)
case Characters.ESCAPE:
_active = false;
retVal = true;
break;
// If the spacebar is pressed, we call the fire method of our
// hero object, causing him to fire a photon
case Characters.SPACE:
((Hero)_objects.elementAt(0)).fire(_objects);
retVal = true;
break;
default:
break;
}
return retVal;
}
// The navigationMovement method is called by the event handler when the trackball is used.
protected boolean navigationMovement(int dx, int dy, int status, int time)
{
// If the trackball was scrolled in the horizontal direction, we add that amount to
// our hero's X velocity
if (dx != 0)
((OBJ)_objects.elementAt(0)).setVelX(dx/Math.abs(dx)*5+((OBJ)_objects.elementAt(0)).getVelX());
// If the trackball was scrolled in the vertical direction, we add that amount to
// our hero's Y velocity
if (dy != 0)
((OBJ)_objects.elementAt(0)).setVelY(dy/Math.abs(dy)*5+((OBJ)_objects.elementAt(0)).getVelY());
return true;
}
}
Gameplay together with the OBJ set of classes is the heart of the logic of our game. Gameplay represents the manager, the high level control that directs actions, while the OBJ classes do the nitty gritty work of processing for each object (objects being things like the hero, the enemies, and photons). Now that we’ve seen Gameplay, lets take a look at OBJ (objects).
Onto the objects in part 4…
Hi Toni,
It is a great tutorial.
Would you kindly tell us where can we access the jpg and the music files for this game.
Many Thanks.
Regards,
Ibn
I have the same question as Ibn. Could you make the image and sound files available?
Very nice tutorial – thanks!!
S. G
Unfortunately, I can’t post the music as it’s copyrighted by Bjorn Lynne- I had to pay a fee to use it in my project. However, I highly suggest paying the fee if you would like it in your game, he does great work and has a number of midi files perfectly suited toward games at his site: http://www.lynnemusic.com/midi.html
The hero ship I also obtained from a site, and I believe it was GPL though I cannot remember for certain now, so use at your own risk
http://www.toniwestbrook.com/images/posts/herogame.png
The starfield I grabbed from a random google image search, any work pretty well.
Toni,
Very good complete, easy to follow, fun tutorial. I am a beginner in JAVA and BB development and have been looking around for weeks for a good UI tutorial like this. The others I have seen got me no where after I was done. But this tutorial touched on many aspects and it really gave me an overall sense of how to go about building an application. Many thanks. Again, Great work!
G.M.
Awesome, glad to hear it helped! I try to explain each section of code in my tutorials, as I know from reading other tutorials on the web that sometimes people assume you have certain knowledge that you might not – and then you’re lost for the rest of the tutorial. I figure no harm in just explaining everything I can, and people can ignore the bits they already know. Thanks again.
Hi, i need an immediate help. i need to move an image over the screen at random positions, just like a bee flying.. that is my task. am able to move the bee according to the directions keys of the keyboard. but i need to move without the key directions, i just start moving when i open that screen. i used timer control to call the move() for the image to move at random x, y positions. but it is not working well. so any help can be helpful
Hi Blanc –
Take a look at the EnemyDrone class in the OBJ file – it does exactly what you’re looking for already. Enemies with AI pattern 0 move with random velocities.
Hi, this tutorial is awesome, really well done, unlike a lot of other tutorials on the internet.
Im not that great with the BB development at the moment, i’ve just started to get into it after recently getting bb Strom. The problem i have is i was using this tutorial along with another open source app (MeBoy Gameboy emulator) because when i run MeBoy, it works on the storm, the problem is it doesn’t seem to pick up any key presses. I was using this tutorial as a reference but you used the track ball. Could you tell me the different codes i need in order to use different numeric keys? Or even point me into the right direction to find a tutorial.
any help would be awesome
Thanks for a really enjoyable and easy to follow tutorial you have something good going on here!
Brian.
Hi Toni:
Great tutorial, I followed it to create my own Spider Solitaire application, since there didn’t seem to be any that I really liked online that I could play without paying a fee.
One thing I noticed, however, is that the software runs fine in the simulator, but I notice that it’s quite slow when placed on an actual blackberry device. For example, the trackball movement from one card stack to another seems to lag behind the user by almost a second or more. I was wondering if you have any advice in efficiency from the simulator to the actual BB device.
Thanks,
Scott
Hi Brian – Sorry for the long delayed response, I’ve been away from the blog for too long. You can use the code above for using the keyboard for everything and leave the trackball out completely. Using the switch statement on line 237, you can pretty much process any incoming keypress. So you would move the code from the navigationMovement function to the keychar one.
Scott – usually it should happen the other way around, running very slowly on the emulator but quickly on the actual device. Slowdowns can happen in a few places – it’s important to look at:
1. How many threads you’re trying to run simultaneously. Threads are great for multitasking, but if you try to multitask past the ability of the processor, performance is going to suffer. You’ll either need to get fancy with when your threads are running, or move things out of threads into serial loops.
2. Check all your threads/loops for exactly how much code is being executed. If you’re trying to do more in a loop than is supported by your framerate, then everything is going to appear sluggish. The goal is to have processing done before the next frame starts.
3. Calls to the java mediaplayer take a while to buffer and start playing. If you notice the slow downs when a sound is being played, you have to do your best to start playing a sound when it won’t interrupt animation. This is a tricky dance.
4. Memory intensive functions – especially in loops (see #2) – make sure you’re not reloading bitmap data every frame, or something like that. This is another juggling act, depending on the Blackberry model you have, you don’t have a ton of memory to play with, so you have to choose between preloading as much as you can to make performance acceptable, but not loading so much that you exhaust your memory (which also causes performance issues).
Hope those help a bit to get you started.
Yes, these help greatly. Thanks for the suggestions, and for posting the tutorial!
-Scott
Thanks you. Very helpful.
thank u very much for giving us such a helpful tutorial.
gfx.initBackground(“stars.jpg”, 3);
Hi Toni,
It is a very good tutorial and I thank you so much for sharing this with us, especially for people like me who are still in early learning curve in blackberry/JAVA development.
I’ve come accross this problem where my simulator (i guess) could not recognise the png or jpg file I put from a code like this:
gfx.initBackground(“stars.jpg”, 3);
I am wondering where exactly should we put the png/jpg file in a directory, or should we load it with the IDE (I use Eclipse). Should we also put complete path name in the code above?
Appreciate any help.
Many thanks,
Adi
Hi Adi – thanks for the kind words! Glad its helped out a bit. If you take a look at the initBackground method of gfx (GFX class), you’ll see it makes a call to:
Bitmap.getBitmapResource
Anytime you see a getResource kind of method (Bitmaps, InputStreams, etc), it’s going to be looking within your COD file for the resource in question. To put it into your COD file, you want to add it to your project in Eclipse. Then when you compile your source, it will add your resource into the final product (COD file).
You can put path information into any getResource, but the root of the search is the base of the package. So for instance, my Eclipse project is setup as:
GalacticBlastDemo
–src
—-com.synthdreams
——stars.jpg
Which would match a search for “stars.jpg” without any additional path info, akin to how it’s setup in the code above. Hope that helps – let me know if any of that didn’t make sense
Toni,
Success!! I put the png files in ‘src’ folder and it works! I’ve also noticed that ‘png’ and ‘PNG’ matters ๐
Thanks a lot!
Sir
Thank u for giving such a valuable information i need images and audio files for this game could u please send them to my mail jayachandra.06ie9@gmail.com
the best i have ever seen in recent months… buddy, you rocks! It is an incredible tutorial. You don’t have idea how this post helps to beginner developers… you rocks!!!
@jacsdev
I’am not found file PNJ in project.please give me all file .PNJ.
thanks so much
Hi Mash –
If you take a look above, I link to one of the png files I used for the spaceship as an example. However, you’ll want to grab some others (e.g. from Google Images) or make your own, whatever works best for the game you’re making.