[agent:claude-cli] feat(minimap): radar circulaire 32 blocs — joueur (blanc), mobs (rouge pulsé), perles (jaune), blocs biome, orientation joueur, dessin CanvasItem natif

This commit is contained in:
2026-04-21 08:44:30 +00:00
parent 42101246d9
commit f8413ce2d4
4 changed files with 200 additions and 0 deletions

View File

@@ -41,6 +41,9 @@ var _speed_lines: Control = null
var _speed_line_data: Array = []
const SPEED_LINE_COUNT: int = 16
# Mini-map
var _mini_map: Control = null
func _ready() -> void:
_style_bar(_oxygen_bar, Color(0.31, 0.76, 0.97))
@@ -66,6 +69,7 @@ func _ready() -> void:
_build_combo_panel()
_build_boost_bar()
_build_speed_lines()
_build_mini_map()
var am: Node = get_node_or_null("/root/AchievementManager")
if am != null:
am.achievement_unlocked.connect(_on_achievement_unlocked)
@@ -603,11 +607,22 @@ func _draw_speed_lines() -> void:
_speed_lines.draw_line(start, end, col, 1.5)
func _build_mini_map() -> void:
var mm_script: Script = load("res://scripts/dolphin/MiniMap.gd")
if mm_script == null:
return
_mini_map = Control.new()
_mini_map.set_script(mm_script)
add_child(_mini_map)
func connect_to_dolphin(dolphin: CharacterBody3D) -> void:
dolphin.stats_changed.connect(_on_stats_changed)
if dolphin.has_signal("boost_changed"):
dolphin.boost_changed.connect(_on_boost_changed)
_dolphin = dolphin
if _mini_map != null and _mini_map.has_method("setup"):
_mini_map.call("setup", dolphin)
var main: Node = get_tree().get_first_node_in_group("main")
if main != null:

183
scripts/dolphin/MiniMap.gd Normal file
View File

