The first thing to do is generate texture with perlin noise, to do that create a new project and add a Sprite2d node.
Then add a script to the root node too use the library FastNoiseLite to generate a texture with some noise on it:
extends Node2D
@onready var sprite = $Sprite
func _ready():
var fast_noise_lite = FastNoiseLite.new();
# Set noise parameters
fast_noise_lite.noise_type = FastNoiseLite.TYPE_PERLIN
fast_noise_lite.frequency = 0.02
fast_noise_lite.fractal_type = FastNoiseLite.FRACTAL_FBM
fast_noise_lite.fractal_octaves = 5
# Set the size of the texture
var texture_size = 1024
# Generate the noise image
var noise_image = fast_noise_lite.get_image(texture_size, texture_size)
# Create a new ImageTexture and set the image
var noise_texture = ImageTexture.create_from_image(noise_image)
# Apply the texture to a sprite
sprite.texture = noise_texture
Here is an example with MeshInstance to use the perlin noise, you can attach this script to a Node3D node.
For the child node pick a MeshInstance3D and also a Camera3D to have something like that:
Here is the script to add to the Node3D:
extends Node3D
@onready var mesh_instance = $MeshInstance
var width = 256
var depth = 256
var height = 50
func _ready():
var fast_noise_lite = FastNoiseLite.new();
# Set noise parameters
fast_noise_lite.noise_type = FastNoiseLite.TYPE_PERLIN
fast_noise_lite.frequency = 0.02
fast_noise_lite.fractal_type = FastNoiseLite.FRACTAL_FBM
fast_noise_lite.fractal_octaves = 5
# Create a PlaneMesh and generate terrain
var plane_mesh = PlaneMesh.new()
plane_mesh.size = Vector2(width, depth)
plane_mesh.subdivide_depth = width - 1
plane_mesh.subdivide_width = depth - 1
var array_mesh = ArrayMesh.new()
array_mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, plane_mesh.surface_get_arrays(0))
# Create SurfaceTool
var surface_tool = SurfaceTool.new()
surface_tool.begin(Mesh.PRIMITIVE_TRIANGLES)
# Generate vertices based on noise
for z in range(depth):
for x in range(width):
var vertex_position = Vector3(x - width/2, 0, z - depth/2)
vertex_position.y = fast_noise_lite.get_noise_2d(x, z) * height
surface_tool.add_vertex(vertex_position)
# Connect the vertices to form triangles
for z in range(depth - 1):
for x in range(width - 1):
var idx = z * width + x
surface_tool.add_index(idx)
surface_tool.add_index(idx + 1)
surface_tool.add_index(idx + width)
surface_tool.add_index(idx + 1)
surface_tool.add_index(idx + width + 1)
surface_tool.add_index(idx + width)
# Commit the mesh to the MeshInstance3D
var generated_mesh = surface_tool.commit()
mesh_instance.mesh = generated_mesh
var light = DirectionalLight3D.new()
light.position = Vector3(0, 300, 0)
light.rotation_degrees = Vector3(45, 0, 45) # Adjust the angle as needed
light.light_energy = 1.5 # Adjust the intensity of the light
light.shadow_enabled = true
light.shadow_bias = 0.05 # Set shadow bias to reduce shadow artifacts like acne
light.shadow_normal_bias = 0.3 # Adjust shadow normal bias to deal with self-shadowing issues
light.shadow_blur = 0.3 # Adjust from 0 to 1, where 0 is sharp and 1 is very soft
add_child(light)
var environment = WorldEnvironment.new()
var env_resource = Environment.new()
env_resource.set_ambient_light_color(Color(0.5, 0.5, 0.5))
env_resource.set_ambient_light_energy(0.5) # Adjust for stronger or weaker ambient light
environment.environment = env_resource
add_child(environment)
var material = StandardMaterial3D.new()
material.albedo_color = Color(0.4, 0.8, 0.2) # A greenish color for grass, for example
material.roughness = 1.0 # Matte finish
mesh_instance.material_override = material
You can then attach this script to the Camera3D, it will add some control to naviguate the terrain.
extends Camera3D
var speed = 10.0
var mouse_sensitivity = 0.1
var yaw = 0.0
var pitch = 0.0
func _ready():
self.position = Vector3(0, 10, 100)
self.look_at(Vector3(0, 0, 0), Vector3.UP)
# Lock the mouse cursor to the center of the screen
Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
func _input(event):
# Handle mouse motion for looking around
if event is InputEventMouseMotion:
yaw -= event.relative.x * mouse_sensitivity
pitch -= event.relative.y * mouse_sensitivity
pitch = clamp(pitch, -89, 89)
rotation_degrees = Vector3(pitch, yaw, 0)
# Exit the game if Escape is pressed
if Input.is_action_pressed("escape"):
get_tree().quit()
func _process(delta):
var direction = Vector3()
# Movement controls (WASD)
if Input.is_action_pressed("ui_up"):
direction -= transform.basis.z
if Input.is_action_pressed("ui_down"):
direction += transform.basis.z
if Input.is_action_pressed("ui_left"):
direction -= transform.basis.x
if Input.is_action_pressed("ui_right"):
direction += transform.basis.x
# Normalize direction and apply movement
if direction.length() > 0:
direction = direction.normalized() * speed * delta
translate(direction)
You can download the full project: Node.7z