Notes on framework building

Posted by (twitter: @triplefox)
December 15th, 2011 7:38 am

In the past I’ve tried building “frameworks.” They never did what I expected them to do. But this past week I’ve worked on another one and it’s actually shaped up reasonably well. Calling it “TriadNME” – it is a haXe/NME/Flash10 2d platforming engine with applicability to other 2d games. Some notes on interesting elements of the design and things I discovered in the process:

  • Mostly based on stealing existing good code from old projects. Proven code is golden, and possibly worth more than the architecture itself. I would actually consider Triad a second generation framework(the first being some bits of code for timing, the entity system, build system, etc.)
  • Very centered around the haXe dynamic/static typing mix. Entities are anonymous objects, with a component system based on “conventions” of data layout(field names, an array of component destructors, etc.) attached to them. I’ve used this for about a year on many projects – it feels like the right mix most of the time.
  • Lax approach to performance to favor reusable functionality. Essentially, I give myself room to use a layer or two of  indirection(callbacks, lookups, etc.), but try not to go much deeper. So, e.g., instead of a hardcoded timer I now use an Alarm class with callbacks. Character controllers, camera movements, etc. are similarly based on callbacks, thus it’s easy to go from (e.g.) flipscreen camera to scrolling and centered around the player. If I want to regain performance from those things – I can usually rewrite the specific functionality.
  • Minimal contextual instancing. By this I mean – at the start I prepare all the core structures(whether as singleton instances or as static classes) and never destroy or replace them. Instead I call reset methods and toggle visiblility on the Flash displaylist. As a result, I never have to litter tests in the code for whether a certain kind of data is available, because it always is. To change contexts, I disable some functionality. As a result, there’s a whole body of code that simply doesn’t exist, since disabling is (in general) easier than destruction. This goes hand-in-hand with the approach to performance.
  • My approach to algorithms has generally improved. I’ve started making my own “xxxTools” static classes for common algorithms – so I have some for math/trig/probability now,  some bits for strings and hashes, some bits specific to my AABB/tile collision system. Conversions between measurements (degrees/radians, pixels/tiles) and alignments (centered, left, right, above, below) are a major part of the toolbox and my core data structures tend to include a lot of conversion methods. Similarly, more structural algorithms like “array of objects with name fields” to “string hash of objects” are starting to come into play. In the past I had a horrible tendency to rewrite simple algorithms and inline stuff that didn’t warrant the customization, but hopefully I’ve come around on that.
  • [haXe-specific] – I use many more anonymous structures. HaXe is structurally typed and lets me add a bit of extra type checking just by wrapping everything in a little structure – e.g. most of my points are {x:Int,y:Int} or {x:Float,y:Float], instead of being a class. This adds a bit more flexibility in writing the data because I can just inline the structure into a function call instead of going through writing a class, a new() method, etc. Doing this lowers performance for the Flash target somewhat, but adds a ton of code clarity.
  • While heaping on engine-level features with sprites, rendering, physics, I specifically tried to engineer them so that they’re easy to rip out if I don’t want to use them for LD. This had the effect of making them modular without an explicitly “modular/pluggable” system design – a field on the main class, one call to initialize, one call to reset, one call to update. Take those out and the system is essentially gone, although other things might reference them – but that’s more of an “integration” problem and usually just requires more things to be deleted.
  • The in-game console is increasingly central to the workflow. I try to cram as many in-game tools as I can because – besides the obvious iteration improvements – it has a knock-on effect on the architecture where I need to keep data in both editable and playable forms, thus it starts decomposing towards the “real” constraints.  The console works as a way to quickly expose editing functionality. This is something I could continue to refine considerably with tool-specific namespaces, unix-esque pipes, etc.
  • I opted for a tilesheet format that uses a “smallest size” of tile and rebuilds larger ones from that, similar to the character-mapped graphics of older consoles(NES, SMS, etc.). This has some interesting effects on editability – with an in-game editor the downsides are mostly covered, but the benefits in internal architecture are surprising: the structure of graphics access is greatly clarified when you have three ways to inspect a tile sheet – by set name, by set index, by tile index. It works particularly well with the autotiling algorithms I’ve made(one of the main reasons I started down this path). It also means that the framework should port more easily to a 3D-accelerated setting since only the rendering methods have to change – the original data for sprites and tiles is already a nice NPOT-sized thing.
I still have some things I want to fix up and test before I declare this ready – sometime tomorrow I’ll publish it, with a little video.

One Response to “Notes on framework building”

  1. robashton says:

    “Mostly based on stealing existing good code from old projects.”

    Pretty much – all of my libraries work like that, small bits of code I’ve pulled out of a project I did in a quick and dirty fashion and then tidied up when the pain points emerged. Stick them together and you have a framework that can be used to build similar things again – use them independently and they can help you not solve the problem twice.

Leave a Reply

You must be logged in to post a comment.

[cache: storing page]