Fix fall-through (spawn on terrain after collision built) and B&W (assign sky env)
This commit is contained in:
@@ -1,18 +1,10 @@
|
|||||||
[gd_scene load_steps=6 format=3 uid="uid://c0world0valheim00"]
|
[gd_scene load_steps=6 format=3 uid="uid://c0world0valheim00"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://scripts/world/world.gd" id="0_world"]
|
||||||
[ext_resource type="Script" path="res://scripts/world/terrain.gd" id="1_terrain"]
|
[ext_resource type="Script" path="res://scripts/world/terrain.gd" id="1_terrain"]
|
||||||
[ext_resource type="Script" path="res://scripts/world/props.gd" id="2_props"]
|
[ext_resource type="Script" path="res://scripts/world/props.gd" id="2_props"]
|
||||||
[ext_resource type="PackedScene" path="res://scenes/player.tscn" id="3_player"]
|
[ext_resource type="PackedScene" path="res://scenes/player.tscn" id="3_player"]
|
||||||
|
[ext_resource type="Environment" path="res://scenes/default_env.tres" id="4_env"]
|
||||||
[sub_resource type="Environment" id="Env_inline"]
|
|
||||||
background_mode = 2
|
|
||||||
ambient_light_source = 3
|
|
||||||
ambient_light_color = Color(0.6, 0.66, 0.72, 1)
|
|
||||||
ambient_light_energy = 0.4
|
|
||||||
tonemap_mode = 3
|
|
||||||
fog_enabled = true
|
|
||||||
fog_light_color = Color(0.72, 0.78, 0.82, 1)
|
|
||||||
fog_density = 0.012
|
|
||||||
|
|
||||||
[sub_resource type="LabelSettings" id="Label_1"]
|
[sub_resource type="LabelSettings" id="Label_1"]
|
||||||
font_size = 16
|
font_size = 16
|
||||||
@@ -20,9 +12,10 @@ outline_size = 4
|
|||||||
outline_color = Color(0, 0, 0, 0.7)
|
outline_color = Color(0, 0, 0, 0.7)
|
||||||
|
|
||||||
[node name="World" type="Node3D"]
|
[node name="World" type="Node3D"]
|
||||||
|
script = ExtResource("0_world")
|
||||||
|
|
||||||
[node name="WorldEnvironment" type="WorldEnvironment" parent="."]
|
[node name="WorldEnvironment" type="WorldEnvironment" parent="."]
|
||||||
environment = SubResource("Env_inline")
|
environment = ExtResource("4_env")
|
||||||
|
|
||||||
[node name="Sun" type="DirectionalLight3D" parent="."]
|
[node name="Sun" type="DirectionalLight3D" parent="."]
|
||||||
transform = Transform3D(0.86, -0.35, 0.37, 0, 0.72, 0.69, -0.51, -0.6, 0.62, 0, 40, 0)
|
transform = Transform3D(0.86, -0.35, 0.37, 0, 0.72, 0.69, -0.51, -0.6, 0.62, 0, 40, 0)
|
||||||
|
|||||||
@@ -2,18 +2,24 @@ extends Node3D
|
|||||||
## M0 mood props — a few low-poly trees and rocks built from primitives in code,
|
## M0 mood props — a few low-poly trees and rocks built from primitives in code,
|
||||||
## so the scene looks "alive" with no imported assets. These are placeholders;
|
## so the scene looks "alive" with no imported assets. These are placeholders;
|
||||||
## real low-poly models (free asset packs, see docs/ROADMAP.md M0) replace them.
|
## real low-poly models (free asset packs, see docs/ROADMAP.md M0) replace them.
|
||||||
|
##
|
||||||
|
## place_on(terrain) is called by world.gd once the terrain exists, so each prop
|
||||||
|
## sits on the real ground height instead of floating at Y=0.
|
||||||
|
|
||||||
func _ready() -> void:
|
var _terrain: Node3D
|
||||||
# A deterministic scattering so it looks intentional, not random each run.
|
|
||||||
|
func place_on(terrain: Node3D) -> void:
|
||||||
|
_terrain = terrain
|
||||||
var rng := RandomNumberGenerator.new()
|
var rng := RandomNumberGenerator.new()
|
||||||
rng.seed = 42
|
rng.seed = 42
|
||||||
for i in range(18):
|
for i in range(18):
|
||||||
var x := rng.randf_range(-55.0, 55.0)
|
var x := rng.randf_range(-55.0, 55.0)
|
||||||
var z := rng.randf_range(-55.0, 55.0)
|
var z := rng.randf_range(-55.0, 55.0)
|
||||||
|
var y: float = _terrain.sample_height(x, z)
|
||||||
if rng.randf() < 0.65:
|
if rng.randf() < 0.65:
|
||||||
_make_tree(Vector3(x, 0, z), rng)
|
_make_tree(Vector3(x, y, z), rng)
|
||||||
else:
|
else:
|
||||||
_make_rock(Vector3(x, 0, z), rng)
|
_make_rock(Vector3(x, y, z), rng)
|
||||||
|
|
||||||
func _make_tree(pos: Vector3, rng: RandomNumberGenerator) -> void:
|
func _make_tree(pos: Vector3, rng: RandomNumberGenerator) -> void:
|
||||||
var root := Node3D.new()
|
var root := Node3D.new()
|
||||||
|
|||||||
@@ -2,27 +2,35 @@ extends Node3D
|
|||||||
## Terrain generator — M0 placeholder.
|
## Terrain generator — M0 placeholder.
|
||||||
##
|
##
|
||||||
## Builds ONE heightmap chunk from deterministic noise, so the world already
|
## Builds ONE heightmap chunk from deterministic noise, so the world already
|
||||||
## looks like rolling ground rather than a flat plane. This is intentionally
|
## looks like rolling ground rather than a flat plane. Single chunk, generated at
|
||||||
## simple: a single chunk, generated at _ready(). Chunk streaming, biomes and
|
## _ready(). Chunk streaming, biomes and server-side determinism (see
|
||||||
## server-side determinism (see docs/ARCHITECTURE.md §4) come in later milestones.
|
## docs/ARCHITECTURE.md §4) come in later milestones.
|
||||||
##
|
##
|
||||||
## The mesh is generated in code (no asset needed) so the project runs the moment
|
## The mesh is generated in code (no asset needed) so the project runs the moment
|
||||||
## Godot opens it, with nothing to import.
|
## Godot opens it, with nothing to import.
|
||||||
|
##
|
||||||
|
## Exposes `sample_height(x, z)` so other systems (player spawn, prop placement)
|
||||||
|
## can sit exactly on the surface instead of guessing a Y and falling through.
|
||||||
|
|
||||||
|
signal ready_built ## emitted once the mesh + collision exist
|
||||||
|
|
||||||
@export var size: int = 128 # chunk is size x size metres
|
@export var size: int = 128 # chunk is size x size metres
|
||||||
@export var resolution: int = 96 # vertices per side (higher = smoother/heavier)
|
@export var resolution: int = 96 # vertices per side (higher = smoother/heavier)
|
||||||
@export var height_scale: float = 10.0
|
@export var height_scale: float = 10.0
|
||||||
@export var noise_seed: int = 1337
|
@export var noise_seed: int = 1337
|
||||||
|
|
||||||
|
var _noise: FastNoiseLite
|
||||||
|
var built: bool = false
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
_build()
|
_build()
|
||||||
|
|
||||||
func _build() -> void:
|
func _build() -> void:
|
||||||
var noise := FastNoiseLite.new()
|
_noise = FastNoiseLite.new()
|
||||||
noise.seed = noise_seed
|
_noise.seed = noise_seed
|
||||||
noise.noise_type = FastNoiseLite.TYPE_PERLIN
|
_noise.noise_type = FastNoiseLite.TYPE_PERLIN
|
||||||
noise.frequency = 0.012
|
_noise.frequency = 0.012
|
||||||
noise.fractal_octaves = 4
|
_noise.fractal_octaves = 4
|
||||||
|
|
||||||
var st := SurfaceTool.new()
|
var st := SurfaceTool.new()
|
||||||
st.begin(Mesh.PRIMITIVE_TRIANGLES)
|
st.begin(Mesh.PRIMITIVE_TRIANGLES)
|
||||||
@@ -40,21 +48,21 @@ func _build() -> void:
|
|||||||
var x1 := (x + 1) * step - half
|
var x1 := (x + 1) * step - half
|
||||||
var z1 := (z + 1) * step - half
|
var z1 := (z + 1) * step - half
|
||||||
|
|
||||||
var p00 := Vector3(x0, _height(noise, x0, z0), z0)
|
var p00 := Vector3(x0, sample_height(x0, z0), z0)
|
||||||
var p10 := Vector3(x1, _height(noise, x1, z0), z0)
|
var p10 := Vector3(x1, sample_height(x1, z0), z0)
|
||||||
var p01 := Vector3(x0, _height(noise, x0, z1), z1)
|
var p01 := Vector3(x0, sample_height(x0, z1), z1)
|
||||||
var p11 := Vector3(x1, _height(noise, x1, z1), z1)
|
var p11 := Vector3(x1, sample_height(x1, z1), z1)
|
||||||
|
|
||||||
_add_tri(st, p00, p01, p11, half, uv_scale)
|
_add_tri(st, p00, p01, p11, half, uv_scale)
|
||||||
_add_tri(st, p00, p11, p10, half, uv_scale)
|
_add_tri(st, p00, p11, p10, half, uv_scale)
|
||||||
|
|
||||||
# Normals give the lighting its shape. Tangents are only needed for normal
|
# Normals give the lighting its shape. Tangents are only needed for normal
|
||||||
# maps, which this low-poly material doesn't use — so we skip them (and avoid
|
# maps, which this low-poly material doesn't use — so we skip them.
|
||||||
# the "UVs required to generate tangents" error).
|
|
||||||
st.generate_normals()
|
st.generate_normals()
|
||||||
|
|
||||||
var mesh := st.commit()
|
var mesh := st.commit()
|
||||||
var mi := MeshInstance3D.new()
|
var mi := MeshInstance3D.new()
|
||||||
|
mi.name = "TerrainMesh"
|
||||||
mi.mesh = mesh
|
mi.mesh = mesh
|
||||||
|
|
||||||
# Simple ground material. Low-poly look = flat-ish colour + lighting does the rest.
|
# Simple ground material. Low-poly look = flat-ish colour + lighting does the rest.
|
||||||
@@ -64,16 +72,28 @@ func _build() -> void:
|
|||||||
mi.material_override = mat
|
mi.material_override = mat
|
||||||
add_child(mi)
|
add_child(mi)
|
||||||
|
|
||||||
# Collision so the player can walk on it.
|
# Collision so the player can walk on it. Build the body BEFORE marking built
|
||||||
|
# so anything spawning on ready_built already has ground under it.
|
||||||
var body := StaticBody3D.new()
|
var body := StaticBody3D.new()
|
||||||
|
body.name = "TerrainBody"
|
||||||
var shape := CollisionShape3D.new()
|
var shape := CollisionShape3D.new()
|
||||||
var col := mesh.create_trimesh_shape()
|
shape.shape = mesh.create_trimesh_shape()
|
||||||
shape.shape = col
|
|
||||||
body.add_child(shape)
|
body.add_child(shape)
|
||||||
add_child(body)
|
add_child(body)
|
||||||
|
|
||||||
func _height(noise: FastNoiseLite, x: float, z: float) -> float:
|
built = true
|
||||||
return noise.get_noise_2d(x, z) * height_scale
|
ready_built.emit()
|
||||||
|
|
||||||
|
## World-space ground height at (x, z). Safe to call before or after _build()
|
||||||
|
## because it lazily creates the noise object.
|
||||||
|
func sample_height(x: float, z: float) -> float:
|
||||||
|
if _noise == null:
|
||||||
|
_noise = FastNoiseLite.new()
|
||||||
|
_noise.seed = noise_seed
|
||||||
|
_noise.noise_type = FastNoiseLite.TYPE_PERLIN
|
||||||
|
_noise.frequency = 0.012
|
||||||
|
_noise.fractal_octaves = 4
|
||||||
|
return _noise.get_noise_2d(x, z) * height_scale
|
||||||
|
|
||||||
func _add_tri(st: SurfaceTool, a: Vector3, b: Vector3, c: Vector3, half: float, uv_scale: float) -> void:
|
func _add_tri(st: SurfaceTool, a: Vector3, b: Vector3, c: Vector3, half: float, uv_scale: float) -> void:
|
||||||
st.set_uv(Vector2((a.x + half) * uv_scale, (a.z + half) * uv_scale))
|
st.set_uv(Vector2((a.x + half) * uv_scale, (a.z + half) * uv_scale))
|
||||||
|
|||||||
31
scripts/world/world.gd
Normal file
31
scripts/world/world.gd
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
extends Node3D
|
||||||
|
## World controller — M0.
|
||||||
|
##
|
||||||
|
## Owns spawn ordering so the player never falls through ungenerated ground:
|
||||||
|
## waits for the terrain to finish building, then drops the player onto the
|
||||||
|
## surface height and lets gravity settle them. Also tells the props where the
|
||||||
|
## ground is so they sit on it instead of floating.
|
||||||
|
|
||||||
|
@onready var terrain: Node3D = $Terrain
|
||||||
|
@onready var props: Node3D = $Props
|
||||||
|
@onready var player: CharacterBody3D = $Player
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
# Freeze the player until the ground exists.
|
||||||
|
player.set_physics_process(false)
|
||||||
|
if terrain.built:
|
||||||
|
_on_terrain_ready()
|
||||||
|
else:
|
||||||
|
terrain.ready_built.connect(_on_terrain_ready)
|
||||||
|
|
||||||
|
func _on_terrain_ready() -> void:
|
||||||
|
# Let props place themselves on the surface.
|
||||||
|
if props.has_method("place_on"):
|
||||||
|
props.place_on(terrain)
|
||||||
|
|
||||||
|
# Spawn the player on the ground at (0,0), a little above so they settle down
|
||||||
|
# onto the collision rather than starting embedded in it.
|
||||||
|
var h: float = terrain.sample_height(0.0, 0.0)
|
||||||
|
player.global_position = Vector3(0.0, h + 2.0, 0.0)
|
||||||
|
player.velocity = Vector3.ZERO
|
||||||
|
player.set_physics_process(true)
|
||||||
Reference in New Issue
Block a user