Make Collision Detection more active wrt. actors #15

Closed
opened 2019-07-15 17:58:11 +00:00 by kirsle · 1 comment

Currently the Collision Detection in Doodle is handled in two phases:

  1. First, all mobile actors are moved and collision is checked against level geometry. At the end of this phase, the actor hitboxes are all noted for phase 2.
  2. Then, the actor hitboxes are checked for any overlapping and the actor OnCollide() handlers are called for any pair that are touching.

This two-phase system isn't suitable for fast-moving actors. For example, trapdoors have a ~6 pixel region that they define for themselves as "solid" and should prevent actors from crossing through; but if an actor was moving more than 6 pixels per tick they might completely phase through the solid part before the Trapdoor is notified about the movement. And so the moving actor clips straight through the trapdoor, even though the doodad itself is a large 72px square, because the OnCollide handler isn't called until after the movement step.

Instead, implement a more active collision detection where actors' OnCollide handlers can be called as part of the Phase 1 collision detection.

The collision code is currently kept in a sub-package that deals only with the collision math and has no access to any of the game's structures. So a callback-based method may be used to work around it.

Current function signature:

func CollidesWithGrid(d doodads.Actor, grid *level.Chunker, target render.Point)

Update the function to also accept an actorBoxes map[string]render.Rect so the game can pass in all of the actor boxes (excluding the d Actor being moved), and a function to call when a collision has occurred. The map can be keyed off the actors' ID strings in the level (UUIDs by default).

func CollidesWithGrid(d doodads.Actor, grid *level.Chunker, target render.Point,
    actorBoxes []render.Rect, onBoxCollision func(string, render.Rect) bool)

The onBoxCollision callback would have a function signature like:

// id of the box the current actor has intersected with
// the rect of the box?
// returns false if the collision is protested (acts solid),
//         true to allow the movement to proceed.
func onBoxCollision(id string, rect render.Rect) bool

The calling function (uix/actor_collision.go) can then be pinged every pixel-of-movement when actors are overlapping and call the OnCollide handlers and have a more precise method to detect collision between actors.

The CollidesWithGrid() function will handle the "moving back" of the actor in case the collision is protested, the same as it does when the actor bumps against a solid wall in the level.

The Phase 2 collision detection above might be obsoleted by this, making collision a single phase operation.

Currently the Collision Detection in Doodle is handled in two phases: 1. First, all mobile actors are moved and collision is checked against level geometry. At the end of this phase, the actor hitboxes are all noted for phase 2. 2. Then, the actor hitboxes are checked for any overlapping and the actor OnCollide() handlers are called for any pair that are touching. This two-phase system isn't suitable for fast-moving actors. For example, trapdoors have a ~6 pixel region that they define for themselves as "solid" and should prevent actors from crossing through; but if an actor was moving more than 6 pixels per tick they might completely phase through the solid part _before_ the Trapdoor is notified about the movement. And so the moving actor clips straight through the trapdoor, even though the doodad itself is a large 72px square, because the OnCollide handler isn't called until after the movement step. Instead, implement a more active collision detection where actors' OnCollide handlers can be called _as part of_ the Phase 1 collision detection. The collision code is currently kept in a sub-package that deals _only_ with the collision math and has no access to any of the game's structures. So a callback-based method may be used to work around it. Current function signature: ```go func CollidesWithGrid(d doodads.Actor, grid *level.Chunker, target render.Point) ``` Update the function to also accept an `actorBoxes map[string]render.Rect` so the game can pass in all of the actor boxes (excluding the `d` Actor being moved), and a function to call when a collision has occurred. The map can be keyed off the actors' ID strings in the level (UUIDs by default). ```go func CollidesWithGrid(d doodads.Actor, grid *level.Chunker, target render.Point, actorBoxes []render.Rect, onBoxCollision func(string, render.Rect) bool) ``` The onBoxCollision callback would have a function signature like: ```go // id of the box the current actor has intersected with // the rect of the box? // returns false if the collision is protested (acts solid), // true to allow the movement to proceed. func onBoxCollision(id string, rect render.Rect) bool ``` The calling function (uix/actor_collision.go) can then be pinged every pixel-of-movement when actors are overlapping and call the OnCollide handlers and have a more precise method to detect collision between actors. The CollidesWithGrid() function will handle the "moving back" of the actor in case the collision is protested, the same as it does when the actor bumps against a solid wall in the level. The Phase 2 collision detection above might be obsoleted by this, making collision a single phase operation.
kirsle added the
enhancement
label 2019-07-15 17:58:11 +00:00
Poster
Owner

Took a different approach.

Stage 1 is left unchanged. Actors move and collide with the level geometry and we note their Original and New Location.

In Stage 2, we re-trace the steps of each actor who's moved. For each step, if the actor (player) intersects with another actor (doodad) we call its OnCollide handler but pass a new property Settled: false (we're calling this handler rapidly as the player passes into the actor during their movement this tick).

OnCollide handlers can return false if they want to block the player. As Stage 2 retraces your steps, it will drop you in the Last Good Location before an actor protested your movement.

Finally (Stage 3) if the player is still intersecting actors, their OnCollide handler is called one last time with Settled: true to indicate "movement and collision checking is done."

Doodad Scripts should check for e.Settled === true before they fire off actions based on the collision (i.e. broadcast messages to linked doodads, change their state, etc.)

Took a different approach. Stage 1 is left unchanged. Actors move and collide with the level geometry and we note their Original and New Location. In Stage 2, we re-trace the steps of each actor who's moved. For each step, if the actor (player) intersects with another actor (doodad) we call its OnCollide handler but pass a new property `Settled: false` (we're calling this handler rapidly as the player passes into the actor during their movement this tick). OnCollide handlers can `return false` if they want to block the player. As Stage 2 retraces your steps, it will drop you in the Last Good Location before an actor protested your movement. Finally (Stage 3) if the player is still intersecting actors, their OnCollide handler is called one last time with `Settled: true` to indicate "movement and collision checking is done." Doodad Scripts should check for `e.Settled === true` before they fire off actions based on the collision (i.e. broadcast messages to linked doodads, change their state, etc.)
kirsle added this to the First Beta Release MVP milestone 2019-07-17 06:08:25 +00:00
Sign in to join this conversation.
No Assignees
1 Participants
Notifications
Due Date
The due date is invalid or out of range. Please use the format 'yyyy-mm-dd'.

No due date set.

Dependencies

No dependencies set.

Reference: SketchyMaze/doodle#15
There is no content yet.