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).




23 Comments

Avatar

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.

    Avatar

    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.

Avatar

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?

    Avatar

    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.

      Avatar

      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.

        Avatar

        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.

        Avatar

        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.

          Avatar

          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.

Avatar

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.

    Avatar

    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).

      Avatar

      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!

    Avatar

    Charlie · January 16, 2020 at 2:00 am

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

Avatar

angel · April 12, 2020 at 6:36 pm

hey, I am new in godot, y want to know how can i move my character (an area2D) with swipes, I’m making a game for android and I want to move the character by swipes, like angry birds, went I drag the character in any direction it will get shot to the oposite side, I don’t know how to do it

    Avatar

    Davide Pesce · April 12, 2020 at 10:30 pm

    Hi angel, I could not give you a comprehensive answer in a comment, but you gave me an idea for a tutorial! In the next few days I will write something about it.

    Avatar

    Davide Pesce · May 2, 2020 at 3:41 pm

    Given the limited free time available, it took me longer than expected… but if you are still interested, here you can find the tutorial on Angry Birds-style movement: How to make a slingshot using a RigidBody2D

Avatar

Juanma · June 7, 2020 at 11:41 am

If anyone is struggling with the “click and point”. you can find a possible solution in the godot docs https://docs.godotengine.org/en/stable/tutorials/2d/2d_movement.html#click-and-move

Avatar

Tim · July 29, 2020 at 1:09 pm

I don’t get it. Just tried to get started and tried to make the example from the godot doc (https://docs.godotengine.org/de/stable/getting_started/step_by_step/exporting.html).

Now I want simply to control my player with my finger on my android smartphone. It just has to follow it (like in the game Archero for example). Isn’t it the same principle as with the mouse?

I’m new to godot and I want to try some ideas for a smartphone app but for that I have to understand this kind of movement 😀

    Avatar

    Davide Pesce · July 29, 2020 at 3:42 pm

    Godot “translates” touch inputs into equivalent mouse inputs, so what works for mouse also works for touch. The code on the page you linked does what you want, I didn’t understand exactly how I can help you…

      Avatar

      Tim · July 30, 2020 at 7:02 am

      The code on the page let’s the player just walk to the point where I touched. I don’t want letting the player walk to a point which I touched. I want the player follow my finger while I touch the touchscreen.

        Avatar

        Davide Pesce · July 30, 2020 at 8:43 am

        OK! I got it! To follow your finger, add this variable to the script:

        var button_pressed = false

        and change the _input and _process function like this:

        func _input(event):
        	if event is InputEventMouseButton:
        		button_pressed = (event.button_index == BUTTON_LEFT and event.pressed)
        
        func _process(delta):
        	if button_pressed:
        		target = get_global_mouse_position()
        	else:
        		target = position
        	
        	var velocity = target - position
        	if velocity.length() > 10:
        		$AnimatedSprite.play()
        		velocity = velocity.normalized() * speed
        	else:
        		$AnimatedSprite.stop()
        		velocity = Vector2()
        
        	position += velocity * delta
        	position.x = clamp(position.x, 0, screen_size.x)
        	position.y = clamp(position.y, 0, screen_size.y)
        
        	if velocity.x != 0:
        		$AnimatedSprite.animation = "walk"
        		$AnimatedSprite.flip_v = false
        		$AnimatedSprite.flip_h = velocity.x < 0
        	elif velocity.y != 0:
        		$AnimatedSprite.animation = "up"
        		$AnimatedSprite.flip_v = velocity.y > 0
          Avatar

          Tim · July 30, 2020 at 9:35 am

          Works 🙂 Thank you very much for your help 😉

          Avatar

          Kavya · March 8, 2021 at 7:12 am

          something went wrong..

          in this line
          var velocity = target – position
          the identifier “target isn’t dclared in the current scope is shown

          my script is as follow
          extends KinematicBody2D
          # Player movement speed
          export var speed = 75
          var button_pressed = false
          # Player dragging flag
          var drag_enabled = false

          func _process(delta):
          if button_pressed:
          target = get_global_mouse_position()
          else:
          target = position

          var velocity = target – position
          if velocity.length() > 10:
          $AnimatedSprite.play()
          velocity = velocity.normalized() * speed
          else:
          $AnimatedSprite.stop()
          velocity = Vector2()

          position += velocity * delta
          position.x = clamp(position.x, 0, screen_size.x)
          position.y = clamp(position.y, 0, screen_size.y)

          if velocity.x != 0:
          $AnimatedSprite.animation = “walk”
          $AnimatedSprite.flip_v = false
          $AnimatedSprite.flip_h = velocity.x 0

          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:
          button_pressed = (event.button_index == BUTTON_LEFT and event.pressed)

          i am new to godot and I really need such tutorials
          Thanks!

Avatar

kavya · March 9, 2021 at 8:41 am

my player can be dragged but it does not animates
i had followed the 2d sprite animation to get the following codes

extends KinematicBody2D
# Player movement speed
export var speed = 75

# Player dragging flag
var drag_enabled = false

var last_direction = Vector2(0, 1)

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)
# Animate player based on direction
animates_player(direction)

func animates_player(direction: Vector2):
if direction != Vector2.ZERO:
# update last_direction
last_direction = direction

# Choose walk animation based on movement direction
var animation = get_animation_direction(last_direction) + “_walk”

# Play the walk animation
$Sprite.play(animation)
else:
# Choose idle animation based on last movement direction and play it
var animation = get_animation_direction(last_direction) + “_idle”
$Sprite.play(animation)

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

func get_animation_direction(direction: Vector2):
var norm_direction = direction.normalized()
if norm_direction.y >= 0.707:
return “down”
elif norm_direction.y <= -0.707:
return "up"
elif norm_direction.x = 0.707:
return “right”
return “down”

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