Subscribe to GameFromScratch on YouTube Support GameFromScratch on Patreon


Game From Scratch C++ Edition Part 3

 

 

So, now we have a functioning game loop and a screen to draw on, lets add some more functionality.  This time we are going to add a menu and a welcome splash screen.  We are going to take a consistent approach in that all “screens” are in charge of handling their own input.

 

Alright, lets start with adding a SplashScreen for when you start the game.  First off all, you are going to need an image to display as the splash screen.  Keep in mind the game is running at a fixed resolution of 1024x768, so ideally you want to make an image that size.  It is outside of the scope of this tutorial to create that image, but after playing around in Blender for a couple of minutes, I came up with this:

 

SplashScreen

Click here to download full size version of SplashScreen.png

 

… what can I say, I am a programmer.  You can use this image or create your own it really doesn’t matter, although to follow along with the code, name it SplashScreen.png if you do decide to create your own.  Now that we have our image, lets write some code.

 

First create a new header file in the Header Files folder named SplashScreen.h.  Now add the following code:

 

1 #pragma once 2 class SplashScreen 3 { 4 public: 5 void Show(sf::RenderWindow& window); 6 }; 7

Click here to download SplashScreen.h

 

Simple enough right?  After the last section, there should be nothing new here.  We are simply creating a class called SplashScreen with a single public function Show(), which takes a RenderWindow as a parameter.  Save your changes.

 

 

Now we want to create SplashScreen.cpp in the Source Files folder.  Once created, add the following code:

1 #include "StdAfx.h" 2 #include "SplashScreen.h" 3 4 5 void SplashScreen::Show(sf::RenderWindow & renderWindow) 6 { 7 sf::Image image; 8 if(image.LoadFromFile("images/SplashScreen.png") != true) 9 { 10 return; 11 } 12 13 sf::Sprite sprite(image); 14 15 renderWindow.Draw(sprite); 16 renderWindow.Display(); 17 18 sf::Event event; 19 while(true) 20 { 21 while(renderWindow.GetEvent(event)) 22 { 23 if(event.Type == sf::Event::EventType::KeyPressed 24 || event.Type == sf::Event::EventType::MouseButtonPressed 25 || event.Type == sf::Event::EventType::Closed ) 26 { 27 return; 28 } 29 } 30 } 31 }

Click here to download Splashscreen.cpp

 

This code is also pretty straight forward.  First thing we do is load the splashscreen image from disk and simply return if it doesn’t exist, meaning of course nothing will be shown.  With SFML an image is not seen on screen, that is the job of a sprite.  An image represents the actual pixel data from a file, but a sprite represents displaying that information on screen.  Why, you might wonder would you bother creating two different classes?  Mostly because you could have multiple sprites using the same image.  For example, our game is going to have at least two paddles, but we are going to use only a single image.  Also, in the case of a sprite sheet you often have multiple images in a single image file.

 

One the sprite is loaded, we simply draw it to the screen.  We don’t need to worry about coordinates because the sprite and the renderWindow are the same size.  Next we simply call RenderWindow.Display() which displays our image on screen.

 

As I said earlier, all windows are responsible for their own event handling, which exactly what happens starting on line 18.  On 19 we declare an infinite loop, then each pass through we check for available events and if the user presses a key, clicks a mouse or closes the window we exit the function, thus closing the splash screen window.

 

The main menu works very similarly but has a couple of key differences.  The menu is essentially just an image that is displayed similar to the splash screen.  Let’s take a look at it now.  Create a file called MainMenu.h ( note, you can use Add Class because MainMenu is a reserved class name in MFC… again, stupid bug. ) and add the following code:

 

1 #pragma once 2 #include "SFML/Window.hpp" 3 #include "SFML/Graphics.hpp" 4 #include <list> 5 6 class MainMenu 7 { 8 9 public: 10 enum MenuResult { Nothing, Exit, Play }; 11 12 struct MenuItem 13 { 14 public: 15 sf::Rect<int> rect; 16 MenuResult action; 17 }; 18 19 MenuResult Show(sf::RenderWindow& window); 20 21 private: 22 MenuResult GetMenuResponse(sf::RenderWindow& window); 23 MenuResult HandleClick(int x, int y); 24 std::list<MenuItem> _menuItems; 25 };

Click here to download MainMenu.h

 

