Pull to refresh

How to make a destructible landscape in Godot 4

Level of difficultyEasy
Reading time2 min
Views1.1K

In my just released game “Protolife: Other Side” I have the destructible landscape. Creatures that we control can make new ways through the walls. Also, some enemies are able to modify the landscape as well.

That was made in Godot 3.5, but I was interested in how to do the same in Godot 4 (spoiler: no big differences).

The solution is pretty simple. I use subdivided plane mesh and HeightMapShape3D as a collider. In runtime, I modify both of them.

How to modify mesh in runtime

There are multiple tools that could be used in Godot to generate or modify meshes (they are described in docs: https://docs.godotengine.org/en/stable/tutorials/3d/procedural_geometry/index.html). I use two tools here:

  • MeshDataTool to modify vertex position

  • SurfaceTool to recalculate normals and tangents

BTW, the latter is the slowest part of the algorithm. I hope there is a simple way to recalculate normals manually just for a few modifier vertices.

func modify_height(position: Vector3, radius: float, set_to: float, min = -10.0, max = 10.0):
    mesh_data_tool.clear()
    mesh_data_tool.create_from_surface(mesh_data, 0)
    var vertice_idxs = _get_vertice_indexes(position, radius)
    # Modify affected vertices
    for vi in vertice_idxs:
        var pos = mesh_data_tool.get_vertex(vi)
        pos.y = set_to
        pos.y = clampf(pos.y, min, max)
        mesh_data_tool.set_vertex(vi, pos)
    mesh_data.clear_surfaces()
    mesh_data_tool.commit_to_surface(mesh_data)
    # Generate normals and tangents
    var st = SurfaceTool.new()
    st.create_from(mesh_data, 0)
    st.generate_normals()
    st.generate_tangents()
    mesh_data.clear_surfaces()
    st.commit(mesh_data)

func _get_vertice_indexes(position: Vector3, radius: float)->Array[int]:
    var array: Array[int] = []
    var radius2 = radius*radius
    for i in mesh_data_tool.get_vertex_count():
        var pos = mesh_data_tool.get_vertex(i)
        if pos.distance_squared_to(position) <= radius2:
            array.append(i)
    return array

How to modify collision shape in runtime

This is much easier than modifying of mesh. Just need to calculate a valid offset in the height map data array, and set a new value to it.

# Modify affected vertices
for vi in vertice_idxs:
var pos = mesh_data_tool.get_vertex(vi)
pos.y = set_to
pos.y = clampf(pos.y, min, max)
mesh_data_tool.set_vertex(vi, pos)
    # Calculate index in height map data array
    # Array is linear, and has size width*height
    # Plane is centered, so left-top corner is (-width/2, -height/2)
    var hmy = int((pos.z + height/2.0) * 0.99)
    var hmx = int((pos.x + width/2.0) * 0.99)
    height_map_shape.map_data[hmy*height_map_shape.map_width + hmx] = pos.y

Editor

I could not resist and made an in-editor landscape map (via @tool script, not familiar with editor plugins yet).

Demo

This is how it may look like in the game itself.

I’ve put all this on github. Maybe someday I will make an addon for the asset library.

I hope that was useful.

P.S. Check my “Protolife: Other Side” game. But please note: this is a simple casual arcade, not a strategy like the original “Protolife”. I’ve made a mistake with game naming :(

Tags:
Hubs:
Total votes 4: ↑3 and ↓1+2
Comments0

Articles