I’m working on a 2D settlement builder in Godot 4 inspired by The Settlers II. The key mechanic is that terrain height determines what you can build where — flat ground allows large buildings, slopes restrict you to small ones, steep terrain is unbuildable. Height also affects carrier movement speed on roads.
Settlers II achieves this with a staggered vertex grid where each point has a height value and the terrain is rendered as textured triangles between them. Building placement is calculated from height differences between neighboring vertices.
Before I build all of this from scratch, I wanted to ask:
Has anyone implemented something similar in Godot — a 2D terrain where tiles/vertices have height values that visually offset them and feed into gameplay logic?
Is there an existing addon, plugin, or demo project that handles 2D heightmap terrain? Everything I’ve found (Terrain3D, HTerrain, etc.) is designed for 3D.
Would it make more sense to use Godot’s 3D renderer with a locked orthographic camera instead of going pure 2D? That way I’d get height rendering for free, but I’m not sure if that creates more problems than it solves for a game that’s fundamentally 2D in gameplay.
Happy to hear about partial solutions too — even if someone’s just done the rendering side or the data structure side, that would save me from reinventing everything. Thanks!
yes, but in 3D. You can’t do this in 2D.
use the meshDataTool to create the geometry. You need an array and functions to access the members using 2 numbers like a matrix.
here’s some old code of mine:
func generate_world() -> void:
var rng : RandomNumberGenerator = RandomNumberGenerator.new()
rng.seed = hash("rand")
#generate mesh
shared_mesh = ArrayMesh.new()
shared_mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, terrain_mesh.mesh.get_mesh_arrays())
var mdt : MeshDataTool = MeshDataTool.new()
mdt.create_from_surface(shared_mesh, 0)
for i in range(mdt.get_vertex_count()):
var vert : Vector3 = mdt.get_vertex(i)
var curr_pos : Vector2i = Vector2i(round(vert.x) + TerrainMap.hmapsiz - 1, round(vert.z) + TerrainMap.hmapsiz - 1)
var col : Color = TerrainMap.ground_type(curr_pos)
mdt.set_vertex_color(i, col)
vert.y = TerrainMap.get_voxel_height(curr_pos)
mdt.set_vertex(i, vert)
shared_mesh.clear_surfaces()
mdt.commit_to_surface(shared_mesh)
shared_mesh.surface_set_material(0, terrain_material)
shared_mesh.regen_normal_maps()
terrain_mesh.mesh = shared_mesh
terrain_collision.shape.set_faces(shared_mesh.get_faces())
terrain_mask.queue_redraw()
use 3D, stop overthinking it. 2D cannot handle the high number of sprites required or high quality textures.