I'm still getting used to coding, but I have this variable here for my CharacterBody2D script:

var facing = dir_last # see get_directon() function to see what dir_last equals

and some code that makes my character bounce off collisions:

States.BOUNCE:
			if ! collision:
				if velocity.is_zero_approx():
					state = States.IDLE # change state
				elif Input.is_action_pressed("move"):
					state = States.MOVE # change state
				elif Input.is_action_just_released("blast"):
					state = States.BLAST # change state
				elif ! Input.is_action_pressed("move"):
					apply_deceleration(delta)
					deceleration = deceleration_blast
			else:
				velocity = velocity.bounce(collision.get_normal())

and I tried to make this code to get and update the last direction the character is facing based on the direction velocity moves them in:

func get_direction():
	# cardinal
	if velocity.x < 0:
		dir_last = Vector2.LEFT
	elif velocity.x > 0:
		dir_last = Vector2.RIGHT
	if velocity.y < 0:
		dir_last = Vector2.UP
	elif velocity.y > 0:
		dir_last = Vector2.DOWN
	
	# diagonal
	if velocity.x < 0 and velocity.y < 0:
		dir_last = Vector2.LEFT + Vector2.UP #NW when bouncing down?
	elif velocity.x > 0 and velocity.y < 0:
		dir_last = Vector2.RIGHT + Vector2.UP #NE when bouncing up?
	elif velocity.x < 0 and velocity.y > 0:
		dir_last = Vector2.LEFT + Vector2.DOWN #SW # when bouncing up?
	elif velocity.x > 0 and velocity.y > 0:
		dir_last = Vector2.RIGHT + Vector2.DOWN #SE # when bouncing down?

And what I'm trying to figure out is why, when after bouncing off collision, does the facing (dir_last) only get updated to diagonal directions, even if the character is bounced in a cardinal direction?

I've tried looking at my code, but I just can't think of what it might be or what could cause that, so I thought I'd ask for help while I continue to try to figure it out.

  • NJL64 Your logic is flawed here. If the thing bounces perceptually (but not perfectly) in the cardinal direction both velocity components will be non-zero although one of them may be very small, but since you only check for a perfect cardinal bounce all non perfect ones will be registered as "diagonal".

In the first block of statements in get_direction(), add checks for is_zero_approx(velocity.x) and is_zero_approx(velocity.y). Or merge the two blocks of checks together.