First thing you may ask is “Why isn’t <list> declared in stdafx?”.  The answer is, you’re right, it should be.  As you know from earlier, any header file you include but aren’t actively being changed should be included in stdafx to accelerate the build process.  For sample code purposes though, it was better to show it included locally instead of having to show stdafx.h over and over.

 

MainMenu is a fairly straight forward class.  First off it defines an enum type that contains the various possible return values the menu could return.  Next it declares a struct called MenuItem that represents the individual menu items in the menu.  This struct is exceedingly simple, containing a sf::Rect<int> that represents the dimensions of the MenuItem. It also contains a MenuResult enum presenting the value to return if that MenuItem is clicked.

 

 

Optional information

 

What's with the <int> ???

You may have just encountered your first line of templated code. In MainMenu.h we declared:
sf::Rect<int> rect;
 
sf::Rect is a templated class.  In this case we are declaring an instance of sf::Rect using the type of int.  In place of int we could have used many other numeric data types, such as float or double or even your own class, as long as it supported all of the operators that sf::Rect requires.
 
In essence, templates allow a class such as sf::Rect to become more generic by removing the requirement of them to know about the datatype they are operating on.  So instead of having to for example, implement three Add() methods for eac int, float and double, they can simply write one.
 
This is an extremely simple and incomplete explanation of templates.  Templates are of extreme importance to C++ and are becoming more and more important with every revision, so it is a very good idea to familiarize yourself with how they work.  Such a discussion is well beyond the scope of this work, so you may wish to consult a site such as this or consult a book such as  C++ Templates The Complete Guide, an excellent book on the subject.
 
You should be able to make it through this tutorial with minimal knowledge of templates, but if you are going to become proficient in C++, it is an area you will need to know extremely well.

 

 

Next MainMenu declares a single public function Show() that returns a MenuResult enum type.  Finally are the private functions and data.  GetMenuResponse() is used internally and will be explained shortly, as is HandleClick().  The last line item is a list of type MenuItem, which will hold the various MenuItems that compose MainMenu.  std::list is a templated class from the Standard Template Library.  If you are unfamiliar with STL, you should rectify that as soon as you get the chance.  There are some good book and link suggestions for learning C++ in the C++ section of the Beginners Guide if you wish to learn more.

 

 

That’s about it for MainMenu.h.  Most of this will make more sense in a few minutes.  I thought I would take a moment to explain something about the design of MainMenu, the nested structs/enums.  You may be wondering why I nested the definition of MenuItem and MenuResult inside of MainMenu and the simple answer is because they belong to MainMenu, so we want to scope them within MainMenu.  Truth of the matter is, those items are of absolutely no use when not dealing with MainMenu, so there is no sense polluting the global namespace with a number of classes with no use to anything but a MainMenu object.  This is a key aspect of object oriented programming, encapsulation.

 

 

This leads to another conversation, the design of MainMenu itself.  Don’t menus share enough functionality they should derive from a common base class and a generic MenuItem class that isn’t specific to a specific type of menu?  The answer is very much yes!  In your own game, if you have multiple menus this is exactly what you would want to do.  In the case of Pang however, we are only going to have a single menu, so I went this route in order to keep the code smaller/easier to understand.  Don’t worry, later in the tutorial we will cover inheritance in more detail.

 

 

Optional information

 

Why a struct?

You may have noticed that MenuItem is a struct instead of a class and may wonder why that is.  The simple answer is, it just as easily could have been a class, but struct is a slightly better choice.
 
First off, the biggest difference between a class and a struct is that a class defaults to private while a struct defaults to public.  Since MenuItem contains entirely public members, choosing struct saves a bit of typing “public: “.
 
The second reason is more a convention thing, but is no less important, especially if you are dealing with other coders.  When dealing with fully publicly available POD types ( Plain Old Data ) that has no member functions, the norm is to use struct.  Finally it is common to use struct for functors but you can forget about that for now, we will cover it later.  There are some advantages to POD types ( like no vtables ) but they are well beyond the scope of what we are dealing with and are generally an example of premature optimization.
 
In short, you could have used class instead and been just as right, struct was just a slightly better fit.



 

Now let’s implement MainMenu.  Create a new file called MainMenu.cpp and add the following code:

 

