Spent a very small amount of time determining the actual 'leaf' nodes in the room graph. These will be useful for determining start and end positions on a map, as well as placing more difficult rooms with better loot because they're off the beaten path.
To implement, I actually had to make a whole new data structure. Before, I had a collection of Rooms and a separate collection of Edges. Now, I additionally have a collection of RoomNodes that have references to their neighbors (kind of replaces edges). A leaf node is any RoomNode that has exactly 1 neighbor.
I am not doing anything with this knowledge yet, but here I added some square gizmos that identify leaf nodes in the scene view:
Not a very productive day.
I managed to clean up some stuff I broke while making the A* hallways. To make those work, I had to change the definition of what was Passable in the grid from Floors, to empty space. Next, i changed the definition of Neighbors to only be the 4 cardinal directions since I don't really want diagonal corridors (they look weird).
All of this epically broke the Player's ability to actually move, so I did some refactoring to make those parts configurable, and now everything is working.
Better than nothing!
I added the ability to connect rooms that aren't vertically or horizontally aligned.
My initial thought was to make crude 'L' shaped hallways, but I realized that they would almost always intersect some other room/hallway, and I didn't want that.
My second thought was to use pathfinding to actually generate the hallway, and I got it about 99% working. Unfortunately, I'm not sold on how the results actually look.
Here are some pics (note: new A* hallways are in brown for illustrative purposes)
This third one exhibits the issue that keeps this only a 99% solution. The 2 rooms with brown doorways, but no connected path, are supposed to be connected, but no path can be found because of the 'courtyard' formed south of the left room.
I'm already picking where to start the hallways by choosing a spot on the wall of each room that has an open-air tile next to it, but that isn't enough.
I think my 2 easiest options are:
Finally, and I almost forgot, I don't really like the aesthetic of the winding hallways anyway. They'd fit great in a dungeon setting, but not so much in a space station. Might have to go back to the drawing board...
Added some doors between rooms. Okay, not actual doors that can be opened and closed, but I carved open hallways between rooms.
I haven't covered the case that 2 rooms are neither vertically or horizontally aligned. So for now, they are inaccessible, so that's next.
Been stumped by my overly-complicated combat system lately, so I decided to take a break and start some proc-gen.
This is just a really simple algorithm that randomly places some rectangles with rigidbodies, lets the physics engine separate them, and then rasterizes the result to the tile grid. It's a start!
Was on a ski trip with my in-laws from Thursday till tonight. However, I managed to get some gamedev in on the plane ride back.
Nothing worth making a GIF of, but I made it so normal attacks will NOT trigger the defender's damage reaction if they miss. This was working for the projectile a couple updates ago, but I made it more generic for any attack,.
I also added logic so the grappling hook will trigger the OnArrival behavior of the defender when it performs the final 'kick out'. This means, if you grapple an enemy right on top of a trap, it will actually take damage.
Not a lot of motivation today. Watched the Spartans hang on to beat a shitty Iowa team by 3. Not very inspiring.
However, I managed to add a little feature. Previously, it was possible to grappling-hook anything; including traps, which I want to limit. Now, there is a flag in the base Entity class, 'moveable', which is considered when trying to grapple. Of course, the Trap wasn't actually inheriting from Entity, I had to change that.
No GIF because I can't show something not working. Also, I'm lazy. Goodnight
Updated the grappling hook to calculate the nearest non-blocked tile and 'kick' (that's what I picture in my head at least. No actual animation) the defender to that position.
It's lacking the "OnArrival" behavior when the defender reaches its final destination. This would trigger the traps attack, for example.
Started some very basic work on the grappling hook today:
The obvious flaw right now is that it pulls the enemy onto the attackers tile. This technically is allowed in my combat system, but I don't want to encourage it. In fact, I may make it impossible for 2 entities to occupy the same tile, with the exception of traps.
To fix this part of the hook, I'll have to deduce the proper nearest point to the attacker to make the destination point for the victim. That shouldn't be too hard, but I'm honestly not sure what it should look like. If the victim isn't going to get pulled to center of the attacker, where should the grappling hook start? Should it have 3 points (attacker's position, destination tile, victim's position)? Or should I just fudge it somehow?
I was absolutely not feeling like doing gamdev after work and class tonight. I actually played video games (Rocket League) for 2 hours instead of doing gamedev, which is rare for me recently.
By 10pm I finally got the motivation to do some gamedev. I wanted to add a 'grappling hook' ability that will pull an entity right in front of the attacker. This will be a utility move for melee classes for closing the distance to ranged enemies. However, when I sat down to implement it, I realized it should be loosely based on my 'push' ability which I haven't kept up to day since the AbilityVisualization and ability UI system reworks. I spent about 30m updating it to a barely functional level (didn't actually implement a visualization, but the ability still 'waits' for all the enemies to move before completing). And then I lost motivation, and am going to bed. 30m is better than 0m!
Added something I've been meaning to get to: missing! Now attacks actually have the concept of not always hitting! For now, its a straight 50/50 RNG roll, but eventually it will be configurable per the ability, and might be modified by things like Entity accuracy, blind state, or target's evasiveness.
Implementing it required giving all AbilityVisualizations the concept of 'missing' as well. The only one I've gotten around to implementing is the ProjectileVisualization. Now, it actually goes past the intended target if it misses! It doesn't do anything fancy like detecting walls, so it looks kinda silly, but that can be refined later.
Once again, my group project has been sucking my mojo away from gamedev, but I managed to be decently productive for ~2hrs of time.
The Player and the Blob were duplicating a lot of code for things like: monitoring when an ability finished, updating passive traits every turn, updating their combat component every turn. I refactored that into a base class, and now they're only different in logical ways.
Generalized the StunTrait that gets applied by certain attacks to be any PassiveTrait via another ScriptableObject hierarchy. Technically, I could make an attack that gives the enemy the passive ActionRegen trait upon hitting, neat!
Here is a GIF of the Grenade ability imparting a 3 turn 'blind' state. Blind will eventually have some effect on combat (lower accuracy?) but for now it doesn't actually do anything:
Tonight, I added quite a bit of infrastructure to support "states". These will be various conditions that can affect an entity such as stun, rooted, and blind. This is implemented by a bitmask. Next, I added a PassiveTrait for stun that makes it easy for Abilities to apply a stun that lasts 3 turns and then removes itself. Whlie stunned, an entity basically forfeits it's turn. Finally, I added some UI to the EntityCanvas to visualize the current states.
My night class has been cramping my gamedev style, but I managed to add a little feature tonight: kill streaks.
Pretty straight forward: kill any entity on your turn, increment the kill streak. Don't kill anything, streak resets. The neat thing, if you can call it that, is that the kill streak is a ResourcePool, just like HP or Action Points. This means it can become a dependency of certain attacks ('have a kill streak of 2+') and I can optionally make some abilities 'consume' the kill streak. Not that I've actually used those ideas anywhere yet.
I might end up not using the yellow UI meter to visualize the kill streak; it's probably overkill. I think putting a simple # on the abilities that require a certain KillStreak might work. But it's good for debugging at least.
Pretty productive day all around:
The action-cost mechanic was a little over zealous. It consumed the action cost of the ability as soon as you selected it, even if you aborted it and chose a different ability later. Now, the ActionPool is passed to the ability, and the ability can decide when to consume the action points. For targetable abilities, this won't happen until you choose a valid target.
Converted HP to the same class as the action point pool. Also added a red UI representation.
Added the concept of PassiveTraits. These are things that, unlike Abilities, don't consume a turn. Though, they may 'tick' every turn like Abilities. I've derived one implementation so far, ActionRegenTrait. It simply gives the player 1 action point back at the beginning of every turn.
I added a concept of "Action Points" which will be the main resource the player has to manage. It will be a cost associated with every ability, and will (hopefully) make the game tactical and fun.
Today I got the very basics of an Action Cost tied to every ability, an Action Pool managed by the CombatEntity component, and some UI.
I really need to find a 'best practice' for tying things like this into Unity UI. My current way involves a lot of delegates, and it's not so bad. But I still find the 'plumbing' to get all the references to each other very tedious. There has to be a better way..
Anyway: onto the gif!
Well I solved the 'cant kill blobs that are on top of traps' bug. It was a logical error on my part, and I'm pretty 'okay' with it as far as bugs go. Then I noticed a very similar bug where enemies that moved onto a trap on their turn wouldn't die ... sometimes .... Sometimes they would. That turned out to be a race condition with syncing up 2 parts of the ability visualization: the animation (which fires an animation event) and the delay I configure into the ability. Basically, if the length of the animation was equal to the delay, it would almost always work. Except sometimes it just wouldn't. I made the delay .05s longer than the animation takes, and it seems to be 100% reliable (in an admittedly small test).
But the bugs didn't stop there. I found a bug where, if you manage to kill yourself on your turn (walk into a trap with 1HP) you don't technically complete your turn, so the game hangs. This is less consequential for the player (game would be over anyway), but it is also true for suicidal blobs. I added some more logic to the TurnManager to detect this case, and it seems to be fixed. But I'm getting kinda leery of all the bugs I've seen in such a short amount of time. I think I might have to refactor exactly how generic my Ability system is, in the name of reducing bugs.
Nothing major done today but I did manage to squash a bug. Under some circumstances, some entities appeared to be getting an extra turn per global game turn. Sometimes it was the player, other times it was a blob or trap. Turns out, it was because of the way I was managing and deleting entities when one died. Without getting into details, I thought I was being clever, but I was not. I think I have it fixed, though it's not an elegant fix at all.
I was all proud of fixing that bug, only to notice a totally separate (I hope) bug. When a blob is on top of a trap and the player shoots him in a way that would kill him, he lives on! His UI even says 0hp which, you never see on normal blobs. If you shoot him again (and he's not standing on a trap) he dies correctly. I'm not really sure what is going on, and I'm still getting over my cold, so I'm gonna call it a night.
Call it a draw: Nick : 1, bugs; 1
Something I've been totally neglecting is how the game will convey vital stats like HP to the player for every entity on screen.
Here is a very quick and dirty per-entity canvas that shows the HP, as well as a fly-out text when they're damaged:
Every good dungeon crawler needs traps. So I made a trap!
Overall, it was pretty easy, and I am happy that I was able to basically stay true to my entity/ability architecture. The trap is a normal TurnTaker entity with 1 ability and an associated visualization. It has a little 'brain' that make sit do the ability every 3rd turn, and change it's sprite to the appropriate one for the charge-up turns.
The only oddity was adding the ability to have a player/blob move onto the trap (if the spikes were up), and still do damage. The issue is that on Entity A's turn (player), he has to do all his own turn stuff (move), and then finally invoke Entity B (trap)'s ability, and all that goes along with that. For now, it's pretty hacked in, but I think I have some ideas for making it more generic later.
Started to add the logic I mentioned in my last post to the AttackAbility class to give targetable AOE abilities some better(?) UX. I ended up splitting it up into two classes since the logic was diverting in a bunch of places within AttackAbility.
I also did some refactoring to the way the TileMaskVisualizer works so that it can display multiple different colors/patterns at once. This lets me show the effective area of AOE abilities separately from the main mask. Observe:
As I hinted at before it still takes 3 clicks: 1 to choose the ability, 1 to select a potential targeted area, and another to confirm. The jury is still out on whether that's reasonable for the player.
Not a super inspired night, but I managed to add some new functionality to attack abilities. Now, in addition to being instant AOE, they can also be a 'targeted AOE'. This means the user needs to select a tile first from within the allowable tile mask, and then a second mask defines the area that is actually effected by the attack (rather than just the targeted tile). This represents things like grenades.
There is no TileMaskVisualization for the grenades actual effected area yet. Initially, I envisioned a secondary tile mask marker that updated in real time as the user moves the cursor around, showing the 3x3 area in a different color. However, this won't work on mobile since there is no concept of hovering. It will probably require clicking once to show the effected area mask, and then again (in the same tile) to confirm the attack. That means that it will take 3 clicks in total: 1 to select the grenade ability icon, 1 to show the effected area from a possible target tile, and 1 to confirm. That seems a little clunky, but I can't think of a better way...
So my computer crashed and I lost some of the progress I made last night + tonight (saved the Unity scene, but not the project, so some assets weren't created). The weird thing is that, since getting my RMA'd components back, it'd been mining Ethereum 24/7 for about 2 days with 100% uptime, but only crashed when I actually started doing gamdev (still mining in the background though). Strange.
Anyway, after redoing all that work, I set to implementing the 'instant' ability I mentioned in the last post. I also took the opportunity to add a heal ability that uses the same concept. Here is a gif:
As I hinted at in a recent post, the UX for selecting and using abilities is a bit clunky. Today, I took a small step towards making it more like how I envision the game will work. Here is a GIF:
Nothing about how abilities work was changed, so there is still the aforementioned issue where you have to click an extra time to activate the AOE attack. But now that the abilities' icons are separated, I can work towards fixing that.
There is a decent amount of plumbing going on behind the scenes too. For instance, the UI buttons and their container are totally generic and just listen for events from the UI manager to populate the right sprites. The designer (me) just needs to drag ability ScriptableObjects into a list in the GameManager called "StartingAbilities" and both the UI and Player class are alerted properly.