Skip to content

2D Physics & Spatial Quadtrees

The PhysicsEngine (Game.physics()) handles 2D collision detection, velocity resolution, physical forces, and spatial indexing. It uses optimized spatial quadtrees to maintain a steady 60 FPS update rate even with hundreds of colliders.


Physics Engine Method Reference

Method Return Type Description
add(ICollisionEntity entity) void Registers a collision entity in the physics quadtree.
remove(ICollisionEntity entity) void Deregisters an entity from the physics world.
move(IMobileEntity entity, double angle, double delta) boolean Moves a mobile entity along a heading while resolving obstacle sliding.
raycast(Point2D start, Point2D end, RaycastType type) RaycastHit Casts a line segment through the world to detect obstructing colliders.
getCollisionEntities(Shape area) Collection<ICollisionEntity> Returns all solid entities overlapping a spatial area (circle, rectangle).
collides(Shape shape) boolean Checks if a shape intersects any solid physics geometry.

Collision Entities & Types

Entities inheriting from CollisionEntity (Creature, Prop, CollisionBox, Trigger) are automatically registered in the physics simulation.

CollisionSetup.java
Prop rock = new Prop(100, 150, "rock");

// 1. Enable solid physics collision
rock.setCollision(true);
rock.setCollisionBoxWidth(24);
rock.setCollisionBoxHeight(16);
rock.setCollisionBoxAlign(Align.CENTER);
rock.setCollisionBoxValign(Valign.DOWN);

// 2. Set collision filtering type
rock.setCollisionType(Collision.STATIC);

Collision Types

Collision Type Performance Impact Behavior & Use Case
STATIC High Performance Stationary level architecture (walls, cliffs, trees, buildings). Spatial quadtree caches bounds statically.
DYNAMIC Normal Tick Check Moving entities (player, enemies, projectiles, movable crates). Evaluated on every physics tick.
ANY Normal Tick Check Reacts to both static architecture and moving entities.
NONE Zero Physics Check Physics disabled (ghost mode, intangible sensors).

Entity Position vs. Collision Anchors

Remember that entity.getLocation() returns the top-left anchor of the entity's bounding box, whereas entity.getCenter() returns the centroid and entity.getCollisionBox() returns the physical collider rectangle. When performing distance checks, line-of-sight raycasts, or tile grid checks, use entity.getCenter() or entity.getCollisionBox() to avoid off-by-half-tile discrepancies. See Coordinate Systems & Spatial Spaces.


Line-of-Sight Raycasting

Raycasting allows AI enemies to check if a player is visible or if line-of-sight is blocked by walls:

EnemyLineOfSight.java
package com.example.game.ai;

import de.gurkenlabs.litiengine.Game;
import de.gurkenlabs.litiengine.entities.Creature;
import de.gurkenlabs.litiengine.physics.RaycastHit;
import de.gurkenlabs.litiengine.physics.RaycastType;
import java.awt.geom.Point2D;

public class EnemyVision {
  public static boolean canSeeTarget(Creature enemy, Creature target) {
    Point2D start = enemy.getCenter();
    Point2D end = target.getCenter();

    // Cast a ray from enemy to target checking for solid obstacles
    RaycastHit hit = Game.physics().raycast(start, end, RaycastType.STATIC);

    // If ray reached the target without hitting a solid wall, line of sight is clear
    return hit == null || hit.getPoint().distance(start) >= start.distance(end);
  }
}

Knockback & Impulse Force Vectors

You can apply directional impulse forces (explosions, weapon knockback, wind currents) using Force:

CombatKnockback.java
package com.example.game.combat;

import de.gurkenlabs.litiengine.Direction;
import de.gurkenlabs.litiengine.entities.Creature;
import de.gurkenlabs.litiengine.physics.Force;
import de.gurkenlabs.litiengine.physics.GravityForce;
import java.awt.geom.Point2D;

public class KnockbackApplier {
  public static void applyKnockback(Creature target, Point2D attackerCenter, float strength) {
    // 1. Calculate angle away from attacker
    double angle = attackerCenter.angle(target.getCenter());

    // 2. Create impulse force that decelerates smoothly over 30 ticks
    Force knockback = new Force(target.getCenter(), strength, (float) angle);
    knockback.setCancelOnCollision(true);

    // 3. Apply to target's movement controller
    target.movement().apply(knockback);
  }

  public static void applyGravity(Creature target, float pullStrength) {
    // Continuous gravity pull towards the bottom
    GravityForce gravity = new GravityForce(target, pullStrength, Direction.DOWN);
    target.movement().apply(gravity);
  }
}

Spatial Area Queries (Quadtree Searches)

Query all colliders within an area (e.g. for AoE explosions or pickup radii) without looping through all world entities:

ExplosionRadius.java
package com.example.game.combat;

import de.gurkenlabs.litiengine.Game;
import de.gurkenlabs.litiengine.entities.ICollisionEntity;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Point2D;
import java.util.Collection;

public class AreaOfEffectQuery {
  public static void triggerExplosion(Point2D center, double radius) {
    // 1. Define radial explosion circle
    Ellipse2D blastArea = new Ellipse2D.Double(
        center.getX() - radius,
        center.getY() - radius,
        radius * 2,
        radius * 2
    );

    // 2. Efficiently query quadtree for overlapping collision entities
    Collection<ICollisionEntity> victims = Game.physics().getCollisionEntities(blastArea);

    victims.forEach(entity -> {
      System.out.println("Hit entity in blast: " + entity.getName());
    });
  }
}

Collision Listeners & Callbacks

CollisionListeners.java
Prop barrel = Game.world().environment().getProp("explosive_barrel");

if (barrel != null) {
  barrel.onCollision(event -> {
    System.out.println("Collision detected on: " + event.getSource().getName());
    event.getInvolvedEntities().forEach(other -> {
      System.out.println("Collided with: " + other.getName());
    });
  });
}

See Also