Tutorial: Old Tam's hut outline beside the fire, pieces snap into it
This commit is contained in:
@@ -36,6 +36,7 @@ var pieces_root: Node3D
|
||||
|
||||
var _hash: Dictionary = {} ## Vector3i -> Array of [piece, point]
|
||||
var last_blocker: Object ## what the last failed overlap check hit (debugging)
|
||||
var outline: Node3D = null ## the tutorial's hut outline, while it's up (placement snaps to it)
|
||||
var _dirty := true
|
||||
|
||||
func setup(w: Node3D) -> void:
|
||||
@@ -206,6 +207,15 @@ func placement(def: Dictionary, hit: Dictionary, yaw: float) -> Dictionary:
|
||||
var info: Dictionary = BuildPiece.kinds()[kind]
|
||||
var hp: Vector3 = hit.position
|
||||
var cands := []
|
||||
# The tutorial's hut outline (ui/hut_outline.gd): a piece aimed near an
|
||||
# empty slot of its own kind goes there, like building to a plan.
|
||||
if outline != null and is_instance_valid(outline):
|
||||
for s in outline.slots:
|
||||
if s[0] == String(def.id) and (s[2] as Node3D).visible:
|
||||
var sxf: Transform3D = s[1]
|
||||
var d := sxf.origin.distance_to(hp)
|
||||
if d < 2.0:
|
||||
cands.append([d * 0.3, sxf])
|
||||
for piece in build_pieces():
|
||||
if piece.global_position.distance_to(hp) > 4.5:
|
||||
continue
|
||||
@@ -394,16 +404,19 @@ func _hut_place(out: Dictionary, center: Vector3, yaw: float, id: String, local:
|
||||
## A spot near `near` with a clear, fairly flat `half`-metre square: nothing
|
||||
## solid in the way and the ground within 0.6 m across it. Searches outward
|
||||
## in rings. Returns `near` itself if nothing is found.
|
||||
func debug_find_site(near: Vector3, half: float) -> Vector3:
|
||||
func debug_find_site(near: Vector3, half: float, step: float = 4.0, min_dist: float = 0.0) -> Vector3:
|
||||
var space := world.get_world_3d().direct_space_state
|
||||
var box := BoxShape3D.new()
|
||||
box.size = Vector3(half * 2.0 + 2.0, 6.0, half * 2.0 + 2.0)
|
||||
var ex: Array[RID] = [terrain.get_node("TerrainBody").get_rid(), world.player.get_rid()]
|
||||
for ring in range(0, 12):
|
||||
var rings := int(ceil(48.0 / step))
|
||||
for ring in range(0, rings):
|
||||
if ring * step < min_dist:
|
||||
continue
|
||||
var n := maxi(1, ring * 6)
|
||||
for i in range(n):
|
||||
var a := TAU * i / n
|
||||
var c := near + Vector3(cos(a), 0, sin(a)) * ring * 4.0
|
||||
var c := near + Vector3(cos(a), 0, sin(a)) * ring * step
|
||||
if not terrain.inside_boundary(c.x, c.z, half + 4.0):
|
||||
continue
|
||||
var hs: Array[float] = []
|
||||
|
||||
@@ -120,6 +120,7 @@ func start(w: Node3D, from_saved: Dictionary = {}) -> void:
|
||||
_enter()
|
||||
|
||||
func stop() -> void:
|
||||
_clear_outline()
|
||||
active = false
|
||||
world = null
|
||||
|
||||
@@ -145,7 +146,16 @@ func _enter() -> void:
|
||||
_check() # (done early? it ticks straight away)
|
||||
|
||||
const DemoWolf = preload("res://scripts/creatures/tutorial_wolf.gd")
|
||||
const HutOutline = preload("res://scripts/ui/hut_outline.gd")
|
||||
var demo_wolf: Node3D = null
|
||||
var outline: Node3D = null ## the hut outline, while the hut step is on
|
||||
|
||||
func _clear_outline() -> void:
|
||||
if outline != null and is_instance_valid(outline):
|
||||
outline.queue_free()
|
||||
outline = null
|
||||
if world != null and is_instance_valid(world):
|
||||
world.build.outline = null
|
||||
|
||||
## A step's set-up: the clock, some animals, the demo wolf.
|
||||
func _setup(su: Dictionary) -> void:
|
||||
@@ -168,6 +178,28 @@ func _setup(su: Dictionary) -> void:
|
||||
if world.terrain.inside_boundary(at.x, at.z, 4.0) and world.terrain.sample_height(at.x, at.z) > world.terrain.water_level + 0.3:
|
||||
world.creatures.spawn(String(sp.kind), at)
|
||||
break
|
||||
if String(su.get("outline", "")) == "hut":
|
||||
_clear_outline()
|
||||
# A good spot a few metres from your fire (or from you).
|
||||
# Search out from the fire in small rings, starting far enough out
|
||||
# that the hut (3 m half-width) leaves the fire 2 m clear of its walls.
|
||||
var near: Vector3 = world.player.global_position
|
||||
var step := 4.0
|
||||
var min_dist := 0.0
|
||||
for pc in world.all_pieces():
|
||||
if pc.item_id == "campfire" and pc.global_position.distance_to(near) < 30.0:
|
||||
near = pc.global_position
|
||||
step = 1.5
|
||||
min_dist = 5.0
|
||||
var site: Vector3 = world.build.debug_find_site(near, 3.0, step, min_dist)
|
||||
var yaw: float = world.player.rotation.y
|
||||
var b := Basis(Vector3.UP, yaw)
|
||||
var first: Transform3D = world.build._free_xf("floor", site + b * Vector3(-1, 0, -1), yaw, true)
|
||||
outline = HutOutline.new()
|
||||
outline.name = "HutOutline"
|
||||
world.add_child(outline)
|
||||
outline.setup(world, first)
|
||||
world.build.outline = outline
|
||||
if su.get("demo_wolf", false):
|
||||
if demo_wolf != null and is_instance_valid(demo_wolf):
|
||||
demo_wolf.queue_free()
|
||||
@@ -200,6 +232,7 @@ func _check() -> void:
|
||||
_advance()
|
||||
|
||||
func _advance() -> void:
|
||||
_clear_outline() # (it was only for the step that's ending)
|
||||
var s := current()
|
||||
done.append(String(s.id))
|
||||
step_done.emit(index, s)
|
||||
@@ -349,6 +382,11 @@ func find_marker() -> Vector3:
|
||||
for pc in world.all_pieces():
|
||||
if pc.item_id == what.substr(6):
|
||||
cands.append(pc)
|
||||
elif what == "outline":
|
||||
# Wherever the outline still has pieces to build: its middle.
|
||||
if outline != null and is_instance_valid(outline):
|
||||
return outline.center()
|
||||
return Vector3.INF
|
||||
elif what == "lake":
|
||||
# The lake's shore, on your side of it.
|
||||
var c: Vector2 = world.terrain.lake_center
|
||||
|
||||
74
scripts/ui/hut_outline.gd
Normal file
74
scripts/ui/hut_outline.gd
Normal file
@@ -0,0 +1,74 @@
|
||||
extends Node3D
|
||||
## The tutorial's hut outline (docs/TUTORIAL_DESIGN.md §2, step 17): a pale,
|
||||
## see-through 4 x 4 m hut on a good spot near your fire, the same 20 pieces
|
||||
## the game's own snapping makes (floors, walls and a door, roofs, gables).
|
||||
## Each piece of the outline fades out once a real piece of that kind stands
|
||||
## in its place. It's a suggestion: you can build anywhere.
|
||||
|
||||
## [item, offset from the first floor, yaw] (from build_system.gd
|
||||
## debug_build_hut at yaw 0: the pieces snap to a fixed grid).
|
||||
const LAYOUT := [
|
||||
["wood_floor", Vector3(0, 0, 0), 0.0], ["wood_floor", Vector3(2, 0, 0), 0.0],
|
||||
["wood_floor", Vector3(0, 0, 2), 0.0], ["wood_floor", Vector3(2, 0, 2), 0.0],
|
||||
["wood_wall", Vector3(0, 0, -1), 0.0], ["wood_wall", Vector3(0, 0, 3), 0.0],
|
||||
["wood_wall", Vector3(2, 0, -1), 0.0], ["wood_door", Vector3(2, 0, 3), 0.0],
|
||||
["wood_wall", Vector3(3, 0, 0), PI * 0.5], ["wood_wall", Vector3(-1, 0, 0), PI * 0.5],
|
||||
["wood_wall", Vector3(3, 0, 2), PI * 0.5], ["wood_wall", Vector3(-1, 0, 2), PI * 0.5],
|
||||
["wood_roof", Vector3(0, 2, 3), 0.0], ["wood_roof", Vector3(0, 2, -1), PI],
|
||||
["wood_roof", Vector3(2, 2, 3), 0.0], ["wood_roof", Vector3(2, 2, -1), PI],
|
||||
["wood_gable", Vector3(-1, 2, 2), PI * 0.5], ["wood_gable", Vector3(-1, 2, 0), -PI * 0.5],
|
||||
["wood_gable", Vector3(3, 2, 2), PI * 0.5], ["wood_gable", Vector3(3, 2, 0), -PI * 0.5],
|
||||
]
|
||||
const MATCH := 0.35 ## a real piece this close to a slot fills it
|
||||
|
||||
var world: Node3D
|
||||
var origin := Transform3D() ## the first floor's place
|
||||
var slots: Array = [] ## [item, Transform3D, MeshInstance3D]
|
||||
var _check := 0.0
|
||||
|
||||
## Lays the outline out with its first floor at `xf` (from build_system's
|
||||
## free placement, so it sits level on the ground).
|
||||
func setup(w: Node3D, xf: Transform3D) -> void:
|
||||
world = w
|
||||
origin = xf
|
||||
var m := StandardMaterial3D.new()
|
||||
m.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
|
||||
m.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA
|
||||
m.albedo_color = Color(0.6, 0.88, 1.0, 0.22)
|
||||
m.cull_mode = BaseMaterial3D.CULL_DISABLED
|
||||
m.no_depth_test = false
|
||||
for e in LAYOUT:
|
||||
var t := Transform3D(xf.basis * Basis(Vector3.UP, float(e[2])), xf * (e[1] as Vector3))
|
||||
var mi := MeshInstance3D.new()
|
||||
mi.mesh = ItemDB.mesh_for(String(e[0]))
|
||||
mi.material_override = m
|
||||
mi.cast_shadow = GeometryInstance3D.SHADOW_CASTING_SETTING_OFF
|
||||
add_child(mi)
|
||||
mi.global_transform = t
|
||||
slots.append([String(e[0]), t, mi])
|
||||
|
||||
## The middle of the hut (the guide's marker points here).
|
||||
func center() -> Vector3:
|
||||
return origin * Vector3(1, 0, 1)
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
_check -= delta
|
||||
if _check <= 0.0:
|
||||
_check = 0.5
|
||||
refresh()
|
||||
|
||||
## Hides each slot a real piece of its kind now fills. Returns how many are
|
||||
## left to build.
|
||||
func refresh() -> int:
|
||||
var left := 0
|
||||
var built: Array = world.build.build_pieces() if world != null else []
|
||||
for s in slots:
|
||||
var filled := false
|
||||
for p in built:
|
||||
if p.item_id == s[0] and (p as Node3D).global_position.distance_to((s[1] as Transform3D).origin) < MATCH:
|
||||
filled = true
|
||||
break
|
||||
(s[2] as MeshInstance3D).visible = not filled
|
||||
if not filled:
|
||||
left += 1
|
||||
return left
|
||||
Reference in New Issue
Block a user