1 #include "stdafx.h" 2 #include "MainMenu.h" 3 4 5 MainMenu::MenuResult MainMenu::Show(sf::RenderWindow& window) 6 { 7 8 //Load menu image from file 9 sf::Image image; 10 image.LoadFromFile("images/mainmenu.png"); 11 sf::Sprite sprite(image); 12 13 //Setup clickable regions 14 15 //Play menu item coordinates 16 MenuItem playButton; 17 playButton.rect.Top= 145; 18 playButton.rect.Bottom = 380; 19 playButton.rect.Left = 0; 20 playButton.rect.Right = 1023; 21 playButton.action = Play; 22 23 //Exit menu item coordinates 24 MenuItem exitButton; 25 exitButton.rect.Left = 0; 26 exitButton.rect.Right = 1023; 27 exitButton.rect.Top = 383; 28 exitButton.rect.Bottom = 560; 29 exitButton.action = Exit; 30 31 _menuItems.push_back(playButton); 32 _menuItems.push_back(exitButton); 33 34 window.Draw(sprite); 35 window.Display(); 36 37 return GetMenuResponse(window); 38 } 39 40 MainMenu::MenuResult MainMenu::HandleClick(int x, int y) 41 { 42 std::list<MenuItem>::iterator it; 43 44 for ( it = _menuItems.begin(); it != _menuItems.end(); it++) 45 { 46 sf::Rect<int> menuItemRect = (*it).rect; 47 if( menuItemRect.Bottom > y 48 && menuItemRect.Top < y 49 && menuItemRect.Left < x 50 && menuItemRect.Right > x) 51 { 52 return (*it).action; 53 } 54 } 55 56 return Nothing; 57 } 58 59 MainMenu::MenuResult MainMenu::GetMenuResponse(sf::RenderWindow& window) 60 { 61 sf::Event menuEvent; 62 63 while(true) 64 { 65 66 while(window.GetEvent(menuEvent)) 67 { 68 if(menuEvent.Type == sf::Event::MouseButtonPressed) 69 { 70 return HandleClick(menuEvent.MouseButton.X,menuEvent.MouseButton.Y); 71 } 72 if(menuEvent.Type == sf::Event::Closed) 73 { 74 return Exit; 75 } 76 } 77 } 78 }

Click here to download MainMenu.cpp

 

This code is fairly long, but not very complex.  First off we implement Show(), which is very similar to SplashScreen from before.  We load an image file “mainmenu.png” from the images folder and create a sprite to display it.  Next we create two MenuItem’s, which correspond with the location of their buttons physical locations within the mainmenu.png image file.  Lastly, each MenuItem has an action, which is represented by the MenuResult enum we described earlier.

 

MainMenu.png:

mainmenu

Click here to download MainMenu.png

 

After defining the location rects and action type of both MenuItems, we then add them to the _menuItems list using push_back(), which adds to an item to a list in the final position.  Next we draw our menu image to screen and tell the display to Display() just like we did in Splashscreen.  Finally, we call the function GetMenuResponse, returning it’s return value as Show’s return value.

 

GetMenuResponse() is the event loop for MainMenu.  It works just like our other two eventloops, except it only handles sf::Event::Closed and sf::Event::MouseButtonPressed.  In the event of close, it returns a value of MenuResult::Exit.  In the event of a button press, it passed the coordinates into HandleClick() returning it’s results.

 

HandleClick() is a very simple method that might look very confusing to you if you are new to STL datatypes.  First off, it declares an iterator, which is a datatype used for navigating through a collection of data.  In the for loop the iterator “it” is initially assigned a location of _menuItems.begin() which is the first element in the list.  Each pass through, the iterator’s ++ operator is called, which moves to the next item in the list, until the iterators location is equal to _menuItems.end().  One thing than can be a bit tricky is List.end() refers to a value past the end of the list, not to the last item in the list.  Think of .end() like you would a file’s EOF.  It does not represent a value, but instead the end of available values. 

 

Iterators also represent a pointer to the underlying datatype contained within the list<>.  Therefore when we dereference it ( using    (*it)  ) it contains a MenuItem type.  We then check to see if the click location is within the MenuItem’s rect.  If in fact the click was within MenuItem’s rect, we return the action (MenuResult). 

 

 

Lastly, we need to make a few changes to Game.h and Game.cpp, which will illustrate the power of using a state driven game structure. 

 

Change Game.h to contain the following:

1 #pragma once 2 #include "SFML\Window.hpp" 3 #include "SFML\Graphics.hpp" 4 5 6 class Game 7 { 8 public: 9 static void Start(); 10 11 private: 12 static bool IsExiting(); 13 static void GameLoop(); 14 15 static void ShowSplashScreen(); 16 static void ShowMenu(); 17 18 enum GameState { Uninitialized, ShowingSplash, Paused, 19 ShowingMenu, Playing, Exiting }; 20 21 static GameState _gameState; 22 static sf::RenderWindow _mainWindow; 23 };

Click here to download Game.h

 

The only changes we made here were the two next new functions on line 15 and 16, as well as the two new states on lines 18 and 19. The purpose of these changes will be explained in a moment.

 

In Game.cpp, we want to edit Game::Start() to look like:

1 void Game::Start(void) 2 { 3 if(_gameState != Uninitialized) 4 return; 5 6 _mainWindow.Create(sf::VideoMode(1024,768,32),"Pang!"); 7 8 _gameState= Game::ShowingSplash; 9 10 while(!IsExiting()) 11 { 12 GameLoop(); 13 } 14 15 _mainWindow.Close(); 16 }

 

The only real change here is line 8.  This simply sets the state to ShowingSplash when the game starts up.  Next up we just add two new states to Game::GameLoop, as follows:

1 void Game::GameLoop() 2 { 3 switch(_gameState) 4 { 5 case Game::ShowingMenu: 6 { 7 ShowMenu(); 8 break; 9 } 10 case Game::ShowingSplash: 11 { 12 ShowSplashScreen(); 13 break; 14 } 15 case Game::Playing: 16 { 17 sf::Event currentEvent; 18 while(_mainWindow.GetEvent(currentEvent)) 19 { 20 _mainWindow.Clear(sf::Color(sf::Color(0,0,0))); 21 _mainWindow.Display(); 22 23 if(currentEvent.Type == sf::Event::Closed) _gameState = 24 Game::Exiting; 25 26 if(currentEvent.Type == sf::Event::KeyPressed) 27 { 28 if(currentEvent.Key.Code == sf::Key::Escape) ShowMenu(); 29 } 30 } 31 break; 32 } 33 } 34 }

 

 

All that we add here are the Game::ShowingMenu and ShowingSplash case handlers, which call ShowMenu() and ShowSplash() respectively.  They are defined as:

 

 

1 void Game::ShowSplashScreen() 2 { 3 SplashScreen splashScreen; 4 splashScreen.Show(_mainWindow); 5 _gameState = Game::ShowingMenu; 6 } 7 8 void Game::ShowMenu() 9 { 10 MainMenu mainMenu; 11 MainMenu::MenuResult result = mainMenu.Show(_mainWindow); 12 switch(result) 13 { 14 case MainMenu::Exit: 15 _gameState = Game::Exiting; 16 break; 17 case MainMenu::Play: 18 _gameState = Game::Playing; 19 break; 20 } 21 }

Click here to download Game.cpp

 

Game::ShowSplashScreen() simply creates a SplashScreen object, calls its Show() method, then sets the game state to Showing Menu.  Game::ShowMenu() creates a MainMenu object and calls it’s Show() method.  In this case, it takes the returned MenuResults value and sets the game’s state accordingly.  This is how we handle the progression from state to state within our game. 

 

 

Pang running now:

imageimage

 

 

 

Click any key or mouse button to get past the splash screen, then in the menu click either “Exit Game” to um… exit.   That’s it for now.  In the next section we will work a bit more with graphics and delve a little deeper into object oriented programming.

 

You can download a completely updated project here.

 

 

Back to Part Two Forward to Part Four




blog comments powered by Disqus


Month List

Popular Comments

Moai Tutorial Part 5: Mi GUI es tu GUI… or how to make buttons and stuff
Subscribe to GameFromScratch on YouTube Support GameFromScratch on Patreon


11. September 2012

 

In this recipe, we are going to show how to create a GUI using Moai.  This functionality isn’t actually built directly into Moai, it is provided by a third party library, so we have to do a bit of setup first.

 

There are a few ways you can get moaigui.  First, it is already included in your moai install in the samples directory.  In the samples folder, look for a contrib folder, in my case it was located at C:\moai-sdk\samples\contrib.  Select the folder moaigui and copy it into your project directory.  Otherwise you can get the newest version using github. To do so ( assuming you have git installed and configured ), open a console window, change directory to your project and type:

git clone https://code.google.com/p/moaigui/

 

This will clone the newest source of the moaigui as a subdirectory in your project.  Alright, that done, on with the code:

 

screenWidth = MOAIEnvironment.screenWidth
screenHeight = MOAIEnvironment.screenHeight
if screenWidth == nil then screenWidth =1280 end
if screenHeight == nil then screenHeight = 800 end

MOAISim.openWindow("Window",screenWidth,screenHeight)

viewport = MOAIViewport.new()
viewport:setSize(screenWidth,screenHeight)
viewport:setScale(screenWidth,screenHeight)

package.path = './moaigui/?.lua;' .. package.path
require "gui/support/class"
local moaigui = require "gui/gui"
local resources = require "gui/support/resources"
local filesystem = require "gui/support/filesystem"
local inputconstants = require "gui/support/inputconstants"
local layermgr = require "layermgr"


local gui = moaigui.GUI(screenWidth,screenHeight)


gui:addToResourcePath(filesystem.pathJoin("moaigui/resources", "fonts"))
gui:addToResourcePath(filesystem.pathJoin("moaigui/resources", "gui"))
gui:addToResourcePath(filesystem.pathJoin("moaigui/resources", "media"))
gui:addToResourcePath(filesystem.pathJoin("moaigui/resources", "themes"))

layermgr.addLayer("gui",99999, gui:layer())
gui:setTheme("basetheme.lua")
gui:setCurrTextStyle("default")

function onButtonClick(event,data)
    label1:setText("You clicked the button")


end



function onLessProgressButtonClick(event,data)
    local curProgress = progress:getProgress()
    if(curProgress > 0) then
        progress:setProgress(curProgress-10)
    end

end

function onMoreProgressButtonClick(event,data)
    local curProgress = progress:getProgress()
    if(curProgress < 100) then
        progress:setProgress(curProgress+10)
    end
end


button = gui:createButton()
button:setPos(0,0)
button:setDim(100,25)
button:setText("This is a button")
button:registerEventHandler(button.EVENT_BUTTON_CLICK,nil,onButtonClick)
button:registerEventHandler(button.EVENT_TOUCH_ENTERS,nil,onButtonClick)

progress = gui:createProgressBar()
progress:setPos(0,25)
progress:setDim(100,25)
progress:setText("This is a progress bar")

button2 = gui:createButton()
button2:setPos(0,50)
button2:setDim(49,25)
button2:setText("Less Progress")
button2:registerEventHandler(button.EVENT_BUTTON_CLICK,nil,onLessProgressButtonClick)
button2:registerEventHandler(button.EVENT_TOUCH_ENTERS,nil,onLessProgressButtonClick)


button3 = gui:createButton()
button3:setPos(51,50)
button3:setDim(49,25)
button3:setText("More Progress")
button3:registerEventHandler(button.EVENT_BUTTON_CLICK,nil,onMoreProgressButtonClick)
button3:registerEventHandler(button.EVENT_TOUCH_ENTERS,nil,onMoreProgressButtonClick)
button3:registerEventHandler(button.EVENT_TOUCH_TAP,nil,onMoreProgressButtonClick)

label1 = gui:createLabel()
label1:setPos(0,75)
label1:setDim(100,25)
label1:setText("Click the top button")
label1:setTextAlignment(label1.TEXT_ALIGN_CENTER)

function onPointerEvent(x, y)
    gui:injectMouseMove(x, y)
end

function onMouseLeftEvent(down)
    if(down) then
        gui:injectMouseButtonDown(inputconstants.LEFT_MOUSE_BUTTON)
    else
        gui:injectMouseButtonUp(inputconstants.LEFT_MOUSE_BUTTON)
    end
end

function onTouchEvent(eventType,idx,x,y,tapCount)
    --gui:injectTouch(eventType,idx,x,y,tapCount)
    onPointerEvent(x, y)
        if (MOAITouchSensor.TOUCH_DOWN == eventType) then
                onMouseLeftEvent(true)
        elseif (MOAITouchSensor.TOUCH_UP == eventType) then
                onMouseLeftEvent(false)
        end
end


if MOAIInputMgr.device.pointer then
    MOAIInputMgr.device.pointer:setCallback(onPointerEvent)
    MOAIInputMgr.device.mouseLeft:setCallback(onMouseLeftEvent)
