fix(gameplay): escape pause menu + block placement collision + perf optim render_distance/throttling
This commit is contained in:
@@ -83,7 +83,7 @@ toggle_inventory={
|
|||||||
|
|
||||||
escape={
|
escape={
|
||||||
"deadzone": 0.5,
|
"deadzone": 0.5,
|
||||||
"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":16777217,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
"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":4194305,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
6
scenes/PauseMenu.tscn
Normal file
6
scenes/PauseMenu.tscn
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
[gd_scene load_steps=2 format=3 uid="uid://pause_menu"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://scripts/PauseMenu.gd" id="1_pause"]
|
||||||
|
|
||||||
|
[node name="PauseMenu" type="Control"]
|
||||||
|
script = ExtResource("1_pause")
|
||||||
@@ -7,6 +7,9 @@ extends Node3D
|
|||||||
|
|
||||||
var inventory: Inventory = Inventory.new()
|
var inventory: Inventory = Inventory.new()
|
||||||
var _inventory_ui: CanvasLayer = null
|
var _inventory_ui: CanvasLayer = null
|
||||||
|
var _pause_menu: Control = null
|
||||||
|
|
||||||
|
var _last_chunk_update_pos: Vector3 = Vector3(99999, 99999, 99999)
|
||||||
|
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
@@ -29,6 +32,12 @@ func _ready() -> void:
|
|||||||
else:
|
else:
|
||||||
push_error("Main: could not load InventoryUI.tscn")
|
push_error("Main: could not load InventoryUI.tscn")
|
||||||
|
|
||||||
|
# Load and attach PauseMenu
|
||||||
|
var pause_scene: PackedScene = preload("res://scenes/PauseMenu.tscn")
|
||||||
|
_pause_menu = pause_scene.instantiate() as Control
|
||||||
|
add_child(_pause_menu)
|
||||||
|
_pause_menu.hide()
|
||||||
|
|
||||||
AudioManager.play_ambient_loop("underwater_ambient")
|
AudioManager.play_ambient_loop("underwater_ambient")
|
||||||
AudioManager.play_music("underwater_theme")
|
AudioManager.play_music("underwater_theme")
|
||||||
AudioManager.play_whale_call_random()
|
AudioManager.play_whale_call_random()
|
||||||
@@ -51,10 +60,30 @@ func _ready() -> void:
|
|||||||
|
|
||||||
|
|
||||||
func _process(_delta: float) -> void:
|
func _process(_delta: float) -> void:
|
||||||
|
# Throttle chunk updates: only when dolphin moved > 4m
|
||||||
|
if dolphin.global_position.distance_to(_last_chunk_update_pos) > 4.0:
|
||||||
world.update_player_position(dolphin.global_position)
|
world.update_player_position(dolphin.global_position)
|
||||||
|
_last_chunk_update_pos = dolphin.global_position
|
||||||
plankton_follower.global_position = dolphin.global_position
|
plankton_follower.global_position = dolphin.global_position
|
||||||
|
|
||||||
|
|
||||||
|
func _unhandled_input(event: InputEvent) -> void:
|
||||||
|
if not event.is_action_pressed("escape"):
|
||||||
|
return
|
||||||
|
# Priority: close inventory first if open
|
||||||
|
if _inventory_ui != null and _inventory_ui.get("_is_open"):
|
||||||
|
_inventory_ui.call("_toggle_inventory")
|
||||||
|
get_viewport().set_input_as_handled()
|
||||||
|
return
|
||||||
|
# Toggle pause menu
|
||||||
|
if is_instance_valid(_pause_menu):
|
||||||
|
if _pause_menu.visible:
|
||||||
|
_pause_menu.hide_pause()
|
||||||
|
else:
|
||||||
|
_pause_menu.show_pause()
|
||||||
|
get_viewport().set_input_as_handled()
|
||||||
|
|
||||||
|
|
||||||
func _on_block_break(hit_position: Vector3, _normal: Vector3) -> void:
|
func _on_block_break(hit_position: Vector3, _normal: Vector3) -> void:
|
||||||
var broken_id: int = world.break_block(hit_position)
|
var broken_id: int = world.break_block(hit_position)
|
||||||
if broken_id > 0:
|
if broken_id > 0:
|
||||||
@@ -68,7 +97,17 @@ func _on_block_place(hit_position: Vector3, normal: Vector3) -> void:
|
|||||||
return
|
return
|
||||||
if not ItemDatabase.is_placeable(selected["item_id"]):
|
if not ItemDatabase.is_placeable(selected["item_id"]):
|
||||||
return
|
return
|
||||||
|
|
||||||
var place_pos := hit_position + normal * 0.5
|
var place_pos := hit_position + normal * 0.5
|
||||||
|
var block_coord := Vector3(floor(place_pos.x), floor(place_pos.y), floor(place_pos.z))
|
||||||
|
var block_center := block_coord + Vector3(0.5, 0.5, 0.5)
|
||||||
|
|
||||||
|
# Refuse if bloc overlaps player AABB (CapsuleShape3D r=0.4 h=2.0)
|
||||||
|
var dolphin_pos: Vector3 = dolphin.global_position
|
||||||
|
var dist := block_center - dolphin_pos
|
||||||
|
if abs(dist.x) < 1.0 and abs(dist.y) < 1.8 and abs(dist.z) < 1.0:
|
||||||
|
return # too close to player, silent refuse
|
||||||
|
|
||||||
if world.place_block(place_pos, selected["item_id"]):
|
if world.place_block(place_pos, selected["item_id"]):
|
||||||
inventory.remove_item_from_slot(inventory.selected_hotbar, 1)
|
inventory.remove_item_from_slot(inventory.selected_hotbar, 1)
|
||||||
|
|
||||||
|
|||||||
67
scripts/PauseMenu.gd
Normal file
67
scripts/PauseMenu.gd
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
extends Control
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
process_mode = Node.PROCESS_MODE_ALWAYS
|
||||||
|
set_anchors_preset(Control.PRESET_FULL_RECT)
|
||||||
|
|
||||||
|
# Semi-transparent background
|
||||||
|
var bg := ColorRect.new()
|
||||||
|
bg.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||||||
|
bg.color = Color(0, 0, 0, 0.7)
|
||||||
|
add_child(bg)
|
||||||
|
|
||||||
|
# Center VBox
|
||||||
|
var vbox := VBoxContainer.new()
|
||||||
|
vbox.set_anchors_preset(Control.PRESET_CENTER)
|
||||||
|
vbox.add_theme_constant_override("separation", 16)
|
||||||
|
vbox.set_offset(SIDE_LEFT, -120)
|
||||||
|
vbox.set_offset(SIDE_RIGHT, 120)
|
||||||
|
vbox.set_offset(SIDE_TOP, -120)
|
||||||
|
vbox.set_offset(SIDE_BOTTOM, 120)
|
||||||
|
add_child(vbox)
|
||||||
|
|
||||||
|
var title := Label.new()
|
||||||
|
title.text = "PAUSE"
|
||||||
|
title.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||||
|
title.add_theme_font_size_override("font_size", 48)
|
||||||
|
vbox.add_child(title)
|
||||||
|
|
||||||
|
var btn_resume := Button.new()
|
||||||
|
btn_resume.text = "Reprendre"
|
||||||
|
btn_resume.pressed.connect(_on_resume)
|
||||||
|
vbox.add_child(btn_resume)
|
||||||
|
|
||||||
|
var btn_menu := Button.new()
|
||||||
|
btn_menu.text = "Menu principal"
|
||||||
|
btn_menu.pressed.connect(_on_main_menu)
|
||||||
|
vbox.add_child(btn_menu)
|
||||||
|
|
||||||
|
var btn_quit := Button.new()
|
||||||
|
btn_quit.text = "Quitter"
|
||||||
|
btn_quit.pressed.connect(_on_quit)
|
||||||
|
vbox.add_child(btn_quit)
|
||||||
|
|
||||||
|
|
||||||
|
func show_pause() -> void:
|
||||||
|
show()
|
||||||
|
get_tree().paused = true
|
||||||
|
Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
|
||||||
|
|
||||||
|
|
||||||
|
func hide_pause() -> void:
|
||||||
|
hide()
|
||||||
|
get_tree().paused = false
|
||||||
|
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
|
||||||
|
|
||||||
|
|
||||||
|
func _on_resume() -> void:
|
||||||
|
hide_pause()
|
||||||
|
|
||||||
|
|
||||||
|
func _on_main_menu() -> void:
|
||||||
|
get_tree().paused = false
|
||||||
|
get_tree().change_scene_to_file("res://scenes/MainMenu.tscn")
|
||||||
|
|
||||||
|
|
||||||
|
func _on_quit() -> void:
|
||||||
|
get_tree().quit()
|
||||||
@@ -77,13 +77,6 @@ func _setup_input_actions() -> void:
|
|||||||
ev.button_index = mouse_actions[action_name]
|
ev.button_index = mouse_actions[action_name]
|
||||||
InputMap.action_add_event(action_name, ev)
|
InputMap.action_add_event(action_name, ev)
|
||||||
|
|
||||||
if not InputMap.has_action("escape"):
|
|
||||||
InputMap.add_action("escape")
|
|
||||||
var ev := InputEventKey.new()
|
|
||||||
ev.keycode = KEY_ESCAPE
|
|
||||||
InputMap.action_add_event("escape", ev)
|
|
||||||
|
|
||||||
|
|
||||||
func _input(event: InputEvent) -> void:
|
func _input(event: InputEvent) -> void:
|
||||||
if event is InputEventMouseMotion and Input.get_mouse_mode() == Input.MOUSE_MODE_CAPTURED:
|
if event is InputEventMouseMotion and Input.get_mouse_mode() == Input.MOUSE_MODE_CAPTURED:
|
||||||
var motion := event as InputEventMouseMotion
|
var motion := event as InputEventMouseMotion
|
||||||
@@ -107,11 +100,6 @@ func _input(event: InputEvent) -> void:
|
|||||||
_do_raycast_place()
|
_do_raycast_place()
|
||||||
if event.is_action_pressed("echolocate"):
|
if event.is_action_pressed("echolocate"):
|
||||||
_trigger_echolocation()
|
_trigger_echolocation()
|
||||||
if event.is_action_pressed("escape"):
|
|
||||||
if Input.get_mouse_mode() == Input.MOUSE_MODE_CAPTURED:
|
|
||||||
Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
|
|
||||||
else:
|
|
||||||
Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
|
|
||||||
|
|
||||||
|
|
||||||
func _physics_process(delta: float) -> void:
|
func _physics_process(delta: float) -> void:
|
||||||
@@ -252,9 +240,9 @@ func _raycast(reach: float) -> Dictionary:
|
|||||||
|
|
||||||
func take_damage(amount: float) -> void:
|
func take_damage(amount: float) -> void:
|
||||||
health = max(0.0, health - amount)
|
health = max(0.0, health - amount)
|
||||||
emit_signal("stats_changed", oxygen, health, hunger)
|
stats_changed.emit(oxygen, health, hunger)
|
||||||
if health <= 0.0:
|
if health <= 0.0:
|
||||||
emit_signal("player_died")
|
player_died.emit()
|
||||||
|
|
||||||
|
|
||||||
func _trigger_echolocation() -> void:
|
func _trigger_echolocation() -> void:
|
||||||
|
|||||||
@@ -266,6 +266,9 @@ func _input(event: InputEvent) -> void:
|
|||||||
if event.is_action_pressed("toggle_inventory"):
|
if event.is_action_pressed("toggle_inventory"):
|
||||||
_toggle_inventory()
|
_toggle_inventory()
|
||||||
get_viewport().set_input_as_handled()
|
get_viewport().set_input_as_handled()
|
||||||
|
elif event.is_action_pressed("escape") and _is_open:
|
||||||
|
_toggle_inventory()
|
||||||
|
get_viewport().set_input_as_handled()
|
||||||
|
|
||||||
|
|
||||||
func _toggle_inventory() -> void:
|
func _toggle_inventory() -> void:
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ var _fish_meshes: Array[MeshInstance3D] = []
|
|||||||
var _fish_velocities: Array[Vector3] = []
|
var _fish_velocities: Array[Vector3] = []
|
||||||
var _time: float = 0.0
|
var _time: float = 0.0
|
||||||
var _player: CharacterBody3D = null
|
var _player: CharacterBody3D = null
|
||||||
|
var _boid_update_timer: float = 0.0
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
_find_player()
|
_find_player()
|
||||||
@@ -62,6 +63,17 @@ func _process(delta: float) -> void:
|
|||||||
if _player == null:
|
if _player == null:
|
||||||
_find_player()
|
_find_player()
|
||||||
|
|
||||||
|
# Throttle boid calculations to 10 Hz
|
||||||
|
_boid_update_timer += delta
|
||||||
|
if _boid_update_timer >= 0.1:
|
||||||
|
_update_boids(_boid_update_timer)
|
||||||
|
_boid_update_timer = 0.0
|
||||||
|
|
||||||
|
# Apply velocities every frame for smooth movement
|
||||||
|
_apply_velocities(delta)
|
||||||
|
|
||||||
|
|
||||||
|
func _update_boids(dt: float) -> void:
|
||||||
for i: int in range(_fish_meshes.size()):
|
for i: int in range(_fish_meshes.size()):
|
||||||
var fish: MeshInstance3D = _fish_meshes[i]
|
var fish: MeshInstance3D = _fish_meshes[i]
|
||||||
var vel: Vector3 = _fish_velocities[i]
|
var vel: Vector3 = _fish_velocities[i]
|
||||||
@@ -91,12 +103,12 @@ func _process(delta: float) -> void:
|
|||||||
avg_vel /= float(neighbor_count)
|
avg_vel /= float(neighbor_count)
|
||||||
var cohesion_force: Vector3 = (avg_pos - fish.position).normalized() * 0.5
|
var cohesion_force: Vector3 = (avg_pos - fish.position).normalized() * 0.5
|
||||||
var alignment_force: Vector3 = avg_vel.normalized() * 0.3
|
var alignment_force: Vector3 = avg_vel.normalized() * 0.3
|
||||||
vel += (cohesion_force + alignment_force) * delta * 2.0
|
vel += (cohesion_force + alignment_force) * dt * 2.0
|
||||||
|
|
||||||
# --- Global cohesion: return to school center ---
|
# --- Global cohesion: return to school center ---
|
||||||
var center_offset: Vector3 = fish.position # positions are local to school node
|
var center_offset: Vector3 = fish.position
|
||||||
if center_offset.length() > school_radius:
|
if center_offset.length() > school_radius:
|
||||||
vel += -center_offset.normalized() * 1.5 * delta * 4.0
|
vel += -center_offset.normalized() * 1.5 * dt * 4.0
|
||||||
|
|
||||||
# --- Player avoidance ---
|
# --- Player avoidance ---
|
||||||
if is_instance_valid(_player):
|
if is_instance_valid(_player):
|
||||||
@@ -104,7 +116,7 @@ func _process(delta: float) -> void:
|
|||||||
var dist_to_player: float = world_fish_pos.distance_to(_player.global_position)
|
var dist_to_player: float = world_fish_pos.distance_to(_player.global_position)
|
||||||
if dist_to_player < 3.0:
|
if dist_to_player < 3.0:
|
||||||
var flee: Vector3 = (world_fish_pos - _player.global_position).normalized()
|
var flee: Vector3 = (world_fish_pos - _player.global_position).normalized()
|
||||||
vel += flee * 4.0 * delta * (3.0 - dist_to_player)
|
vel += flee * 4.0 * dt * (3.0 - dist_to_player)
|
||||||
|
|
||||||
# Clamp speed
|
# Clamp speed
|
||||||
if vel.length() > swim_speed * 1.5:
|
if vel.length() > swim_speed * 1.5:
|
||||||
@@ -113,6 +125,12 @@ func _process(delta: float) -> void:
|
|||||||
vel = vel.normalized() * 0.3
|
vel = vel.normalized() * 0.3
|
||||||
|
|
||||||
_fish_velocities[i] = vel
|
_fish_velocities[i] = vel
|
||||||
|
|
||||||
|
|
||||||
|
func _apply_velocities(delta: float) -> void:
|
||||||
|
for i: int in range(_fish_meshes.size()):
|
||||||
|
var fish: MeshInstance3D = _fish_meshes[i]
|
||||||
|
var vel: Vector3 = _fish_velocities[i]
|
||||||
fish.position += vel * delta
|
fish.position += vel * delta
|
||||||
|
|
||||||
# Rotate mesh to face velocity direction
|
# Rotate mesh to face velocity direction
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ class_name ChunkManager
|
|||||||
|
|
||||||
const CHUNK_SIZE: int = 16
|
const CHUNK_SIZE: int = 16
|
||||||
|
|
||||||
@export var render_distance: int = 4
|
@export var render_distance: int = 2
|
||||||
@export var world_seed: int = 12345
|
@export var world_seed: int = 12345
|
||||||
|
|
||||||
var chunks: Dictionary = {}
|
var chunks: Dictionary = {}
|
||||||
@@ -36,6 +36,26 @@ func update_player_position(pos: Vector3) -> void:
|
|||||||
if not chunks.has(coord):
|
if not chunks.has(coord):
|
||||||
_load_chunk(coord)
|
_load_chunk(coord)
|
||||||
|
|
||||||
|
func _regen_border_neighbors(chunk_coord: Vector3i, local: Vector3i) -> void:
|
||||||
|
# Regen adjacent chunk only if block is on the border face
|
||||||
|
var offsets: Array[Vector3i] = []
|
||||||
|
if local.x == 0:
|
||||||
|
offsets.append(Vector3i(-1, 0, 0))
|
||||||
|
elif local.x == CHUNK_SIZE - 1:
|
||||||
|
offsets.append(Vector3i(1, 0, 0))
|
||||||
|
if local.y == 0:
|
||||||
|
offsets.append(Vector3i(0, -1, 0))
|
||||||
|
elif local.y == CHUNK_SIZE - 1:
|
||||||
|
offsets.append(Vector3i(0, 1, 0))
|
||||||
|
if local.z == 0:
|
||||||
|
offsets.append(Vector3i(0, 0, -1))
|
||||||
|
elif local.z == CHUNK_SIZE - 1:
|
||||||
|
offsets.append(Vector3i(0, 0, 1))
|
||||||
|
for offset: Vector3i in offsets:
|
||||||
|
var neighbor: Vector3i = chunk_coord + offset
|
||||||
|
if chunks.has(neighbor):
|
||||||
|
chunks[neighbor].generate_mesh()
|
||||||
|
|
||||||
func break_block(world_pos: Vector3) -> int:
|
func break_block(world_pos: Vector3) -> int:
|
||||||
var chunk_coord: Vector3i = world_to_chunk_coord(world_pos)
|
var chunk_coord: Vector3i = world_to_chunk_coord(world_pos)
|
||||||
if not chunks.has(chunk_coord):
|
if not chunks.has(chunk_coord):
|
||||||
@@ -47,6 +67,7 @@ func break_block(world_pos: Vector3) -> int:
|
|||||||
return 0
|
return 0
|
||||||
chunk.set_block(local.x, local.y, local.z, BlockDatabase.BlockType.AIR)
|
chunk.set_block(local.x, local.y, local.z, BlockDatabase.BlockType.AIR)
|
||||||
chunk.generate_mesh()
|
chunk.generate_mesh()
|
||||||
|
_regen_border_neighbors(chunk_coord, local)
|
||||||
return old_id
|
return old_id
|
||||||
|
|
||||||
func place_block(world_pos: Vector3, block_id: int) -> bool:
|
func place_block(world_pos: Vector3, block_id: int) -> bool:
|
||||||
@@ -60,6 +81,7 @@ func place_block(world_pos: Vector3, block_id: int) -> bool:
|
|||||||
return false
|
return false
|
||||||
chunk.set_block(local.x, local.y, local.z, block_id)
|
chunk.set_block(local.x, local.y, local.z, block_id)
|
||||||
chunk.generate_mesh()
|
chunk.generate_mesh()
|
||||||
|
_regen_border_neighbors(chunk_coord, local)
|
||||||
return true
|
return true
|
||||||
|
|
||||||
func get_block(world_pos: Vector3) -> int:
|
func get_block(world_pos: Vector3) -> int:
|
||||||
|
|||||||
Reference in New Issue
Block a user