Compare commits

..

4 Commits

Author SHA1 Message Date
a4100e3026 Changes to the player
- Made the character inherit from StairsCharacter3D (Plugin)
- Made the players head smoothly adjust when stepping.
- Reintroduced the previously broken headbobbing.
- Adjusted collisions.
2025-09-21 23:48:46 +02:00
31363b1d52 Moved some files (+Added VibrationComponent) 2025-09-21 23:46:52 +02:00
8a5373c340 Improved the level loading system 2025-09-21 23:45:58 +02:00
b3b73433a2 Added a loading text effect 2025-09-21 23:43:16 +02:00
28 changed files with 496 additions and 55 deletions

View File

@ -62,6 +62,20 @@ folder_colors={
[input]
ui_accept={
"deadzone": 0.5,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194309,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194310,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":32,"physical_keycode":0,"key_label":0,"unicode":32,"location":0,"echo":false,"script":null)
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":0,"pressure":0.0,"pressed":true,"script":null)
]
}
ui_cancel={
"deadzone": 0.5,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194305,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":1,"pressure":0.0,"pressed":true,"script":null)
]
}
move_forward={
"deadzone": 0.2,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":87,"key_label":0,"unicode":119,"location":0,"echo":false,"script":null)

View File

@ -1,6 +1,8 @@
class_name InteractiveLoader
extends Node
@export var auto_free: bool = false
var load_progress: Array = []
@ -26,4 +28,7 @@ func load_threaded(path: String) -> Resource:
else:
SPrint.print_msg("[Interactive Loader] is no longer in the scene tree. We can't wait for a process frame anymore, resulting in a freeze.", -1.0, SPrint.WARNING)
if auto_free:
queue_free()
return ResourceLoader.load_threaded_get(path)

View File

@ -10,6 +10,7 @@ func _enter_tree() -> void:
for spawnpoint: PlayerSpawnPoint in loaded_if_spawnpoint:
if GameGlobals.spawn_id == spawnpoint.spawn_id:
var was_threaded: bool = level_loader.load_threaded
level_loader.load_threaded = false
load_level()
level_loader.load_threaded = was_threaded
return
@ -34,14 +35,14 @@ func unload_level() -> void:
if keep_loaded_duration > 0.0:
var duration: float = keep_loaded_duration
while is_inside_tree() and duration > 0.0:
while duration > 0.0:
if has_overlapping_bodies():
return
duration -= get_process_delta_time() * float(not get_tree().paused)
await get_tree().process_frame
if not is_instance_valid(get_tree()):
if not is_inside_tree() or not is_instance_valid(get_tree()):
return
level_loader.unload_level()

View File

@ -5,22 +5,36 @@ extends Marker3D
signal level_loaded
signal level_unloaded
static var initial_level_reference: PackedScene
@export_file("*.tscn", "*.scn") var scene_path: String
@export var load_threaded: bool = true
@export_tool_button("Load Level", "Slot") var editor_load: Callable = load_level
@export_tool_button("Unload Level", "Clear") var editor_unload: Callable = unload_level
@export_group("Level ID")
@export var override_level_id: bool = true
@export var level_id: StringName = &""
var level: Node
var is_loading: bool = false
func load_level() -> void:
if is_loading:
return
if not Engine.is_editor_hint() and override_level_id:
GameGlobals.set_level(level_id)
if is_instance_valid(level):
if level.scene_file_path == scene_path:
if _get_uid(level.scene_file_path) == _get_uid(scene_path):
push_warning("Level already loaded")
return
unload_level()
is_loading = true
var scene: PackedScene
if load_threaded:
@ -33,6 +47,8 @@ func load_level() -> void:
level = scene.instantiate()
add_child(level)
is_loading = false
initial_level_reference = null
level_loaded.emit()
@ -45,11 +61,21 @@ func unload_level() -> void:
if child.owner == null:
child.queue_free()
initial_level_reference = null
level_unloaded.emit()
func _load_level_threaded() -> PackedScene:
var interactive_loader := InteractiveLoader.new()
interactive_loader.auto_free = true
add_child(interactive_loader)
return await interactive_loader.load_threaded(scene_path)
var resource: Resource = await interactive_loader.load_threaded(scene_path)
#interactive_loader.queue_free()
return resource
func _get_uid(path: String) -> int:
var uid: int = ResourceUID.text_to_id(ResourceUID.path_to_uid(path))
#print("UID: ", uid, " Path: ", path)
return uid

View File

@ -0,0 +1,31 @@
class_name LevelThresholdArea
extends Area3D
static var is_level_loading: bool = false
@export var level_loaders: Array[LevelLoader] = []
# Called when the node enters the scene tree for the first time.
func _ready() -> void:
body_entered.connect(prevent_if_any_loading.unbind(1))
func prevent_if_any_loading() -> bool:
for loader: LevelLoader in level_loaders:
if not loader.is_loading:
continue
get_tree().paused = true
is_level_loading = true
SceneFader.set_loading_hints_visible(true)
while loader.is_loading:
await get_tree().process_frame
SceneFader.set_loading_hints_visible(false)
get_tree().paused = false
is_level_loading = false
return true
return false

View File

@ -0,0 +1 @@
uid://dhrqjudaignuc

View File

@ -0,0 +1,139 @@
@tool
class_name VibrationComponent
extends Node
## A small helper node to quickly perform controller vibrations.
## If [code]false[/code], calling [method vibrate] will not start a controller vibration.
@export var enabled: bool = true:
get = is_enabled,
set = set_enabled
## [param duration] is the duration of the effect in seconds
## (a duration of [code]0[/code] will try to play the vibration indefinitely).[br]
## The vibration can be stopped early by calling [method stop_vibration].
@export var duration: float = 0.15:
get = get_duration,
set = set_duration
## The device index to perform the vibration on.
@export var device: int = 0:
get = get_device,
set = set_device
## The delay the vibration by this amount.
@export var delay: float = 0.0
@warning_ignore_start("unused_private_class_variable")
@export_tool_button("Test Vibration", "InputEventJoypadMotion")
var _editor_test_vibration: Callable = vibrate
@export_tool_button("Stop Vibration", "MissingNode")
var _editor_stop_test_vibration: Callable = stop_vibration
@warning_ignore_restore("unused_private_class_variable")
@export_group("Magnitude")
## [param weak_magnitude] is the strength of the weak motor
## (between [code]0[/code] and [code]1[/code]).
@export_range(0.0, 1.0) var weak_magnitude: float = 0.1:
get = get_weak_magnitude,
set = set_weak_magnitude
## [param strong_magnitude] is the strength of the strong motor
## (between [code]0[/code] and [code]1[/code]).
@export_range(0.0, 1.0) var strong_magnitude: float = 0.2:
get = get_strong_magnitude,
set = set_strong_magnitude
## Multiplies [member weak_magnitude] and [member strong_magnitude] by this amount.
@export var magnitude_multiplier: float = 1.0:
get = get_magnitude_multiplier,
set = set_magnitude_multiplier
var animated_weak_magnitude: float = 0.0:
set(value):
animated_weak_magnitude = clampf(value, 0.0, 1.0)
var animated_strong_magnitude: float = 0.0:
set(value):
animated_strong_magnitude = clampf(value, 0.0, 1.0)
func _process(delta: float) -> void:
if not is_zero_approx(animated_weak_magnitude + animated_strong_magnitude):
Input.start_joy_vibration.call_deferred(
device, animated_weak_magnitude, animated_strong_magnitude, delta
)
animated_weak_magnitude = 0.0
animated_strong_magnitude = 0.0
## Performs the controller vibration based on given parameters of the component.[br]
## [b]Info[/b]: The [member enabled] parameter must be [code]true[/code].
func vibrate() -> void:
# Don't vibrate if the user has it turned off.
if not Engine.is_editor_hint():
if (
not InputManager.using_controller
#or not ProjectSettings.get_setting("game/input/controller_vibrations", true)
):
return
if enabled:
if delay > 0.0:
await get_tree().create_timer(delay).timeout
Input.start_joy_vibration(
device,
clampf(weak_magnitude * magnitude_multiplier, 0.0, 1.0),
clampf(strong_magnitude * magnitude_multiplier, 0.0, 1.0),
duration
)
## Stops the vibration of the joypad, based on the [member device] parameter,
## started with [method vibrate].
func stop_vibration() -> void:
Input.stop_joy_vibration(device)
func set_enabled(value: bool) -> void:
enabled = value
func is_enabled() -> bool:
return enabled
func set_device(device_id: int) -> void:
device = device_id
func get_device() -> int:
return device
func set_duration(length: float) -> void:
duration = length
func get_duration() -> float:
return duration
func set_magnitude_multiplier(multiplier_value: float) -> void:
magnitude_multiplier = multiplier_value
func get_magnitude_multiplier() -> float:
return magnitude_multiplier
func set_weak_magnitude(weak_value: float) -> void:
weak_magnitude = weak_value
func get_weak_magnitude() -> float:
return weak_magnitude
func set_strong_magnitude(strong_value: float) -> void:
strong_magnitude = strong_value
func get_strong_magnitude() -> float:
return strong_magnitude

View File

@ -0,0 +1 @@
uid://bbwtct3hoxwws

View File

@ -3,6 +3,8 @@ extends Node
const PLAYER: PackedScene = preload(GameGlobals.PLAYER_PATH)
@export_group("Debug", "debug_")
@export_custom(PROPERTY_HINT_GROUP_ENABLE, "checkbox_only") var debug_enabled: bool = false
@export var debug_spawn_point: StringName = &""
@export_range(-1, 0, 1, "or_greater") var debug_chapter_idx: int = -1
@ -10,14 +12,17 @@ var player_character: PlayerCharacter
func _init() -> void:
if OS.has_feature("editor") and not debug_spawn_point.is_empty():
GameGlobals.game = self
func _enter_tree() -> void:
if OS.has_feature("editor") and debug_enabled:
if not debug_spawn_point.is_empty():
GameGlobals.set_spawn_id(debug_spawn_point)
if debug_chapter_idx >= 0:
GameGlobals.set_chapter_index(debug_chapter_idx)
GameGlobals.game = self
# Called when the node enters the scene tree for the first time.
func _ready() -> void:

View File

@ -1,5 +1,5 @@
class_name Character3D
extends CharacterBody3D
extends StairsCharacter3D
@export var movement: CharacterMovement
@export var speed_scale: float = 1.0
@ -12,11 +12,11 @@ var air_control: float = 1.0
var freeze_air_control: bool = false
var last_platform_velocity := Vector3.ZERO
var _was_on_floor: bool = false
var _previous_velocity: Vector3
var previous_velocity: Vector3
func move(direction: Vector3, delta: float) -> void:
direction *= (speed * speed_scale)
direction *= speed * speed_scale
var on_floor: bool = is_on_floor()
@ -56,7 +56,7 @@ func move(direction: Vector3, delta: float) -> void:
velocity = velocity.move_toward(direction, speed_change * delta)
await get_tree().physics_frame
_previous_velocity = velocity
previous_velocity = velocity
#DebugDraw3D.draw_arrow(global_position, global_position + velocity, Color.BLUE, 0.5, true)
#SPrint.print_msg("%s Velocity: %s" % [self , velocity], 0.02)

View File

@ -1,4 +1,4 @@
class_name GameCamera3D
class_name PlayerHead
extends Node3D
const ACTION_CAMERA_UP: StringName = &"camera_up"
@ -38,13 +38,24 @@ const CONTROLLER_INVERT_Y_PATH: StringName = &"game/input/camera_controller_inve
@export var headbob_frequency: float = 0.5
@export var headbob_multiplier: float = 1.0
@export var headbob_character: Character3D
@export var headbob_target: Node3D
@export_group("Step Smoothing", "step_")
@export var step_smoothing_target: Node3D
@export var step_speed: float = 8.0
var rotational_direction: Vector2
var rotational_velocity: Vector2
var input_event: InputEvent
var using_controller: bool = false
# Headbobbing
var headbob_time: float = 0.0
var headbob_enabled: bool = true
# Stair smooting
var stair_camera_offset_height: float = 0.0
var _previous_position: Vector3
var _stair_camera_target_height: float = 0.0
var _stair_camera_step_smoothing: bool = false
func _ready() -> void:
@ -62,18 +73,23 @@ func _ready() -> void:
ProjectSettings.settings_changed.connect(_update_settings)
_update_settings.call()
_setup_step_smoothing()
func _physics_process(delta: float) -> void:
func _process(delta: float) -> void:
if not GameGlobals.in_cutscene:
_process_input(delta)
_perform_head_bob(delta)
_process_step_smoothing(delta)
func _unhandled_input(event: InputEvent) -> void:
if event is InputEventMouseMotion or event is InputEventJoypadMotion:
input_event = event
#region Mouse/Controller rotation
func apply_rotation(rot: Vector2) -> void:
rotate_y(-rot.x)
rotation.x = rotation.x - rot.y
@ -156,12 +172,14 @@ func _process_controller(delta: float) -> void:
func _lerp_rotational_velocity(sensitivity: float, friction: float, delta: float) -> Vector2:
return rotational_velocity.lerp(rotational_direction * sensitivity, friction * delta)
#endregion
#region Headbobbing
func _perform_head_bob(delta: float) -> void:
if not headbob_enabled:
camera.position = Vector3.ZERO
camera.rotation.z = 0.0
headbob_target.position = Vector3.ZERO
headbob_target.rotation.z = 0.0
headbob_time = 0.0
return
@ -172,7 +190,38 @@ func _perform_head_bob(delta: float) -> void:
headbob_time += delta * headbob_speed * headbob_multiplier
headbob_time = fposmod(headbob_time, TAU)
camera.position.y = sin(headbob_time * headbob_frequency) * headbob_range
camera.position.x = cos(headbob_time * headbob_frequency / 2) * headbob_range
camera.rotation.z = deg_to_rad(lerp(camera.position.x, camera.position.y, 0.5)) * TAU
SPrint.print_msgf(str("Head Z-Rotation: ", camera.rotation.z), true)
headbob_target.position.y = sin(headbob_time * headbob_frequency) * headbob_range
headbob_target.position.x = cos(headbob_time * headbob_frequency / 2) * headbob_range
headbob_target.rotation.z = deg_to_rad(lerp(headbob_target.position.x, headbob_target.position.y, 0.5)) * TAU
SPrint.print_msgf(str("Head Z-Rotation: ", headbob_target.rotation.z), true)
#endregion
#region Step Smoothing
func smooth_step(height_change: float) -> void:
_stair_camera_target_height -= height_change
_stair_camera_step_smoothing = true
func _setup_step_smoothing() -> void:
headbob_character.on_stair_step.connect(_on_stair_step)
stair_camera_offset_height = step_smoothing_target.position.y
func _on_stair_step() -> void:
smooth_step(global_position.y - _previous_position.y)
func _process_step_smoothing(delta: float) -> void:
if _stair_camera_step_smoothing and is_instance_valid(step_smoothing_target):
_stair_camera_target_height = lerp(_stair_camera_target_height, 0.0, step_speed * delta)
if absf(_stair_camera_target_height) < 0.0025:
_stair_camera_target_height = 0.0
_stair_camera_step_smoothing = false
var target_pos := Vector3.UP * (stair_camera_offset_height + _stair_camera_target_height)
step_smoothing_target.position = target_pos.rotated(Vector3.LEFT, rotation.x)
_previous_position = global_position
#endregion

View File

@ -25,9 +25,10 @@ var has_control: bool = true
var is_crouching: bool = false
var crouch_speed_affection: float = 0.0
@onready var head: GameCamera3D = $Head
@onready var collision_shape: CollisionShape3D = $CollisionShape3D
@onready var head: PlayerHead = $Head
@onready var collision_shape: CollisionShape3D = $CylinderCollider
@onready var standup_checker: Area3D = $StandupChecker
@onready var stair_stepper: StairStepper = $StairStepper
func _ready() -> void:
@ -51,12 +52,15 @@ func _physics_process(delta: float) -> void:
if not is_on_floor():
apply_gravity(delta)
elif not _was_on_floor:
if _previous_velocity.y <= -2.0:
if previous_velocity.y <= -2.0:
head.camera.shake(0.025, 0.175)
if not has_control:
move(Vector3.ZERO, delta)
move_and_slide()
move_and_stair_step()
#if is_on_floor():
#stair_stepper.handle_step_climbing()
return
# Handle jump.
@ -78,8 +82,15 @@ func _physics_process(delta: float) -> void:
speed *= remap(crouch_speed_affection, 0.0, 1.0, 1.0, standup_speed_affection)
move(movement_direction, delta)
move_and_stair_step()
move_and_slide()
SPrint.print_msgf(
"Player Horizontal-Velocity: %s\nPlayer Vertical-Velocity: %s"
% [(velocity * Utils.VEC3_HOR).length(), velocity.y], true
)
#if is_on_floor():
#stair_stepper.handle_step_climbing()
func apply_gravity(delta: float) -> void:
@ -119,4 +130,8 @@ func handle_crouching(delta: float) -> void:
head.position.y = lerp(head.position.y, target_camera_height, weight)
crouch_speed_affection = lerp(crouch_speed_affection, float(is_crouching), weight * standup_speed_affection_speed_multiplier if not is_crouching else 1.0)
SPrint.print_msgf("Crouching Speed Affection: %s\n%s" % [crouch_speed_affection, is_crouching], true)
if crouch_speed_affection <= 0.05:
crouch_speed_affection = 0.0
SPrint.print_msgf("Crouching Speed Affection: %s\nIs Crouching: %s" % [crouch_speed_affection, is_crouching], true)

View File

@ -1,4 +1,4 @@
[gd_scene load_steps=9 format=3 uid="uid://clhy3kiceqf2o"]
[gd_scene load_steps=10 format=3 uid="uid://clhy3kiceqf2o"]
[ext_resource type="Script" uid="uid://day6rhxicaxqf" path="res://src/gameplay/characters/player/player_character.gd" id="1_hqu6r"]
[ext_resource type="Script" uid="uid://dsjlv8midt2g2" path="res://src/gameplay/characters/character_movement.gd" id="2_1ixuj"]
@ -13,14 +13,24 @@ max_turning_speed = 70.0
peak_gravity_multiplier = 0.3
metadata/_custom_type_script = "uid://dsjlv8midt2g2"
[sub_resource type="CylinderShape3D" id="CylinderShape3D_fjt7c"]
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_bdj5f"]
margin = 0.01
radius = 0.46
height = 1.8
[sub_resource type="CylinderShape3D" id="CylinderShape3D_gy1j0"]
height = 1.59
radius = 0.49
[sub_resource type="CylinderShape3D" id="CylinderShape3D_bdj5f"]
margin = 0.01
height = 1.8
radius = 0.46
[node name="PlayerCharacter" type="CharacterBody3D"]
[sub_resource type="CylinderShape3D" id="CylinderShape3D_ntkcp"]
height = 1.59
radius = 0.45
[node name="PlayerCharacter" type="StairsCharacter3D"]
collider = NodePath("CylinderCollider")
step_height_up = 0.7
step_height_down = 0.7
collision_layer = 2
script = ExtResource("1_hqu6r")
standup_speed_affection = 0.15
@ -28,30 +38,43 @@ standup_speed_affection_speed_multiplier = 0.5
movement = SubResource("Resource_vq0uu")
metadata/_custom_type_script = "uid://day6rhxicaxqf"
[node name="CollisionShape3D" type="CollisionShape3D" parent="."]
[node name="CapsuleCollider" type="CollisionShape3D" parent="."]
process_mode = 4
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.9, 0)
shape = SubResource("CylinderShape3D_fjt7c")
visible = false
shape = SubResource("CapsuleShape3D_bdj5f")
disabled = true
[node name="Head" type="Node3D" parent="." node_paths=PackedStringArray("camera", "headbob_character")]
[node name="CylinderCollider" type="CollisionShape3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.9, 0)
shape = SubResource("CylinderShape3D_bdj5f")
[node name="Head" type="Node3D" parent="." node_paths=PackedStringArray("camera", "headbob_character", "headbob_target", "step_smoothing_target")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.6, 0)
script = ExtResource("2_fjt7c")
camera = NodePath("ShakingCamera")
camera = NodePath("Headbobbing/StairStepping/ShakingCamera")
headbob_range = 0.05
headbob_frequency = 2.0
headbob_character = NodePath("..")
headbob_target = NodePath("Headbobbing")
step_smoothing_target = NodePath("Headbobbing/StairStepping")
[node name="ShakingCamera" type="Camera3D" parent="Head"]
[node name="Headbobbing" type="Node3D" parent="Head"]
[node name="StairStepping" type="Node3D" parent="Head/Headbobbing"]
[node name="ShakingCamera" type="Camera3D" parent="Head/Headbobbing/StairStepping"]
current = true
fov = 85.0
script = ExtResource("4_ci1ud")
metadata/_custom_type_script = "uid://44s0v8mukxk4"
[node name="FootstepComponent" type="Node" parent="." node_paths=PackedStringArray("character")]
script = ExtResource("4_vq0uu")
character = NodePath("..")
[node name="StandupChecker" type="Area3D" parent="."]
input_ray_pickable = false
[node name="CollisionShape3D" type="CollisionShape3D" parent="StandupChecker"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.99, 0)
shape = SubResource("CylinderShape3D_gy1j0")
shape = SubResource("CylinderShape3D_ntkcp")
[node name="FootstepComponent" type="Node" parent="."]
script = ExtResource("4_vq0uu")

View File

@ -3,15 +3,21 @@ extends Node
signal player_died(death_type: DeathTypes)
const MAIN_MENU_PATH: String = "uid://dwd8wf02j2epn"
const INITIAL_LEVEL_ID: StringName = &"OutsideArea"
const GAME_PATH: String = "uid://s7cw6ulb7kh7"
const PLAYER_PATH: String = "uid://clhy3kiceqf2o"
const CHAPTER_LIST: ChapterList = preload("uid://d2mcwotliowv7")
const LEVELS: Dictionary[StringName, String] = {
&"OutsideArea": "uid://lraild3yetsh",
&"EntranceHall": "uid://cmdy6f6kesmbj",
}
enum DeathTypes {
SAW,
}
var game: Game
var level: StringName = &""
var player: PlayerCharacter
var player_alive: bool = true
var spawn_id: StringName = &"": set = set_spawn_id
@ -26,6 +32,7 @@ func get_player() -> PlayerCharacter:
func reset_game() -> void:
player_alive = true
spawn_id = &""
level = INITIAL_LEVEL_ID
chapter_index = 0
in_cutscene = false
@ -36,6 +43,7 @@ func load_from_save() -> void:
var data: Dictionary[StringName, Variant] = SaveManager.persistent_data
spawn_id = data.get(&"spawn_id")
chapter_index = data.get(&"chapter_index")
level = data.get(&"level", &"")
func set_spawn_id(id: StringName) -> void:
@ -50,6 +58,12 @@ func set_chapter_index(index: int) -> void:
SaveManager.persistent_data[&"chapter_index"] = chapter_index
func set_level(level_id: StringName) -> void:
level = level_id
SaveManager.persistent_data[&"level"] = level
func kill_player(death_type: DeathTypes) -> void:
if not player_alive:
return

View File

@ -8,6 +8,7 @@ var persistent_data: Dictionary[StringName, Variant] = {
&"spawn_id": &"",
&"sequence_index": 0,
&"chapter_index": 0,
&"level": &"",
&"level_data": {
}

View File

@ -12,24 +12,33 @@ var is_loading: bool = false
@onready var color_rect: ColorRect = %ColorRect
@onready var loading_hint_animations: AnimationPlayer = %LoadingHintAnimations
@onready var loading_hint: TextureRect = %LoadingHint
@onready var loading_label: RichTextLabel = $LoadingLabel
func _ready() -> void:
loading_label.hide()
color_rect.hide()
color_rect.color.a = 0.0
func load_to_path(path: String) -> void:
func load_to_path(path: String, initial_level: String = "") -> void:
if is_loading:
return
is_loading = true
await fade_in()
await get_tree().create_timer(1.0).timeout
#await get_tree().create_timer(1.0).timeout
# Load the initial level into memory first, so it can be retrived by other things later.
if not initial_level.is_empty():
LevelLoader.initial_level_reference = await interactive_loader.load_threaded(initial_level)
print("Initial Level Reference Loaded: ", LevelLoader.initial_level_reference)
var scene: PackedScene = await interactive_loader.load_threaded(path)
print("Scene Loaded: ", scene)
assert(is_instance_valid(scene), "Scene is invalid.")
get_tree().change_scene_to_packed(scene)
@ -49,13 +58,11 @@ func fade_in() -> void:
is_fading = false
loading_hint_animations.play(_ANIM_LOADING)
loading_hint.show()
set_loading_hints_visible(true)
func fade_out() -> void:
loading_hint_animations.stop()
loading_hint.hide()
set_loading_hints_visible(false)
is_fading = true
@ -65,3 +72,14 @@ func fade_out() -> void:
color_rect.hide()
is_fading = false
func set_loading_hints_visible(value: bool) -> void:
loading_hint.visible = value
loading_label.visible = value
if value:
loading_label.set_text(loading_label.text) # Restart the effect.
loading_hint_animations.play(_ANIM_LOADING)
else:
loading_hint_animations.stop()

View File

@ -1,8 +1,14 @@
[gd_scene load_steps=7 format=3 uid="uid://dis4efdm5s2fc"]
[gd_scene load_steps=9 format=3 uid="uid://dis4efdm5s2fc"]
[ext_resource type="Script" uid="uid://cynllcoh2smgv" path="res://src/globals/autoloads/scene_fader/scene_fader.gd" id="1_7tt87"]
[ext_resource type="Script" uid="uid://d0k03wk1s7cw0" path="res://src/core/interactive_loader.gd" id="2_dwqb8"]
[ext_resource type="Texture2D" uid="uid://dhw8y2oqxvgwu" path="res://godot_icon.svg" id="3_dwqb8"]
[ext_resource type="Script" uid="uid://41co2svrlbkc" path="res://src/ui/rich_effects/loading_text_effect.gd" id="3_s8mqo"]
[sub_resource type="RichTextEffect" id="RichTextEffect_tvon4"]
resource_name = "RichTextLoading"
script = ExtResource("3_s8mqo")
metadata/_custom_type_script = "uid://41co2svrlbkc"
[sub_resource type="Animation" id="Animation_t3447"]
length = 0.001
@ -85,6 +91,20 @@ grow_vertical = 2
mouse_filter = 2
color = Color(0, 0, 0, 1)
[node name="LoadingLabel" type="RichTextLabel" parent="."]
anchors_preset = -1
anchor_top = 0.5
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
mouse_filter = 2
bbcode_enabled = true
text = "[loading]Loading...[/loading]"
horizontal_alignment = 1
vertical_alignment = 1
custom_effects = [SubResource("RichTextEffect_tvon4")]
[node name="HintMargin" type="MarginContainer" parent="."]
anchors_preset = 15
anchor_right = 1.0

View File

@ -1,8 +1,9 @@
[gd_scene load_steps=8 format=3 uid="uid://dspysc2bld6eu"]
[gd_scene load_steps=10 format=3 uid="uid://dspysc2bld6eu"]
[ext_resource type="Script" uid="uid://dfnb036hysorj" path="res://src/core/level/level_loader.gd" id="1_kntr3"]
[ext_resource type="PackedScene" uid="uid://drr80goa61wrx" path="res://src/core/level/level_area.tscn" id="2_5mqkb"]
[ext_resource type="Script" uid="uid://3hlvt5k34xva" path="res://src/core/player_spawn_point.gd" id="3_aoi14"]
[ext_resource type="Script" uid="uid://dhrqjudaignuc" path="res://src/core/level/load_threshold_area.gd" id="3_mcc85"]
[ext_resource type="Script" uid="uid://dgsfc4i6bovwa" path="res://src/core/chapter/chapter_area.gd" id="4_sujqt"]
[sub_resource type="BoxShape3D" id="BoxShape3D_aoi14"]
@ -11,6 +12,9 @@ size = Vector3(17, 9, 36)
[sub_resource type="BoxShape3D" id="BoxShape3D_5mqkb"]
size = Vector3(31, 19, 37)
[sub_resource type="BoxShape3D" id="BoxShape3D_mcc85"]
size = Vector3(4, 3, 0.5)
[sub_resource type="BoxShape3D" id="BoxShape3D_sujqt"]
size = Vector3(4, 3, 1)
@ -21,12 +25,14 @@ size = Vector3(4, 3, 1)
[node name="OutsideArea" type="Marker3D" parent="LevelLoaders"]
script = ExtResource("1_kntr3")
scene_path = "uid://lraild3yetsh"
level_id = &"OutsideArea"
metadata/_custom_type_script = "uid://dfnb036hysorj"
[node name="EntranceHall" type="Marker3D" parent="LevelLoaders"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.3, 3.5, 33)
script = ExtResource("1_kntr3")
scene_path = "uid://cmdy6f6kesmbj"
level_id = &"EntranceHall"
metadata/_custom_type_script = "uid://dfnb036hysorj"
[node name="LevelAreas" type="Node" parent="."]
@ -49,6 +55,25 @@ transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 9, 53)
shape = SubResource("BoxShape3D_5mqkb")
debug_color = Color(1, 1, 0, 0.41960785)
[node name="LevelLoadThresholds" type="Node" parent="."]
[node name="LevelThresholdArea" type="Area3D" parent="LevelLoadThresholds" node_paths=PackedStringArray("level_loaders")]
collision_layer = 0
collision_mask = 2
script = ExtResource("3_mcc85")
level_loaders = [NodePath("../../LevelLoaders/OutsideArea"), NodePath("../../LevelLoaders/EntranceHall")]
metadata/_custom_type_script = "uid://dhrqjudaignuc"
[node name="CollisionShape" type="CollisionShape3D" parent="LevelLoadThresholds/LevelThresholdArea"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.8, 5, 36.2)
shape = SubResource("BoxShape3D_mcc85")
debug_color = Color(1, 0.5176471, 0, 0.41960785)
[node name="CollisionShape2" type="CollisionShape3D" parent="LevelLoadThresholds/LevelThresholdArea"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.8, 5, 33)
shape = SubResource("BoxShape3D_mcc85")
debug_color = Color(1, 0.5176471, 0, 0.41960785)
[node name="Spawnpoints" type="Node" parent="."]
[node name="OutsideSpawnPoint" type="Marker3D" parent="Spawnpoints"]

View File

@ -12,6 +12,7 @@ const AMBIENCE_STREAM: AudioStream = preload("uid://cyxvsy5wjxhl7")
func _ready() -> void:
continue_button.disabled = not SaveManager.save_exists()
(continue_button if not continue_button.disabled else new_game_button).grab_focus()
AudioManager.play_audio(AMBIENCE_STREAM, AudioManager.AMBIENCE)
@ -24,7 +25,7 @@ func _unhandled_input(event: InputEvent) -> void:
func load_game() -> void:
AudioManager.fade_audio(AMBIENCE_STREAM, -80.0, SceneFader.fade_in_duration, AudioManager.AMBIENCE, true)
SceneFader.load_to_path(GameGlobals.GAME_PATH)
SceneFader.load_to_path(GameGlobals.GAME_PATH, GameGlobals.LEVELS.get(GameGlobals.level, &""))
func _on_new_game_button_pressed() -> void:

View File

@ -10,6 +10,9 @@ var can_pause: bool = true
func _input(event: InputEvent) -> void:
if LevelThresholdArea.is_level_loading:
return
if event.is_action_pressed(ACTION_PAUSE):
if options_menu.is_visible_in_tree():
options_menu.close_menu()

View File

@ -0,0 +1,48 @@
@tool
class_name RichTextLoading
extends RichTextEffect
var bbcode: String = "loading"
func _init() -> void:
resource_name = "RichTextLoading"
func _process_custom_fx(char_fx: CharFXTransform) -> bool:
_process_big_wave(char_fx)
_process_small_wave(char_fx)
_process_fade(char_fx)
return true
func _process_big_wave(char_fx: CharFXTransform) -> void:
var freq: float = char_fx.env.get("freq", 1.0)
var height: float = char_fx.env.get("height", 25.0)
var char_range: float = char_fx.env.get("range", 0.15)
var curve: float = char_fx.env.get("curve", -13.0)
var time_offset := float(-char_fx.relative_index) * char_range
var sined_time: float = (sin((char_fx.elapsed_time + PI) * freq + time_offset) + 1.0) / 2.0
sined_time = ease(sined_time, curve)
var y_offset: float = sined_time * height
char_fx.offset += Vector2.DOWN * y_offset
func _process_small_wave(char_fx: CharFXTransform) -> void:
var freq: float = char_fx.env.get("freq", 4.0)
var height: float = char_fx.env.get("height", 12.5)
var char_range: float = char_fx.env.get("range", 0.5)
var curve: float = char_fx.env.get("curve", 6.5)
var time_offset := float(-char_fx.relative_index) * char_range
var sined_time: float = (sin((char_fx.elapsed_time + PI) * freq + time_offset) + 1.0) / 2.0
sined_time = ease(sined_time, curve)
var y_offset: float = sined_time * height * (1.0 - char_fx.offset.y / 25.0)
char_fx.offset += Vector2.UP * y_offset
func _process_fade(char_fx: CharFXTransform) -> void:
var alpha: float = 1.0 - char_fx.offset.y / 25.0
char_fx.color.a = alpha

View File

@ -0,0 +1 @@
uid://41co2svrlbkc