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.
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.
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.
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:
Prefab
system inspired by Bone Klod’s scene design for widgets. A recent innovation by bevy discord user Nionidh enables composablePrefab
s.- Cuicui layout, a layout algorithm your grandma could understand (ok, depends on your grandma) It emits a useful error when the constraints you specified are wrong, and it is easy to gork, you’ll be able to know the final layout from looking at source code.
- Reuses bevy’s 2d Rendering. Now that bevy supports
RenderLayers
, there is no reason to split UI and sprites. It also allows us to re-use pre-existing crates. Go ahead, add particles to your UI, add dynamic 2d lights or jump-flood outlines, or even animate it. You can do all of that in cuicui. - Give back control to users. Specifically, the
Transform
can be manipulated. - More than a button widgets.
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)