Categories: Devlog | Game Design | Game Development | News | Retrospective | Technical | Web Development |
  • Getting Started with Unit and Integration Testing in Godot

  • Game Development · 2019-05-13 · nightblade
  • screenshot with code on top (Image credit: Godot blog)

    Godot affords the possibility of unit and integration testing gdscript code via the GUT extension. If you're getting started with unit/integrated testing (collectively "automated testing") or Godot, and interested in what automated testing can do for you, read on.

    What is Automated Testing and Why do I Care?

    Your game contains lots of logic. When you make changes, you need to re-test that everything that worked before, still works (nothing broke). As you find and fix bugs, and get into corner-cases of your game (eg. testing the entire play-through), manually re-testing everything becomes more and more time-consuming.

    Automated testing provides a solution to these problems: what if you could write code that would test your game code? What if that code could run through all the necessary scenarios of your game in a matter of minutes, or seconds? What if you could run the tests after every single change, and be sure that you never broke anything?

    Automated testing provides exactly this benefit. You write test-code that tests your "production" code. Unit tests, which test very small levels of functionality, run in in a matter of milliseconds; integration tests, which typically test workflows (fight monster, get experience, level up, spend skill points) take longer, but allow you to automate workflows.

    Finally, any experienced developer with automated testing will agree: the mindset change you get from automated testing is unparalleled. Not only can you test every single line of code you change, making sure it works, but you can also make bigger, riskier changes, because of your safety-net of unit tests. You can actually go faster, and make bigger changes, with your safety net of automated tests.

    The Downside of Automated Testing

    Like many things, automated testing is a trade-off. Unit testing is the de-facto standard now in the software industry, so we happily accept the downsides in exchange for the wealth of benefits we receive. Regardless, here they are:

    • Automated tests take time to write. You could be writing production code instead! For very tiny projects, proof-of-concepts, prototypes, and simple applications, you may not care about testing at all.
    • Writing testable code changes your code. Your code ends up being more decoupled, more isolated, and more testable. While these are all good software engineering practices, some communities look down on this.
    • Flaky tests. Poorly-written integration tests (and sometimes unit tests) can fail on and off, sometimes for reasons not related to your change. (As you become more experienced with testing, you learn how to write less-flaky tests.)
    • Major refactoring is expensive. If you decide to change a significant amount of code - like moving a method from one place to another, or significantly changing functionality - you now need to pay the cost of updating lots of broken tests.

    Overall, the benefits heavily outweigh the costs. There's really no reason not to write automated tests!

    Unit vs. Integration Testing

    We touched earlier on the difference between unit and integration tests. Typically, unit tests test a single method or class. The hallmark of unit tests, is that they don't depend on anything external - they don't touch the database, the file-system, they don't make web-service calls, etc. Because of this, unit tests tend to be extremely stable and robust, and run in sub-millisecond time. A large suite of thousands of unit tests may run in just a few seconds.

    However, unit tests, by nature, test only a very limited set of functionality. Integration tests, in contrast, typically test more complex scenarios, and/or workflows.

    Imagine we wrote an RPG-style game. The user can battle monsters; on victory, he receives experience points, and can use those to invest in learning new skills. If we structured this as typical domain-driven classes (such as Player, Monster, etc.) we might write unit tests such as:

    • Player.attack(target) damages the target
    • Player.hurt(damage) decreases health
    • Player.hurt(damage) throws an error if damage is less than zero (programmer bug)

    These are all class/object/method-level tests. In contrast, we might write integration tests such as:

    • When a player fights an enemy and defeats it, the player receives experience points
    • When the player receives enough XP, they level up and gain skill points
    • When monsters defeat the player, the game reaches a "game over" state

    These tests, by nature, work with a larger set of code - so you can test more things. But, they can also be more fragile - if something breaks in the workflow (damage calculation broken? Level-up calculation broken? Skill-points UI broken?), the test can fail.

    Godot with Mono vs. GDscript

    Godot provides two options for unit testing, depending on whether you prefer C# or GDscript.

    If you prefer C#, create your project as a Godot + Mono project, and write as much logic as possible in the C# code. This way, you can use the "usual" set of C# testing tools (NUnit, Moq, etc.) and everything Just Works.

    If you can't, won't, or prefer GDscript, you can use the GUT extension. (GUT stands for Godot Unit Test.)

    Getting Started Testing (with Gut)

    The GUT project page on GitHub provides a detailed explanation of getting started, which I won't repeat here. Install GUT in your project, create a new scene with the Gut object, and you're ready to go.

    Instead, I'll focus on getting started testing: what do you test? Assuming you have an existing project with an extensive code-base and zero unit tests, what do you do?

    Fundamentally, you need to start doing two things:

    1) Refactor your code to make it more testable 2) Write tests for any new code

    We didn't really touch on testable code in any depth. The short version is: write simple classes with simple methods, and avoid dependencies on any external "things." For example, if your Player.damage class depends on a Globals.battle_difficulty multiplier, accept it as a passed-in value instead of referencing the Globals script. Change this:

    // player.gd
    class damage(target):
      var total_defense = target.defense * (1 + Globals.battle_difficulty)
      target.current_health -= self.strength - total_defense
    
    // Elsewhere
    player.damage(selected_monster)
    

    To this:

    // player.gd
    class damage(target, battle_difficulty):
      var total_defense = target.defense * (1 + battle_difficulty)
      target.current_health -= self.strength - total_defense
    
    // Elsewhere
    player.damage(selected_monster, Globals.battle_difficulty)
    

    While the code is functionally equivalent, the latter is far more testable: you can pass in whatever value of battle_difficutly you want in your tests, even invalid values like -3! In fact, this is the kind of functionality you want to unit-test: in addition to making sure it works with a correct difficulty, you can test that it fails appropriately (eg. throws an error or returns 0) with an invalid value - something you can't easily test through the actual game.

    In Conclusion

    We covered a lot of ground in this article. In summary:

    • We talked about automated testing, and why we care about it at all
    • We looked at some of the benefits and advantages of unit-testing
    • We touched on some of the downsides, most of which are not really disadvantageous
    • We looked at unit vs. integration tests, and one way to define integration tests as workflows
    • We covered testing with C# (in brief) vs. testing GDscript with Godot
    • We looked at writing a few simple unit tests on a player class, and making code more testable

    I hope this post benefited you, and convinced you to at least try unit testing! Please let me know your feedback on Twitter - whether you decide to try testing, what you didn't understand, and what kind of topics you would like me to cover in future articles on Godot and testing.

  • Godot Game Crashes On Startup, Logo Shows/Hides

  • Technical · Godot Android · 2019-04-29 · nightblade
  • I recently rebuilt a small app/game using Godot. For reference, the game contained one core loop, and took about four hours to build (from scratch). I reused all of the assets from the previous version of the game (audio and graphics), which took the bulk of development time on the original.

    The game built and ran flawlessly in Godot on Windows. As soon as I ran it on Android, the game briefly showed the Godot loading logo for ~100ms, then it disappeared, then reappeared/disappeared around four times (you can see the original issue I opened about on GitHub, here).

    The root cause of all this was multiple resources preloading the same script. I had a scene that preloaded HelperScript.gd and then Second.tscn; but Second.tscn also preloaded HelperScript.gd, causing the crash.

    I found three different ways to address this problem:

    • Use singletons. Instead of preloading HelperScript.gd everywhere, I simply added it to the list of auto-loaded singletons in Godot. Problem solved. (This solution also makes sense, because I needed the same common method from HelperScript.gd.)
    • Use load instead of preload. This makes me think preload is bugged; calling load succeeds without any issues. Obviously, this has performance implications - you may want to preload your entire game at startup time.
    • Refactor. In my case, I needed a common function from a shared script. What if I used the same file, but two different functions? Perhaps I could split HelperScript.gd into two different scripts, and just load what I need instead.

    If you find your Godot game crashes on startup without explanation, maybe this is something you can investigate. (Failing that, go for a git bisect and try to isolate the commit that broke everything.)

  • Text Rendering Performance Check for a ReactJS ASCII Roguelike

  • Technical · ReactJS Roguelike · 2019-03-19 · nightblade
  • image

    Recently, I was tasked with learning ReactJS (or just "React"). So, I made the most obvious choice possible: I decided to write an ASCII roguelike, which uses React as the front-end.

    Why an ASCII Roguelike?

    Why? For several, obvious reasons:

    • I have very limited time to work on this; perhaps as little as one hour a day, or less.
    • ASCII roguelikes require little beyond a good monospace font -- no complex graphics, very simple animations
    • I enjoy creating roguelikes. It's fun. Fun motivates me to work on this, which fuels my learning.
    • Having created several (failed/incomplete) roguelikes in the past, I know how to code them. I don't have to think too hard about level generation algorithms, line-of-sight (or area-of-sight) lighting, etc. so I can focus on the technology.

    I set out to create, in my mind, the first step of any text roguelike: draw text on the screen really fast and see how it performs. HTML should render at lightning-speed in my modern browser, right?

    Wrong.

    The Simplest Possible Performance Test

    React is just a front-end technology; it doesn't dictate how I should structure my HTML. With that in mind, I set out to answer two questions:

    • What kind of performance can I expect with my roguelike?
    • What factors affect performance? Does changing the font, applying colour (or not), using nested div tags vs. span tags, etc. make any difference?

    In brief, the answer to question #2 ended up being "no." Performance bottlenecks on something else entirely; none of those changes make any significant difference.

    I wrote a little Javascript that tests exactly what I want:

    • Define dimensions of the screen. In this case, 50x16 tiles.
    • Create a div tag for each tile. Style it appropriately (eg. width/height big enough to fit any character)
    • Very frequently (like, 60 times per second), update each tile's display to a random character with a random colour.

    You can see the code, more or less (with FPS counting), here. It's simple, and to the point.

    The Results

    In a word: abysmal! On my fairly beefy dev machine (lots of RAM, good CPU, mediocre GPU), it renders at a measly 6-7FPS. You can see the results in this tweet.

    As I mentioned earlier, I tried several variations; none of them improve performance, at all. The core of it comes down to a call to set the character itself. Pseudocode:

    var character = ...
    var colour = ...
    var tile = window.tiles[i];
    tile.innerText = character
    

    It turns out that browsers, even modern ones, even on beefy hardware, are really, really slow when you set innerText. The only alternative - using innerHTML -- is slower, and probably broken on Internet Explorer.

    The Conclusion

    For an ASCII roguelike without too much going on, 6FPS is probably enough. If I really cared about performance, I could switch to canvas-rendering and a bitmap font (lots of work and not sure how it works with React), or using images - either images of text, or real images.

    For me, the goal is to learn ReactJS, so I plan to continue forward with this as-is, without major surgery.

  • Creating Tilemaps with Tile Entities in Godot

  • Technical · Godot · 2018-10-02 · nightblade
  • Godot's tile editor provides you with the ability to quickly make 2D tile-maps, similar to RPG Maker. This makes it easy to design and tweak maps. But can we take it a step further? Can we, in fact, use the tilemap editor to create functioning entities -- making it a domain-specific editor for our specific game?

    We can. This post walks through a lightning-quick method you can use.

    The Core Idea

    Godot's Tilemap entities are simple. They're essentially a reference to a tileset (graphic with all your tiles on it), and one or more layers of those tiles drawn on a grid. In Godot, tilemaps are all about presentation.

    What if you want to add functionality though? Imagine you're creating a 2D adventure or RPG (like, hmm, Eid Island maybe?), and you want to be able to draw enemies on the map. Or trees you can chop down, coins you can pick up, or anything else.

    By itself, Godot doesn't provide a way to do this. However, I asked around on Discord, and checked some issues on GitHub about this; a definitive, simple approach appeared, something like a best-practice. How? Easy.

    • Define your tilesets as usual, including objects
    • Draw them in as many layers as you like, as usual
    • At runtime, iterate over the tiles, replacing the object ones with real entities (Scene instances)

    The Secet Sauce: Swapping Tiles for Entities

    Godot makes this process quite easy. As a pre-requisite, I would recommend:

    • All your tiles have proper, uniquely-identifying names (like Tree or Plain Water)
    • You create entities for everything you want to replace, such as a Tree scene (subclasses Node2D, has a Sprite, script, etc.)
    • You map the two. I use a dictionary such as: var entity_tiles = { "Tree": preload("res://Scenes/Entities/Tree.tscn") }

    With this in place, we simply iterate over the tiles and -- if the name appears in our dictionary -- replace the tile with its corresponding entity. Here's an early reference implementation in Eid Island.

    # Find entities on the map (eg. trees). Remove them and replace them with
    # real entities (scenes) so that we can have logic (attach scripts) to them.
    func _populate_entities(map):
        var possible_tilemaps = map.get_children()
        for tile_map in possible_tilemaps:
            if tile_map is TileMap:
                var tile_set = tile_map.tile_set
                for cell in tile_map.get_used_cells():
                    var tile_id = tile_map.get_cellv(cell)
                    var tile_name = tile_set.tile_get_name(tile_id)
                    if entity_tiles.has(tile_name):
    
                        # Spawn + replace with entity of the same name
                        var scene = entity_tiles[tile_name]
                        var instance = scene.instance()
                        map.add_child(instance)
                        instance.position.x = cell.x * Globals.TILE_WIDTH
                        instance.position.y = cell.y * Globals.TILE_HEIGHT
    
                        # Remove tile
                        tile_map.set_cellv(cell, -1)
    

    Globals.TILE_WIDTH and TILE_HEIGHT refer to the (fixed) tile size; you can, alternatively, use the tileset/tilemap to get the cell size, or use the entity size as a reference (although the entity should be exactly one tile size for that to work).

    With this in place, you can quickly and easily create levels with real functionality from simple tilesets.

    Next Steps

    One obvious missing piece of functionality is customization. What if my tile is, say, a door that warps somewhere, and I want to specify the destination properties when I draw the tile? How can I do that?

    Unfortunately, I don't know the answer yet. As far as I know, Godot doesn't allow you to add additional variables/properties to the tile itself. Perhaps you could store the data elsewhere, such as a separate dictionary mapping entity/coordinates to custom data.

    If you know how to solve this latter problem, drop us a note on Twitter or in the Godot Discord chat so we can use it too!

  • Eman Quest Restrospective: A Procedural RPG in a Month

  • Devlog · Retrospective · 2018-09-28 · nightblade
  • screenshot of a hero facing a boss

    This month, I set out with a very specific goal: create a 2D procedurally-generated RPG; something I've never heard of (or done before). Although I ran out of time, I plan to devote another month to the game. This retrospective discusses the good, the bad, and the work ahead of us.

    Goal: What is a Procedurally-Generated RPG?

    I grew up playing Secret of Mana, Final Fantasy, Chrono Trigger, and other great titles. One day, I thought to myself, what if I can only create one more game before I die? What would I create?

    The answer? Something akin to those favourites; a game that generates a brand new, quality RPG, every time you plan. I spent some weeks brainstorming what I needed and didn't need, plans to create content such as monsters, weapons, etc. Some things clearly need to be procedurally-generated (such as dungeons and maps), while others don't (such as enemy sprites and player skills).

    A couple of false-starts later, I finally opened up the Godot editor and started the third (or fourth) attempt.

    The Good: What Went Well

    Initially, I focued on the technical side of procedural generation: creating stuff! As of writing, the game:

    • Generates a new, persistent world map each time; this includes a river that runs into a small lake, and randomly-positioned dungeon entrances
    • A procedurally-generated forest dungeon; it's a single, large map, with trees, dirt paths, monsters, and a boss.
    • Procedurally-generated equipment (weapons and armour), each with unique stats modifiers
    • Loot in the form of treasure chests in the forest dungeon.
    • The story. While very basic, the game generates the intro plot text, including the town name and final enemy type

    The forest dungeon took longer than expected. On the technical side, I figured out how to make Godot generate and persist my maps/game; initially with packed scenes (which got garbage-collected when I changed scenes, causing crashes), and later by separating the generation of game data from the presentation (populating scenes/tilemaps/etc. from content).

    Solving technical issues aside, I also spent too much time on what didn't go so well: the battle engine.

    The Bad: What Could've Been Better

    I find the concept of memory games fascinating (as an ex-Lumosity player). An Extra Credits video on puzzle vs. reflex mechanics challenged me to introduce something new into my game.

    At the heart of every RPG, in terms of fun gameplay, lies a fun and challenging battle system. I decided to design a memory-based battle system:

    battle engine animation

    This probably proved to be my undoing. I spent several days designing, balancing, and tweaking the battle engine; adding new features, action types, and skills. Five beta testers provided feedback after trying it out:

    • One really liked it
    • One said it's okay and could be improved
    • Three others didn't like it

    In the end, after several days, I forced myself to stop tweaking/changing it and decided to focus on fulfilling the rest of the game.

    Last, and perhaps worst, the game feels incomplete. Sure, you get a randomly-generated forest; but even with the intended scope for the end of the month (two dungeons with two different variations, including unique monsters), it might not be enough to give players that experience of a procedurally-generated RPG.

    What's Next

    I don't intend to give up; I plan to fill out and ship a version of this game (although with what functionality/content remains to be seen). My current plan of action looks like:

    • Finish the end-game sequence so players can actually complete the game
    • Create two variants on the forest (frost forest and death forest) each with unique enemies
    • Create a second dungeon (cave) with two variants (one is a volcano)

    Finally, I plan to create a proper RPG world. A good RPG, like a good novel, contains elements that all harmonize and work together to communicate a singular world/message. Right now, we have a patch of hastily-named enemies, skills, etc. and these need to be brought together.

    More specifically, I didn't yet start the other hard/fun part of a procedural RPG: creating a procedural world (and story) that feels unique. This includes a unique/different protagonist for each story.

    Finally, another round of beta testing should reveal whether the game is interesting/fun or not, and I should either finish it as-is or if I need to rewrite/rethink battles entirely. Time will tell. September may be almost gone, but October brings another month brimming with potential for me to see this game through to its potential.

    If you're interested in following my development on this project, feel free to follow me on Twitter.