Knockback
Knockback is a short timed impulse applied to an entity after a collision. It is represented by KnockbackComponent and driven by KnockbackSystem.
While a KnockbackComponent is present on an entity, several systems yield control to the impulse:
InputSystem— skips the entity, so player input has no effect during knockbackMovementSystem— skips the entity, so velocity-driven movement is suppressedEnemyAISystem— skips the entity, so AI-driven movement does not fight the impulse
KnockbackComponent
public struct KnockbackComponent: Component {
public var velocity: SIMD2<Float> // Impulse direction and speed
public var remainingTime: Float // Seconds left on the impulse (default: 0.2)
}
The component is added by CollisionSystem and removed automatically by KnockbackSystem once remainingTime reaches zero.
KnockbackSystem
KnockbackSystem runs at priority: 12. Lower priority numbers run first, so it runs before EnemyAISystem (15) and MovementSystem (20). Each frame it:
- Applies
velocity × deltaTimedirectly toTransformComponent.positionfor every entity with aKnockbackComponent. - Decrements
remainingTimebydeltaTime. - Removes the component once
remainingTime ≤ 0.
The system moves entities directly via TransformComponent — it does not write to VelocityComponent.
Player–Enemy Collision
When a player and enemy overlap, CollisionSystem does the following:
- Positional nudge — each entity is displaced by a mass-weighted fraction of the MTV, separating them immediately. See MassComponent for the formula.
- Knockback — both entities receive a
KnockbackComponentwith:
| Field | Value |
|---|---|
velocity | normalise(MTV) × (1500 / mass) (away from the other entity) |
remainingTime | 0.1 seconds |
Both the nudge and knockback speed are mass-dependent — heavier entities are displaced less and receive a slower impulse.
Enemy–Enemy Collision
When two enemies overlap, CollisionSystem displaces each by half the MTV in opposite directions. No knockback is applied — enemies simply separate and resume normal AI movement on the next frame.
Preventing Mid-Flight Cancellation
Knockback is only applied if the entity does not already have a KnockbackComponent. A second collision during an active knockback impulse is ignored:
private func applyKnockbackIfNeeded(to entity: Entity, velocity: SIMD2<Float>,
duration: Float, world: World) {
guard world.getComponent(type: KnockbackComponent.self, for: entity) == nil else { return }
world.addComponent(component: KnockbackComponent(velocity: velocity, remainingTime: duration),
to: entity)
}
This prevents rapid successive contacts from resetting or stacking the impulse.
System Execution Order
Systems execute in ascending priority order (lower number = runs first).
| Priority | System | Role |
|---|---|---|
12 | KnockbackSystem | Applies and expires the impulse |
15 | EnemyAISystem | Skips enemies with KnockbackComponent |
20 | MovementSystem | Skips entities with KnockbackComponent |
30 | CollisionSystem | Detects overlaps, adds KnockbackComponent |