Difference between revisions of "Tuto tiny-ecs beatemup Part 12 You Win"
From GiderosMobile
(wip) |
|||
(3 intermediate revisions by the same user not shown) | |||
Line 1: | Line 1: | ||
__TOC__ | __TOC__ | ||
− | == | + | == You Win == |
− | A | + | A game wouldn't be finished without a Win Scene. Let's add it! |
− | + | Please create a file "'''win.lua'''" in the '''"scenes"''' folder and the code: | |
− | Please create a file "''' | ||
<syntaxhighlight lang="lua"> | <syntaxhighlight lang="lua"> | ||
+ | Win = Core.class(Sprite) | ||
+ | |||
+ | function Win:init() | ||
+ | -- bg | ||
+ | application:setBackgroundColor(g_ui_theme.backgroundcolor) | ||
+ | -- we create a movie clip to spice up the win scene | ||
+ | local frames = {} | ||
+ | local imagefilenametrunc = "gfx/player1/win/party-m-0001m_" | ||
+ | local imgnumstartat, imgnumendat = 1, 44 -- image name sequence (eg.: image004.jpg, image005.jpg, ...) | ||
+ | for i = 1, imgnumendat-imgnumstartat+1 do | ||
+ | local iter = imgnumstartat+i-1 | ||
+ | if iter < 10 then | ||
+ | frames[#frames+1] = Bitmap.new(Texture.new(imagefilenametrunc.."000"..(imgnumstartat+i-1)..".png", true)) | ||
+ | elseif iter < 100 then | ||
+ | frames[#frames+1] = Bitmap.new(Texture.new(imagefilenametrunc.."00"..(imgnumstartat+i-1)..".png", true)) | ||
+ | elseif iter < 1000 then | ||
+ | frames[#frames+1] = Bitmap.new(Texture.new(imagefilenametrunc.."0"..(imgnumstartat+i-1)..".png", true)) | ||
+ | else | ||
+ | frames[#frames+1] = Bitmap.new(Texture.new(imagefilenametrunc..(imgnumstartat+i-1)..".png", true)) | ||
+ | end | ||
+ | frames[i]:setAnchorPoint(0.5, 0.5) | ||
+ | end | ||
+ | -- mc timelines | ||
+ | local anims = {} | ||
+ | local timing = 8 | ||
+ | for i = 1, #frames do anims[i] = {(i-1)*timing+1, i*timing, frames[i]} end | ||
+ | -- add anim frames | ||
+ | local mc_win = MovieClip.new(anims, true) | ||
+ | mc_win:setGotoAction(imgnumendat*timing, imgnumstartat*timing) | ||
+ | mc_win:gotoAndPlay(imgnumstartat*timing) | ||
+ | -- typewriter effect | ||
+ | local text = [[ | ||
+ | You defeated the bad boyz and made the city a better place. | ||
+ | |||
+ | Game made using Gideros framework (what else?). | ||
+ | |||
+ | Programmer: mokalux 2024 ;-) | ||
+ | ]] | ||
+ | local tw = TypeWriter.new(myttf, text, 32*8, 3) -- TypeWriter:init(font, text, delay, char) | ||
+ | tw:setTextColor(0x55ff00) | ||
+ | tw:setLayout( { w=myappwidth/1.8, flags=FontBase.TLF_CENTER } ) | ||
+ | tw:addEventListener("finished", function(e) | ||
+ | end) | ||
+ | -- buttons setup | ||
+ | local sndbtn = {sound=Sound.new("audio/ui/sfx_sounds_button1.wav"), time=0, delay=0.2} | ||
+ | local sfxvolume = g_sfxvolume * 0.01 | ||
+ | local tooltiplayer = Sprite.new() | ||
+ | -- buttons (only one button) | ||
+ | local mybtnmenu = ButtonMonster.new({ | ||
+ | autoscale=false, pixelwidth=20*8, pixelheight=8*8, | ||
+ | pixelscalexup=0.8, pixelscalexdown=0.9, | ||
+ | pixelcolorup=g_ui_theme.pixelcolorup, pixelcolordown=g_ui_theme.pixelcolordown, | ||
+ | text="MENU", ttf=myttf, textcolorup=g_ui_theme.textcolorup, textcolordown=g_ui_theme.textcolordown, | ||
+ | sound=sndbtn, volume=sfxvolume, | ||
+ | }, 1, tooltiplayer) | ||
+ | -- buttons table for keyboard navigation | ||
+ | self.btns = {} | ||
+ | self.btns[#self.btns + 1] = mybtnmenu | ||
+ | self.selector = 1 -- starting button | ||
+ | -- position | ||
+ | mc_win:setPosition(3.5*myappwidth/16, 9*myappheight/16) | ||
+ | tw:setPosition(5*myappwidth/16, 1*myappheight/16) | ||
+ | mybtnmenu:setPosition(8*myappwidth/16, 14*myappheight/16) | ||
+ | -- order | ||
+ | self:addChild(mc_win) | ||
+ | self:addChild(tw) | ||
+ | for k, v in ipairs(self.btns) do self:addChild(v) end | ||
+ | self:addChild(tooltiplayer) | ||
+ | -- buttons listeners | ||
+ | for k, v in ipairs(self.btns) do | ||
+ | v:addEventListener("clicked", function(e) self.selector = k switchToScene(Menu.new()) end) -- Menu | ||
+ | v:addEventListener("hovered", function(e) self.selector = e.currselector end) | ||
+ | v.btns = self.btns -- for keyboard navigation | ||
+ | end | ||
+ | -- let's go | ||
+ | local function fun() | ||
+ | -- called async otherwise may crash the app | ||
+ | self:updateButtons() | ||
+ | Core.yield(1) | ||
+ | end | ||
+ | Core.asyncCall(fun) | ||
+ | self:myKeysPressed() | ||
+ | end | ||
+ | |||
+ | -- update buttons state | ||
+ | function Win:updateButtons() | ||
+ | for k, v in ipairs(self.btns) do | ||
+ | v.currselector = self.selector | ||
+ | v:updateVisualState() | ||
+ | if k == self.selector then v:selectionSfx() end -- play sound on keyboard navigation | ||
+ | end | ||
+ | end | ||
+ | |||
+ | -- keyboard navigation | ||
+ | function Win:myKeysPressed() | ||
+ | self:addEventListener(Event.KEY_DOWN, function(e) | ||
+ | -- keyboard navigation | ||
+ | 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:updateButtons() | ||
+ | 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:updateButtons() | ||
+ | -- Menu | ||
+ | elseif e.keyCode == KeyCode.SPACE or e.keyCode == g_keyaction1 then switchToScene(Menu.new()) | ||
+ | elseif e.keyCode == KeyCode.ESC then switchToScene(Menu.new()) | ||
+ | end | ||
+ | -- modifiers | ||
+ | local modifier = application:getKeyboardModifiers() | ||
+ | local alt = (modifier & KeyCode.MODIFIER_ALT) > 0 | ||
+ | -- Menu | ||
+ | if not alt and e.keyCode == KeyCode.ENTER then switchToScene(Menu.new()) | ||
+ | -- switch fullscreen | ||
+ | elseif alt and e.keyCode == KeyCode.ENTER then | ||
+ | if not application:isPlayerMode() then | ||
+ | ismyappfullscreen = not ismyappfullscreen | ||
+ | application:setFullScreen(ismyappfullscreen) | ||
+ | end | ||
+ | end | ||
+ | end) | ||
+ | end | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | + | == Code comments == | |
− | * | + | A MovieClip that will play in an infinite loop until the user leaves the scene: |
− | * | + | * we fetch all the sequenced images in the given path |
− | + | * using a loop we add a number at the end of the truncated path | |
− | + | * the sequenced images are stored in the ''frames'' table | |
− | * ''' | + | * using the ''frames'' table we create our timeline in the ''anims'' table |
+ | * finally we can use the timeline to create the MovieClip | ||
− | + | A typewriter effect. Please add "'''typewriter.lua'''" in the '''"classes"''' folder. The code: | |
− | "''' | ||
<syntaxhighlight lang="lua"> | <syntaxhighlight lang="lua"> | ||
− | + | --[[ TextField TypeWriter Effect | |
+ | -- based on the work of @pie, @koeosstudio, @hgy29, mixed by @mokalux ;-) | ||
+ | -- function TypeWriter:init(font, text, speed, char) | ||
+ | ---- font: Font, text: string, speed: number (millisecond), char: the number of characters to display at a time, default = 1 | ||
+ | -- return event: | ||
+ | ---- "finished", data: speed, data: char | ||
+ | ]] | ||
− | + | TypeWriter = Core.class(TextField) | |
− | |||
− | |||
− | + | function TypeWriter:init(font, text, delay, char) | |
+ | self:setVisible(false) -- start invisible | ||
+ | local i = 0 | ||
+ | local in_str = text | ||
+ | local str_length = utf8.len(in_str) | ||
+ | local typeSpeedTimer = Timer.new(delay or 100, str_length) | ||
+ | char = char or 1 | ||
− | == | + | local function getString() |
− | + | if i <= str_length then | |
− | + | i += char -- number of characters to add each time | |
+ | local out_str = utf8.sub(in_str, 1, i) | ||
+ | self:setText(out_str) | ||
+ | self:setVisible(true) | ||
+ | else | ||
+ | local ev = Event.new("finished") | ||
+ | ev.delay = delay | ||
+ | ev.char = char | ||
+ | self:dispatchEvent(ev) | ||
+ | end | ||
+ | end | ||
+ | typeSpeedTimer:addEventListener(Event.TIMER, getString) | ||
+ | typeSpeedTimer:start() | ||
+ | end | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | + | And a classic button to navigate to the Menu Scene. | |
− | |||
− | |||
− | == | + | == Going further == |
− | '''Congratulations'''! | + | '''Congratulations'''! You finished the tutorial. Wanna go further? |
+ | * add more levels | ||
+ | * add more enemies | ||
+ | * add a keybuffering system for combo attacks | ||
+ | * play with the values, ... | ||
− | + | '''The game is yours to master!!!''' | |
+ | |||
+ | == The Project file == | ||
+ | The project file being too big for the Wiki, you can find it on '''GitHub''': | ||
+ | * '''https://github.com/mokalux/gideros_wiki_repository/tree/main/tuto_beu_cbump_tecs''' | ||
+ | Click on '''tuto_beu_cbump_tecs.zip''' then on "'''Download raw file'''". | ||
+ | |||
+ | '''PS''': the final project may differ a little bit from the tuto ;-) | ||
Prev.: [[Tuto tiny-ecs beatemup Part 11 Systems 3]]</br> | Prev.: [[Tuto tiny-ecs beatemup Part 11 Systems 3]]</br> | ||
− | ''' | + | |
+ | '''END''' | ||
'''[[Tutorial - tiny-ecs beatemup]]''' | '''[[Tutorial - tiny-ecs beatemup]]''' | ||
{{GIDEROS IMPORTANT LINKS}} | {{GIDEROS IMPORTANT LINKS}} |
Latest revision as of 02:34, 25 November 2024
You Win
A game wouldn't be finished without a Win Scene. Let's add it!
Please create a file "win.lua" in the "scenes" folder and the code:
Win = Core.class(Sprite)
function Win:init()
-- bg
application:setBackgroundColor(g_ui_theme.backgroundcolor)
-- we create a movie clip to spice up the win scene
local frames = {}
local imagefilenametrunc = "gfx/player1/win/party-m-0001m_"
local imgnumstartat, imgnumendat = 1, 44 -- image name sequence (eg.: image004.jpg, image005.jpg, ...)
for i = 1, imgnumendat-imgnumstartat+1 do
local iter = imgnumstartat+i-1
if iter < 10 then
frames[#frames+1] = Bitmap.new(Texture.new(imagefilenametrunc.."000"..(imgnumstartat+i-1)..".png", true))
elseif iter < 100 then
frames[#frames+1] = Bitmap.new(Texture.new(imagefilenametrunc.."00"..(imgnumstartat+i-1)..".png", true))
elseif iter < 1000 then
frames[#frames+1] = Bitmap.new(Texture.new(imagefilenametrunc.."0"..(imgnumstartat+i-1)..".png", true))
else
frames[#frames+1] = Bitmap.new(Texture.new(imagefilenametrunc..(imgnumstartat+i-1)..".png", true))
end
frames[i]:setAnchorPoint(0.5, 0.5)
end
-- mc timelines
local anims = {}
local timing = 8
for i = 1, #frames do anims[i] = {(i-1)*timing+1, i*timing, frames[i]} end
-- add anim frames
local mc_win = MovieClip.new(anims, true)
mc_win:setGotoAction(imgnumendat*timing, imgnumstartat*timing)
mc_win:gotoAndPlay(imgnumstartat*timing)
-- typewriter effect
local text = [[
You defeated the bad boyz and made the city a better place.
Game made using Gideros framework (what else?).
Programmer: mokalux 2024 ;-)
]]
local tw = TypeWriter.new(myttf, text, 32*8, 3) -- TypeWriter:init(font, text, delay, char)
tw:setTextColor(0x55ff00)
tw:setLayout( { w=myappwidth/1.8, flags=FontBase.TLF_CENTER } )
tw:addEventListener("finished", function(e)
end)
-- buttons setup
local sndbtn = {sound=Sound.new("audio/ui/sfx_sounds_button1.wav"), time=0, delay=0.2}
local sfxvolume = g_sfxvolume * 0.01
local tooltiplayer = Sprite.new()
-- buttons (only one button)
local mybtnmenu = ButtonMonster.new({
autoscale=false, pixelwidth=20*8, pixelheight=8*8,
pixelscalexup=0.8, pixelscalexdown=0.9,
pixelcolorup=g_ui_theme.pixelcolorup, pixelcolordown=g_ui_theme.pixelcolordown,
text="MENU", ttf=myttf, textcolorup=g_ui_theme.textcolorup, textcolordown=g_ui_theme.textcolordown,
sound=sndbtn, volume=sfxvolume,
}, 1, tooltiplayer)
-- buttons table for keyboard navigation
self.btns = {}
self.btns[#self.btns + 1] = mybtnmenu
self.selector = 1 -- starting button
-- position
mc_win:setPosition(3.5*myappwidth/16, 9*myappheight/16)
tw:setPosition(5*myappwidth/16, 1*myappheight/16)
mybtnmenu:setPosition(8*myappwidth/16, 14*myappheight/16)
-- order
self:addChild(mc_win)
self:addChild(tw)
for k, v in ipairs(self.btns) do self:addChild(v) end
self:addChild(tooltiplayer)
-- buttons listeners
for k, v in ipairs(self.btns) do
v:addEventListener("clicked", function(e) self.selector = k switchToScene(Menu.new()) end) -- Menu
v:addEventListener("hovered", function(e) self.selector = e.currselector end)
v.btns = self.btns -- for keyboard navigation
end
-- let's go
local function fun()
-- called async otherwise may crash the app
self:updateButtons()
Core.yield(1)
end
Core.asyncCall(fun)
self:myKeysPressed()
end
-- update buttons state
function Win:updateButtons()
for k, v in ipairs(self.btns) do
v.currselector = self.selector
v:updateVisualState()
if k == self.selector then v:selectionSfx() end -- play sound on keyboard navigation
end
end
-- keyboard navigation
function Win:myKeysPressed()
self:addEventListener(Event.KEY_DOWN, function(e)
-- keyboard navigation
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:updateButtons()
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:updateButtons()
-- Menu
elseif e.keyCode == KeyCode.SPACE or e.keyCode == g_keyaction1 then switchToScene(Menu.new())
elseif e.keyCode == KeyCode.ESC then switchToScene(Menu.new())
end
-- modifiers
local modifier = application:getKeyboardModifiers()
local alt = (modifier & KeyCode.MODIFIER_ALT) > 0
-- Menu
if not alt and e.keyCode == KeyCode.ENTER then switchToScene(Menu.new())
-- switch fullscreen
elseif alt and e.keyCode == KeyCode.ENTER then
if not application:isPlayerMode() then
ismyappfullscreen = not ismyappfullscreen
application:setFullScreen(ismyappfullscreen)
end
end
end)
end
Code comments
A MovieClip that will play in an infinite loop until the user leaves the scene:
- we fetch all the sequenced images in the given path
- using a loop we add a number at the end of the truncated path
- the sequenced images are stored in the frames table
- using the frames table we create our timeline in the anims table
- finally we can use the timeline to create the MovieClip
A typewriter effect. Please add "typewriter.lua" in the "classes" folder. The code:
--[[ TextField TypeWriter Effect
-- based on the work of @pie, @koeosstudio, @hgy29, mixed by @mokalux ;-)
-- function TypeWriter:init(font, text, speed, char)
---- font: Font, text: string, speed: number (millisecond), char: the number of characters to display at a time, default = 1
-- return event:
---- "finished", data: speed, data: char
]]
TypeWriter = Core.class(TextField)
function TypeWriter:init(font, text, delay, char)
self:setVisible(false) -- start invisible
local i = 0
local in_str = text
local str_length = utf8.len(in_str)
local typeSpeedTimer = Timer.new(delay or 100, str_length)
char = char or 1
local function getString()
if i <= str_length then
i += char -- number of characters to add each time
local out_str = utf8.sub(in_str, 1, i)
self:setText(out_str)
self:setVisible(true)
else
local ev = Event.new("finished")
ev.delay = delay
ev.char = char
self:dispatchEvent(ev)
end
end
typeSpeedTimer:addEventListener(Event.TIMER, getString)
typeSpeedTimer:start()
end
And a classic button to navigate to the Menu Scene.
Going further
Congratulations! You finished the tutorial. Wanna go further?
- add more levels
- add more enemies
- add a keybuffering system for combo attacks
- play with the values, ...
The game is yours to master!!!
The Project file
The project file being too big for the Wiki, you can find it on GitHub:
Click on tuto_beu_cbump_tecs.zip then on "Download raw file".
PS: the final project may differ a little bit from the tuto ;-)
Prev.: Tuto tiny-ecs beatemup Part 11 Systems 3
END