Jakob's Blog coding for fun and growth

Gamedev #6: New features in 0.2.1

Paddlers 0.2.1 is online and brings a ton of new features! Among other features, I present a new take on the tower defense aspect of the game, custom shaders for improved graphics, a skill-tree, and quests to guide the players through the game mechanics.

Play the demo here! (Register with a fake email, it is still not used in any way.)

It is no coincidence that this release is packed with features. After a long time refactoring Paddlers and creating a browser game framework called Paddle, I wanted to focus for some time on implementing new features for the game itself.

This post touches on the most important changes, starting with those which have been implemented directly in the framework and finishing with features specific to Paddlers the game.

Table of contents

Changes affecting the framework Paddle

Improved render pipeline

The render pipeline in Paddle has been completely reworked for this release. There is now a clear distinction between three phases.

  1. Tessellation of any object to a mesh of triangles. (Once per object is usually sufficient.)
  2. Transforming and positioning a mesh. (Usually done every frame.)
  3. Painting a prepared mesh. (= applying attributes to the mesh to define the color/texture/shader of vertices)

The API on this is likely to change as it matures but it already allowed me to do some cool stuff in Paddlers which was not possible before.

I have very little experience with low-level graphics programming, so you can imagine that these sorts of things can be time-consuming. Furthermore, coming up with a good API is impossible at the first attempt, without the necessary experience. For your entertainment, here is an image that shows how things can go wrong when messing with the rendering process.

Image: Broken rendering of map view, with many rivers drawn on top of each other Screenshot from the work-in-progress renderer.

Custom shaders

The initial render pipeline in Paddlers was mostly a copy from quicksilver, which supports the drawing of images and geometric shapes. The flowing water in my game has simply been represented by a sliding texture that I sketched in Inkscape. (Which looked terrible.) Now I added support to fill any geometric shape not only with colors or images, but also with a custom shader. Using the same texture but a different fragment shader, I now have rendered water that I like much better.

Image: Animated water

fn draw(&mut self, state: &mut Self::State, display: &mut DisplayArea, timestamp: f64) {
    {
        let water_shader = &state.shaders.water;
        display.update_uniform(
            water_shader.render_pipeline(),
            "Time",
            &UniformValue::F32((timestamp / 1000.0) as f32),
        );
        let water_area = Rectangle::new((0.0, TOWN_TILE_S * TOWN_LANE_Y as f32), (TOWN_TILE_S * TOWN_X as f32, TOWN_TILE_S));
        display.draw(&water_area, water_shader);
    }
}

The code snippet above shows how the shader is used in Paddlers to fill the area that is specified for the water. A uniform called Time is specified as well, which the shader can access. (This way the water flow movement can all be computed by the GPU.) (Link to fragment shader used.)

The API to use shaders is a first trial and I will probably iterate on it a couple of times before Paddle will have a 0.1 release. Especially registering shaders is still a bit dubious and I am sure it will change in the future. I am not going to show any code for that right now.

Besides the water, there is also a completely new view in Paddlers 0.2.1 which utilizes the custom shaders: The skill overview page. It has a mysteriously moving background that reminds of water in the dark and smoke at the same time. To see it in action, go and play the demo and unlock one of the skills! :)

Image: Skill overview page inside the game Overview of skills in a new view.

Lyon dependency update (0.13 to 0.17)

While rewriting virtually all the code around WebGL and tessellation, I also updated the dependency of Paddle on Lyon to the newest version. This is exposed to potential future users of Paddle and I saw this as a blocker before considering even a 0.1 release of Paddle, thus I am pleased I managed to squeeze this change in.

Event broadcasting with Nuts

Paddle relies heavily on Nuts for simple message broadcasting between components. For example, paddle::share(Signal::PlayerStateUpdated); broadcasts a signal (using nuts::send under the hood). Every component can listen to this signal to perform some updates whenever the player state changes. In this example, the sender and receiver are oblivious of each other. And the sender requires absolutely no channel setup thanks to Nuts.

How is it possible? Routing is done completely based on the compile-time type of the message. But that is a topic for another day. (I have a draft about Nuts lying around unfinished for months already, hopefully, I will finish it eventually.)

This broadcasting worked fine so far for messages that only need to be shared with other components. But for messages that are consumed by one specific receiver, it was a bit of a problem, since each listener can only borrow the data. Consuming therefore implies cloning the data. A concrete example where this bothered me was for a JSON payload received from the server that I wanted to forward from the general networking component to a specific component.

