↓
iaen.es Devlog Leer en español

Gotic · 4 min read

Procedural wildlife in Godot 4: rabbits, deer and butterflies on a budget

Butterflies, rabbits, deer and gulls around the player, placed with a fixed seed and no physics. The first version cost 8 ms per frame; here's how it got down to 2.1.

#animals#procedural#performance#mods#godot

Procedural wildlife in Godot 4: rabbits, deer and butterflies on a budget

After filling Gotic with villages, the island still felt like a stage set: plenty of grass and not a single creature. The Fauna de la isla ("island wildlife") mod brings life around the player without asking anything of them: butterflies over the grass, rabbits in the meadow, deer at the forest edge and a flock of gulls circling over the nearest stretch of coast (the cover shot is taken from the water, off the starting beach).

Run at rabbits and deer and they bolt. Walk slowly and they let you come close, and you can pet them with R (or USAR, "use"). With the day–night mod, butterflies turn into fireflies at night and the gulls leave; with the weather mod, nothing flies while it rains.

How it's built

No animal has a physics body. Everything sticks to the ground by reading the island's height (Island.height_at) and is placed near the player with a fixed-seed random generator: the same spot gets the same layout whenever you arrive, and the tests can reproduce it. There are fixed caps: 24 butterflies, 3 rabbits, 2 deer and 5 gulls. Anything that ends up more than 110 m away respawns nearby.

Each species has a rule for where it can be: rabbits in open grass, deer at the forest edge, and none on sand, water, snow, steep slopes or inside other mods' clearings (villages, ruins, the sheep pen). The 24 butterflies are a single MultiMesh: one draw call.

Sneaking up without scaring them takes three lines. If you move slower than walking pace, the scare distance drops to a quarter (2 m instead of 8 for a rabbit, 3.75 instead of 15 for a deer):

var calm: bool = world.player.ground_speed() < CALM_SPEED
var scare := (RABBIT_SCARE if rabbit else DEER_SCARE) * (CALM_FACTOR if calm else 1.0)
var speed := RABBIT_SPEED if rabbit else DEER_SPEED

They flee for 2.5 s; if the next step would land in water or down a ravine, they turn.

Later, for the bestiary, the wildlife learned to report what each animal is doing (idle, fleeing, being petted), the gull learned to glide 1.6 s out of every 9 and the butterfly to land with its wings spread 2.6 s out of every 11, so each has a moment worth photographing.

What went wrong

  • 8 ms per frame looking for a place to stand. There's no grass on the beach, so all 24 butterflies, with 40 attempts each, searched for a spot every single frame. Measured headless with the whole game running: 10.8 ms per frame. Now anything that can't find a spot waits 3 s between searches (a test makes sure the random generator doesn't advance over 10 consecutive frames) and, while I was at it, only nearby villages get animated: 2.1 ms. Worse, the wildlife commit went in with the test suite cut off by its time limit, never reaching PRUEBAS OK ("tests OK"). The next commit fixed it and said so.
  • Butterflies glued to the player. Those with no grass nearby were hidden underground, but the flight code lifted them to 40 cm above the ground… right next to the player. In the villages, every butterfly swarmed around you. A screenshot caught it, not a test; now there's a test.
  • A rabbit fleeing from under your feet. An animal that couldn't find a spot fled from right under the player; a test caught it. Now it hides underground and searches again every few seconds.
  • MultiMesh lies without a window. In headless tests, get_instance_transform always returns the identity. The mod keeps each butterfly's position separately and the tests read that.
  • The bestiary crashed drawing the rabbit's page with the wildlife mod installed, because no test opened that tab with both mods. Each species can now bring its own model.

Looking at the meadow with wildlife, one screenshot cost 471 draw calls.

Still unverified

It hasn't been measured on a phone, and neither has that same meadow shot without wildlife, so I don't know how many of those 471 calls are its own. And in the bestiary, the gull only reached 3 stars with the best lens and the wildlife frozen on the frame of the shot: with a real player, unmeasured.

More from Gotic →