Anonymous Functions, and Textshmup: A retrospect

Posted by Overkill on April 8, 2009 at 8:53 pm under Uncategorized

Some of you may have recently played my 24h game textshmup. As expected, it wasn’t that well received. It was a bad concept to begin with, after all! :D

It was too hard to read quickly and type responses. Audio cues and colored text were added to make the game slightly easier to react to, but even then, it was just too tedious. It was just a randomly generated endurance test, with no goal, and no intermediate tasks.

That said, I had fun making it! I mentioned last time that I made it in LuaVerge. I never mentioned why it was so quick to write.

Well let’s go!

For one, I used the vx library I had written. It came with the spiffy nifty object-oriented class system that I made.

I had my entire game use a single well-divided render/update loop, which boiled down to this:

while true do
    Render()
    Update()
end

I declare two lists which contain all the callbacks done by the render and update events:

render_list = {}
update_list = {}

Here is Render(), in its entirety:

function Render()
    vx.screen:RectFill(0, 0, vx.screen.width, vx.screen.height, 0)
    for i, f in ipairs(render_list) do
        f()
    end
    vx.ShowPage()
end

The Render() function starts by clearing the screen, then it iterates over all entries in the render list, and calls them all. At the end, it shows the changes on the screen. This was ridiculously nice to hook in new rendering events.

Similarly, here was my Update() code, which used frame-throttling so each update callback could be written in per-tick logic:

function Update()
    frame_limiter:Update()
    
    local i = 0
    while i < frame_limiter.gap do
        for _, f in ipairs(update_list) do
            f()
        end
        i = i + 1
    end
    vx.SetAppName(TITLE .. ' ' .. frame_limiter.frame_rate)
end

Both of these worked great for global functions and static methods.

There was one issue with the way they were designed though. Which wasn't immediately apparent, but here goes.

Say I made a class named BlueBox, which has an x, y, x2 and y2. I declare a method for it like this.

function BlueBox:Draw()
    vx.screen:RectFill(self.x, self.y, self.x2, self.y2, vx.RGB(0, 0, 255))
end

In Lua, the colon : in that function is syntax sugar for the following:

function BlueBox.Draw(self)
    vx.screen:RectFill(self.x, self.y, self.x2, self.y2, vx.RGB(0, 0, 255))
end

Which is syntax sugar for:

BlueBox.Draw = function(self)
    vx.screen:RectFill(self.x, self.y, self.x2, self.y2, vx.RGB(0, 0, 255))
end

See that 'self' parameter? Well, earlier when I'm calling the functions, I go like this:

f()

That's a call with no arguments. If we try and add the method pointer for a particular blue box's render, it will call it without passing self, so 'self' is nil:

-- Won't pass the self parameter!
table.insert(render_list, self.Render)
-- Similarly, won't pass the self parameter!
table.insert(render_list, self:Render)

Thus object instances aren't passed around. Not good!

So how do you pass around the 'self' parameter to this list? Well, fortunately, Lua allows you to create anonymous functions, wherever you want because functions are first-class values. And these anonymous functions can be passed "upvalues", which are local values defined in a scope outside the function.

Using these facts, I wrote a trivial fix to bind method pointers to their objects, and leave Render unchanged! I made a function that did the method wrapping for me:

function MethodPointer(self, meth)
    return function(...)
        return self[meth](self, ...)
    end
end

The function is passed the 'self' parameter and the method pointer to bind together, and it returns an anonymous function which will take care of calling, with 'self' being passed automatically.

Then I can go add to the render_list and update list:

table.insert(render_list, MethodPointer(self, 'Render'))
table.insert(update_list, MethodPointer(self, 'Update'))

This is actually pretty close to how Javascript gets around similar problems, but with its definition of 'this'.

This little tidbit allowed my various components of my game to plugged and unplugged from the main loop as it progressed.

For my text input, I used a callback to process commands when enter was pressed, using the same method pointer stuff. The callback passed around the piece of text, and would return true or false on whether or not the command was permissible. It was very nice.

Lua is just awesome. I love it.

Anyways. As you might see, coding isn't really a colossal obstacle, with Lua around. Instead, the new obstacle is coming up with good ideas!

Something to remember for the next time I try and make a 24h game, and what I always need to consider, while making Resonance.

Catch you later, everyone!

Tags: , , , , , , ,

No comments (make one!)


