generated from krampus/template-godot4
Added loading screen
This commit is contained in:
parent
dcd5d5d57d
commit
cfb7b28971
@ -9,6 +9,7 @@ UI_OFF,Off
|
||||
UI_BACK,"⏎ Back"
|
||||
UI_LOCKED,Locked
|
||||
UI_QUIT,Quit
|
||||
UI_LOADING,Loading
|
||||
,
|
||||
PAUSE_HEADING,Paused
|
||||
PAUSE_RESUME,Resume
|
||||
|
|
@ -16,7 +16,7 @@ warnings/threads/thread_model=2
|
||||
[application]
|
||||
|
||||
config/name="Grunk"
|
||||
run/main_scene="uid://884jqafhtrv0"
|
||||
run/main_scene="uid://qpq2cm1hgeha"
|
||||
config/project_settings_override="user://settings.godot"
|
||||
config/features=PackedStringArray("4.4", "Forward Plus")
|
||||
run/max_fps=60
|
||||
|
@ -1,8 +1,12 @@
|
||||
class_name Game extends Node
|
||||
## Interface to the game as an application.
|
||||
|
||||
@export_category("Game Scenes")
|
||||
@export var world_scene: PackedScene
|
||||
@export_file("*.tscn") var start_scene: String
|
||||
|
||||
var _loading_resources: Dictionary[String, Promise] = {}
|
||||
|
||||
@onready var content: Node = %Content
|
||||
@onready var loading_screen: Control = %LoadingScreen
|
||||
|
||||
## Handy typed singleton access.
|
||||
static var settings: GameSettingsType:
|
||||
@ -16,5 +20,91 @@ static var runtime: GameRuntimeType:
|
||||
static var instance: Game
|
||||
|
||||
|
||||
class Promise:
|
||||
var _callbacks: Array[Callable] = []
|
||||
var _end_callbacks: Array[Callable] = []
|
||||
|
||||
func then(fn: Callable) -> Promise:
|
||||
_callbacks.push_back(fn)
|
||||
return self
|
||||
|
||||
func finally(fn: Callable) -> Promise:
|
||||
_end_callbacks.push_back(fn)
|
||||
return self
|
||||
|
||||
func resolve(res: Variant) -> void:
|
||||
for fn: Callable in _callbacks + _end_callbacks:
|
||||
fn.call(res)
|
||||
|
||||
|
||||
class ScenePromise:
|
||||
extends Promise
|
||||
|
||||
func resolve(res: Variant) -> void:
|
||||
@warning_ignore("unsafe_cast")
|
||||
var instance: Node = (res as PackedScene).instantiate()
|
||||
super.resolve(instance)
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
Game.instance = self
|
||||
_initial_load.call_deferred()
|
||||
|
||||
|
||||
func _initial_load() -> void:
|
||||
queue_scene(start_scene)
|
||||
|
||||
|
||||
## Unload the running scene & queue up a new scene to be loaded in the background.
|
||||
##
|
||||
## The loading screen will be shown until the scene is loaded.
|
||||
func queue_scene(path: String) -> Promise:
|
||||
return queue_load(path, ScenePromise.new(), "PackedScene").finally(_finish_scene_load)
|
||||
|
||||
|
||||
## Queue a resource to be loaded in the background.
|
||||
##
|
||||
## Returns a `Promise` which can be used to attach callbacks
|
||||
## which will be called with the resource after it is loaded.
|
||||
func queue_load(path: String, promise: Promise = null, type_hint: String = "") -> Promise:
|
||||
if not promise:
|
||||
promise = Promise.new()
|
||||
_loading_resources[path] = promise
|
||||
ResourceLoader.load_threaded_request(path, type_hint)
|
||||
return promise
|
||||
|
||||
|
||||
func _unload_content() -> void:
|
||||
for child: Node in content.get_children():
|
||||
child.queue_free()
|
||||
|
||||
|
||||
func _finish_scene_load(scene_instance: Node) -> void:
|
||||
# Unpause in case the previous scene was paused.
|
||||
get_tree().paused = false
|
||||
# Reset time scale in case it's been changed.
|
||||
Engine.time_scale = 1.0
|
||||
|
||||
content.add_child(scene_instance)
|
||||
scene_instance.reparent(content)
|
||||
|
||||
|
||||
func _process(_delta: float) -> void:
|
||||
if _loading_resources:
|
||||
loading_screen.visible = true
|
||||
|
||||
for key: String in _loading_resources.keys():
|
||||
match ResourceLoader.load_threaded_get_status(key):
|
||||
ResourceLoader.THREAD_LOAD_LOADED:
|
||||
_loading_resources[key].resolve(ResourceLoader.load_threaded_get(key))
|
||||
_loading_resources.erase(key)
|
||||
ResourceLoader.THREAD_LOAD_FAILED:
|
||||
assert(false, "Failed loading resource: " + key)
|
||||
ResourceLoader.THREAD_LOAD_INVALID_RESOURCE:
|
||||
assert(false, "Can't load invalid resource: " + key)
|
||||
_:
|
||||
# Continue loading
|
||||
pass
|
||||
|
||||
if not _loading_resources:
|
||||
loading_screen.visible = false
|
||||
|
@ -1,8 +1,236 @@
|
||||
[gd_scene load_steps=3 format=3 uid="uid://qpq2cm1hgeha"]
|
||||
[gd_scene load_steps=14 format=3 uid="uid://qpq2cm1hgeha"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://dxl25lkyped4" path="res://src/game/game.gd" id="1_qnjlk"]
|
||||
[ext_resource type="PackedScene" uid="uid://884jqafhtrv0" path="res://src/world/world.tscn" id="2_s6lek"]
|
||||
[ext_resource type="FontFile" uid="uid://oq8ue2qrfijg" path="res://assets/fonts/Silkscreen/Silkscreen-Regular.ttf" id="2_s6lek"]
|
||||
[ext_resource type="Script" uid="uid://ctf1if4ly6nun" path="res://src/game/loading_screen.gd" id="3_kgj8g"]
|
||||
|
||||
[sub_resource type="Theme" id="Theme_s6lek"]
|
||||
Label/colors/font_color = Color(0.137255, 0.984314, 0.34902, 1)
|
||||
Label/constants/outline_size = 16
|
||||
Label/font_sizes/font_size = 32
|
||||
Label/fonts/font = ExtResource("2_s6lek")
|
||||
|
||||
[sub_resource type="Animation" id="Animation_s6lek"]
|
||||
resource_name = "initial_display"
|
||||
step = 0.05
|
||||
tracks/0/type = "value"
|
||||
tracks/0/imported = false
|
||||
tracks/0/enabled = true
|
||||
tracks/0/path = NodePath("MarginContainer/HBoxContainer/Loading:visible_ratio")
|
||||
tracks/0/interp = 1
|
||||
tracks/0/loop_wrap = true
|
||||
tracks/0/keys = {
|
||||
"times": PackedFloat32Array(0, 0.8, 1),
|
||||
"transitions": PackedFloat32Array(1, 1, 1),
|
||||
"update": 0,
|
||||
"values": [0.0, 0.0, 1.0]
|
||||
}
|
||||
tracks/1/type = "value"
|
||||
tracks/1/imported = false
|
||||
tracks/1/enabled = true
|
||||
tracks/1/path = NodePath("MarginContainer/HBoxContainer/Ellipsis:visible_characters")
|
||||
tracks/1/interp = 1
|
||||
tracks/1/loop_wrap = true
|
||||
tracks/1/keys = {
|
||||
"times": PackedFloat32Array(0),
|
||||
"transitions": PackedFloat32Array(1),
|
||||
"update": 1,
|
||||
"values": [0]
|
||||
}
|
||||
tracks/2/type = "value"
|
||||
tracks/2/imported = false
|
||||
tracks/2/enabled = true
|
||||
tracks/2/path = NodePath("MarginContainer/HBoxContainer/Blinker:visible")
|
||||
tracks/2/interp = 1
|
||||
tracks/2/loop_wrap = true
|
||||
tracks/2/keys = {
|
||||
"times": PackedFloat32Array(0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8),
|
||||
"transitions": PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1),
|
||||
"update": 1,
|
||||
"values": [false, true, false, true, false, true, false, true, false]
|
||||
}
|
||||
|
||||
[sub_resource type="Animation" id="Animation_kgj8g"]
|
||||
length = 0.001
|
||||
tracks/0/type = "value"
|
||||
tracks/0/imported = false
|
||||
tracks/0/enabled = true
|
||||
tracks/0/path = NodePath("MarginContainer/HBoxContainer/Loading:visible_ratio")
|
||||
tracks/0/interp = 1
|
||||
tracks/0/loop_wrap = true
|
||||
tracks/0/keys = {
|
||||
"times": PackedFloat32Array(0),
|
||||
"transitions": PackedFloat32Array(1),
|
||||
"update": 0,
|
||||
"values": [0.0]
|
||||
}
|
||||
tracks/1/type = "value"
|
||||
tracks/1/imported = false
|
||||
tracks/1/enabled = true
|
||||
tracks/1/path = NodePath("MarginContainer/HBoxContainer/Ellipsis:visible_characters")
|
||||
tracks/1/interp = 1
|
||||
tracks/1/loop_wrap = true
|
||||
tracks/1/keys = {
|
||||
"times": PackedFloat32Array(0),
|
||||
"transitions": PackedFloat32Array(1),
|
||||
"update": 1,
|
||||
"values": [0]
|
||||
}
|
||||
tracks/2/type = "value"
|
||||
tracks/2/imported = false
|
||||
tracks/2/enabled = true
|
||||
tracks/2/path = NodePath("MarginContainer/HBoxContainer/Blinker:visible")
|
||||
tracks/2/interp = 1
|
||||
tracks/2/loop_wrap = true
|
||||
tracks/2/keys = {
|
||||
"times": PackedFloat32Array(0),
|
||||
"transitions": PackedFloat32Array(1),
|
||||
"update": 1,
|
||||
"values": [false]
|
||||
}
|
||||
|
||||
[sub_resource type="Animation" id="Animation_l80un"]
|
||||
resource_name = "ellipsis_loop"
|
||||
length = 1.2
|
||||
loop_mode = 1
|
||||
step = 0.05
|
||||
tracks/0/type = "value"
|
||||
tracks/0/imported = false
|
||||
tracks/0/enabled = true
|
||||
tracks/0/path = NodePath("MarginContainer/HBoxContainer/Loading:visible_ratio")
|
||||
tracks/0/interp = 1
|
||||
tracks/0/loop_wrap = true
|
||||
tracks/0/keys = {
|
||||
"times": PackedFloat32Array(0),
|
||||
"transitions": PackedFloat32Array(1),
|
||||
"update": 0,
|
||||
"values": [1.0]
|
||||
}
|
||||
tracks/1/type = "value"
|
||||
tracks/1/imported = false
|
||||
tracks/1/enabled = true
|
||||
tracks/1/path = NodePath("MarginContainer/HBoxContainer/Blinker:visible")
|
||||
tracks/1/interp = 1
|
||||
tracks/1/loop_wrap = true
|
||||
tracks/1/keys = {
|
||||
"times": PackedFloat32Array(0),
|
||||
"transitions": PackedFloat32Array(1),
|
||||
"update": 1,
|
||||
"values": [false]
|
||||
}
|
||||
tracks/2/type = "value"
|
||||
tracks/2/imported = false
|
||||
tracks/2/enabled = true
|
||||
tracks/2/path = NodePath("MarginContainer/HBoxContainer/Ellipsis:visible_characters")
|
||||
tracks/2/interp = 1
|
||||
tracks/2/loop_wrap = true
|
||||
tracks/2/keys = {
|
||||
"times": PackedFloat32Array(0, 0.3, 0.6, 0.9),
|
||||
"transitions": PackedFloat32Array(1, 1, 1, 1),
|
||||
"update": 1,
|
||||
"values": [0, 1, 2, 3]
|
||||
}
|
||||
|
||||
[sub_resource type="AnimationLibrary" id="AnimationLibrary_kgj8g"]
|
||||
_data = {
|
||||
&"RESET": SubResource("Animation_kgj8g"),
|
||||
&"ellipsis_loop": SubResource("Animation_l80un"),
|
||||
&"initial_display": SubResource("Animation_s6lek")
|
||||
}
|
||||
|
||||
[sub_resource type="AnimationNodeAnimation" id="AnimationNodeAnimation_s6lek"]
|
||||
animation = &"ellipsis_loop"
|
||||
|
||||
[sub_resource type="AnimationNodeAnimation" id="AnimationNodeAnimation_kgj8g"]
|
||||
animation = &"initial_display"
|
||||
|
||||
[sub_resource type="AnimationNodeStateMachineTransition" id="AnimationNodeStateMachineTransition_l80un"]
|
||||
advance_mode = 2
|
||||
|
||||
[sub_resource type="AnimationNodeStateMachineTransition" id="AnimationNodeStateMachineTransition_xptat"]
|
||||
switch_mode = 2
|
||||
advance_mode = 2
|
||||
|
||||
[sub_resource type="AnimationNodeStateMachine" id="AnimationNodeStateMachine_l80un"]
|
||||
states/ellipsis_loop/node = SubResource("AnimationNodeAnimation_s6lek")
|
||||
states/ellipsis_loop/position = Vector2(630, 100)
|
||||
states/initial_display/node = SubResource("AnimationNodeAnimation_kgj8g")
|
||||
states/initial_display/position = Vector2(399, 100)
|
||||
transitions = ["Start", "initial_display", SubResource("AnimationNodeStateMachineTransition_l80un"), "initial_display", "ellipsis_loop", SubResource("AnimationNodeStateMachineTransition_xptat")]
|
||||
|
||||
[node name="Game" type="Node"]
|
||||
script = ExtResource("1_qnjlk")
|
||||
world_scene = ExtResource("2_s6lek")
|
||||
start_scene = "uid://884jqafhtrv0"
|
||||
|
||||
[node name="Content" type="Node" parent="."]
|
||||
unique_name_in_owner = true
|
||||
|
||||
[node name="LoadingScreen" type="Control" parent="."]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
theme = SubResource("Theme_s6lek")
|
||||
script = ExtResource("3_kgj8g")
|
||||
|
||||
[node name="ColorRect" type="ColorRect" parent="LoadingScreen"]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
color = Color(0.0196078, 0.0431373, 0.0627451, 1)
|
||||
|
||||
[node name="MarginContainer" type="MarginContainer" parent="LoadingScreen"]
|
||||
layout_mode = 1
|
||||
anchors_preset = 2
|
||||
anchor_top = 1.0
|
||||
anchor_bottom = 1.0
|
||||
offset_top = -40.0
|
||||
offset_right = 40.0
|
||||
grow_vertical = 0
|
||||
theme_override_constants/margin_left = 32
|
||||
theme_override_constants/margin_bottom = 32
|
||||
|
||||
[node name="HBoxContainer" type="HBoxContainer" parent="LoadingScreen/MarginContainer"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Prompt" type="Label" parent="LoadingScreen/MarginContainer/HBoxContainer"]
|
||||
layout_mode = 2
|
||||
text = ">"
|
||||
|
||||
[node name="Blinker" type="Label" parent="LoadingScreen/MarginContainer/HBoxContainer"]
|
||||
visible = false
|
||||
layout_mode = 2
|
||||
text = "_"
|
||||
|
||||
[node name="Loading" type="Label" parent="LoadingScreen/MarginContainer/HBoxContainer"]
|
||||
layout_mode = 2
|
||||
text = "UI_LOADING"
|
||||
visible_characters = 0
|
||||
visible_characters_behavior = 1
|
||||
visible_ratio = 0.0
|
||||
|
||||
[node name="Ellipsis" type="Label" parent="LoadingScreen/MarginContainer/HBoxContainer"]
|
||||
layout_mode = 2
|
||||
text = "..."
|
||||
visible_characters = 0
|
||||
visible_characters_behavior = 1
|
||||
visible_ratio = 0.0
|
||||
|
||||
[node name="LoaderAnimation" type="AnimationPlayer" parent="LoadingScreen"]
|
||||
libraries = {
|
||||
&"": SubResource("AnimationLibrary_kgj8g")
|
||||
}
|
||||
|
||||
[node name="LoaderTree" type="AnimationTree" parent="LoadingScreen/LoaderAnimation"]
|
||||
unique_name_in_owner = true
|
||||
root_node = NodePath("%LoaderTree/../..")
|
||||
tree_root = SubResource("AnimationNodeStateMachine_l80un")
|
||||
anim_player = NodePath("..")
|
||||
|
||||
[connection signal="visibility_changed" from="LoadingScreen" to="LoadingScreen" method="_on_visibility_changed"]
|
||||
|
9
src/game/loading_screen.gd
Normal file
9
src/game/loading_screen.gd
Normal file
@ -0,0 +1,9 @@
|
||||
extends Control
|
||||
|
||||
@onready var loader_tree: AnimationTree = %LoaderTree
|
||||
@onready var state_machine: AnimationNodeStateMachinePlayback = loader_tree["parameters/playback"]
|
||||
|
||||
|
||||
func _on_visibility_changed() -> void:
|
||||
if state_machine:
|
||||
state_machine.start("initial_display", true)
|
1
src/game/loading_screen.gd.uid
Normal file
1
src/game/loading_screen.gd.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://ctf1if4ly6nun
|
@ -4,14 +4,12 @@ const PITCH_LIMIT := deg_to_rad(85.0)
|
||||
const FOCUS_SENSITIVITY := 0.2
|
||||
const FOCUS_ACCELERATION := 8
|
||||
|
||||
@onready var player: Player = owner
|
||||
|
||||
@onready var _target := Vector2(rotation.x, rotation.y)
|
||||
|
||||
|
||||
func _unhandled_input(event: InputEvent) -> void:
|
||||
if event is InputEventMouseMotion:
|
||||
if Input.get_mouse_mode() == Input.MOUSE_MODE_CAPTURED and player.look_enabled:
|
||||
if Input.get_mouse_mode() == Input.MOUSE_MODE_CAPTURED and Player.instance.look_enabled:
|
||||
camera_motion((event as InputEventMouseMotion).relative)
|
||||
elif event is InputEventMouseButton:
|
||||
Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
|
||||
@ -21,7 +19,7 @@ func camera_motion(motion: Vector2) -> void:
|
||||
var x_sensitivity: float = Game.settings.mouse_sensitivity_x
|
||||
var y_sensitivity: float = Game.settings.mouse_sensitivity_y
|
||||
var invert_pitch: bool = Game.settings.invert_pitch
|
||||
if player.firing:
|
||||
if Player.instance.firing:
|
||||
# Focus movement when firing
|
||||
# Game mechanic, should not be user-configurable.
|
||||
x_sensitivity = FOCUS_SENSITIVITY
|
||||
@ -42,7 +40,7 @@ func reset_pitch(tween_duration: float) -> void:
|
||||
|
||||
func _physics_process(delta: float) -> void:
|
||||
var mouse_accel: float = Game.settings.mouse_acceleration
|
||||
if player.firing:
|
||||
if Player.instance.firing:
|
||||
mouse_accel = FOCUS_ACCELERATION
|
||||
|
||||
var weight := 1 - exp(-mouse_accel * delta)
|
||||
|
@ -6,7 +6,6 @@ const VELOCITY_FACTOR := 2.0
|
||||
|
||||
var _on_right_foot := false
|
||||
|
||||
@onready var player: Player = owner
|
||||
|
||||
@onready var left_foot: FootController = %LeftFoot
|
||||
@onready var right_foot: FootController = %RightFoot
|
||||
@ -16,12 +15,12 @@ var _on_right_foot := false
|
||||
|
||||
|
||||
func play_footstep() -> void:
|
||||
if player.sneaking:
|
||||
if Player.instance.sneaking:
|
||||
return
|
||||
|
||||
var foot := right_foot if _on_right_foot else left_foot
|
||||
|
||||
var relative_speed := player.velocity.length() - MUTE_VELOCITY
|
||||
var relative_speed := Player.instance.velocity.length() - MUTE_VELOCITY
|
||||
if relative_speed < 0:
|
||||
return
|
||||
|
||||
|
@ -16,13 +16,11 @@ var timescale: float:
|
||||
get:
|
||||
return self["parameters/timescale/scale"]
|
||||
|
||||
@onready var player: Player = owner
|
||||
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
var speed := player.velocity.length()
|
||||
var speed := Player.instance.velocity.length()
|
||||
var weight := 1 - exp(-BLEND_ACCELERATION * delta)
|
||||
if player.is_on_floor():
|
||||
if Player.instance.is_on_floor():
|
||||
var timescale_target := speed * VELOCITY_TIMESCALE_FACTOR
|
||||
timescale = lerpf(timescale, timescale_target, weight)
|
||||
else:
|
||||
@ -30,7 +28,7 @@ func _process(delta: float) -> void:
|
||||
|
||||
if Game.settings.enable_head_bob:
|
||||
var blend_target := 0.0
|
||||
if player.is_on_floor():
|
||||
if Player.instance.is_on_floor():
|
||||
blend_target = speed * VELOCITY_BLEND_FACTOR
|
||||
|
||||
blend = lerpf(blend, blend_target, weight)
|
||||
|
@ -183,6 +183,12 @@ func _signal_death() -> void:
|
||||
|
||||
|
||||
func _physics_process(delta: float) -> void:
|
||||
# REMOVEME
|
||||
if Input.is_action_just_pressed("ui_page_down"):
|
||||
get_grabbed()
|
||||
if Input.is_action_just_pressed("ui_page_up"):
|
||||
World.instance.save_progress()
|
||||
|
||||
# Will be null if no valid interactor is selected.
|
||||
var interactive: Interactive = interact_ray.get_collider() as Interactive
|
||||
hud.select_interactive(interactive)
|
||||
|
@ -344,10 +344,10 @@ anchor_left = 0.5
|
||||
anchor_top = 0.5
|
||||
anchor_right = 0.5
|
||||
anchor_bottom = 0.5
|
||||
offset_left = -401.486
|
||||
offset_top = -302.289
|
||||
offset_right = -401.486
|
||||
offset_bottom = -302.289
|
||||
offset_left = -402.339
|
||||
offset_top = -299.253
|
||||
offset_right = -402.339
|
||||
offset_bottom = -299.253
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
script = ExtResource("4_ud8na")
|
||||
|
@ -2,6 +2,7 @@ class_name World extends Node
|
||||
## Access and flow control for the game world.
|
||||
|
||||
@export var pause_enabled := true
|
||||
@export var save_path := "user://saved_game.tscn"
|
||||
|
||||
@export var manager: WorldManager
|
||||
@export var spook_manager: SpookManager
|
||||
@ -61,4 +62,20 @@ func on_player_death() -> void:
|
||||
func on_game_over() -> void:
|
||||
# TODO: reload from last checkpoint
|
||||
# in the mean time, just reload the level
|
||||
load_level(current_level)
|
||||
Game.instance.queue_scene(save_path)
|
||||
|
||||
|
||||
func _reown_tree(node: Node) -> void:
|
||||
for c: Node in node.get_children():
|
||||
c.set_owner(self)
|
||||
_reown_tree(c)
|
||||
|
||||
|
||||
func save_progress() -> void:
|
||||
# No way this works, right?
|
||||
print("Preparing world for save...")
|
||||
var save := PackedScene.new()
|
||||
_reown_tree(self)
|
||||
save.pack(self)
|
||||
print("Writing save to ", save_path)
|
||||
ResourceSaver.save(save, save_path)
|
||||
|
Loading…
x
Reference in New Issue
Block a user