SOLVED: Something is affecting player velocity and not sure what?

Godot Version

Godot 4.5.1

Question

I unfortunately did something wrong and my player now flies off the screen, always to the left. Velocity is 0, but get_real_velocity() is a constant (-1570.69, 0.0). I do have git backups thankfully but didn't make material changes to the player code recently - mostly related to giving damage/knockback. Can anyone help or have ideas on what I did wrong? Thank you for your help!The game manager autoload only handles inventory and opening a map (map change added recently) now but will add the code below the player code just in case. The other autoloads haven’t been updated since this issue started appearing.

class_name Player
extends CharacterBody2D
#@onready var _player: AnimatedSprite2D = %PlayerSprite
@onready var point_light_2d: PointLight2D = $PointLight2D
@onready var _collision_shape_2d: CollisionShape2D = $CollisionShape2D
@onready var _progress_bar: ProgressBar = $CanvasLayer/PanelContainer/MarginContainer/VBoxContainer/HBoxContainer/ProgressBar
@export var max_speed : float = 600.0
@export var acceleration : float = 1200.0
@export var deceleration : float = 1080.0
@export var light_energy:  float = 0.0
@onready var walk_stone: AudioStreamPlayer2D = $WalkStone
@onready var _player_spine: SpineSprite = $PlayerSpine
@onready var _player: Player = $"."
@onready var player_inventory : Inventory
#set up weapon arms
@onready var weapon_mb_area_2d: Area2D = $PlayerSpine/SpineBoneWeaponMB/WeaponMBArea2D
@onready var weapon_r_area_2d: Area2D = $PlayerSpine/SpineBoneWeaponR/WeaponRArea2D

#set up obstacle avoidance variables
@onready var _ray_casts: Node2D = $RayCasts
@onready var avoidance_strength := -21000.0

@export var max_health := 10
@onready var health := max_health: set = set_health


@onready var current_skin1 : String = "SouthEyes"
@onready var current_skin2 : String = "SouthEyes"
@onready var current_skin3 : String = "SouthEyes"
@onready var animation_name : String = "Idle"
@onready var current_weapon : String = "BequeathedDagger.tres"

#loading current weapon data - use "load" vs "preload" if not a constant string
@onready var weapon_data:= load("res://Items/"+ str(current_weapon))


var inventory_resource = load("res://player/inventory.gd")
var inventory = inventory_resource.new()


#if there's another active prompt or button set to true so attack button is used by ui
var ui_active: bool 

var pause_non_attack_animations : bool = false
var pause_attack_animations : bool = false
var current_angle : int = 0

#knockback when hit
var knockback: Vector2 = Vector2.ZERO
var knockback_timer : float = 0.0



#current scale that feeds into flipping sprite based on direction ,you need to update if the scale of the spine sprite changes
var spine_scale:= 0.35


#TODO finish animations, make weapin bone node only active for active skin, code weapon damage

func _ready()-> void: 
	#set up initial skin
	change_skin("PlayerCloak/Front", "Weapons/Dagger/Front","None")
	change_animation("Blink",true,3,1.0)
	#for using portals between scenes
	PortalSpawn.on_trigger_player_spawn.connect(_on_spawn)
	
	#adds player as active in game manager to feed into other variables like inventory
	GameManager.player = self
	
	#player specific light
	point_light_2d.energy = light_energy
	
	#set up health bar
	health = max_health
	_progress_bar.max_value = max_health
	_progress_bar.value = health
	
	#raycasts to not get stuck on stuff
	for raycast:RayCast2D in _ray_casts.get_children():
		raycast.add_exception(self)
		
	#disable all weapon arms, specific arm will be enabled during attack
	weapon_mb_area_2d.monitorable = false
	weapon_r_area_2d.monitorable = false
	weapon_mb_area_2d.monitoring = false
	weapon_r_area_2d.monitoring = false
	
	#set ui active to false when starting
	ui_active = false
	
func set_health(new_health: int) -> void: 
	#sets up health and healthbar
	health = clampi(new_health, 0, max_health)
	_progress_bar.value = health
	if health <= 0:
		die()
		
func _on_spawn(position:Vector2,direction:String):
	global_position = position
	#animation_player.play("move_"+direction)
	
func _physics_process(delta:float) -> void: 
	print(position)
	var direction := Input.get_vector("move_left","move_right", "move_up","move_down")
	var has_input_direction := direction.length() >0.0

	var desired_velocity := direction * max_speed
	desired_velocity += calculate_avoidance_force() * delta
	_ray_casts.rotation = direction.angle()
		#if knockback occurs, velocity = knockback else move

	if has_input_direction:
			velocity = velocity.move_toward(desired_velocity, acceleration * delta) + knockback
	else:
			velocity = velocity.move_toward(Vector2.ZERO, deceleration *delta) + knockback
	#var current_speed_percent := velocity.length()/max_speed
	#var direction_discrete := direction.sign()
	print(get_real_velocity())
	if knockback_timer >0.0:
		knockback_timer -= delta
	if knockback_timer <= 0.0: 
		knockback = Vector2.ZERO
	
	var angle = round(direction.angle()/(PI/4))+3
	angle = wrapi(int(angle),0,8)
	
	if direction.length()!= 0:
		#0, 1 , 2
		#7      3
		#6, 5 , 4
		if current_angle != angle:
			if angle == 1:
				_player_spine.scale.x = spine_scale 
				change_skin("PlayerCloak/Back", "Weapons/Dagger/Front","None")
				current_angle = angle
			if angle == 0:
				_player_spine.scale.x = -1 * spine_scale  
				change_skin("PlayerCloak/Side", "Weapons/Dagger/Side","None")
				current_angle = angle
			if angle == 2:
				_player_spine.scale.x = spine_scale 
				change_skin("PlayerCloak/Side", "Weapons/Dagger/Side","None")
				current_angle = angle
				#eyes = "SideFace"
			if angle == 3:
				_player_spine.scale.x =  spine_scale 
				change_skin("PlayerCloak/Side", "Weapons/Dagger/Side","None")
				current_angle = angle
				#eyes = "SouthEyes"
			if angle == 7:
				_player_spine.scale.x = -1 * spine_scale 
				change_skin("PlayerCloak/Side", "Weapons/Dagger/Side","None")
				current_angle = angle
			if angle == 5:
				_player_spine.scale.x = spine_scale
				change_skin("PlayerCloak/Front", "Weapons/Dagger/Front","None")
				current_angle = angle
			if angle == 4:
				_player_spine.scale.x = spine_scale
				change_skin("PlayerCloak/Front", "Weapons/Dagger/Front","None")
				current_angle = angle
			if angle == 6:
				_player_spine.scale.x = -1 * spine_scale 
				change_skin("PlayerCloak/Front", "Weapons/Dagger/Front","None")
				current_angle = angle

		#set movement animation based on direction
	if velocity.length() >10.0 and pause_non_attack_animations == false:
			if angle == 1:
				change_animation("WalkingFront",true,0,1.0)
			elif angle == 0 or angle ==2:
				change_animation("WalkingBackSide",true,0,1.0)
			elif angle ==5:
				change_animation("WalkingFront",true,0,1.0)
			elif angle == 7 or angle ==3:
				change_animation("WalkingSide",true,0,1.0)
			elif angle == 4 or angle ==6:
				change_animation("WalkingFrontSide",true,0,1.0)
#if has_input_direction:
#		if walk_stone.is_playing() == false:
#			walk_stone.play()
#		match direction_discrete:
#			Vector2.UP:
#				_player.play(&"WALK_UP",1.0, false)
#			Vector2.DOWN:
#				_player.play(&"WALK_DOWN",1.0, false)
#			Vector2.LEFT:
#				_player.play(&"WALK_LEFT",1.0, false)
#			Vector2.RIGHT:
#				_player.play(&"WALK_RIGHT",1.0, false)
	else:
		walk_stone.stop()
		change_animation("Idle",true,0,1.0)
#		match direction_discrete:
#			Vector2.UP:
#				_player.play(&"IDLE_UP",1.0, false)
#			Vector2.DOWN:
#				_player.play(&"IDLE_DOWN",1.0, false)
#			Vector2.LEFT:
#				_player.play(&"IDLE_LEFT",1.0, false)
#			Vector2.RIGHT:
#				_player.play(&"IDLE_RIGHT",1.0, false)
	if Input.is_action_just_pressed("advance_button") and pause_attack_animations != true and ui_active == false:
		attack()
	#if hit apply knowckback

	move_and_slide()
#set how much bounceback when hitting obstacles
func calculate_avoidance_force() -> Vector2:
	var avoidance_force := Vector2.ZERO
	for raycast:RayCast2D in _ray_casts.get_children():
		if raycast.is_colliding():
			var collision_position := raycast.get_collision_point()
			var ray_length := raycast.target_position.length()
			var direction_away_from_obstacle := collision_position.direction_to(raycast.global_position)
			var intensity := 1.0 - collision_position.distance_to(raycast.global_position)/ray_length
			var force := direction_away_from_obstacle * avoidance_strength * intensity
			avoidance_force += force
	return avoidance_force
	
func die() ->void:
	set_physics_process(false)
	_collision_shape_2d.set_deferred("disabled",true)

func walk_to(destination_global_position: Vector2) -> void:
	pass
		#var direction := global_position.direction_to(destination_global_position)
		#var distance := global_position.distance_to(destination_global_position)
		#var duration :=  distance / (max_speed * 0.2)
		#var tween := create_tween()
		#tween.tween_property(self, "global_position", destination_global_position, duration)
		#tween.finished.connect(func():
				#set_physics_process(true) )

func change_scene()->void:
	for i in get_tree().get_nodes_in_group("Portal"):
		if i.name == PortalSpawn.coming_from_portal: 
			global_position = i.get_node("SpawnPoint").global_position
			break

func change_skin(skin_name: String, skin_name2: String,skin_name3: String )-> void:
	if skin_name != current_skin1 or skin_name2 != current_skin2 or skin_name3 != current_skin3:
		var custom_skin = _player_spine.new_skin("custom_skin")
		var data = _player_spine.get_skeleton().get_data()
		custom_skin.add_skin(data.find_skin(skin_name))
		if skin_name2 != "None":
			custom_skin.add_skin(data.find_skin(skin_name2))	
		if skin_name3 != "None":
			custom_skin.add_skin(data.find_skin(skin_name3))
		_player_spine.get_skeleton().set_skin(custom_skin)
		_player_spine.get_skeleton().set_slots_to_setup_pose()
		current_skin1 = skin_name
		current_skin2 = skin_name2
		current_skin3 = skin_name3


func change_animation (current_animation_name:String, loop:bool, track:int, mix:float)->void:
		#print('request animation: '+animation_name)
		var animation_state = _player_spine.get_animation_state()
		var current_entry = animation_state.get_current(track)
	#set_animation (animation_name,loop, track index)
	#add_animation (animation_name,delay, loop, index)
		if current_animation_name != animation_name:
			#animation_state.add_empty_animation(track, mix, 0)
			animation_state.add_animation(current_animation_name,0,loop,track)
			animation_name = current_animation_name

func attack()->void:
	var animation_state = _player_spine.get_animation_state()
	if current_angle == 1:
		weapon_mb_area_2d.monitoring = true
		weapon_mb_area_2d.monitorable = true
		change_animation("SlashBack",false,2,1.0)
		animation_state.add_empty_animation(2, 0.1, 0)
	elif current_angle == 0 or current_angle == 2:
		weapon_mb_area_2d.monitoring = true
		weapon_mb_area_2d.monitorable = true
		change_animation("SlashSide",false,2,1.0)
		animation_state.add_empty_animation(2, 0.1, 0)
	elif current_angle == 4 or current_angle ==6:
		weapon_r_area_2d.monitoring = true
		weapon_r_area_2d.monitorable = true
		#pause_non_attack_animations = true
		change_animation("SlashFront",false,2,1.0)
		animation_state.add_empty_animation(2, 0.1, 0)
	elif current_angle == 5:
		weapon_r_area_2d.monitoring = true
		weapon_r_area_2d.monitorable = true
		#pause_non_attack_animations = true
		change_animation("SlashFront",false,2,1.0)
		animation_state.add_empty_animation(2, 0.1, 0)
	elif current_angle == 3 or current_angle ==7:
		weapon_mb_area_2d.monitoring = true
		weapon_mb_area_2d.monitorable = true
		#pause_non_attack_animations = true
		change_animation("SlashSide",false,2,1.0)
		animation_state.add_empty_animation(2, 0.1, 0)


func _on_weapon_r_area_2d_body_entered(body: Node2D) -> void:
	if body.is_in_group("Enemies"):
		body.take_damage(weapon_data.damage)
		print(body.health)
		


func _on_weapon_mb_area_2d_body_entered(body: Node2D) -> void:
	if body.is_in_group("Enemies"):
		body.take_damage(weapon_data.damage)
		print(body.health)


func _on_player_spine_animation_event(spine_sprite: Object, animation_state: Object, track_entry: Object, event: Object) -> void:
	#sets weapon collision to not monitoring/monitorable if not actively attacking
	if track_entry.get_animation().get_name() == "SlashSide" and event.get_data().get_event_name() == "complete":
		weapon_mb_area_2d.monitoring = false
		weapon_mb_area_2d.monitorable = false
	elif track_entry.get_animation().get_name() == "SlashFront" and event.get_data().get_event_name() == "complete":
		weapon_r_area_2d.monitoring = false
		weapon_r_area_2d.monitorable = false
	elif track_entry.get_animation().get_name() == "SlashBack" and event.get_data().get_event_name() == "complete":
		weapon_r_area_2d.monitoring = false
		weapon_r_area_2d.monitorable = false

func apply_knockback (direction: Vector2, force: float, knockback_duration: float) -> void:
	knockback = direction * force
	knockback_timer = knockback_duration


func _on_hit_box_body_entered(body: Node2D) -> void:
	#Apply knockback when hit
	if body.is_in_group("Enemies") or body.is_in_group("Goo"):
		if body.is_in_group("Enemies"):
			print("knockback")
			var knockback_direction = (body.global_position - global_position).normalized()
			apply_knockback(knockback_direction, 20.0, 0.1)
		elif body.is_in_group("Goo"):
			var knockback_direction = (body.global_position - global_position).normalized()
			apply_knockback(knockback_direction, 20.0, 0.1)

Game Manager Code

extends Node

signal player_initialized
var player
var initialized := false 

var map :PackedScene = preload("uid://87eb0n2n1djx")
var map_open:bool = false

func _ready():
	process_mode = self.PROCESS_MODE_ALWAYS

func _process(delta):
	if player and initialized == false:
		initialize_player()
		initialized = true
		return





	
func initialize_player():
	if not player:
		return



		
	
	
	emit_signal("player_initialized",player)
	
	player.inventory.connect("inventory_changed", _on_player_inventory_changed)
	
	var existing_inventory = load("user://inventory.tres")
	if existing_inventory:
		player.inventory.set_items(existing_inventory.get_items())
	else:
		#let's give the player 3 pieces of wood
		player.inventory.add_item("Healing Herbs",3)
		
func _on_player_inventory_changed(inventory):
	ResourceSaver.save(inventory, "user://inventory.tres")


func _input(event)->void:
	if event is InputEventKey:
		if event.pressed and not event.is_echo():
			if Input.is_action_pressed("open_map") and map_open == false:
					var newmap = map.instantiate()
					get_parent().add_child(newmap)
					map_open = true
					get_tree().paused = true


			elif Input.is_action_pressed("open_map") and map_open == true:
				var newmap = map.instantiate()
				if is_instance_valid(newmap):
					get_parent().get_node("Map").queue_free()
					map_open = false
					get_tree().paused = false

Seems like you are adding knockback every frame, is there anything applying knockback when the game starts?

Thanks for looking into this, I appreciate you taking a stab at it. Unfortunately knock back doesn’t seem to make a difference. Just to be safe I removed knockback from the velocity calculation to test but it didn’t fix the player flying offscreen. When I print(velocity) velocity is still zero, but has_real_velocity() has a value so it seems like something is moving the player outside of the velocity calculation.

What does your scene tree look like?

Here’s a screenshot of the player scene tree.

is the “Lantern” placed beside the player? Do they shader a collision layer/mask? Maybe the lantern collision is moving the player, then the parent transform is applied to the lantern, moving together and forcing collisions.

2 Likes

It is! I removed the lantern from the scene to see if that fixed it and it did - I’ll have to go back and rethink what I was doing there. Thank you SO much! You solved so much frustration.