Slight site update

Posted by Overkill on April 7, 2009 at 11:14 pm under Uncategorized

I made a few updates update to Bananattack. Nothing tremendously major, but probably noticeable.

Made the lime green unripe banana color in the header a little less pukey. Sharpened the Banana image moderately to look more crisp. I think both of these things make the top area of the page a lot nicer to look at.

Made the font a bit smaller and not-Verdana, and I actually think this improved the readability of most text here. And I modified the way links are presented, so it’s more obvious from a glance which areas are clickable in my messages.

I also updated my About Page somewhat! Now with moderately more awesome.

Oh, and I realized that my posts weren’t displayed tags at all with my custom template. Fixed that. So now you can ‘ooh’ and ‘ah’ at the horrendous categorization metadata you didn’t see before.

That’s all for now. I’m open for suggestions to improve this site more if you have ideas! :D

EDIT: Now with more visible pagination links! And less stupid when there ARE no previous/next pages.

Tags:

No comments (make one!)


Textshmup

Posted by Overkill on April 5, 2009 at 3:35 pm under Uncategorized

For my 24 hour game, I made a weird game. A text-based shoot-em-up. Think of a text adventure, played on the console, but the scenario is constantly updating. You have obstacles to avoid and enemies to blast. Your situtation is narrated, and you type text commands to avoid things.

You constantly move forward, and need evade the hills and ceilings of the game’s endless cave.
As a result you need to adjust altitude to avoid things.
Crashing into obstacles results in an instant death.

As you fly along, you will encounter enemies, who need to be taken out quickly.

You have lasers and missiles. Lasers are weak, but unlimited and fast firing.
On the other hand, your missiles are powerful, but limited and slower firing, and recharge gradually as you go on.

Your shields protect you when engaged but gradually decrease in energy.
Your shields charge and replenish when disabled.
Your frame has limited health, and so as you go further in, toggling shields becomes crucial to survival.

At the end of a run, it displays the distance traveled, your score, and the enemies you destroyed.

It was all coded up in LuaVerge, making it ridiculously quick to code up.

So, now that’s it’s been introduced, download textshmup.

UPDATE:
Textshmup now has the ability to speed up/slow down your ship.
The higher your speed, the less missiles you replenish, and the more obstacles you encounter.
So it’s sort of a difficulty modifier, I guess?
Also: it reports the time you were alive. I forget if it did that before, but whatever.

Tags: , , , , , , ,

No comments (make one!)


Temporal Stasis

Posted by Overkill on April 3, 2009 at 6:00 pm under Uncategorized

I’m putting this project on hold temporary, since I have exams. I also want to participate in Thrasher’s 24h compo idea tomorrow, and I want make a twitter app to automate Gamagame.

I will return sooner or later though. Later!

Tags: , , ,

No comments (make one!)


Resonating

Posted by Overkill on March 29, 2009 at 11:09 pm under Uncategorized

So I had earlier abandoned Resonance for another game idea, thinking it was the right thing to do. I figured that Resonance was too complicated, and would require too much work to get anywhere. In a way, I was right, but now I realize that I was also mistaken in these judgements. It was complex, but not to the point of disrepair. It just needs some loving and nurturing.

Upon review I know of several ways that I can make this idea work better. In addition to that, I have a lot of existing code for Resonance, that simply needs moderate updating to work with new ideas I’ve acquired.

I’m thinking of reducing the complexity of Resonance quite a bit, so that it’s more fun, and more structured for use in story and level design. Level design is something I desperately need to improve, but I think I’ve finally formulated some ideas to remedy that. Once I’ve verified that they work for me, I’ll be glad to share them to everyone reading this.

I know how vague this sounds right now, but I intend to turn my development around. Sticking with a project has always been difficult to me, but I think Resonance has many things that I consider good for a full game.

School is consuming my time, and it’s confusing my thoughts. I don’t write things down and plan enough, and this is why I have been all over the place with my development time. I end up adding things to engines which ultimately I don’t even need, or making my life needlesssly challenging by reinventing the wheel. I distract myself with various tiny details rather than working toward a bigger picture.

So Resonance it is. What I need for the game will determine what I spend time on. I have yet to develop a full list of what I need, only ideas.

Thus my immediate task: To jot down what Resonance had before, and what Resonance needs to become a feasible project while not compromising on fun.

