025 - Feb 10, 2019

For the past nearly three years the focus has been on setting the technical foundation for the game, but of late we've switched to working on gameplay and things that players see and interact with.

Along that vein, one of the first things that I added this week was some basic lighting.

Before after lighting Before and after adding our basic lighting.

The depth that was added to the scene from this basic lighting was a pleasant reminder about how small graphical enhancements can make a world of a difference.

I'm still not sure about the exact visual style that I want for the game, but I keep visualizing darker tones so I'll need to experiment with and explore that direction to see if I land on something that fits what I want to communicate with the game.

Fixing Terrain Bugs

Next up I fixed an issue that was causing entities to be rendered below the terrain.

Our terrain is comprised of a grid of tiles, each tile being two triangles.

When rendering an entity we'll figure out what tile it is on, and which of the triangles within that tile that our entity is currently above.

From there we'll create a ray at the entity's (x,z) coordinates that is above the terrain and pointing downwards.

We use this ray to run the watertight ray-triangle intersection algorithm against the triangle that the entity is above in order to find out the y coordinate of the terrain at the player's current location.

Then we render the player at that y coordinate. This way as an entity moves around it is always rendered on top of the terrain.

We've had this could in place for at least a couple of months but it wasn't working. The player would just jump above or below the terrain.

I figured out that it was due to a floating point precision issue. Instead of being rendered at a height of say, 1.91 or 1.45, the height was always 1.0 or 2.0.

Remember how I said that I was casting a ray downwards. Well I was setting the origin as some really large number. So large that there wasn't enough precision in our calculation.

Simply dropping that number to a more reasonable ray origin of 99 fixed the issue. We just need an origin that is always above the terrain, and right now our highest terrain height is 1.96 units.

The terrain code had tests but they weren't as extensive as other parts of the client since they were written when I first first getting used to testing the game client. Had there been better tests this bug likely would've never happened.

All in all took about an hour to track down.

Chatting with NPCs

I then got started on the conversation panel that you see when you talk to an npc.

Chat with NPC Starting a conversation with an entity. You're supposed to be able to click to select a response but I haven't added that interaction yet.

A conversation with an npc is a graph. Each node in the graph has the text that the player or npc has spoken and then a vector of responses.

// Some of our types that power conversations with npc's
// These were written before I started more extensively commenting..
// So I'll have to circle back to explain these fields with comments..

#[derive(Deserialize, Debug, Default)]
#[serde(deny_unknown_fields)]
pub struct ConversationGraph {
    pub nodes: HashMap<u16, ConversationNode>,
    pub start_nodes: Vec<u16>,
}

// TODO: Make fields private
#[derive(Deserialize, Serialize, Debug, Default, Clone, PartialEq)]
#[serde(deny_unknown_fields)]
pub struct ConversationNode {
    pub text: String,
    pub speaker: Speaker,
    pub responses: Vec<Response>,
    #[serde(default)]
    pub weighting: u8,
    pub criteria: Option<EntCriteria>,
    /// Reaching this node advances the entity to another step in a quest
    pub quest_advance: Option<Quest>,
    /// Items that you receive when you reach this conversation node
    #[serde(default)]
    pub receive: Vec<InventItem>,
    #[serde(default)]
    pub consume: Vec<InventItem>,
}

In the gif above there is only one response, but different nodes in that conversation will have multiple responses to choose from.

That conversation is already written out, I just couldn't show it because I haven't finished adding the client side functionality to be able to click the respond and advance in a conversation.

As mentioned last week, our user interface code is still young so I'm still feeling my way into the correct abstractions to be able to add new interactive interfaces quickly.

For now I just implement exactly what I need and then if I've seen the same pattern a few times I'll abstract it. That is just to say that after we build a few more interfaces we'll have a better sense of how to quickly build new interfaces going forwards.

Centering Text

One new bit of functionality that I needed was being able to center text.

My original plan was to write a method to iterate over the glyphs that I was going to render, find the midpoint and then shift the glyphs to be centered at that midpoint midpoint, but just as I got started I remembered that there is an existing library that does this.

So I migrated from rusttype to glyph_brush. glyph_brush uses rusttype under the hood so I felt confident that it would all work fine.

I ended up just reading the glyph_brush example and then figuring out how to make use of it in our code. This went mostly smoothly minus some caching issues that ended up just being due to glyph_brush's' default hashing algorithm having collisions on 32 bit systems. WebAssembly is 32 bit.

Tests

I'm really appreciating our tests. I've fallen into a routine where I can build out complex functionality without needing to run the game in the browser.

Then at the end I'll fire up the game and visually verify that everything works (since you can't trust fully a test until you've seen what it's testing).

Personal

I spent Saturday getting a physical and then hanging out with my sister and her husband so didn't spend as much time working on the game this weekend as usual.

Didn't finish all of the interactions so you can't actually click on the responses yet.

I'll get that working this week and continue to work on the game client. My focus right now is making it possible to finish the first quest in the game client.

We have an integration test for this quest so it definitely works on the backend, so we'll mainly need to add the user interface and graphics in order to complete it on the frontend.

This means that you'll be seeing lots more visual improvements over the coming weeks!


See ya next time!

- CFN