OpenTower: A Resource Management Puzzle

Hello OpenFL community!

This post is one part showcase, one part request for critique. I am still very much in the discovery phase of learning about OpenFL, and before diving deep into some passion project I want to make sure that I understand the groundwork and best practices for the tools that I’m using.

So, over the past month or so I’ve worked on re-implementing an old open-source Ludum Dare game created by Markus “Notch” Persson called Breaking the Tower. The original game was written in Java and published as an applet (in other words, old technology), but the entire game is open source for others to explore.

Seeing that this nifty little diversion was created in 48 hours for a video game competition, I figured that it would be an interesting exercise to re-implement it in Haxe using OpenFL. I could focus on learning some of the fundamentals of this relatively new technology, rather than getting bogged down in creative “designer’s block” and/or scope creep.

The Game

You may play a rough, alpha version of the game here!

The source code is available on GitHub!

Questions

After you’ve attempted to play the game and have become sufficiently disillusioned with my half-baked implementation, I would greatly appreciate your advice and insights into how to better architect my code for this sort of application.

General advice is welcome, but here are a few specific questions that have plagued me during development:

  1. In the original Java code, a single-frame buffer of sorts was passed to every game object, and the object was responsible for drawing its own bitmap in the proper location on the screen. My understanding is that this strategy is called “active rendering.”

    OpenFL, on the other hand, advocates a “passive rendering” strategy, in which the developer simply adds sprites to the display list and updates their position and other properties as needed. Frame-by-frame redrawing is handled internally.

    I tried my best to organize my code with passive rendering in mind. Where possible, sprites are added to the display list in an init() function called once, and sprite properties are updated in event handlers (rather than in the render() function called every frame).

    However, I wasn’t always successful at keeping my render functions clean. Generally, I faced the issue that my spritesheet (a tileset) was passed in via an init() function called after an object’s construction. Although this helped me separate the logic of a game object from its visual representation, it also meant that I could not reference the sprite sheet in any event handlers. Instead, I had to store extra information about game objects in private properties (that’s okay, I guess) and then still update the sprites on every frame referencing those properties (not good at all).

    Are there more appropriate/canonical strategies for handling this separation of concerns—logic and rendering?

  2. Speaking of event-based visual updates, I started to go down a rabbit hole with defining custom event types on which I could store data. After making two of these custom event types with almost the same source code, I felt that I was missing some obvious utility to do exactly this task (dispatch events with simple pieces of data associated with them). Since I had already written non-event-based code that worked fine, I punted on the issue until I could ask the OpenFL forum.

    Is there a better way to handle the large number of data-tagged event types that inevitably show up in game development?

  3. With the large number of trees and rocks, using the Tilemap classes seemed like an obvious decision, until I needed to do a custom drawing of a simple graphic object (the health bar) interspersed in the Tilemap’s tile ordering. You can see the original forum post where I asked about this. In that topic, the consensus was to create multiple tiny Tiles in a TileContainer that could be transformed (scaled, rotated, etc) as though they were just blobs of pixels on the screen.

    That strategy seemed to work just fine, but I wonder if others haven’t figured out better solutions to adding ad-hoc “mini-canvases” for custom drawing within a Tilemap. Has anyone else attempted this sort of Tilemap customization? What are your strategies?

The Future

I eventually want to write a series of blog posts outlining the design process of a game like this in OpenFL, talking through it “tutorial-style” so that readers can write the code as they go along. The process has been invaluable to me already, and I feel that others would appreciate such a comprehensive resource outlining the best practices for OpenFL/Haxe development in 2021.

I also want to customize the game to make it unique to me, and not infringe on Notch’s intellectual property. This part of development is some of the most exciting stuff (creative, self-expression work, rather than trudging through display logic and platform-specific nuances).

But I don’t want to pursue either of those tasks until I get out of my own echo chamber and hear some feedback from more experienced developers. There is little worse than an uneducated developer like me touting kludges as “best practices” on the Internet and consequently driving many other aspiring game designers into confusion and disillusionment.

So, regardless of whether you read this entire rambling post, please let me know what your thoughts and critiques are, good or bad! This project is the most fun I’ve ever had doing video game development. At the end of the day, I want the products I produce to be both fun to play, and useful for other developers to learn from when reading the code.

4 Likes

I won, but the game crashed with this error:

OpenTower.js:768 Uncaught [lime.utils.Assets] ERROR: There is no SOUND asset with an ID of "assets/Sounds/Win Song.wav"

:man_facepalming:

Yep, I should have seen that coming. I was having fun adding some music that I (personally) own to the win screen, but I didn’t want it to get into source control (especially with all of the recent drama regarding GitHub and DMCA). Obviously I didn’t do enough patch work to ensure that the missing sound file didn’t cause issues.

I’ll take care of that ASAP!

1 Like

I was a HUGE fan of Settlers (1993) and this game had a number of similarities, so I really enjoyed it! I haven’t yet completed a game (work got in the way), but I’m very impressed none the less.