Oh and I might also sink some time into making a simple site for this silly Gamagame challenge I’ve imposed upon myself.

Anyway. Until the next incoherent babble post, farewell!
 
 

Summary (as of March 30, 2009)

Productivity Points: 15/21 (-4 for days since post, +5 for deciding on the game idea)
Tasks for the Week (Must sum to 10 points):

  • Describe how to scale back the old game system by reducing complexity. (4)
  • Decide on how much code can be reused. (1)
  • Come up with a rough story outline (2)
  • Come up with actually concrete designs for the first level of the game. (3)

Tags: , ,

No comments (make one!)


The Game Making Game

Posted by Overkill on March 26, 2009 at 5:35 pm under Uncategorized

So, after agonizing a lot last week, I came up with an idea. A ridiculous idea that started as a joke. One so ridiculous, it just might work for real development.

Let me present to you a challenge. I call it Gamagame.

Gamagame is a one-player game where the player (literally) plays the role of a game creator!

Starting off
The player starts by deciding what sort of genre and the expected play time of their game project. The player will then be thrown into the world of making games. There are plenty of adversaries and events to overcome in the Gamagame world, and it is the duty of the player to conquer them.

Basics

  • The player starts with 14 Productivity Points. This amount is lowered by inactivity, and rewarded by constructive activity. Details on how to gain and lose points is described below.
  • The player can have a maximum of 21 Productivity Points initially.
  • When the player has 90%+ of their Max Productivity Points, he is “kicking ass”. Streaks should be noted.
  • When the player is above or at exactly 30% Productivity Points, he is “okay”.
  • When the player is below 30% Productivity Points, he is “crawling”.
  • When the player has 0 Productivity Points, the project is considered “idle”.
  • When the player has -7 Productivity Points, the project is called “frozen”, and Gamagame should probably end before it becomes shameful. It can be continued though in an attempt to rescue it.
  • When the player has -14 Productivity Points, the project is called “buried” with no chance of rescue, and Gamagame ends with shame.

Getting Points

  • The player gets 5 points for coming up with the initial game idea.
  • After the initial idea, the player splits assigns a bunch of tasks for each week. These can be broad categories for later weeks, but should be solidified ideas when getting closer in time. The player rate each task for the current week, with 10 points to divide between all tasks for that week. Fractional points may be given. The point rating assigned is the reward of productivity points the player gets upon completion.
  • The player can announce their intent to get extra work done (that is, tasks for later weeks), for a fixed 2 extra productivity points at the end of the week. The points for the extra tasks themselves aren’t rewarded, only the fixed 2 point reward at the end of the week. Failing to do anything by the end of the week after intending to do extra means that 3 points are lost.
  • The player can put Gamagame on hold for as long as they want, if there are distractions like work, school, or various life events interfering. No productivity points will be lost. But all delays should be counted, and for every 14 days spent on hold, 3 productivity points should be lost.

Penalties and Project Progression

  • For each day on the project, 1 productivity point is lost.
  • When the project is considered 50% complete, the penalties double, and max productivity points goes to 42.
  • When the project is considered 75% complete, the penalties and rewards both double (penalties are then quadruple the base), and max productivity points goes to 70.
  • These can be adjusted if they’re too ravenous, but make a note of it, and don’t intentionally cheat the system.

Teammates

  • If the player is to enrol other teammates, don’t include them in your game score or anything (especially don’t make them play this silly game unless they’re as crazy as you).
  • If they complete one of YOUR tasks, you get points for that, but don’t deliberately put their tasks under your tasks for the week.
  • The only other effect team members have: any teammate who ends up doing nothing or qutting costs you 3 productivity points when you realize this.

Ending Gamagame

  • Gamagame is won when the project is 100% complete and released.
  • The player loses upon forfeiting Gamagame, or by being “buried” shamefully for having -14 productivity points.
  • Keep track of how many wins, loses, and shames you have. They could indicate a problem with how you’re developing games!

 
 
Also note the fixed reward of 2 points for doing extra tasks each week. This is to somewhat prevent being sidetracked, and to ensure that new ideas are considered ahead of time and waited on, instead of being thrown in for no reason. 2 points alone isn’t enough to save you in the long run anyhow.

Anyway, let’s begin craziness!
 
 

Summary (as of March 26, 2009)

Player Info
Name: Overkill
Productivity Points (-1 per day of inactivity): 14 / 21 PP
Status: Okay

