No Commands: Delicious Graphical Menus

Aesthetics plays an important part in the games that you make. Making the main menu look like it fits the game is equally as important. If done right, it can set the mood for the player even before actually playing the game.

Let's face it. Using Command Listeners and objects for your menus just doesn't feel right. Command objects just don't look like they're part of the game. On some phones, the game is hidden when the menu is activated. Phones don't even display the label for the keys you have to press to activate the menu when the game is in fullscreen mode. Graphical menus solves these problems and makes the game look cool too.

Clipping is used extensively in this tutorial. If you need a quick study, turn to this tutorial : Clipping Images or Displaying Only Parts of an Image.


Loading the Images
In this tutorial we will make a simple vertical menu in the middle of the canvas. The MIDlet will be designed for mobile phones with 176x208 screen resolution.


Sample Vertical Menu


I have prepared a clean project for you to start with. It includes the basic game template and the input handling code from the tutorial Input Handling : Keypress with Repeat Rate. It also includes the images we're going to use for the menu. Download the one for the version of NetBeans you will use:


When you open the project in NetBeans, you should see these 2 files inside the images folder:

logo.png - 176x208 pixels

logo.png


menuitems.png - 82x80 pixels

menuitems.png



First we'll have to load the images. So open the clsCanvas code and add these variable declarations above the constructor:

private midMain fParent;

private Image imgBG;
private Image imgMenu;

