diff --git a/level/scenes/Player_Lilguy.tscn b/level/scenes/Player_Lilguy.tscn index ed36649..83220b6 100644 --- a/level/scenes/Player_Lilguy.tscn +++ b/level/scenes/Player_Lilguy.tscn @@ -133,7 +133,7 @@ _spring_arm = NodePath("SpringArm3D") [node name="SpringArm3D" type="SpringArm3D" parent="SpringArmOffset"] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 2, 0) -spring_length = 5.0 +spring_length = 10.0 [node name="Camera3D" type="Camera3D" parent="SpringArmOffset/SpringArm3D"] current = true diff --git a/level/scenes/enemies/basic_enemy.tscn b/level/scenes/enemies/basic_enemy.tscn index 4bdd449..a55017f 100644 --- a/level/scenes/enemies/basic_enemy.tscn +++ b/level/scenes/enemies/basic_enemy.tscn @@ -25,7 +25,7 @@ script = ExtResource("1_basic_enemy") move_speed = 3.5 attack_damage = 5.0 detection_range = 500.0 -max_health = 20.0 +max_health = 10.0 [node name="Mesh" type="MeshInstance3D" parent="."] transform = Transform3D(-0.29981995, -0.01038597, -0.00033419454, 0.00636219, -0.19110087, 0.23117083, -0.008215994, 0.23102501, 0.19120643, 0, 0.41641736, 0) diff --git a/level/scenes/enemy_spawner.tscn b/level/scenes/enemy_spawner.tscn index 4101694..cd7fec9 100644 --- a/level/scenes/enemy_spawner.tscn +++ b/level/scenes/enemy_spawner.tscn @@ -13,7 +13,7 @@ shading_mode = 0 script = ExtResource("1_spawner") spawn_radius = 20.0 spawn_height = 0.5 -enemies_per_wave = 3 +enemies_per_wave = 10 auto_start_next_wave = false wave_delay = 5.0 enemy_scenes = Array[PackedScene]([ExtResource("2_basic_enemy")]) diff --git a/level/scenes/health_orb.tscn b/level/scenes/health_orb.tscn new file mode 100644 index 0000000..f3aa571 --- /dev/null +++ b/level/scenes/health_orb.tscn @@ -0,0 +1,31 @@ +[gd_scene load_steps=5 format=3] + +[ext_resource type="Script" path="res://level/scripts/health_orb.gd" id="1_orb"] + +[sub_resource type="SphereMesh" id="SphereMesh_orb"] +radius = 0.35 +height = 0.7 + +[sub_resource type="StandardMaterial3D" id="Mat_orb"] +albedo_color = Color(0.2, 1, 0.35, 1) +emission_enabled = true +emission = Color(0.2, 1, 0.35, 1) +emission_energy_multiplier = 2.0 + +[sub_resource type="SphereShape3D" id="Shape_orb"] +radius = 0.8 + +[node name="HealthOrb" type="Area3D"] +script = ExtResource("1_orb") + +[node name="Mesh" type="MeshInstance3D" parent="."] +mesh = SubResource("SphereMesh_orb") +surface_material_override/0 = SubResource("Mat_orb") + +[node name="CollisionShape3D" type="CollisionShape3D" parent="."] +shape = SubResource("Shape_orb") + +[node name="Glow" type="OmniLight3D" parent="."] +light_color = Color(0.4, 1, 0.5, 1) +light_energy = 1.5 +omni_range = 3.0 diff --git a/level/scenes/player.tscn b/level/scenes/player.tscn index 757ac8b..73405a5 100644 --- a/level/scenes/player.tscn +++ b/level/scenes/player.tscn @@ -5055,7 +5055,7 @@ _spring_arm = NodePath("SpringArm3D") [node name="SpringArm3D" type="SpringArm3D" parent="SpringArmOffset"] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 2, 0) -spring_length = 5.0 +spring_length = 10.0 [node name="Camera3D" type="Camera3D" parent="SpringArmOffset/SpringArm3D"] current = true diff --git a/level/scripts/base_unit.gd b/level/scripts/base_unit.gd index dc6742f..84cce21 100644 --- a/level/scripts/base_unit.gd +++ b/level/scripts/base_unit.gd @@ -11,15 +11,29 @@ signal respawned() @export var max_health: float = 100.0 @export var can_respawn: bool = true @export var respawn_delay: float = 3.0 +@export var health_regen: float = 0.0 ## HP restored per second by passive regen (server-applied). 0 = disabled. +@export var regen_delay: float = 4.0 ## Seconds to wait after taking damage before regen resumes. + +const REGEN_TICK := 0.5 # Seconds between regen applications var current_health: float = 100.0 var is_dead: bool = false var _respawn_point: Vector3 = Vector3.ZERO +var _last_damage_time: float = -1000.0 func _ready(): current_health = max_health _respawn_point = global_position + # Server drives a passive regen tick for any unit with health_regen > 0 + if multiplayer.is_server() and health_regen > 0.0: + var regen_timer := Timer.new() + regen_timer.name = "RegenTimer" + regen_timer.wait_time = REGEN_TICK + regen_timer.autostart = true + add_child(regen_timer) + regen_timer.timeout.connect(_on_regen_tick) + func _enter_tree(): set_multiplayer_authority(str(name).to_int()) @@ -34,6 +48,9 @@ func take_damage(amount: float, attacker_id: int = -1, knockback: float = 0.0, a if is_dead: return + # Record when we were last hit so passive regen can pause briefly + _last_damage_time = Time.get_ticks_msec() / 1000.0 + # Apply blocking reduction if applicable (duck typing - check if method exists) var final_damage = amount var final_knockback = knockback @@ -68,6 +85,18 @@ func take_damage(amount: float, attacker_id: int = -1, knockback: float = 0.0, a if current_health <= 0: _die(attacker_id) +## Server-side passive regeneration tick (driven by RegenTimer) +func _on_regen_tick(): + if not multiplayer.is_server(): + return + if is_dead or health_regen <= 0.0 or current_health >= max_health: + return + # Hold off regen for a short window after taking damage + var now := Time.get_ticks_msec() / 1000.0 + if now - _last_damage_time < regen_delay: + return + heal(health_regen * REGEN_TICK) + ## Heal the unit @rpc("any_peer", "reliable") func heal(amount: float): diff --git a/level/scripts/basic_enemy.gd b/level/scripts/basic_enemy.gd index f1b2e71..a3c886b 100644 --- a/level/scripts/basic_enemy.gd +++ b/level/scripts/basic_enemy.gd @@ -13,6 +13,7 @@ class_name BasicEnemy @export var attack_damage: float = 15.0 @export var attack_knockback: float = 10.0 @export var attack_cooldown: float = 1.5 +@export var health_orb_drop_chance: float = 0.4 ## Chance (0-1) to drop a health orb on death @export_category("Attack Timing") @export var attack_startup: float = 0.3 # Wind-up before hit @export var attack_active: float = 0.4 # Hit window duration @@ -36,6 +37,9 @@ func _enter_tree(): func _ready(): super._ready() + # Wave lobsters die for good — no reviving (BaseEnemy defaults this to true) + can_respawn = false + # Find mesh, hitbox, and hurtbox _mesh = get_node_or_null("Mesh") _hitbox = get_node_or_null("HitBox") @@ -255,6 +259,34 @@ func _reset_material(): if _mesh and _original_material: _mesh.set_surface_override_material(0, _original_material.duplicate()) +## Handle death: drop loot, then despawn (server-authoritative, fires once per death) +func _die(killer_id: int): + var already_dead = is_dead + super._die(killer_id) + + if not multiplayer.is_server() or already_dead: + return + + # Chance to drop a health orb + if randf() < health_orb_drop_chance: + var level = get_tree().get_current_scene() + if level and level.has_method("spawn_health_orb"): + level.spawn_health_orb(global_position + Vector3.UP) + + # Non-reviving enemies remove their corpse on every client after a short beat + if not can_respawn: + _schedule_despawn() + +## Free this enemy on all peers a couple seconds after death +func _schedule_despawn(): + await get_tree().create_timer(2.0).timeout + if is_instance_valid(self): + rpc("_despawn") + +@rpc("any_peer", "call_local", "reliable") +func _despawn(): + queue_free() + ## Death callback func _on_enemy_died(killer_id: int): super._on_enemy_died(killer_id) diff --git a/level/scripts/enemy_spawner.gd b/level/scripts/enemy_spawner.gd index 147c62b..6dc06bb 100644 --- a/level/scripts/enemy_spawner.gd +++ b/level/scripts/enemy_spawner.gd @@ -17,6 +17,11 @@ signal all_enemies_defeated() @export var auto_start_next_wave: bool = false ## Auto-start next wave when all defeated @export var wave_delay: float = 5.0 ## Delay before next wave auto-starts +@export_category("Timed Waves") +@export var auto_waves: bool = true ## Spawn a fresh wave on a repeating timer +@export var wave_interval: float = 15.0 ## Seconds between timed waves +@export var first_wave_delay: float = 3.0 ## Delay before the very first timed wave + @export_category("Enemy Pool") @export var enemy_scenes: Array[PackedScene] = [] ## List of enemy scenes to spawn from @@ -26,6 +31,8 @@ var active_enemies: Array[Node] = [] var _wave_delay_timer: float = 0.0 var _waiting_for_next_wave: bool = false var _enemy_id_counter: int = 0 +var _auto_waves_active: bool = false +var _auto_wave_timer: float = 0.0 ## Debug visualization @export var show_spawn_radius: bool = false ## Show spawn radius circle in game @@ -35,6 +42,18 @@ func _ready(): print("[EnemySpawner] Ready. Server: ", multiplayer.is_server()) _create_debug_circle() +## Begin spawning waves automatically on a timer (server only) +func start_auto_waves(): + if not multiplayer.is_server() or not auto_waves: + return + _auto_waves_active = true + _auto_wave_timer = first_wave_delay + print("[EnemySpawner] Auto waves enabled - first wave in ", first_wave_delay, "s, then every ", wave_interval, "s") + +## Stop the timed-wave loop +func stop_auto_waves(): + _auto_waves_active = false + ## Start a new wave func start_wave(): if not multiplayer.is_server(): @@ -240,6 +259,13 @@ func _process(_delta): if multiplayer.multiplayer_peer == null or not multiplayer.is_server(): return + # Timed waves - spawn a fresh wave on a repeating interval + if _auto_waves_active: + _auto_wave_timer -= _delta + if _auto_wave_timer <= 0: + _auto_wave_timer = wave_interval + start_wave() + # Handle wave delay timer if _waiting_for_next_wave: _wave_delay_timer -= _delta diff --git a/level/scripts/health_orb.gd b/level/scripts/health_orb.gd new file mode 100644 index 0000000..bb8ec01 --- /dev/null +++ b/level/scripts/health_orb.gd @@ -0,0 +1,51 @@ +extends Area3D +class_name HealthOrb + +## Floating pickup that restores health when a player walks into it. +## Server-authoritative pickup; the bob/spin visuals run locally on every peer. + +@export var heal_amount: float = 25.0 +@export var bob_height: float = 0.3 +@export var bob_speed: float = 2.0 +@export var spin_speed: float = 2.0 + +var orb_id: int = -1 # Assigned by Level when spawned +var _base_y: float = 0.0 +var _time: float = 0.0 +var _collected: bool = false +@onready var _mesh: Node3D = get_node_or_null("Mesh") + +func _ready(): + _base_y = position.y + _time = randf() * TAU # Random phase so multiple orbs don't bob in unison + + # Detect players (physics layer 1) without colliding with anything + collision_layer = 0 + collision_mask = 1 + + # Only the server resolves pickups + if multiplayer.is_server(): + body_entered.connect(_on_body_entered) + +func _process(delta): + _time += delta + position.y = _base_y + sin(_time * bob_speed) * bob_height + if _mesh: + _mesh.rotate_y(spin_speed * delta) + +func _on_body_entered(body: Node3D): + if _collected or not multiplayer.is_server(): + return + if not (body is Character) or body.is_dead: + return + # Leave the orb in place if the player is already at full health + if body.current_health >= body.max_health: + return + + _collected = true + body.heal(heal_amount) + + # Remove the orb from every client via the level's centralized system + var level = get_tree().get_current_scene() + if level and level.has_method("remove_health_orb"): + level.remove_health_orb(orb_id) diff --git a/level/scripts/health_orb.gd.uid b/level/scripts/health_orb.gd.uid new file mode 100644 index 0000000..d7a4dc4 --- /dev/null +++ b/level/scripts/health_orb.gd.uid @@ -0,0 +1 @@ +uid://bbekeah8iio6t diff --git a/level/scripts/level.gd b/level/scripts/level.gd index 6bc5e4e..66db664 100644 --- a/level/scripts/level.gd +++ b/level/scripts/level.gd @@ -6,6 +6,7 @@ extends Node3D @onready var players_container: Node3D = $PlayersContainer @onready var weapons_container: Node3D = $WeaponsContainer @onready var enemies_container: Node3D = $EnemiesContainer +var orbs_container: Node3D = null @onready var enemy_spawner: EnemySpawner = $EnemySpawner @onready var player_spawn_points: Node3D = $PlayerSpawnPoints @onready var menu: Control = $Menu @@ -59,6 +60,15 @@ func _ready(): add_child(enemies_container) print("Created EnemiesContainer") + # Create or find health orbs container + if has_node("OrbsContainer"): + orbs_container = get_node("OrbsContainer") + else: + orbs_container = Node3D.new() + orbs_container.name = "OrbsContainer" + add_child(orbs_container) + print("Created OrbsContainer") + # Don't initialize weapons in _ready() - wait for Host/Join to be pressed # This is handled in initialize_multiplayer() which is called after the # multiplayer peer is properly set up @@ -96,6 +106,13 @@ func initialize_multiplayer(): # Spawn armed enemies _spawn_armed_enemies() + # Scatter a few health orbs so the pickup is visible from the start + _spawn_initial_health_orbs() + + # Begin automatic timed enemy waves + if enemy_spawner: + enemy_spawner.start_auto_waves() + # Spawn the host player (peer ID 1) print("[Level] Spawning host player") var host_info = Network.players.get(1, {"nick": "Host", "skin": "blue"}) @@ -265,6 +282,11 @@ func _on_player_connected(peer_id, player_info): print("[Server] Syncing enemy ", enemy.name, " at position ", enemy.global_position, " to peer ", peer_id) rpc_id(peer_id, "_spawn_dummy_local", enemy_id, enemy.global_position) + # Sync existing health orbs to the newly joined player + print("[Server] Syncing health orbs to newly connected peer: ", peer_id) + for orb_id in _active_orbs.keys(): + rpc_id(peer_id, "_spawn_health_orb_local", orb_id, _active_orbs[orb_id]) + # Sync equipped weapons for all existing players to the newly joined player print("[Server] Syncing equipped weapons to newly connected peer: ", peer_id) for player_node in players_container.get_children(): @@ -647,6 +669,76 @@ func _client_spawn_weapon(weapon_data_path: String, spawn_position: Vector3, ini print("[Client ", multiplayer.get_unique_id(), "] Calling spawn_world_weapon locally") spawn_world_weapon(weapon_data_path, spawn_position, initial_velocity, weapon_id) +# ---------- HEALTH ORBS ---------- +var _orb_spawn_counter: int = 0 +var _active_orbs: Dictionary = {} # orb_id -> spawn position (Vector3), for late-join sync + +## Spawn a health orb at a position (server only, replicates to all clients) +func spawn_health_orb(spawn_pos: Vector3): + if not multiplayer.is_server(): + return + _orb_spawn_counter += 1 + rpc("_spawn_health_orb_local", _orb_spawn_counter, spawn_pos) + +## Spawn a health orb on all clients (call_local spawns on server too) +@rpc("any_peer", "call_local", "reliable") +func _spawn_health_orb_local(orb_id: int, spawn_pos: Vector3): + if not orbs_container: + push_error("[Level] OrbsContainer not found!") + return + + var orb_name = "HealthOrb_" + str(orb_id) + if orbs_container.has_node(orb_name): + return # Already spawned (e.g. duplicate sync) + + var orb_scene = load("res://level/scenes/health_orb.tscn") + if not orb_scene: + push_error("[Level] Failed to load health_orb.tscn") + return + + var orb = orb_scene.instantiate() + orb.orb_id = orb_id + orb.name = orb_name + orb.position = spawn_pos + orbs_container.add_child(orb, true) + + # Server tracks active orbs for late-join sync + if multiplayer.is_server(): + _active_orbs[orb_id] = spawn_pos + + print("[Peer ", multiplayer.get_unique_id(), "] Spawned health orb ", orb_name, " at ", spawn_pos) + +## Remove a health orb from all clients (called by HealthOrb when collected) +func remove_health_orb(orb_id: int): + if not multiplayer.is_server(): + return + if _active_orbs.has(orb_id): + _active_orbs.erase(orb_id) + rpc("_remove_orb_on_clients", orb_id) + +## RPC to remove an orb on all clients +@rpc("any_peer", "call_local", "reliable") +func _remove_orb_on_clients(orb_id: int): + var orb_name = "HealthOrb_" + str(orb_id) + if orbs_container and orbs_container.has_node(orb_name): + orbs_container.get_node(orb_name).queue_free() + +## Scatter a few health orbs at server start +func _spawn_initial_health_orbs(): + if not multiplayer.is_server(): + return + + # Wait a frame for everything to be ready + await get_tree().process_frame + + var orb_positions = [ + Vector3(6, 1, 6), + Vector3(-6, 1, -6), + Vector3(-6, 1, 6), + ] + for pos in orb_positions: + spawn_health_orb(pos) + # ---------- ARMED ENEMY SPAWNING ---------- var _armed_enemy_counter: int = 0 diff --git a/level/scripts/player.gd b/level/scripts/player.gd index 28fa5d2..67d2350 100644 --- a/level/scripts/player.gd +++ b/level/scripts/player.gd @@ -69,6 +69,11 @@ func _enter_tree(): $SpringArmOffset/SpringArm3D/Camera3D.current = is_multiplayer_authority() func _ready(): + # Players passively regenerate health (set before super._ready so the regen timer is created) + if health_regen <= 0.0: + health_regen = 3.0 + regen_delay = 5.0 + super._ready() # Set respawn point to current position (where we spawned) - base_unit._ready already does this # Don't override with a hardcoded position diff --git a/level/ui/scenes/keybind_hint.tscn b/level/ui/scenes/keybind_hint.tscn new file mode 100644 index 0000000..86174c8 --- /dev/null +++ b/level/ui/scenes/keybind_hint.tscn @@ -0,0 +1,41 @@ +[gd_scene load_steps=4 format=3] + +[ext_resource type="Script" path="res://level/ui/scripts/keybind_hint.gd" id="1_script"] +[ext_resource type="Theme" uid="uid://dvsh7tuhulnfm" path="res://level/ui/theme/wow_style.tres" id="2_theme"] + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_kb"] +bg_color = Color(0.15, 0.15, 0.15, 0.85) +border_width_left = 2 +border_width_top = 2 +border_width_right = 2 +border_width_bottom = 2 +border_color = Color(0.6, 0.5, 0.2, 1) +corner_radius_top_left = 4 +corner_radius_top_right = 4 +corner_radius_bottom_right = 4 +corner_radius_bottom_left = 4 + +[node name="KeybindHint" type="PanelContainer"] +anchors_preset = 1 +anchor_left = 1.0 +anchor_right = 1.0 +offset_left = -210.0 +offset_top = 20.0 +offset_right = -20.0 +offset_bottom = 20.0 +grow_horizontal = 0 +grow_vertical = 1 +theme = ExtResource("2_theme") +theme_override_styles/panel = SubResource("StyleBoxFlat_kb") +script = ExtResource("1_script") + +[node name="Margin" type="MarginContainer" parent="."] +layout_mode = 2 +theme_override_constants/margin_left = 10 +theme_override_constants/margin_top = 8 +theme_override_constants/margin_right = 10 +theme_override_constants/margin_bottom = 8 + +[node name="Content" type="VBoxContainer" parent="Margin"] +layout_mode = 2 +theme_override_constants/separation = 4 diff --git a/level/ui/scripts/hud_manager.gd b/level/ui/scripts/hud_manager.gd index 4b3118c..a5fd7f0 100644 --- a/level/ui/scripts/hud_manager.gd +++ b/level/ui/scripts/hud_manager.gd @@ -8,6 +8,7 @@ var unit_frame: Control = null var target_frame: Control = null var character_sheet: Control = null var tab_hint: Control = null +var keybind_hint: Control = null var escape_menu: Control = null # Player reference @@ -77,6 +78,7 @@ func _create_ui_components(): _create_unit_frame() _create_character_sheet() _create_tab_hint() + _create_keybind_hint() _create_escape_menu() ## Create action bar at bottom of screen @@ -125,6 +127,16 @@ func _create_tab_hint(): else: push_error("[HUD] Failed to load tab_hint.tscn") +## Create keybind hint (always visible, top-right corner) +func _create_keybind_hint(): + var keybind_hint_scene = load("res://level/ui/scenes/keybind_hint.tscn") + if keybind_hint_scene: + keybind_hint = keybind_hint_scene.instantiate() + add_child(keybind_hint) + print("[HUD] Keybind hint created") + else: + push_error("[HUD] Failed to load keybind_hint.tscn") + ## Create escape menu (toggle with Escape) func _create_escape_menu(): var escape_menu_scene = load("res://level/ui/scenes/escape_menu.tscn") diff --git a/level/ui/scripts/keybind_hint.gd b/level/ui/scripts/keybind_hint.gd new file mode 100644 index 0000000..04091d7 --- /dev/null +++ b/level/ui/scripts/keybind_hint.gd @@ -0,0 +1,75 @@ +extends PanelContainer +## Always-on compact panel listing the current controls. +## Reads bindings straight from the InputMap so it stays accurate if keys are rebound. + +# Each row is a friendly label plus the input action(s) that feed it. +# Grouped actions (like movement) show their keys joined together. +const ROWS := [ + { "label": "Move", "actions": ["move_forward", "move_left", "move_backward", "move_right"] }, + { "label": "Jump", "actions": ["jump"] }, + { "label": "Sprint", "actions": ["shift"] }, + { "label": "Dash", "actions": ["dash"] }, + { "label": "Attack", "actions": ["attack"] }, + { "label": "Block", "actions": ["block"] }, + { "label": "Pick Up / Drop", "actions": ["pickup"] }, + { "label": "Character Sheet", "actions": ["toggle_character_sheet"] }, + { "label": "Chat", "actions": ["toggle_chat"] }, + { "label": "Menu", "actions": ["quit"] }, +] + +func _ready() -> void: + _build() + +func _build() -> void: + var content: VBoxContainer = $Margin/Content + + var title := Label.new() + title.text = "CONTROLS" + title.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER + title.add_theme_font_size_override("font_size", 16) + title.add_theme_color_override("font_color", Color(1, 0.8, 0, 1)) + title.add_theme_color_override("font_outline_color", Color.BLACK) + title.add_theme_constant_override("outline_size", 2) + content.add_child(title) + + var grid := GridContainer.new() + grid.columns = 2 + grid.add_theme_constant_override("h_separation", 12) + grid.add_theme_constant_override("v_separation", 3) + content.add_child(grid) + + for row in ROWS: + var keys: Array[String] = [] + for action in row["actions"]: + var k := _key_text(action) + if k != "": + keys.append(k) + if keys.is_empty(): + continue + grid.add_child(_make_label(" ".join(keys), Color(1, 0.82, 0.2, 1))) + grid.add_child(_make_label(row["label"], Color.WHITE)) + +func _make_label(text: String, color: Color) -> Label: + var l := Label.new() + l.text = text + l.add_theme_font_size_override("font_size", 13) + l.add_theme_color_override("font_color", color) + l.add_theme_color_override("font_outline_color", Color.BLACK) + l.add_theme_constant_override("outline_size", 1) + return l + +## Returns a human-readable name for the first event bound to an action. +func _key_text(action: String) -> String: + if not InputMap.has_action(action): + return "" + for ev in InputMap.action_get_events(action): + if ev is InputEventKey: + var code: int = ev.physical_keycode if ev.physical_keycode != 0 else ev.keycode + return OS.get_keycode_string(code) + elif ev is InputEventMouseButton: + match ev.button_index: + MOUSE_BUTTON_LEFT: return "LMB" + MOUSE_BUTTON_RIGHT: return "RMB" + MOUSE_BUTTON_MIDDLE: return "MMB" + _: return "Mouse %d" % ev.button_index + return "" diff --git a/level/ui/scripts/keybind_hint.gd.uid b/level/ui/scripts/keybind_hint.gd.uid new file mode 100644 index 0000000..9aaa283 --- /dev/null +++ b/level/ui/scripts/keybind_hint.gd.uid @@ -0,0 +1 @@ +uid://dh85b10pfih5e diff --git a/project.godot b/project.godot index 3fe638d..6090984 100644 --- a/project.godot +++ b/project.godot @@ -24,8 +24,10 @@ HUD="*res://level/ui/scripts/hud_manager.gd" [display] -window/size/viewport_width=1280 -window/size/viewport_height=720 +window/size/viewport_width=1920 +window/size/viewport_height=1080 +window/stretch/mode="canvas_items" +window/stretch/aspect="expand" [input]