Kontraux wrote: ↑Mon Aug 26, 2024 5:15 pm
Congrats man, it is really well done. Everything is so slick, I'm guessing it's partly the vector graphics, everything looks ultra-sharp. It gives your game such a distinct style. And yeah actually if you do want to talk about the technicals, I would be interested in how exactly that works. I don't know much about vector graphics, my understanding is that Love can't just draw them like a .png, did you have to build some kind of system to support them, do you convert them to meshes or handle them with a custom shader or something?
Hi, indeed, LOVE doesn't have pure vector support. There are some libraries, but the vector format is a lot of math and just way too complex to deal with directly (but it's probaly possible). Instead, I have tons of prepared gradients, ui icons, paths and shapes as Inkscape files, which I can easily combine and change and then rasterize to use as .png
I also extensively used
HotParticles editor to animate every effect (and some trigonometry functions as well). Water is animated with a Voronoi noise function in shader. No sprite sheets or atlases were used for animation since all sprites are a lit bit upscaled (and RAM usage grows quadratically with higher image resolutuion), so all animations are procedural.
As for the code, the game is mostly written using OOP with meta-tables for inheritance.
This the only class functionality I ever needed:
Code: Select all
local Class = {}
Class.__index = Class
function Class:new() end
function Class:derive(class_type)
assert(class_type ~= nil, "parameter class_type must not be nil!")
assert(type(class_type) == "string", "parameter class_type class must be string!")
local cls = {}
cls["__call"] = Class.__call
cls.type = class_type
cls.__index = cls
cls.super = self
setmetatable(cls, self)
return cls
end
function Class:__call(...)
local inst = setmetatable({}, self)
inst:new(...)
return inst
end
return Class
Terrain is generated and rendered with the LOVE noise algorithm, but player-built and pre-built tiles replace the noise algo with Szudzik paring function to track coordinates of the placed objects (and they go into save file). That way I don't need to keep in memory x and y coordinates of all tiles, only the indexes of built tiles (which are just the result of Szudzik pairing of x and y). And by using Szudzik I can efficiently find, render and interact with any tile in the world.
In the early stages of development I tried to create thousands of tiles at once as objects to procedurally create the world, and LUA would eventually start leaking memory and the garbage collector seemingly would stop working. At that point new projectiles would start to increasingly consume dozens of megabytes of memory for no apparent reason. Luckily, pairing functions and noises allow to generate the world in fast manner without storing all the tile info in the memory at once.
So, in OOP sense, procedurally generated tiles are just the result of noise, player-built tiles are objects (which can be interacted with), enemies and projectiles are objects (which is not ideal, but I couldn't come up with a way to store projectiles more effeciently and being able to attach some functionality to them at the same time).
I tried using something like ECS instead of OOP, but the systems started to entangle with each other which would cause memory leaks, so instead I mostly used OOP while trying to "curb" it as much as possible to use less memory.
Animations are all procedural, collisions are mostly aproximated as distance between circles.
When I needed objects to use new functions, I would create or extend new class. I was using SOLID to structure my code at first, but then ditched most of the OOP guidelines to actually write working code faster. In particular, Liskov principle might increase overhead and makes it difficult to debug.
By the way, does anyone know how to code UI for LUA in fast and efficient manner? I resorted to make every interactible element as object with hover function in class and position them using something akin viewport units in CSS, but I had to painstakingly align each element with each other (like buttons with text) and it would just take forever.