Teokbokki (Physics and Collisions)

Bappa includes a practical physics system named tteokbokki that provides fundamental physics capabilities for 2D games. While not as feature-rich as dedicated physics engines, it offers functionality that works well within Bappa’s ECS architecture for most common 2D game scenarios.

Physics Architecture Overview

The tteokbokki physics system is organized into several key modules:

  1. Spatial System: Handles collision detection between different shape types
  2. Motion System: Manages physical properties and movement
  3. Integration with ECS: Leverages component-based architecture for efficient physics categorization

This modular approach allows you to use only the parts you need for your specific game requirements.

Core Physics Components

Dynamics Component

The Dynamics component represents the physical properties of an entity:

type Dynamics struct {
    InverseMass        float64   // 1/mass (0 for immovable objects)
    InverseAngularMass float64   // 1/moment of inertia
    Vel                vector.Two // Linear velocity
    Accel              vector.Two // Linear acceleration
    AngularVel         float64   // Angular velocity
    AngularAccel       float64   // Angular acceleration
    SumForces          vector.Two // Accumulated forces
    SumTorque          float64   // Accumulated torque
    Elasticity         float64   // Bounciness (0-1)
    Friction           float64   // Friction coefficient
}

Create dynamic objects with the NewDynamics function:

// Create a dynamic object with mass 10
dynamics := blueprintmotion.NewDynamics(10)

// Create an immovable (static) object
dynamicsStatic := blueprintmotion.NewDynamics(0) // 0 for static

Shape Component

The Shape component defines the physical shape of an entity for collision detection:

// Create a rectangular shape for collision
rectangle := blueprintspatial.NewRectangle(64, 32)

// Create a triangular platform (one-way collision)
platform := blueprintspatial.NewTriangularPlatform(120, 16)

// Create a ramp with specified dimensions and slope
ramp := blueprintspatial.NewDoubleRamp(200, 40, 0.2)

// Create a custom polygon
 vertices := []vector.Two{
  // vetices for some custom convex shape
}
blueprintspatial.NewPolygon(vertices)

The system supports:

  • Polygons: Custom shapes with arbitrary vertices
  • Rectangles: Standard box collision
  • Circles: Circular collision (useful for characters)
  • Compound Shapes: Specialized shapes like ramps and platforms

The Spatial System

The spatial system handles collision detection using a multi-phase approach:

Broad-phase

Uses simple bounding volumes (AABBs and circles) for initial collision filtering.

Detection

Implements more precise polygon-based collision detection for shapes that pass the broad-phase check:

// Check for collision between two shapes
if ok, collisionResult := spatial.Detector.Check(
    *playerShape, *blockShape, playerPosition.Two, blockPosition.Two,
); ok {
    // Handle collision...
}

The collision result contains:

  • Normal: Direction of the collision
  • Depth: How much objects are overlapping
  • Start/End Points: The collision contact points
  • Collision Edges: The specific edges involved in the collision (index and vertices)

Integration

For proper integration in Bappa, update both position and rotation in a single step each tick:

// Apply forces to dynamics components
motion.Forces.AddForce(dynamics, force)

// Integrate dynamics to get new position and rotation
newPos, newRot := motion.Integrate(dynamics, position, rotation, deltaTime)

// Update entity components with new values
position.X = newPos.X
position.Y = newPos.Y
rotation = blueprintspatial.Rotation(newRot)

// Update world vertices for collision detection
shape.Polygon.WorldVertices = spatial.UpdateWorldVertices(
    shape.Polygon.LocalVertices,
    position.Two,
    scale.Two,
    float64(rotation)
)

Forces

Applies and accumulates forces like gravity, friction, and custom forces:

// Apply gravity
gravity := motion.Forces.Generator.NewGravityForce(mass, 9.8, 50.0)
motion.Forces.AddForce(dynamics, gravity)

// Apply friction
friction := motion.Forces.Generator.NewFrictionForce(velocity, 0.3)
motion.Forces.AddForce(dynamics, friction)

Impulses

Handles instantaneous changes in momentum for collisions:

// Apply an impulse (instantaneous force)
impulse := vector.Two{X: 0, Y: -500}
motion.ApplyImpulse(dynamics, impulse, vector.Two{})

Collision Resolution

The system includes several collision resolvers for different gameplay needs:

Standard Resolver

Resolves collisions with proper physical responses, including elasticity and friction:

// Full physics-based collision resolution
motion.Resolver.Resolve(
    &objectAPosition.Two,
    &objectBPosition.Two,
    objectADynamics,
    objectBDynamics,
    collisionResult,
)

Vertical Resolver

Specialized resolver focusing on vertical-only movement (useful for platformers):

