Saturday, December 29, 2012

Section Transitions

You may have noticed that all of my demos so far involve a single level (I call them Sections in code). Of course, this won't do for a real game, so I implemented a few things so that we can trigger changes from one level to another.

1. Introduced the Section Registry to the Asset Editor program. The section registry is more or less a list of all the sections in the game. Its primary purpose, for the time being, is to bind file names to section titles, so this screenshot is a bit bare:


So, I've used the level editor to create two maps, and then I've registered them as sections in the asset editor--and titled them Level 1 and Level 2.

2. Added a method hook on the Game Interface that can be called from scripts, called 'ChangeSection.' See the screenshot on how this is used:


What this says is that when the player collides with this entity (seen in the screenshot as one of those invisible triggers, placed right outside the border of the section), a message is sent to the game to change sections, with a few parameters:

  • The level name, seen above as Level 2. The section registry is used to find the corresponding level file to load.
  • The tag of an entity representing the target location (where the player will be standing), seen above as 'right'. Oh, I added a 'tag' field to entities and re-purposed the collision categories screen as a general 'entity properties' screen. See screenshot below to see how an entity on Level 2 was tagged.

  • The last parameter is a description of a well-known transition type. In this case, we're moving left a section, so the parameter value is 'left'
For now, up/down/left/right are supported, as you'll see in the video, but I wanted to make this abstract enough for other transition types. For example, when entering a cave, much of the logic is the same: we need a target section and a place to put the player when they enter, but we'll probably want to fade to black and then fade in from black, or do a screen wipe, or something like that. Same with going up stairs and down stairs, or falling in a pit, or whatever else the game requires.

Anyway, enough writing, on with the video! Here's me running around two very similar levels as a demo. Note that because level transitions can go anywhere, we can even have a level transition into itself, as shown near the end of the video! And because transitions are controlled via script, we can write levels with strange transitions (for example, a door that takes you to a different level or target position depending on whether a switch is hit, or if you have less than 1 heart left, etc).










Saturday, December 22, 2012

Recreating a Familiar Scene... With a Twist

I did a few things which allowed me to produce a cool demo.

Step 1: Introduce a circular model (and a new texture to use it on).


Implementation detail: While collision between polygons and circles is pretty trivial, the ability to stretch and rotate entities freely means that 'circle' is really 'ellipse'. And collision between two arbitrary ellipses, while not unsolvable, is more computationally complex than standard polygon collision. Therefore, circles are approximated as 16-sided polygons ('hexadecagon', the internet suggests, and my spell checker complains of).

Step 2: For entities that act as triggers, spawn points, etc: allow us to set 'no texture' to the entity and exclude it from the rendering process in-game (but not in the editor). This is what they look like:


These triggers are always rendered on the top layer. They have every other property that entities have, namely script events, which enables us to make something happen when the user collides with the trigger zone, or...

Step 3: Introduced an 'update' script event that can be attached to entities. The 'update' script runs every logical frame of the game, or at an interval specified.

Step 4: Added a 'BounceVeloicty' parameter to the arguments passed through to the entity collision event. This could actually be calculated in the script, but it seems that using it will be so common that it's probably more efficient to calculate it from the engine and pass it through every time. The 'bounce velocity' is just what it sounds like--the direction an entity would travel if it were to bounce off of the entity it collide with (like a wall, or another ball).

Step 5: Add a little script, make a video. Enjoy!


Next up: Linking multiple sections (levels) to each other and transitioning between them.

Wednesday, December 19, 2012

When Entities Collide

I implemented an event that fires when an entity collides with another entity. When setting up a demo for this event, I quickly realized I'd need to set up a system of conditional collisions--known as collision categories--to produce the desired behavior.

This is best demonstrated with a simple example. I have a brazier that can spawn moving fireballs. I want the fireballs to be destroyed when they touch a wall. But I also want the fireballs to pass over other braziers (and the brazier that it comes out of, if it spawns directly in the middle!)

Refer to the following video when reading the rest of this post, as it's much easier to explain in video format.



The first brazier has the following script:


local entity = section:CreateEntity(t);
entity:SetModel("Solid");
entity:Attach("selfCollideEntity", function() entity:Destroy() end)

This ends up creating an entity that passes through everything. Why? Because the entity has no collision targets. To fix this, we need to add the following script, which is on the second brazier:

entity:AddCollisionTarget("wall");

(Note that by default, all 'walls' are tagged with the collision category 'wall'. It's a special collision category which has the property of blocking the player's movement when the player collides. Setting a model to 'Solid' in the editor automatically adds the collision category 'wall'.)

Now the entity is destroyed at a wall. Unfortunately, the brazier is also tagged with 'wall', so that's where the fireball disappears. Our third brazier has the same script, but the brazier next to it has its 'wall' tag removed. In the video, you can see that the fireballs pass through the brazier, but so does the player.

The solution: tag the brazier as 'wall' and 'brazier'...




... and add the following to the fireball creation script:

entity:AddCollisionExclude("brazier");

In summary, this means the fireball collides with anything marked 'wall', as long as that thing isn't marked 'brazier'.