To solve this, Paddle now also supports paddle::send::<_, T>(msg); which transfers ownership of a message to a specific receiver (specified by type, in this case, T). This uses private channels of Nuts under the hood, which are also new in Nuts. Here, the routing is done based on the compile-time type of the receiver as well as the message.

New game features for Paddlers

There are too many features in this release to point them all out individually. But let me give you an overview of what big decisions about the game have been made and implemented.

Tower defense changes

In all previous versions of Paddlers, arriving visitors (attackers) swam through the village as soon as they arrived. Now they are put in a queue and the player has to actively let them in. The amount of Paddlers in the queue is limited and can be upgraded as the player progresses.

Image: Visitor queue Gate at the village entrance where visitors are queued up.

This change tackles two fundamental game design issues I faced.

First, it bridges the gap between the two time scales that Paddlers operates on. On the one hand, the game is designed to be a strategy game with only a few tactical decisions each day. On the other hand, when sitting in front of the screen, there should be something for the player to do.

With this change, a player can log in, empty the queue and play a bit of tower defense in real-time. Then, one can send out more invitations and spend the resources gained, and call it a day right there.

Second, players could spam invitations all day without penalty. The available Paddlers to invite are a common good shared among all players. Clearly, there is a problem if one player can claim all of them at once. Now the invitations per player are at least limited by the slots available in the queue. It is a good first step but not a complete solution. I have several more features in mind to put more incentives towards choosing wisely whom to invite, I hope I can implement them soon.

Quests

At first, I was not sure if quests as present in so many other games were a good fit for Paddlers. But eventually, I decided, yes, they help me to create a kind of tutorial for the key game mechanics. And they are a good fit for the long-term progression aspect of the game.

The quests are named Duties and are meant to be direct tasks from the God (the player) to the Paddlers. But the narrative behind this is rather lackluster at the moment, to be honest. I will need to put some thought into how to improve this.

Quests are accessible through a menu button at the Temple.

Image: Quests view New view with open quests.

Technically, the quests are specified in *.ron files and they are uploaded to the SQL database at installation time on the server. The client loads all information about the quests dynamically from the server.

To keep the quest progression tracking responsive at the frontend, it is done autonomously in the client, without constantly checking for the newest updates on the server. Of course, the server still has to do the safety checks in the end when collecting the quest reward, but I want to keep the number of messages going back and forth between server and client as low as possible.

Right now, there are 8 quests in the game. With my current setup, it is very easy to add more quests to the game. I will see how much I want to use them.

Tax paying civilians

There are two different types of Paddlers in the game. Workers who walk around and collect resources, and tenants who just sit in their nest and do nothing. Each player starts with one worker and no tenant. But the generated anarchist villages have usually three Paddlers just sitting around and no workers.

Image: Two ducks sitting in a nest Two unimpressed Paddlers sitting in their nests.

The feature to build new nests as a player and let Paddlers move in has been added in 0.2.1. Already before, Players could send invitations to tenants of foreign villages and receive a reward for making them happy. Now, those Paddlers also give a daily reward to the village owner of their home.

In the future, there should be different personalities, attributes, and needs associated with each Paddler. And the player should have a certain degree of choice who gets to move in. But for now, all tenants are identical and they can only visit other villages and pay taxes.

Look ahead

After this release with all those features, I plan to first do some cleaning up of all the construction sites I left on the road. It is great to see the game Paddlers grow quickly and I love the creative aspect of it. However, I want to make sure things grow at their natural speed and keep a clean codebase.

At some point, I should also add documentation and release a 0.1 version of Paddler. I will do so as soon as Paddle feels a bit more decoupled from Paddlers as it does right now.

As the code matures, I also want to move more and more from Paddlers over to Paddle. Ideally, it will become a framework that covers the frontend client as well as the backend with user management and so on. I believe there is an audience of game developers who would love to quickly spin up a browser game with client and server all written in Rust and easy code sharing between the two. But that is a vision far, far away in the future.

First, I need to get the client code in the framework right. That means putting lots of thoughts into the API and test its practicality with Paddlers and potentially other example projects. All of that, one step at a time.

Comments can be submitted through reddit.

Technology Stack

Rust

The Rust programming languages had its first stable release in May 2015. Although it is in its core a systems programming language, it has been adopted rapidly in different environments. Many programmers love using it and the community is growing quickly.

WebAssembly

WebAssembly is a new web standard (1.0 since October 2017) that allows running bytecode in the browser. It it possible to compile Rust, C/C++ and other languages to Wasm.

WebGL

WebGL is a web standard that brings low-level graphics to the HTML5 canvas. Simply put, it is the OpenGL for the web.