Need help with move_and_collide collisions

Godot Version

v4.1.1.stable.official [bd6af8e0e]

Question

I’m creating a simple game with two squares that bounce off of eachother. To achieve this I use move_and_collide() and the velocity.bounce() functions within CharacterBody2D. However, on occasion the squares will bounce off of eachother more than once, causing them to jitter back and forth. I’m hoping anyone has an explanation why or a fix?

extends CharacterBody2D

@export var speed = 12
var extraSpeed = 1.0
@export var health = 10
@export var blockColor: Color = Color(1,1,1)
@export var attackSetup = [0,0,0,0]

@onready var attackLeft = $defaultAttack/attackLeft
@onready var collisionLeft = $defaultAttackCollision/CollisionShapeLeft
@onready var attackRight = $defaultAttack/attackRight
@onready var collisionRight = $defaultAttackCollision/CollisionShapeRight
@onready var attackTop = $defaultAttack/attackTop
@onready var collisionTop = $defaultAttackCollision/CollisionShapeTop
@onready var attackBottom = $defaultAttack/attackBottom
@onready var collisionBottom = $defaultAttackCollision/CollisionShapeBottom

@onready var hBar = $blockHealth/ProgressBar
@onready var blockMods = $blockMods

@onready var testMod = $blockMods/wallSpeed
@onready var modifiers = [testMod]

var ranDir = 0
var collided = false
var active = false
var inAA = false

#checks
var wallCollided = false
var enemyCollided = false

func _ready():
	_update_default_attacks()
	self.modulate = blockColor
	ranDir = deg_to_rad(randi_range(0,359))
	velocity = Vector2(speed, 0).rotated(ranDir)
	hBar.max_value = health
	hBar.value = health

func _physics_process(_delta):
	hBar.value = health
	if active == true:
		var collision = move_and_collide(velocity * extraSpeed)
		if collision != null:
			if self.name == "playerBlock":
				print(str(collision.get_collider()) + " " + str(collision))
			velocity = velocity.bounce(collision.get_normal())
			if collision.get_collider().is_in_group("blocks") == true:
				#print("colliding")
				enemyCollided = true
				collision.get_collider().velocity = collision.get_collider().velocity.bounce(collision.get_normal())
				if inAA == true:
					collision.get_collider().health -= 1
					inAA = false
			collision = null
		if health <= 0:
			queue_free()

func _on_default_attack_collision_body_entered(body):
	if body.is_in_group("wall") == true:
		wallCollided = true
	inAA = true

func _update_default_attacks():
	if attackSetup[0] == 1:
		attackLeft.visible = true
		collisionLeft.disabled = false
	else:
		attackLeft.visible = false
		collisionLeft.disabled = true
		
	if attackSetup[1] == 1:
		attackRight.visible = true
		collisionRight.disabled = false
	else:
		attackRight.visible = false
		collisionRight.disabled = true
		
	if attackSetup[2] == 1:
		attackTop.visible = true
		collisionBottom.disabled = false
	else:
		attackTop.visible = false
		collisionBottom.disabled = true
		
	if attackSetup[3] == 1:
		collisionTop.disabled = false
		attackBottom.visible = true
	else:
		collisionTop.disabled = true
		attackBottom.visible = false

Example

It may be that the collision results in a small overlap, next frame the block does not exit the overlap and results in another collision and bounce (this time back into the colliding block), a collision and the overlap remains, then the cycle repeats.

If you only bounce when the current velocity is moving toward the colliding normal, maybe that will prevent this jittering loop.

if collision.get_collider().is_in_group("blocks") == true:
	enemyCollided = true
	var collider: CharacterBody2D = collision.get_collider()

	# detect angle similarity with a dot product, negative means facing each other somewhat.
	var dot: float = collider.velocity.dot(collision.get_normal())
	if dot < 0.0:
		collider.velocity = collider.velocity.bounce(collision.get_normal())

		if inAA == true:
			collision.get_collider().health -= 1
			inAA = false