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