@@ -0,0 +1,183 @@
extends Control
## MiniMap — radar circulaire 2D dans le coin HUD.
## Rayon 32 blocs. Montre : joueur (blanc), mobs (rouge), perles (jaune),
## blocs spéciaux via couleur biome (dessin manuel sur CanvasItem).
const MAP_RADIUS_PX: float = 52.0 # rayon en pixels du cercle radar
const MAP_WORLD_RADIUS: float = 32.0 # rayon en unités monde représenté
var _dolphin: Node3D = null
# Pulsation des icônes mobs
var _time: float = 0.0
func _ready() -> void:
# Positionnement: coin bas-gauche au-dessus de la barre boost
anchor_left = 0.0
anchor_right = 0.0
anchor_top = 1.0
anchor_bottom = 1.0
var size: float = MAP_RADIUS_PX * 2.0 + 8.0
offset_left = 16.0
offset_right = 16.0 + size
offset_top = -(78.0 + size + 12.0)
offset_bottom = -(78.0 + 12.0)
custom_minimum_size = Vector2(size, size)
mouse_filter = MOUSE_FILTER_IGNORE
func setup(dolphin: Node3D) -> void:
_dolphin = dolphin
func _process(delta: float) -> void:
_time += delta
queue_redraw()
func _draw() -> void:
if not is_instance_valid(_dolphin):
return
var center: Vector2 = Vector2(MAP_RADIUS_PX + 4.0, MAP_RADIUS_PX + 4.0)
var r: float = MAP_RADIUS_PX
# --- Background circle ---
draw_circle(center, r + 2.0, Color(0.0, 0.0, 0.0, 0.45))
draw_arc(center, r + 2.0, 0.0, TAU, 48, Color(0.3, 0.7, 1.0, 0.55), 1.5)
# --- Clip mask simulation: draw a dark ring outside the circle to fake clipping ---
# (Godot 4 CanvasItem draw doesn't natively clip circles — we use stencil trick with overdraw)
var player_pos: Vector3 = _dolphin.global_position
var player_yaw: float = _dolphin.rotation.y # for oriented radar
# --- Draw world blip grid (simplified biome color dots) ---
_draw_biome_blips(center, r, player_pos, player_yaw)
# --- Draw Pearls ---
var pearls := get_tree().get_nodes_in_group("pearl")
for pearl: Node in pearls:
if not is_instance_valid(pearl):
continue
var wp: Vector3 = pearl.global_position
var blip: Vector2 = _world_to_radar(player_pos, player_yaw, wp, r)
if blip.length() <= r:
draw_circle(center + blip, 3.5, Color(1.0, 0.92, 0.2, 0.9))
# --- Draw Mobs ---
var mobs: Array = []
mobs.append_array(get_tree().get_nodes_in_group("shark"))
mobs.append_array(get_tree().get_nodes_in_group("mob"))
# Also scan MobSpawner children directly
var spawner: Node = _find_mob_spawner()
if spawner != null:
_collect_mob_children(spawner, mobs)
for mob: Node in mobs:
if not is_instance_valid(mob):
continue
var mp: Vector3 = mob.global_position
var blip: Vector2 = _world_to_radar(player_pos, player_yaw, mp, r)
if blip.length() <= r:
var pulse: float = 0.75 + sin(_time * 4.0) * 0.25
draw_circle(center + blip, 3.5, Color(1.0, 0.2, 0.2, pulse))
# --- Draw Player dot (center, white with ring) ---
draw_circle(center, 4.5, Color(1.0, 1.0, 1.0, 1.0))
draw_arc(center, 5.5, 0.0, TAU, 16, Color(0.5, 0.8, 1.0, 0.8), 1.0)
# --- Direction tick (north indicator) ---
var north_angle: float = -player_yaw - PI * 0.5
var north_dir: Vector2 = Vector2(cos(north_angle), sin(north_angle)) * (r - 4.0)
draw_line(center + north_dir * 0.85, center + north_dir, Color(1.0, 0.3, 0.3, 0.9), 2.0)
# --- Border ring (final, on top) ---
draw_arc(center, r, 0.0, TAU, 64, Color(0.25, 0.6, 0.9, 0.75), 2.0)
func _world_to_radar(player_pos: Vector3, player_yaw: float, world_pos: Vector3, r: float) -> Vector2:
var dx: float = world_pos.x - player_pos.x
var dz: float = world_pos.z - player_pos.z
# Rotate by -player_yaw so the map is oriented (player forward = up)
var cos_y: float = cos(-player_yaw)
var sin_y: float = sin(-player_yaw)
var rx: float = dx * cos_y - dz * sin_y
var rz: float = dx * sin_y + dz * cos_y
# Scale to radar pixels
var scale: float = r / MAP_WORLD_RADIUS
return Vector2(rx * scale, rz * scale)
func _draw_biome_blips(center: Vector2, r: float, player_pos: Vector3, player_yaw: float) -> void:
# Sample a coarse grid around the player and draw colored pixels by block type
# We do a 5x5 sparse grid to keep it performant
var chunk_mgr: Node = _find_chunk_manager()
if chunk_mgr == null or not chunk_mgr.has_method("get_block"):
return
var step: float = MAP_WORLD_RADIUS / 5.0
for ix: int in range(-5, 6):
for iz: int in range(-5, 6):
var wx: float = player_pos.x + ix * step
var wz: float = player_pos.z + iz * step
var wy: float = player_pos.y
var block_id: int = chunk_mgr.call("get_block", Vector3(wx, wy, wz))
if block_id <= 1:
continue
var col: Color = _block_color(block_id)
col.a = 0.3
var blip: Vector2 = _world_to_radar(player_pos, player_yaw, Vector3(wx, wy, wz), r)
if blip.length() < r - 4.0:
draw_rect(Rect2(center + blip - Vector2(1.5, 1.5), Vector2(3.0, 3.0)), col)
func _block_color(block_id: int) -> Color:
match block_id:
2: return Color(0.76, 0.70, 0.50) # sand
3: return Color(0.45, 0.45, 0.45) # rock
4: return Color(0.9, 0.2, 0.2) # coral rouge
5: return Color(0.2, 0.4, 0.9) # coral bleu
6: return Color(0.1, 0.6, 0.1) # kelp
7: return Color(0.55, 0.35, 0.15) # epave
8: return Color(0.7, 0.9, 1.0) # glace
_: return Color(0.5, 0.5, 0.5)
func _find_chunk_manager() -> Node:
var candidates := get_tree().get_nodes_in_group("chunk_manager")
if candidates.size() > 0:
return candidates[0]
return get_node_or_null("/root/Main/World/ChunkManager")
func _find_mob_spawner() -> Node:
var root: Node = get_tree().current_scene
if root == null:
return null
return _search_for_mob_spawner(root)
func _search_for_mob_spawner(node: Node) -> Node:
if node.get_script() != null:
var path: String = node.get_script().resource_path
if "MobSpawner" in path:
return node
for child: Node in node.get_children():
var found: Node = _search_for_mob_spawner(child)
if found != null:
return found
return null
func _collect_mob_children(mob_spawner: Node, out: Array) -> void:
# MobSpawner spawns mobs as children of current_scene, tracked in _sharks/_jellyfish_list
# We can read them directly from scene tree by checking script type
var scene_root: Node = get_tree().current_scene
if scene_root == null:
return
for child: Node in scene_root.get_children():
if child is CharacterBody3D and child != _dolphin:
if not out.has(child):
out.append(child)

View File

@@ -31,6 +31,7 @@ func _ready() -> void:
_build_visuals()
_find_player()
_generate_patrol_points()
add_to_group("shark")
func _find_player() -> void:

View File

@@ -15,6 +15,7 @@ func _ready() -> void:
body_entered.connect(_on_body_entered)
_build_visual()
_build_shape()
add_to_group("pearl")
func _build_visual() -> void: