In the post Godot Tutorial – Part 5: Player movement, we saw how to move the player using keyboard and joypad. In the comments of that tutorial, a reader asked for suggestions on how to move the player by dragging it around with the mouse. In this small bonus tutorial, we will see how to drag KinematicBody2D nodes.

Warning! This tutorial require the CollisionShape2D node added in Part 6: Physics and collisions tutorial, so don’t read this tutorial before you complete that!

Preparing player node for dragging

Let’s open the SimpleRPG project.

First thing, select the Player node. In the Inspector, in the properties inherited from CollisionObject2D, enables the Pickable option.

A pickable object can detect the mouse pointer entering/leaving, and if the mouse pointer is inside it, report input events through the _input_event() method. We will use the press and release of the left mouse button respectively to begin and end player dragging.

The script

Open Player’s script. First, we will need a variable to store whether dragging is active or not. Write this code next to the declaration of the speed variable:

# Player dragging flag
var drag_enabled = false

Now, we need to add two new functions to the script.

The first is _input_event(), which, as we mentioned earlier, is used to intercept mouse events that specifically involve the player.

func _input_event(viewport, event, shape_idx):
	if event is InputEventMouseButton:
		if event.button_index == BUTTON_LEFT:
			drag_enabled = event.pressed

The working of this function is simple: when an InputEventMouseButton event is detected (i.e. the press or release of a mouse button), we check that the button pressed is the left one. If so, the drag_enabled variable is set to the value of event.pressed: true if the button is pressed and false otherwise.

The second function we must add is _input(). This function (inherited from Node) is called when any input is detected. We must implement this function because it can happen that the mouse button is released when the pointer is outside the player. In that case, _input_event() does not receive the event, and the dragging procedure will not be completed correctly.

func _input(event):
	if event is InputEventMouseButton:
		if event.button_index == BUTTON_LEFT and not event.pressed:
			drag_enabled = false

The code is very similar to the previous function, but we only handle the release of the mouse button. Otherwise, player dragging would start by clicking anywhere on the screen.

Finally, in the _physics_process() function, insert this code immediately before calling move_and_collide():

# If dragging is enabled, use mouse position to calculate movement
if drag_enabled:
	var new_position = get_global_mouse_position()
	movement = new_position - position;

If drag_enabled is true, the function calculates player’s movement as the difference between the position of the mouse in global coordinates (obtained by calling the function get_global_mouse_position()) and the current position of Player. The move_and_collide() function will perform this movement, placing the player at the cursor position.

Limiting movement speed

The code we wrote works, but it allows us to move the player much faster than when we use the keyboard or the joypad. So, we want to limit the speed to the value set in the speed variable.

To do this, we need to check if the calculated movement is greater than what is possible at maximum speed. In that case, we need to recalculate the movement so that it’s in the same direction, but limited in length.

Enter this code before the move_and_collide() function, inside the if block:

if movement.length() > (speed * delta):
	movement = speed * delta * movement.normalized()

Now, the player can’t move faster than its maximum speed:

Below is the updated script code:

extends KinematicBody2D

# Player movement speed
export var speed = 75

# Player dragging flag
var drag_enabled = false

func _physics_process(delta):
	# Get player input
	var direction: Vector2
	direction.x = Input.get_action_strength("ui_right") - Input.get_action_strength("ui_left")
	direction.y = Input.get_action_strength("ui_down") - Input.get_action_strength("ui_up")
	
	# If input is digital, normalize it for diagonal movement
	if abs(direction.x) == 1 and abs(direction.y) == 1:
		direction = direction.normalized()
	
	# Calculate movement
	var movement = speed * direction * delta
	
	# If dragging is enabled, use mouse position to calculate movement
	if drag_enabled:
		var new_position = get_global_mouse_position()
		movement = new_position - position;
		if movement.length() > (speed * delta):
			movement = speed * delta * movement.normalized()
	
	# Apply movement
	move_and_collide(movement)

func _input_event(viewport, event, shape_idx):
	if event is InputEventMouseButton:
		if event.button_index == BUTTON_LEFT:
			drag_enabled = event.pressed

func _input(event):
	if event is InputEventMouseButton:
		if event.button_index == BUTTON_LEFT and not event.pressed:
			drag_enabled = false

Conclusions