Game Info
Game genre: Sidescroller + shmup (possibly a Shmuptroidvania, we’ll see)
Expected play time: 3 hours

Here we go! Now to hopefully not lose at yet another thing. May my craziness be a rewarding thing.

Tags: , , ,

No comments (make one!)


Wheels are to Cars, as Game Engines are to…?

Posted by Overkill on March 24, 2009 at 12:36 am under Uncategorized

Can take a minute to say something?
I am normally quite complacent on the subject,
But I have tortured myself for far too long, and something needs to be said.
I always have aspired to be excellent creator, who would invest years of hard labour into a masterpiece: a car.
Instead, I’ve spent my life do far lower pursuits.
Small imperfections have deterred me from my true passions. So I worked diligently to correct them.
I reinvented the wheel to learn how I could make a wheel of my own.
I worked to improve the inferior wheels, but never received enough praise.
I avoided just using the popular wheels, because they were all made by snobs who want nothing to do with me.
I’ve struggled trying to find a wheel that came close to meeting my very arbitrary needs, but with no success.

I’ve let the wheel get in the way of making a car.

But, there’s a community and politics established around each wheel that has been made.
To leave my poorly designed wheels on the road leaves the people trying to make cars with them out in the cold.
Some of these people who cling on to these rusty wheels are my friends.
These wheels mean a lot to all of us, in our ventures to hopefully eventually make a car.
I wanted to make a car, but my knowledge of wheel-making has caused me untold hours of grief.

My attention to details has gotten to the point where a slightly imperfect wheel causes me to drop all plans to make a car, ever.

All I wanted was to make a car.
Now I’m stuck in lab, with a collection of unfinished gadgets that have nothing to do with cars.
Help?

Tags: , , , , ,

No comments (make one!)


It is never too late to be great.

Posted by Overkill on February 2, 2009 at 10:19 pm under Uncategorized

Early on during the last week, me and Toen were having a nice talk about how so many of our projects fell apart due to lack of interest and direction. This led to a bunch of talk about planning, collaboration, and organization, something I’ve always lacked. After some disccusion, he decided to make an entry about how planning is urgent and so often overlooked (especially when working in groups), and specifically in the Verge community,

This inspired me to take a new approach to planning, and begin to write ideas down in detail before actual implementation. I decided, clarity is essential if I ever want to do something longterm. I think this will prevent feature creep, and keep myself from losing focus.

I started outlining general things that I want out of the gameplay, as well as making some level designs for shmuptroidvania. It will be a mix of bullet-heavy shmup action, and platforming with exploration, and the transition between the two modes of will be relatively painless.
 
 
 
But, then for a while during the past week, I just became too stressed out. I couldn’t really deal with the crappy weather, and the monotony of school and responsibilities. Started sleeping in and missing classes, and generally did nothing. It wasn’t until near the end of the weekend that I started to recover from this stress-induced aversion to work.

But I recovered! When I did, I started to piece together some of the final steps for LuaVerge. Got cracking, and finally put in a system for garbage collected resources.

To request a Verge resource be garbage collected automatically, you call a function v3.GCHandle(handle, destructor_name), which returns a reference that will manage this handle’s scoping. When that reference is no longer used, the destructor is called, and the variable is freed. I made this fairly low-level though, so v3.GCHandle have no public variables or any methods on them. They are a simply a mechanism for gc scoping internals. Here is an quick and extremely dirty example of it being used

function LoadImageGC(filename)
    local t = {}
    -- The resource handle, which is accessed from
    t.image = v3.LoadImage(filename)
    -- The returned table has a gc_handle that
    -- manages the garbage collection of this resource
    -- If this escapes scope, the image handle held will become invalidated.
    t.gc_handle = v3.GCHandle(self.image, "FreeImage")
    return t
end

-- A small scope block, allocates an image, and the image
-- will escape scope directly after.
-- Collection will occur on next Lua collectgarbage() event.
do
    local img = v3.LoadImageGC("hero.png")
end

I mainly put this in for OO wrappers, like the vx library, which of course uses it. All handle-style references use it, with the exception of vx.File, since files must be closed by the user anyway (especially if writing), and Verge reuses handles so defered collection could result in some bad issues.

vx.File was something I added today to the work-in-progress build. It basically wraps the Verge File library, which is surprisingly powerful, alongside the string tokenizing functions.

