trizZzle Huh. Maybe I didn't place your suggestions correctly. Here's how it acts for me right now.
<iframe width="560" height="315" src="" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
I added true to move_and_collide() in my physics process,

and I tried removing move_and_slide() from the MOVE state and placing it at the bottom of physics process.

Is this how you did it?
Thank you for getting back to me by the way. I'm VERY grateful for your help!
I'll share the code as it exists now just in case:
extends CharacterBody2D
# states
enum States {IDLE, MOVE, CHARGE, BLAST, SPIN, BOUNCE}
var state = States.IDLE # default state
# main variables
var pwr_lvl = 0 # What bar is it on for how far it can shoot
var speed = 0 # speed when moving
var deceleration = 0
var button_held_for = 0 # amount of time button is being held for
# boolean variables
var can_charge_player = true
var can_call_timer = true
# exportable variables (can be altered in editor)
@export var acceleration = 2.5 # higher starts quicker
@export var deceleration_bounce = 9.5
@export var deceleration_blast = 9.5 # higher stops quicker
@export var deceleration_spin = 3.5 # higher stops quicker
@export var direction_last = Vector2(1, 1) # default direction facing (SE)
@export var speed_move = 50 # speed when moving with input
@export var speed_spin = 100 # speed when spinning
@export var speed_max = 999 # maximum speed
@export var bounce_strength = 500
@export var pwr_lvl_min = 200
@export var pwr_lvl_max = 1000
@export var pwr_lvl_increment = 200
# node variables
@onready var anim_power_meter = $PowerMeter/AnimationPowerMeter
@onready var anim_turtle = $AnimationTurtle
@onready var sound_ricochet = $SoundRicochet
@onready var timer_blast_cooldown = $TimerBlastCooldown
@onready var timer_power_meter = $PowerMeter/TimerPowerMeter
# functions
func _physics_process(delta: float): # delta variable has value of the frame time that engine passes to function.
# local variables
var collision = move_and_collide(velocity * delta, true) # define collision (true added)
var direction_input = Input.get_vector("left", "right", "up", "down")
var direction_facing = direction_last # last direction is direction you are facing
direction_last = get_direction() # returns value from respective function
match state:
States.IDLE:
if Input.is_action_pressed("move"):
state = States.MOVE # change state
elif Input.is_action_pressed("space_bar") and can_charge_player:
state = States.CHARGE # change state
elif Input.is_action_just_pressed("spin"):
state = States.SPIN # change state
else: # run idle code
velocity = Vector2.ZERO # idle
speed = 0 # reset speed
anim_turtle.play("idle") # resume idle animation
States.MOVE:
if collision and collision.get_collider().name == "Bumper":
state = States.BOUNCE
elif Input.is_action_just_released("move"):
state = States.IDLE # change state
elif Input.is_action_pressed("space_bar") and can_charge_player:
state = States.CHARGE # change state
else: # run move code
speed = speed_move # set speed to move speed
velocity = direction_input * speed # move
anim_turtle.play("animate") # play animation
States.BOUNCE:
if velocity.is_zero_approx():
state = States.IDLE # change state
elif Input.is_action_pressed("space_bar") and can_charge_player:
state = States.CHARGE # change state
elif collision:
if collision.get_collider().name == "Bumper": # bounce code
velocity = velocity.bounce(collision.get_normal()).normalized() * bounce_strength # bounce (get collision vector's normal)
deceleration = deceleration_bounce # this goes here so the rate doesn't reset after being established in a prev state
else:
velocity = velocity.bounce(collision.get_normal()) # bounce (get collision vector's normal)
anim_turtle.play("shell")
sound_ricochet.play()
apply_deceleration() # always decelerate
States.CHARGE:
if Input.is_action_just_released("space_bar"):
state = States.BLAST # change state
else: # charge code
if Input.is_action_pressed("move"):
direction_last = direction_input # able to change direction while spinning
if ! velocity.is_zero_approx(): # if velocity not 0
speed = 0 # start from zero everytime pressed
velocity = Vector2.ZERO # be still while charging
can_call_timer = true # reenable ability to call timer
anim_turtle.play("shell") # play animation
button_held_for += delta # number increases every frame that button is held down to keep track of how long
speed = pwr_lvl # power level determines speed for blast attack (pl can't fit in for spd by itself)
States.BLAST:
if ! speed: # if speed reaches 0 (no speed)
state = States.IDLE # change state
elif Input.is_action_pressed("space_bar") and can_charge_player:
state = States.CHARGE # change state
elif collision:
state = States.BOUNCE
else: # run blast code
velocity = direction_facing * speed # blast in direction facing
velocity = velocity.normalized() * speed # prevent going faster when going diagonal
deceleration = deceleration_blast # set deceleration value
apply_deceleration() # decelerate
anim_turtle.play("shell") # play animation
button_held_for = 0 # reset
pwr_lvl = 0 # reset
if can_call_timer: # makes sure the timer doesn't start every frame while in the BLAST state
can_call_timer = false # because it has already been started once
timer_blast_cooldown.start() # start cooldown
States.SPIN:
if Input.is_action_just_pressed("spin"):
state = States.IDLE # change state
elif Input.is_action_pressed("space_bar") and can_charge_player:
state = States.CHARGE # change state
elif Input.is_action_just_released("space_bar"):
state = States.BLAST # change state
elif Input.is_action_just_released("move"):
deceleration = deceleration_spin
apply_deceleration()
elif collision:
velocity = velocity.bounce(collision.get_normal()) # bounce
sound_ricochet.play() # play sfx
else: # spin code (accelerate/move)
apply_acceleration(direction_input)
position += velocity * delta # move without move_and_slide() dictating collision effect
anim_turtle.play("shell") # play animation
speed = velocity.length() # speed value reflects velocity
get_direction()
power_meter()
_on_timer_blast_cooldown_timeout()
move_and_slide() # taken from MOVE state
#print("Direction Facing: ", direction_facing)
#print("Speed: ", speed)
#print("Velocity: ", velocity)
#print("Power Level: ", power_level)
#print("Button Held For: ", button_held_for)
#print("Timer: ", timer_blast_cooldown.time_left)
#print("Can Charge Player: ", can_charge_player)
#print("Can Call Timer: ", can_call_timer)
func power_meter() -> void: # under construction
if Input.is_action_just_pressed("space_bar"): # if button just pressed (otherwise timer would reset and never timeout)
pwr_lvl = pwr_lvl_min # power level starts at minimum
timer_power_meter.start() # start timer
print("held down!")
elif Input.is_action_just_released("space_bar"):
timer_power_meter.stop()
anim_power_meter.frame = 0
print("released!")
func _on_timer_power_bar_timeout() -> void:
if pwr_lvl < pwr_lvl_max: # only if lower than max
pwr_lvl += pwr_lvl_increment # increase power meter
anim_power_meter.frame = (anim_power_meter.frame + 1)
elif pwr_lvl >= pwr_lvl_max: # if reached max
pwr_lvl = pwr_lvl_max # stay at max
print(pwr_lvl)
print("increased!")
func apply_acceleration(direction_input) -> void: # currently, acceleration is only being used for SPIN
speed = speed_spin
velocity = velocity.move_toward(direction_input * speed, acceleration) # to, by
func apply_deceleration() -> void:
velocity = velocity.move_toward(Vector2.ZERO, deceleration) # decelerate velocity
speed = move_toward(speed, 0, deceleration) # decelerate speed
func _on_timer_blast_cooldown_timeout() -> void: # on timer node timeout
if timer_blast_cooldown.time_left > 0: # timer starts
can_charge_player = false # disable ability to charge
else: # og code: elif ! Input.is_action_pressed("space_bar"):
can_charge_player = true # enable ability to charge
# with this small timer you can stop to charge very quickly, without being able to button mash
func _on_cool_down_blast_timeout() -> void:
if ! States.BLAST: # if not in the BLAST state
can_call_timer = true # reenable ability to call timer
func get_direction(): # used for obtaining directions (getting what the last direction was)
var direction = velocity # value of direction
var angle = fposmod(rad_to_deg(direction.angle()), 360) # (f for float; a positive modulus is 0 or greater), normalizes the angle and ensures it remains within the range of 0 to 360 (circle)
var sector = wrapi(snapped(angle, 45) / 45, 0, 8) # snap a 45 degree angle and wrap between 0-8; determine sector based on the snapped angle (360 / 45 = 8; there are 8 directions)
if velocity.is_zero_approx(): # when you let go
return direction_last # return and maintain last value of dir_last (it maintains it when you stop because its zero. That's why it works)
direction_last = [Vector2(1, 0), # E
Vector2(1, 1), # SE
Vector2(0, 1), # S
Vector2(-1, 1), # SW
Vector2(-1, 0), # W
Vector2(-1, -1), # NW
Vector2(0, -1), # N
Vector2(1, -1)] [sector] # NE (select direction based on sector index)
return direction_last # return corresponding vector coordinates
# Next up:
# Study Vector Math
# study other parts of code you want to understand better
# practice typing the lines of code over and over
# tidy up code to make it more readable
If yours is different, would you consider sharing the project back to me for comparison?