else
    MOAIInputMgr.device.touch:setCallback(onTouchEvent)
end

Run the code and you will see:

image

 

Buttons and bars and labels, oh my!

 

The top 10 lines are pretty much unchanged from earlier tutorials, except I switched to the native resolution of my device, since I am doing a lot more on device testing lately.  Obviously you can change screenWidth and screenHeight to match your preferred settings.  The new code starts at:

 

package.path = './moaigui/?.lua;' .. package.path
require "gui/support/class"
local moaigui = require "gui/gui"
local resources = require "gui/support/resources"
local filesystem = require "gui/support/filesystem"
local inputconstants = require "gui/support/inputconstants"
local layermgr = require "layermgr"

The first line looks a bit daunting, but it is important to realize that Lua doesn’t really understand file directories the way you or I do.  It uses a variable called package.path to determine where to look for source files.  We want to add the moaigui subdirectory, which is what this line does, we append that folder and tell it to include all .lua files within in the path.  We then have a series of require statements for importing various required parts of the moaigui library.  We don’t have to add paths to these values, as moaigui takes care of that for us.

 

local gui = moaigui.GUI(screenWidth,screenHeight)

Create a moaigui GUI object named gui, by passing in the screens width and height.

 

gui:addToResourcePath(filesystem.pathJoin("moaigui/resources", "fonts"))
gui:addToResourcePath(filesystem.pathJoin("moaigui/resources", "gui"))
gui:addToResourcePath(filesystem.pathJoin("moaigui/resources", "media"))
gui:addToResourcePath(filesystem.pathJoin("moaigui/resources", "themes"))

These are a number of essential files for moaigui, these lines make them all available in the resource path. 

 

layermgr.addLayer("gui",99999, gui:layer())
gui:setTheme("basetheme.lua")
gui:setCurrTextStyle("default")

Add a layer to the moaigui layer manager for the gui layer.  It needs to be the topmost layer so it will receive first crack at all the input coming in.  This is why we set it at layer 99999.  Just don’t go creating a layer 100000 now, ok?  We then load a default theme ( we will look at this in a second, but don’t worry, someone else has already done the work for us ) named basetheme.lua, and set the text style to default… if you don’t do this, text wont show up, so do it.

 

function onButtonClick(event,data)
    label1:setText("You clicked the button")
end

function onLessProgressButtonClick(event,data)
    local curProgress = progress:getProgress()
    if(curProgress > 0) then
        progress:setProgress(curProgress-10)
    end
end

function onMoreProgressButtonClick(event,data)
    local curProgress = progress:getProgress()
    if(curProgress < 100) then
        progress:setProgress(curProgress+10)
    end
end

Here we create 3 button click/touch handlers, one for each button we are going to create.  The first simply updates the text on a label, while the next two advance or decrease the progress status on a progress bar by 10%.

 

button = gui:createButton()
button:setPos(0,0)
button:setDim(100,25)
button:setText("This is a button")
button:registerEventHandler(button.EVENT_BUTTON_CLICK,nil,onButtonClick)

progress = gui:createProgressBar()
progress:setPos(0,25)
progress:setDim(100,25)
progress:setText("This is a progress bar")

button2 = gui:createButton()
button2:setPos(0,50)
button2:setDim(49,25)
button2:setText("Less Progress")
button2:registerEventHandler(button.EVENT_BUTTON_CLICK,nil,onLessProgressButtonClick)


button3 = gui:createButton()
button3:setPos(51,50)
button3:setDim(49,25)
button3:setText("More Progress")
button3:registerEventHandler(button.EVENT_BUTTON_CLICK,nil,onMoreProgressButtonClick)

Speaking of buttons and progress bars, here we create 3 buttons and a progress bar.  There are a few things to note about moaigui.  First off, position is all done by percentages ( which I personally love!  so easy ).  So saying setDim(100,25), sets the dimensions to 100% width and 25% height.  Nice easy way to scale your UI to the device you are running on.  For each of the buttons, we register the click function we just declared in the call registerEventHandler().

 

label1 = gui:createLabel()
label1:setPos(0,75)
label1:setDim(100,25)
label1:setText("Click the top button")
label1:setTextAlignment(label1.TEXT_ALIGN_CENTER)

Oh, and we create a label too, that will update when the user clicks the topmost button.  Using setTextAlignment, we set the text centered.

 