I would show the actual correction, but you posted the code as an image.

    DaveTheCoder Sorry. I have a hard time when using the "insert code" feature. It always displays it improperly. I didn't know you had to highlight it and THEN press "insert code".

    func get_direction():
    	# cardinal
    	if velocity.x < 0:
    		dir_last = Vector2.LEFT
    	elif velocity.x > 0:
    		dir_last = Vector2.RIGHT
    	if velocity.y < 0:
    		dir_last = Vector2.UP
    	elif velocity.y > 0:
    		dir_last = Vector2.DOWN
    	
    	# diagonal
    	if velocity.x < 0 and velocity.y < 0:
    		dir_last = Vector2.LEFT + Vector2.UP #NW when bouncing down?
    	elif velocity.x > 0 and velocity.y < 0:
    		dir_last = Vector2.RIGHT + Vector2.UP #NE when bouncing up?
    	elif velocity.x < 0 and velocity.y > 0:
    		dir_last = Vector2.LEFT + Vector2.DOWN #SW # when bouncing up?
    	elif velocity.x > 0 and velocity.y > 0:
    		dir_last = Vector2.RIGHT + Vector2.DOWN #SE # when bouncing down?

    Here it is if you want to show me. I'm a visual learner, so I appreciate it. 🙂

    If by merging the two blocks, you mean merging the diagonal ifs into the cardinal ifs, I've tried that, but let me try the zero aprox thing. Thank you btw.

      NJL64 "insert code" feature
      That doesn't work reliably. Paste the code like normal text, and place ~~~ and ~~~ on lines before and after it.

      # cardinal
      if is_zero_approx(velocity.y):
          if velocity.x < 0:
              dir_last = Vector2.LEFT
              return
          elif velocity.x > 0:
              dir_last = Vector2.RIGHT
              return
      if is_zero_approx(velocity.x):
          if velocity.y < 0:
              dir_last = Vector2.UP
              return
          elif velocity.y > 0:
              dir_last = Vector2.DOWN
              return
      
      # diagonal
      ...

      The multiple return's are a little messy, but if dir_last is assigned a value in the first section, you don't want it changed in the second section.

      There may be a more elegant way of doing this.

        DaveTheCoder Thanks! And no worries. I wanted this get_direction function to be more concise as it was, but I'm still learning.

        How would you do the second section?

        It still correctly updates the direction the character is facing when applying input, but it still only updates with diagonal directions when bouncing, again, even if the character velocity is moving in a cardinal direction as a result of the bounce.

        When it bounces up it prints "Facing: (1, -1)". and if it bounces down it says (-1, 1).

        So far, it still works exactly the same with this code change. I just don't know what it could be.

          NJL64 When it bounces up it prints "Facing: (1, -1)". and if it bounces down it says (-1, 1).

          What's the value of velocity when that happens, when get_direction() is called?

            DaveTheCoder
            From the print statements:
            When it started bouncing: (394.4125, -295.7835)
            Before it reached zero: (-0.000315, 3.000107)

            get_direction() is called in the Physics Process function, so I can't put a red dot next to it. I'm not sure how to determine what value velocity is (besides zero) when get_direction is called, because it's called every frame if I understand correctly.

            This is currently the script:

            extends CharacterBody2D
            
            # state 
            enum States {IDLE, MOVE, BLAST, BOUNCE, SPIN}
            var state = States.IDLE # default state
            
            # exportable variables (can be changed in editor)
            @export var acceleration = 1.5 # increase in speed by this much
            @export var deceleration = 0 # default
            @export var deceleration_move = 15 # deceleration when moving with input
            @export var deceleration_blast = 210 # deceleration when blasting
            @export var dir_last = Vector2(0, 1) # note: needs to be global to work
            @export var speed = 0 # default
            @export var speed_blast = 500 # speed when blast is performed
            @export var speed_move = 100 # speed when moving with input
            
            # functions
            func _physics_process(delta): # the delta variable has the value of the frame time that the engine passes to the function.
            	# local variables
            	var collision = move_and_collide(velocity * delta)
            	var dir_input = Input.get_vector("left", "right", "up", "down")
            	var facing = dir_last # see get_directon() function to see what dir_last equals
            		
            	match state:
            		
            		States.IDLE:
            			if Input.is_action_pressed("move"):
            				state = States.MOVE # change state
            			elif Input.is_action_just_released("blast"):
            				state = States.BLAST # change state
            			elif Input.is_action_pressed("spin"):
            				state = States.SPIN
            			else:
            				velocity = Vector2.ZERO # idle
            				speed = 0
            			
            		States.MOVE:
            			if ! Input.is_action_pressed("move") and velocity.is_zero_approx():
            				state = States.IDLE # change state
            			elif Input.is_action_just_released("blast"):
            				state = States.BLAST # change state
            			else:
            				velocity = dir_input * speed
            				speed = speed_move
            				move_and_slide()
            				
            		States.BLAST:
            			if Input.is_action_pressed("move"): # if input JUST pressed, not ALREADY pressed
            				state = States.MOVE 
            			elif collision:
            				state = States.BOUNCE # change state
            			else:
            				velocity = facing * speed # blast in direction facing
            				velocity = velocity.normalized() * speed # avoid going faster when diagonal
            				deceleration = deceleration_blast
            				speed = speed_blast
            				apply_deceleration(delta)
            				
            		States.BOUNCE:
            			if ! collision:
            				if velocity.is_zero_approx():
            					state = States.IDLE # change state
            				elif Input.is_action_pressed("move"):
            					state = States.MOVE # change state
            				elif Input.is_action_just_released("blast"):
            					state = States.BLAST # change state
            				elif ! Input.is_action_pressed("move"):
            					apply_deceleration(delta)
            					deceleration = deceleration_blast
            			else:
            				velocity = velocity.bounce(collision.get_normal()) # bounce (get the vector's normal)
            				# note: only faces diagonal after bouncing, even if moving cardinal
            		
            # Experimental state only when 'A' is pressed on keyboard:
            		States.SPIN:
            			if Input.is_action_pressed("spin"):
            				state = States.IDLE
            			elif Input.is_action_just_released("blast"):
            				state = States.BLAST # change state
            			elif Input.is_action_just_released("move"):
            				deceleration = deceleration_move
            				apply_deceleration(delta)
            			elif collision: # says null instance without "collision"
            				velocity = velocity.bounce(collision.get_normal())
            			else:
            				apply_acceleration(dir_input)
            				move_and_slide()
            				
            	get_direction()
            	print("State: ", state)
            	print("Facing: ", facing)
            	print("Speed: ", speed)
            	print("Velocity: ", velocity)
            	
            func apply_acceleration(dir_input):
            	velocity = velocity.move_toward(dir_input * speed_move, acceleration)
            	
            func apply_deceleration(delta):
            	velocity = velocity.move_toward(Vector2.ZERO, deceleration * delta)
            	
            func get_direction():
            	if is_zero_approx(velocity.y):
            		if velocity.x < 0:
            			dir_last = Vector2.LEFT
            			return
            		elif velocity.x > 0:
            			dir_last = Vector2.RIGHT
            			return
            	if is_zero_approx(velocity.x):
            		if velocity.y < 0:
            			dir_last = Vector2.UP
            			return
            		elif velocity.y > 0:
            			dir_last = Vector2.DOWN
            			return
            	
            	# diagonal
            	if velocity.x < 0 and velocity.y < 0:
            		dir_last = Vector2.LEFT + Vector2.UP #NW when bouncing up?
            		return
            	elif velocity.x > 0 and velocity.y < 0:
            		dir_last = Vector2.RIGHT + Vector2.UP #NE when bouncing up?
            		return
            	elif velocity.x < 0 and velocity.y > 0:
            		dir_last = Vector2.LEFT + Vector2.DOWN #SW # when bouncing down?
            		return
            	elif velocity.x > 0 and velocity.y > 0:
            		dir_last = Vector2.RIGHT + Vector2.DOWN #SE # when bouncing down?
            		return
            • xyz replied to this.

              In _physics_process, you could accumulate the values of velocity and dir_last in arrays. For example, store 100 values before and after a bounce. Then you could print and look at them after a bounce, to see what happened.

              NJL64 Your logic is flawed here. If the thing bounces perceptually (but not perfectly) in the cardinal direction both velocity components will be non-zero although one of them may be very small, but since you only check for a perfect cardinal bounce all non perfect ones will be registered as "diagonal".

                xyz I understand. That makes perfect sense. I think that's what's wrong actually. Thank you!

                Is there a way I could possibly check for directions better, so that I can check for imperfect cardinal bounces?

                I know my homemade get_direction() function isn't the best and probably only accounts for 8 perfect directions.

                • xyz replied to this.

                  xyz Thank you so much! I'm gonna try that.

                  xyz Sorry. I know you said you answered similar questions already, but I was wondering if you could help me understand a way to apply this to my function?

                  Mine's not for inputs, but rather just the character moving in general.

                  I just thought I'd ask ahead of time in case I have trouble. I'm researching some of this stuff right now and thinking of how to apply it to my script.

                  When I get the angle of the velocity vector and snap it to 45 degrees, what should I do with it then? (I'm still brushing up on my Vector math)

                  I would really appreciate it. 🙂

                  • xyz replied to this.

                    NJL64
                    Just use your velocity vector instead of input direction vector. You can disregard the tolerance stuff as it doesn't apply to your case

                      xyz So I wrote this function (although I admit I don't understand it myself):

                      func get_dir():
                      	var dir = velocity
                      	var angle = posmod(rad_to_deg(dir.angle()), 360)
                      	var sector = wrapi(snapped(angle, 45) / 45, 0, 8)

                      But, how should I use it to update the value of "dir_last"? 🙁

                      • xyz replied to this.

                        NJL64 Return the value from the function and assign it to dir_last

                          xyz What's the value from the function?

                          • xyz replied to this.

                            NJL64 Functions can return values. That's in fact one of their main purpose, to calculate something and then return it. Look at my code on the link again. The function there returns a direction string.

                              xyz So I tried this:

                              func get_dir():
                              	var dir = velocity
                              	var angle = posmod(rad_to_deg(dir.angle()), 360)
                              	var sector = wrapi(snapped(angle, 45) / 45, 0, 8)
                              	return ["E", "SE", "S", "SW", "W", "NW", "N", "NE"] [sector]

                              and this:

                              dir_last = get_dir()

                              and it works for input and seems to work for bouncing off of things! But if I could fix one last issue, it should work how I want it.

                              Unfortunately I can no longer say

                              velocity = dir_last * speed

                              when shooting my character in the direction facing using the "BLAST" state.

                              If you would be so, SO generous to help me with with an alternative so that I can still shoot the character in the direction they are facing, it would really help me out.

                              Thank you!

                              • xyz replied to this.