↓
iaen.es Devlog Leer en español

Gotic · 4 min read

An in-game photo camera in Godot 4: viewfinder, zoom, JPG files and measuring the framing

In Gotic you can pick up a camera, look through the viewfinder and take photos saved as JPGs in an album. The hard part wasn't the photo: it was knowing what's in it.

#photography#godot#mods#testing

An in-game photo camera in Godot 4: viewfinder, zoom, JPG files and measuring the framing

Next to the chest on the starting beach there's a crate with a camera. You take it with USAR (use), hang it around your neck and, while you're wearing it, the FOTO (photo) button, or P, puts you behind the viewfinder: the view moves to the character's eyes, a frame appears with the zoom level and how many shots are left, and you shoot with a click, the space bar or DISPARAR (shoot). There's a flash and a message saying what you caught. Photos go to an album (L) with a grid, a large view with the day and time, and a delete option. It holds 48.

How it's built

Its own camera in the eyes. The plan was to pull the game camera in to almost zero distance, but the turbo rewrites its field of view every frame (it would eat the zoom) and the orbit camera stops turning at such a tiny horizontal distance. The viewfinder uses its own Camera3D that follows the game camera's yaw, with its own pitch range, from −75° to 80°.

The photo. The interface layers are hidden for one frame, the screen is read, scaled down to 640 px wide and saved as a JPG in a folder derived from the save file's name, so no test ever touches real photos. The save only stores each photo's data. Measured on PC: about 15–20 ms in total, a one-frame hitch at 60 FPS. Each JPG weighs 13–20 KB on the beach, and 61 KB with the style studio's Game Boy filter on, which also shows up in the photo.

The camera only measures. When you shoot, for every subject another mod has registered, it works out its height in the frame, how far it is from the centre, whether it faces you or not, whether something is in the way, its distance and its pose. Whoever registered the subject (the bestiary, for example) decides what makes a good photo. The camera has no idea what a creature is.

"Something in the way" means a ray against everything with collision, plus terrain samples every 2 m for distant subjects. But tree crowns have no collision, so they're checked separately, as ellipsoids, using the data on where each tree grows:

static func _hits_ellipsoid(a: Vector3, b: Vector3, center: Vector3, radii: Vector3) -> bool:
	var p := (a - center) / radii
	var q := (b - center) / radii
	var d := q - p
	var t := clampf(-p.dot(d) / maxf(d.length_squared(), 0.000001), 0.0, 1.0)
	return (p + d * t).length() < 1.0

Dividing by the radii turns the ellipsoid into a unit sphere, and all you need is the point on the segment closest to its centre.

The photo album with FOTOS and CRIATURAS tabs and four photos of the beach and the sky
The album: 4 of 48 photos. The CRIATURAS (creatures) tab is added by another mod, the bestiary

What went wrong

The camera was built by an AI subagent (Opus) in its own worktree, and several bugs showed up in real photos, not in the tests:

  • Tree crowns didn't block anything. An air manta hidden behind a tree scored 3 stars. Hence the ellipsoids.
  • Zoom arrived one frame late. A photo right after changing it came out with the old zoom. Another mod's test caught it: the bestiary's.
  • You couldn't look up high. The game camera's pitch has a limit designed for playing; with it, a gull 24 m up came out off-centre. That's why the viewfinder has its own.
  • Entering the viewfinder reset the game camera's pitch to zero, and since that's the one that gets saved, closing the game with the viewfinder open left it there.
  • Without a window there's no screen to read: in the tests, reading it prints an engine ERROR: (and the suite fails), so the photo comes from a placeholder image and everything else is measured the same way.

In the end: 167 checks of its own and 27 mutations seen failing.

Still unverified

Nothing on a phone: how long it takes to read the screen at native resolution and encode the JPG, or fingers on the viewfinder and zoom. The shutter sound was generated and its numbers say it doesn't clip, but I haven't heard it. And the viewfinder frame's corners brush against the MENÚ button: left as is.

More from Gotic →