Detect Collision Between Actors
* Move all collision code into the pkg/collision package. * pkg/doodads/collision.go -> pkg/collision/collide_level.go * pkg/doodads/collide_actors.go for new Actor collide support * Add initial collision detection code between actors in Play Mode.
This commit is contained in:
parent
241186209c
commit
f8a83cbad9
34
pkg/collision/collide_actors.go
Normal file
34
pkg/collision/collide_actors.go
Normal file
|
@ -0,0 +1,34 @@
|
|||
package collision
|
||||
|
||||
import (
|
||||
"git.kirsle.net/apps/doodle/lib/render"
|
||||
"git.kirsle.net/apps/doodle/pkg/log"
|
||||
)
|
||||
|
||||
// IndexTuple holds two integers used as array indexes.
|
||||
type IndexTuple [2]int
|
||||
|
||||
// BetweenBoxes checks if there is a collision between any
|
||||
// two bounding rectangles.
|
||||
//
|
||||
// This returns a generator that spits out indexes of the
|
||||
// intersecting boxes.
|
||||
func BetweenBoxes(boxes []render.Rect) chan IndexTuple {
|
||||
generator := make(chan IndexTuple)
|
||||
|
||||
go func() {
|
||||
// Outer loop: test each box for intersection with the others.
|
||||
for i, box := range boxes {
|
||||
for j := i + 1; j < len(boxes); j++ {
|
||||
if box.Intersects(boxes[j]) {
|
||||
log.Info("Actor %d intersects %d", i, j)
|
||||
generator <- IndexTuple{i, j}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
close(generator)
|
||||
}()
|
||||
|
||||
return generator
|
||||
}
|
|
@ -1,7 +1,8 @@
|
|||
package doodads
|
||||
package collision
|
||||
|
||||
import (
|
||||
"git.kirsle.net/apps/doodle/lib/render"
|
||||
"git.kirsle.net/apps/doodle/pkg/doodads"
|
||||
"git.kirsle.net/apps/doodle/pkg/level"
|
||||
)
|
||||
|
||||
|
@ -30,61 +31,6 @@ func (c *Collide) Reset() {
|
|||
c.Bottom = false
|
||||
}
|
||||
|
||||
// CollisionBox holds all of the coordinate pairs to draw the collision box
|
||||
// around a doodad.
|
||||
type CollisionBox struct {
|
||||
Top []render.Point
|
||||
Bottom []render.Point
|
||||
Left []render.Point
|
||||
Right []render.Point
|
||||
}
|
||||
|
||||
// GetCollisionBox returns a CollisionBox with the four coordinates.
|
||||
func GetCollisionBox(box render.Rect) CollisionBox {
|
||||
return CollisionBox{
|
||||
Top: []render.Point{
|
||||
{
|
||||
X: box.X,
|
||||
Y: box.Y,
|
||||
},
|
||||
{
|
||||
X: box.X + box.W,
|
||||
Y: box.Y,
|
||||
},
|
||||
},
|
||||
Bottom: []render.Point{
|
||||
{
|
||||
X: box.X,
|
||||
Y: box.Y + box.H,
|
||||
},
|
||||
{
|
||||
X: box.X + box.W,
|
||||
Y: box.Y + box.H,
|
||||
},
|
||||
},
|
||||
Left: []render.Point{
|
||||
{
|
||||
X: box.X,
|
||||
Y: box.Y + box.H - 1,
|
||||
},
|
||||
{
|
||||
X: box.X,
|
||||
Y: box.Y + 1,
|
||||
},
|
||||
},
|
||||
Right: []render.Point{
|
||||
{
|
||||
X: box.X + box.W,
|
||||
Y: box.Y + box.H - 1,
|
||||
},
|
||||
{
|
||||
X: box.X + box.W,
|
||||
Y: box.Y + 1,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Side of the collision box (top, bottom, left, right)
|
||||
type Side uint8
|
||||
|
||||
|
@ -101,7 +47,7 @@ CollidesWithGrid checks if a Doodad collides with level geometry.
|
|||
|
||||
The `target` is the point the actor wants to move to on this tick.
|
||||
*/
|
||||
func CollidesWithGrid(d Actor, grid *level.Chunker, target render.Point) (*Collide, bool) {
|
||||
func CollidesWithGrid(d doodads.Actor, grid *level.Chunker, target render.Point) (*Collide, bool) {
|
||||
var (
|
||||
P = d.Position()
|
||||
S = d.Size()
|
||||
|
@ -120,7 +66,7 @@ func CollidesWithGrid(d Actor, grid *level.Chunker, target render.Point) (*Colli
|
|||
)
|
||||
|
||||
// Test all of the bounding boxes for a collision with level geometry.
|
||||
if ok := result.ScanBoundingBox(GetBoundingRect(d), grid); ok {
|
||||
if ok := result.ScanBoundingBox(doodads.GetBoundingRect(d), grid); ok {
|
||||
// We've already collided! Try to wiggle free.
|
||||
if result.Bottom {
|
||||
if !d.Grounded() {
|
||||
|
@ -250,3 +196,40 @@ func CollidesWithGrid(d Actor, grid *level.Chunker, target render.Point) (*Colli
|
|||
func (c *Collide) IsColliding() bool {
|
||||
return c.Top || c.Bottom || c.Left || c.Right
|
||||
}
|
||||
|
||||
// ScanBoundingBox scans all of the pixels in a bounding box on the grid and
|
||||
// returns if any of them intersect with level geometry.
|
||||
func (c *Collide) ScanBoundingBox(box render.Rect, grid *level.Chunker) bool {
|
||||
col := GetCollisionBox(box)
|
||||
|
||||
c.ScanGridLine(col.Top[0], col.Top[1], grid, Top)
|
||||
c.ScanGridLine(col.Bottom[0], col.Bottom[1], grid, Bottom)
|
||||
c.ScanGridLine(col.Left[0], col.Left[1], grid, Left)
|
||||
c.ScanGridLine(col.Right[0], col.Right[1], grid, Right)
|
||||
return c.IsColliding()
|
||||
}
|
||||
|
||||
// ScanGridLine scans all of the pixels between p1 and p2 on the grid and tests
|
||||
// for any pixels to be set, implying a collision between level geometry and the
|
||||
// bounding boxes of the doodad.
|
||||
func (c *Collide) ScanGridLine(p1, p2 render.Point, grid *level.Chunker, side Side) {
|
||||
for point := range render.IterLine2(p1, p2) {
|
||||
if _, err := grid.Get(point); err == nil {
|
||||
// A hit!
|
||||
switch side {
|
||||
case Top:
|
||||
c.Top = true
|
||||
c.TopPoint = point
|
||||
case Bottom:
|
||||
c.Bottom = true
|
||||
c.BottomPoint = point
|
||||
case Left:
|
||||
c.Left = true
|
||||
c.LeftPoint = point
|
||||
case Right:
|
||||
c.Right = true
|
||||
c.RightPoint = point
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
58
pkg/collision/debug_box.go
Normal file
58
pkg/collision/debug_box.go
Normal file
|
@ -0,0 +1,58 @@
|
|||
package collision
|
||||
|
||||
import "git.kirsle.net/apps/doodle/lib/render"
|
||||
|
||||
// CollisionBox holds all of the coordinate pairs to draw the collision box
|
||||
// around a doodad.
|
||||
type CollisionBox struct {
|
||||
Top []render.Point
|
||||
Bottom []render.Point
|
||||
Left []render.Point
|
||||
Right []render.Point
|
||||
}
|
||||
|
||||
// GetCollisionBox returns a CollisionBox with the four coordinates.
|
||||
func GetCollisionBox(box render.Rect) CollisionBox {
|
||||
return CollisionBox{
|
||||
Top: []render.Point{
|
||||
{
|
||||
X: box.X,
|
||||
Y: box.Y,
|
||||
},
|
||||
{
|
||||
X: box.X + box.W,
|
||||
Y: box.Y,
|
||||
},
|
||||
},
|
||||
Bottom: []render.Point{
|
||||
{
|
||||
X: box.X,
|
||||
Y: box.Y + box.H,
|
||||
},
|
||||
{
|
||||
X: box.X + box.W,
|
||||
Y: box.Y + box.H,
|
||||
},
|
||||
},
|
||||
Left: []render.Point{
|
||||
{
|
||||
X: box.X,
|
||||
Y: box.Y + box.H - 1,
|
||||
},
|
||||
{
|
||||
X: box.X,
|
||||
Y: box.Y + 1,
|
||||
},
|
||||
},
|
||||
Right: []render.Point{
|
||||
{
|
||||
X: box.X + box.W,
|
||||
Y: box.Y + box.H - 1,
|
||||
},
|
||||
{
|
||||
X: box.X + box.W,
|
||||
Y: box.Y + 1,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
|
@ -2,7 +2,6 @@ package doodads
|
|||
|
||||
import (
|
||||
"git.kirsle.net/apps/doodle/lib/render"
|
||||
"git.kirsle.net/apps/doodle/pkg/level"
|
||||
)
|
||||
|
||||
// Actor is a reusable run-time drawing component used in Doodle. Actors are an
|
||||
|
@ -36,40 +35,3 @@ func GetBoundingRect(d Actor) render.Rect {
|
|||
H: S.H,
|
||||
}
|
||||
}
|
||||
|
||||
// ScanBoundingBox scans all of the pixels in a bounding box on the grid and
|
||||
// returns if any of them intersect with level geometry.
|
||||
func (c *Collide) ScanBoundingBox(box render.Rect, grid *level.Chunker) bool {
|
||||
col := GetCollisionBox(box)
|
||||
|
||||
c.ScanGridLine(col.Top[0], col.Top[1], grid, Top)
|
||||
c.ScanGridLine(col.Bottom[0], col.Bottom[1], grid, Bottom)
|
||||
c.ScanGridLine(col.Left[0], col.Left[1], grid, Left)
|
||||
c.ScanGridLine(col.Right[0], col.Right[1], grid, Right)
|
||||
return c.IsColliding()
|
||||
}
|
||||
|
||||
// ScanGridLine scans all of the pixels between p1 and p2 on the grid and tests
|
||||
// for any pixels to be set, implying a collision between level geometry and the
|
||||
// bounding boxes of the doodad.
|
||||
func (c *Collide) ScanGridLine(p1, p2 render.Point, grid *level.Chunker, side Side) {
|
||||
for point := range render.IterLine2(p1, p2) {
|
||||
if _, err := grid.Get(point); err == nil {
|
||||
// A hit!
|
||||
switch side {
|
||||
case Top:
|
||||
c.Top = true
|
||||
c.TopPoint = point
|
||||
case Bottom:
|
||||
c.Bottom = true
|
||||
c.BottomPoint = point
|
||||
case Left:
|
||||
c.Left = true
|
||||
c.LeftPoint = point
|
||||
case Right:
|
||||
c.Right = true
|
||||
c.RightPoint = point
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
"git.kirsle.net/apps/doodle/lib/render"
|
||||
"git.kirsle.net/apps/doodle/lib/ui"
|
||||
"git.kirsle.net/apps/doodle/pkg/balance"
|
||||
"git.kirsle.net/apps/doodle/pkg/collision"
|
||||
"git.kirsle.net/apps/doodle/pkg/doodads"
|
||||
)
|
||||
|
||||
|
@ -142,7 +143,7 @@ func (d *Doodle) DrawCollisionBox(actor doodads.Actor) {
|
|||
|
||||
var (
|
||||
rect = doodads.GetBoundingRect(actor)
|
||||
box = doodads.GetCollisionBox(rect)
|
||||
box = collision.GetCollisionBox(rect)
|
||||
)
|
||||
|
||||
d.Engine.DrawLine(render.DarkGreen, box.Top[0], box.Top[1])
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
"git.kirsle.net/apps/doodle/lib/render"
|
||||
"git.kirsle.net/apps/doodle/lib/ui"
|
||||
"git.kirsle.net/apps/doodle/pkg/balance"
|
||||
"git.kirsle.net/apps/doodle/pkg/collision"
|
||||
"git.kirsle.net/apps/doodle/pkg/doodads"
|
||||
"git.kirsle.net/apps/doodle/pkg/level"
|
||||
"git.kirsle.net/apps/doodle/pkg/log"
|
||||
|
@ -167,60 +168,48 @@ func (w *Canvas) Loop(ev *events.State) error {
|
|||
log.Debug("loopConstrainScroll: %s", err)
|
||||
}
|
||||
|
||||
// Move any actors.
|
||||
for _, a := range w.actors {
|
||||
if v := a.Velocity(); v != render.Origin {
|
||||
// Create a delta point from their current location to where they
|
||||
// want to move to this tick.
|
||||
delta := a.Position()
|
||||
delta.Add(v)
|
||||
// Move any actors. As we iterate over all actors, track their bounding
|
||||
// rectangles so we can later see if any pair of actors intersect each other.
|
||||
boxes := make([]render.Rect, len(w.actors))
|
||||
for i, a := range w.actors {
|
||||
// Get the actor's velocity to see if it's moving this tick.
|
||||
v := a.Velocity()
|
||||
|
||||
// Check collision with level geometry.
|
||||
info, ok := doodads.CollidesWithGrid(a, w.chunks, delta)
|
||||
if ok {
|
||||
// Collision happened with world.
|
||||
log.Error("COLLIDE %+v", info)
|
||||
}
|
||||
delta = info.MoveTo // Move us back where the collision check put us
|
||||
|
||||
// Move the actor's World Position to the new location.
|
||||
a.MoveTo(delta)
|
||||
|
||||
// Keep them contained inside the level.
|
||||
if w.wallpaper.pageType > level.Unbounded {
|
||||
var (
|
||||
orig = a.Position() // Actor's World Position
|
||||
moveBy render.Point
|
||||
size = a.Size()
|
||||
)
|
||||
|
||||
// Bound it on the top left edges.
|
||||
if orig.X < 0 {
|
||||
moveBy.X = -orig.X
|
||||
}
|
||||
if orig.Y < 0 {
|
||||
moveBy.Y = -orig.Y
|
||||
}
|
||||
|
||||
// Bound it on the right bottom edges. XXX: downcast from int64!
|
||||
if w.wallpaper.maxWidth > 0 {
|
||||
if int64(orig.X+size.W) > w.wallpaper.maxWidth {
|
||||
var delta = int32(w.wallpaper.maxWidth - int64(orig.X+size.W))
|
||||
moveBy.X = delta
|
||||
}
|
||||
}
|
||||
if w.wallpaper.maxHeight > 0 {
|
||||
if int64(orig.Y+size.H) > w.wallpaper.maxHeight {
|
||||
var delta = int32(w.wallpaper.maxHeight - int64(orig.Y+size.H))
|
||||
moveBy.Y = delta
|
||||
}
|
||||
}
|
||||
|
||||
if !moveBy.IsZero() {
|
||||
a.MoveBy(moveBy)
|
||||
}
|
||||
}
|
||||
// If not moving, grab the bounding box right now.
|
||||
if v == render.Origin {
|
||||
boxes[i] = doodads.GetBoundingRect(a)
|
||||
continue
|
||||
}
|
||||
|
||||
// Create a delta point from their current location to where they
|
||||
// want to move to this tick.
|
||||
delta := a.Position()
|
||||
delta.Add(v)
|
||||
|
||||
// Check collision with level geometry.
|
||||
info, ok := collision.CollidesWithGrid(a, w.chunks, delta)
|
||||
if ok {
|
||||
// Collision happened with world.
|
||||
log.Error("COLLIDE %+v", info)
|
||||
}
|
||||
delta = info.MoveTo // Move us back where the collision check put us
|
||||
|
||||
// Move the actor's World Position to the new location.
|
||||
a.MoveTo(delta)
|
||||
|
||||
// Keep the actor from leaving the world borders of bounded maps.
|
||||
w.loopContainActorsInsideLevel(a)
|
||||
|
||||
// Store this actor's bounding box after they've moved.
|
||||
boxes[i] = doodads.GetBoundingRect(a)
|
||||
}
|
||||
|
||||
// Check collisions between actors.
|
||||
for tuple := range collision.BetweenBoxes(boxes) {
|
||||
log.Error("Actor %s collides with %s",
|
||||
w.actors[tuple[0]].ID(),
|
||||
w.actors[tuple[1]].ID(),
|
||||
)
|
||||
}
|
||||
|
||||
// If the canvas is editable, only care if it's over our space.
|
||||
|
|
|
@ -23,6 +23,47 @@ func (wp *Wallpaper) Valid() bool {
|
|||
return wp.repeat != nil
|
||||
}
|
||||
|
||||
// Canvas Loop() task that keeps mobile actors constrained inside the borders
|
||||
// of the world for bounded map types.
|
||||
func (w *Canvas) loopContainActorsInsideLevel(a *Actor) {
|
||||
// Infinite maps do not need to constrain the actors.
|
||||
if w.wallpaper.pageType == level.Unbounded {
|
||||
return
|
||||
}
|
||||
|
||||
var (
|
||||
orig = a.Position() // Actor's World Position
|
||||
moveBy render.Point
|
||||
size = a.Size()
|
||||
)
|
||||
|
||||
// Bound it on the top left edges.
|
||||
if orig.X < 0 {
|
||||
moveBy.X = -orig.X
|
||||
}
|
||||
if orig.Y < 0 {
|
||||
moveBy.Y = -orig.Y
|
||||
}
|
||||
|
||||
// Bound it on the right bottom edges. XXX: downcast from int64!
|
||||
if w.wallpaper.maxWidth > 0 {
|
||||
if int64(orig.X+size.W) > w.wallpaper.maxWidth {
|
||||
var delta = int32(w.wallpaper.maxWidth - int64(orig.X+size.W))
|
||||
moveBy.X = delta
|
||||
}
|
||||
}
|
||||
if w.wallpaper.maxHeight > 0 {
|
||||
if int64(orig.Y+size.H) > w.wallpaper.maxHeight {
|
||||
var delta = int32(w.wallpaper.maxHeight - int64(orig.Y+size.H))
|
||||
moveBy.Y = delta
|
||||
}
|
||||
}
|
||||
|
||||
if !moveBy.IsZero() {
|
||||
a.MoveBy(moveBy)
|
||||
}
|
||||
}
|
||||
|
||||
// PresentWallpaper draws the wallpaper.
|
||||
func (w *Canvas) PresentWallpaper(e render.Engine, p render.Point) error {
|
||||
var (
|
||||
|
|
Loading…
Reference in New Issue
Block a user