2D TopDown Movement and Animation hiccups

Godot Version

4.6.2 Stable

Question

Godot beginner here, go easy on me. Thanks in advance.

Issue 1: If I’m moving Right and then add Up and Down, everything behaves as expected but if I am facing Right (no velocity) and just press up or down, the sprite moves up and down and then flips to face the Left once I stop pressing the button. Additionally Left and Right trigger the animations just fine but Up or Down by themselves stay in the idle animation and just slide Up or Down.

To note: My player is separated into three AnimatedSprite2Ds: Body, Head, and Gun

Issue 2: Input: Down + Right = player moving down and right (diagonally) and the PlayerHead and PlayerGun rotate to match the angle. Continue pressing Down but switch to Left = the same thing, to the left, as wanted.
But if I press Sprint while doing the same thing, the player will move down + left but the head and gun rotate to point Up and Right.

Issue 3:
My code for rotating the head and gun to match the angle works on the Left, Right, and Diagonals but doesn’t do anything for just straight Up or Down. I feel like there’s a connection but I can’t see it.

I’m stumped.

My goal is to have 8 directions. The body can face in 2 directions, left and right. The head and gun can face in 2 directions, left and right, but can be rotated to “point” in all8 directions. I had this working at one point but between adding actual left frames, ditching flip_h in the process and trying to implement new features and systems, I completely broke it and had to start fresh. So here I am.

I do not want to post my code for anyone to see but I need some help so I’m sorry for anyone who sees it lol I’ve been at this project and others (basically the same one just restarted over and over) for like 10-12 hours a day for a solid week or so now so I’m willing to be that it’s a simple fix but I can’t look at code anymore, I’m positive that my brain is actually changing what I see to look right at this point lol

Here’s a video showcasing the issues. Pretty sure the quality is going to take a massive dive, so sorry in advance, hopefully it still gets the point across.

extends CharacterBody2D


var speed: int = 10
var speed_scale: float = 1.5
var direction: Vector2
var look_direction: Vector2 = Vector2.RIGHT
var is_sprinting: bool = false
var is_strafing: bool = false
var is_reloading: bool = false
var is_shooting: bool = false


func _physics_process(_delta: float) -> void:
	get_input()
	movement()
	move_and_slide()
	animation()

func get_input():
# DIRECTIONAL INPUT
#--------------------------------------------------------------
	direction = Input.get_vector("left", "right", "up", "down")
	if direction != Vector2.ZERO:
		velocity = direction * speed
		look_direction = direction
	else:
		velocity = Vector2.ZERO

# MOVEMENT INPUT
#--------------------------------------------------------------
	if Input.is_action_pressed("sprint"):
		is_sprinting = true
		velocity *= speed_scale
	else:
		is_sprinting = false

	if Input.is_action_pressed("strafe") and velocity !=Vector2.ZERO:
		is_strafing = true
		direction.x *= -1
		velocity.y = 0
	else:
		is_strafing = false

# ACTION INPUT
#--------------------------------------------------------------
	if Input.is_action_just_pressed("shoot"): #add shoot timer here
		print("shoot")
		shoot()

	if Input.is_action_just_pressed("reload") and not is_reloading:
		print("reloaded")
		reload()
		
func movement():
# SPRITE MOVEMENT-BASED ROTATION
#--------------------------------------------------------------
	#if LEFT and UP and is NOT Strafing
	if direction.x < 0 and direction.y < 0 and not is_strafing:
		$PlayerWeapon.rotation_degrees = 45
		$PlayerHead.rotation_degrees = 20
	#if LEFT and DOWN and is NOT Strafing
	elif direction.x < 0 and direction.y > 0 and not is_strafing:
		$PlayerWeapon.rotation_degrees = -45
		$PlayerHead.rotation_degrees = -20
	#if RIGHT and UP and is NOT Strafing
	elif direction.x > 0 and direction.y < 0 and not is_strafing:
		$PlayerWeapon.rotation_degrees = -45
		$PlayerHead.rotation_degrees = -20
	#if RIGHT and DOWN and is NOT Strafing
	elif direction.x > 0 and direction.y > 0 and not is_strafing:
		$PlayerWeapon.rotation_degrees = 45
		$PlayerHead.rotation_degrees = 20
	#if last dir_x was LEFT and we go down
	elif look_direction.x < 0 and direction.y == 1:
		$PlayerWeapon.rotation_degrees = -90
		$PlayerHead.rotation_degrees = -45
	#if last dir_x was RIGHT and we go down
	elif look_direction.x > 0 and direction.y == -1:
		$PlayerWeapon.rotation_degrees = -90
		$PlayerHead.rotation_degrees = -45
	elif look_direction.x > 0 and direction.y == 1:
		$PlayerWeapon.rotation_degrees = 90
		$PlayerHead.rotation_degrees = 45
	elif look_direction.x < 0 and direction.y == -1:
		$PlayerWeapon.rotation_degrees = 90
		$PlayerHead.rotation_degrees = 45
	#------------------------------------------------------------
	#rotation of the weapon sprite when strafing
	#------------------------------------------------------------
	#if LEFT and UP and IS Strafing
	elif direction.x < 0 and direction.y < 0 and is_strafing:
		$PlayerWeapon.rotation_degrees = 45
		$PlayerHead.rotation_degrees = 20
	#if LEFT and DOWN and IS Strafing
	elif direction.x < 0 and direction.y > 0 and is_strafing:
		$PlayerWeapon.rotation_degrees = -45
		$PlayerHead.rotation_degrees = -20
	#if RIGHT and UP and IS Strafing
	elif direction.x > 0 and direction.y < 0 and is_strafing:
		$PlayerWeapon.rotation_degrees = -45
		$PlayerHead.rotation_degrees = -20
	#if RIGHT and DOWN and IS Strafing
	elif direction.x > 0 and direction.y > 0 and is_strafing:
		$PlayerWeapon.rotation_degrees = 45
		$PlayerHead.rotation_degrees = 20
	#------------------------------------------------------------
	#this makes the rotation snap back to 0 when not pressing up or down
	else:
		$PlayerWeapon.rotation_degrees = 0
		$PlayerHead.rotation_degrees = 0

func animation():
	if is_shooting or is_reloading:
		return

	if velocity and not is_sprinting:
		if direction == Vector2.LEFT:
			$PlayerBody.play("walk_left")
			$PlayerWeapon.play("walk_left")
			$PlayerHead.play("walk_left")
		elif direction == Vector2.RIGHT:
			$PlayerBody.play("walk_right")
			$PlayerWeapon.play("walk_right")
			$PlayerHead.play("walk_right")
		elif direction.x < 0 and velocity.y != 0:
			$PlayerBody.play("walk_left")
			$PlayerWeapon.play("walk_left")
			$PlayerHead.play("walk_left")
		elif direction.x > 0 and velocity.y != 0:
			$PlayerBody.play("walk_right")
			$PlayerWeapon.play("walk_right")
			$PlayerHead.play("walk_right")

	if velocity and is_sprinting:
		if direction == Vector2.LEFT:
			$PlayerBody.play("run_left")
			$PlayerWeapon.play("run_left")
			$PlayerHead.play("run_left")
		elif direction == Vector2.RIGHT:
			$PlayerBody.play("run_right")
			$PlayerWeapon.play("run_right")
			$PlayerHead.play("run_right")

	if velocity == Vector2.ZERO:
		if look_direction.x > 0:
			$PlayerBody.play("idle_right")
			$PlayerWeapon.play("idle_right")
			$PlayerHead.play("idle_right")
		else:
			$PlayerBody.play("idle_left")
			$PlayerWeapon.play("idle_left")
			$PlayerHead.play("idle_left")

func shoot():
	is_shooting = true
	if look_direction.x > 0:
		$PlayerWeapon.play("shoot_right")
		$PlayerHead.play("shoot_right")
	elif look_direction.x < 0:
		$PlayerWeapon.play("shoot_left")
		$PlayerHead.play("shoot_left")
	await $PlayerHead.animation_finished
	await $PlayerWeapon.animation_finished
	is_shooting = false

func reload():
	is_reloading = true
	if look_direction.x > 0:
		$PlayerWeapon.play("reload_right")
		$PlayerHead.play("reload_right")
	elif look_direction.x < 0:
		$PlayerWeapon.play("reload_left")
		$PlayerHead.play("reload_left")
	await $PlayerHead.animation_finished
	await $PlayerWeapon.animation_finished
	is_reloading = false

You probably haven’t gotten any replies because your code looks like LLM code and people figure you won’t actually learn anything if they help you.

So the first thing to do was clean up your code. You have so much going on in it that it’s distracting. It’s hard to figure out your logic. I did what’s known as refactoring. It’s now actually 5 lines longer, but easier to read. I did a few things:

  1. I moved all your $NodePaths into @onready variables. This allows you to easily update the reference to a node on a single line. It also allows auto complete in the editor by using strict typing.
  2. I added constants for all of your text, removing the Magic Strings problem. Now every reference to them is hard coded. It saves memory when your game is running, and the names will pop up in auto complete.
  3. The next thing I did was create three helper functions for applying rotating, and movement and gun animations. You had the same code repeated over and over. I reduced those all to single line function calls. Just like you did in _physics_process(). This allowed me to start seeing patterns in your code - and where you broke patterns. (More below.)

Take a look at your refactored code:

extends CharacterBody2D

const WALK_LEFT = "walk_left"
const WALK_RIGHT = "walk_right"
const RUN_LEFT = "run_left"
const RUN_RIGHT = "run_right"
const IDLE_LEFT = "idle_left"
const IDLE_RIGHT = "idle_right"
const SHOOT_LEFT = "shoot_left"
const SHOOT_RIGHT = "shoot_right"
const RELOAD_LEFT = "reload_left"
const RELOAD_RIGHT = "reload_right"

var speed: int = 10
var speed_scale: float = 1.5
var direction: Vector2
var look_direction: Vector2 = Vector2.RIGHT
var is_sprinting: bool = false
var is_strafing: bool = false
var is_reloading: bool = false
var is_shooting: bool = false

@onready var player_body: AnimatedSprite2D = $PlayerBody
@onready var player_weapon: AnimatedSprite2D = $PlayerWeapon
@onready var player_head: AnimatedSprite2D = $PlayerHead


func _physics_process(_delta: float) -> void:
	get_input()
	movement()
	move_and_slide()
	animation()


func get_input():
	# DIRECTIONAL INPUT
	#--------------------------------------------------------------
	direction = Input.get_vector("left", "right", "up", "down")
	if direction != Vector2.ZERO:
		velocity = direction * speed
		look_direction = direction
	else:
		velocity = Vector2.ZERO

	# MOVEMENT INPUT
	#--------------------------------------------------------------
	if Input.is_action_pressed("sprint"):
		is_sprinting = true
		velocity *= speed_scale
	else:
		is_sprinting = false

	if Input.is_action_pressed("strafe") and velocity !=Vector2.ZERO:
		is_strafing = true
		direction.x *= -1
		velocity.y = 0
	else:
		is_strafing = false

	# ACTION INPUT
	#--------------------------------------------------------------
	if Input.is_action_just_pressed("shoot"): #add shoot timer here
		print("shoot")
		shoot()

	if Input.is_action_just_pressed("reload") and not is_reloading:
		print("reloaded")
		reload()


func movement():
	# SPRITE MOVEMENT-BASED ROTATION
	#--------------------------------------------------------------
	#if LEFT and UP and is NOT Strafing
	if direction.x < 0 and direction.y < 0 and not is_strafing:
		_rotate_sprites(45, 20)
	#if LEFT and DOWN and is NOT Strafing
	elif direction.x < 0 and direction.y > 0 and not is_strafing:
		_rotate_sprites(-45, -20)
	#if RIGHT and UP and is NOT Strafing
	elif direction.x > 0 and direction.y < 0 and not is_strafing:
		_rotate_sprites(-45, -20)
	#if RIGHT and DOWN and is NOT Strafing
	elif direction.x > 0 and direction.y > 0 and not is_strafing:
		_rotate_sprites(45, 20)
	#if last dir_x was LEFT and we go down
	elif look_direction.x < 0 and direction.y == 1:
		_rotate_sprites(-90, -45)
	#if last dir_x was RIGHT and we go down
	elif look_direction.x > 0 and direction.y == -1:
		_rotate_sprites(-90, -45)
	elif look_direction.x > 0 and direction.y == 1:
		_rotate_sprites(90, 45)
	elif look_direction.x < 0 and direction.y == -1:
		_rotate_sprites(90, 45)
	#------------------------------------------------------------
	#rotation of the weapon sprite when strafing
	#------------------------------------------------------------
	#if LEFT and UP and IS Strafing
	elif direction.x < 0 and direction.y < 0 and is_strafing:
		_rotate_sprites(45, 20)
	#if LEFT and DOWN and IS Strafing
	elif direction.x < 0 and direction.y > 0 and is_strafing:
		_rotate_sprites(-45, -20)
	#if RIGHT and UP and IS Strafing
	elif direction.x > 0 and direction.y < 0 and is_strafing:
		_rotate_sprites(-45, -20)
	#if RIGHT and DOWN and IS Strafing
	elif direction.x > 0 and direction.y > 0 and is_strafing:
		_rotate_sprites(45, 20)
	#------------------------------------------------------------
	#this makes the rotation snap back to 0 when not pressing up or down
	else:
		_rotate_sprites(0, 0)


func animation():
	if is_shooting or is_reloading:
		return

	if velocity and not is_sprinting:
		if direction == Vector2.LEFT:
			_play_movement_animations(WALK_LEFT)
		elif direction == Vector2.RIGHT:
			_play_movement_animations(WALK_RIGHT)
		elif direction.x < 0 and velocity.y != 0:
			_play_movement_animations(WALK_LEFT)
		elif direction.x > 0 and velocity.y != 0:
			_play_movement_animations(WALK_RIGHT)

	if velocity and is_sprinting:
		if direction == Vector2.LEFT:
			_play_movement_animations(RUN_LEFT)
		elif direction == Vector2.RIGHT:
			_play_movement_animations(RUN_RIGHT)

	if velocity == Vector2.ZERO:
		if look_direction.x > 0:
			_play_movement_animations(IDLE_RIGHT)
		else:
			_play_movement_animations(IDLE_LEFT)


func shoot():
	is_shooting = true
	if look_direction.x > 0:
		_play_gun_animations(SHOOT_RIGHT)
	elif look_direction.x < 0:
		_play_gun_animations(SHOOT_LEFT)
	await player_head.animation_finished
	await player_weapon.animation_finished
	is_shooting = false


func reload():
	is_reloading = true
	if look_direction.x > 0:
		_play_gun_animations(RELOAD_RIGHT)
	elif look_direction.x < 0:
		_play_gun_animations(RELOAD_LEFT)
	await player_head.animation_finished
	await player_weapon.animation_finished
	is_reloading = false


# Helper function to reduce code bloat
func _rotate_sprites(weapon_rotation: float, head_rotation: float) -> void:
		player_weapon.rotation_degrees = weapon_rotation
		player_head.rotation_degrees = head_rotation


# Helper function to reduce code bloat
func _play_movement_animations(animation_name: String) -> void:
	player_body.play(animation_name)
	player_weapon.play(animation_name)
	player_head.play(animation_name)


# Helper function to reduce code bloat
func _play_gun_animations(animation_name: String) -> void:
	player_weapon.play(animation_name)
	player_head.play(animation_name)

Look at your movement function. With the function calls, it’s a lot easier to see that you only have 5 different calls you make over 13 different if/else statements. Which means you could simplify that to only 5 if/else statements and get the same result. I didn’t do that, but I’m going to try now.

Let’s take a look at the 1st and 9th statement:

	#if LEFT and UP and is NOT Strafing
	if direction.x < 0 and direction.y < 0 and not is_strafing:
		_rotate_sprites(45, 20)
	#if LEFT and UP and IS Strafing
	elif direction.x < 0 and direction.y < 0 and is_strafing:
		_rotate_sprites(45, 20)

Notice the difference? The only difference is that one tests for strafing, and the other for no strafing, but regardless - they do the exact same thing. They are also split by look_direction tests. So you logic is basically:

  1. If the player is moving, and not strafing: pick a rotation and that’s it.
  2. If the player is not moving, but looking LEFT or RIGHT and moving UP or DOWN: pick a rotation and that’s it.
  3. If the player is not looking, but is strafing: pick a rotation, and that’s it.
  4. If the player is not moving of looking LEFT or RIGHT: set rotation back to zero.

That’s it.

Should looking override strafing? Should looking not override non-strafing movement? I don’t know, but your code says YES to both those questions.

Should looking only work if the player is moving up or down? Your code says YES, but based on your issues, I think maybe not. But…your comments are confusing:

	#if last dir_x was LEFT and we go down
	elif look_direction.x < 0 and direction.y == 1:
		_rotate_sprites(-90, -45)
	#if last dir_x was RIGHT and we go down
	elif look_direction.x > 0 and direction.y == -1:
		_rotate_sprites(-90, -45)

Assuming that dir_x is the look direction, then the first comment matches the code. But the second comment does not - because the code says we are going up. (y == 1 is down and y == -1 is up in 2D.)

Maybe this is why. You can only look LEFT or RIGHT when moving up or down. Is this intended? Also, I have no idea if the rotations here are correct. But you cannot look UP or DOWN ever.

It’s hard to help you refactor this without knowing what you intend to happen.

I refactored your animation code.

  1. I assumed that if the player isn’t moving, the default animation is to face right. If it’s not, just switch the logic to include 0 for the left.
  2. You don’t ever care what the y direction is to determine facing, so I just pulled all that code out.
  3. When possible, it’s good to check for positive things and eliminate “not” from your logic, as it makes your code easier to read.
func animation():
	if is_shooting or is_reloading:
		return

	if velocity:
		if is_sprinting:
			if direction.x >= 0:
				_play_movement_animations(RUN_RIGHT)
			else:
				_play_movement_animations(RUN_LEFT)
		else:
			if direction.x >= 0:
				_play_movement_animations(WALK_RIGHT)
			else:
				_play_movement_animations(WALK_LEFT)

	if velocity == Vector2.ZERO:
		if look_direction.x >= 0:
			_play_movement_animations(IDLE_RIGHT)
		else:
			_play_movement_animations(IDLE_LEFT)

Conclusion

You still have to figure out if your rotation logic is correct. If you need help with that, we can discuss it here. Hopefully this code gets you closer:

extends CharacterBody2D

const WALK_LEFT = "walk_left"
const WALK_RIGHT = "walk_right"
const RUN_LEFT = "run_left"
const RUN_RIGHT = "run_right"
const IDLE_LEFT = "idle_left"
const IDLE_RIGHT = "idle_right"
const SHOOT_LEFT = "shoot_left"
const SHOOT_RIGHT = "shoot_right"
const RELOAD_LEFT = "reload_left"
const RELOAD_RIGHT = "reload_right"

var speed: int = 10
var speed_scale: float = 1.5
var direction: Vector2
var look_direction: Vector2 = Vector2.RIGHT
var is_sprinting: bool = false
var is_strafing: bool = false
var is_reloading: bool = false
var is_shooting: bool = false

@onready var player_body: AnimatedSprite2D = $PlayerBody
@onready var player_weapon: AnimatedSprite2D = $PlayerWeapon
@onready var player_head: AnimatedSprite2D = $PlayerHead


func _physics_process(_delta: float) -> void:
	get_input()
	movement()
	move_and_slide()
	animation()


func get_input():
	# DIRECTIONAL INPUT
	#--------------------------------------------------------------
	direction = Input.get_vector("left", "right", "up", "down")
	if direction != Vector2.ZERO:
		velocity = direction * speed
		look_direction = direction
	else:
		velocity = Vector2.ZERO

	# MOVEMENT INPUT
	#--------------------------------------------------------------
	if Input.is_action_pressed("sprint"):
		is_sprinting = true
		velocity *= speed_scale
	else:
		is_sprinting = false

	if Input.is_action_pressed("strafe") and velocity !=Vector2.ZERO:
		is_strafing = true
		direction.x *= -1
		velocity.y = 0
	else:
		is_strafing = false

	# ACTION INPUT
	#--------------------------------------------------------------
	if Input.is_action_just_pressed("shoot"): #add shoot timer here
		print("shoot")
		shoot()

	if Input.is_action_just_pressed("reload") and not is_reloading:
		print("reloaded")
		reload()


