Necking devlog 1

I’m Nicola. This is the beginning of devildahu’s weekly devlog.

Necking

We are working on Necking. Necking is a competitive/cooperative 1-on-1 online game where players are giraffes and fight for male dominance in the giraffe way.

Necking is the way male giraffes fight each other to assert dominance. Level of violence during necking vary widely, going from drawing blood, including quick one-tap-and-its-over and even reaching the zone of affection where it’s difficult to tell if the giraffes are hitting each other or tenderly caressing.

Necking started as a project in late December 2021, after watching an animal documentary on giraffes. So that’s a bit more than a year ago. I’ve been contributing to it on and off for the past year. I’ve picked up at a faster pace development about two weeks ago (19th of January)

I had stopped work on the project around July 2022, in the middle of a migration.

Custom joint system

Initially, the game used custom joint physics, built on the heron collision detection library (it is a physics library, but at the time, we only used the collision detection). In 2022, rapier (a popular rust physics library) got a really good integration with bevy and I decided to migrate to it.

The joint physics looked great but didn’t have credible or enjoyable physics at all, which is a major issue for a physics-based video game.

Three giraffes fighting, the giraffes are made of a large yellow box and
  seven default capsule 3d shapes for neck, the heads are also just a single
  box

3d Diagram describing how input on a X/Y axis can be translated into
  a capsule rotation

Diagram showing how to compute the position of the N-th capsule in a
  chain of capsules based on a given input

Migrating to rapier

In the week of the 19th I spent most of my time reimplementing existing features. Notably mouse controls. I also rewrote the way input is translated into actions (of course, since that’s the part that touches the physics).

mouse picking

Target indicator

I added little arrows to show in which direction the neck is tilting. I felt that the movement was very difficult to read without the arrows. The arrows represent the “target” position of the neck, the one the giraffe tries to reach, but due to its limited strength, and being pushed by the other giraffe, won’t necessarily be the exact position of the neck. So what is shown on screen doesn’t reflect exactly the player input. This is really bad, it feels wrong and sluggish, there is a dissociation between input and effect. Adding the arrows shows to the player as soon as possible that the game registered their input. It feels just right.

Bevy controls design

On a more technical aspect. I completely rewrote the controls system. This involved transforming a single component representing all possible input methods, including AI and Network controlled giraffes, into many different components, four (4) per input methods.

It’s a bit weird and contrary to what I’m used to do, but it actually works extremely well. There is a component to control each neck vertebra using mouse, using the left stick, the right stick, etc. There is a component for each way to control the whole giraffe body, etc. There are even 3 different components to control the tongue of the giraffe!

pub mod giraffe;
pub mod neck;
pub mod tongue;
//..
        app.init_resource::<Dragging>()
            .add_system(neck::apply_left_stick)
            .add_system(neck::apply_right_stick)
            .add_system(neck::apply_stick_move_stiffness)
            .add_system(neck::apply_mouse)
            .add_system(neck::reset_mouse)
            .add_system(neck::apply_mouse_move_stiffness)
            .add_system(giraffe::controller_cam_update.before("camera"))
            .add_system(giraffe::keyboard_cam_update.before("camera"))
            .add_system(giraffe::keyboard_move.before("camera"))
            .add_system(giraffe::controller_move.before("camera"))
            .add_system(tongue::gp_move)
            .add_system(tongue::keyboard_move);

Bevy UI

I also rewrote the UI. The last draft used my library, bevy-ui-build-macros. It is a monstrosity, please do not use it.

Because it is a monstrosity, I had to switch to something different. The default way to define UI in bevy is a catastrophe, so I needed something else to replace my ungodly macro. Recently, someone posted in the bevy discord bevy_ui_dsl. I used this, but it felt like it didn’t improve much on the default experience.

Anyway, once I could just press spacebar twice to start the game, I left that on the side and focused on gameplay. There is still so much other stuff to do.

Second week

All of that was in the first week of the development resumption. I could now focus on actual gameplay.

I added movement. The giraffe controls exactly like a tank. This required changing the camera point of view to a more classic video-gamey position. I first wanted to make the game look like a documentary using trope documentary camera sequences (long shot with zooms, pans, and even maybe commentary on top, in the vein of Regular Human Basketball). Maybe I’ll go back to this in the future, but currently, the over-the-shoulder cam makes more sense, as a gamer, it’s a bit easier to pick up.

First movement design, since then, the giraffes have slowed down.

Now that the giraffes could move, I needed a way to prevent them from occupying the same space.

I used the rapier character controller, I had to fiddle a lot with it. I was using a horizontally laid capsule for the giraffe body. This resulted in giraffes just climbing each other as soon as they touched. This behavior is actually seen sometimes in nature when two males neck each other (yes, in a sexual manner). But that’s not what I want (at least yet) for my game, and anyway they don’t do it all the time.