I really appreciate your post here too @RNanoware and the questions you’ve raised. I may not have the answers, but you’ve got me thinking about these things. I’ll add some thoughts, but I’m keen to learn what others have to say.

I’m not sure if this changes anything, but the spritesheet could be passed in as a constructor argument.
Or, is the availability of of the spritesheet an issue at the time of object instantiation?

Referring to the non-event-based code you point to there, text updates in my experience, can be costly. A bit of logic could be used to discern if an update to the text property is needed, or perhaps transition to event based.

With respect to sending custom events with data, what you’ve done there looks ok to me.

I’ve enjoyed your efforts here @RNanoware, I look forward to learning more!

1 Like

Thank you for the positive feedback, @Bink! I’m glad you’ve found the game enjoyable so far.

For a while I did in fact leave the spritesheet as a constructor argument. However, I ran into issues when I wanted to create a tree or crop within a Job (which has no visual representation). I vacillated between passing the SpriteSheet instance everywhere as a dependency, and turning it into some sort of singleton or static member of an overarching class.

Eventually I went with the init function (as the original game used), because it seemed to make sense that a Tree (or Peon or Tower or Rock) could logically exist (and be manipulated) before “appearing” on the Island. The difference is that, unlike in raw Java, with OpenFL it is somewhat inefficient to set a Tile’s ID to the same value on every frame. But referencing a potentially null SpriteSheet in various event handlers is correspondingly dangerous.

Good to know! I’ll definitely be prioritizing a refactor in that area as I continue refining the game’s code.

This game is quite compelling, thank you for sharing your work. :slight_smile:

I have no familiarity with the original, so my comments on the game itself lack that frame of reference.

First, the questions you’ve raised:

  1. I’m also working out how to tackle this, and I don’t think I have a good answer. One canonical pattern is MVC, but it isn’t always a good solution. I find this article and the followup somewhat helpful - specifically the idea that it’s abstraction and separation of concerns which matter, and patterns have to be chosen and adapted for their use case.

  2. Looking at the two similar event classes you’ve created, you could define a single class extending Event, with an extraData object of type Dynamic. I also have a feeling that it might be possible to produce the current result with less code using macros, but I don’t know how (or even for sure whether it’s possible).

  3. I have not tried customising the Tilemap. To add a normal graphic object in the middle of the GPU draw operation means splitting the draw operation into two. Or somehow adding the normal graphic object to the tilemap so it can be drawn by a single call to the GPU.

On to the game itself:

  1. It’s very absorbing and has great potential!
  2. Peons sometimes seem to try to reach resources on islands, though they can never get there.
  3. Peons often get “stuck” pathfinding around resources while (seemingly) trying to get to other resources. It might be better if they picked up the first resource they bump into, even if they were aiming somewhere else.
  4. (slightly contradicting the previous point) After placing a building, peons don’t seem to treat it as a priority - sometimes it appears to be ignored for a long time before a peon happens to stumble over and build it. It might be better if unbuilt buildings attracted peons.
  5. Better pathfinding all around might improve the game, though I can see that there may be advantages to it being less than perfect.
  6. Maybe bridges or boats could be built to allow access to resources on islands?
  7. Maybe breaking the tower could release a “boss” enemy?
  8. A fast forward button would be great. Sometimes there’s nothing to do for a few minutes until the peons decide to mine the rocks which were getting in the way of the trees they had been cutting down while there was plenty of wood.
  9. Maybe the peons could prioritise the low-inventory resources to some degree?
  10. After winning for the first time, I wasn’t sure how the tower was being broken. It was about 30 minutes into my second game when I saw the peon mining rock from the tower and I realised that I needed more peons, not warriors. A help button might be helpful, with a small amount of text explaining small details which might not be obvious.
  11. I realise that the peon behaviour can be controlled to some extent by building and selling guard towers. In games where rock is relatively scarce (or blocked by many trees) it can take a long time to gather those 30 rock - at which point they could be used for more dwellings or saved for barracks. Maybe guard towers could be replaced with flagpoles which only cost wood?

I really enjoyed playing three games today, each taking less time than the one before (the third was about 17 minutes). I’ll update if I think of any other feedback, but the ten points above might be enough for now. :slight_smile: Great work, I look forward to seeing what else you do!

Edit: To illustrate my own lack of game development experience, behold the only OpenFL game I’ve made which can actually be called a game:

Mini Rainbow TD (10MB - file size warning)

It’s worth taking everything I say with a grain of salt, I’m an amateur. :slight_smile:

About your question 0 :

You are going in a wrong way with events oriented pattern.
Imagine an huge horde of 50.000 peons. 50.000 listeners which are themselves registering/unregistering between and all around themselves… Impossible to maintain.

You should consider to study the ECS pattern, Entity•Components•System, not the framework, the design pattern.
That’s what allow a lot of entities which adopts some Components, or functionalities, to work/interact ones with others without the leak of performance caused by events driven patterns.

I guess it is exactly why there was a render() function.
Each frame, with an ECS pattern, you may centralize :

  • THE initialize() call.
  • THE update() call.
  • THE render() call.