LuaVerge is essentially ready, but I’m withholding release until more tests are done, and vx is successfully updated. We’re getting there really quick! Then shmuptroidvania is back in the foreground.

Anyway, I’m back, and ready to rock through school and game making.

Tags: , , , ,

No comments (make one!)


Minty chewing gum.

Posted by Overkill on January 25, 2009 at 2:10 am under Uncategorized

This week, I poured over some more scripting language stuff in LuaVerge. Monotonous work really, but it needed to be done!

As a result, LuaVerge is nearly done. I’m convinced it’s at about the point where it’s usable again.
 
 
 
I added a way to load from .vpk packfiles with Lua scripts. So now require statements will also examine packfiles for source code, meaning that things like vx, and peoples’ games can be packed away in a single file.

I might add override loadfile and dofile too, if it’s actually required, but I figure those are mainly lowlevel things, and most work can USUALLY be done by require. I guess if someone were using Lua as a file format for game data (like a database of items or something) as well, then you’d need to worry about this. I’ll take peoples’ input here, for those Lua users out there.
 
 
 
One of the biger things I spent time scratching my head over was binding Verge’s builtin variables. You see, Lua shoves in getters and setters for variables as functions. So at the lower level, to get the backend variable VC-style entity.x[ent], it calls v3.get_entity_x(ent).

However forcing people to call raw functions rather than assign variables, I’m told is annoying. So, I added support, through Lua’s crazy voodoo magic with metatables.

Since ‘variable’-style accesses to builtins are more expensive than their raw function calls, a bit of optimization had to be considered, to at least lower these costs. After all, the older LuaVerge had issues with speedy access.

This was a bit of a surprise to me at first, but also really neat, local variables have a much faster access time than global ones. Storing local copies of global variables gives a significant efficiency boost. Some report about a 10% lookup for globals, so imagine if it were a nested table of some sort. Caching variable lookups as much as possible would very much improve performance.

So using this knowledge and a few tricks, I think I’ve constructed a way to make fast lookups to LuaVerge builtins. I won’t bore or scare you with the details.

But if you insist, and are curious, all the work is in the source repository for Verge at http://www.verge-rpg.com/svn/verge3/ with username “anonymous” and password “anonymous”.

Particular files to look at are:

 
 
 
So after all this, I started to repair the vx library so it’d work after the giant rewrite. Surprisingly, it wasn’t too hard to repair.

Why? Because vx enclosed all its source in a custom class system. Object oriented code made things compartmentalized and simple enough to refactor.

I made a simple vergeclass function that declares a class, and basically allows you to declare objects of that class after it’s been made.

You declare classes like this:

vergeclass "Thing"

(Whoa! no brackets around the function call? Lua lets you omit them for literals like quoted strings or tables). Anyway, after that point, there’s a globally named class called “Thing” which can be instantiated.

Constructors are declared like

function Thing:__init(a, b, c)
    self.a = a
    self.b = b
    self.c = c
end

And methods inside the class are sort like

function Thing:method()
    print "Hody2!"
end

Objects can be instantiated like this:

mything = Thing(5, 3, "tacos")

Their methods can be invoked fairly simply:

mything:method()

The downside, was the original system I had (before LuaVerge was rewritten) was horrendously inefficient, but it worked somehow. Lua is magical and allows you to horrendously hack things that just work.

Anyway, I revisited this code not because of this but for another reason. None of classes could be ‘extended’ or ‘inherited’ from, making the OOP limited. Granted, for vx, inheritance wasn’t necessary, and I didn’t really desire people extending the core objects because I’d much prefer composition.

Basically objects should as much as possible use “has-a” relations instead, and avoid “is-a” whenever possible, however tempting it is. It decreases coupling somewhat that way.
 
 
 
But I had other code, with Resonance’s sidescroller engine that I wanted to salvage. It used a moderate degree of inheritance, which given for free by luabind. Luabind being the thing that glued LuaVerge behind the scenes before we tossed it, and it came with a class system with inheritance… Anyway.

So I added a single-inheritance system to vx’s vergeclass so I could eliminate this old dependency and have working code again. This involved entirely rewriting the old system now that I understand the Lua library, Lua-based optimizations and metatables a lot better.

It was actually pretty enjoyable to overhaul the whole thing, and… weird. Metatables have some godly power to them. Anyway I finished, and even gave two methods of setting up inheritance.

