Code example to build your own GUI menu

Started by Edak, June 30, 2011, 11:43:22 PM

Previous topic - Next topic

Edak

Hey everyone, I dunno how helpful this could be but I had a heck of a time getting the concept down so I'll release some working code that I believe to be a great example. My original goal was a menu system before ever loading map and or player models and all the stuff needed for a game (this also works for loading screens if used right) so it's a lil complex for the simplicity I was looking for but still more simple than using Nifty GUI (at least for me) and I think the overhead should be less as well, anyway to the code:


Setting up the object:
This is an example of a game menu, it's a small black box that is centered and only uses a portion of the screen

                gameMenu = new Menu(GameHeight, GameWidth); //always pass in the game's width and height, It seems a little arbitrary but it feels effecient
                gameMenu.Width = 128; //These define the size of the menu it's self, anything behind this will be show (for example the rendered world)
                gameMenu.Height = 160;
                gameMenu.X = Menu.CENTER;
                gameMenu.Y = Menu.CENTER;
                gameMenu.build();
                Texture gameMenuButtonTexture = new Texture(BitmapFactory.decodeResource(getResources(), R.raw.gamemenu));
                //gameMenu.addButton("name", width, height, destX, destY, srcX, srcY, textureImage)
                gameMenu.addElement("quit", 128, 20, Menu.CENTER, 16, 1, 1, gameMenuButtonTexture);
                gameMenu.addElement("mainmenu", 128, 20, Menu.CENTER, 66, 0, 30, gameMenuButtonTexture);
                gameMenu.addElement("settings", 128, 20, Menu.CENTER, 122, 0, 66, gameMenuButtonTexture);

You must do gameMenu.show = true; before calling gameMenu.draw(framebuffer) will do anything.


This is an example of a main menu, the type of menu that fills the entire screen, when this is active, you don't need to render the world

                mainMenu = new Menu(GameHeight, GameWidth);
                Texture start_button = new Texture(BitmapFactory.decodeResource(getResources(), R.raw.startgame)); //load image for element.
                //mainMenu.addButton("name", width, height, destX, destY, srcX, srcY, textureImage) //Variables are very similar jPCT's blit
                mainMenu.addElement ("start", 128, 14, Menu.CENTER, Menu.CENTER, 1, 1, start_button);
                mainMenu.build(); //required in every instance at least once
                mainMenu.show = true;


I call this right before my  FrameBuffer.display(); As far as I'm aware this is the best spot to call a blit.
Don't do any checks against this, instead check against Menu.show

mainMenu.draw(framebuffer);
gameMenu.draw(framebuffer);


And if you want to check against an element getting touched:

@Override
public boolean onTouchEvent(MotionEvent me) {
    if (me.getAction() == MotionEvent.ACTION_DOWN) {
         String menuSelect = gameMenu.checkTouch(me.getX(), me.getY());  //By supplying X and Y you will get a string name of the element
         //TODO: put gameMenu.show check inside of checkTouch
         if("start".equals(menuSelect) && gameMenu.show){ //If the start portion was touched (and the menu is currently showing)
                Logger.log("start element was touched!"); //Do something about it
         }
    }
}



This is the menu class:

/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package edak.moon.scape;


import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Color;

import com.threed.jpct.FrameBuffer;
import com.threed.jpct.Texture;
import com.threed.jpct.Logger;

/**
*
* @author Kade
*/
public class Menu {
    //NOTE: If you don't otherwise specify, menu will assume it's covering the entire game
    public int Width;
    public int Height;
    public int gameWidth;
    public int gameHeight;
   
    public boolean show = false;
   
    //Assumes the menu starts in the top left corner
    public int X = 1;
    public int Y = 1;
   
    public Element[] elements = new Element[10];
    private int elementCount = 0;
   
    //Assumes you want to color the menu a solid color, set a bitmap here otherwise
    private Bitmap backgroundImage = null;
    //Assumes that color is black
    private int backgroundColor = Color.BLACK;
   
    //After the bitmap has been created with paint, this texture is what's rendered
    //I believe I'm going to remove the need for Paint and Canvas soon, and just require texture friendly sizes.
    private Texture menuTexture;
   
    //Static for center, I assumed this required a value;
    static int CENTER = 99000;
   
    //The menu's only requirements are the game's width and height in pixels
    public Menu(int GameHeight, int GameWidth){
        gameHeight = GameHeight;
        gameWidth = GameWidth;
        Height = GameHeight;
        Width = GameWidth;
    }
   
   
   
    //Build constructs the menu's background
    public void build(){
        backgroundImage = null;
        build(null);
    }
   
    //if you supply a background image you must give a paint object
    public void build(Paint paint){
        Bitmap bitmap = Bitmap.createBitmap(1024, 1024, Bitmap.Config.ARGB_4444);
        if(backgroundImage != null){
            Canvas canvas = new Canvas(bitmap);
            canvas.drawBitmap(backgroundImage, 0, 0, paint);
        }else{
            bitmap.eraseColor(backgroundColor);
        }
        menuTexture = new Texture(bitmap);
        FixXY();
    }
   
    public void draw(FrameBuffer fb){
        if(show){
            //Take the created background and show it first (remember this is either a solid color or some supplied background image)
            fb.blit(menuTexture, 1, 1, X, Y, Width, Height, false);
            //Then draw each element on top of it. This is set up so that each element is a texture (or part of a texture) so they can be
                //manipulated in real time
            for(int i = 0; i < elementCount; i++){
                elements[i].Blit(fb);
            }
        }
    }
   

