# CursorManager —— 对齐 ClientVS22 mousemodule.py 的光标状态机。 # # 光标和“鼠标跟随物品”是两层:本节点只管理当前交互语义, # MouseController 负责跟随鼠标的物品图标。使用 40250 的 .sub 纹理注册真实光标, # 没有对应资源时回退到 Godot 系统光标。 extends Node signal changed(shape: String) const UiAssets = preload("res://ui/ui_assets.gd") const NORMAL := "NORMAL" const ATTACK := "ATTACK" const TARGET := "TARGET" const TALK := "TALK" const CANT_GO := "CANT_GO" const PICK := "PICK" const DOOR := "DOOR" const CHAIR := "CHAIR" const MAGIC := "MAGIC" const BUY := "BUY" const SELL := "SELL" const CAMERA_ROTATE := "CAMERA_ROTATE" const HSIZE := "HSIZE" const VSIZE := "VSIZE" const HVSIZE := "HVSIZE" const ITEM := "ITEM" const ALL_CURSORS := [ NORMAL, ATTACK, TARGET, TALK, CANT_GO, PICK, DOOR, CHAIR, MAGIC, BUY, SELL, CAMERA_ROTATE, HSIZE, VSIZE, HVSIZE, ITEM ] var _shape := NORMAL var _assets_root := "" var _custom_cursors_registered := false var _cursor_images: Dictionary = {} func setup(assets_root: String) -> void: _assets_root = assets_root _register_custom_cursors() func _register_custom_cursors() -> void: if _assets_root.is_empty(): return var cursors := { NORMAL: "ETC/ymir work/ui/cursor/cursor.sub", ATTACK: "ETC/ymir work/ui/cursor/cursor_attack.sub", TARGET: "ETC/ymir work/ui/cursor/cursor_attack.sub", TALK: "ETC/ymir work/ui/cursor/cursor_talk.sub", CANT_GO: "ETC/ymir work/ui/cursor/cursor_no.sub", PICK: "ETC/ymir work/ui/cursor/cursor_pick.sub", DOOR: "ETC/ymir work/ui/cursor/cursor_door.sub", CHAIR: "ETC/ymir work/ui/cursor/cursor_chair.sub", MAGIC: "ETC/ymir work/ui/cursor/cursor_chair.sub", BUY: "ETC/ymir work/ui/cursor/cursor_buy.sub", SELL: "ETC/ymir work/ui/cursor/cursor_sell.sub", CAMERA_ROTATE: "ETC/ymir work/ui/cursor/cursor_camera_rotate.sub", HSIZE: "ETC/ymir work/ui/cursor/cursor_hsize.sub", VSIZE: "ETC/ymir work/ui/cursor/cursor_vsize.sub", HVSIZE: "ETC/ymir work/ui/cursor/cursor_hvsize.sub", } var shape_to_godot := { NORMAL: Input.CURSOR_ARROW, ATTACK: Input.CURSOR_CROSS, TALK: Input.CURSOR_POINTING_HAND, PICK: Input.CURSOR_CAN_DROP, CANT_GO: Input.CURSOR_FORBIDDEN, BUY: Input.CURSOR_DRAG, SELL: Input.CURSOR_MOVE, DOOR: Input.CURSOR_HELP, CHAIR: Input.CURSOR_WAIT, CAMERA_ROTATE: Input.CURSOR_BDIAGSIZE, HSIZE: Input.CURSOR_HSIZE, VSIZE: Input.CURSOR_VSIZE, HVSIZE: Input.CURSOR_FDIAGSIZE, } var hotspots := { HSIZE: Vector2(16, 16), VSIZE: Vector2(16, 16), HVSIZE: Vector2(16, 16), } for name in cursors: var rel: String = cursors[name] var tex: Texture2D = UiAssets.load_tex(_assets_root, rel) if tex != null: var img: Image = _extract_image(tex) if img != null: _cursor_images[name] = img var hs: Vector2 = hotspots.get(name, Vector2.ZERO) if shape_to_godot.has(name): Input.set_custom_mouse_cursor(img, shape_to_godot[name], hs) _custom_cursors_registered = true func _extract_image(tex: Texture2D) -> Image: if tex is AtlasTexture: var at := tex as AtlasTexture if at.atlas: var base_img: Image = at.atlas.get_image() if base_img: var r := Rect2i(int(at.region.position.x), int(at.region.position.y), int(at.region.size.x), int(at.region.size.y)) return base_img.get_region(r) elif tex: return tex.get_image() return null func set_cursor(shape: String) -> void: if shape not in ALL_CURSORS: shape = NORMAL _shape = shape Input.set_default_cursor_shape(_godot_shape(shape)) if _cursor_images.has(shape): var hs := Vector2(16, 16) if shape in [HSIZE, VSIZE, HVSIZE] else Vector2.ZERO Input.set_custom_mouse_cursor(_cursor_images[shape], Input.CURSOR_ARROW, hs) changed.emit(shape) func get_cursor() -> String: return _shape func reset() -> void: set_cursor(NORMAL) func _godot_shape(shape: String) -> Input.CursorShape: match shape: ATTACK, TARGET: return Input.CURSOR_CROSS PICK: return Input.CURSOR_CAN_DROP TALK: return Input.CURSOR_POINTING_HAND DOOR: return Input.CURSOR_HELP CHAIR, MAGIC: return Input.CURSOR_WAIT BUY: return Input.CURSOR_DRAG SELL: return Input.CURSOR_MOVE CAMERA_ROTATE: return Input.CURSOR_BDIAGSIZE HSIZE: return Input.CURSOR_HSIZE VSIZE: return Input.CURSOR_VSIZE HVSIZE: return Input.CURSOR_FDIAGSIZE CANT_GO: return Input.CURSOR_FORBIDDEN ITEM: return Input.CURSOR_DRAG _: return Input.CURSOR_ARROW