Zero External Assets — Generating All Art from Code
I don't know Blender and I'm not sorry about it
Every time I open Blender, I accidentally extrude a face, rotate the viewport into oblivion, and close the application. I've tried the donut tutorial three times. I never finished the donut.
So when I started building Oliver's Planes, I made a decision: no external 3D models. No texture files. No asset packs. Every single mesh would be built from Godot's built-in primitives — BoxMesh, CylinderMesh, ConeMesh, SphereMesh. If I couldn't make it from those, it wasn't going in the game.
What started as a limitation became the game's defining aesthetic.
How to build a plane from boxes and cones
A plane is surprisingly simple when you break it down into geometric primitives:
func build_plane():
# Fuselage: a stretched cylinder
var fuselage = MeshInstance3D.new()
var fuse_mesh = CylinderMesh.new()
fuse_mesh.top_radius = 0.3
fuse_mesh.bottom_radius = 0.35
fuse_mesh.height = 3.0
fuselage.mesh = fuse_mesh
fuselage.rotation_degrees.x = 90 # Lay it on its side
# Nose cone
var nose = MeshInstance3D.new()
var nose_mesh = ConeMesh.new()
nose_mesh.radius = 0.35
nose_mesh.height = 0.8
nose.mesh = nose_mesh
nose.position = Vector3(0, 0, -1.9)
nose.rotation_degrees.x = -90
# Wings: flat stretched boxes
var wing = MeshInstance3D.new()
var wing_mesh = BoxMesh.new()
wing_mesh.size = Vector3(5.0, 0.08, 0.8)
wing.mesh = wing_mesh
wing.position = Vector3(0, 0, 0.2)
# Tail: smaller box
var tail = MeshInstance3D.new()
var tail_mesh = BoxMesh.new()
tail_mesh.size = Vector3(2.0, 0.06, 0.5)
tail.mesh = tail_mesh
tail.position = Vector3(0, 0, 1.4)
add_child(fuselage)
add_child(nose)
add_child(wing)
add_child(tail)A cylinder fuselage, a cone nose, a flat box for wings, another flat box for the tail. Add a vertical stabilizer (another thin box, rotated 90°) and landing gear (two small cylinders) and you've got a recognizable airplane.
The magic is in the proportions. Get the wing span right relative to the fuselage length, and the silhouette reads immediately as "airplane." People don't need texture details to recognize shapes — our brains are pattern-matching machines.
Cars from stacked boxes
Oliver's Racers uses the same approach. A car is:
- Body: A box, slightly wider than it is tall
- Cabin: A smaller box on top, set back slightly
- Wheels: Four cylinders, rotated 90° on the Z axis
- Bumpers: Thin boxes at front and rear
func build_car(color: Color):
# Body
var body = MeshInstance3D.new()
var body_mesh = BoxMesh.new()
body_mesh.size = Vector3(1.0, 0.4, 2.2)
body.mesh = body_mesh
# Cabin (windshield area)
var cabin = MeshInstance3D.new()
var cabin_mesh = BoxMesh.new()
cabin_mesh.size = Vector3(0.9, 0.35, 1.0)
cabin.mesh = cabin_mesh
cabin.position = Vector3(0, 0.35, -0.1)
# Wheels
for pos in [
Vector3(-0.5, -0.15, 0.7), # front-left
Vector3(0.5, -0.15, 0.7), # front-right
Vector3(-0.5, -0.15, -0.7), # rear-left
Vector3(0.5, -0.15, -0.7), # rear-right
]:
var wheel = MeshInstance3D.new()
var wheel_mesh = CylinderMesh.new()
wheel_mesh.top_radius = 0.18
wheel_mesh.bottom_radius = 0.18
wheel_mesh.height = 0.15
wheel.mesh = wheel_mesh
wheel.position = pos
wheel.rotation_degrees.z = 90
add_child(wheel)
# Color via material
var mat = StandardMaterial3D.new()
mat.albedo_color = color
body.material_override = mat
cabin.material_override = matDifferent car types vary the proportions. A sports car has a lower, wider body with a tiny cabin. A truck has a tall cabin and a long rear box. An F1 car is extremely flat with a cone nose. Same primitives, different dimensions.
Trees, terrain, and everything else
Pine trees are two or three stacked cones (biggest at the bottom, smallest on top) on a cylinder trunk. Deciduous trees are a sphere on a cylinder. Both look charming at the scale they appear in the game.
Terrain is the most complex generated asset. It's a subdivided plane mesh with vertices displaced by layered noise:
func generate_terrain(size: int, resolution: int) -> ArrayMesh:
var noise = FastNoiseLite.new()
noise.seed = randi()
noise.frequency = 0.01
var verts = PackedVector3Array()
var uvs = PackedVector2Array()
for z in range(resolution):
for x in range(resolution):
var px = (float(x) / resolution - 0.5) * size
var pz = (float(z) / resolution - 0.5) * size
var py = noise.get_noise_2d(px, pz) * 15.0
verts.append(Vector3(px, py, pz))
uvs.append(Vector2(float(x) / resolution, float(z) / resolution))
# ... build index array, calculate normals, create meshThe noise frequency controls how hilly the terrain is. I layer two noise samples — one at low frequency for broad hills, one at high frequency for fine detail. The result looks surprisingly natural.
Buildings in the raceway are just boxes with different aspect ratios. Some have a smaller box on top (air conditioning unit?). A cylinder becomes a water tower. A flat box with stripes becomes a crosswalk.
Why the aesthetic works
There's a word for this kind of look: paper craft. Like someone made a model out of construction paper and geometric shapes. It has the same charm as early PlayStation 1 games or Katamari Damacy — you know it's stylized, and that's part of the appeal.
Players have compared the look to wooden toys, LEGO, and low-poly indie games. These are all compliments in my book. The constraint of "only primitives" forced a consistent visual language across the entire game. Everything looks like it belongs together because everything is made from the same five shapes.
No Blender. No texture files. No asset store purchases. No legal ambiguity about asset licenses. Just code and primitives. And honestly? The games look better for it.
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.