Knowing when to let your prototypes die

Where Gods go to Die initially started off as a beginner's project for getting familiar with Unreal Engine 4. I ended up spending a lot of time reworking things over and over again. What started as a Blueprints-only project evolved into one that merged Blueprints with C++ until towards the end, I was almost exclusively writing code in C++.

When I started the project, I didn't have a very strong direction for the game besides learning the engine and getting familiar with how everything fit together. As such, I did what most beginners do and started off with one of the default template games and reverse-engineering how things worked at a basic level.

At some point I decided to add my own Blueprint functionality to the sidescroller templates and just fiddled around with the camera and the default Character class. I added a dash mechanic by linearly interpolating the desired dash distance and that functioned pretty well. I added trigger boxes that would toggle between a side-view camera and an angled top-view camera. Things were going great, and then I started thinking about art style. I wanted something that felt pixelated but still maintain an overall 3D look. The easiest step for me was to just rewrite all the stuff I'd written for a Paper2D character. Not a big deal, since I was early into the project.

I added all kinds of things, but the main problem was not having an overall idea of the game, or even the minimum viable product I wanted to create. The Paper2D sprite didn't deal with rotations very well so I added a function that would convert the mouse cursor location on the screen to the world space and return the desired dash direction vector. This still didn't look right so I added a short movement lock after dashing and then a rotation for the sprite chracter in the direction of the dash. I was still holding on to my strange side-to-top camera transitions, so I made the dash work for the side view but based on the direction the sprite was already facing in.

If you couldn't already tell, I was getting caught up in the details without having a clear direction. I was adding things that seemed cool at the time without any sense of cohesion and implementing things at the wrong levels of abstraction. Just to quickly gloss over, I added a mechanic that swung the player around an NPC character. Basic AI movement and patrols. A component for checking if the player sprite was being blocked by any objects and if so, turn them translucent. I animated a placeholder sprite just so that I could see animations happening when walking around.

By this time, 2 months had flown by and my "action RPG hack'n'slash" had a little action, no RPG, and absolutely zero hacking and slashing. I started trying to implement a basic melee combat system that would have some basic combos built in, similar to something like NieR:Automata's combat. (side note: I was very pleasantly surprised when I found out how deep Automata's combat can be with all kinds of combinations that you could weave together, just look at this video to see how expressive the combos are!

Okay well, creating a single attack doesn't seem too hard, just do a trace in the direction and check for enemy collisions, right? Great, now how do I get the attacks to flow from one attack to the next? I looked into an action buffer system that would save your last-pressed button so that it would fire right after the current action was done. This would mean that I could queue up an attack while a dash was happening and get an attack at the end of the dash if I executed the combo within some timeframe. I could never figure out how to get the timings to work nicely with each other because I didn't want to have to hardcode the timings for each action by adding delays here and there.

I was stuck at this wall for quite some time and then I started reading about UE's Gameplay Ability System (GAS). I didn't fully understand what it was or why I would need it, but it seemed like something interesting that I could use to implement a combo system that was fairly flexible. My only problem was: I didn't understand anything about how to get it into my project, there wasn't a lot of documentation on the system and it wasn't very beginner friendly. The main thing that I figured was that you needed to do some C++ in order to get it set up.

Seeing the opportunity to dive into C++, I decided to rewrite all the Blueprint stuff I'd been working on. I also changed my mind again about the art style and switched back to a 3D character. I rewrote almost everything for the player character and created a camera actor that wasn't attached to the player character with a spring arm component, instead attaching it at runtime. The camera actor would move itself every tick by looking at the player character's current location. Something I was pretty proud of was interpolating the distance between the camera and the character so that the camera would lag behind the player but always come back to center on the player character in the same amount of time each time, even with large movements like the dash. Effectively, this means that the camera would move at a greater speed the further it is from the player, and then slow down as it gets closer to the player character. You can see this smooth motion in the GIF at the top of the page, where the green line shows the path that the camera took.

This turned out to be a much more elegant solution compared to my previous solution, which was to clamp the camera within a specified area so that it would never be out of that area no matter how far away the player character moved. The camera would then move by some fraction of the distance to the player. This was alright, but it lead to very jittery camera motions, especially during the dash. It was also very bad at diagonal motion. In order to vary the speed based on the distance from the player character, I attempted to implement some logic that would look up the appropriate speed value using the current distance and then increase the distance the camera should move this frame based on that speed value. Very messy stuff. My new solution was many lines of code shorter, and performed so much better.

Back on the gameplay side of things, I had finally implemented the dash ability for the third time, this time a lot more optimised by using a delegate function that would get subscribed to the tick event for the duration of the dash, instead of having a boolean condition called every tick. The problem was, there wasn't any reason to optimise like this at this stage of development. There wasn't even any other abilities that had been implemented.

I tried to switch lanes and get progress in a different direction by trying out some postprocessing techniques. I wanted to see if it was possible to take the 3D model and render it in a pixelated form. After some research, I found a technique using the CustomDepth property that could be used to only render the player character this way, and keep everything else rendered normally. I thought this was cool, but I wasn't completely happy with it. The end result looked more like a blurry mess rather than a pixel art beauty, and it was extremely jittery when any motion was introduced. I found that it wasn't half bad when the pixelisation strength was set pretty low, so I left it where it was to be returned to at a future point in time.

At this point, I felt I was comfortable enough with C++ in UE4 to make an attempt at integrating GAS. After lots of source code digging and reading user-created documentation, I finally had everything set up but it just.. didn't do anything? I was getting caught up in the details again, and I felt that I needed to create all these classes and do all this set up, but I wasn't actually getting anything playable out.

My motivation was starting to wane, and I started looking around for game jams I could join so that I could have a more definite timeframe to work in, which is how I started working on Soul Food. I don't know if I'll ever revive Where Gods go to Die. While I learned a lot about UE4 and game development during this time, I think the project will remain at rest in some dusty corner of my hard disk, and that's okay. Let sleeping gods lie, as they say.

Back to top