Skip to content

Instantly share code, notes, and snippets.

@bitwes
Last active May 20, 2021 18:48
Show Gist options
  • Save bitwes/98c4f2d5bb9b964fb8cbde7bc3eb35ae to your computer and use it in GitHub Desktop.
Save bitwes/98c4f2d5bb9b964fb8cbde7bc3eb35ae to your computer and use it in GitHub Desktop.
Place this in a new Control Node that has a (0, 0) rect_size. Set properties. Custom node outlines can be added in the array. Colors for each custom is kept in the other array.
# ------------------------------------------------------------------------------
# Usage
# Create a Control node in the scene you want to paint boxes for and set the
# script for the Control to this.
#
# Draw Stoppers, Ignores, Passsers:
# When enabled these options will draw rectangles for any object that contains
# the mouse when you click. Stoppers, Ignores, Passers refers to the
# value of mouse_filter.
#
# Disabled - Disables all drawing of boxes and disables mouse input processing.
#
# Draw Customs - This allows you to set node paths to always draw an object's
# rectangle. You can use Custom Colors to specify a color for each Draw
# Custom node you've setup.
#
# Custom Colors - Colors to be used with the corosponding index in Draw Customs.
# If Custom Colors does not have enough colors then a default color will be
# used. If Custom Colors has too many colors nobody really caares and so
# nothing special happens. Not evena print statement. It just doesn't
# matter one bit.
#
# Background
# I tried to make this object the one that draws all the boxes. I couldn't
# figure out how to resolve screen stretch mode scales. The boxes were always
# a little off if there was any stretching enabled. I changed the approach to
# making each of the objects themselves draw the box by connecting to the
# draw signal. It feels a little janky but does not appear to have any scaling
# issues.
# ------------------------------------------------------------------------------
extends Control
var _stoppers = []
var _ignores = []
var _passers = []
var _string_font = null
# input handling related methods
var _look_for = ["_input", "_gui_input", "_unhandled_input"]
# occasionally godot seems to hang on generating the text. I appear to have
# worked around this issue by using this dictionary to hold the texts that
# have already been made. IDK why.
var _obj_text = {}
export var draw_stoppers = true
export var draw_passers = true
export var draw_ignores = false
export var disabled = false
export var draw_customs = true
export(Array, NodePath) var customs = []
export(Array, Color) var custom_colors = []
# This dictionary helps prevent shape names from being printed on top of
# each other. This is cleared prior to every draw and populated as shape
# boxes are drawn. Only helps when boxes have the same position.
# key = the position that draw_string was called with
# value = the width of the text that was drawn at that location plus a spacer
var _drawn_name_locations = {}
const INDENT = "| "
var _screen_scale = Vector2(1, 1)
func _ready():
# get the default font.
var label = Label.new()
_string_font = label.get_font("")
label.free()
set_disabled(disabled)
_setup_customs()
func _setup_customs():
if(!draw_customs):
return
for i in range(customs.size()):
var c = Color(0, 0, 1)
if(i < custom_colors.size()):
c = custom_colors[i]
var cust = get_node(customs[i])
_connect_draw(cust, c, 3)
func _connect_draw(node, c, width):
var did_conn = false
if(node.has_signal("draw") and !node.is_connected("draw", self, "_on_obj_draw")):
node.connect("draw", self, "_on_obj_draw", [node, c, width])
node.update()
did_conn = true
elif(!node.has_signal("draw")):
print(node, " missing draw signal")
return did_conn
func _on_obj_draw(obj, c, width):
var the_rect = obj.get_global_rect()
the_rect.position -= obj.rect_global_position
obj.draw_rect(the_rect, c, false, width)
if(_string_font != null):
# Easier to read non-transparent text
var txt_color = Color(c)
txt_color.a = 1
var pos = Vector2(10, 10)
var text = str("[", obj.name, "]")
obj.draw_string(_string_font, pos, text, txt_color)
# func _draw_box(object, c, width):
# var r = object.get_global_rect()
# r.size = r.size * _screen_scale
# r.position -= get_global_position()
# var p = object.get_global_position() - get_global_position()
# draw_rect(r, c, false, width)
# if(_string_font != null):
# # Easier to read non-transparent text
# var txt_color = Color(c)
# txt_color.a = 1
# var pos = p + Vector2(2, 10)
# while(_drawn_name_locations.has(pos)):
# pos.x += _drawn_name_locations[pos]
# var text = str("[", object.name, "]")
# draw_string(_string_font, pos, text, txt_color)
# _drawn_name_locations[pos] = _string_font.get_string_size(text).x + 5
# func _draw_boxes(objects, c, width):
# for n in objects:
# _draw_box(n, c, width)
# func _draw_customs():
# var nodes = []
# for i in customs.size():
# var c = Color(0, 0, 1)
# if(i < custom_colors.size()):
# c = custom_colors[i]
# _draw_box(get_node(customs[i]), c, 2)
# func _draw():
# return
# _drawn_name_locations.clear()
# if(draw_ignores):
# _draw_boxes(_ignores, Color(0, 1, 0, .25), 9)
# if(draw_passers):
# _draw_boxes(_passers, Color(1, 1, 0, .25), 6)
# if(draw_stoppers):
# _draw_boxes(_stoppers, Color(1, 0, 0, .25), 3)
# if(draw_customs):
# _draw_customs()
func _make_list_of_local_methods(thing):
var to_return = ''
var methods = thing.get_method_list()
for method in methods:
# 65 indicates it is a local method (overload).
if(_look_for.has(method.name) and method.flags == 65):
to_return += str(method.name, " ")
return str("[", to_return, "]")
func _node2str(thing):
var to_return = ''
if(_obj_text.has(thing)):
to_return = _obj_text[thing]
else:
to_return = thing.name
to_return = str(_make_mouse_filter(thing), to_return)
if(thing.get_script() != null):
to_return += str("(", thing.get_script().resource_path, ")")
to_return += _make_list_of_local_methods(thing)
_obj_text[thing] = to_return
return to_return
func _make_mouse_filter(node):
var to_return = '<'
if(node.has_method("get_mouse_filter")):
var filter = node.get_mouse_filter()
var letter = ""
if(filter == 0):
letter = "S"
elif(filter == 1):
letter = "P"
elif(filter == 2):
letter = "I"
to_return += letter
to_return += '>'
return to_return
func _print_stoppers():
print(_stoppers.size(), " Stopper(s)")
for stopper in _stoppers:
print("* ", _node2str(stopper))
func _print_passers():
print(_passers.size(), " Passer(s)")
for passer in _passers:
print("* ", _node2str(passer))
func _clear_ers_array(arr):
for n in arr:
if(n.is_connected("draw", self, "_on_obj_draw")):
n.disconnect("draw", self, "_on_obj_draw")
n.update()
arr.clear()
func _add_to_ers(node):
var filter = node.get_mouse_filter()
if(filter == 0 and draw_stoppers):
_connect_draw(node, Color(1, 0, 0, .25), 3)
_stoppers.append(node)
elif(filter == 1 and draw_passers):
_connect_draw(node, Color(1, 1, 0, .25), 9)
_passers.append(node)
elif(filter == 2 and draw_ignores):
_connect_draw(node, Color(0, 1, 0, .25), 6)
_ignores.append(node)
func print_nodes_containing_pos(pos):
_clear_ers_array(_stoppers)
_clear_ers_array(_ignores)
_clear_ers_array(_passers)
_obj_text.clear()
print("------------------------------")
print("objects at ", pos)
_check_children_for_pos(get_parent(), pos)
_print_stoppers()
_print_passers()
print("------------------------------")
update()
func _check_children_for_pos(node, pos, indent = ''):
var kids = node.get_children()
for kid in kids:
if(kid.has_method("get_global_rect")):
# This doesn't take rotation into account. Could be better.
# Ingores invisible objects bc they cannot interfere with the mouse
if(kid.get_global_rect().has_point(pos) and kid.visible):
print(indent, _node2str(kid))
_add_to_ers(kid)
_check_children_for_pos(kid, pos, indent + INDENT)
else:
_check_children_for_pos(kid, pos, indent + INDENT)
func _input(event):
if(disabled):
return
if(event is InputEventMouseButton and event.pressed):
#print_nodes_containing_pos(event.position)
call_deferred("print_nodes_containing_pos", event.position)
func set_disabled(value):
disabled = value
set_process_input(!disabled)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment