Samugari Dev

Postmortem of my Love2d game

Recently I had an idea for a new game. The idea was somewhat similar to Balatro, so I looked up what engine was used to make that game and found that it used an engine called "Love2d"

Love2d is a very simple engine, it provides a Lua library for drawing and other common game functionality and that's it. No UI or anything.

And so I started making the game.

At first I was amazed at how easy Love2d was to use and I very quickly got something playable up and running.

But it did not take long before I started running into problems.

Separate objects

I started by writing all the code for drawing the game objects in the draw() function of the main script.

This quickly got messy, so I split out the different objects into separate scripts so that the main draw function looked more like this.

function love.draw()
    score.draw()
    gameBoard.draw()
    stageclear.draw()
end
            

Clicking buttons

I wanted to add some buttons. But found that there is no built-in "onclick" functionality. Checking if a button is clicked involves checking the position of the mouse against the bounds of the button.

if x >= button.x and x <= button.x + button.w and
   y >= button.y and y <= button.y + button.h
then
    button:onClick()
    return
end
            

Alright problem solved.

But wait, I don't want to have to manually call this on all buttons.

Multiple buttons

My solution was to add them all into a list and run through the list every time the mouse is clicked.

function Clickable.mousepressed(x,y,button)
    if button == 1 then
        for _, clickable in pairs(allClickables) do
            if x >= clickable.x and x <= clickable.x + clickable.w and
               y >= clickable.y and y <= clickable.y + clickable.h
            then
                clickable:onClick()
                return
            end
        end
    end
end
            

This worked for a while. But then I added a pop-up menu.

Hidden buttons

After doing so, I started noticing strange behavior, sometimes the game would do some action seemingly without a cause.

I figured out the problem; I was clicking the buttons from the pop-up even though the buttons were not drawn.

Alright, I added functions to register and unregister buttons when they came into view.

function Clickable:register()
    allClickables[self.id] = self
end

function Clickable:unregister()
    allClickables[self.id] = nil
end
            

This worked, although having to remember to register/unregister the buttons was kind of a pain.

At this point, the game was already quite complicated, but I had only been working on the main gameplay.

Then I wanted to add a new screen, a shop.

Scenes

I had implemented all the drawing logic in individual scripts for the different UI elements, but it was all drawn from the main script.

I realized that I had to make some kind of scene system. I ended up making a scene script for each scene in the game and an interface that all the scenes implemented that looked something like this.

function Scene_shop.onLoad()

end

function Scene_shop.onUnload()

end

function Scene_shop.onUpdate(dt)

end

function Scene_shop.onDraw()

end      
            

These functions would be routed from the main script based on which scene was being shown.

Now, the logic of each scene could be written separately, and the load/unload could take care of setting up the scene, including registering buttons.

UI scaling and reusable objects

By default, all drawing in love2d is based on pixels. This means that if you resize the screen, the game will not resize.

Another problem was that the draw() functions for the objects were hardcoded at certain positions.

And so, I went on to solve these two problems as so

local x = Ui.s(5)
local y = Ui.s(20)
UiGems.draw(x, y)                
            

The problem of scaling was solved by making the UI.s() utility function which calculated a number that scaled with the size of the screen.

The problem of object positioning was solved by passing coordinates for where the object should be drawn to the drawing functions of the objects.

Animations

I had added animations to some of the objects in the game. But the animation logic was kind of mixed in with the rest of the logic of the object which had become quite messy.

My solution was to make a new "animation" type of script that would create an "offset" that would be added to the object before drawing it, like so.

function Anim_BounceUp:draw(offset)
    local yOffset = 0
    if self.playing then
        -- Simple sine wave animation
        yOffset = -math.sin(self.time * math.pi) * self.bounceHeight
    end

    offset.y = offset.y + yOffset
end                
            

Burnout

At this point I had spent a good amount of effort simply adding some basic gameplay and setting up the framework.

I felt like every time I wanted to do something, I had to invent a way to do it from the bottom up.

In short, it felt more like I was the one making the game engine.

Especially making UI without something like CSS felt very tedious.

I ended up losing interest and moving on to other projects.

But I think that if I started over with the mindset that I had to start by creating a framework for the game, I could make something good.

I like thinking about these kinds of high-level architectural problems, so it might be a fun challenge for another time.

Copyright © 2025.