Search code examples
godot4

Godot 4 point and move system offset even though all sprites are 0,0


I'm a rookie making my first 2d game in Godot, which I'm drafting from a much smaller JS,CSS, & HTML version called Storie. I am attempting to create a Point-click-and move function into my game.

All was well in my main sprites scene, but when I tried to use it in my game scene, there was a huge offset frome where I clicked and where the sprite went

extends CharacterBody2D

@export var speed = 400


var target = position

func _input(event):
    if event.is_action_pressed("click"):
        target = get_global_mouse_position()

func _physics_process(_delta):
    velocity = position.direction_to(target) * speed
    # look_at(target)
    if position.distance_to(target) > 10:
        move_and_slide()

Oh, and also, I mistakenly switched to Godot 4 without realising the repercussions

Thank you, Dimitri


Solution

  • About mixing coordinate systems

    There are global coordinates, which are relative to the origin of the world. And there are local coordinates, which are relative to the parent node.

    Now, position is local coordinates, so this is local coordinates:

    var target = position
    

    And this get_global_mouse_position() is global coordinates. Thus, this is global coordinates:

    target = get_global_mouse_position()
    

    Therefore, target is switching coordinates system. Once it has the global coordinates of the mouse position, then this is mixing coordinates system:

    velocity = position.direction_to(target) * speed
    

    Thus, the result will result in an incorrect behavior. The same happens here:

    position.distance_to(target)
    

    Using global coordinates

    You could convert to local coordinates (e.g. using to_local), however the velocity should be based on global coordinates (also look_at, we will get to that).

    Thus, instead of using position which is in local coordinates, use global_position which is in global coordinates:

    extends CharacterBody2D
    
    @export var speed = 400
    
    
    var target = global_position
    
    func _input(event):
        if event.is_action_pressed("click"):
            target = get_global_mouse_position()
    
    func _physics_process(_delta):
        velocity = global_position.direction_to(target) * speed
        # look_at(target)
        if global_position.distance_to(target) > 10:
            move_and_slide()
    

    Using look_at

    By the way, look_at also wants global coordinates.

    Perhaps you want to move to inside the conditional (as there would be no direction to look at, if you are already at the position):

    func _physics_process(_delta):
        velocity = global_position.direction_to(target) * speed
        if global_position.distance_to(target) > 10:
            look_at(target)
            move_and_slide()
    

    Properly avoid overshooting the target

    You can compute how much the character would move.

    Do you remember speed is distance over time?

    Well, delta is the time. If speed = distance / delta and we solve for distance we have:

    var distance = speed * delta
    

    You could, for example, use in your check:

    func _physics_process(delta:float) -> void:
        velocity = global_position.direction_to(target) * speed
        var distance := speed * delta
        if global_position.distance_to(target) > distance:
            look_at(target)
            move_and_slide()
    

    However, I suggest you figure out which is the maximum speed the character can move this frame without overshooting the target:

    func _physics_process(delta:float) -> void:
        var distance_to_target := global_position.distance_to(target)
        var direction_to_target := global_position.direction_to(target)
        var max_speed := distance_to_target / delta
        velocity = direction_to_target * clampf(speed, 0.0, max_speed)
        look_at(target)
        move_and_slide()
    

    Note: clampf(speed, 0.0, max_speed) would be equivalent to minf(speed, max_speed assuming speed is positive to begin with. However, I believe clampf(speed, 0.0, max_speed) is more readable.

    The above should stop at the target. However, if you want to have a check, use is_zero_approx:

    func _physics_process(delta:float) -> void:
        var distance_to_target := global_position.distance_to(target)
        var direction_to_target := global_position.direction_to(target)
        if not is_zero_approx(distance_to_target):
            var max_speed := distance_to_target / delta
            velocity = direction_to_target  * clampf(speed, 0.0, max_speed)
            look_at(target)
            move_and_slide()