The Pose System — Blending In With Your Environment
My first win was a rock
I'd painted myself grey-brown, crouched in the corner of the stage, and set my pose to "ball." The AI seeker walked right past me. Its tension bar flickered to two pips and then dropped. I was grinning like an idiot. That's the moment I knew the pose system was working.
Three poses, three silhouettes
The chameleon has three poses that dramatically change its silhouette:
- Stand — default upright lizard. Distinctive profile. Easy to spot because nothing in the environment looks like a standing lizard.
- Ball — tucked into a compact pebble shape. Body squished, head ducked, legs flattened. Great for hiding near rocks or rounded props.
- Star — limbs splayed flat against a surface. Body widened and flattened. Perfect for pressing yourself against a wall or lying flat on the floor.
Cycling between them is a single function:
func cycle_pose() -> String:
var order := ["stand", "ball", "star"]
set_pose(order[(order.find(pose) + 1) % order.size()])
return poseClean. Modular. Rotates through the three options in order. The returned string gets synced over the network (as an integer index to save bytes).
How poses are implemented
Each pose is a set of scale and position adjustments on the body part nodes:
func set_pose(p: String) -> void:
pose = p
var body: Node3D = part_nodes["body"]
var head: Node3D = part_nodes["head"]
var legs: Node3D = part_nodes["legs"]
var tail: Node3D = part_nodes["tail"]
body.scale = Vector3.ONE
body.position = Vector3.ZERO
head.scale = Vector3.ONE
head.position = Vector3.ZERO
legs.scale = Vector3.ONE
tail.scale = Vector3.ONE
match p:
"ball":
body.scale = Vector3(1.15, 0.62, 0.9)
head.position = Vector3(0, -0.42, -0.06)
head.scale = Vector3(1, 0.8, 1)
legs.scale = Vector3(1, 0.3, 1)
"star":
body.scale = Vector3(1.05, 1.05, 0.7)
legs.scale = Vector3(1.8, 1.1, 0.8)
tail.scale = Vector3(1, 1.25, 0.7)First, everything resets to default (Vector3.ONE scale, Vector3.ZERO position). Then the match block applies the specific adjustments for the chosen pose. For "ball," the body squishes vertically (y = 0.62), the head tucks down (y offset = -0.42), and the legs practically disappear (y = 0.3). For "star," the legs spread wide (x = 1.8) and the body flattens on the z-axis.
No animations. No bone system. No rig. Just scale and position. It's crude, but because the chameleon is already made of spheres and blobs, the deformation looks natural. A sphere squished on one axis is still a plausible organic shape.
The three-variable camouflage equation
Winning in Meccha Chameleon requires getting three things right simultaneously:
1. Color — paint yourself to match the surfaces around you 2. Pose — change your silhouette to match environmental objects 3. Stillness — hold perfectly still
Get any one wrong and the seeker finds you. Great color but standing upright? Your lizard-shaped silhouette gives you away. Perfect ball-pose near rocks but painted bright blue? Obvious. Crouched, painted, but fidgeting? The AI's movement detection multiplier kicks in at 3x.
The brilliance of this design (credit to the original MECCHA CHAMELEON) is that each variable is simple to understand but combining them creates real depth. Color is a painting puzzle. Pose is a spatial awareness puzzle. Stillness is a patience test. Together, they make hiding feel skillful rather than random.
3D adds a new dimension (literally)
In the 2D version, you only need to match from one viewing angle — the camera's. In 3D, the seeker can approach from any direction. A pose that looks like a rock from the front might look obviously wrong from the side. This means you have to think about where the seeker is likely to come from and orient yourself accordingly.
Players naturally start doing things like backing into corners (reducing the angles they can be seen from) or hiding behind objects where only one approach is possible. The 3D version doesn't add any explicit new mechanics — the same three variables apply — but the extra dimension of space makes the strategy much richer.
Network sync
When you cycle poses, the paint_dirty flag goes true and the pose is included in the next paint sync message:
ui.want_pose.connect(func():
if me and not GS.am_seeker:
me.cycle_pose()
paint_dirty = true)On the receiving end, the pose string is sent alongside paint data and the remote lizard's set_pose() is called. The whole system piggybacks on the existing paint sync — no separate message type needed. A pose change and a color change are the same network event as far as the relay cares.
Why not more poses?
Three is enough. I experimented with five (adding "flat" for lying completely horizontal and "tall" for stretching upward), but playtesters found the extra options overwhelming. In a tense moment, you want to cycle quickly to the right shape. With three options, you're never more than two button presses from the pose you need. With five, you'd mash the button and overshoot. Fewer options, faster decisions, more fun.
Written by Oliver
Indie game developer building 3D web experiences in Godot and Next.js. I write about creative coding, procedural generation, and game feel.