RTS Selecting rectangle

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By EvilMax
:warning: Old Version Published before Godot 3 was released.

I would like to know how to create a selecting rectangle like in any RTS

I want to be able to:

  1. Click the left mouse button
  2. Drag my mouse to another location
  3. Every unit that is inside the screen-aligned rectangle given by the starting and ending points is selected

PS:
It would be nice if you propose a way for both 2D and 3D.

:bust_in_silhouette: Reply From: sangano

Hi,

I’m trying to do the same, I have some ideas to make this, maybe can help you.

1.- Create a 2d scene with a script who extends a Node2d to draw the rectangle.
2.- Add the 2d scene to a new one (in my case 3d) to see the draw
3.- Instance the script from the 2d scene in the 3d scene to get the rectangle(Rect2) when I’m dragging, , with this I will check every(charcaters) object from my 3d scene and see if they are inside of the rectangle.

When I have some of this working I will post the code.

Can you please post the code?

Aaron Franke | 2018-07-17 23:34

:bust_in_silhouette: Reply From: ulty

Here is some code I’m using to create a rectangle on a control node to select its texture button children

func recy(event): # Rectangle draw test
	if(event.type == InputEvent.MOUSE_BUTTON):
		if(event.button_index==1):
			if(event.is_pressed()):
				from  = event.pos
				to    = event.pos
				multi_select=true		
			else:
				multi_select=false		# done remove rect
				update()				# draw call without rect 
	elif(event.type == InputEvent.MOUSE_MOTION): 	
		if(multi_select):							
			to = event.pos	# update position and
			update()		# draw






# draw and select here
func _draw():

	if(multi_select):
		var tl = Vector2(min(to.x,from.x),min(to.y,from.y))
		var br = Vector2(max(to.x,from.x),max(to.y,from.y))
		var tr = Vector2(br.x,tl.y)
		var bl = Vector2(tl.x,br.y)
		var my = Rect2(tl, br-tl )
		
		draw_rect( my, Color(0,1,0,0.2))
		
		draw_line( tl, tr, Color(0,1,0) )
		draw_line( tl, bl, Color(0,1,0) )
		draw_line( br, tr, Color(0,1,0) )
		draw_line( br, bl, Color(0,1,0) )
		
		my.pos=tl+get_global_pos() # the center container prevents me from getting an easy rect so i have to ask the button for its global rect and terfore make my mose rect global aswell
		
		
		for i in get_node("radar").get_children():
			if(i.button):
				i.image.set_modulate(global.cl)
				if(my.intersects(i.button.get_global_rect() )):
					i.image.set_modulate(global.ch)
					if(-1==selec_rect_items.find(i)):
						selec_rect_items.append(i)
				else:
					if(-1!=selec_rect_items.find(i)):
						selec_rect_items.erase( i )
	else:
#		for i in get_node("radar").get_children():
#			if(i.button):
#				pass
		for i in selec_rect_items:
			i.image.set_modulate(global.cl)
			i.set_selected(true)
		selec_rect_items.clear()
:bust_in_silhouette: Reply From: ulty

Took me some time but here is a way to select units in 2D via Ray Casts:
http://docs.godotengine.org/en/latest/tutorials/ray-casting.html
First the single click then the rectangle you have asked about.
Simple raycast:

var results  = get_world_2d().get_direct_space_state().intersect_point( global_mouse_pos, 1 )

next, rectangle raycast:

func test(pos_i):	
# called by a controlnode with the current global mouse position
var para = Physics2DShapeQueryParameters.new()
var rect = RectangleShape2D.new()
var rect_size = Vector2(100,100) # 2*the size of the rectagle
rect.set_extents(rect_size )
para.set_shape( rect )
para.set_transform( Matrix32(Vector2(1,0),Vector2(0,1), pos_i+rect_size))
var result  = get_world_2d().get_direct_space_state().intersect_shape( para  )
if !result.empty():
	for i in result:
		print (i.collider.get_name())
		i.collider.get_node("Sprite").set_modulate(Color(randf(),randf(),randf()))
		
else:
	print("nobody under rect")

Um… what about Area2D
Also, this is not a __ray__­cast, it is, at best a shape intersection. (or if you wish, a shapecast)

Bojidar Marinov | 2016-04-05 14:40