A Simple OOP Game Tutorial #5
WELCOME TO THE 4th INSTALLMENT OF ECS VS. OOP WEEKLY SERIES!
Before proceeding, if you have not read the previous post and previous part, please check those first, this post will wait for you. You will need the information and guide provided in the previous posts for this one.
Done? Good! You may now resume your quest!
UPDATES
Source code is now available here
DISCLAIMER
This post will not try to teach Lua coding or any programming logic. This is to focus primarily in OOP design and pattern.
If you want to learn basic programming and game development, let me know. I will make an in-depth guide about it for beginners if requested.
INTRODUCTION
Last time we only made additions in the backend, so no changes in the visual/front end. Still, here is what we have so far:
Last time, we made a scene
base class and other gamestates/screens subclasses. I know, the terms scene
, screen
, state
can be confusing, indeed they are, but for me, and especially with this tutorial, they all refer to the same thing, well at least the usage that is.
If you want to refactor the naming convention to be consistent, let me know and I shall do that.
But before we proceed, let us first go and add the following to the classes/base/scene.lua
:
function Scene:update(dt) end
function Scene:draw() end
function Scene:mousepressed(mx, my, mb) end
function Scene:keypressed(key) end
function Scene:get_id() return self.id end
They will kind of act like virtual
methods.
There is a lot of thing we need to change with classes/title_screen.lua
, so I suggest to completely erase every content of what we already have and paste the following instead:
local Scene = require("classes.base.scene")
local TitleScreen = Scene:extend()
local title = "Shape Clicker"
local str_play = "Press enter or space to play"
local str_quit = "Press escape to quit"
local font, font2
local window_width, window_height
function TitleScreen:new(manager)
TitleScreen.super.new(self, "title_screen", manager)
font = love.graphics.newFont(64)
font2 = love.graphics.newFont(18)
window_width, window_height = love.graphics.getDimensions()
return self
end
function TitleScreen:draw()
love.graphics.setColor(1, 0, 0, 1)
love.graphics.setFont(font)
love.graphics.print(title,
window_width/2, window_height/2 - 32,
0, 1, 1,
font:getWidth(title)/2,
font:getHeight(title)/2)
love.graphics.setFont(font2)
love.graphics.print(str_play,
window_width/2, window_height/2 + 64,
0, 1, 1,
font2:getWidth(str_play)/2,
font2:getHeight(str_play)/2)
love.graphics.print(str_quit,
window_width/2, window_height/2 + 96,
0, 1, 1,
font2:getWidth(str_quit)/2,
font2:getHeight(str_quit)/2)
end
function TitleScreen:keypressed(key)
if key == "enter" or key == "space" then
local GameScreen = require("classes.game_screen")
self.manager.switch(GameScreen)
elseif key == "escape" then
love.event.quit()
end
end
return TitleScreen
Also, for all the subclasses of the scene, namely game_screen.lua
. gameover_screen.lua
, pause_screen.lua
, title_screen.lua
, make the following additions:
function XXXXXX:new(manager)
--before the `end` keyword
return self --add this
end
IMPLEMENTING GAMESTATE MANAGER
Now, to test the gamestates we have done, let us make a gamestate manager module. This manager module will be a singleton, meaning, we will not create any instance of it, this is kind of like static
in C/C++ concepts.
Why do this? Because we do not need to make any instance of it. We only want one to use this as general as possible.
Now, there is a lot of gamestate manager library out there which are far better than what I am going to provide, what I am going to make is hacky as just fit for this tutorial.
My aim is not to teach the concept of gamestate management or how to properly do it, in my personal projects I have one that is complex and has a lot of features suitable for the kind of game I am making, abstracting it away from the project will be a hassle for now so for now, bear with this ugly gamestate manager I am going to show.
Create a file in the root of the project (not inside the classes/
folder) named gamestate_manager.lua
and write the following:
local GamestateManager = {}
local states = {}
local current_state
local previous_state
function GamestateManager.init(state)
current_state = state:new(GamestateManager)
current_state:set_active(true)
table.insert(states, current_state)
end
function GamestateManager.switch(next_state)
previous_state = current_state
current_state = next_state:new(GamestateManager)
current_state:set_active(true)
table.insert(states, current_state, 1)
print("Switched from " .. previous_state:get_id() .. " to " .. current_state:get_id())
end
function GamestateManager.update(dt)
for _, state in ipairs(states) do
if state:get_active() then
state:update(dt)
end
end
end
function GamestateManager.draw()
for _, state in ipairs(states) do
if state:get_active() then
state:draw()
end
end
end
function GamestateManager.mousepressed(mx, my, mb)
for _, state in ipairs(states) do
if state:get_active() then
state:mousepressed(mx, my, mb)
end
end
end
function GamestateManager.keypressed(key)
for _, state in ipairs(states) do
if state:get_active() then
state:keypressed(key)
end
end
end
return GamestateManager
TESTING
To test that that manager is indeed working, rename the main.lua
file into whatever you like, in my case I renamed it to main_temp.lua
. After that, create another file called main.lua
and put the following:
local GamestateManager = require("gamestate_manager")
function love.load()
love.graphics.setBackgroundColor(1, 1, 1, 0.8)
local title_screen = require("classes.title_screen")
GamestateManager.init(title_screen)
end
function love.update(dt)
GamestateManager.update(dt)
end
function love.draw()
GamestateManager.draw()
end
function love.mousepressed(mx, my, mb)
GamestateManager.mousepressed(mx, my, mb)
end
function love.keypressed(key)
GamestateManager.keypressed(key)
end
And then run the game, you will only see the title of our game in the center of the screen. Sweet!
Now press enter
or space
and voila! We just switched to a new state! (see terminal/console for the printed output)
This is it for now! Again, pardon for the short post. Time and schedule is kinda tricky right now.
Next Week’s Post:
For the next post, we will continue to:
- implement more states
- implement the UI classes and subclasses
Stay tuned via RSS or follow me on Twitter