SamSuka
deadwinter
deadwinter

patreon


Coder's Corner: The Symbiosis of Engine & Code

[Hello!  I've been packing and moving my home all weekend so this week's update is from Jason, providing you an update on the coding side of our game project!]

It's been a long time since I've written anything related to the game, mostly because I've been swamped with all these other tasks at hand.  There's not much I can do about that except to work through them until opportunities open up to work on the game again, especially when we are extremely close to having a demo ready to go for Patreon.  There are some minor states that need to be worked through, some tweaks to special moves, and then we've got something.  It's just tough to get these last few steps done with all these tasks piling up.

So, since I often have found myself unable to work in the code for long periods of time, what's become crucial to keep things moving is having a variety of ways to enable Allie to be able to do work unimpeded by things I do.  Expecting anyone to have to wait on me to take care of items with the code would put us in a bad spot, but fortunately there are plenty of ways that I can put my code in a good position for Allie to be able to take the foundation in place and run with it.  When it comes to making sure the game hits that sweet spot for gameplay, I'm not always the best judge of this, but it takes a painfully long time to compile a game, send it to someone, get feedback on a single value (such as, say, melee damage), change it, send a new copy, and repeat the cycle.  

Fortunately, Godot puts us in the best position possible, where Allie never really has to touch the code at all.  Some of this is the engine which anyone can manage to utilize in the engine pretty quickly, but other items have required extra thought to put us in a good position going forward.

Firstly, what can the engine do for us?  I don't want to have Allie bother with looking at my code, even if I organize it well.  It's not always obvious where things are in the code, but anyone on a project should be at least slightly familiar with the engine's inspector.  Every node in the game can be inspected, since to change values (such as size or visibility), there has to be somewhere that these numbers are stored. Most of these items are standard to whatever the node is; we can expect that an animated sprite always has a value for the frame number it's on.  In addition to all of these, for nodes that have scripts (i.e. code) attached, I can extend the variables in any way I want.  Probably the biggest example is character move speed.  Instead of setting values directly in the code for it, I can assign the 'export' parameter to an item I want to be accessible and changeable to anyone who works on the project.

So, when I create "export var MOVE_SPEED", I am automatically creating a new entry in the inspector for whatever node I am currently debugging.  From there, all anyone else has to do is click on the node that would use this variable--in our case, character information--and now that variable can be edited by anyone without needing to seek out a variety of things in the engine.

That likely sounds all straightforward, but Godot provides something else that gives us a lot of of debugging freedom, being able to change values in real-time and see their results show up live in the game.  One of the biggest difficulties has been to do things like set up hitbox sizes for collision detection.  Our initial thought was to use the visible collision shapes to determine collision box sizes, but it turns out these are not always representative of the actual shape when also modifying the boxes in code.  So, being able to change values for collision box sizes and then see their real-time response has been helpful in speeding up the necessary tweaking to our code to make combat feel properly responsive.

Creating hitboxes and values that spawn at certain times in an animation originally was a code-heavy procedure that had to happen on the programming side, but with some adjustments to our approach for how to handle hitbox during an attack, we came up with a pretty good setup for  generating hitboxes at specific animation timestamps.  Being able to call code from the animation tracks themselves is a huge deal, one of the big benefits of using an engine like Godot!  At the specific point in the animation track, a code's function can be executed, and Allie can pass in all the parameters we need to set up a proper hitbox for an attack:

*The center of the hitbox, relative to the animation that it is tied to

*The size of the hitbox, which grows outward from the center point

*The lifespan of the hitbox, so it is only active for a part of the animation before it clears itself.

Originally, characters had hitboxes that were static, but constantly being resized or turned on and off, and that created all sorts of issues with trying to manage that outside of code for special cases.  In this new hitbox-spawning method, Allie could create any number of hitboxes on a certain frame to represent complex shapes, even, so with a bit of initial setup, we now have a very useful way to describe fairly complex shapes without requiring a lot of effort--it's just a bunch of rectangles created at the same time.  And since this is all accessible through the animation player, it becomes easy to handle without another line of code.  In fact, there is so much that could be added to an animation player's timeline, it almost seems feasible that the vast majority of a game could be developed just by animations and function calls with just the right amount of preparation.

The last portion of what I've developed to ease the burden of code comes down to the artificial intelligence and how characters receive and respond to inputs.  In our initial game build, AI was set up to call certain animations and execute behavior directly between the AI and the character in question.  While this in theory works just fine, it means for each new character to behave in any fashion at all, some new code must be created to handle a character's unique behaviors, there wasn't really a plug-and-play option.  When we redid the code for a 3D space, we also redid how inputs work.  Everything in the game has a virtual 'button' so to speak.  The interface between the characters, for example, have basic directional controls, and values like 'attack', 'action', 'jump', and 'switch' defined.  This interface also handles an input buffer and pattern detection if we needed it (for example, more complex inputs like one expects from a fighting game), but it also allows for hot-swapping control schemes.  If I am using a controller or a keyboard, those feed into the control interface, which interprets the raw inputs into the normalized virtual input.  Essentially, the character will behave no differently between either of those input schemes.

But when AI is introduced, the idea remains the same.  I have a basic AI for our base zombie that seeks out a target and attacks them.  It doesn't directly control the character anymore, but the AI is hooked into that control interface, which then gets interpreted as virtual button presses again.  (This is a lot like how a TASbot might interface with a game for example, for those familiar with those streams, since the program is manipulating a controller to play a game rather than directly manipulating the game).  The biggest benefit there is that with how the characters are set up, every character has a generic set of global code that always just works if their state machine is set up correctly: idle, walking, attacking.  Every character shares the same global code, and only in some instances are there unique local states that only certain characters can access--such as each special attack, those are unique to each player character.

With how the global state code is set, this means that any new enemy or NPC that follows the guidelines for setting up animations and states can just specify their input type and then be debugged that way.  An enemy could not only have the generic AI tied to them, they could have a keyboard or controller tied to them instead.  This might not sound revolutionary as a lot of games offer that same sort of functionality, but it's a critical step in being able to make all of the code stay under-the-hood so development of more enemies can be done with minimal extra effort.

Anyway, that's sort of the state of how our code is today--because of my other obligations, I have to make sure I can maximize the engine wherever possible so we aren't constantly waiting on me to develop code for a new enemy to work.  It's not quite there yet, but after some of our final changes in the Patreon demo and then the public demo that should follow some time after, the hope is that we can just create a ton more content and not worry about what's going on behind the scenes at that point.  Thanks for reading!


Coder's Corner: The Symbiosis of Engine & Code

More Creators