手掌指针
This commit is contained in:
+108
-2
@@ -1,11 +1,31 @@
|
||||
@tool
|
||||
class_name Crosshair
|
||||
extends Control
|
||||
## 简易准心:屏幕正中央的一个点,用 _draw() 实时绘制,不依赖任何贴图。
|
||||
## 准心 / 指针:屏幕正中央。
|
||||
## 三种状态:
|
||||
## 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):
|
||||
@@ -18,7 +38,37 @@ extends Control
|
||||
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
|
||||
@@ -36,10 +86,17 @@ extends Control
|
||||
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()
|
||||
|
||||
|
||||
@@ -49,9 +106,58 @@ func _notification(what: int) -> void:
|
||||
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)
|
||||
|
||||
Reference in New Issue
Block a user