...well, not really. It says what this post is about, but it certainly doesn't show everything! As always, there's a video for that! Make sure you watch in a large viewer and in HD, as it might be difficult to read the fonts on the screen (which serve as annotations) in a small viewer.
So, how does this work? Easy...for now. The current implementation--but almost certainly not final--is to use a single texture source for the entire font. It looks like this:
So, I wrote a quick utility to create a texture with one frame per letter (it could also be implemented as a bunch of textures, but it doesn't make any difference--the frame version is slightly easier from a coding standpoint). Once this was done, there were actually no new features required in the engine! Just a small helper function that creates the entities for you given a string. And that's it. All of the effects you see--including the final one where I slash the letters away with my sword--are just entity features that can be found in previous posts and videos.
This is a nice start, but there are a couple problems.
One, I would like to be able to use any font, not just this pre-built one. This would likely be accomplished by setting up a pool of available fonts at design-time, and then rendering the characters of that font to an texture at run-time (that is, when the game begins). So, there is never an actual graphics file for a font (that I own, at least--of course, the operating system has a file somewhere).
Two, the kerning is non-existent with the current method. Kerning information is available via the windows API, and it shouldn't be too difficult to use (just a slight modification of the helper method) to properly space the entities once I have it.
However, this will suffice for now, and I'll come back to these features at another time. The fonts may not look perfect, but they can do some cool things!
Note: The font in the upper left corner showing diagnostic information is rendered in a different (less flexible) way, so this is definitely a new feature.
I know I said I'd get into something like a health meter for enemies this time, but I got a bit sidetracked. I added lighting! And a boomerang, and jumping! A few screenshots, but be sure to watch the video at the bottom.
Lights are simply entities on a special layer that's marked as a 'light layer'. Like other entities, they can be manipulated in a variety of ways, use any texture and color, etc. This week's video will show off some of the features. Check it out!
Hopefully I can throw together a more technical blog post to explain the lighting if anyone is interested, but for now I'm out of time. Until next time!
Okay! Big update from a technical standpoint, small update from a gameplay standpoint. Warning, if you don't really care much about technical stuff, you might want to scroll down to the second screenshot and start from there!
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.
An action-adventure game isn't really much fun without enemies, so I thought it might be fun to implement a basic enemy next. I ended up with these little guys:
They're pretty simple enemies. If you recognize any of these from the Zelda series, you probably already know what they do:
These spike things (which the Internet actually tells me are called 'blade traps') lie waiting until the player crosses their horizontal or vertical axis, at which point they charge at the player. When they hit another spike, or a wall, they return to their original location. I thought this would be a simple enough starting point for implementing a very basic AI control mechanism.
I actually added a twist to mine--the player has half a second to get out of the way upon 'waking' the spike enemy before it charges at them.
The spike, therefore (at least, my implementation of it) has four states:
Standard - The spike's default state. It's at rest, waiting for the player to approach it
Waking - The player has woken the spike and has 0.5 seconds to get out of the way. Depending on this, the spike will either return to 'Standard', or move to...
Chasing - The spike moves in a straight horizontal or vertical line in the appropriate direction.
Returning - The spike goes back to its original position, and then returns to the Standard state.
This is probably the best place for a video, which shows the thing in action:
So, what did I have to do to get this working? A few things. As you might know from previous updates, entities function by having scripts (which could also be described as behaviors) attached to them. The following scripts are currently supported by the infrastructure:
Init - Called immediately when the entity is loaded
Update - Called every n to m seconds, depending on how the designer specifies. An update could be called every .5 seconds, every .03 seconds, or randomly every 1 to 4 seconds.
Collide - Occurs when the entity collides with another one. A previous post describes setting up collision categories for the collide event.
Enter - Occurs when the entity collides with another one, but doesn't occur again until they have 'uncollided' and collided again.
Leave - The complement event to 'enter'.
Trigger - This is a custom behavior which the engine doesn't call directly -- it is meant to be called by other scripts. For example, an enemy might call a custom 'damage' trigger (with custom arguments--say, the damage type and the strength) on another entity.
So, that's a quick recap of the scripting system I've set up. It's fairly simple, but allows for infinite possibilities.
I decided I'd implement the Entity State system by introducing an Entity State Collection, which consists of:
The current state
A collection of states.
An Entity State consists of:
The name of the state (i.e. 'waking', 'chasing', etc.)
A collection of script behaviors--collide, update, init, etc. Init is called when the state is changed.
So now, I can cleanly isolate scripting behaviors to certain states. In addition, entities still have a 'global' implied state; that is, script behaviors that are always running no matter what state the entity is in. For example, the player might leave footsteps behind (implemented by an Update script that runs every half second) whether he/she is walking, running, being knocked back, etc. This behavior would therefore be put into the global behavior collection, and not in a particular state.
So there you have it. One thing I don't have (yet) is an Entity Template Designer, so in the meantime I've temporarily hacked the engine to set up the spike enemy with the appropriate behaviors. Here is what the code looks like; as you can see, it's fairly straightforward:
Eventually, a designer tool of some sort will replace this code. The 'table' I'm indexing is from a Lua script file. That is all for this update. The actual script can be found below. Thanks for reading!
local t = { }; ------------------------------------ t["init"] = function(e) --Global initialization. Set the original position of the spike as two custom state variables local entity = e.Entity entity["returnX"] = entity.Position.X entity["returnY"] = entity.Position.Y end ------------------------------------ t["standard_init"] = function(e) e.Entity:SetTexture("spike_sleep"); end ------------------------------------ t["standard_update"] = function(e) local section = e.Section; local entity = e.Entity; local position = entity.Position; local entity = e.Entity; --Create two large rectangles used to test for collision against the player local hpoly = polySquare(v2(position.X, position.Y), v2(1000, 1)); local vpoly = polySquare(v2(position.X, position.Y), v2(1, 1000)); --Actually run the collision, colliding against tag 'player' local h = e.Section:PolyEntitiesTag(hpoly, "player"); --Check the horizontal (and vertical) collision results and set the state to waking if either hit if (h.Count > 0) then entity:SetState("waking"); else local v = e.Section:PolyEntitiesTag(vpoly, "player"); if (v.Count > 0) then entity:SetState("waking"); end end end ------------------------------------ t["waking_init"] = function(e) local entity = e.Entity --Set up function that will be run in .5 seconds local stillAttack = function() --Perform a similar collision detection as in the 'standard' state local position = entity.Position; local hpoly = polySquare(v2(position.X, position.Y), v2(1000, 1)); local vpoly = polySquare(v2(position.X, position.Y), v2(1, 1000)); local h = e.Section:PolyEntitiesTag(hpoly, "player"); local chase = false; if (h.Count > 0) then chase = true; else local v = e.Section:PolyEntitiesTag(vpoly, "player"); if (v.Count > 0) then chase = true; end end if chase then entity:SetState("chasing") else entity:SetState("standard") end end --Open the eye and then run the above function in .5 seconds entity:SetTexture("spike_awake"); entity:ControlNamed( sequence( delay(.5), action(stillAttack)), "wakecheck"); end ------------------------------------ t["chasing_init"] = function(e) --We've entered the chasing state, let's figure out which way to actually go local entity = e.Entity; local player = e.Section:FindEntity("player") if player == null then return end --Do some angle calculations to figure out whether to go up, down, left, or right local playerPosition = player.Position; local position = entity.Position; local rawAngle = math.atan2(playerPosition.Y - position.Y, playerPosition.X - position.X) local angle = closestCardinalAngle(rawAngle) local speed = 300; --Move in that direction entity:ControlNamed(motion(v2(math.cos(angle) * speed, math.sin(angle) * speed)), "motion") end ------------------------------------ t["chasing_collide"] = function(e) --More interesting things will happen here someday e.Entity:SetState("returning"); end ------------------------------------ t["returning_init"] = function(e) --Go back to start local entity = e.Entity; local distance = math.abs(entity.Position.X - entity["returnX"]) +math.abs(entity.Position.Y - entity["returnY"]); local time = distance / 100; entity:ControlNamed( sequence( range("finish", time, interp("position", v2(entity.Position.X, entity.Position.Y), v2(entity["returnX"], entity["returnY"]), "linear")), action(function() entity:SetState("standard") end)), "motion"); end ------------------------------------ return t;
Particles are cool! Lots of games and game engines have ways of setting up things that emit hundreds or thousands of little glowing particles. Technically I already had most of that in place already via scripting and the Section.CreateEntity method. One thing I didn't have, though, was the 'glowing' part, an effect achieved with Additive Blend mode (in contrast with regular old transparency).
See all the particles? A picture tells a few words. But it's not a thousand in this case. Luckily, this video does tell a thousand words (almost--make sure annotations are on for all of these!). Enjoy!
Next up I'll be working on Entity State. That is, I'll be setting up the infrastructure for allowing entities to be in different states, where each state has a collection of behaviors (scripts). This will be the framework for Enemy design, which should be exciting!
A powerful component of some game engines--whether they're 2D or 3D--is the ability to give things (enemies, fireballs, particles, whatever) parent-child relationships such that transformations (moving, scaling, rotating) to the parent automatically affect all children.
For example, an enemy might cast a spell that makes 4 fireballs swarm around him. Instead of the programmer manually calculating the position these fireballs--which can get quite complicated as the player moves around--a system might be set up so that the fireballs are position and moved with respect to the enemy.So, from a programming standpoint, the fireballs simply spin around (0, 0), and when the enemy moves, the fireballs move with it.
I have a video, as always, that demonstrates this new feature. Check it out!
Additional notes on the video:
You'll notice that I have a new character sprite. Say goodbye to Link and say hello to...well, it's a temporary design and will most likely change, so there's no point giving him a name.
Those test triggers (the white diamonds) appear to show off some sort of particle system. Although you might call them 'particles', they're actually just entities just like everything else, meaning they support the complete feature set thus far.
The entire scenario demonstrated in the video is entirely designed within the level editor via the scripting interface. For example, here's the part of the script that creates the tiny little balls at the end:
Still here, but nothing new to show from the last week. Ran into an obnoxious memory problem with LuaInterface. It's been resolved, but it took out a good chunk of fun feature-adding time! Stay tuned for something a bit more exciting.