extends SceneTree ## The trailer (~46 s), recorded with Movie Maker (tools/trailer/make_trailer.sh): ## godot --path . --fixed-fps 30 --write-movie out.avi --script tools/trailer/trailer.gd -- --no-creatures ## Eight shots: dawn flyover + title, morning meadow, a hut at dusk, the wolf ## moon, a blizzard, down the cliff tunnel, the Cove's pier, and an end card ## over the bay (with the version from project.godot). const FPS := 30.0 const UITheme = preload("res://scripts/ui/theme.gd") var world: Node3D var cam: Camera3D var dn: Node var black: ColorRect var big: Label var sub: Label var cap: Label var hut_c := Vector3.ZERO var fire_p := Vector3.ZERO var yaw := 0.0 func _initialize() -> void: _run.call_deferred() func _h(x: float, z: float) -> float: return world.terrain.sample_height(x, z) func _g(p: Vector3, up: float = 0.0) -> Vector3: return Vector3(p.x, _h(p.x, p.z) + up, p.z) func _run() -> void: var H: Node = root.get_node("Hints") H.path = "user://trailer_hints.json" # (never your real tips) H.load_prefs() H.enabled = false # (Its own save folder, so the Cove can be loaded as a world of its own.) var S: Node = root.get_node("Saves") S.root_dir = "user://trailer_worlds" _overlay() await _enter_world("") await _stage() await _shot_dawn() await _shot_meadow() await _shot_hut() await _shot_wolves() await _shot_blizzard() await _shot_tunnel() await _enter_world("cove") await _shot_cove() await _shot_end() for w in S.list_worlds(): S.delete_world(w.id) DirAccess.remove_absolute(S.root_dir) DirAccess.remove_absolute(H.path) quit(0) ## Loads the world to film: the Valley with no save (""), or a fresh world ## in `worldlet`. Hides the HUD and takes the camera. func _enter_world(worldlet: String) -> void: var S: Node = root.get_node("Saves") if world != null: black.modulate.a = 1.0 world.queue_free() for _i in range(3): await process_frame S.current_id = "" if worldlet.is_empty() else S.create_world("Trailer", 1337, {"worldlet": worldlet}) world = load("res://scenes/world.tscn").instantiate() root.add_child(world) for _i in range(8): await process_frame var p: CharacterBody3D = world.player p.set_physics_process(false) p.survival.set_process(false) if world.hud != null: world.hud.visible = false if world.has_node("HUD"): world.get_node("HUD").visible = false if world.wolves != null: world.wolves.enabled = false dn = world.day_night dn.frozen = true world.weather.set_process(false) world.weather.force("clear") cam = Camera3D.new() cam.fov = 62 world.add_child(cam) cam.current = true # --- set dressing -------------------------------------------------------------------- func _stage() -> void: var spawn: Vector3 = world.player.global_position var site: Vector3 = world.build.debug_find_site(spawn + Vector3(10, 0, 6), 3.0) yaw = 0.6 var hut: Dictionary = await world.build.debug_build_hut(site, yaw) hut_c = site if hut.has("pieces") and not hut.pieces.is_empty(): hut_c = hut.pieces[0].global_transform * Vector3(1, 0, 1) var b := Basis(Vector3.UP, yaw) fire_p = _g(hut_c + b * Vector3(0, 0, 5.5)) var fire: Node3D = world.spawn_piece(ItemDBRef().get_item("campfire"), Transform3D(Basis(), fire_p)) fire.fuel = fire.MAX_FUEL for _i in range(5): await physics_frame func ItemDBRef() -> Node: return root.get_node("ItemDB") func _park_player(at: Vector3) -> void: world.player.global_position = at # (the weather particles follow the player) # --- overlay ------------------------------------------------------------------------- func _overlay() -> void: var ui := CanvasLayer.new() ui.layer = 50 root.add_child(ui) var r := Control.new() r.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT) ui.add_child(r) big = UITheme.label("", 84) sub = UITheme.label("", 26, Color(0.95, 0.9, 0.8)) cap = UITheme.label("", 34) for l in [big, sub, cap]: l.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT) l.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER l.vertical_alignment = VERTICAL_ALIGNMENT_CENTER l.add_theme_constant_override("outline_size", 6 if l == big else 3) l.add_theme_color_override("font_outline_color", Color(0, 0, 0, 0.55)) l.add_theme_color_override("font_shadow_color", Color(0, 0, 0, 0.5)) l.add_theme_constant_override("shadow_offset_x", 2) l.add_theme_constant_override("shadow_offset_y", 2) l.modulate.a = 0.0 r.add_child(l) big.offset_top = -70 # The smaller lines sit on a soft dark box, so they read over snow and sun. var box := StyleBoxFlat.new() box.bg_color = Color(0.04, 0.03, 0.02, 0.55) box.set_corner_radius_all(8) box.content_margin_left = 22 box.content_margin_right = 22 box.content_margin_top = 8 box.content_margin_bottom = 10 for l in [sub, cap]: l.add_theme_stylebox_override("normal", box) l.grow_horizontal = Control.GROW_DIRECTION_BOTH l.grow_vertical = Control.GROW_DIRECTION_BOTH sub.set_anchors_and_offsets_preset(Control.PRESET_CENTER) sub.offset_top = 46 sub.offset_bottom = 46 cap.set_anchors_and_offsets_preset(Control.PRESET_CENTER_BOTTOM) cap.grow_vertical = Control.GROW_DIRECTION_BEGIN cap.offset_top = -70 cap.offset_bottom = -70 black = ColorRect.new() black.color = Color.BLACK black.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT) r.add_child(black) ## 0..1 ramp: in over `fin` seconds from `a`, out over `fout` ending at `b`. static func _env(t: float, a: float, b: float, fin: float = 0.6, fout: float = 0.6) -> float: if t < a or t > b: return 0.0 return clampf(minf((t - a) / fin, (b - t) / fout), 0.0, 1.0) ## Runs `secs` of a shot: `f(t, k)` each frame (t seconds, k = 0..1); fades ## from and to black at its ends. func _shot(secs: float, f: Callable, caption: String = "", fade: float = 0.5) -> void: var n := int(secs * FPS) cap.text = caption for i in range(n): var t := i / FPS f.call(t, t / secs) black.modulate.a = 1.0 - _env(t, 0.0, secs, fade, fade) cap.modulate.a = _env(t, 0.6, secs - 0.3, 0.5, 0.5) if not caption.is_empty() else 0.0 await process_frame func _clock(hours: float) -> void: dn.time = hours / 24.0 dn.apply(true) # --- the shots ------------------------------------------------------------------------- func _shot_dawn() -> void: big.text = "CINDERWILD" sub.text = "Survive the island. Build a home." var c := Vector3(0, 0, -10) await _shot(7.5, func(t: float, k: float): _clock(7.4 + k * 0.8) var a := 2.2 + k * 0.55 cam.global_position = c + Vector3(cos(a), 0, sin(a)) * 115.0 + Vector3(0, 58.0 - k * 10.0, 0) cam.look_at(c + Vector3(0, 6, 0)) _park_player(cam.global_position) big.modulate.a = _env(t, 1.4, 6.6, 1.0, 0.8) sub.modulate.a = _env(t, 2.4, 6.6, 1.0, 0.8), "", 0.9) func _shot_meadow() -> void: var from := _g(hut_c + Vector3(-30, 0, -22), 2.2) var to := _g(hut_c + Vector3(-12, 0, -8), 1.8) var cre: Node = world.creatures var herd: Array = [] for i in range(3): var at := _g(hut_c + Vector3(-14 + i * 2.5, 0, -2 + i * 1.5)) var d = cre.spawn("deer" if i < 2 else "rabbit", at) if d != null: herd.append(d) await _shot(5.0, func(_t: float, k: float): _clock(9.0) var pos := from.lerp(to, k) pos.y = maxf(pos.y, _h(pos.x, pos.z) + 1.8) cam.global_position = pos cam.look_at(_g(hut_c + Vector3(-14, 0, 0), 1.0)) _park_player(pos), "Gather. Hunt. Cook.") for d in herd: if is_instance_valid(d): d.queue_free() func _shot_hut() -> void: var b := Basis(Vector3.UP, yaw) var from := _g(hut_c + b * Vector3(9, 0, 13), 3.2) var to := _g(hut_c + b * Vector3(4, 0, 9.5), 2.0) await _shot(6.0, func(_t: float, k: float): _clock(18.25 + k * 0.4) var e := k * k * (3.0 - 2.0 * k) cam.global_position = from.lerp(to, e) cam.look_at((hut_c + fire_p) * 0.5 + Vector3(0, 1.3, 0)) _park_player(cam.global_position), "Build a home.") func _shot_wolves() -> void: dn.day = 7 # Three wolves, walked round the far side of the fire by hand (in the # fire's light, in frame) rather than left to their own circling. var pack: Array = [] for i in range(3): var w: Node3D = load("res://scripts/creatures/tutorial_wolf.gd").new() w.setup("wolf", world, _g(fire_p + Vector3(7, 0, 0)), 900 + i) world.add_child(w) w.set_physics_process(false) w.set_process(false) pack.append(w) for _i in range(5): await physics_frame # A warm fill from the fire's side, so the circling pack reads at night. var fill := OmniLight3D.new() fill.light_color = Color(1.0, 0.62, 0.35) fill.light_energy = 2.2 fill.omni_range = 18.0 fill.shadow_enabled = true world.add_child(fill) fill.global_position = fire_p + Vector3(0, 3.5, 0) await _shot(5.5, func(_t: float, k: float): _clock(21.6) var a := yaw + 2.4 + k * 0.35 cam.global_position = _g(fire_p + Vector3(cos(a), 0, sin(a)) * 3.4, 1.7) cam.look_at(_g(fire_p - Vector3(cos(a), 0, sin(a)) * 6.0, 0.4)) _park_player(cam.global_position) var far := a + PI for i in range(pack.size()): var wa := far - 0.75 + i * 0.6 + k * 0.45 var r := 5.0 + i * 0.7 var wp := _g(fire_p + Vector3(cos(wa), 0, sin(wa)) * r) var to := _g(fire_p + Vector3(cos(wa + 0.2), 0, sin(wa + 0.2)) * r) - wp pack[i].global_position = wp pack[i].rotation.y = atan2(-to.x, -to.z), "Hold out through the night.") # (creatures face -Z, as creature.gd) fill.queue_free() for w in pack: if is_instance_valid(w): w.queue_free() dn.day = 3 func _shot_blizzard() -> void: world.weather.force("blizzard") var b := Basis(Vector3.UP, yaw) for _i in range(60): await process_frame # (let the snow build up) await _shot(4.5, func(_t: float, k: float): _clock(13.0) var a := yaw - 0.4 - k * 0.25 cam.global_position = _g(hut_c + Vector3(cos(a), 0, sin(a)) * 14.0, 3.0) cam.look_at(hut_c + Vector3(0, 1.5, 0)) _park_player(cam.global_position), "Weather the winter.") world.weather.force("clear") if world.weather_fx != null: world.weather_fx.visible = false # (no leftover snow on the end card) ## Down the cliff tunnel (J1): dug through and propped, lit by torch stands ## you've set, the camera walking down the first stretch in the dark. func _shot_tunnel() -> void: var t: Node3D = world.tunnel if t == null: return t.opened = true t.dug = t.STRETCHES for k in range(1, t.STRETCHES + 1): t.props[k] = true t._rebuild() var ts: Dictionary = ItemDBRef().get_item("torch_stand") var candle: Dictionary = ItemDBRef().get_item("tallow_candle") var land1: Vector3 = t.ORIGIN + Vector3(t._landing_x(1), t._floor_y(1), t._lane_z(1)) world.spawn_piece(ts, Transform3D(Basis(), t._mouth(1) + Vector3(-0.5, 0, 0.9))) world.spawn_piece(ts, Transform3D(Basis(), land1 + Vector3(0.6, 0, -0.8))) world.spawn_piece(candle, Transform3D(Basis(), t._mouth(1) + Vector3(3.0, -2.0, -0.95))) world.spawn_piece(candle, Transform3D(Basis(), t._mouth(2) + Vector3(-0.4, 0, 0.95))) for _i in range(5): await physics_frame var from: Vector3 = t.top + Vector3(-0.6, 1.65, 0) var to: Vector3 = land1 + Vector3(-1.4, 1.65, 0) dn.underground = 1.0 await _shot(5.5, func(_t: float, k: float): dn.underground = 1.0 _clock(13.0) var e := k * k * (3.0 - 2.0 * k) var pos := from.lerp(to, e) cam.global_position = pos cam.look_at(pos + Vector3(3.0, -1.4 + e * 0.9, 0.0) + Vector3(0, 0, lerpf(0.0, 1.2, e))) _park_player(pos), "Dig through the cliffs.") dn.underground = 0.0 ## The Cove (W3): morning over the bay, along the pier. func _shot_cove() -> void: var pier: Node3D = world.pier if pier == null: return var side: Vector3 = pier.dir.cross(Vector3.UP) # (From out over the bay, with the stream's mouth along the shore.) var from: Vector3 = pier.end + pier.dir * 18.0 + side * 14.0 + Vector3(0, 2.5, 0) var to: Vector3 = pier.end + pier.dir * 6.0 + side * 7.0 + Vector3(0, 1.6, 0) if world.moorings != null: # (0.6.0: a couple of rowing boats tied up at the end) world.moorings.load_data({"moored": {"a": {"berth": 0, "name": ""}, "b": {"berth": 3, "name": ""}}}) await _shot(6.0, func(_t: float, k: float): _clock(8.2 + k * 0.3) var e := k * k * (3.0 - 2.0 * k) cam.global_position = from.lerp(to, e) cam.look_at(pier.start.lerp(pier.end, 0.35) + Vector3(0, 1.0, 0)) _park_player(Vector3(pier.start.x, pier.start.y + 1.0, pier.start.z)), "Find what lies beyond.") func _shot_end() -> void: big.text = "CINDERWILD" sub.text = "Self-hosted co-op survival ยท %s" % String(ProjectSettings.get_setting("application/config/version", "")) # Rising over the Cove's bay, looking back at the land. var pier: Node3D = world.pier var c: Vector3 = Vector3.ZERO if pier == null else pier.start var out: Vector3 = Vector3(0, 0, 1) if pier == null else pier.dir await _shot(6.0, func(t: float, k: float): _clock(17.6 + k * 0.4) cam.global_position = c + out * (40.0 + k * 14.0) + Vector3(0, 8.0 + k * 14.0, 0) cam.look_at(c - out * 30.0 + Vector3(0, 4.0, 0)) _park_player(c + Vector3(0, 1.0, 0)) big.modulate.a = _env(t, 0.8, 5.6, 0.8, 0.6) sub.modulate.a = _env(t, 1.6, 5.6, 0.8, 0.6), "", 0.7)