      ///I know fixXY is in buttons too but the approach is different enough that seperate functions are needed
    ///This is for aligning the menu in the game screen, supplying negative values offests from the opposite side
    ///if you don't set gameHeight or gameWidth it will assume the menu you're supplying covers the entire screen
        public void FixXY(){
            if(X == Menu.CENTER){
                X = (gameWidth/2)-(Width/2);
            }               
            if(Y == Menu.CENTER){
                Y = (gameHeight/2)-(Height/2);
            }
        }
   
    ///This is the most annoying part of adding an element, but it's pretty straight forward
        //name: whatever you want for the element, also used against touch events
        //width and height: the size of the final element you want on screen
            ///this determins the touch size if you use this as a button
            //this also determins what on the source bitmap is copied
        //destX destY: where on the menu to place this button it's relative to the menu size and placement
        //srcX srcY: the texture image you are blitting, where in that texture do you want to start copying the element
    public void addElement(String name, int width, int height, int destX, int destY, int srcX, int srcY, Texture image){
        //I read a long page about performance in Android and I specifically remember
            //setting the variable directly is faster than a getter or a setter so hey might as well do what I can
        elements[elementCount] = new Element(name);
        elements[elementCount].image = image;
        elements[elementCount].width = width;
        elements[elementCount].height = height;
        elements[elementCount].destX = destX;
        elements[elementCount].destY = destY;
        elements[elementCount].srcX = srcX;
        elements[elementCount].srcY = srcY;
        elements[elementCount].Width = Width;
        elements[elementCount].Height = Height;
        elements[elementCount].X = X;
        elements[elementCount].Y = Y;
        elements[elementCount].fixXY();
        elementCount++;
    }
   
   
    public Element elementByName(String name){
        for(int i=0;i < elementCount; i++){
            if(elements[i].name.equals(name)){
                return elements[i];
            }
        }
        return null;
    }
   
   
    ///This goes in onTouchEvent and you pass it the touched X and Y
        //returns the name of the set element that was touched, otherwise null
    public String checkTouch(float x, float y){
        for(int i=0;i < elementCount; i++){
            if(elements[i].touched(x, y)){
                return elements[i].name;
            }
        }
        return null;
    }
   
    public class Element {
        public String name;
        public int width;
        public int height;
        public int destX;
        public int destY;
        public int srcX;
        public int srcY;
        public int Height;
        public int Width;
        public int X;
        public int Y;
        public Texture image = null;
        public boolean show = true;
        //public int Color = null;
       
       
        //Every element is required a name
        public Element(String Name){
            name = Name;
        }
       
        public void Blit(FrameBuffer fb){
            if(show){
                fb.blit(image, srcX, srcY, destX, destY, width, height, true);
            }
        }
       
       
        //Every element has the option to be used like a button by simply calling Menu.checkTouch(x, y) and looking for the element's name
        public boolean touched(float x, float y){
            if(x > this.destX-5 && x < this.destX+width+5 && y > this.destY-5 && y < this.destY+height+5){
                return true;
            }else{
                return false;
            }
        }
       
         ///By supplying negative x or y you can offset the image from the bottom of the screen vs the top
        //Also the option to supply Menu.CENTER_BUTTON
        ///TODO: Menu.CENTER_POS_X = inbetween the max X and the middle X ie centered between the center
        public void fixXY(){
            if(destX < 0){
                destX+= Width+X;
            }
            if( destY < 0){
                destY+= Height+Y;
            }
            if(destX == Menu.CENTER){
                destX = (X+(Width/2))-(width/2);
            }else{
                destX += X;
            }
            if(destY == Menu.CENTER){
                destY = (Y+(Height/2))-(height/2);
            }else{
                destY += Y;
            }
        }
    }
   
}


Any questions or comments welcome.

-I've done a bit of work on it today, now creating a menu that's not the entire size of the screen is possible
--Ok, I went through and cleared up a bunch of junk, buttons are now elements, and elements can have a touch event checked against them
---Added some comments and small fixes here and there

Edak

This is a work in progress, so any progress I get I'll post.

I am working on making three menus right now, one that's called at the game start and when you're not in game, one that's called while in the game and you hit the settings button on the android phone( this one will contain things like 'End Game', 'Change Settings', etc) and the last menu object will be a settings menu, that's called when you select 'Change Settings'


3-S-E

Thank you man!
I'm back from my vacation and about to continue my project. Your GUI Code seams to be perfect for my intentions, so I'll give it a try today and report back, if everything works fine. If I'll have to modifie something, I'll report too.  8)

Edak

Glad I could be of help :-)

Quick question to anyone looking:

In my Menu class  I have


public void Blit(FrameBuffer fb){
            if(show){
                fb.blit(image, srcX, srcY, destX, destY, width, height, true);
            }
        }


and would like to include a transparency, with the current way I blit textures, how do I alter transparency?

rhine

Thought i'd chime in... ;)

You could overload the method with an additional parameter (labeled alpha or opacity or transparency) of type int. 0 min, 100 max.

RhoX

Man! You're code seems to be what I was looking for to start a new application development!
I'm gonna try it and then I'll reply my experinces  :D!

Thanks!!

RhoX

Edak

Thanks rhine, But doing so wouldn't magically make jPCT's blit include a transparency variable. I'm assuming if I supply a PNG with some transparency it would be transparent but I haven't tested it. I just don't know how to supply an alpha to blit.

Oh and you're welcome RhoX, I'm glad this is helping :-)