public clsCanvas(midMain m) {


Add the actual image loading code in the load() method. Make sure they are inside the try..catch or exception handling code:

public void load(){
try{
// load the images here
imgBG = Image.createImage("/images/logo.png");
imgMenu = Image.createImage("/images/menuitems.png");

}catch(Exception ex){

Add these lines inside the unload() method to make sure the resources reserved for those objects will get freed:


public void unload(){
// make sure the object get's destroyed
imgMenu = null;
imgBG = null;

}



The logo can now replace the call to fillRect() that we used to clear the screen since the logo takes up the whole screen. Remove or comment the lines from the main loop as indicated below:


//restore the clipping rectangle to full screen
g.setClip(0, 0, getWidth(), getHeight());

/* start - delete lines
//set drawing color to black
g.setColor(0x000000);
//fill the whole screen
g.fillRect(0, 0, getWidth(), getHeight());
*/ end - delete lines

// set drawing color to white
g.setColor(0xffffff);


Now we can draw the logo. Insert this code under the call to setClip() inside the main loop :


//restore the clipping rectangle to full screen
g.setClip(0, 0, getWidth(), getHeight());

g.drawImage(imgBG, 0, 0, Graphics.TOP | Graphics.LEFT);





Drawing the Menu
We'll need a variable to store the currently focused menu item. Let's call it menuIndex and declare it under the other variables we declared earlier:


private Image imgMenu;

private int menuIndex = 0;

public clsCanvas(midMain m) {



The drawing code for the menu will be placed on a new method name drawMenu(). Add this code above the run() method:


public void drawMenu(Graphics g){
int cy = 0;
for (int i = 0; i < 5; i++){
//compute the Y position of the menu item
cy = 64 + (i * 22);
//set the clipping rectangle to where the item will be drawn
g.setClip(47, cy, 82, 20);
if (menuIndex == i){
//draw the light button if the item is focused
g.drawImage(imgMenu, 47, cy - 20, Graphics.TOP | Graphics.LEFT);
} else {
//draw the dark button if the item is not focused
g.drawImage(imgMenu, 47, cy, Graphics.TOP | Graphics.LEFT);
}
//offset of the label is 6 pixels from the top of the button
cy += 6;
//set the clipping rectangle to where the label will be drawn
g.setClip(47, cy, 82, 8);
//draw the label so that it is inside the clipping rectangle
g.drawImage(imgMenu, 47, cy - (40 + (i * 8)), Graphics.TOP | Graphics.LEFT);
}
}

public void run() {



The menu will be drawn 64 pixels from the top of the screen and the menu items are drawn at 22 pixel intervals and since each of the menu items is only 20 pixels in height, a 2 pixel space will be left between the menu items acting as a margin. Depending on the value of menuIndex, either a light-blue or a dark-blue button will be drawn. Finally, the label of each menu item is drawn 6 pixels lower than the menu items position.

To show the menu on the screen, simply insert the following lines of code at the point after which the logo has been drawn:


//restore the clipping rectangle to full screen
g.setClip(0, 0, getWidth(), getHeight());
//draw the logo
g.drawImage(imgBG, 0, 0, Graphics.TOP | Graphics.LEFT);

//draw the menu
drawMenu(g);
//restore the clipping rectangle to full screen again
g.setClip(0, 0, getWidth(), getHeight());

// set drawing color to white



Notice that we now have 2 calls to setClip() where the clipping rectangle is reset to fullscreen. The second call is necessary because when the drawMenu() is finished, the method would have resized and positioned the clipping rectangle to that of the last label in the menu. Reseting the clipping rectangle after drawMenu() is called allows us to draw more stuff in other parts of the screen.

The only thing left to do now is to make the menu respond to keypresses. Modify the conditional statement under the call to the checkKeys() method inside the main loop like so:


checkKeys(iKey, lCurrTick);

if (isDown[upKey]){
//move focus up
if (menuIndex > 0){
menuIndex--;
} else {
menuIndex = 4;
}
} else if (isDown[downKey]){
//move focus down
if (menuIndex < 4){
menuIndex++;
} else {
menuIndex = 0;
}
} else if (isDown[fireKey]){
//do action depending on the menu item selected
if (menuIndex == 4){
isRunning = false;
}
}



The last code allows us to move the highlight or focus from one menu item to the other. Pressing the UP key should now move the focus one menu item higher in the list. Pressing the DOWN key should move the focus one menu item lower in the list. Pressing the FIRE key should select the focused menu item and initiate an "action". So far, there is only one "action" defined in this menu and that is for exiting the MIDlet from the EXIT menu item.

Here's the completed clsCanvas code so you can check if you missed something:


package MyGame;

import javax.microedition.lcdui.Graphics;
import javax.microedition.lcdui.Image;
import javax.microedition.lcdui.game.GameCanvas;

public class clsCanvas extends GameCanvas implements Runnable {
// key repeat rate in milliseconds
public static final int keyDelay = 250;

//key constants
public static final int upKey = 0;
public static final int leftKey = 1;
public static final int downKey = 2;
public static final int rightKey = 3;
public static final int fireKey = 4;

//key states for up, left, down, right, and fire key
private boolean[] isDown = {
false, false, false, false, false
};
//last time the key changed state
private long[] keyTick = {
0, 0, 0, 0, 0
};
//lookup table for key constants :P
private int[] keyValue = {
GameCanvas.UP_PRESSED, GameCanvas.LEFT_PRESSED,
GameCanvas.DOWN_PRESSED, GameCanvas.RIGHT_PRESSED,
GameCanvas.FIRE_PRESSED
};

private boolean isRunning = true;
private Graphics g;
private midMain fParent;

private Image imgBG;
private Image imgMenu;
//stores the focused menu item
private int menuIndex = 0;

public clsCanvas(midMain m) {
super(true);
fParent = m;
setFullScreenMode(true);
}

public void start(){
Thread runner = new Thread(this);
runner.start();
}

public void load(){
try{
// load the images here
imgBG = Image.createImage("/images/logo.png");
imgMenu = Image.createImage("/images/menuitems.png");

}catch(Exception ex){
// exit the app if it fails to load the image
isRunning = false;
return;
}
}

public void unload(){
// make sure the object get's destroyed
imgMenu = null;
imgBG = null;
}

public void checkKeys(int iKey, long currTick){
long elapsedTick = 0;
//loop through the keys
for (int i = 0; i < 5; i++){
// by default, key not pressed by user
isDown[i] = false;
// is user pressing the key
if ((iKey & keyValue[i]) != 0){
elapsedTick = currTick - keyTick[i];
//is it time to toggle key state?
if (elapsedTick >= keyDelay){
// save the current time
keyTick[i] = currTick;
// toggle the state to down or pressed
isDown[i] = true;
}
}
}
}

public void drawMenu(Graphics g){
int cy = 0;
for (int i = 0; i < 5; i++){
//compute the Y position of the menu item
cy = 64 + (i * 22);
//set the clipping rectangle to where the item will be drawn
g.setClip(47, cy, 82, 20);
if (menuIndex == i){
//draw the light button if the item is selected
g.drawImage(imgMenu, 47, cy - 20, Graphics.TOP | Graphics.LEFT);
} else {
//draw the dark button if the item is not selected
g.drawImage(imgMenu, 47, cy, Graphics.TOP | Graphics.LEFT);
}
//offset of the label is 6 pixels from the top of the button
cy += 6;
//set the clipping rectangle to where the label will be drawn
g.setClip(47, cy, 82, 8);
//draw the label so that it is inside the clipping rectangle
g.drawImage(imgMenu, 47, cy - (40 + (i * 8)), Graphics.TOP | Graphics.LEFT);
}
}

public void run() {
int iKey = 0;
long lCurrTick = 0; // current system time in milliseconds;

load();
g = getGraphics();
while(isRunning){

lCurrTick = System.currentTimeMillis();
iKey = getKeyStates();

checkKeys(iKey, lCurrTick);

if (isDown[upKey]){
//move focus up
if (menuIndex > 0){
menuIndex--;
} else {
menuIndex = 4;
}
} else if (isDown[downKey]){
//move focus down
if (menuIndex < 4){
menuIndex++;
} else {
menuIndex = 0;
}
} else if (isDown[fireKey]){
//do action depending on the menu item selected
if (menuIndex == 4){
isRunning = false;
}
}

//restore the clipping rectangle to full screen
g.setClip(0, 0, getWidth(), getHeight());

//draw the logo
g.drawImage(imgBG, 0, 0, Graphics.TOP | Graphics.LEFT);

//draw the menu
drawMenu(g);
//restore the clipping rectangle to full screen again
g.setClip(0, 0, getWidth(), getHeight());

// set drawing color to white
g.setColor(0xffffff);
//display the key code last pressed
g.drawString(Integer.toString(iKey), 2, 2, Graphics.TOP | Graphics.LEFT);

flushGraphics();

try{
Thread.sleep(30);
} catch (Exception ex){

}
}
g = null;
unload();
fParent.destroyApp(false);
fParent = null;
}
}



Press the F6 key in NetBeans to see if it actually works.




Here's a link to a low-res flash video of the MIDlet running on an N70:
Click to View Video

The technique used here to draw the vertical menu is pretty old school. I've been using it in DOS based games written in Turbo Pascal, DirectX games written in VisualBasic 6.0, and in C# using XNA Game Studio Express. One thing to remember is that you're not just limited to using vertical menus in your game. I just used a vertical menu as an example because they are very user friendly and easy to make. You can change the shape of the menu items to anything you want, knowing that you can use transparent images or change the colors to match your game. Here's a screenshot of a character selection screen for a demo quiz game:


Character sprites came from the MMORPG Trickster.


Using the graphics methods to make the menus for your game let's you use your creativity and the design is only limited by your imagination. Just remember that however unlimited your imagination may seem to be, the phone on the other hand, has limited capabilities.

~John Constantime:
"There's always a catch...damn cellphones!"

Something is unclear? I missed something? Got a question? Post a comment.

8 comments   |   post a comment
said...
Any change you could show how you would do two Game canvas menus linking?

Or maybe text -> Gif conversion?
said...
Hello,

Yes, I've been thinking about how to show a menu hierarchy system that is easy to understand.

I'm choosing between 3 types:
1) Using states to determine which menu/sub menu to display in a single loop. This one keeps the code small.

2) Using states and a separate loop for each menu/sub menu. I prefer this one because it keeps the code more "readable". Since each loop is in a separate method, images/objects for each sub menu is destroyed when a sub menu is exited.

3) Using a menu/sub menu class system. I don't use this, it feels "heavy". I avoid using too many classes in a game and keep the programming as linear as possible. So there is less method calls that slows the game down and less object instances that eat up memory.

I actually use the second type but the code is longer. The second type gives you the option of loading separate resources/images for each menu/sub menu. I also don't use arrays to store menu item strings mainly because the menus I use are all graphical/image based.

Text to Gif? Sounds big...specially if you want to implement the compression algorithm(LZW?). Sorry, it just seems too complicated for a beginners tutorial.

Good Luck!!!
said...
Great Great Great Blog Devlin!!!
Would like to know what would be the best way to handle multiple screen sizes.
Thnx
vJ
said...
I won't even pretend to know the best way to handle multiple screen sizes. Even most games made by the most known developers and publishers target only 1 screen size and target only a one if not a few selected mobile devices.

I do know a couple of things you can try out:

1) Use SVG images instead of bitmaps. Provided your target device supports SVG images.

2) Make different graphics for different resolutions and load the right images at runtime. (Bigger Jar Size)

