#Isometric 16-Directional Movement in Godot

15 messages · Page 1 of 1 (latest)

static jay
#

Hello everyone, I'm working on enhancing character movement in my isometric game and need some help. Currently, my script supports basic directional movement (right, left, down, up, and diagonals like down-right, up-right, etc.). However, I want to upgrade this to support smooth movement across 16 precise directions (0°, 22°, 45°, 67°, 90°, 112°, 135°, 157°, 180°, 202°, 225°, 247°, 270°, 292°, 315°, 337°). This will allow me to match the character's movement more accurately with corresponding sprites for each direction. Does anyone have advice or examples on how to implement this finer directional control in Godot? Here's my current script that identifies the basic eight directions. Any guidance on refining it to handle 16 directions smoothly would be greatly appreciated!

extends CharacterBody2D
const SPEED = 3000

func _physics_process(delta):
    var direction = Input.get_vector("ui_left", "ui_right", "ui_up", "ui_down")
    if direction:
        self.velocity = direction * SPEED * delta
    else:
        self.velocity = Vector2.ZERO

    if vel == Vector2.ZERO:
        print("Character is stationary")
    else:
        if vel.x > 0 and vel.y == 0:
            print("Moving right")
        elif vel.x < 0 and vel.y == 0:
            print("Moving left")
        elif vel.y > 0 and vel.x == 0:
            print("Moving down")
        elif vel.y < 0 and vel.x == 0:
            print("Moving up")
        elif vel.x > 0 and vel.y > 0:
            print("Moving down-right")
        elif vel.x > 0 and vel.y < 0:
            print("Moving up-right")
        elif vel.x < 0 and vel.y > 0:
            print("Moving down-left")
        elif vel.x < 0 and vel.y < 0:
            print("Moving up-left")
quiet agate
# static jay Hello everyone, I'm working on enhancing character movement in my isometric game...

Rather than hand-coding in the directions, you would want to first create a new function to handle it. The process would be something along the lines of:

  • Make sure the input is normalized (get_vector() should be)
  • Convert your angle to degrees, should be able to first use atan2(direction.x, direction.y) to get the angle in radians, and then do rad_to_deg( the atan2 result)
  • You can then treat that degrees value as you would any other number, so you can round it to the nearest 22.5 by doing round( angle / 22.5f ) * 22.5f to get it snapped to the nearest 16 degree angle
  • You can then create a new Vector2, where the x value is cos( angle ) and the y value is sin( angle )
quiet agate
# static jay Hello everyone, I'm working on enhancing character movement in my isometric game...
    Vector2 GetVerticalMovement(){
        Vector2 input = Input.GetVector("ui_left", "ui_right", "ui_up", "ui_down");

        if(input.X==0 && input.Y==0){ return Vector2.Zero; }
        
        // Calculate the angle in radians
        float angleRadians = Mathf.Atan2(input.Y, input.X);

        // Convert radians to degrees
        float angleDegrees = Mathf.RadToDeg(angleRadians);

        // Snap to the nearest 22.5-degree increment
        angleDegrees = Mathf.Round(angleDegrees / 22.5f) * 22.5f;

        // Convert back to radians
        angleRadians = Mathf.DegToRad(angleDegrees);

        // Calculate the new x and y components using the rounded angle (in radians)
        return new Vector2(
            Mathf.Cos(angleRadians),
            Mathf.Sin(angleRadians)
        ); 
    }```
Written in C# as that's what I'm comfortable with, but that works in Godot to snap angles to an increment. It **also** means if you change your mind about 16 directions in the future, you can just change the Round from 22.5f to whatever you want.
static jay
# quiet agate ```c# Vector2 GetVerticalMovement(){ Vector2 input = Input.GetVector...

Thanks a lot. You made my code so much simple. Here is the final result after your comments:

func _physics_process(delta):
    var direction = Input.get_vector("ui_left", "ui_right", "ui_up", "ui_down")
    
    if direction.length() > 0.0000000000001:
        var snaped_direction = snap_direction(direction)
        var snaped_angle = directions_degrees(snaped_direction)
        update_sprite_directions(snaped_angle)

func snap_direction(input_vector:Vector2):
    if input_vector == Vector2.ZERO:
        return Vector2.ZERO
    else:
        var angle_radians = input_vector.angle()
        var angle_degrees = rad_to_deg(angle_radians)
        angle_degrees = round(angle_degrees / 22.5) * 22.5
        angle_radians = deg_to_rad(angle_degrees)
        return Vector2(cos(angle_radians), sin(angle_radians))

func directions_degrees(direction):
    return int(rad_to_deg(direction.angle()))
    
func update_sprite_directions(degrees):
    idle_sprite.texture = Global.idle_collection[degrees]
alpine ferry
#

A crazy coincidence you where trying to solve the exact same problem as me, I used your code as the basis for mine - in my case I am converting the 45 degree angles to an integer index and pulling from an array of sprite sheets

func snap_direction(input_vector:Vector2):
    if input_vector == Vector2.ZERO:
        return 0.0
    else:
        var angle_radians = input_vector.angle()
        var angle_degrees = rad_to_deg(angle_radians)
        angle_degrees = round(angle_degrees / 45) * 45
        return angle_degrees

func face_direction(direction: Vector2) -> void:
    var snapped_direction = snap_direction(direction)
    var new_direction = int((snapped_direction) / 45)
    if new_direction < 0:
        new_direction += 8

    if new_direction == facing_direction:
        return

    facing_direction = new_direction as Direction
    texture_sprite.texture = sprite_sheets[facing_direction]
    texture_viewport.render_target_update_mode = SubViewport.UPDATE_ONCE
#

I had something kind of working before but it wasn't handling the transition between 0 and 7 correctly, the character would snap to the north east as soon as the direction rotated above the 0 mark - your solution handles it correctly

static jay
alpine ferry
#

I did try for a little while to avoid converting to degrees, but it was breaking my brain a little bit to think in radians

static jay
# alpine ferry I did try for a little while to avoid converting to degrees, but it was breaking...

here is my updated version.

func _physics_process(delta):
    var direction = Input.get_vector("ui_left", "ui_right", "ui_up", "ui_down")
    self.velocity = direction * SPEED * delta
    if direction.length() > 0.0000000000001:
        var snaped_direction = snap_direction(direction)
        update_sprite_directions(snaped_direction)
    move_and_slide()

func snap_direction(input_vector:Vector2):
    if input_vector == Vector2.ZERO:
        return Vector2.ZERO
    else:
        var angle_radians = input_vector.angle()
        var angle_degrees = rad_to_deg(angle_radians)
        angle_degrees = round(angle_degrees / 22.5) * 22.5
        return int(angle_degrees)
    
func update_sprite_directions(degrees):
    idle_sprite.texture = Global.idle_collection[degrees]
alpine ferry
#

I think my problem before was that I was converting to an absolute 0-360 value instead of keeping the -180-180 range that mirrors radians

quiet agate
#

Honestly I have three braincells, so I just put it into degrees and then back to radians- I'm sure one could figure out how to do it without having the conversion, but... at the end of the day, this isn't going to be a massive performance hit on it.

alpine ferry
#

☝️ this is where I landed as well

#

if it shows up on profiles I"ll do something about it, but till then this reads really well

quiet agate
#

I also don't think in general the code used is using any libraries that is heavier by much- mostly uses the math libraries, which are ran directly in C++ I believe, so it's not like you're going to see much of a performance hit

alpine ferry
#
func snap_direction(input_vector:Vector2):
    if input_vector == Vector2.ZERO:
        return Vector2.ZERO
    else:
        var angle_radians = input_vector.angle()
        var angle_degrees = rad_to_deg(angle_radians)
        angle_degrees = round(angle_degrees / 22.5) * 22.5
        return int(angle_degrees)

I noticed you are returning a Vector2 on the if but an int on the else