Search code examples
godotgdscript

rotation of character by finger


Task is to make a rotation of character around its vertical axis. as input we want to use a round motion by finger at any place of screen. we tried to use two methods firts by using ray but then you need to drag finger around character ( working but not like we need )

func _process(_delta):
    look_at_cursor()

func look_at_cursor():
    var player_position = global_transform.origin
    var drop_plane = Plane(Vector3(0, 1, 0), player_position.y)
    var mouse_position = get_viewport().get_mouse_position()
    var ray_length  = 1000
    var ray_start = $Position3D/Camera.project_ray_origin(mouse_position)
    var end_ray = ray_start + $Position3D/Camera.project_ray_normal(mouse_position) * ray_length
    var cursor_pos = drop_plane.intersects_ray(ray_start, end_ray)
    look_at(cursor_pos, Vector3.UP)
    shoot_point = end_ray

and second one is with InputEventScreenDrag, its working but only in one direction. help here plz.

func _input(event):
    if event is InputEventScreenDrag:
            var rotx = event.relative.x * rot_speed 
            var roty = event.relative.y * rot_speed
            #if rotx < 0:
            #    rotx = - rotx
            #if roty < 0:
            #    roty = - roty
            #rotate_y(rotx+roty)

Solution

  • This took me a while to figure out between testing and solving some jitter problem… I'll explain.

    First of all, I decided to start with the technique I describe in Holding screen touch in godot. So the start of my _input looks like this:

    func _input(event:InputEvent):
        if not event is InputEventMouse\
          and not event is InputEventScreenDrag\
          and not event is InputEventScreenTouch:
            return
    
        var is_left_click:bool = event is InputEventMouse\
          and ((event as InputEventMouse).button_mask & BUTTON_LEFT) != 0
        var is_drag := event is InputEventScreenDrag\
          or (event is InputEventMouseMotion and is_left_click)
    

    And for the rotation, I decided to use the concept of curl. And by that I mean, I'll be using the cross product to figure out in what direction to rotate. This way we don't need to define a center of rotation (so you can do circles in any part of the screen and it should still work).

    Let us declare curl:

    var curl:float = 0.0
    

    And since we will be doing cross product, we need to hold the last relative, so we can do a cross product with the current one. So declare last_relative:

    var last_relative:Vector3 = Vector3.ZERO
    

    And let us do the cross product:

        var relative := Vector3(event.relative.x, event.relative.y, 0).normalized()
        curl = relative.cross(last_relative).z
        last_relative = relative
    

    However, we need to make sure to reset that for the next drag. We will set it when is_drag is false (that is on press and on release):

        if is_drag:
            var relative := Vector3(event.relative.x, event.relative.y, 0).normalized()
            curl = relative.cross(last_relative).z
            last_relative = relative
        else:
            last_relative = Vector3.ZERO
            curl = 0.0
    

    Next issue on the table is rotation speed. I ended up using _physics_process. Which also allows us to use delta, so that rotation speed is stable.

    Speaking of rotation speed, declare rot_speed:

    var rot_speed:float = TAU
    

    TAU means one rotation per second.

    And now we can write _physics_process:

    func _physics_process(delta: float) -> void:
        rotate_y(sign(curl) * rot_speed * delta)
    

    Next up is jitter. One part of it was input emulation. So I disabled it. But that was not enough. To fully solve jitter, _input will not write curl, but target_curl instead.

    First declare target_curl:

    var target_curl:float = 0.0
    

    And then set it in _input:

        if is_drag:
            var relative := Vector3(event.relative.x, event.relative.y, 0).normalized()
            target_curl = relative.cross(last_relative).z
            last_relative = relative
        else:
            last_relative = Vector3.ZERO
            target_curl = 0.0
    

    Now, we will ease curl into target_curl in physics_process:

    func _physics_process(delta: float) -> void:
        if target_curl > curl:
            curl += delta
            if curl > target_curl:
                curl = target_curl
        elif target_curl < curl:
            curl -= delta
            if curl < target_curl:
                curl = target_curl
    
        rotate_y(sign(curl) * rot_speed * delta)
    

    Finally, when the user is holding position (not dragging), we don't get any _input. Which means the user can do a little arc, and hold position, and the physics body would continue to rotate. We solve that by erasing target_curl every physics frame.

    Complete code listing:

    extends KinematicBody
    
    var rot_speed:float = TAU
    var last_relative:Vector3 = Vector3.ZERO
    var curl:float = 0.0
    var target_curl:float = 0.0
    
    func _input(event:InputEvent):
        if not event is InputEventMouse\
          and not event is InputEventScreenDrag\
          and not event is InputEventScreenTouch:
            return
    
        var is_left_click:bool = event is InputEventMouse\
          and ((event as InputEventMouse).button_mask & BUTTON_LEFT) != 0
        var is_drag := event is InputEventScreenDrag\
          or (event is InputEventMouseMotion and is_left_click)
    
        if is_drag:
            var relative := Vector3(event.relative.x, event.relative.y, 0).normalized()
            target_curl = relative.cross(last_relative).z
            last_relative = relative
        else:
            last_relative = Vector3.ZERO
            target_curl = 0.0
    
    func _physics_process(delta: float) -> void:
        if target_curl > curl:
            curl += delta
            if curl > target_curl:
                curl = target_curl
        elif target_curl < curl:
            curl -= delta
            if curl < target_curl:
                curl = target_curl
    
        rotate_y(sign(curl) * rot_speed * delta)
        target_curl = 0.0
    

    Tested on Windows with mouse, and on Android with touch.