Some backend work this week, to support item generator effects in level objects. An example is a bookshelf. Bookshelves are interactive objects that may or may not contain a scroll or a tome. Initially will appear full of books, but will become empty when searched. If an item is found, it’s automatically transferred to the inventory.
An additional fine point for bookshelves is that they are generated as a single feature in the native plugin that does the dungeon generation and placement work, whereas the C# side generates a cluster of bookshelves centered around the calculated feature position. So, some extra work to make things passable, etc. This is useful as we can do several nifty things if the native plugin generator works in terms of features, and the C# side can instantiate each feature into multiple similar entities. Another example would be a boss enemy with a number of minions, but that will come later on.
Another requirement for this work is to specify some sort of 2-dimensional enum for item types and subtypes, so we can tell the item generator system that we want a “tome” or a “scroll” object that has less-equal item level than some specified.
Another random fix was the AI of ranged attackers, where they would not take into account that obstacles might be in the way of the fired arrow. Well, now they do, and move into a position that provides a clearer shot.
I’ve also started having a look at the prefab support in the dungeon generation, written last summer, as I have to write some tool to generate prefabs to use with generators.
More miscellaneous work this week, under the general guise of “emergent gameplay”
Adjusted how diagonal movement blocking works, for QoL reasons. Before, if you wanted to move diagonally, both tangent direction had to be unblocked. So that, when moving, you never see the player going over any obstacle. I really, really disliked how that worked in practice, as 8-directional movement stops being fluid. Now, diagonal movement is only blocked when both tangent directions are blocked. This works far, far better and the movement is now very smooth again, without surprise-blocks that don’t seem to make sense.
Added support for thrown bombs (well, general category of thrown things that have a particular effect on landing). So that there are more AoE options for non-spellcasters, for example some elemental AoE for fighters, or some smoke clouds for stealth users, etc. I also added support for delayed-blast effects, so that you can have situations where you throw a delayed blast bomb, then it gets moved somehow (e.g. via trap, kick or a kinetic spell), and then it explodes. Russian roulette gameplay could then be to start throwing delayed blast bombs on teleport traps 😀
Realised the importance of a GUI for split and merge stacks, wrote some basic placeholder GUI for it, but also realised that I could not fit that somewhere reasonable based on the existing item GUI that I have… To elaborate: the current item GUI logic, which I was proud of, is to always have a pair of containers (player & ground, or player & chest, or player & other container), and you multi-select things in one container, multi-select things in the other container, and do a transfer. To help things, I also had some toggles for all/none selection per container. That’s all great, until we need to split a stack, as that just can’t happen with the multi-select which is binary in nature. So, back to drawing board for how the GUI will work for inventory transfers.
Added some initial support for destructible objects and durability. Again, this feature creep is essential, as breaking things is an integral part of roguelike life, as the remains of broken barrels, jars and doors from various games would all attest to. This can creep further into weapon durability, but I’m not going down that route, yet, although the deceptive simplicity of integrating that is tempting.
The major thing this week was making better Diablo 2-style auras but not rendering them dynamically; instead, I made a simple editor tool (based on this) that bakes auras as texture animation, bundling everything up as a texture atlas and using that. Made the in-game aura rendering code far far easier, than having separate shaders for each. Since each texture atlas supports up to 2048 animation slices (due to texture array limitations), for 64 frames per animation I can store 32 different auras, which I think is more than enough.
Another feature that I’m in the process of adding (added, bugged, debugging now) is freezing levels when you leave. First of all, freezing is important, otherwise if all the entities from all levels play, well that just doesn’t scale. So, to avoid a boring “freeze immediately” after you leave a level, at the moment I allow for 10 standard actions (a minute in “game time”), which allows entities to suffer repeated effects, move a bit, etc.
Some refactoring and some basic feature support this week, all towards bosses and elite enemies.
On the refactoring side, I ditched even more ScriptableObjects in favour of my own JSON-powered flyweight classes. The changed classes were some name generators and associated data.
On the same topic, I’ve made a “boss name generator” for boss creatures, which creates names such as “Satha the sadistic” and “Iveri the power-hungry”. Basically, the first name will eventually be derived using a language that’s prevailing in the region where the boss will be found, followed by some villainous adjective.
Fixed a visual bug where some visual effects like burning, freezing etc would be seen before the projectiles that caused them landed
Added support for creature traits and made a few. Traits are a collection of effects applied to a creature, like providing resistances, increased base health, etc. What this plans to be is what Heroes of Might & Magic did: every creature was differentiated from others based on its stats and, most importantly, traits, which gave unique flavour to units. The difficult part is to come up with interesting traits of course, befitting the genre and gameplay.
Added support for auras as an effect (applicable in traits). An aura is an effect that is centered on an entity and gets applied repeatedly to all entities (either friendly, or not, or anyone) within a particular radius. For example a boss with a damage aura could cause its enemies to take damage repeatedly while in radius, where as a boss with a damage-enhancing aura would cause all its allies/minions to get damage-enhancing bonuses while in radius.
Elite creatures get a level bonus plus a corresponding trait for elite creatures (e.g. max health, better damage, faster, etc), whereas boss creatures get a bigger level bonus plus a corresponding trait (or two?) for boss creatures (some aura, etc). I want to have something like Diablo 2’s unique monster bonuses
Elite creatures have a different visual indicator to boss creatures: same effect, different colour.
Early draft version (aka terrible looking) for visuals to represent auras. They are rendered correctly as a ground layer, alongside decals, but they need to be spiced up. I’ll be looking of course at Diablo 2 for reference, but the rest of the work for this might be art-related or making lots of pretty shaders, which is best reserved for an “I-don’t-want-to-code-architecture” day
Spawned creatures can be normal, elite or boss.
Here are the Diablo 2 auras to get inspired for any further visual work on this (source 1, source 2):
Quite a bit of refactoring this week, and some bug fixing as well. Net results: level-scalable effects and more/better status effects.
Effects now are level-scalable, and they also include a source entity. Basically, I have this Effect class everywhere in the gameplay code, which previously was just a generic Effect.Execute( Entity target). While super-general, it’s also super-problematic when I want to have more info. Long story short, I realised that changing that to Effect.Execute( Entity target, Entity source, float effectLevel) is absolutely great and useful for anywhere effects are used in the codebase. So, I can have a damage effect that scales with level, e.g. 5-30 damage at level 3, and add 1-6 to the min/max for each level thereafter. Example: instead of having a lot of healing potion variations, I could even get away with just a single healing potion with level scaling. There can be still weak/strong potion variations by changing the constant/scaling values (if a weak potion cures 1d8+2d4level, a strong potion could cure 2d8+4d4level). Level scaling is optional, so many effects are of course not scaled like that. By knowing who the source is, it’s easy to track the source entity to award XP on death, among other uses. This can/should chain, for example if a player creates a trap that applies a burning effect to a creature, that dies after 3 turns of being burnt, the user should get the XP.
Status effects got a big update, to support more complex things. I’ve added burning and freezing to test a few things:
Can now attach visual effects to the entity with that status, e.g. a freezing entity will appear blueish, whereas a burning entity will be surrounding in flames.
Can now attach effects, permanent or temporary, to status effects. For example burning has a permanent effect where, for a few turns, we repeatedly apply a permanent hit points modification (fire damage), so basically damage over time. Freezing has a temporary effect where it reduces the total speed by 50% for its duration. Status effects can optionally stack, so if I decide later to have a status effect, such as poison, that can stack, then getting poisoned twice might be twice as bad. Status effects can be associated to verbal, somatic or visual components, where as active abilities might require any combination of verbal (e.g. spell/warcry), somatic (anything that requires body movement) or visual (anything that requires a clear view of a target) component.
I’m not going to go the route of status effects cancelling each other and changing to other things (e.g. burning + frozen cancelling each other out, etc). If necessary, it can be explained anyway as your feet are burning while your arms are encased in ice 🙂
Secret things are uncovered only when they are in view, and only uncover them by the player.
I also fixed a bug with passing StructuredBuffers to Unity shaders. Unity did not want to send a compute buffer to gpu correctly when in the shader I use StructuredBuffer and in Unity I specify the buffer with a size of 4,6,8 uints and a few more. Yes, totally reasonable, but it should warn me maybe that shader and ComputeBuffer do not match?? Ugh, spent 2-3 hours on that at least.
Several “random” bits of work again this week, as my TODO list is a hydra. Solve one entry, and poof more appear. Not as bugs, but more like a dependency tree where you try to resolve a node and the fog of war dissipates and reveals more things that are not in place 😀
Framework for rendering “entity effects”, graphical effects attached to (and dependent on) a particular entity. Two examples are shown in the video above: a boss aura like Dungeonmans, and an early version of a burning effect.
Some debugging GUI for increasing attributes/skills
Added potions and tomes that increase attributes and skills
Allow per-creature blood spatters (on injury) and blood pools (on death). So that skeletons, ghosts and oozes don’t bleed.
Refactoring my configuration database to not have dependencies at all, as it got cyclic. So, I have for example the configuration for a creature race that depends on the configuration for an effect. The moment I started adding more effects, one of the effect types might depend on a creature configuration, so the dependency chain got cyclic, so I had to get rid of that. Now I’m doing a two-pass approach: first I load all the configurations in their most basic form and register their name (“allocate” them so to speak). Then in the second pass, I read everything normally, and order does not matter as I’ve allocated all the configurations by name in advance.
Machinery/planning for how a creature gets to learn active abilities (spells, active skills, special attack moves, etc):
Monsters have a mapping of ability-to-level-requirement. E.g. a monster wizard of level 3 would know firebolt, same creature level 7 would know fireball.
Equipping particular weapon categories might allow for special moves. E.g. equipping polearms would give a skill (that costs energy) which attacks from 2 tiles away
Progression in mastery levels in particular skills can unlock some active abilities.
Consumable items (e.g. book)
(tbd) Special dungeon objects
(tbd) Shops/trainers/people in cities
Try to be a bit more gamey and less simulationist with items and effects. Equipment will not have on-use effects, whereas potions should generally have different effects than, say, scrolls or items that can be used (wands? etc). Potions and scrolls should be different, beyond “this can get wet” for example
Mainly refactoring work this week, with the purpose of unifying the handling of abilities by the player or the AI. Concrete outcomes of this refactoring process:
New WIP ability, usable by both player and AI: fireball.
We need to select a target position
It has an area of effect
It’s effect is “fire damage”
It spawns a particle system, initialised with the positions of affected tiles
Fireball itself is a projectile, so if it hits an obstacle, it will stop (and explode)
We can setup the skill to be able to shoot multiple ones, homing ones, or both
AI can use the fireball skill too, by evaluating the “score” of each possible tile, determined by the possibility to hit ourselves (-5 score), allies (-2 score each) and enemies (1 score each). A score greater than 1 means “good enough”
AI applies a recency-based weight when choosing a skill to execute. So, a no-cooldown skill that was just executed will have 75% weighting which goes back to 100% after several standard actions. This is to avoid repetition of maximally scoring abilities, and make the AI hopefully feel less robotic.
New WIP ability: magic missile.
Like fireball, but does damage to a single tile
Number of projectiles can be greater than one, and for each projectile we can select a different target. Homing missiles still need a clear path to a destination
Reworked item transfer interface a bit, so that when transferring items between player/container or player/ground, we can select a number of items from container, a number of items from the player, and then click a button to make the transaction. So that with a standard action we can move e.g. 2 items to the container and pick 3 items up.
Cleared up input/keys a bit (still far, far from final though). Now we have the following categories of actions:
Ability-based: Fireball, melee attack, ranged attack, use item, etc. These typically need to be configured with some gui (which item? which direction? etc). Most of these would not appear in an input configuration file, as we have just too many. Instead, they’ll be mapped to…
Hotkeys: so that I can map any ability-based actions to hotkeys. Whereas the essential actions are limited (move in a direction, primary action, etc), most abilities have to be mapped via hotkeys.
Actions that do not require a gui, e.g. quicksave, quickload, teleport, look.
Press-and-hold actions, e.g. highlighting of creatures or objects, minimap view
AI is a huge topic, and it can range from if-then-else to incredibly complicated systems, meshing different techniques together to create the ultimate autonomous agents. There are plenty of choices out there, some more suitable than others. Suitability really depends on the game genre, the agents that we want simulate, how smart should they be, how capable should they be, how much emergent gameplay can they facilitate, etc.
In Age of Transcendence, as in most RPGs/roguelikes, most enemies are not around for long. So, incredible development effort on AI will most likely be wasted. You can’t see how smart the angry hungry wolf is, if it’s there for 5 turns! So, Keep It Simple for standard enemies.
In The Future, when I add NPC adventurers into the game, because they need to survive for far longer, they’ll need better AI than run-of-the-mill monsters.
Different monsters need to behave differently, to have some variation. A wolf attacks with its pack, an archer will try to shoot you from afar, a zombie will come to smash your face from up close. Dnd 4th edition has this lovely breakdown of monster roles (here’s a nice write-up).
This is by all means not a complete list, just a few I know and I’ve contemplated using one time or another:
If-then-else. The basics! We need to start from here, AI 101, the zombie: If character in sight, then if in melee distance, attack, else move towards melee distance.
(H)FSM. Tempting approach due to its simplicity, but when things get complex, it becomes a nightmare to edit/maintain.
GOAP. Well-used approach, typically for real-time games. As long as we abstract our game logic into the things GOAP needs (world state, available actions, action cost, action preconditions and effects), then we can request action plans (a list of sequential actions) that can be used to achieve a goal (e.g. kill all visible threats)
Behavior trees. Another well-used approach, they became very popular, but their popularity has been in steady decline. There’s a recent article that advocate them being used more for agent actuation (executing a plan) rather than decision making
Utility AI. This is another relatively recent method, that assigns utility value to available actions, and selects the appropriate course of action based on what has the greatest utility. It’s a nice system, but it’s number-sensitive (== a lot of time spent in designing/testing curves)
After testing in toy scenarios all of these techniques, I’m most inclined to start with the venerable if-then-else. In fact, because it’s so simple, I’ve already implemented 2 basic enemy behaviours, the “basic melee” and the “basic ranged”.
The basic melee enemy wanders until they see the player. When they see the player, they plan a path towards them, and when they get in melee distance, they attack.
The basic ranged enemy wanders until they see the player. When they see the player, if the range is appropriate, they perform a ranged attack. If the player is too close, they flee using an appropriate flee map. The flee map is the well-described variation of Dijkstra map, inverted, scaled and rescanned.
Because it’s such a common scenario, I’ve added the ability for creatures to be able to open doors if their intelligence is high enough, so that they can flee/chase more effectively.
Where to go from here? The first few steps:
Instead of using the default attacks, try and use any available abilities
Occasionally use any consumables
Start forming AI archetypes like dnd4’s monster roles.
The enemy FoV and planned path can be visualized with a hotkey
When enemies swap positions (they can), a cooldown needs to pass before they can swap again, otherwise they can be perpetually swap while trying to get to the player
Dead creatures dump their inventory
Enchantments are now color-coded based on if they are beneficial or not (green vs red)
A little zoo of topics here, and what they have in common is me avoiding gameplay programming (which comes next), and that they’re again nice-to-haves rather than essentials. But they were all fun to do, so that’s that. Here’s a video that shows the underwater effect and the decals:
Recently I refactored sprite rendering to use variants with multiple configuration variables, one of them being the type of the pass being regular, shadow or occluded. The occluded pass is like the regular pass, but we draw in areas that fail the Z test, and make this pass semi-transparent. So far so good, and what does this have to do with the underwater effect?
Well, previously my sprites have been holier-than-thou, as they all seem to be walking on top of any body of water, like the sea or some lakes/rivers in the level maps. Clearly that does not look very nice, and does not give the impression that we’re inside that liquid. So, after a few experiments, I thought I’d reuse (the programmer’s wet dream) the occluded pass to do that. How? A bit of background info first: In my rendering pipeline, sprites do use depth/Z values, so I can have order in rendering, characters appearing behind trees, etc.
First we need to detect when we’re on tiles with liquid. On those tiles, push the Z value further back: more for deep liquid, less for shallow liquid. Push the sprites so that part of the sprites is underground. Important: the position on screen is identical, I’m just pushing the Z value so that part of the sprite will fail the Z test
Now sprites are successfully hiding in the liquid, and so far it’s already looking good! Some short creatures might be completely underwater though, so you won’t be able to see them. So we can enhance the visualization, and here is where we consider the occlusion pass.
Since there’s going to be a part of the sprite that fails the Z test, this will pass the occluded pass test (which renders what fails Z test), so it really “just works” as long as no settings are changed (I had to hack things to show how it would look without the occluded pass running). The sprite portion that’s underwater is naturally rendered with transparency.
And now for the fun of it, because we know that things get distorted underwater and that water is never perfectly still, especially as adventurers and monsters plow through it, we can add a little bit of distortion in the occluded area. So in the pixel shader, if the occluded area represents area-in-liquid (we already have used this info in the vertex shader to push the Z back), then we just apply some periodic distortion in the sampling of the horizontal texture coordinate. And here’s the result!
Finally, just to spice things up slightly more, items dropped underwater are slightly visible, and the level of visibility depends on the depth. So, in shallow liquid are generally faint, but in deeper water they are even more faint. Maybe I should make them completely invisible in deep water, but let’s see, maybe later.
One common effect in games is decals: images that are used as “stickers” in the game world. Examples could be footprints, blood spatter, scorch marks, etc. While these have certain challenges in order to implement in 3D, it’s far easier to do in 2D, as it’s just another sprite splatted in the world. And typically, decals can fade out after a while. So, since I’ve got the relevant rendering machinery already, I wanted to add support for decals, for any purpose that I see fit later on.
What should have been a walk in the part, turned out to be a pain thanks to Unity’s bad rendering debugging facilities. So, given the way I’ve structured the code, adding decals should have been a walk in the park. It’s another sprite pass, and I implement it as a persistent particle system, where decals have a lifetime of 5-10 seconds, after which they fade out. So, I added a few blood spatter sprites, wrote a basic shader and hooked the systems up, and lo and behold, nothing to be seen. Long story short and a few hours later, the problem was that the Compute buffer (that I’m using to send instancing data) was set up on C# side to be 3 uints per element, and in the shader I had a StructuredBuffer<uint> that I was addressing by buffer[i*3+0], buffer[i*3+1] and buffer[i*3+2]. I was expecting that the memory would be aliased but that was not the case. And no errors of course did not help (“Hey, you’re binding a uint3 buffer to an incompatible uint shader buffer!”). Anyway, long story short, that was it, and now we have decals. I hooked them up with damage, so that when a creature gets damaged it spawns a small blood spatter, and when it gets killed it spawns a big one. Yay for proof of concept, more to come when needed.
LZ4 compression for save files
Save files can get large, due to the large number of 2D arrays for various purposes: lots of layers per map, world map data, some other heavyweight caches, entities, etc. The finished game should have hundreds of cities and dungeons. I’m not going to go too deep into this rabbit hole for now, as it’s eventually loading needs to become asynchronous, but to begin with, I wanted to reduce the file size.
The starting test case is 7 multi-level dungeons , so about 15-25 levels altogether, plus world map. Size on disk is 21MB, it takes about 3.2 seconds to save and 3.5 seconds to load (in Play in Editor, not final build). So I got a simple LZ4 implementation for C++ and put it in the C++ plugin. The plugin now has 3 more functions:
Save an array of bytes to a file using LZ4
Get the number of uncompressed bytes needed by an LZ4 file on disk
Decompress an LZ4 file to a preallocated array of bytes
The LZ4 bytes store as a first value an integer with how many bytes will we need. The reason I did that was because the plugin functions work with preallocated memory. So C# can query the correct size, allocate the byte array and send the array to be populated in C#. Still it’s far from optimal due to some possibly unnecessary copies, but hey, it works.
The timings for the LZ4 version are pretty much the same, but the file size is now 5MB instead of 21MB. Yay!
This is just a quick demo of a new visual feature: overlapping tiles for ground layer transitions. The problem before was that the transitions between the floor tiles were sharp and square. A square tile would either have e.g. a grass or dungeon floor tile. This was problematic when we have a non-full-square blocker tile occupying a border tile, such as these grass wall tiles, and the problem effect is shown here. What happens is that it looks like the grass border is gray, when it actually isn’t. To counter the problem, a new pass is needed, that can ensure smooth transitions.
For this new pass, we need autotiles of the type “rug” (so, just 16 of them)
I’ve developed a small Python tool that allows me to easily do that from an input set of files, so I’m just loading up some tiles from an Oryx tileset and place them appropriately:
The layout can be then exported and embedded in a texture atlas, to be used by the game. So, in game, before we apply the layer, the background tiles look like this:
The new autotile layer is rendered after the background and before any other passes. My map tiles have zone IDs, so grass would be the outer zone (id == 1) and dungeon floor would be an inner zone (id == 2). If we are in an inner zone and we’re on the boundary with an outer zone that has overlap tileset, we set the appropriate bits in a bitmask, that we’re going to use to read the appropriate autotile. This results in the bottom image:
Now we can add the rest of the passes, resulting in a nicely smooth transition:
And for reference, here’s the same view without the new overlap pass: