#Preventing 'enemy' physics bodies being pushed by player on collision

7 messages · Page 1 of 1 (latest)

shell flame
#

I am working on creating a vertical scrolling shmup prototype and currently am trying to get collisions working. One problem I have is with the player (characterbody2d) being able to push the enemies (also characterbody2d) on collision. I currently have a knockback effect coded in causing the player to 'bounce' off of the enemy immediately on collision but the enemy is still pushed a little during the initial collision period and if the player is knocked into a second enemy that one is often pushed as well.

I am looking for advice on how to prevent this from happening. Currently when the player collides with the enemy I am turning the enemy's collision layer for the player off for a fraction of a second which has helped, but is not a full solution. My understanding of how the physics process works is also that I cannot disable the physics process for the enemy as well for a moment because the enemy would stop moving and also prevent bullet collision detection.

#

Player movement code:

class_name PlayerShipMoveComponent
extends Node

@onready var ship: PlayerShip = $"../.."
@export var body: CharacterBody2D
@export var movement_debug: PlayerMovementDebugComponent

var target_velocity: Vector2 = Vector2.ZERO
var collision_original: Vector2 = Vector2.ZERO
var push_vector: Vector2 = Vector2.ZERO
var collision_strength: float = 250
var collision_offset_factor: float = 1
var delta_access: float
var collision_timer: float = 0:
    set(value):
        if value < 0:
            collision_timer = 0
        else: collision_timer = value

# Called when the node enters the scene tree for the first time.
func _ready() -> void:
    pass # Replace with function body.

func _physics_process(delta: float) -> void:
    delta_access = delta
    if collision_timer > 0: collision_timer -= delta
    if push_vector != Vector2.ZERO:
        knockback_fade()
    move_ship()

func move_ship():
    if push_vector != Vector2.ZERO:
        body.velocity = target_velocity + push_vector
    else:
        body.velocity = target_velocity 
    if body.move_and_slide():
        on_collision()

func on_collision():
    knockback_start()
    if ship.debug: 
        movement_debug.notify()
        movement_debug.set_line(collision_original, push_vector)

func knockback_start():
    var enemy_destroyed: bool = false
    var collider = body.get_last_slide_collision().get_collider()
    if not collider.is_in_group("Exclude Knockback"):
        if collider.is_in_group("Enemy Simple") or collider.is_in_group("Enemy Miniboss") or collider.is_in_group("Enemy Boss"):
            ship.damage(collider.player_collision())
            enemy_destroyed = collider.collision_damage(ship.stats.collision_damage)
            collision_strength = collider.collision_strength()
        else: print("Enemy is not in recognised group")
        if not enemy_destroyed:
            collision_timer = 0.1
            collision_original = body.global_position - body.get_last_slide_collision().get_position()
            push_vector = collision_original * PlayerGlobals.screen_knockback_factor
            collision_offset_factor = 0

func knockback_fade():
    ## Equation for falloff is y = 1-x^2, where collision_offset_factor is x
    if push_vector.length() > 5:
        collision_offset_factor += delta_access
        if collision_offset_factor < 1:
            push_vector = collision_original * (1 - ((4 * collision_offset_factor) ** 2)) * collision_strength
    else:
        collision_original = Vector2.ZERO
        push_vector = Vector2.ZERO
        if ship.debug: movement_debug.toggle_visibility(false)
#

Enemy movement code (currently a simple follow path process):

class_name EnemyMoveComponent
extends Node

@export var movement_type: EnemyGlobals.ENEMY_MOVEMENT_TYPE
@export var enemy_body: EnemyBodyComponent
@export var follow: PathFollow2D
var speed: float = 200
var movement: Vector2

# Called when the node enters the scene tree for the first time.
func _ready() -> void:
    pass

# Called every frame. 'delta' is the elapsed time since the previous frame.
func _physics_process(delta: float) -> void:
    match movement_type:
        EnemyGlobals.ENEMY_MOVEMENT_TYPE.SIMPLE_PATH: path_simple(delta)

func path_simple(delta: float):
    follow.progress += speed * delta
    movement = follow.global_position - enemy_body.global_position
    enemy_body.velocity = movement
    enemy_body.move_and_slide()
#

Enemy characterbody2d code:

extends CharacterBody2D

@export var enemy_hurt_component: EnemyHurtComponent
@export var type: EnemyGlobals.ENEMY_TYPE
@export var disable_collision_layers: Array[Globals.COLLISION_LAYERS]
var collision: bool = true
var stats: EnemyStatsBase
var collision_timer: float = 0:
    set(value):
        if value < 0:
            collision_timer = 0
        else: collision_timer = value

func _physics_process(delta: float) -> void:
    if collision_timer > 0 and (collision_timer - delta) <= 0:
        collision_timer -= delta
        collide_toggle(true)
    elif collision_timer > 0: 
        collision_timer -= delta

func prepare(enemy_stats):
    stats = enemy_stats

func bullet_collision(damage: float): 
    enemy_hurt_component.receive_damage(damage)

func player_collision() -> float:
    if collision:
        collide_toggle(false)
        collision_timer = 0.2
    return stats.collision_damage

func collision_damage(damage: bool) -> bool:
    return enemy_hurt_component.receive_damage(damage)

func collision_strength() -> float:
    return stats.knockback_strength

func collide_toggle(state):
    for i in disable_collision_layers:
        self.set_collision_layer_value(i+1, state)
    collision = state
chrome hinge
shell flame
#

Yeah that's what I started working on. I've just been distracted with the Monster Hunter launch. It's a bit of work to reconfigure my components for it and I'm still figuring out the area specific version of code.