Files
agame/ui/crosshair.gd
T
2026-10-09 15:28:00 +08:00

164 lines
5.1 KiB
GDScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
@tool
class_name Crosshair
extends Control
## 准心 / 指针:屏幕正中央。
## 三种状态:
## NONE 没指着可拖拽物 → 画一个点
## HOVER 指着可拖拽物 → 显示 grab.png(张开的手,提示「可以抓」)
## HOLDING 正抓着东西 → 显示 grab_hover.png(抓取态)
##
## 挂法:CanvasLayer > Crosshair(Control),Control 的锚点设为「Full Rect」
## (anchor 全 0/1),这样 size 就等于视口尺寸,中心即屏幕中心。
## 因为是 CanvasLayer 子节点,在 window/stretch/mode=canvas_items 下会自动跟随缩放。
##
## 状态由 Player 驱动:拾取组件的 interact_state_changed 信号 → set_grab_state()。
## 指着可拖拽物体时的图标。没在 Inspector 里指定时自动加载。
const HOVER_TEXTURE_PATH := "res://character/assets/grab_hover.png"
## 正抓着东西时的图标。没在 Inspector 里指定时自动加载。
const HOLD_TEXTURE_PATH := "res://character/assets/grab.png"
## 准心的三种状态。
enum GrabState {
NONE, ## 没指着可拖拽物体
HOVER, ## 指着可拖拽物体(还没抓)
HOLDING, ## 正抓着东西
}
@export_group("点")
## 点的颜色
@export var dot_color: Color = Color(1, 1, 1, 0.9):
set(v):
dot_color = v
queue_redraw()
## 点的半径(像素)
@export_range(0.5, 12.0, 0.5) var dot_radius: float = 2.5:
set(v):
dot_radius = v
queue_redraw()
@export_group("手型")
## 指着可拖拽物体时的图标。留空则自动加载 grab.png
@export var hover_texture: Texture2D:
set(v):
hover_texture = v
queue_redraw()
## 正抓着东西时的图标。留空则自动加载 grab_hover.png
@export var hold_texture: Texture2D:
set(v):
hold_texture = v
queue_redraw()
## 图标显示的边长(像素)。素材是 32×32,默认 32 就是 1:1 不缩放,像素最清晰。
@export_range(8.0, 96.0, 1.0) var hand_size: float = 32.0:
set(v):
hand_size = v
queue_redraw()
## 图标着色。素材自带颜色,默认 Color.WHITE 表示按原样显示(乘法调色)。
@export var icon_modulate: Color = Color(1, 1, 1, 1):
set(v):
icon_modulate = v
queue_redraw()
## 是否把同一张图同步给系统鼠标指针。
## 游戏内鼠标是 MOUSE_MODE_CAPTURED 锁定隐藏的,这行只在鼠标可见时(Esc / 菜单)才有可见效果。
@export var sync_os_cursor: bool = true
@export_group("描边")
## 是否给点画描边(浅色背景上也看得清)
@export var outline: bool = true:
set(v):
outline = v
queue_redraw()
## 描边颜色
@export var outline_color: Color = Color(0, 0, 0, 0.6):
set(v):
outline_color = v
queue_redraw()
## 描边宽度(像素)
@export_range(0.5, 4.0, 0.5) var outline_width: float = 1.0:
set(v):
outline_width = v
queue_redraw()
## 当前状态。
var _state: int = GrabState.NONE
func _ready() -> void:
# 准心不吃鼠标事件,否则会挡住后面的 UI / 拖拽
mouse_filter = Control.MOUSE_FILTER_IGNORE
if hover_texture == null and ResourceLoader.exists(HOVER_TEXTURE_PATH):
hover_texture = load(HOVER_TEXTURE_PATH)
if hold_texture == null and ResourceLoader.exists(HOLD_TEXTURE_PATH):
hold_texture = load(HOLD_TEXTURE_PATH)
queue_redraw()
func _notification(what: int) -> void:
# 窗口尺寸变化时重画(stretch 模式下实时跟着变)
if what == NOTIFICATION_RESIZED:
queue_redraw()
## 由 Player 调用,切换准心状态(GrabState)。
func set_grab_state(state: int) -> void:
if state == _state:
return
_state = state
if sync_os_cursor and not Engine.is_editor_hint():
_sync_os_cursor()
queue_redraw()
## 某个状态该用哪张图。公开出来是为了让系统指针复用同一份素材,不必两处各加载一遍。
func texture_for_state(state: int) -> Texture2D:
match state:
GrabState.HOVER:
return hover_texture if hover_texture != null else hold_texture
GrabState.HOLDING:
return hold_texture if hold_texture != null else hover_texture
_:
return null
func _draw() -> void:
var c := size * 0.5 # 控件中心 = 屏幕中心
var tex := texture_for_state(_state)
if tex != null:
_draw_icon(c, tex)
else:
_draw_dot(c)
## 一个点(可选描边)
func _draw_dot(c: Vector2) -> void:
if outline and outline_width > 0.0:
draw_circle(c, dot_radius + outline_width, outline_color)
draw_circle(c, dot_radius, dot_color)
## 手型图标:居中、按原始宽高比缩放(非方形素材不会被拉变形)。
func _draw_icon(c: Vector2, tex: Texture2D) -> void:
var tex_size := Vector2(tex.get_width(), tex.get_height())
var k := hand_size / maxf(tex_size.x, tex_size.y)
var draw_size := tex_size * k
draw_texture_rect(tex, Rect2(c - draw_size * 0.5, draw_size), false, icon_modulate)
## 把当前状态的图标同步给系统鼠标指针。热点放在指尖附近,
## 这样「点下去」的位置落在手指上,而不是图标左上角。
func _sync_os_cursor() -> void:
var tex := texture_for_state(_state)
if tex == null:
Input.set_custom_mouse_cursor(null, Input.CURSOR_ARROW)
return
var hotspot := Vector2(tex.get_width() * 0.5, tex.get_height() * 0.12)
Input.set_custom_mouse_cursor(tex, Input.CURSOR_ARROW, hotspot)