function onPointerEvent(x, y)
    gui:injectMouseMove(x, y)
end

function onMouseLeftEvent(down)
    if(down) then
        gui:injectMouseButtonDown(inputconstants.LEFT_MOUSE_BUTTON)
    else
        gui:injectMouseButtonUp(inputconstants.LEFT_MOUSE_BUTTON)
    end
end

You need to feed UI events in to moaigui, so when the user hits a key, moves the mouse or taps the screen, if you want moaigui to know about it, you need to tell it.  This is done with a series of inject calls, like injectMouseMove() when the user moves the mouse.  If you don’t wire events up as moaigui is concerned, they didn’t happen.  For example, with the current setup, you can type till the cows come home, and your gui will be completely oblivious.

 

function onTouchEvent(eventType,idx,x,y,tapCount)
    --gui:injectTouch(eventType,idx,x,y,tapCount)
    onPointerEvent(x, y)
        if (MOAITouchSensor.TOUCH_DOWN == eventType) then
                onMouseLeftEvent(true)
        elseif (MOAITouchSensor.TOUCH_UP == eventType) then
                onMouseLeftEvent(false)
        end
end

Currently touch support in moaigui is a bit… in development.  So for now we emulate the user using a mouse.  This means when the user touches the screen, we create a mouse pointer event, when the user touches the screen, we create a left click event and when the user releases the screen, be set the left mouse button status to up.  I also got it working by making a few small changes to the moagui code itself, but unless someone specifically requests it, I wont go into them here.

 

if MOAIInputMgr.device.pointer then
    MOAIInputMgr.device.pointer:setCallback(onPointerEvent)
    MOAIInputMgr.device.mouseLeft:setCallback(onMouseLeftEvent)
else
    MOAIInputMgr.device.touch:setCallback(onTouchEvent)
end

Finally, we wire up a callback for each io event we want moai to tell us about.

 

One other thing I mentioned earlier was the theme file basetheme.lua.  This file is located in the /moaigui/resources/themes/ directory, and looks like this:

data = {
    textstyles = {
        default = {
            font = "arial-rounded.TTF",
            size = 44,
        },
        listselected = {
            font = "arial-rounded.TTF",
            size = 14,
            color = {0, 0, 0, 1}
        },
        listunselected = {
            font = "arial-rounded.TTF",
            size = 14,
            color = {1, 1, 1, 1}
        }
    },
    label = {

    },
    button = {
        normal = "button normal center.png",
        pushed = "button pushed center.png",
        hover = "button hover center.png",
    },
    checkbox = {
        normal = "check box.png",
        hover = "check box.png",
        pushed = "check box.png",
        selected = "check box select.png",
    },
    radiobutton = {
        normal = "radio button.png",
        hover = "radio button.png",
        pushed = "radio button.png",
        selected = "radio button select.png",
    },
    editbox = {
        normal = "edit box center.png",
    },
    vertscrollbar = {
        normal = "vert scroll center.png",
        scrollernormal = "vert scroller.png",
        scrollerhover = "vert scroller.png",
    },
    horzscrollbar = {
        normal = "horz scroll center.png",
        scrollernormal = "horz scroller.png",
        scrollerhover = "horz scroller.png",
    },
    vertslider = {
        normal = "vert slider center.png",
        slidernormal = "vert slider.png",
        sliderhover = "vert slider.png",
    },
    horzslider = {
        normal = "horz slider center.png",
        slidernormal = "horz slider.png",
        sliderhover = "horz slider.png",
    },
    progressbar = {
        normal = "progress bar center.png",
        bar = "progress bar.png",
        mask = "progress bar mask.png",
    },
    textbox = {
    
    },
    widgetlist = {
        selected = "listselected",
        unselected = "listunselected",
    },
    cursors = {
        default = {

        },
    },
}

return data

You can easily make small changes to your UI look and feel by changing this file.  As you can see, I vastly increased the default font size to improve visibility on my phone.  You can also change the button images, and other UI elements, by altering their images in the /moaigui/resources/gui directory, while additional or different fonts should go in the moaigui/resources/fonts folder.

 

In the next exciting tutorial we will put our new found GUIness to work.

 

Now you know, and knowing… is half the battle. Or was that a quarter?  No, no, definitely half.

 

Programming , ,

blog comments powered by Disqus

Month List

Popular Comments