I was hoping to have this entry posted last year, but there were still some bugs and ugly bits I wanted to iron out before writing about what I’ve been up to.
It’s been a busy few months, but progress has been good. The largest task I’ve been working on is the scripting system, and there’s finally enough of it implemented to be able to “play” a short part of the game. There is still a huge amount to do, but I’m pleased with how things are currently working.
Previously I was using a system of triggers, conditions and actions to run scriptable events. This worked well enough for simple things, such as an NPC that says one line of text, but it started to struggle once more complicated actions and cutscenes were required.
In the end I felt the best course of action was to build a scripting language into the game, rather than trying to turn the existing system into something more powerful. It took a few weeks of development, but it’s finally usable. Here’s a comparison between the old and new systems:
Before:
[template_triggers] {
[*] {
event* = EVENT_INSPECT_ENTITY;
condition* = global_flag_equals('vagabond.has_spoken_to_tensai', 0);
action* = display_textbox('[Text goes here]');
}
}
The above configuration would add a “template_triggers” component to the entity, and assign a single trigger that would listen for the EVENT_INSPECT_ENTITY
. When the entity receives the event, it would then check to make sure the “vagabond.has_spoken_to_tensai
” flag, and would then display some text if flag wasn’t set.
It’s good for simple things like doorways and one-line NPC’s, but it quickly gets unreadable once more complicated decision trees are needed. You can very quickly end up with a dozen or more repeated conditions. Not good.
After:
(listen :event-inspect-entity
(when (not (global-flag? "vagabond.has_spoken_to_tensai"))
(message-box "[Text goes here]")))
I went with a Lisp style syntax, mostly because there is a wealth of information about writing Lisp parsers. The plan is to have a few dozen commands baked into the engine, and then the rest defined in the script engine itself. So far it has worked pretty well, but I expect I might not be so optimistic in a few months.
Probably the hardest problem was pausing the various script processes without blocking the rest of the application. The original script interpreter would run all functions sequentially, which made it easier to implement but impossible to pause a script for external input. In the end I added a stack-based VM and a very (very) simple “process” system, which allows multiple scripts to run in parallel. Processes can pause themselves (and other processes), which makes for some interesting possibilities.
It was a lot of work, and probably not the best way of handling things, but it’s now possible to create some more interesting behaviour purely through scripting.