• Crystal Caverns March Update

  • Devlog · Crystal Caverns · 2021-03-29 · nightblade
  • Introducing Crystal Caverns, a match-3/roguelike hybrid with persistent upgrades. In the last two months, I took this game from a rough prototype to what you see in the video. At present, the game includes:

    • A bare-bones battle system: players and monsters take turns attacking.
    • A basic match-3 game with collapse mechanics, albeit you can swap any two tiles
    • Victory and defeat
    • Persistent currency (crystal shards) which you retain across battles

    Battles appear bare-bones at the moment, but include some depth: - Players can attack, critical-strike, boost defense, poison enemies, drain, and heal. - Monster attacks affect the board in various ways (currently, they just eat random tiles).

    That includes enough of the core game to improve upon.

    Next month, I plan to turn my attention more towards the roguelike side of the game: generating dungeons with monsters, finding treasure (including persistent treasure), and balancing exploration with progression.

  • Blog Reboot

  • News · 2021-03-18 · nightblade
  • sign that says: changes ahead

    Hello! Long-time readers of this blog may notice a couple of changes:

    • A new, mobile-responsive theme
    • A complete restructuring of content

    I decided to redo the blog to make it mobile-friendly, and to hange the content - instead of game development notes and game design articles, instead, I plan to focus on my games.

    What can you expect going forward? Weekly posts show-casing the best of whatever current project I'm working on.

    Old articles will stay available for a while.

  • Web Application Translation Architecture in .NET

  • Other · 2020-08-11 · nightblade
  • Tags: .NET Core, Architecture

    globe of languages

    So you want to localize and internationalize your web application? There are a lot of considerations, but here, we briefly discuss just the translation part.

    This post covers a couple of the architectural/design options and discusses their respective trade-offs. While this is a bit specific to .NET, other languages no doubt provide similar concepts, with various levels of API support (e.g. language stored in cookies)

    Individual Translations via RESX Files

    .NET and .NET Core provide some infrastructure called "resource files" (.resx) extension). These are XML files which you can edit directly in Visual Studio; at runtime, they compile down to a binary format. They generally recommend creating a few resource files (one per back-end controller or shared module). Advantages of this approach include:

    • Easy editing of the file (in-IDE)
    • Easy versioning/history of the file (it's a text format)
    • API support for configuring the prefered language in a cookie, etc.
    • Localized changes on translation change (no need to re-test everything)
    • Efficient, with a small size at runtime

    The disadvantages of this format include:

    • Any translation change requires recompiling the entire application
    • Editing dozens of files can be very cumbersome
    • Difficult to make a translation change and see it immediately in-app (unless you're a developer)
    • You can't store any sort of metadata (e.g. notes) with translations

    Overall, I think this approach works well if you plan to update translations periodically and don't need an external translater. (If you do, and they're not a coder, you'll need to make additional tooling to export/import the strings in a format they can understand.)

    Using a Database for Translations

    One common alternative approach is to store the translations in a database (relational or otherwise) and simply load/display them at runtime. This confers some additional advantages over resource files:

    • Ability to update a translation and instantly see the change in-app
    • Ability to store meta-data (like notes) with each message
    • You can quickly query to find missing strings in various languages
    • Non-technical users can easily edit translations via a simple web UI

    However, it contains some additional downsides:

    • You need to write a web UI to allow translators to be able to view/update translations
    • Making several database calls just to load one view/page, can be costly in terms of performance
    • You can read/cache strings in memory on app-start, but then your app requires additional memory per language

    I think this approach suits situations where you absolutely must be able to see updated translations reflected immediately, or where you have non-technical translators who need an easy way to be able to update translations.

    If you know of any other architectures/designs, drop me a note on Twitter and let me know!

  • Thoroughly Testing Your Game

  • Other · 2020-07-07 · nightblade
  • Tags: Planning, Testing

    2 unit tests, 0 integration tests

    Why Test?

    Of all the fun, interesting, exciting game development topics I could write about, I chose: testing.

    Why?

    Because testing is essential to shipping your game without bugs and without hours upon hours of manual effort. As we'll see shortly, there are well-known patterns and practices you can apply; some generic to softwre testing, and some specific to game testing.

    I discovered these practices after working more then a decade as a professional software developer, and applying them on and off for a decade as a hobbyist game developer. They take time, yes. They work, often and they're better (faster, more reliable, consistent) than manual testing.

    Overall Strategy

    Your goal with software testing is to release your game at the highest possible quality: that means NO BUGS! Everything should work the way the designers intended, without us spending tons of manual hours play-testing the same parts over and over and over (yawn).

    Your overall strategy needs to include the following categories:

    • Unit Testing: So you know your methods/classes/scripts/objects work at a granular level.
    • System Testing: Make sure cross-object workflows succeed, like "user last-shots an enemy and gets bonus gold." Sometimes called integration testing, functional testing, or end-to-end testing.
    • Regression Testing: Every bug you find and catch should be exploited by a unit test. If that test passes, it guarantees the bugs won't repeat.
    • Exploratory Testing: There's no substitute for playing your game and trying weird stuff. You'll find quirks and things that your automated tests won't catch.

    Without these fundamentals, be prepared for lots of bugs and poor game reviews! Above and beyond them, depending on your available time, skill, and interest, you may benefit from additional categories:

    • Balancing: Write tests that make sure the game is balanced, e.g. each class has roughly the same DPS, or that dungeons generate with roughly the right progressive difficulty
    • Performance or Crash Testing: Running previously-problematic code and making sure it's fast or doesn't crash. This includes running your game overnight to see if bad things happen.

    I can't reasonably explain all those topics in a suitable depth without writing an essay, but get in touch with me if you would like more information on these topics.

    Testing Process

    Regardless of how little or how much you utilize the above categories of testing, you need to properly test your game whenever you make a change. Properly means: sufficiently testing those areas that may be affected, to make sure they still work as expected.

    After all, global variables are still a thing. Singletons can easily be accessed from anywhere. Highly-coupled code is common in (and out) of games. You need to make sure you didn't break stuff by mistake.

    How? Simple; but it all depends on two things:

    • You have a continuous integration setup which builds on every commit
    • You have a way of producing a final kit (installer, standalone executable, etc.) that you're going to ship

    Let's start with baby steps. Whenever you make a code change:

    • Write unit tests
    • Run all unit tests continuously (before you check in)
    • Have a continuous integration solution (e.g. Jenkins, Travis-CI) build your code and run tests every commit

    This makes sure your code works (very granularly, at a class/scene/script method) and that you didn't break anything in an unrelated area. Good unit testing usually uncovers bugs (while you're writing the test code). They run fast (sub-second), so you can test lots of things quickly.

    If you have integration/functional/system tests, they're usually slower (seconds each), but highly valuable, since they're like a mini version of your game in some scenario/situation. Run those as part of the build, too.

    After the build runs, it generates a playable binary of some sort. Against this, you want to properly test the new feature/change/fix you just added. Like, properly, including things leading up to it and things after it should have happened.

    With this done, you can be reasonably sure your game works, assuming you have good coverage of unit, regression, and system tests. You may want to run some exploratory tests: mess around and see if you can find anything or break anything.

    Release-Time Testing Process

    Your game is done, hurray! Congratulations! Now, the fun part: testing your game before you ship it. Since you have a robust build system in place, take the binaries from a passing build, and test it.

    The best way to know if your game is likely ready, is:

    • Play it from beginning to end
    • Stop and fix any bugs you find, verifying them through the build binaries
    • Depending on the game length, either start over, or finish the run

    Repeat this until you can complete the game without finding anything to fix.

    Also, when you find a bug, you don't need to re-test everything; you can re-test just the affected area (e.g. inventory management) and any other areas that might be affected because of shared/common/global code (e.g. item crafting).

    Balancing, Performance and Crash Testing

    If you write tests for balancing (make sure monsters, or skills, or levels, etc. are balanced with respect to each other), performance (make sure a certain part of the game runs fast enough), or crash (exercise a workflow and check it doesn't crash) testing, you can run them as little or as much as you like.

    I recommend running them at least weekly or per-sprint, to make sure nothing broke unexpectedly. If you have a good CI system and don't mind longer test runs, you can run them as part of the CI.

    Conclusion

    Testing is expensive! It takes time and effort! Write tests, debug tests, update tests! Anyone who tried this process will likely tell you it's worth it. The pay-off of pressing a button or pushing a commit, and having confidence that your game works, in every way you tested, is well worth the effort.

    Don't be daunted. Take baby steps. Practice. Ask questions. Try to add a bit more to each new project you start, so you can eventually master all these skills. They take time.

    If you ever want a second opinion or sounding board, or have questions or comments, feel free to reach out to me on Twitter or Discord.

  • A Primer on AABB Collision Resolution

  • Other · 2020-01-30 · nightblade
  • Tags: Physics

    video demo

    This blog post includes a discussion about AABB collision resolution: what it is and isn't, it's strengths and weaknesses, some common pitfalls, and how you can (hopefully) implement it in your low-level gaming tool of choice, if needed.

    I learned all this (the second time) as part of adding fast/stable collision resolution to Puffin, a fast, lightweight 2D game engine built on top of MonoGame.

    AABB, Collision Detection, and Collision Resolution

    Some quick definitions to start:

    • AABB (Axis Aligned Bounding Boxes) means two non-rotated boxes, that are aligned on one axis. In 2D, everything on the same "layer" or "z" is on the same axis; in 3D, it means your boxes are on the same plane.
    • Collsiion detection means detecting if there is a collision (two AABBs overlapping) or will be a collision (eg. in 0.3s these two will start colliding).
    • Collision resolution means resolving the collision. Broadly, there are two approaches to this: prevention or pre-collision resolution (stop just at the onset of collision when the two AABBs touch), and post-collision resolution (once two AABBs overlap, move them backward in time until they no longer overlap).

    My approach to AABB uses pre-collision resolution, because it tends to be less complex and more stable.

    Pros and Cons of AABB

    Why should you use AABB collision resolution? There are many other options, such as collision points, sphere/line collsion algorithms, polygons, etc.

    The strengths of AABB include:

    • It works well in most cases. Most games can do well enough with just bounding boxes on their entities.
    • It's relatively simple to code (math-wise), because it's just boxes.
    • It's quite cheap computationally (eg. doesn't have an expensive square-root calculation, unlike spherical checks)

    However, it includes some drawbacks:

    • It doesn't work with rotated boxes
    • It doesn't work well with non-box shapes
    • It requires extra work for it to work well with multi-entity collisions
    • It's succeptible to "tunnelling" (high-speed objects move through solid objects because of their velocity)

    If you can live with those limitations, I recommend AABB, primarily because it is computationally cheap (works well with a high number of colliding entities).

    Collision Resolution is Complex, like Physics

    While AABB collision resolution is relatively easier to code, it doesn't mean it's easy to code. Many game frameworks don't include collision resolution, because this is part of the physics engine.

    Read that again: it's often part of the physics engine. Physics engines are notoriously difficult to get right, and require lots of fiddling and corner-case evaluation. Even high-quality physics engines have limitations, such as tunneling.

    It took me around 10 hours to discover all the caveats and get this to work right. And it works well, including with multi-entity collisions. Test thoroughly.

    That said, my implementation includes a few bonus features:

    • It's resistant to collision tunneling (but not impervious)
    • It works with multiple objects colliding at the same time
    • It allows an object to optionally "collide and slide" along the object it collides with

    With that out of the way, let's dive into the actual theory of how to make a stable AABB resolution, and then some code.

    High-Level Description of AABB

    AABB collision resolution works by looking at the X and Y component resolutions of your velocity. Simply put:

    • Consider the intended destination of your moving entity (where it will be after updating its position, not where it is now)
    • Look at the distance dx to travel before we collide on the X-axis and dy for the Y-axis
    • Divide these by your component X-velocity and Y-velocity respectively (vx and vy) to figure out how long before each axis collision takes place (tx and ty)
    • Resolve the collision on the axis that collides first

    AABB sweep test with velocity

    This excellent diagram (credit: LaroLaro on GameDev.SE) shows a moving object (A) that will collide with a second object (B). Based on the component velocities, you can see from the projected A box that the faster collision will be on the Y-axis first.

    Because collision resolution takes place on a single axis at a time, you may end up having to resolve the same collision multiple times to get a stable resolution. I find that running the collision resolution twice suffices.

    And Now, the Code

    Below, I discuss some pseudocode that's almost the same as the actual (C#) code from Puffin. The same code can apply to any programming language or framework.

    One unorthodox implementation detail I used: when each entity moves, I make a note of their "intended" destination X/Y. If that location would cause it to collide inside an object, I instead update it so it stops just at the point of collision. In my pseudocode below, you'll see this as intendedX and intendedY.

    For every collidable entity, you're going to compare it to every other collidable entity. Since we're using AABBs, this is pretty simple: just compare the coordinates plus intended movement of the moving entity, against the entity that isn't moving:

    private static bool isAabbCollision(float x1, float y1, int w1, int h1, float x2, float y2, int w2, int h2)
    {
        // Adapted from https://tutorialedge.net/gamedev/aabb-collision-detection-tutorial/
        return x1 < x2 + w2 &&
            x1 + w1 > x2 &&
            y1 < y2 + h2 &&
            y1 + h1 > y2;
    }
    

    You simply call this with e1.x + e1.velocity.x, e1.y + e1.velocity.y, e1.width, e1.height, e2.x, e2.y, e2.width, e2.height and it will return if they collide or not.

    However, to stop at the point of collision, we need to consider our entity's velocity: if it's moving right, then the distance to collide on the X-axis is the right edge of e1 compared to the left-edge of e2. If it's moving left, then vice-versa (left edge of e1 vs. the right edge of e2). The same thing applies when we resolve on the Y-axis.

    // Assuming we have two AABBs, what's the actual distance between them?
    // eg. if `e1` is on the left of `e2`, we want `dx` to be `e2.left - e1.right`.
    private static (float, float) CalculateAabbDistanceTo(Entity e1, Entity e2)
    {
        float dx = 0;
        float dy = 0;
    
        if (e1.X < e2.X)
        {
            dx = e2.X - (e1.X + e1.Width);
        }
        else if (e1.X > e2.X)
        {
            dx = e1.X - (e2.X + e2.Width);
        }
    
        if (e1.Y < e2.Y)
        {
            dy = e2.Y - (e1.Y + e1.Height);
        }
        else if (e1.Y > e2.Y)
        {
            dy = e1.Y - (e2.Y + e2.Height);
        }
    
        return (dx, dy);
    }
    

    Then, for every collidable entity, if it results in an AABB collision with another collidable entity, we figure out which axis collides first, based on which one collides first time-wise:

    // Another entity occupies that space. Use separating axis theorem (SAT)
    // to see how much we can move, and then move accordingly, resolving at whichever
    // axis collides first by time (not whichever one is the smallest diff).
    (float xDistance, float yDistance) = CalculateAabbDistanceTo(entity, target);
    (float xVelocity, float yVelocity) = (entity.VelocityX, entity.VelocityY);
    float xAxisTimeToCollide = xVelocity != 0 ? Math.Abs(xDistance / xVelocity) : 0;
    float yAxisTimeToCollide = yVelocity != 0 ? Math.Abs(yDistance / yVelocity) : 0;
    

    Resolving collision based on collision time solves some corner-cases where an object is very close to collision on one axis, but moving much faster on the other axis (eg. a player falling off a tall building moves into it, and instead of colliding against the side, he collides with the top).

    Once we know which collision is first, it's easy to resolve if the collision is only on one axis:

    float shortestTime = 0;
    
    if (xVelocity != 0 && yVelocity == 0)
    {
        // Colliison on X-axis only
        shortestTime = xAxisTimeToCollide;
        entity.IntendedMoveDeltaX = shortestTime * xVelocity;
    }
    else if (xVelocity == 0 && yVelocity != 0)
    {
        // Collision on Y-axis only
        shortestTime = yAxisTimeToCollide;
        entity.IntendedMoveDeltaY = shortestTime * yVelocity;
    }
    

    Finally, the most complex case: what do we do if the object would collide on both X- and Y-axes? We resolve the fastest collision first:

    else
    {
        // Collision on X and Y axis (eg. slide up against a wall)
        shortestTime = Math.Min(Math.Abs(xAxisTimeToCollide), Math.Abs(yAxisTimeToCollide));
        entity.IntendedMoveDeltaX = shortestTime * xVelocity;
        entity.IntendedMoveDeltaY = shortestTime * yVelocity;
    }
    

    Easy! If it would take 0.1s to collide on the X-axis, and 0.2 on the Y-axis, we increment the entity's X and Y by their velocity times 0.1 (the faster collision time).

    Finally, for stable resolutions, make sure you run the collision resolution twice per update. Since MonoGame-based frameworks give you the update time, simply run the update twice, with half of the elapsed time, each update:

    var halfElapsed = TimeSpan.FromMilliseconds(elapsed.TotalMilliseconds / 2);
    // Resolve collisions twice to stabilize multi-collisions.
    this.ProcessMovement(halfElapsed, entity);
    this.ProcessMovement(halfElapsed, entity);
    

    That's it, you're done!

    Slide on Collide

    With basic collision resolution out of the way, you might ask "how do I slide up against the target object instead of simply stopping abruptly?"

    The answer to that caused about 50% of my development time. The answer is "you collide as usual but then move on the other axis as much as is reasonable," where reasonable means "don't move so much you collide with something else." In this case, don't slide if doing so would land you in another AABB collision.

    Another complication I can't explain well is my need to refer to the "old" intended X/Y distances; I'm not 100% sure at this moment why I needed those, but those are needed for a proper resolution.

    Some code:

        if (entity.SlideOnCollide)
        {
            // Setting oldIntendedX/oldIntendedY might put us directly inside another solid thing.
            // No worries, we resolve collisions twice, so the second iteration will catch it.
    
            // Resolved collision on the X-axis first
            if (shortestTime == xAxisTimeToCollide)
            {
                // Slide vertically
                entity.IntendedMoveDeltaX = 0;
                // If we're in a corner, don't resolve incorrectly; move only if we're clear on the Y-axis.
                // Fixes a bug where you  move a lot in the corner (left/right/left/right) and suddenly go through the wall. 
                if (!isAabbCollision(entity.X, entity.Y + oldIntendedY, entity.Width, entity.Height,
                    collideAgainst.X, collideAgainst.Y, target.Width, target.Height))
                    {
                        entity.IntendedMoveDeltaY = oldIntendedY;
                    }
            }
            // Resolved collision on the Y-axis first
            if (shortestTime == yAxisTimeToCollide)
            {
                // Slide horizontally
                entity.IntendedMoveDeltaY = 0;
                // If we're in a corner, don't resolve incorrectly; move only if we're clear on the X-axis.
                // Fixes a bug where you  move a lot in the corner (left/right/left/right) and suddenly go through the wall.
                if (!isAabbCollision(entity.X + oldIntendedX, entity.Y, entity.Width, entity.Height,
                    collideAgainst.X, collideAgainst.Y, target.Width, target.Height))
                    {
                        entity.IntendedMoveDeltaX = oldIntendedX;
                    }
            }
        }
    }
    

    Wrapping it All Up

    That concludes a somewhat whirlwind tour of AABB collision detection. I hope you came out of it understanding the pros and cons, how to assess when you need it or not, and enough pseudo-code to actually get it working for you.

    I would love to hear from you! If you have any feedback, please drop me a message/tweet on Twitter.