That's all for now!

My Sword Does Damage Now

My blade has been infused with an ancient power...

A force found only in ancient legends, striking fear into the hearts of enemies inanimate bushes...

The power of....

section:TriggerAt("playerDamageSelf", { swordPolygon });

Okay, so there's a bunch of stuff that I did recently. I'll get to the damage trigger in a minute. First things first, major changes:

Removed 'Tiles' from the game and replaced them with entities. There was a missing abstraction here--Tiles and Entities were close to identical except for a few properties, so I merged them into one object. Of course, from the editor's point of view, there's still tiles, since it's incredibly convenient to edit layered, axis-aligned stacks of tiles for the level geometry. However, when the game loads these from data, it simply converts them to regular old entities.

Updated the script editor. Here's what it looks like right now:


Most notable is the 'test' button, which compiles and attempts to run the script. Since there's no game context and scripts make use of a number of parameters passed in, the editor uses mock objects that implement those interfaces. At the very least, this ensures two things: that the code compiles, and that the methods called on those interfaces exist, and that the parameters are correct. A limitation of this, however, is that I can't ensure that all branches are executed. In the script above (which turns a light on and off when the player slashes it), the method entity:GetState always returns null, so the 'else' clause won't ever run in the test script.

Also note the label above the method body which tells you the implicit function signature of the event. A future enhancement would be some sort of browser that shows you the available methods on those objects. Intellisense would be great, but that's way out of scope.

Another minor but useful update: added a launch button to the editor. Fourth item on the toolbar (play). Runs the game with whatever section you're editing.



Okay, now for the demos.

This first video illustrates the new trigger 'playerDamageSelf'. This is an event handler for entities, so the 'self' in the name refers to the entity. That is, when you define 'playerDamageSelf', you are declaring what happens to an entity when the player damages it.

There are four sets of bushes, each with a slightly more advanced feature than the last:

1. The leftmost bushes simply change texture when slashed.
2. The script clears the collision model out so the player can walk over it after it's slashed.
3. A single leaf entity is spawned in the middle of the bush.
4. Multiple leaf entities with randomized positions and velocities are spawned.



Next up: Making entities collide. Remember the video with the fireballs coming out of the braziers? Well, let's make them disappear when they hit a wall.

Monday, December 17, 2012

Back from a short hiatus

Was on vacation for the better part of the week, so no updates. Working on getting the scripting architecture exactly where I want it before updating, which is mostly just code restructuring. Look for an update soon!

Monday, December 10, 2012

Enhancing scripts

In my last post, I mentioned a problem I had where I had to specify the 'source texture' when changing a tile's texture from a script. My proposed solution was to move scripts out of the 'TileStack' level and into the 'Tile' level. I went ahead and did this, worked great!


So now, the 'change texture' and 'change animation' script methods are simpler and easier to use.

Having all of this game logic entirely contained in scripts inspired me to move any game-specific logic out of the engine (C#) and into scripts (Lua). So, I migrated the player movement and sword-swinging script (there's barely any code there yet) to a script. There's nothing to show for this, since it's just moving code from one place to another, but it does allow me to change player behavior more dynamically at runtime without rebuilding the code.

Okay, now for the fun stuff. I added a few major things to the scripting infrastructure:

  • A 'CreateEntity' method on the Section interface. This does exactly what you'd expect; it creates an entity based on a number of parameters, such as position, rotation, texture, etc.
  • Added a 'Motion Controller' component to entities. The first implementation of this is a very simple linear motion controller (you give it a velocity vector and the entity will move in that direction every update).
  • Added a ScriptInterface so that scripts can manipulate their own lifetime and other behaviors. This has a few methods, notably the ability to set a 'delay until the script can run again' and 'the number of times a script can run before it is destroyed.'
The following video demonstrates these new features:




Next up: I'll code another event trigger representing 'playerDamage.' That is, the event will be triggered when the player slashes their sword at something.

Saturday, December 8, 2012

Bringer of Light

Minor updates before the demo
  • Updated the editor to allow rotation of textures on tiles and entities. Added shortcut keys (Ctrl+Left, Ctrl+Right) to rotate 90 degrees in either direction.
  • You'll notice a minor change of scenery. Moving indoors for a bit (added a few interior textures).
  • Refactored the scripting engine to pre-compile tile/entity scripts at map load time instead of evaluating them during a trigger.
Okay, a short update. Mostly I just wanted to make a script that did something a bit cooler--so that's what I did. Here's a video of a bunch of lights that change texture from off to on when you touch them:


Now, you might have noticed that there's a bit of awkwardness in the script itself: why should I have to specify the 'source' texture (ground_light_off) in order to change it? The reason, for now, is that the script is actually applied to a collection of tiles (a tile stack). When it executes, it doesn't know which layer of the tile to apply the new texture to. Specifying a source texture lets the method figure out a best guess. Here's the code for that:


I don't really like this method, it's sort of hacky. It seems a more elegant structure would be to allow each layer of the tile stack to have its own scripting triggers. So that's what I'm going to do next.