Fugue Devlog 7: Environmental Effects and Objects


A tree

With the dialogue system in decent shape, I'm moving onto exterior environments. There's a lot to figure out here: environmental effects like wind, fire, smoke, rain, and snow; terrain design; rocks; vegetation including grass, bushes, and trees; skyboxes; and water (which I'm categorizing into "still" water, like those in ponds and pools, and "active" water, like that of the sea). In general creating "natural" effects and objects is way harder than human-made interiors and objects!

Several of the effects (rain, snow, fire, smoke) can be implemented as particle systems. I played around with Godot's Particles node and am pretty happy with the results. Here are a couple demos showing falling leaves and petals:

Falling leaves using a particle system

Falling petals using a particle system

The only thing that doesn't seem possible is the leaves rotating along the x/z axes. It's not a huge deal since these are more of mood-setting elements and there'll be plenty else on the screen to focus on.

Many of the remaining effects are usually implemented with shaders: wind in grass and trees (this tree wind shader works pretty well), the grass itself, and water. I don't have much experience with shaders so I spent some time learning more about them and playing around with them. I haven't yet managed to finish anything substantial yet but will keep exploring there.

In terms of skyboxes, those seem not too difficult to implement in Blender: basically you build your skybox and then export it as a panoramic image into Godot.

Terrain, rocks, water, and vegetation are a lot harder. For terrain and water there are fortunately some Godot plugins available that make it much easier. For terrain, there's Zylann's HTerrain plugin for heightmap-based terrains. This works well for outdoor natural environments and it supports some things out of the box, like painting grass textures and basic wind for grass. Unfortunately the heightmap-based approach doesn't work for things like caves and overhangs, so it's not a panacea.

For caves and overhangs there's Zylann's VoxelTerrain plugin for voxel-based terrains. It's not as mature as HTerrain (lacks a few of its key features, like LOD and grass painting) and it requires compilation into Godot (i.e. not just an addon you can drop in), so I'm not sure if this is exactly what I want to use.

Alternatively, I could create the terrains in Blender using the sculpting and texture painting tools there and import them into Godot. Caves aren't too difficult in Blender (you can use a cube and some displace modifiers to create the right rocky walls as a starting point) so maybe this is the way to go.

For rivers there's Arnklit's Waterways. I wonder if I can stretch it to work for other bodies of water. In any case I may need something different for still bodies of water, like those in grottos:

A grotto

For trees, there were two plugins for Blender I looked at: tree-gen and modular-tree. I also looked at hand-making trees based on "How to Create a Low Poly Tree in 1 Minute" (a similar technique, mainly using Blender's particle system for leaves/small branches, came up in a few other videos). The generated tree-gen mesh has almost 1.3 million vertices which is way too many. In comparison the basic tree generated by modular-tree has about 21k vertices1 and the handmade low-poly tree has only 6.6k vertices. That might still be too many—I don't yet have an intuition of what number I should aim for, I just know that fewer vertices are better in my case. The low-poly one also better matches the look of the game so far (low-poly, photo-realistic textures, and dithering, which in this case is just an artifact of Blender).

tree-gen example

modular-tree example

Low poly example based on "How to Create a Low Poly Tree in 1 Minute"

I wrote a Blender addon that generates the base of the tree (trunk and branches, but without the leaves) in a manner similar to that low-poly tutorial. You still need to manually set up the vertices and particle system for the leaves, but I may expand the script to do some of that automatically. It was a struggle to get the script working properly (my linear algebra is really rusty and the Blender Python documentation, at least for the bmesh parts, is really lacking), and it's not nearly as powerful as tree-gen or modular-tree, but it does what I need and in the style suitable for the game.

Here's the tree with this basic wind shader:

Tree with wind

With trees and other vegetation, as well as rocks, you need not only to create the tree meshes, but also distribute them. This post details the use of a particle system to distribute vegetation throughout a terrain, but Zylann has yet another (!) plugin, Scatter3d, that lets you paint scene instances (e.g. meshes) in Godot. For wind, Godot has a tutorial for a wind shader.

For rocks, Blender has an add-on to generate rocks, which as far as I can tell is based around feeding noise into a displacement modifier (this is the basic approach for creating caves in Blender as well). I've also seen rocks generated with a particle shader, which probably makes more sense if you have a desert landscape that needs tons of small rocks scattered about. If I've learned anything while researching how to approach these natural elements, it's that a rock is not a rock, and water is not water, and so on...there are rocks that are big, for up-close use, or medium-sized ones for foreground elements, or tiny ones for a sprawling landscape; water in a calm pool is not the same as water running through a river which is not the same as water that's crashing against a beach.

A tricky thing with natural elements is the right amount of realism. There's of course a lot of effort spent on figuring out ways to most accurately simulate natural environments and effects, and realism usually comes with a big cost to performance. I'm not trying to make natural environments that approach reality but that are interesting and convincing enough. Whenever I come across a video or post detailing how to make a tree or a water shader or what not, I always have to figure out the context. Is these for rendering in a 3d short film? Is this for a triple-A video game where players are assumed to have the latest graphics cards? Is this a demo of how realistic you can get an effect but way too computationally demanding for practical use?

For example, I found this beautiful Breath of the Wild style grass created using a shader. It's a bit resource intensive and, as wonderful as it looks, it's just not the vibe Fugue's going for.

Next I need to pull these pieces together and try building a couple natural environments—probably a forest clearing and a grotto to start.

  1. It was a little tricky to set up modular-tree. One note is to checkout the blender_28 branch if you're using Blender 2.8 or above. The other is that there's a bug when adding a twig node. What worked for me is to first add a twig node and "execute" the node, creating a mesh called tree, then rename the mesh to twig (or whatever else). Then set up the tree nodes: a trunk node into a branch node, then a "tree parameters" node separately. Check create_leafs, then click the eyedropper for the leaf parameter and select your twig mesh in the scene hierarchy. Then click execute tree

Fugue Devlog 6: More Dialogue System!


A few pain points came up after working with the dialogue system and editor more. The schema of having the text (what's being said) and varying choices and outcomes on the same level didn't make much sense, since the vast majority of dialogue is simple linear exchanges. Having the possibility of choices and outcomes attached to every utterance made the editor graph really unwieldy. This led to a few changes:

  • Because the type of an event can be inferred from who the speaker is, i.e. a "thought" is anything where the speaker is "self", and otherwise it's a "verse", the event types can be dropped.
  • I renamed "events" to "verses", because the term "event" is confusing in the context of dialogue.
  • Verses no longer have one piece of text attached to them but can encompass multiple "lines". Each "line" of a verse has at minimum some text and its speaker, but it can also have a timeouts and/or a delay, or emit a signal to trigger other parts of the game.
    • These are specified by a bit of syntax at the beginning of the line's text. E.g. !foo;t5;d8|This is the text means this line will: emit a signal called "foo", have a 5 second timeout, and a 8 second delay.

These changes meant the dialogue editor could be streamlined:

More streamlined script schema

Another small quality-of-life improvement is a button on each outcome to add a new verse already connected to that outcome. Before I'd have to add the new verse, then drag it next to the outcome so I could connect the cable.

The other update to the dialogue system is a better dialogue box layout system, to minimize overlapping dialogue boxes:

Demo of the dialogue box layout system

This was tricky to figure out. The primary constraint is that you can't reposition dialogue boxes so much that it's unclear who the associated speaker is. The safest axis of movement is along the x-axis, so the very simple approach is to just go from left to right and move boxes either left or right if there are overlaps. Of course there are many situations where this won't help, but I don't think the game will have much more than a few simultaneous speakers at once. The other simplification is that this layout adjustment is applied only to "clear" dialogue boxes; that is those within range that you can clearly "hear" the speakers. Anything out of range will be drawn below these if there's overlap.

I also implemented a custom rich text effect based on this tutorial. It seems like a powerful system:

Custom text effect

Fugue Devlog 5: Dialogue System Implementation


I'm chipping away at implementing the dialogue system. It's daunting; sometimes it feels like trying to build a house all at once. Once you start to sort out what the foundation is, what part depends on what other part, etc, a build order starts to become clear and the whole implementation becomes more manageable. If you can sort that out on paper and think through most if not all of the possible issues, implementation is really straightforward.

Some of the key features like choice selection, tracking dialogue states (e.g. remembering how many times you've interacted with that speaker), and speaker tracking are demoed below:

Demo of choice selection and tracking dialogue state

I discussed an ambient dialogue system in a previous post and made some headway on implementing it. The off-screen dialogue box handling needs more work (really janky atm), but I have the dialogue box "blurring" (which fuzzes dialogue boxes as you get further from then, to mimic those voices becoming harder to hear) more or less working.

I had to compromise a bit though. I originally envisioned a gaussian blur effect, where at sufficient distances the dialogue boxes were basically smeared into nothingness. But blurring individual UI element is really complicated. It looked like the only approach was to setup separate viewports for each dialogue box, render those to textures, and then apply a blur shader to those textures. Way too complicated.

Here's the current implementation of dialogue "blurring". As the player gets further from the speakers, the dialogue boxes scale down and become transparent. It needs some tweaking, e.g. the transparency change looks awkward as a linear function, maybe should be using squared distance to feel more organic. But I think this works well as a general approach. This also means that distant dialogue takes up less screen space, so there'll be less clutter.

Demo of dialogue "blurring"

Here's the handling of off-screen/ambient dialogue. I ended overcomplicating it, spending too much time trying to implement this unnecessarily complicated version, then found that the simpler approach worked better anyways (just clamping off-screen dialogue boxes to screen space). There was some weirdness that needed fixing—basically, off-screen objects in perspective cameras are positioned counterintuitively, so the y-positioning of the dialogue boxes can look unexpected. The most noticeable case is when off-screen dialogue boxes are positioned at the top the screen for things that are behind the camera. It feels more "natural" to have those at the bottom of the screen; so there's a small snippet that ensures that's the case.

Demo of off-screen/ambient dialogue

The other feature, which is also a bit janky right now, is interrupting ongoing NPC dialogue. Discussions where the player isn't involved, i.e. among a group of only NPCs, advance automatically. If the player enters the vicinity of any of the NPCs involved in that discussion, and the player can speak with at least one of those NPCs, they will pause their discussion and address the player. If the player leaves the vicinity, they resume their discussion from wherever they left off.

There's a lot to tweak here, like timing around the discussion pausing and resuming, checking for race conditions (always a possibility when timers are involved), and figuring out how best to handle the lead-in/lead-out snippets (here: "Do you need something?" when the player interrupts and "What was I saying..." when the discussion resumes).

Demo of interrupting an NPC dialogue

Aside from cleaning up the implementations of these features, I'm mostly finished with this first pass at the dialogue system. Still many things to figure out about how dialogue is best triggered and how it should be associated with entities and so on. No doubt things will need fixing and I'll want to do things the system doesn't support as-is. And the tools will also change; I'm already finding pain points with the dialogue editor that need improving.

The next step is to start reviewing all of my notes for the game's world and story and start putting together a more concrete draft/design document. That'll help me figure out what other key mechanic systems are required.

Fugue Devlog 4: Dialogue System Improvements


Not a lot of coding today, but a lot more work on the dialogue system. Sketching the manager render sequence out, figuring out the speaker tracking, dialog box placement, etc.

Sketch of the ambient dialogue system

Dialogue system notes

More dialogue system notes

I've tried to think through all possible cases but surely some have been overlooked. I just hope that none are serious enough to require a complete restructuring. There are so many cases to consider, but a few that are trickier include:

  • Should multiple on-screen decisions be allowed? If so, how do players select between decisions? Right now I'm thinking that only one decision be allowed, but don't know of a robust way of enforcing that constraint in the dialogue editor validator.
  • Should time stop/player movement be locked while in dialogue? I like the idea of being able to move around, break off from conversations, etc. Makes it feel more lively. But it adds in complications of pausing and resuming dialogue. And fixing the player in place while in a conversation feels like a reasonably "real" constraint to have.
    • That being said, pausing and resuming is necessary for ambient conversations. For example, two people having a conversation, then pausing to address the player when they get close enough (e.g. "Can I help you?"), and resuming their conversation once the player leaves their vicinity. This is part of a broader question of whether or not the ambient dialogue system should be distinct from the "player" dialogue system. Ideally they are one and the same, and the player's involvement is a special case (e.g. time stops/player movement stops only if an active script includes the player as a speaker; dialogue always auto-advances/times out if the player isn't involved, etc).
  • How should dialogue progression be handled? There are a lot of potentially conflicting cases here. In general there are three ways a statement can advance: the player hits the next dialogue input, the player selects a choice (if the statement is a decision), or the statement times out (if it has a timeout specified). This is straightforward if there's one statement on-screen. But if there are multiple, as with simultaneous dialogue, then what? If some statements have timeouts and some don't, does pressing the next dialogue input advance them all? Or do they wait for the timeouts to finish? There isn't really a clear answer here. I may just have to pick something and be ready to change it as the rest of the game develops.

This last point is kind of the case with the dialogue system more broadly. I won't really know how well any of this works until I implement it and start working through test cases.

I did get a very basic version of the speaker tracking implemented, was pretty straightforward to do in Godot. Basically I calculate the full bounding box ("AABB") of a spatial node and use that to determine its center x coordinate and maximum y coordinate. Then unproject that point to the camera/screen space. It will get more complicated with multiple speakers and potentially overlapping dialogue boxes, though I think I have a decent solution to that in the sketches above.

Basic speaker tracking

Blender scripting

I also learned a bit of Blender scripting (which uses Python). Most people I know who do a lot of 3d modeling use Rhino/Grasshopper, and I've seen a lot of really amazing work done with scripting in that tool. It looks like Blender is just as capable, which is cool.

I made a big button for quickly exporting the current file to my Godot model folder. Normally I have to go to the export dialogue, navigate to the appropriate folder, then save. Navigating through folders is slow...it's a small thing but smoothes out the workflow a lot.

Big quick export button in Blender

I'm also really impressed by the environment Blender provides for scripting. It has a console for trying things out and logs every interaction you have with the UI so that you can easily figure out what functions do what. There are also several templates provided that make it easy to quick prototype an idea.

Blender scripting environment

Fugue Devlog 3: Dialogue Editor and Manager


Dialogue is a key part to Fugue, so I'm taking care to design those systems well from the start. The challenge is trying to imagine all the potential scenarios I might want to play out with the dialogue system beforehand. I'm probably going to get it wrong but I tried my best to design for maximum flexibility/minimum regret.

There are three main pieces that go into the system:

  • The schema: how is the data that describes a dialogue/conversation encounter (a "script") structured?
  • The editor: if I'm going to be writing and tweaking a lot of these scripts, I need a tool that's robust, quick, and intuitive.
  • The manager: the system that handles running scripts in the game, e.g. figuring out where/how to render the text, handle choice selection, etc.

But first, what features am I looking for in a dialogue system?

  • Branching and dynamic dialogue: choices in dialogue and factors outside that specific encounter influence the course of a conversation.
    • Choices that depend on other variables (either hidden or shown but not selectable until the criteria is met)
    • Variable substitution
    • Pick up at different points depending on previous conversations
    • Circular conversations (e.g. that let you return to a menu of questions to ask someone)
    • Time-limited decisions, especially because managing time will be an important part of the game
  • Rich formatting: colors, bold, italic, and whatever else I can get
  • Entity agnostic: Conversations can be had with both objects and NPCs (without needing to classify objects as NPCs or anything hacky like that)
  • Flexible in triggering: A conversation can be triggered by player choice (e.g. approaching an object/NPC and interacting), by entering the proximity of something, or after some other action is taken
  • Integrate into broader scenes: triggering other actions/animations and events, capture the cadence and rhythm of a conversation with pauses (delays and timeouts) and by revealing the text over time (the "typing" effect)
  • Feels well integrated into the surrounding ambient environment: less "we are locked into having a conversation now"
  • Handle multiple simultaneous speakers: For example, to convey the feeling of everyone talking over each other in a large group

In terms of developing the game and editing dialogue, there are a couple other quality-of-life features, like making it easy to attach a script to an object, NPC, or trigger area and supporting validation/tests to minimize bugs.

The script schema

This is the schema that's currently in place.

A dialogue script has two top-level keys:

  • root: The root note that determines how the dialogue starts. It's just an array of "Outcomes" (see below)
  • events: An array of "Events", which are the basic unit of a dialogue script. This is a flat array, though represents and is parsed into a graph.

An "Event" has the following structure:

  • id: Used to keep track of event relationships. Only needs to be unique to its parent dialogue.
  • type: There are two types of Events:
    • thought: An internal dialogue statement, italicized, and has no associated speaker
    • verse: A spoken dialogue statement, spoken by a speaker
  • text: The actual statement that's shown. Can use BBCode, which means colors and other styles can be applied.
  • speaker: An optional speaker name to show with the rendered text.
  • delay: Optional delay in seconds before the next event is rendered. For pacing a conversation.
  • timeout: Optional timeout in seconds the player has to make a choice or to auto-progress the dialogue. If there are choices, letting the time run out is a "null" choice.
  • signal: Optional signal name (signals are Godot's way of having nodes communicate with each other without direct references) to emit when this event starts. This can be used to trigger things like other actions/animations in the environment (I think, I haven't tested it yet).
  • outcomes: An array of "Outcomes". An Outcome is a link to another Event, with zero or more conditions attached to it.
    • The order of the array matters. Outcomes have their conditions evaluated in the array order; the first to evaluate to true (or to have no conditions) is selected as the next Event.
    • An Outcome with no conditions is the "default" Outcome; there can be only one.
    • An Outcome has:
      • ids: The next events to load if this Outcome is selected. Something I'm thinking through now is whether this should only be a single id or multiple ids (the current implementation); the relevance is for the simultaneous speakers feature. Not sure how to do that yet without making the progression of the conversation hard to anticipate.
      • conditions: An array of Conditions that must evaluate to true for the Outcome to be selected
  • choices: An array of "Choices". When selected a Choice sets a local variable called choice; Outcomes can condition on this variable (i.e. a Choice can lead to a specific Outcome but more complex behaviors are also supported). A choice consists of:
    • id: This is what the choice variable is set to if the Choice is selected
    • required: An array of Conditions that have to be satisfied for this Choice to be selectable
    • show_required: An array of Conditions that have to be satisfied for this Choice to be visible (e.g. for secret choices)
    • text: The text displayed for the Choice. Supports BBCode, so colors and other styles can be applied.

The other piece are Conditions, which have the following recursive schema broken into two types:

  • Comparison:
    • variable: The variable name for the left side of the comparison
    • value: The value or variable name for the right side of the comparison
    • type: Indicates if value is a "value" or a "variable"
    • comparator: One of ==, !=, <, <=, >, >=, for comparing the left and right sides
  • JointComparison:
    • op: An and or or operation
    • a: A Condition
    • b: Another Condition

Thus JointComparisons can contain more JointComparisons and so on.

The dialogue editor

I shouldn't be editing dialogue scripts by hand but through an editor that keeps things valid where possible. This is implemented as a "main screen" EditorPlugin for Godot using its built-in GraphEdit node and other UI elements. I was surprised at how much can be done with just the built-in components, though it was a struggle at times. I learned a lot in the process but some of Godot's UI behavior is unusual coming from frontend web development.

The dialogue editor

An additional feature is a validator. It runs through the script and identifies common errors, checking that:

  • In the script root:
    • There's one default entrypoint. That is, the script has to have some default starting event.
    • Each entrypoint is connected to an event.
    • Each entrypoint eventually leads to a terminal event (i.e. no conversations that loop forever).
  • For all Conditions:
    • All values and variables are defined.
    • All variables reference existing global state variables or choice.
  • For each Event:
    • The text is not empty.
    • Must have a parent (which can be the root).
    • Each Outcome must be connected to another event.
    • Has one default Outcome.
    • Has one default Choice, if it has any Choices.

There are some other small quality-of-life features, like highlighting all events a given event is connected to. The editor will also automatically layout nodes, but it's not very good at the moment. It also gets very dense, very quickly given how many properties there are for an events. I want to figure out how to make that representation more compact and support faster free-flow writing.

The dialogue manager

The dialogue manager is what reads a dialogue script and plays it out in-game. So it needs to render and position the text, render the choices and handle their interactions, etc. So far it's relatively simple (if the schema does its job well, the dialogue manager shouldn't have to do much). But it will probably get more complicated with more advanced features like speaker position tracking, simultaneous dialogue, and ambient dialogue.

An example script played out by the dialogue manager

I won't have any voice acting (bad voice acting is worse than no voice acting!) but I want the talking that does happen to still feel like ambient sound and conversation. This video on game design that accommodates for deaf people or people with hearing difficulties mentions sound cue indicators that can be enabled in Fortnite:

Sound cue indicators in Fortnite, from "Making Games Better for the Deaf and Hard of Hearing" by Game Maker's Toolkit

Sound cues won't be important in Fugue, but maybe something like this can give a sense of ambient snippets of conversation happening around you. In general I want conversations to feel less like you're fixed in a place with a big block of text at the bottom of the screen and more weaved into the environment, which I think this helps with.

I need to implement and experiment with this kind of approach. It might get way too cluttered or be otherwise overwhelming. One way I could approach that is not showing snippets of speech as the visual cue except when you're close enough where you'd be able to make out what they're saying. At further distances I could group further conversations away into a more abstract representation of speech happening off-screen.

Next steps

Working out and implementing the rest of the dialogue system is enough to keep me occupied for awhile. Figuring out the ambient dialogue system, a better way to do simultaneous dialogue, and dynamically positioning dialogue boxes based on the speaker position are the next challenges. Then testing everything, fixing any issues, and feeling confident in its robustness and expressiveness.

After that, I want to try building an exterior environment and work on player movement/scene transitions.

Bigger tasks off the top of my head: an inventory system and building out more object interaction, then thinking through some of the more specialized systems. Right now that includes: a card game, a legal system, and character ability puzzles. But what of those remain and what they ultimately need to do depends on figuring out the rest of the world and story in more detail.

<< >>