In this tutorial, we have learned how to use mouse events to move a KinematicBody2D node by dragging it across the screen. With a few minor modifications, it is also possible to create a point and click movement system (I leave this as an exercise, but if you need help, leave a comment below).




12 Comments

Matt · January 3, 2020 at 5:15 pm

This code didn’t work for me. I basically had to modify the _input method to add the following else condition:

elif event.button_index == BUTTON_LEFT and event.pressed:
drag_enabled = true

I don’t think the _input_event is even needed, as in my debugging it never fired.

    Davide Pesce · January 6, 2020 at 8:18 pm

    Have you enabled the Pickable property of the Player node? The _input_event() function is called only if Pickable is enabled.

    Your solution does not implement dragging. With your code, the player moves by clicking anywhere in the window.

Charlie · January 12, 2020 at 9:47 pm

I have the same problem, and yeah I’ve triple checked to make sure Pickable is enabled. Do we have to manually connect the signal for this?

    Davide Pesce · January 13, 2020 at 7:26 am

    You don’t need to connect the signal, the _input_event function is called anyway. Try to check if at least one collision layer is selected for Player (you can find it in the Inspector, in the PhysicsBody2D section).By default, the first layer should be selected, but if for some reason it’s not, the input events do not work.

      Charlie · January 14, 2020 at 2:04 am

      Yep, that’s selected. Tried messing with different layers, doesn’t seem to make any difference. I can’t even get any console print output from the _input_event function.

        Davide Pesce · January 14, 2020 at 1:09 pm

        I should take a look at your project to be able to help you… if you can upload it somewhere (on Dropbox or Google Drive for example) and send me the link I will.

        Sharon · January 19, 2020 at 1:30 pm

        The issue is that part 5.1 builds on part 6 and not part 5.

        Part 6 add CollisionShape2D, which is needed for this code to work.

          Davide Pesce · January 19, 2020 at 7:41 pm

          This issue was fixed a few days ago thanks to Permagate’s comment. I’ve changed the order of the tutorials and there is now a warning at the beginning of this one.

Permagate · January 15, 2020 at 2:44 pm

I also encountered an issue where _input_event was not executed for some reasons. After mucking around, I found out the issues:

1. _input_event will work if both pickable flag in the KinematicBody2D is checked AND it has a CollisionShape2D child. This page is linked from Part 5. At that point, CollisionShape2D has not been added in the tutorial yet (it is added in the next chapter).

2. The other issue was when I added ColorRect node under Root node. This also prevents _input_event from firing. By default, its MouseFilter mode is set to stop, which means it will catch any mouse events and stop it from being handled by any other nodes. I fixed it by setting the mouse filter (In Inspector, Control -> Mouse -> Filter) to Ignore.

    Davide Pesce · January 15, 2020 at 3:43 pm

    1. _input_event will work if both pickable flag in the KinematicBody2D is checked AND it has a CollisionShape2D child. This page is linked from Part 5. At that point, CollisionShape2D has not been added in the tutorial yet (it is added in the next chapter).

    Yeah, you’re right! As soon as possible I update the tutorial to report this thing, thanks for the feedback!

    The other issue was when I added ColorRect node under Root node. This also prevents _input_event from firing. By default, its MouseFilter mode is set to stop, which means it will catch any mouse events and stop it from being handled by any other nodes. I fixed it by setting the mouse filter (In Inspector, Control -> Mouse -> Filter) to Ignore.

    It was written in the tutorial where the ColorRect is added:

    Then set the Mouse → Filter property to Ignore to prevent the ColorRect to intercept mouse events (this is required if you use the code to drag the player with the mouse).

      Permagate · January 16, 2020 at 12:36 am

      Hehe, now you know I haven’t completed the tutorial yet. Hopefully I can finish it soon. You have one of the better tutorial for godot beginner like me in my opinion, so thank you!

    Charlie · January 16, 2020 at 2:00 am

    Aha, yes that was the trick! (adding a CollisionSHape2D child) Thanks for pointing that out.

Leave a Reply

Your email address will not be published. Required fields are marked *

By continuing to browse this Website, you consent to the use of cookies. More information

This Website uses:

By continuing to browse this Website without changing the cookies settings of your browser or by clicking "Accept" below, you consent to the use of cookies.

Close