I supply a temporary global function super(...) which must be invoked in a child class’s initializer to ensure the object inherits all the attributes that its parent class has. Later, I might enforce the call be made on every subclass just to ensure undefined behaviour won’t happen.

Now any time a method is called, it’ll look in its own class first, and failing that it’ll look in it’s parent’s class. If a class needs to explicitly call a method of its parent, it can index self.parent and use self.parent.method(self, ...).

So using the Thing example from earlier, the calls to create a child class are either this…

vergeclass("SuperThing", Thing) do
    SuperThing:__init(a, b, c, x, y, z)
        super(a, b, c)
        self.x = x
        self.y = y
        self.z = z
    end
end

…or this, which I prefer because it looks like Python kind of:

vergeclass "SuperThing"(Thing) do
    SuperThing:__init(a, b, c, x, y, z)
        super(a, b, c)
        self.x = x
        self.y = y
        self.z = z
    end
end

Either one of them reads “The class SuperThing is a child of Thing”. And there you go! Inheritance! Nifty! Sexy!

I even added one other thing! Properties. Other languages provide properties to declare pseudovariables with functions that are called when their values are accessed or mutated. I do similarly, through a bit of metatable magic.

Let’s take an example of a typical RPG player for a second, who has an HP attribute. To declare a property, you can go something like this:

Player._property('alive', function(self) return self.hp ~= 0 end)

You can then use the ‘alive property like a readonly variable, for instance on a player object named bob:

if bob.alive then print("We're still kicking!") end

I might expand this class system to allow multiple inheritance or mixins at some point. Not a high priority, but it’d be nifty!
 
 
 
So yeah, this has been my week. I’d love to hear about yours too! :D

Tags: , , , , ,

No comments (make one!)


McSchooldorf

Posted by Overkill on January 18, 2009 at 1:47 am under Uncategorized

School definitely crushes productivity, it’s true. But I figure it’s not all a waste. If school is consuming programming time, and some of it seems moderately interesting to me, why not post a Gruedorf about it anyways?

For my Game Development class project, I taught myself some basic 3ds Max stuff this past week so I could start to make some simple lowpoly 3D models. Here’s a start of the model I’ve been working on so far!

The modelling was done with 3ds max. After some frustration in getting polygons to unwrap into nonoverlapping UV regions, I exported the UV Texture Maps and doodled on Paint Shop Pro. It was rather fun. The palette on the guy’s face and hair is shamelessly stolen from my Resonance hero sprite.

My intent is to hopefully make a face paced hack-and-slash + platformer with cube men everywhere. This might change later, when I feel more creative.
 
 
 
In my Computer Security class, I wrote program in Python that would use a dictionary attack to figure out a password that starts with “R”. It used Python’s telnetlib module, because the login was performed remotely over Telnet, and it used the threading module to perform multiple connections. The target server we were attacking certainly wasn’t very secure… Once the protocol was working, it was burning through about 100 words in the dictionary every minute, until it eventually made it up to “retribution”, the winning password phrase.
 
 
 
In my Image Processing class, the assignment was to write a program that rotates an image and uses bilinear interpolation to smooth the pixels.

This was especially easy since I had code left from Brockoly that did image rotation, and it used the Corona library in C++ to load images. I simply scaled back the code so there was less junk, and made it so images could be saved as PNGs.

Corona once again saved the day there, since it has a PNG writer builtin. Very handy. Also very easy, which makes me wonder why we don’t have png saving in Verge yet, since it’s quite simple — only about five lines! Ahem.

So when I was finished, I had a commandline program that could take any input image, rotation (in radians or degrees), scale, and the name of a PNG output file, and do all the processing work. Sexy.
 
 
 
In other news, LuaVerge works again more or less, thanks to Zeromus.

It’s quite different in how the internals are accessed. On the other hand, the internal variable and function things are successfully shared between Lua and VC code. In other words any feature (that’s not a language feature of VC) is available in Lua too.

The only thing that’s missing is boilerplate magic Lua code so the internal variables’ get/set methods are wrapped up in tables in a nice way. This can hopefully be added soon too, if I’m not too busy.

When that’s caught up, on to shmuptroidvania work and cube man game.
 
 
 
See you next week, folks!

Tags: , , , , ,

No comments (make one!)