Implement a kind of drag and move action

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By dancaer69

I’m new in godot and I have some experience with python. I’ve made a word game before some years and I’m trying to recreate it in godot. I’ve seen a lot of similar word games in android which have the function I would like to implement. I uploaded an image to visualize what I’m trying to do.

I want to have 7 buttons which will have a letter each and be inside an area in a circle(kind of). Then by pressing the mouse button(or tap and hold on touchscreen), I want to be able to move from the pressed button to the others and every time contact with another button to add it’s letter, so to create words. And when release mouse button(or finger) to register the current word. And also to draw a line to visualize the connections as you can see in picture.

But I don’t know how to do it and which is the best and easier way, so I’m stuck. I generally have some difficulties because godot has a lot of stuff and it’s very different from app development approach and ides(at least from my little experience). I’ve already added some scenes and functionality, like display a hud with score(using the godot’s gui tutorial as guide), and a countdown timer. Now I need to add the buttons, but I don’t know how to do it(and If I can use buttons and sprites or some other object) so to be possible to implement this kind of movement.

:bust_in_silhouette: Reply From: clemens.tolboom

You can use

  1. buttons to begin with which you can later theme on or make them draw themselves.
  2. use a Line2D to register the mouse moves when left mouse is down.

This will get you started I guess. Make sure project setting Emulate Touch from Mouse is enabled

extends Node2D

onready var line:Line2D = $Line2D

func _ready():
	line.clear_points()

	var _d
	_d = $Button.connect("mouse_entered", self, '_on_Button_mouse_entered', [$Button])
	_d = $Button2.connect("mouse_entered", self, '_on_Button_mouse_entered', [$Button2])
	_d = $Button3.connect("mouse_entered", self, '_on_Button_mouse_entered', [$Button3])
	_d = $Button4.connect("mouse_entered", self, '_on_Button_mouse_entered', [$Button4])

	_d = $Button.connect("mouse_exited", self, '_on_Button_mouse_exited', [$Button])
	_d = $Button2.connect("mouse_exited", self, '_on_Button_mouse_exited', [$Button2])
	_d = $Button3.connect("mouse_exited", self, '_on_Button_mouse_exited', [$Button3])
	_d = $Button4.connect("mouse_exited", self, '_on_Button_mouse_exited', [$Button4])

func _input(event:InputEvent):
	if event is InputEventMouseMotion:
		return

	if event is InputEventMouseButton:
		var e:InputEventMouseButton = event
		if e.button_mask == BUTTON_LEFT:
			if event.is_pressed():
				line.add_point(event.position)
		else:
			# process line
			line.clear_points()

	if event is InputEventScreenDrag:
		# Emulate Touch from Mouse
		line.add_point(event.position)

func _on_Button_mouse_entered(b:Button):
	print("Enter: ", b.text)

func _on_Button_mouse_exited(b:Button):
	print("Exit: ", b.text)

Thanks,
I’ve made some progress. I created a texturebutton scene with label child and then I used inherited scenes for the seven buttons which I added to the area2d scene. I also set the text of the buttons with random letters. I decided to change color of the text(at least for now), but my main problem is how to implement the mouse movement on buttons.
I’m reading the documentation about Input examples and Mouse Motion, so I hope to figure it out. Any extra help it could be useful though.

dancaer69 | 2021-02-08 19:06

I’ve added a code snippet with gives line drawing using Line2D component.

clemens.tolboom | 2021-02-08 20:25

I forgot to add and event.is_pressed() … will check back later this week.

clemens.tolboom | 2021-02-08 20:30

I added some more code using is_pressed and clearing the points. Please mark this as the answer if satisfied :wink:

clemens.tolboom | 2021-02-08 21:44

Thanks again, this was very helpful.
I get an error though until I added a Line2D object in my Board.tscn, which is an Area2D and there I have the 7 button inherited scenes.
I have a main game scene and I’ve added there the Board.tscn, and I added the code to the main game scene script.
I replaced the:

onready var line:Line2D = $Line2D

(which I thought that creates a new Line2D)
with:

onready var line = $Board/Line2D

and then worked fine.

dancaer69 | 2021-02-09 08:26

I’m curious how you transform/reduce the Line2D data into into the letter order. The points array gets very long.

