In my previous post, I demonstrated how a simple 'spike' enemy might be programmed, and provided the Lua scripts that control its behavior. Well, sometime in the middle of coding those behaviors, I realized how difficult creating robust code for more complex behaviors is going to be. So, I took a step back and asked myself 'why are these behaviors in Lua?'
I originally chose Lua as a scripting language for several reasons. It's lightweight, popular (not a reason in itself, but it generally means more community support), relatively fast, and easy to use. But writing extensive game logic in scripts beyond basic sequential commands has a number of disadvantages:
- Debugging is difficult in scripting languages
- Development is a more tedious process, since you don't get intellisense or any other productivity-boosting features of a good IDE
- Invoking scripted methods is always going to be significantly slower than compiled code
- Weak built-in libraries and language features compared to C#/.NET. Also, it's much easier to write run-time safe code in a compiled language.
- The engine is already in C#, so having no cross-language boundaries is an advantage.
One reason I wanted a scripting language in the first place is so the game could be ported by having the majority of content in Lua scripts with a slimmer engine that could easily be converted to C++ if needed. However, I can use Mono in the future instead if I really want to.
So, with this in mind, I've decided to go C# all the way. Because all game logic is in a separate assembly with no dependency on the engine, it can be considered a 'plugin'. It is bound at run-time rather than compile time. The below picture demonstrates how my assembly structure is set up:
So, things like 'player' and 'spike' (currently the only two complex behaviors I have) live in the Implementation assembly. The game engine (and thus the editor) dynamically load this assembly at run-time and use reflection to instantiate 'Behavior' objects (or, in the editor's case, let the user assign behaviors to an entity. I haven't actually done that part yet.)
So, yeah, the result of all this stuff is that I'm not using Lua anymore. I may or may not use it in the future for simple behaviors (i.e. this switch opens that door, etc), but all complex behaviors will be in C#. And with the way I've structured my project, the engine can be used by anyone who wants to build their own game--they simple need to implement behaviors in their own assembly and modify a text file which tells the engine what assembly to dynamically load.
So, this conversion took the majority of my time this week, but I still had a little time to have some fun.
Here's some things I did for fun after converting everything to C#:
- Enhance the trigger infrastructure to take arbitrary trigger arguments. For example, the 'DamageEnemy' trigger might take 'damage amount' and 'damage type' as arguments, and also a 'damage direction' that an enemy that receives 'knockback' might use.
- Made it so the spike enemy can 'damage' the player. There's no damage meter yet (maybe next update?), but there's a cool knockback-and-temporary-invincibility effect very similar to how 'A Link to the Past' handles damage. See video below for a demo!
- The player can slash at the spike enemies to similarly knock them back. Also shown in the video.
Okay, here's a video of all that stuff in action:
One thing worth mentioning is how the the game handles the enemy collision with the player's sword. Here's the source code for that part:
Here is a short video showing how it works. Note that for the video I've dramatically slowed down time, zoomed in, and turned the opacity to 0.7 for the 'sword' entity instead of 0. The rotating white block is actually what's doing all of the work here!
And, if you're interested, a brief translation of the code to English:
- Translate the player's 'facing' variable (left, right, up, down) into an angle.
- Create a 'sword node' at the player's position. This is because I don't have support for rotation 'around' an arbitrary point, so to simulate that I use entity parenting--create a node, attach the thing you want to rotate to it as a child (and position it relative to the node), and then rotate the node.
- The line 'swordNode.Control(...)' sets up rotation from sourceRotation to targetRotation. The arc angle (it's declared outside of the shown code) is about 33 degrees (pi * 3 / 16).
- Create an actual 'sword' and append it to the node. Note that the sword is an abstract thing and not visible--the graphic for the player has the sword 'built-in'.
- Position it 30 units ahead of the player and give it a certain size.
- Attach a collision event to the invisible 'sword' object. The callback (an anonymous method here) simply invokes a trigger on the target entity (it uses the player's position to calculate the knockback direction). The collision event's current target is 'spike', but in the future we'd make it 'enemy' or even 'takesSwordDamage', and make all things that can be hurt with a sword handle that trigger.
- I attach an 'update' function to the sword which generates some particles as it swings.
- I set a texture for debugging purposes, but set the opacity to 0, so you can't see it.
- Finally, I run the animation for the player and when it's over set its state back to 'standard' (the code above actually runs in the initializer for a 'swording' state).
Okay, that's all for this update. I hope it was interesting. Next I'll be adding a health meter to the player and to enemies, or something of that nature.