Tuto Gideros Game Template1 Part 6 Options
Options scene
This is going to be the longest scene of this tutorial!
Our Options scene will mainly consist of:
- some sliders to control the sound volume
- the keys to control the player and a key remapping system
- the keyboard navigation system setup
Adding some Sliders
For this Gideros Game Template1, we will add some sliders. I have chosen to add this Class: UI_Slider#Another_Slider.
You can download the ASlider Class here: Media:Aslider.lua (tip: right click and save link as)
Please add the aslider.lua file in the classes folder (I rename it to aslider.lua).
In order to see it in your project, right click on Files->Refresh.
It makes sense to create the file in the scenes folder. You can call the file "options.lua". Here is the code:
Options = Core.class(Sprite)
local keyNames = { -- display nice KeyCode name
[KeyCode.LEFT] = "LEFT",
[KeyCode.RIGHT] = "RIGHT",
[KeyCode.UP] = "UP",
[KeyCode.DOWN] = "DOWN",
[KeyCode.NUM0] = "NUMPAD 0",
[KeyCode.NUM1] = "NUMPAD 1",
[KeyCode.NUM2] = "NUMPAD 2",
[KeyCode.NUM3] = "NUMPAD 3",
[KeyCode.NUM4] = "NUMPAD 4",
[KeyCode.NUM5] = "NUMPAD 5",
[KeyCode.NUM6] = "NUMPAD 6",
[KeyCode.NUM7] = "NUMPAD 7",
[KeyCode.NUM8] = "NUMPAD 8",
[KeyCode.NUM9] = "NUMPAD 9",
[KeyCode.SPACE] = "SPACE",
[KeyCode.SHIFT] = "SHIFT",
[KeyCode.CTRL] = "CONTROL",
[KeyCode.ALT] = "ALT",
[KeyCode.TAB] = "TAB",
}
function Options:init()
-- TITLE
self.mytitle = ButtonMonster.new({
autoscale=false, pixelwidth=16*8, pixelheight=4*8,
pixelcolorup=0xaaaaaa,
text="OPTIONS", ttf=myttf,
})
-- SLIDERS
local slitcolor, knobcolor = g_ui_theme.pixelcolorup, g_ui_theme.pixelcolordown
self.bgmvolumeslider = ASlider.new({
initialvalue=g_bgmvolume, maximum=100, --steps=2,
slitcolor=slitcolor, slitalpha=1, slitw=5*myappwidth/10, slith=24,
knobcolor=knobcolor, knobalpha=0.6, knobw=12, knobh=26,
text="BGM VOLUME: ", textscale=2, textoffsetx=-26*8, textoffsety=7,
id=1,
})
self.sfxvolumeslider = ASlider.new({
initialvalue=g_sfxvolume, maximum=100, --steps=2,
slitcolor=slitcolor, slitalpha=1, slitw=5*myappwidth/10, slith=24,
knobcolor=knobcolor, knobalpha=0.6, knobw=12, knobh=26,
text="SFX VOLUME: ", textscale=2, textoffsetx=-26*8, textoffsety=7,
id=2,
})
self.difficultyslider = ASlider.new({
initialvalue=g_difficulty, maximum=2, --steps=2,
slitcolor=slitcolor, slitalpha=1, slitw=5*myappwidth/10, slith=24,
knobcolor=knobcolor, knobalpha=0.6, knobw=12, knobh=26,
text="DIFFICULTY: ", textscale=2, textoffsetx=-26*8, textoffsety=7, textrotation=0,
id=3,
})
-- position
self.mytitle:setPosition(myappwidth/2, 0.75*myappheight/10)
self.bgmvolumeslider:setPosition(29*8, 2.5*myappheight/10)
self.sfxvolumeslider:setPosition(29*8, 3.5*myappheight/10)
self.difficultyslider:setPosition(29*8, 4.5*myappheight/10)
-- order
self:addChild(self.mytitle)
self:addChild(self.bgmvolumeslider)
self:addChild(self.sfxvolumeslider)
self:addChild(self.difficultyslider)
-- tooltip layer
local tooltiplayer = Sprite.new()
-- buttons
local key
self.sfxsound = {sound=Sound.new("audio/ui/sfx_sounds_button1.wav"), time=0, delay=0.2}
self.sfxvolume = g_sfxvolume * 0.01
-- movement
if (keyNames[g_keyleft] or 0) == 0 then key = utf8.char(g_keyleft)
else key = keyNames[g_keyleft] -- display nice KeyCode name
end
self.btnLEFT = ButtonMonster.new({
autoscale=false, pixelwidth=17*8, pixelheight=6*8,
pixelscalexup=0.8, pixelscalexdown=0.9,
pixelcolorup=g_ui_theme.pixelcolorup, pixelcolordown=g_ui_theme.pixelcolordown,
text=key, ttf=myttf, textcolorup=g_ui_theme.textcolorup, textcolordown=g_ui_theme.textcolordown,
sound=self.sfxsound, volume=self.sfxvolume,
tooltiptext="left", tooltipttf=myttf, tooltiptextcolor=g_ui_theme.tooltiptextcolor,
tooltipoffsetx=g_ui_theme.tooltipoffsetx, tooltipoffsety=g_ui_theme.tooltipoffsety,
}, 2, tooltiplayer)
if (keyNames[g_keyright] or 0) == 0 then key = utf8.char(g_keyright)
else key = keyNames[g_keyright] -- display nice KeyCode name
end
self.btnRIGHT = ButtonMonster.new({
autoscale=false, pixelwidth=17*8, pixelheight=6*8,
pixelscalexup=0.8, pixelscalexdown=0.9,
pixelcolorup=g_ui_theme.pixelcolorup, pixelcolordown=g_ui_theme.pixelcolordown,
text=key, ttf=myttf, textcolorup=g_ui_theme.textcolorup, textcolordown=g_ui_theme.textcolordown,
sound=self.sfxsound, volume=self.sfxvolume,
tooltiptext="right", tooltipttf=myttf, tooltiptextcolor=g_ui_theme.tooltiptextcolor,
tooltipoffsetx=g_ui_theme.tooltipoffsetx, tooltipoffsety=g_ui_theme.tooltipoffsety,
}, 3, tooltiplayer)
if (keyNames[g_keyup] or 0) == 0 then key = utf8.char(g_keyup)
else key = keyNames[g_keyup] -- display nice KeyCode name
end
self.btnUP = ButtonMonster.new({
autoscale=false, pixelwidth=17*8, pixelheight=6*8,
pixelscalexup=0.8, pixelscalexdown=0.9,
pixelcolorup=g_ui_theme.pixelcolorup, pixelcolordown=g_ui_theme.pixelcolordown,
text=key, ttf=myttf, textcolorup=g_ui_theme.textcolorup, textcolordown=g_ui_theme.textcolordown,
sound=self.sfxsound, volume=self.sfxvolume,
tooltiptext="up", tooltipttf=myttf, tooltiptextcolor=g_ui_theme.tooltiptextcolor,
tooltipoffsetx=g_ui_theme.tooltipoffsetx, tooltipoffsety=g_ui_theme.tooltipoffsety,
}, 1, tooltiplayer)
if (keyNames[g_keydown] or 0) == 0 then key = utf8.char(g_keydown)
else key = keyNames[g_keydown] -- display nice KeyCode name
end
self.btnDOWN = ButtonMonster.new({
autoscale=false, pixelwidth=17*8, pixelheight=6*8,
pixelscalexup=0.8, pixelscalexdown=0.9,
pixelcolorup=g_ui_theme.pixelcolorup, pixelcolordown=g_ui_theme.pixelcolordown,
text=key, ttf=myttf, textcolorup=g_ui_theme.textcolorup, textcolordown=g_ui_theme.textcolordown,
sound=self.sfxsound, volume=self.sfxvolume,
tooltiptext="down", tooltipttf=myttf, tooltiptextcolor=g_ui_theme.tooltiptextcolor,
tooltipoffsetx=g_ui_theme.tooltipoffsetx, tooltipoffsety=g_ui_theme.tooltipoffsety,
}, 4, tooltiplayer)
-- actions
if (keyNames[g_keyaction1] or 0) == 0 then key = utf8.char(g_keyaction1)
else key = keyNames[g_keyaction1] -- display nice KeyCode name
end
self.btnACTION1 = ButtonMonster.new({
autoscale=false, pixelwidth=17*8, pixelheight=6*8,
pixelscalexup=0.8, pixelscalexdown=0.9,
pixelcolorup=g_ui_theme.pixelcolorup, pixelcolordown=g_ui_theme.pixelcolordown,
text=key, ttf=myttf, textcolorup=g_ui_theme.textcolorup, textcolordown=g_ui_theme.textcolordown,
sound=self.sfxsound, volume=self.sfxvolume,
tooltiptext="punch1", tooltipttf=myttf, tooltiptextcolor=g_ui_theme.tooltiptextcolor,
tooltipoffsetx=g_ui_theme.tooltipoffsetx, tooltipoffsety=g_ui_theme.tooltipoffsety,
}, 5, tooltiplayer)
if (keyNames[g_keyaction2] or 0) == 0 then key = utf8.char(g_keyaction2)
else key = keyNames[g_keyaction2] -- display nice KeyCode name
end
self.btnACTION2 = ButtonMonster.new({
autoscale=false, pixelwidth=17*8, pixelheight=6*8,
pixelscalexup=0.8, pixelscalexdown=0.9,
pixelcolorup=g_ui_theme.pixelcolorup, pixelcolordown=g_ui_theme.pixelcolordown,
text=key, ttf=myttf, textcolorup=g_ui_theme.textcolorup, textcolordown=g_ui_theme.textcolordown,
sound=self.sfxsound, volume=self.sfxvolume,
tooltiptext="punch2", tooltipttf=myttf, tooltiptextcolor=g_ui_theme.tooltiptextcolor,
tooltipoffsetx=g_ui_theme.tooltipoffsetx, tooltipoffsety=g_ui_theme.tooltipoffsety,
}, 6, tooltiplayer)
if (keyNames[g_keyaction3] or 0) == 0 then key = utf8.char(g_keyaction3)
else key = keyNames[g_keyaction3] -- display nice KeyCode name
end
self.btnACTION3 = ButtonMonster.new({
autoscale=false, pixelwidth=17*8, pixelheight=6*8,
pixelscalexup=0.8, pixelscalexdown=0.9,
pixelcolorup=g_ui_theme.pixelcolorup, pixelcolordown=g_ui_theme.pixelcolordown,
text=key, ttf=myttf, textcolorup=g_ui_theme.textcolorup, textcolordown=g_ui_theme.textcolordown,
sound=self.sfxsound, volume=self.sfxvolume,
tooltiptext="jump punch", tooltipttf=myttf, tooltiptextcolor=g_ui_theme.tooltiptextcolor,
tooltipoffsetx=g_ui_theme.tooltipoffsetx, tooltipoffsety=g_ui_theme.tooltipoffsety,
}, 7, tooltiplayer)
-- btn menu
self.btnMENU = ButtonMonster.new({
autoscale=false, pixelwidth=12*8, pixelheight=6*8,
pixelscalexup=0.8, pixelscalexdown=0.9,
pixelcolorup=g_ui_theme.pixelcolorup, pixelcolordown=g_ui_theme.pixelcolordown,
text="MENU", ttf=myttf, textcolorup=0x0009B3, textcolordown=0x45d1ff,
sound=self.sfxsound, volume=self.sfxvolume,
tooltiptext="Enter", tooltipttf=myttf, tooltiptextcolor=g_ui_theme.tooltiptextcolor,
tooltipoffsetx=g_ui_theme.tooltipoffsetx, tooltipoffsety=g_ui_theme.tooltipoffsety,
}, 8, tooltiplayer)
-- put the btns in a table for keyboard navigation
self.btns = {}
self.btns[#self.btns + 1] = self.btnUP -- 1
self.btns[#self.btns + 1] = self.btnLEFT -- 2
self.btns[#self.btns + 1] = self.btnRIGHT -- 3
self.btns[#self.btns + 1] = self.btnDOWN -- 4
self.btns[#self.btns + 1] = self.btnACTION1 -- 5
self.btns[#self.btns + 1] = self.btnACTION2 -- 6
self.btns[#self.btns + 1] = self.btnACTION3 -- 7
self.btns[#self.btns + 1] = self.btnMENU -- 8
self.selector = 1 -- starting button
self.iskeyboardnavigation = false
-- position
self.btnLEFT:setPosition(1.5*myappwidth/10+myappleft, 6*8+6*myappheight/10)
self.btnRIGHT:setPosition(3.5*myappwidth/10+myappleft, 6*8+6*myappheight/10)
self.btnUP:setPosition(2.5*myappwidth/10+myappleft, 6*8+4.6*myappheight/10)
self.btnDOWN:setPosition(2.5*myappwidth/10+myappleft, 6*8+7.4*myappheight/10)
for i = 5, 7 do -- buttons (eg. punch, ...)
self.btns[i]:setPosition(5.6*myappwidth/10+myappleft, (i-5)*6*8+6*myappheight/10)
end
for i = 8, #self.btns-1 do -- buttons (eg. kick, ...)
self.btns[i]:setPosition(7.6*myappwidth/10+myappleft, (i-8)*6*8+6*myappheight/10)
end
self.btnMENU:setPosition(myappwidth-self.btnMENU:getWidth()/2+myappleft, myappheight-self.btnMENU:getHeight()/2)
-- order
for k, v in ipairs(self.btns) do self:addChild(v) end
self:addChild(tooltiplayer) -- last add tooltip layer
-- sliders listeners
self.bgmvolumeslider:addEventListener("value_just_changed", self.onValueJustChanged, self)
self.bgmvolumeslider:addEventListener("value_changing", self.onValueChanging, self)
self.bgmvolumeslider:addEventListener("value_changed", self.onValueChanged, self)
self.sfxvolumeslider:addEventListener("value_just_changed", self.onValueJustChanged, self)
self.sfxvolumeslider:addEventListener("value_changing", self.onValueChanging, self)
self.sfxvolumeslider:addEventListener("value_changed", self.onValueChanged, self)
self.difficultyslider:addEventListener("value_just_changed", self.onValueJustChanged, self)
self.difficultyslider:addEventListener("value_changing", self.onValueChanging, self)
self.difficultyslider:addEventListener("value_changed", self.onValueChanged, self)
-- buttons listeners
for k, v in ipairs(self.btns) do
-- v:addEventListener("pressed", function(e) -- e.currselector, e.disabled
-- self.selector = k
-- self:keyActions()
-- end)
v:addEventListener("clicked", function(e) -- e.currselector, e.disabled
self:cancelKeyRemapping() -- setColorTransform, self.isremapping = false, updateButton()
self.selector = k
self:keyActions()
end)
v:addEventListener("hovered", function(e) -- e.currselector, e.disabled
if not self.isremapping then
self.selector = e.currselector
end
end)
v.btns = self.btns -- for keyboard navigation
end
-- mouse listener
self:addEventListener(Event.MOUSE_DOWN, function(e)
if self:hitTestPoint(e.x, e.y) then -- cancel key remapping
self.iskeyboardnavigation = false
self:cancelKeyRemapping()
e:stopPropagation()
end
end)
-- let's go!
self:difficulty(g_difficulty)
self.isremapping = false
self:myKeysPressed()
self:updateButton()
end
-- sliders
function Options:difficulty(x) -- translate int to text
if x >= 2 then self.difficultyslider.textfield:setText("DIFFICULTY: \e[color=#ddc]HARD\e[color]")
elseif x >= 1 then self.difficultyslider.textfield:setText("DIFFICULTY: \e[color=#ddc]NORMAL\e[color]")
else self.difficultyslider.textfield:setText("DIFFICULTY: \e[color=#ddc]EASY\e[color]")
end
end
function Options:onValueJustChanged(e)
self:cancelKeyRemapping()
if e.id == 1 then -- bgm volume
g_bgmvolume = e.value
self.bgmvolumeslider:setValue(g_bgmvolume)
elseif e.id == 2 then -- sfx volume
g_sfxvolume = e.value
self.sfxvolumeslider:setValue(g_sfxvolume)
elseif e.id == 3 then -- difficulty
g_difficulty = e.value
self:difficulty(g_difficulty)
end
end
function Options:onValueChanging(e)
if e.id == 1 then -- bgm volume
g_bgmvolume = e.value
self.bgmvolumeslider:setValue(g_bgmvolume)
elseif e.id == 2 then -- sfx volume
g_sfxvolume = e.value
self.sfxvolumeslider:setValue(g_sfxvolume)
elseif e.id == 3 then -- difficulty
self:difficulty(e.value)
end
end
function Options:onValueChanged(e)
if e.id == 1 then -- bgm volume
g_bgmvolume = e.value
local audio = Sound.new("audio/ui/sfx_sounds_button1.wav")
local channel = audio:play() -- feedback
if channel then channel:setVolume(g_bgmvolume*0.01) end
self.bgmvolumeslider:setValue(g_bgmvolume)
elseif e.id == 2 then -- sfx volume
g_sfxvolume = e.value
local audio = Sound.new("audio/ui/sfx_sounds_button1.wav")
local channel = audio:play() -- feedback
if channel then channel:setVolume(g_sfxvolume*0.01) end
self.sfxvolumeslider:setValue(g_sfxvolume)
for _, v in pairs(self.btns) do v:setVolume(g_sfxvolume*0.01) end -- change the ui buttons sfx volume
elseif e.id == 3 then -- difficulty
g_difficulty = e.value
self:difficulty(g_difficulty)
end
mySavePrefs(g_configfilepath)
end
-- key remapping
function Options:remapKey(xbool)
local btn = self.btns[self.selector]
if xbool then btn:setColorTransform(255/255, 255/255, 0/255, 255/255)
else btn:setColorTransform(255/255, 255/255, 255/255, 255/255)
end
end
function Options:cancelKeyRemapping()
self.isremapping = false
self:remapKey(self.isremapping)
self:updateButton()
end
-- update button state
function Options:updateButton()
for k, v in ipairs(self.btns) do
v.currselector = self.selector
v:updateVisualState()
if self.iskeyboardnavigation then
if k == self.selector then v:selectionSfx() end
end
end
end
-- keys handler
function Options:myKeysPressed()
self:addEventListener(Event.KEY_DOWN, function(e)
if self.isremapping then -- KEY REMAPPING
if e.keyCode == KeyCode.ESC or e.keyCode == KeyCode.ENTER then
self:cancelKeyRemapping()
return
end
local keycode = e.keyCode
local key = keyNames[keycode]
if (keyNames[keycode] or 0) == 0 then key = utf8.char(keycode) end
self.btns[self.selector]:changeText(key)
if self.selector == 1 then g_keyup = keycode -- follows self.btns order
elseif self.selector == 2 then g_keyleft = keycode -- follows self.btns order
elseif self.selector == 3 then g_keyright = keycode -- follows self.btns order
elseif self.selector == 4 then g_keydown = keycode -- follows self.btns order
elseif self.selector == 5 then g_keyaction1 = keycode -- follows self.btns order
elseif self.selector == 6 then g_keyaction2 = keycode -- follows self.btns order
elseif self.selector == 7 then g_keyaction3 = keycode -- follows self.btns order
end
self:cancelKeyRemapping()
mySavePrefs(g_configfilepath)
else -- KEYBOARD NAVIGATION
if e.keyCode == KeyCode.ESC or e.keyCode == KeyCode.BACK then self:gotoScene() end
-- keyboard
if e.keyCode == KeyCode.UP or e.keyCode == g_keyup or e.keyCode == KeyCode.LEFT or e.keyCode == g_keyleft then
self.selector -= 1 if self.selector < 1 then self.selector = #self.btns end
self.iskeyboardnavigation = true
self:updateButton()
elseif e.keyCode == KeyCode.DOWN or e.keyCode == g_keydown or e.keyCode == KeyCode.RIGHT or e.keyCode == g_keyright then
self.selector += 1 if self.selector > #self.btns then self.selector = 1 end
self.iskeyboardnavigation = true
self:updateButton()
end
-- modifier
local modifier = application:getKeyboardModifiers()
local alt = (modifier & KeyCode.MODIFIER_ALT) > 0
if not alt and (e.keyCode == KeyCode.ENTER or e.keyCode == KeyCode.SPACE or e.keyCode == g_keyaction1) then -- validate
self:keyActions()
elseif alt and e.keyCode == KeyCode.ENTER then -- switch full screen
if not application:isPlayerMode() then
ismyappfullscreen = not ismyappfullscreen
application:setFullScreen(ismyappfullscreen)
end
end
end
end)
end
-- buttons actions
function Options:keyActions()
self.isremapping = false
for k, v in ipairs(self.btns) do
if k == self.selector then
if v.isdisabled then
print("btn disabled!", k)
elseif k >= 1 and k <= #self.btns-1 then -- action buttons
self.isremapping = true
self:remapKey(self.isremapping)
elseif k == #self.btns then -- MENU button
self:gotoScene()
else
print("nothing here!", k)
end
end
end
end
-- scenes navigation
function Options:gotoScene()
self:removeAllListeners()
for i = stage:getNumChildren(), 1, -1 do
stage:removeChildAt(i)
end collectgarbage()
stage:addChild(Transitions.new(Menu.new())) -- next scene
end
Code comments
In the init function we add some assets like a sound to play for the buttons when hovered.
We add our buttons:
- START goes to the game scene
- OPTIONS goes to the options scene
- EXIT exits the game
We use the g_difficulty value and transform it to text. The value transformed to text will serve as a tooltip. This is of course optional but I add it here for demonstration.
The buttons have many options to configure but they should be easy to implement/understand.
The numbers at the end of each buttons are selectors. They are the buttons id and are used for navigation. Please make sure to assign them accordingly.
All the buttons are put in a table which will serve navigating with the keyboard.
The buttons are then positioned and listeners are added.
updateButton
This function updates the button visual state and plays sound when navigating with the keyboard.
This function listens for KEY_DOWN events to navigate through the buttons with the keyboard.
The ESC key or the BACK button key on mobile will exit the app.
Pressing ALT+ENTER will make the game fullscreen.
Pressing SPACE, ENTER or g_keyaction1 will validate an action.
gotoScene
This function will either:
- do nothing if button is disabled
- navigate to the next scene corresponding to the button id
- exit the game
Next?
The final scene for this template: the Game scene.
Prev.: Tuto Gideros Game Template1 Part 5 Menu
Next: Tuto Gideros Game Template1 Part 7 Game
Tutorial - Gideros Game Template1