[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:
@@ -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
183
scripts/dolphin/MiniMap.gd
Normal 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)
|
||||
@@ -31,6 +31,7 @@ func _ready() -> void:
|
||||
_build_visuals()
|
||||
_find_player()
|
||||
_generate_patrol_points()
|
||||
add_to_group("shark")
|
||||
|
||||
|
||||
func _find_player() -> void:
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user