Category: DevLog

  • DevLog 3: Colliders Part 1

    Spoiler Warning: By the end of this first post, I still haven’t actually handled any collisions.

    This post isn’t going to be as straight forward as my previous posts. I’ve rewritten this a few times, and decided just a consciousness dump might be good for me, and good for others to understand that things aren’t always clear or easy. I might want to have a different tag for stream of consciousness posts like this.

    I’m going to start turning this whole project into a bunch of teeny programs to prove concepts. My project is getting cluttered with junk and it doesn’t even really do anything, yet. The good news is that there’s going to be a git repository that sort of grows and changes as the project goes on, and maybe turns into a tool someone can use to learn from, or at very least, something for me to look back on and be like “what was I thinking?”

    As I said in a previous post, I’m trying to implement as much as I can without using the Playdate SDK. If you’re here to learn how to use the Playdate’s built in colliders, head over to the official SDK. The link changes often so your best bet is to just go to https://play.date/dev/ and seach “Sprite collision detection”, and while you’re at it, you can look at the library they based their collision detection on. https://github.com/kikito/bump.lua . The SDK is wonderful, I’m here’s to learn how to do as much of this as I can myself.

    Now that we got that out of the way, let’s see how bad I am at implementing this myself. I’ve read a bulk of the bump library, and I’ve cried when reading Game Engine Architecture by Jason Gregory so I can probably do this. I also read the entire source code for Celeste on the Pico8.

    My original version of this game, the engine handled all this for me, so I’m feeling a little overwhelmed at having such a blank slate, freedom is scary.

    The Plan

    This game doesn’t require anything too fancy, so I’m going to start out with two types of collisions.

    Tilemap

    AABB (Axis-Aligned Bounding Box)

    That’s it.

    Tilemap

    Tilemap is going to work like this. The screen / level will be split into a grid of 8×8 pixels. Each tile will have a flag associated. These flags will be “solid”, “not-solid” to start out. I’ll likely add different things to have “disappears when player touches it”, and one way platforms, special static blocks and things like that, but we’ll burn that bridge when we get there.

    The benefit of a tilemap is having to do less collision checks.

    TILEMAP IMAGE

    Pros: It doesn’t matter if there’s a million tiles, or 1 tile, the amount of collision checks is constant. Now, that image is a little misleading, you only need to check for collisions in the direction you’re moving, and might need to check for more than 1 collision in a specific direction if the object is bigger than a tile. That being said, it’s still better than checking against every single possible thing in a scene.

    Cons: Objects can’t move (less than the size of a tile).

    Now take a minute, think about how you would implement this. If you haven’t read the post on bit, bytes and data types https://blog.lodomo.dev/2025/04/20/cs-bits-bytes-and-data-types-in-lua/ check it out. There’s loads of ways we can abstract the map.

    What if each tile just had a number? that gives us 4,294,967,296 options for each tile. If we could store it in chars, that would be nice, but iterating through strings in Lua isn’t very efficient. Let’s check out some tests.

    Let’s do a little test on a single screen.

    X_BOOL_50x30 = {}
    for i = 1, 30 do
        X_BOOL_50x30[i] = {}
        for j = 1, 50 do
            X_BOOL_50x30[i][j] = false
        end
    end
    
    X_NUM_50x30 = {}
    for i = 1, 30 do
        X_NUM_50x30[i] = {}
        for j = 1, 50 do
            X_NUM_50x30[i][j] = 0
        end
    end
    
    X_STRING_50x30 = {}
    for i = 1, 30 do
        X_STRING_50x30[i] = "00000000000000000000000000000000000000000000000000"
    end
    
    A screenshot of a dataset preview showing three labeled datasets:

    X_BOOL_50x30: 1530 items, total size 33,712

    X_NUM_50x30: 1530 items, total size 33,712

    X_STRING_50x30: 30 items, total size 3,082

    So now there’s 50 tiles * 30 tiles * 30 tables (one for each row) for the bool and num tables. That takes a whopping 33,712 bytes of memory for a single screen, just for flag data.

    The table of strings is just 30 items, 30 strings each one 50 chars long. It’s less than 1/10th of the size of the number/boolean tables and still has 255 possible tile options per tile.

    Let’s test how well lua can go through all these and see if that overhead is worth it or not.

    I loop through and get the value of every data member, and do nothing with it, this is purely just a table lookup. Then it prints to the screen what the average time for 1500 (50×30) lookups.

    The image shows a yellow handheld gaming device, the Playdate, with performance benchmark results displayed on its screen. The screen displays the following text:

Bool: 3.384577 ms  
Num: 3.53881 ms  
String: 13.25907 ms

    Ooof. Welp, I’m a little concerned. For strings, that’s still a very very small amount of time per look up (0.0085~ ms) but it’s still about 4x slower than numbers.

    “The real problem is that programmers have spent far too much time worrying about efficiency in the wrong places and at the wrong times; premature optimization is the root of all evil (or at least most of it) in programming.” – Donald Knuth

    Look, Don, I just don’t want this slowing me down later that my tile maps are causing problems.

    I have 16,000,000 bytes to play with in RAM, a full screen tilemap is 33,000? maybe it’s worth the overhead to have faster lookups, and just be better about loading on the fly. The time to render each frame is around 20-30 milliseconds, and that is a much more precious resource.

    Now onto the collisions…

    The Code

    So I think in a final version the logic will be a bit different to keep objects to a more defined job they handle, but this will be something that works. Make it exist, then make it good.

    Creates a tile map that has colliders 1 tile in from the edge, all the way around.

        for row = 1, ROWS do
            TILE_MAP[row] = {}
            for col = 1, COLS do
                if row == 2 or row == ROWS - 1 or col == 2 or col == COLS - 1 then
                    TILE_MAP[row][col] = 1
                else
                    TILE_MAP[row][col] = 0
                end
            end
        end
    

    Color in those tiles

        for row = 1, #TILE_MAP do
    for col = 1, #TILE_MAP[row] do
    local tile = TILE_MAP[row][col]
    if tile == 1 then
    gfx.fillRect((col - 1) * CELL, (row - 1) * CELL, CELL, CELL)
    end
    end
    end

    Now we need a moving object.

    PLAYER = {
        x = 120,
        y = 120,
        width = 16,
        height = 16,
        velocity = {
            x = 1,
            y = 1,
        },
        draw = function(self)
            gfx.fillRect(self.x, self.y, self.width, self.height) -- x, y, width, height
        end,
        update = function(self)
            self.x = self.x + self.velocity.x
            self.y = self.y + self.velocity.y
        end,
    }
    

    Right now there is no tile checking, but I wanted to make sure this works. Test at every step.

        drawVelocity = function(self)
            local l_x = self.x
            local l_y = self.y
            local r_x = self.x + self.width
            local r_y = self.y + self.height
    
            gfx.drawLine(l_x, l_y, l_x + self.velocity.x, l_y + self.velocity.y)
            gfx.drawLine(r_x, l_y, r_x + self.velocity.x, l_y + self.velocity.y)
            gfx.drawLine(l_x, r_y, l_x + self.velocity.x, r_y + self.velocity.y)
            gfx.drawLine(r_x, r_y, r_x + self.velocity.x, r_y + self.velocity.y)
        end,
    

    Ok, so now these lines will be where the next frame will be. I made the speed 4 pixels per frame, going at a 45 degree angle down and right.

    Since we have the line of the direction were going it helps me visualize what I need to do, and then start to process that into logic. In psuedo code:

    If the velocity in the x direction is positive:
    check along the right side of the new position
    If the velocity in the x direction is negative:
    check along the left side of the new position
    If the velocity is the x direction is neutral:
    there cannot be new x-direction collision, do nothing.

    If the velocity in the y direction is positive:
    check along the bottom of the new position
    If the velocity in the y direction is negative:
    check along the top of the new position
    If the velocity in the y direction is neutral:
    there cannot be new y-direction collision, do nothing.

    AAAND this is where I think I’m going to end it this week. I’d rather give you all little updates as I finish them than nothing. Keeps me honest to keep working.

    Until Next Time,

    Lodomo

  • DevLog 2: Implementing Classes

    DevLog 2: Implementing Classes

    For a platformer, the most important element is how it feels. If the controls aren’t responsive or intuitive, nothing else matters. Good controls require solid physics. Good physics require accurate collisions. Accurate collisions require a well-structured architecture. Right now, I’m focused on building that structure from the ground up.

    Lua wasn’t taught at my university, so I’m learning it alongside the Playdate SDK. While the SDK is well-made, I’m intentionally minimizing my use of it. This is not a criticism of Panic’s work. I want to implement the systems myself to fully understand each part.

    Classes in Lua

    I’m using to using object-oriented programming, and Lua doesn’t have the idea of classes as part of it’s core structure. I need to abstract classes manually. It makes sense for most things in side the game to be objects. I don’t need to stick strictly to this paradigm, but I think it’s a great place to start.

    I initially wanted to just write closures to simulate object, wrapping everything into a function of functions. The code is super clean, there’s no silly colons. It would not matter at all on a modern system, but dealing with only 16mb of ram, I’d rather get rid of this overhead right away.

    I used object.lua from the Playdate SDK’s CoreLibs as a reference. It provides a working class system, but includes methods I either didn’t need or didn’t fully understand. I’ve kept the bare minimum and will push code up and down as needed.

    Class = {}
    Class.__index = Class
    
    function Class:new()
        local obj = setmetatable({}, self)
        return obj
    end
    
    function Class:print()
        for k, v in pairs(self) do
            print(k, v)
        end
    end

    So this makes a “Class” table in the global space. Using Class:new() will clone the data members of the table (in this case there are none), and give reference to the “print” method so that only gets created once.

    Every single class will derive from “class” so every single object will have a obj:print() to dump all the data members and methods if I need to use it for debugging.

    Point = {}
    Point.__index = Point
    setmetatable(Point, { __index = Class })
    
    -- Class Data Members
    function Point:new(x, y)
        local obj = Class:new()
        setmetatable(obj, self)
        obj.x = x or 0
        obj.y = y or 0
        return obj
    end
    
    -- Move a point along the x axis
    function Point:move_x(x)
        self.x = self.x + x
    end
    
    -- Move a point along the y axis
    function Point:move_y(y)
        self.y = self.y + y
    end
    
    -- Move along both axes
    function Point:move(x, y)
        self:move_x(x)
        self:move_y(y)
    end
    
    -- Draw the point as a single pixel, or define a radius.
    function Point:draw(radius)
        if radius == nil then
            return DrawPixel(self.x, self.y)
        end
    
        return FillCircleAtPoint(self.x, self.y, radius)
    end

    So far:

    • Class
      • DeltaTime (For counting time between frames / button presses, etc)
      • Point
        • Vector
        • Shapes
        • MainCharacter
      • ButtonState (For a single button)
      • Controller State (For the entire playdate controller)

    Lua’s dynamic typing makes passing functions down super easy. If “Point:move(x, y)” exists, it will be available to any derived class (like Vector, or Shape)

    Right now, MainCharacter is a point, (plus some more). I might introduce an “entity” class between the two to have enemies and NPCs, but until I need to pull that out I will keep it where it is.

    The “Template” project is free and open source, hosted here:
    https://github.com/lodomo/PlaydateResources

    This repository will continue to evolve.

    Extras

    The import script is carefully crafted. SDK CoreLibs are all imported at the top, commented out, and enabled as needed. No need to go hunting for what is available. Then I put some handy constants into the _G table, like PI.

    To streamline testing, I created a Makefile. Running “make run” compiles the game, and launches it directly into the Playdate Simulator. I work in the terminal with Neovim, and Tmux so having a terminal-based workflow is the best for me.

    Next time: Basic Collision Detection!

    -Lodomo

  • Devlog 1: Inspiration, Planning, and Scope

    Devlog 1: Inspiration, Planning, and Scope

    I’ve had the idea for this game rattling around my head for years. It’s part of the reason I decided to go get my Computer Science degree. A little overkill, I know, but that’s how I roll.

    As soon as I saw the Playdate I knew that was the console it needed to be on.

    I made a proof of concept for a gamejam a few years ago. It takes place in the middle of the story, and is more of a “filler episode”

    You can play the original proof of concept here:
    https://lodomo.itch.io/bouldermagenightmare

    Inspiration

    The earliest memories I have of video games are with my cousin and playing The Legend of Zelda on NES. He also had a Turbografx. There was a game on there called “Parasol Stars” which was an odd little game I’ve never seen in the wild since. It was an off shoot of the game BubbleBobble but with some really neat mechanics.

    You were a boy with an umbrella, and you would strike enemies with it to stun them. Once stunned you could pickup and throw the enemies at other enemies. You could also grab water droplets to throw at enemies. If you pick up enough droplets you could flood the stage. Certain droplets had elemental characteristics.

    But, just another arcade game won’t be enough. Boulder Mage needs a story.

    I can’t stand games that take a long time to get going. The game needs to start almost immediately. In arcade games, that’s easy. You just throw a level at a player and they start to figure it out. For something story-driven it’s a very fine line of hand holding. In Legend Of Zelda: A Link to the Past, your call to action is almost immediate. You’re woken up, and need to go save the day.

    Game-feel is also very important. Maddy Thorsen has an AMAZING breakdown on the feel of Celeste. https://www.maddymakesgames.com/articles/celeste_and_forgiveness/index.html I plan on implementing every single one of these that applies to Boulder Mage.

    My last key inspiration for gameplay is the Chao Garden / Tamagotchi / Chocobos / Yoshi.

    I’ll get deeper into this as development goes. But what if you had to raise your own Yoshi-esque chickens? And then that became it’s own little sub-game? It might be a big idea to pull off for Playdate, but I think I can do it.

    Planning

    I’m a big planner. I’ve got notebooks all over the place for projects, daily tasks, and so far it’s treated me well. I’ve adopted something from my time learning as a Master Training Specialist to make solid plans to execute projects. Some might think it’s too much, but it really helps me when I don’t know what to do next.

    Here’s a template you can adapt to your own project: hop.lodomo.dev/plan-doc

    You don’t need to fill out every section. You don’t need all the answers. But get some of that out so when you’re getting further into development you always have a path forward.

    I won’t be releasing the entire Boulder Mage planning document because it has lots of secrets for the game. You’ve already read my inspiration sections. Here’s what my game features is starting to look like.

    Game Features

    • Main Abilities
      • Move horizontal
        • Ground Dash (Learned)
      • Jump – With some horizontal control after take off
        • Air Dash (Learned)
        • Double Jump (Learned)
      • Swing Staff
      • Pogo Staff (Learned)
      • Grab Item
        • Carry Item
        • Throw Item
      • Ground Pound (Learned)
    • Meditation Zones
      • Areas that require careful platforming to lead to an unlockable.If you look close you can see I drew this when I was going by “BathThief”
    • Collectables
      • Pythagoras Beans, Plant them to gain access to secretsPythagoras thought beans were human souls.
      • Elemental Gems, Not required to beat the game, but give extra perks to attacks
    • Fast Travel
      • Rufus (NPC) gives access to fast travel portals. Some are to anywhere, some are location specific. See map.

    Scope

    Most of the scope is going to be in my planning document, but I also like giving myself a clear picture of “what I want” and “what I can’t give up”

    What I can’t give up:

    • JUICY controls. If this isn’t in the game, it’s not worth making.
      • This is 100% what I’m coding first, before anything else. Can I get a square to move how I want it to feel.
    • 4 areas, 4 skills, 4 extra quests, 4 bosses,
    • Engaging puzzles
    • Chicken raising. One egg, One Mount minimum.
    • World building without force feeding the info to the player.

    What I want:

    • A world full of NPCs that give life to the world
    • Interactive environments
    • Full Chicken breeding, racing, Chao garden style mini-game

    Scope creep can DESTROY you. It’s either going to delay your project, burn you out, or make you hate everything you’ve been working on.

    It will make every project feel sisyphean. Sometimes you gotta let the rock roll.

    Why Playdate?

    I think it’s important to give yourself restrictions. I found my scope spiraling out of control with my other ideas. This has some really strict rules.

    • 2 Colors,
    • 400×240 (or smaller) Resolution,
    • 2 buttons,
    • A crank,
    • A d-pad,
    • An accelerometer,
    • A mic
    • and 16MB Ram.

    It will come with it’s own challenges, but thinks are less likely to grow out of control. Plus, how cool is the Playdate?

    Until next time,

    -Lodomo

  • Devlog 0: git commit -m “Initial Post”

    My name is Lorenzo. I’d love for you to join me on my journey deeper into game development, and I hope you learn something from me along the way as well.

    I’m at the end of my Bachelor’s degree in Computer Science this spring. Before going back to college, I was an instructor for four years. I’m deeply passionate about teaching, and learning.

    Lodomo.Dev is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.

    This Substack has a two main goals. The primary goal is a development log for Boulder Mage: The Parasol of Destiny, my upcoming game for PlayDate! Secondary, its a space for exploring story telling, game development, computer science, digital art, and more.

    If you’re interested in building games, deepening your understanding of programming, or just enjoy seeing ideas take shape from the ground up, you’ll feel right at home here.

    If you subscribe, you’ll get:

    • Weekly updates on Boulder Mage: The Parasol of Destiny (working title)
    • Lessons on Computer Science through the lens game development. From ‘What is a bit?’ to Polymorphism. Learn agnostic to coding language, and then see examples in Lua, and/or C.
    • Pixel art tips using Aseprite.
    • Open-source planning docs, design deep dives, and discussions on scope (and scope creep!)
    • The practice of wabi-sabi in design.
    • Thoughts on GenAI — where it helps, where it hurts, and when it’s ethical
      (To those who know, that em dash is intentional)
    • Who/What are my inspirations? Hint: Mostly Shigesato Itoi.
    • Book suggestions for further reading on all these topics
    • Discord announcements for when I’m streaming development.

    The format for these posts will be mostly text and images, but I will make the occasional video, and the more you support me, the more time I’ll be able to dedicate to video versions of all my posts.

    Why subscribe?

    Everything I create will always be open source. That doesn’t mean I won’t retain copyright on my IP, but you will be able to use most of the code with little restrictions. All licenses will be available for each project on GitHub.

    Subscribers get:

    • Bonus artwork
    • Aseprite key giveaways
    • Easier access to all code bases.
    • Deep discounts on the game when it launches on itch.io (or compile it yourself!)
    • Your name in the credits
    • Other stuff I haven’t come up with, yet!

    When Boulder Mage wraps up, I’ve already got the next project lined up.


    That was a lot, so what IS Boulder Mage?

    What if there was a boulder who is also a mage?

    At it’s heart, this game will be an homage to Parasol Stars. It’s going to be part metroidvania, part platformer, part Chao Garden, and all of my heart.

    If you want a taste, here’s my entry to LOSPEC Gamejam that won second place:

    https://lodomo.itch.io/bouldermagenightmare


    Thanks for reading. Now, let’s build cool stuff together.

    – Lorenzo