It is a far more flexible and faster solution, avoiding you a lot of headaches.
At first, when you come from ActionScript it is hard to believe in, but at the end, it is procedural, quick to execute, simple to update/maintain the codes and their utilities.

Anyway, I like the way it goes, keep on the good job !

About your question 1 :

I guess the previous answer the case.
Events may be limited to the User/Player interaction, which may update data… Not graphics/Not texts, just data. And in your render() main loop function, you may check the data logically displayed and update the display.

About your question 2 :

You can also switch - at the fly - a Tile by a TileContainer which may contains the concerned former Tile and the new complementary Tiles.
When no more use of that complementary tiles, switch back your TileContainer in a single Tile.

In conclusion, it is first really good to discover your work which is great !.
I love the directions it takes, so many memories from the past are raising, like raging about the lost ones, or silly ones which can’t swim but still trying to collect impossible to get ressources.
So funny !
I will try again later.

2 Likes

When I first learned AS3, I wrote a fighter jet shoot-em-up and added homing missiles. Every missile had it’s own brain to determine where to target itself :sweat:. I changed this later to a MissileManager that iterated through all missiles[] and updated their homing logic.

FYI events support “bubbling” when on the display list. I suppose Tile and Tilemap do not support this, so that doesn’t entirely help. The approach there is to add a listener to a container, then every child object can dispatchEvent at will and the parent will receive the event.

If you need a response right away, events are nice. If the change is not processed until an update() step, then it’s okay to just change a status boolean or enum (dirty = true, status = NEED_SOMETHING, etc) and iterate through your objects and handle the flag

This could also be done for tile IDs, like tileChanged = true and if (tileChanged) tile.id = newTileID. Re-assigning an int value is not terribly expensive however and is often cheaper than checking and modifying an additional boolean

1 Like

Thank you all for the great feedback! I’ll try to address everything you all said in my subsequent development, and also talk about a few things here.

@smiley8088 Your ideas about how to make custom events less of a hassle are especially intriguing to me! I did consider making a single new event type with a Dynamic data field, but for such ubiquitous and performance-critical objects I was concerned about the overhead of all the typecasting. Macros, however, sound like such a fun puzzle to sink my teeth into—if for no other reason than to learn more about the incredibly powerful feature set that Haxe provides! I’ve already been messing around with macros for creating static final strings of asset file paths (in the style of HaxeFlixel), so if events continue to be relevant, I’ll definitely explore the macro route a bit further.

And thank you for all of the gameplay-specific advice too! I would like to do a significant reworking of the peon job logic to make them seem more “intelligent” without also making the game trivially easy. Peons do, in fact, check resources that they collide with to see if it would satisfy their current “fetch” job, and collect that resource (rather than pressing on to their original target) if a match is discovered. But this only works for their current job, and not for “there’s a tree in the way of my rock: maybe I should harvest the tree instead.” For everything else that you mentioned, though, you’re absolutely right that the peons are distressingly incompetent, to the point of detracting from the gameplay.

I do kind of want to keep the tower “rock-mining” concept a secret to be discovered. It seems like it could be a pleasing “a-ha” moment for players to see that the tower’s bricks are the same color as the stones, that peons are carrying pebbles away from the tower, etc… But I also want to ensure that as soon as the tower starts coming down, the reason for its demise is absolutely clear (rather than a confused “why am I winning?” emotion).

@Stephane I’ve done a bit of ECS work in the past! It would certainly help keep an entity-heavy game like this one clean and efficient. However, I’m also trying to balance a longer-term goal of understanding the reasoning behind how a game engine like HaxeFlixel is constructed, in part by following the design patterns laid out in their code. Venturing too far into a pure ECS architecture (or any other design pattern) would make it more difficult for me to read and understand unfamiliar parts of an engine without that architecture in mind.

As for TileContainers, that strategy is exactly what I ended up doing for both the smoke puffs and the health bars! I was very pleased with how the abstraction ended up working.

@singmajesty After reading your comment I checked the OpenFL source out of curiosity, and I found that setting the tile ID to the same value is indeed handled gracefully in the code! I can also inspect the source for other properties (like textfield content reassignment) for similar behaviors, and decide on light- or heavy-weight flags/events as necessary to keep from bogging down the update/render loop with assignment calls.


As mentioned before, I’ve been digging around HaxeFlixel’s source and finding a great number of fascinating design decisions regarding many of my issues mentioned here. They use a global bitmap cache so that sprites can request the same image (or spritesheet) in their own constructor function without generating a gratuitous number of redundant BitmapData objects or separate draw calls. And, for health bars, their FlxBar class does scaling tricks similar to what I ended up doing, to keep the number of draw calls down. Granted, I explicitly added colored pixels to my spritesheet asset to reference when drawing the health bars, while they have a more robust caching system that seems to create those pixels/rectangles on the fly.

Ultimately, it seems that some of these issues are not able to be decomposed into bite-size programming gems. But I’ve also been able to learn a ton by doing this project in raw OpenFL, and it has helped an incredible amount in being able to read and understand game engine code!

1 Like