Also once they were not in the same plane anymore, that was it, they were either half buried in the ground or floating in space, with no way to get back on firm ground.

I had to adjust the giraffe collider so that they couldn’t climb each other. I used a rounded cuboid, but it took time to get to it, more on that later.

Even with a cuboid, there is still a major problem! The rapier character controller doesn’t support rotation. This is fine if you are using a classic shape for controller such as the vertical capsule; it has a circular footprint, and therefore rotation doesn’t change the space the shape occupies. But giraffes (very much like tanks) do have more of a rectangular footprint, hence rotation is meaningful.

So when you rotate, it’s still fairly easy to occupy the same space as another giraffe. I’ve not implemented anything to fix that yet though.

Two giraffes occupying the same space

Same image, but without models, it shows the physics shape used for the
  giraffes

Tongue controls

Giraffes have funny tongues! ahahah! Of course, the natural consequence is that the tongue must be a central element of gameplay. Players must be able to control the tongue and do funny things with it. Otherwise, might as well use cats with long necks instead of giraffes, come on.

Cool giraffe fact

(I’ve a list of 20 cool giraffe facts, might as well start dropping them now)

Giraffe tongues are about 45cm long (18 inch). It is extremely prehensile, since it must be able to pick out acacia leaves from its surrounding thorns.

But here is the bummer. We are now using rapier for joints. This allows me to specify joints (ragdoll physics) for more than just the 7 neck vertebrae. It looks great on the tongue, ears and ossicones (the horn-looking things). It was very hard to specify the joint parameters so that they work well with the model, but I already solved this last year.

But now, to move the tongue, I need to move the joint linking the tongue to the head, change its parameters to something else. The “root” of the tongue would move to the inside of the head and the rest of it would follow up. I needed to find the right joint parameters to express “the inside of the head”.

As it is, I would need to change a little bit the code, compile (1s) restart the game and look at the tongue (10s) to see the result. It simply is not feasible in any reasonable time, and it’s extremely tedious and I care for myself at least a bit.

Component Mirror

The way I adjust minor values for components in bevy, is using bevy-inspector-egui. It allows me to modify at runtime some component values and the copy them back into the source code. Problem: bevy-inspector-egui requires libraries to implement bevy’s Reflect interface. Rapier joints and collider shapes do not, and cannot implement bevy’s Reflect trait.

So I made bevy_mod_component_mirror.

In this crate, I define “mirrors” of the joint. They are just structures that can be created from rapier’s joints and colliders, can be modified, implement bevy’s Reflect trait and can update the joints and colliders with the modified values.

As a bonus, the joints structure is much more plain and understandable than the actual rapier joints and collider shape struct. This is because rapier’s struct definition is informed foremost with speed in mind, at the expanse of readability.

BLeeeehhrrrrrpppppps, boinyongyongsluuuurp, bleeeeehhhhrp, boinyongyongsluuuuuurp…

Cuicui

Now that the burning question of the tongue was solved, I decided to go back to the game menu.

a menu with two very large ugly checkboxes and tinny text

The player must be able to chose how many giraffes there are, what controls to use, if they are online, etc. Also we have a basic options menu.

I hope this doesn’t sound like a lot, because those are very limited options. But even then, it’s impossible to accomplish using bevy_ui.

I also wanted the text to be outlined, but again, impossible in bevy_ui.

Placing elements in bevy_ui is also a struggle, the layout model (Flexbox) relies a lot on implicit parameters, it’s very hard to get an idea of how things will look when you write the code. And even using something like bevy-inspector-egui, it is hard to understand what is going on. bevy-inspector-egui fairs poorly with bevy_ui, because the Flexbox components have a lot of parameters and it quickly becomes a struggle to manage them.

Well, this simply means bevy_ui is not usable for necking. At least right now.

Enters Cuicui

Cuicui is the name for the UI framework I’m developing. Over the past 18 months, I’ve accumulated a lot of opinions on how a good bevy UI framework would look like. And cuicui is the concretization of those opinions.

Most notably:

Cuicui layout

Those are not empty promises, cuicui layout already exists.

Demonstration of cuicui layout.

This took a bit more than a day to get working, and half a day to write documentation and a type-safe wrapper to construct a layout tree (the compiler yells at you if you make an invalid layout).

The cuicui source code is available here (will be licensed under permissive apache 2.0).

The docs explaining the layout algorithm is here.

The plan for next week is to work up to Wednesday on Necking and start work on a 3 weeks-and-it’s-done-I-promise game. See you next Sunday for another devlog.

(you may be interested in this blog’s rss feed)