164 lines
5.1 KiB
GDScript
164 lines
5.1 KiB
GDScript
@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)
|