func movement():
	# SPRITE MOVEMENT-BASED ROTATION
	#--------------------------------------------------------------
	#if LEFT and UP and is NOT Strafing
	if direction.x < 0 and direction.y < 0 and not is_strafing:
		_rotate_sprites(45, 20)
	#if LEFT and DOWN and is NOT Strafing
	elif direction.x < 0 and direction.y > 0 and not is_strafing:
		_rotate_sprites(-45, -20)
	#if RIGHT and UP and is NOT Strafing
	elif direction.x > 0 and direction.y < 0 and not is_strafing:
		_rotate_sprites(-45, -20)
	#if RIGHT and DOWN and is NOT Strafing
	elif direction.x > 0 and direction.y > 0 and not is_strafing:
		_rotate_sprites(45, 20)
	#if last dir_x was LEFT and we go down
	elif look_direction.x < 0 and direction.y == 1:
		_rotate_sprites(-90, -45)
	#if last dir_x was RIGHT and we go down
	elif look_direction.x > 0 and direction.y == -1:
		_rotate_sprites(-90, -45)
	elif look_direction.x > 0 and direction.y == 1:
		_rotate_sprites(90, 45)
	elif look_direction.x < 0 and direction.y == -1:
		_rotate_sprites(90, 45)
	#------------------------------------------------------------
	#rotation of the weapon sprite when strafing
	#------------------------------------------------------------
	#if LEFT and UP and IS Strafing
	elif direction.x < 0 and direction.y < 0 and is_strafing:
		_rotate_sprites(45, 20)
	#if LEFT and DOWN and IS Strafing
	elif direction.x < 0 and direction.y > 0 and is_strafing:
		_rotate_sprites(-45, -20)
	#if RIGHT and UP and IS Strafing
	elif direction.x > 0 and direction.y < 0 and is_strafing:
		_rotate_sprites(-45, -20)
	#if RIGHT and DOWN and IS Strafing
	elif direction.x > 0 and direction.y > 0 and is_strafing:
		_rotate_sprites(45, 20)
	#------------------------------------------------------------
	#this makes the rotation snap back to 0 when not pressing up or down
	else:
		_rotate_sprites(0, 0)


func animation():
	if is_shooting or is_reloading:
		return

	if velocity:
		if is_sprinting:
			if direction.x >= 0:
				_play_movement_animations(RUN_RIGHT)
			else:
				_play_movement_animations(RUN_LEFT)
		else:
			if direction.x >= 0:
				_play_movement_animations(WALK_RIGHT)
			else:
				_play_movement_animations(WALK_LEFT)

	if velocity == Vector2.ZERO:
		if look_direction.x >= 0:
			_play_movement_animations(IDLE_RIGHT)
		else:
			_play_movement_animations(IDLE_LEFT)


func shoot():
	is_shooting = true
	if look_direction.x > 0:
		_play_gun_animations(SHOOT_RIGHT)
	elif look_direction.x < 0:
		_play_gun_animations(SHOOT_LEFT)
	await player_head.animation_finished
	await player_weapon.animation_finished
	is_shooting = false


func reload():
	is_reloading = true
	if look_direction.x > 0:
		_play_gun_animations(RELOAD_RIGHT)
	elif look_direction.x < 0:
		_play_gun_animations(RELOAD_LEFT)
	await player_head.animation_finished
	await player_weapon.animation_finished
	is_reloading = false


# Helper function to reduce code bloat
func _rotate_sprites(weapon_rotation: float, head_rotation: float) -> void:
		player_weapon.rotation_degrees = weapon_rotation
		player_head.rotation_degrees = head_rotation


# Helper function to reduce code bloat
func _play_movement_animations(animation_name: String) -> void:
	player_body.play(animation_name)
	player_weapon.play(animation_name)
	player_head.play(animation_name)


# Helper function to reduce code bloat
func _play_gun_animations(animation_name: String) -> void:
	player_weapon.play(animation_name)
	player_head.play(animation_name)

Oof, that one hurt. No AI just genuine inexperience and struggle to grasp the logic so far.
Not a fan of AI and quite desperate to learn so thank you for taking the time to break this all down despite any assumptions. I am still in my very first phase of learning all of this with no prior experience but I learn by doing. I can watch all of the videos I want and read all of the documentation I can but if I’m not trying to implement things while learning them they’ll wind up lost on me (not that they aren’t anyway right now).

This was actually something I did shortly after posting so I guess I’m on the right track there.

Just to address this really quick: Yes. After going back over this (with the clarity of walking away for a day) I realized that I did a terrible job of keeping my comments updated as my code grew or I tried new things. Rather than helping my code make sense to myself (let alone anyone else) it’s just serving to confuse.

Ok, I like that way more. I honestly haven’t seen a lot of constants being used in any of the tutorials or videos I’ve watched so I’ve been avoiding them. My limited understanding of them was that constants were like variables with a little less flexibility. Not sure how accurate that is but I think in my mind I convinced myself that rigidity was bad. Putting them into this context, though, I can see how more “rigidity” is what I needed, specifically with parts of my code that actually require that. Is there any sort of guidelines for using variables vs. constants?