Regarding where to put the code I suggest to add the script to the board (as that is responsible for letter handling) and when ‘done’ use a signal to tell the word to the parent. From my head (maybe incomplete) it looks something like

# Main scene.gd
func _ready():
    $Board.connect('word_selected', self, 'process_word')

func process_word(word:String):
    # Display word

# Board.gd
signal word_selected

func emit_word(word:String):
    emit_signal('word_selected', word)

clemens.tolboom | 2021-02-10 14:57

I didn’t, because I still try to find a way to detect and access to the other buttons after I click in one of them and move holding mouse button. I searched about this and tried some suggestions but I haven’t pass this problem yet, so to think about the line.
I tried area2d and mouse_entered/exited signals, which worked on a test project, but it doesn’t work without mouse filter. At the test project needed to use it for other controls, but I cannot do that because I have other buttons which don’t work afterwards.
Yesterday I found in a relative thread a suggestion which I use like this:

func findNodes():
var mousePos = get_viewport().get_mouse_position()
if mouse_pressed==true:
	for i in range(Letters().size()):
		if Letters()[i].get_global_rect().has_point(mousePos):
			$WordBoard/Label.set_text($WordBoard/Label.text + Letters()[i].text)
		break

Maybe can work with this way, but for now isn’t. If I put the function on “if event is_pressed”, works only for button which just pressed and not on mousemove.
If I put it on:

if event is InputEventMouseMotion:
    return

before the “return”, then It kind of works, but triggers more than once(while moving mouse inside button’s area), and If I remove the “break” in loop. With “break” detects only the pressed button.
So without the “break” need to find a way to stop multi trigger and also to run when click on a button, because now it triggers when I click anywhere and move mouse to a button area.
The code is in main scene’ s script.

dancaer69 | 2021-02-11 08:55

I have added the mouse_entered/exited for the Buttons (I have 4) and disabled them … the result is mouse_entered/exited event. Not sure that makes it better but you could use the mouse_entered events to collect letters right?

clemens.tolboom | 2021-02-12 14:03

Thanks,
I suspended the project for now and I’m following some more tutorials to get more familiar with godot’ s new stuff.
I just tried your suggestion now. This way mouse_entered/exited works, but again not only when mouse button is pressed.
And also I get a nil error when trying to get the text.
I don’t have Buttons, but TextureButtons with Labels as childs, so something there need to change.
EDIT:
I found how to get access to the labels. Needed to use “b:TextureButton” as type and then

b.get_child(1).text

to access the label.
But it works only when mouse is over a button. Not working with mouse pressed. Probably because the pressed button gets the focus so the entered/exited/ signals don’t emit.

dancaer69 | 2021-02-12 18:03

An update in case help some others.
I made some progress after some different approaches which didn’t work and seems that I basically achived what I wanted.
First I changed the texture buttons with kinematic bodies with sprites. Probably it could work with area2d(I tried it before but I succeed with the kinematic bodies).
In ButtonBase scene:

func _on_LetterButton_mouse_entered():
if WordUtils.dragging : 
	print("entered while mouse down")
	on_dragging()
else:
	#$AnimationPlayer.stop()
	print("released")
	
func _on_LetterButton_mouse_exited():
pass

func _on_LetterButton_input_event(viewport, event,  shape_idx):
if event is InputEventMouseButton:
	if event.button_index == BUTTON_LEFT and   event.pressed:
		on_dragging()
		
func on_dragging():
$Character.set("custom_colors/font_color",Color(0,2,0))
$AnimationPlayer.play("rise")
$CollisionShape2D.disabled = true
wordboard.set_text(wordboard.text + $Character.text)

and on Board scene(which contains the button instances):

func _ready():
letternodes = get_tree().get_nodes_in_group("letters")

func _input(event):
if event is InputEventMouseButton:
	if event.button_index == BUTTON_LEFT and not event.is_echo() and event.is_pressed():
		WordUtils.dragging = true
	else: 
		WordUtils.dragging = false
		at_stop_dragging()

func at_stop_dragging():
for i in letternodes:
	i.get_child(3).stop()
	   i.get_child(2).set("custom_colors/font_color",Color(1,1,1))
	i.get_child(1).disabled = false

With this I can press in one button and detect the others while dragging.

dancaer69 | 2021-02-27 12:00