3) With tile based games, try to make the tile size fit a range of resolutions so it won't slow down the game too much when a larger area of the map is shown in phones with higher screen resolutions. So you have to decide the minimum resolution and maximum resolution for the game, draw the tiles that looks "OK" on both resolutions, test the map. You know you're drawing too many tiles when the game starts to slow down noticeably. When that happens make the tiles larger so less tiles will be drawn.

4) Resize the bitmaps at runtime to suit the screen resolution. Not recommended (then why did you mention it in the first place? Coz' it might work for you.)

You can try just one or a combination of those methods and see which one suits you the most.

Good Luck!
said...
Hi Devlin,

I really respect you for the work you have put in to your site, and the tutorials are great!

So far I have managed to learn a great deal and get together a new front end menu that i put together with your tutorials and a sunday afternoon on photoshop ;)

What I would really like to see as you said above, is a hiearchy styled menu system.. Im stuck because I want to be able to call another screen that is a custom canvas that will contain a grid that I will draw on screen for use with the application im developing. But for some reason I create a new class and call a new instance in menu item 4 and update the screen but it does now stay in the same screen or update... So any advice would be greatly appreciated.

Cheers and Many regards,

gonche
said...
Just to add step 2 sounds great and would also like to implement multiple loops and methods for speed purposes and compactness.

Have a good day, Cheers

gonche
said...
Hi Devlin,

After thinking about my current problem, it occurred to me, that I should take a copy of the while loop and paste it underneath with a new isRunning flag... so on selection of say the 4th item on the menu.. it will then exit the running loop as it becomes false and will then enter the second loop with my new draw method!

So hey presto it works and I am happy again... always good to go and have a break and a rethink.

Cheers,

gonche
said...
i want this menue appear when i press left(or right) selection key instead of fire. when im working in fullscreen mode then submenu appear when i press left or right selection key. i want to avoid that submenu and want to show my own menue by left or right selection key...but there is no keyPress event for right or left selection key...how can i achieve this?