The reply I got on Reddit did the same and again, I like that way more. I’ve been hesitant to add more functions because I don’t really know their limitations so it’s good to see them being used this way (as helpers). It makes sense and looks way cleaner. I haven’t read up on functions in the documentation yet so I’ll definitely be spending some time there. Starting a custom function name with and underscore: is that a personal preference, best practice, or a combination of both?

Looking at the code block underneath where you said that, I think this is making more sense to me. Focus more on if something is happening and closing it with an else statement as opposed to attaching an is not to everything. And obviously figuring out where that does and doesn’t apply. Right?

Hoping to take that on tomorrow so we’ll see how it goes. Thank you a ton for all of this, I really appreciate it. Fully admitting that I’m biting off more than I can chew but that’s unfortunately just kind of how I learn. If I have any questions I will definitely follow up but again, thank you for all of the help you’ve already given. Massive help.

1 Like

Glad to hear it.

Yes. Anything that starts with a dollar sign or a quote should be moved to the top of the file.

Here’s how I recommend you use comments while you’re learning. When you make a new function:

  1. Add a comment for every thing you think you need to do, on it’s own line.
  2. Fill in the code under each comment until it’s working.
  3. Add any overall comments about how the function is used at the top of the function with ## to turn it into documentation - if you think it needs it.
  4. Delete all the scaffolding comments.

Also this:

#--------------------------------------------------------------
	

is a red flag in code these days that screams an LLM made this. Learn to separate your code with a single blank line to separate parts of a function, and two to separate functions. That’s all contained in the GDScript Style Guide. Read it. Love it. Live it.

If you have a String in your code, it should be a constant. If you’re preloading a file, it should be a constant.

If you are using a $NodePath, it should be an @onready variable.

If you want to change it easily while testing, or is something you’ll edit a lot in the Inspector, it should be an @export variable.

Otherwise, it should be a variable.

It’s convention. It’s part of the style guide. It indicates that only this file should be using this function. In other languages it would be called a private accessors, but in GDScript everything is public, so it’s just there to help you organize your code, and us to read it.

Yes. What I did is called refactoring. If you think back to algebra and how you simplified math equations - it’s the exact same thing. You do each step and it makes things simpler.

Sounds good. Glad I could help.

Ah, gotcha. Picked that up from Coding with Russ on YouTube. I’ve been going over the GDScript styling guide that you linked and also picked up a couple books in a Humble Bundle (Humble Bundle … bundle?) that seem to adhere to the documentation so I’m trying to sear all of that into my brain so I don’t have to rely on other sources for that.

I did finally get some time to try out the refactored code. It fixed issue #2 so thank you.

The first issue that I’m still having is that if I am facing right and then move up or down, I keep facing right. If I am facing left, however, and then try to move up or down, the sprite instantly flips to face the right (but does still move up or down as it should). I thought that it’s because my look_direction variable is set to Vector2.RIGHT by default but I had assumed since it’s updating via my get_input() function that the player would retain that and simply play that direction’s animation while moving up or down. Do I need to add a condition of direction.y == 1 and direction.y == -1 to my animation function?

Clarification: the default direction is right only in that the player spawns in facing the right in the beginning. Other than that, there would be no default direction, rather a reliance on the most recent direction.x (look_direction.x in my case). That actually works in terms of the idle.

I wasn’t sure what you meant by “switch the logic to include 0 for the left” so I tried: setting look_direction’s default value to Vector2.ZERO, adding direction.x <= 0 to the condition for the left animations, removing the = from the conditions, and all of the combinations of those I could manage. None of those worked.

I had assumed that since I have my last known direction.x stored as a variable for facing that the direction.y wouldn’t matter so that could be why I seemed to have omitted it. Just for more clarification: the player can only face left or right, but the player’s head and weapon sprites do rotate based on the direction.y. i.e. if direction.y == 1 the sprites should rotate to face straight down. I attempted that in my code but obviously I got those parts wrong, too.

Haven’t tackled that one yet, still on the to-do list.

1 Like

At this point if you want me to help debug your code, I think it would be a better use of my time if you just put your game up on GitHub and posted a link. Then I could just pull the project and actually see what’s happening.