# GroundItems (P2) —— 世界里的掉落物:GC_ITEM_GROUND_ADD/DEL → 小物体 + 名条 + 拾取。 # # var gi := preload("res://ui/ground_items.gd").new() # add_child(gi) # gi.setup(m2client, mount_node, player_getter, proto, item_list) # # 按拾取键时: gi.try_pickup() -> 捡最近的一个( Node3D func setup(m2client: Node, mount_node: Node3D, player_getter: Callable, proto_node: Node = null, il: RefCounted = null) -> void: client = m2client mount = mount_node proto = proto_node item_list = il _player_getter = player_getter if client.has_signal("ground_item_added"): client.ground_item_added.connect(_on_added) if client.has_signal("ground_item_removed"): client.ground_item_removed.connect(_on_removed) # 已在场的(重连) if client.has_method("get_ground_items"): for d in client.get_ground_items(): _on_added(d) func _on_added(d: Dictionary) -> void: var vid := int(d.get("vid", 0)) if vid == 0: return var vnum := int(d.get("vnum", 0)) var owner := String(d.get("owner", "")) if _by_vid.has(vid): var existing: Node3D = _by_vid[vid] if is_instance_valid(existing): (existing.get_node("tag") as Label3D).text = _tag_text(vnum, owner) return var node := Node3D.new() node.name = "drop_%d" % vid node.position = d.get("pos", Vector3.ZERO) var mesh := MeshInstance3D.new() var box := BoxMesh.new() box.size = Vector3(0.25, 0.25, 0.25) mesh.mesh = box mesh.position.y = 0.3 var mat := StandardMaterial3D.new() mat.albedo_color = Color(1.0, 0.85, 0.3) mat.emission_enabled = true mat.emission = Color(0.6, 0.5, 0.1) mesh.material_override = mat node.add_child(mesh) var tag := Label3D.new() tag.name = "tag" tag.text = _tag_text(vnum, owner) tag.position.y = 0.75 tag.billboard = BaseMaterial3D.BILLBOARD_ENABLED tag.no_depth_test = true tag.pixel_size = 0.005 tag.modulate = Color(1.0, 0.9, 0.5) node.add_child(tag) node.set_meta("vid", vid) mount.add_child(node) _by_vid[vid] = node func _on_removed(vid: int) -> void: var n: Node3D = _by_vid.get(vid, null) if n: _by_vid.erase(vid) n.queue_free() func clear_for_map_change() -> void: for vid in _by_vid.keys(): var node: Node3D = _by_vid[vid] if is_instance_valid(node): node.queue_free() _by_vid.clear() func _process(dt: float) -> void: var p: Node3D = _player_getter.call() if _player_getter.is_valid() else null if p == null: return var t := Time.get_ticks_msec() / 1000.0 for vid in _by_vid: var n: Node3D = _by_vid[vid] if is_instance_valid(n): var m := n.get_child(0) as MeshInstance3D if m: m.rotation.y = t * 2.0 # 近了名条高亮 var near := p.global_position.distance_to(n.global_position) <= PICKUP_RANGE (n.get_node("tag") as Label3D).modulate = Color(0.4, 1.0, 0.4) if near else Color(1.0, 0.9, 0.5) # 捡最近的一个(范围内)。返回捡的 vid,0 = 没有。 func try_pickup() -> int: var p: Node3D = _player_getter.call() if _player_getter.is_valid() else null if p == null or client == null: return 0 var best_vid := 0 var best_d := PICKUP_RANGE for vid in _by_vid: var n: Node3D = _by_vid[vid] if not is_instance_valid(n): continue var d := p.global_position.distance_to(n.global_position) if d < best_d: best_d = d best_vid = int(vid) if best_vid != 0: client.pickup_item(best_vid) return best_vid func _name_for(vnum: int) -> String: if proto: var pd: Dictionary = proto.item(vnum) if not pd.is_empty(): return String(pd.get("locale_name", pd.get("name", "item %d" % vnum))) if item_list and item_list.has(vnum): return item_list.type_of(vnum) return "item %d" % vnum func _tag_text(vnum: int, owner: String) -> String: var text := _name_for(vnum) return "%s (%s)" % [text, owner] if not owner.is_empty() else text