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__
  
== The Systems 3 ==
+
== You Win ==
A couple more systems to add and we are done. We also add the '''Debug Systems''' to help us visualize all the boxes (hitbox, hurtbox, collsion box, ...).
+
A game wouldn't be finished without a Win Scene. Let's add it!
  
== sDestructibleObjects.lua ==
+
Please create a file "'''win.lua'''" in the '''"scenes"''' folder and the code:
Please create a file "'''sDestructibleObjects.lua'''" in the '''"_S"''' folder and the code:
 
 
<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>
  
The System removes a breakable object when destroyed and spawn a collectible in the '''onRemove''' function:
+
== Code comments ==
* it runs every frame
+
A MovieClip that will play in an infinite loop until the user leaves the scene:
* it affects only entities with an ''isdestructibleobject'' id
+
* we fetch all the sequenced images in the given path
* on hurt adds an hit fx
+
* using a loop we add a number at the end of the truncated path
* on destroyed adds particles
+
* the sequenced images are stored in the ''frames'' table
* '''onRemove''' spawns a collectible
+
* using the ''frames'' table we create our timeline in the ''anims'' table
 +
* finally we can use the timeline to create the MovieClip
  
== sCollectible.lua ==
+
A typewriter effect. Please add "'''typewriter.lua'''" in the '''"classes"''' folder. The code:
"'''sCollectible.lua'''" in the '''"_S"''' folder. The code:
 
 
<syntaxhighlight lang="lua">
 
<syntaxhighlight lang="lua">
</syntaxhighlight>
+
--[[ 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
 +
]]
  
This System spawns a collectible:
+
TypeWriter = Core.class(TextField)
* runs once on init and '''every game loop''' (''process'')
 
* there are two kind of collectibles: health and jump attacks (updated in the HUD)
 
  
'''There are quite a bit of commented code you can delete as this Entity is immediately removed'''
+
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
  
== sSpritesSorting.lua.lua ==
+
local function getString()
"'''sSpritesSorting.lua.lua'''" in the '''"_S"''' folder. The code:
+
if i <= str_length then
<syntaxhighlight lang="lua">
+
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>
  
Finally this System sorts the actors on the y axis:
+
And a classic button to navigate to the Menu Scene.
* runs once on init and '''every game loop''' (''process'')
 
* there is a distinction between the actor being on floor and jumping
 
  
== Next? ==
+
== Going further ==
'''Congratulations'''! We have finished our game.
+
'''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, ...
  
Let's add the final scene: '''YOU WIN'''!
+
'''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>
'''Next: [[Tuto tiny-ecs beatemup Part 13 XXX]]'''
+
 
 +
'''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


Tutorial - tiny-ecs beatemup