// Platform-style vertical-only resolution
motion.VerticalResolver.Resolve(
    &playerPosition.Two,
    &platformPosition.Two,
    playerDynamics,
    platformDynamics,
    collisionResult,
)

Static Resolvers

Simple position correction without physics simulation, useful for immovable objects:

// Only move object A, treating B as immovable
spatial.Resolver.ResolveBStatic(shapeA, shapeB, &posA, &posB, collisionResult)

Standard Physics Systems

These are key systems that should be added to the bottom of a scene’s local core systems to ensure automatic physics integration/transformation.

Integration System

The IntegrationSystem updates entity positions and rotations based on their dynamic properties:

// Add the integration system to your core systems
coreSystems := []blueprint.CoreSystem{
    // Other systems...
    &coresystems.IntegrationSystem{},
}

Transform System

The TransformSystem updates collision shapes’ world coordinates based on entity position, rotation, and scale:

// Add the transform system to your core systems
coreSystems := []blueprint.CoreSystem{
    // Other systems...
    &coresystems.TransformSystem{},
}

Performance Considerations

Several factors affect the physics system’s performance:

  • Collision Checks Scaling: Without spatial partitioning, the number of potential collision checks grows quadratically with the number of physics objects, which is the main performance bottleneck.

  • Interface Usage: The vector system uses interfaces (TwoReader, TwoFace, etc.) which introduces some overhead due to dynamic dispatch, though modern Go compilers optimize this fairly well.

  • Concrete Type Conversions: In several places, interface types are converted to concrete types (e.g., vector.Two), adding some overhead but improving computation speed for subsequent operations.

For most 2D games, these performance considerations won’t be an issue. The system is designed to balance clean API design with reasonable performance for typical game scenarios.

Optimizing Physics Implementation

To get the most out of the physics system:

1. Limit Dynamic Objects

Keep entities with both collision and dynamics components to a reasonable number. Only give full physics properties to objects that truly need them.

2. Implement Custom Collision Filtering (Tags)

Create custom filtering to only check relevant collision pairs:

// Only check player vs. environment collisions
playerVsEnvironmentQuery := warehouse.Factory.NewQuery().And(
    PlayerTag,
    warehouse.Factory.NewQuery().Or(
        TerrainTag,
        PlatformTag
    )
)

// Only check enemy vs. environment collisions
enemyVsEnvironmentQuery := warehouse.Factory.NewQuery().And(
    EnemyTag,
    warehouse.Factory.NewQuery().Or(
        TerrainTag,
        PlatformTag
    )
)

// Don't check environment vs. environment

3. Choose the Right Resolvers

Use specialized resolvers where appropriate:

  • Standard Resolver: For objects that need realistic physical interactions
  • Vertical Resolver: For platformers to improve and gameplay feel
  • Static Resolvers: When you know one object should never move

Systems Execution Order

The order of physics systems is crucial. Here’s a typical order for a platformer:

coreSystems := []blueprint.CoreSystem{
    &coresystems.GravitySystem{},              // Apply gravity forces
    &coresystems.PlayerMovementSystem{},       // Apply player input forces
    &coresystems.IntegrationSystem{},          // Update positions based on forces
    &coresystems.TransformSystem{},            // Update collision shapes
    &coresystems.PlayerBlockCollisionSystem{}, // Handle solid block collisions
    &coresystems.PlayerPlatformCollisionSystem{}, // Handle one-way platforms
    &coresystems.OnGroundClearingSystem{},
}

When to Use tteokbokki

The tteokbokki physics system is well-suited for:

  • Typical 2D Games: Platformers, action games, simple simulations
  • Limited Dynamic Objects: Games where only a subset of objects (player, enemies, projectiles) need physics
  • Simple Collision Needs: Basic overlap detection and response
  • Medium-scale Projects: Where physics isn’t the primary bottleneck

Current Limitations

Be aware of these limitations when planning your game’s physics:

  • Limited Optimization: Not designed for large numbers of dynamic interacting objects
  • Missing Advanced Features: No joints, constraints, or complex physical materials
  • Simple Collision Resolution: May have issues with high-speed collisions or stacked objects
  • Limited Continuous Detection: Fast-moving objects might occasionally tunnel through thin obstacles

Conclusion

The tteokbokki physics system offers a pragmatic approach to 2D physics that integrates well with Bappa’s ECS architecture. While it doesn’t have all the bells and whistles of a specialized physics engine, it provides a solid foundation for most 2D game physics needs. The system shines in typical game scenarios where the number of dynamic, interacting objects remains moderate, and its limitations only become apparent in more extreme use cases.

By understanding both the capabilities and limitations of the system, you can make informed decisions about how to implement physics in your Bappa games, creating responsive and engaging gameplay while maintaining good performance.