WIP: Fix actor collisions with offset hitboxes
When an actor's Hitbox doesn't begin at 0,0 the collision checks between boxes is buggy. Current progress: * Player is a 32x64 size sprite with a hitbox of 0,32 32x32 (bottom half) * Landing onTop works * Hitting onBottom works * Bug: colliding from the side currently pushes the player 32px down into the floor. With non-offset doodads walking sideways into e.g. a locked door halts the X and Y movement until you let go, but offset doodads get pushed down mysteriously.
This commit is contained in:
parent
1f00af5741
commit
618d4b07c5
|
@ -95,7 +95,7 @@ var (
|
||||||
AutoSaveInterval = 5 * time.Minute
|
AutoSaveInterval = 5 * time.Minute
|
||||||
|
|
||||||
// Default player character doodad in Play Mode.
|
// Default player character doodad in Play Mode.
|
||||||
PlayerCharacterDoodad = "boy.doodad"
|
PlayerCharacterDoodad = "example-mario.doodad"
|
||||||
|
|
||||||
// Levelpack and level names for the title screen.
|
// Levelpack and level names for the title screen.
|
||||||
DemoLevelPack = "assets/levelpacks/builtin-Tutorial.levelpack"
|
DemoLevelPack = "assets/levelpacks/builtin-Tutorial.levelpack"
|
||||||
|
|
|
@ -24,7 +24,9 @@ func GetBoundingRect(a Actor) render.Rect {
|
||||||
// account their self-declared collision hitbox.
|
// account their self-declared collision hitbox.
|
||||||
//
|
//
|
||||||
// The rect returned has the X,Y coordinate set to the actor's position, plus
|
// The rect returned has the X,Y coordinate set to the actor's position, plus
|
||||||
// the X,Y of their hitbox, if any.
|
// the X,Y of their hitbox, if any. For example, their sprite size could be 64x32
|
||||||
|
// and their hitbox is the lower 0,32,32,32 half. This function would return the
|
||||||
|
// world coordinate of where their bounding box begins.
|
||||||
//
|
//
|
||||||
// The W,H of the rect is the W,H of their declared hitbox.
|
// The W,H of the rect is the W,H of their declared hitbox.
|
||||||
//
|
//
|
||||||
|
|
|
@ -117,7 +117,7 @@ func BoxCollidesWithGrid(d Actor, grid *level.Chunker, target render.Point) (*Co
|
||||||
// Adjust the actor's bounding rect by its stated Hitbox from its script.
|
// Adjust the actor's bounding rect by its stated Hitbox from its script.
|
||||||
// e.g.: Boy's Canvas size is 56x56 but he is a narrower character with a
|
// e.g.: Boy's Canvas size is 56x56 but he is a narrower character with a
|
||||||
// hitbox width smaller than its Canvas size.
|
// hitbox width smaller than its Canvas size.
|
||||||
S = SizePlusHitbox(GetBoundingRect(d), hitbox)
|
S = GetBoundingRectHitbox(d, hitbox)
|
||||||
actorHeight := P.Y + S.H
|
actorHeight := P.Y + S.H
|
||||||
|
|
||||||
// Test if we are ALREADY colliding with level geometry and try and wiggle
|
// Test if we are ALREADY colliding with level geometry and try and wiggle
|
||||||
|
|
|
@ -160,11 +160,10 @@ func (d *Doodle) DrawCollisionBox(canvas *uix.Canvas, actor *uix.Actor) {
|
||||||
var (
|
var (
|
||||||
rect = collision.GetBoundingRect(actor)
|
rect = collision.GetBoundingRect(actor)
|
||||||
box = collision.GetCollisionBox(rect)
|
box = collision.GetCollisionBox(rect)
|
||||||
hitbox = actor.Hitbox()
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Adjust the actor's bounding rect by its stated Hitbox from its script.
|
// Adjust the actor's bounding rect by its stated Hitbox from its script.
|
||||||
rect = collision.SizePlusHitbox(rect, hitbox)
|
rect = collision.GetBoundingRectHitbox(actor, actor.Hitbox())
|
||||||
|
|
||||||
box = collision.GetCollisionBox(rect)
|
box = collision.GetCollisionBox(rect)
|
||||||
|
|
||||||
|
|
|
@ -31,6 +31,7 @@ func (w *Canvas) loopActorCollision() error {
|
||||||
// collision later, store each actor's original position before the move.
|
// collision later, store each actor's original position before the move.
|
||||||
boxes = make([]render.Rect, len(w.actors))
|
boxes = make([]render.Rect, len(w.actors))
|
||||||
originalPositions = map[string]render.Point{}
|
originalPositions = map[string]render.Point{}
|
||||||
|
originalHitboxes = map[string]render.Rect{} // original world hitboxes
|
||||||
)
|
)
|
||||||
|
|
||||||
// Loop over all the actors in parallel, processing their movement and
|
// Loop over all the actors in parallel, processing their movement and
|
||||||
|
@ -48,6 +49,7 @@ func (w *Canvas) loopActorCollision() error {
|
||||||
func(i int, a *Actor) {
|
func(i int, a *Actor) {
|
||||||
// defer wg.Done()
|
// defer wg.Done()
|
||||||
originalPositions[a.ID()] = a.Position()
|
originalPositions[a.ID()] = a.Position()
|
||||||
|
originalHitboxes[a.ID()] = collision.GetBoundingRectHitbox(a, a.Hitbox())
|
||||||
|
|
||||||
// Advance any animations for this actor.
|
// Advance any animations for this actor.
|
||||||
if a.activeAnimation != nil && a.activeAnimation.nextFrameAt.Before(now) {
|
if a.activeAnimation != nil && a.activeAnimation.nextFrameAt.Before(now) {
|
||||||
|
@ -130,11 +132,13 @@ func (w *Canvas) loopActorCollision() error {
|
||||||
w.loopContainActorsInsideLevel(a)
|
w.loopContainActorsInsideLevel(a)
|
||||||
|
|
||||||
// Store this actor's bounding box after they've moved.
|
// Store this actor's bounding box after they've moved.
|
||||||
boxes[i] = collision.SizePlusHitbox(collision.GetBoundingRect(a), a.Hitbox())
|
boxes[i] = collision.GetBoundingRect(a)
|
||||||
}(i, a)
|
}(i, a)
|
||||||
// wg.Wait()
|
// wg.Wait()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// log.Warn("== BEGIN BetweenBoxes")
|
||||||
|
|
||||||
var collidingActors = map[*Actor]*Actor{}
|
var collidingActors = map[*Actor]*Actor{}
|
||||||
for tuple := range collision.BetweenBoxes(boxes) {
|
for tuple := range collision.BetweenBoxes(boxes) {
|
||||||
a, b := w.actors[tuple.A], w.actors[tuple.B]
|
a, b := w.actors[tuple.A], w.actors[tuple.B]
|
||||||
|
@ -146,13 +150,16 @@ func (w *Canvas) loopActorCollision() error {
|
||||||
|
|
||||||
collidingActors[a] = b
|
collidingActors[a] = b
|
||||||
|
|
||||||
// log.Error("between boxes: %+v <%s> <%s>", tuple, a.ID(), b.ID())
|
log.Error("between boxes: %+v A=<%s> B=<%s>", tuple, a.ID(), b.ID())
|
||||||
|
|
||||||
// Call the OnCollide handler for A informing them of B's intersection.
|
// Call the OnCollide handler for A informing them of B's intersection.
|
||||||
if w.scripting != nil {
|
if w.scripting != nil {
|
||||||
var (
|
var (
|
||||||
rect = collision.SizePlusHitbox(collision.GetBoundingRect(b), b.Hitbox())
|
rect = collision.GetBoundingRectHitbox(b, b.Hitbox())
|
||||||
|
// lastGoodBox = rect
|
||||||
lastGoodBox = render.Rect{
|
lastGoodBox = render.Rect{
|
||||||
|
// Level Positions of the doodad is based on the top left
|
||||||
|
// of its graphical sprite, not its (possibly offset) hitbox.
|
||||||
X: originalPositions[b.ID()].X,
|
X: originalPositions[b.ID()].X,
|
||||||
Y: originalPositions[b.ID()].Y,
|
Y: originalPositions[b.ID()].Y,
|
||||||
W: boxes[tuple.B].W,
|
W: boxes[tuple.B].W,
|
||||||
|
@ -180,7 +187,7 @@ func (w *Canvas) loopActorCollision() error {
|
||||||
// only return false if it protests the movement, but not trigger
|
// only return false if it protests the movement, but not trigger
|
||||||
// any actions (such as emit messages to linked doodads) until
|
// any actions (such as emit messages to linked doodads) until
|
||||||
// Settled=true.
|
// Settled=true.
|
||||||
if origPoint, ok := originalPositions[b.ID()]; ok {
|
if origHitbox, ok := originalHitboxes[b.ID()]; ok {
|
||||||
// Trace a vector back from the actor's current position
|
// Trace a vector back from the actor's current position
|
||||||
// to where they originated from. If A protests B's position at
|
// to where they originated from. If A protests B's position at
|
||||||
// ANY time, we mark didProtest=true and continue backscanning
|
// ANY time, we mark didProtest=true and continue backscanning
|
||||||
|
@ -192,15 +199,26 @@ func (w *Canvas) loopActorCollision() error {
|
||||||
// horizontal movement on the X axis.
|
// horizontal movement on the X axis.
|
||||||
// Touching the solid actor from the side is already fine.
|
// Touching the solid actor from the side is already fine.
|
||||||
var onTop = false
|
var onTop = false
|
||||||
|
var onBottom = false // they hit the bottom instead
|
||||||
|
|
||||||
var (
|
var (
|
||||||
lockX int
|
lockX int
|
||||||
lockY int
|
lockY int
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// If their original hitbox is offset from their sprite corner,
|
||||||
|
// gather the offset now.
|
||||||
|
var (
|
||||||
|
origPosition = originalPositions[b.ID()]
|
||||||
|
hitboxPadding = render.Point{
|
||||||
|
X: render.AbsInt(origHitbox.X - origPosition.X),
|
||||||
|
Y: render.AbsInt(origHitbox.Y - origPosition.Y),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
for point := range render.IterLine(
|
for point := range render.IterLine(
|
||||||
origPoint,
|
origHitbox.Point(),
|
||||||
b.Position(),
|
b.Position(), // TODO: verify non 0,0 hitbox doodads work
|
||||||
) {
|
) {
|
||||||
point := point
|
point := point
|
||||||
test := render.Rect{
|
test := render.Rect{
|
||||||
|
@ -211,45 +229,84 @@ func (w *Canvas) loopActorCollision() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
if info, err := collision.CompareBoxes(boxes[tuple.A], test); err == nil {
|
if info, err := collision.CompareBoxes(boxes[tuple.A], test); err == nil {
|
||||||
|
// A and B have their drawings overlapping on the page. Get each
|
||||||
|
// of their declared hitboxes (if smaller) to see if their hitboxes
|
||||||
|
// intersect as well.
|
||||||
|
var (
|
||||||
|
aHitbox = collision.GetBoundingRectHitbox(a, a.Hitbox())
|
||||||
|
bHitbox = collision.GetBoundingRectHitbox(b, b.Hitbox())
|
||||||
|
)
|
||||||
|
|
||||||
// B is overlapping A's box, call its OnCollide handler
|
// B is overlapping A's box, call its OnCollide handler
|
||||||
// with Settled=false and see if it protests the overlap.
|
// with Settled=false and see if it protests the overlap.
|
||||||
err := w.scripting.To(a.ID()).Events.RunCollide(&CollideEvent{
|
err := w.scripting.To(a.ID()).Events.RunCollide(&CollideEvent{
|
||||||
Actor: b,
|
Actor: b,
|
||||||
Overlap: info.Overlap,
|
Overlap: info.Overlap,
|
||||||
InHitbox: info.Overlap.Intersects(a.Hitbox()),
|
InHitbox: aHitbox.Intersects(bHitbox),
|
||||||
Settled: false,
|
Settled: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// log.Warn("ActorCollision: CompareBoxes info was %+v", info)
|
||||||
|
|
||||||
// Did A protest?
|
// Did A protest?
|
||||||
if err == scripting.ErrReturnFalse {
|
if err == scripting.ErrReturnFalse {
|
||||||
// Are they on top?
|
// Are they on top?
|
||||||
aHitbox := collision.SizePlusHitbox(collision.GetBoundingRect(a), a.Hitbox())
|
var (
|
||||||
if render.AbsInt(test.Y+test.H-aHitbox.Y) == 0 {
|
aHitbox = collision.GetBoundingRectHitbox(a, a.Hitbox())
|
||||||
// log.Error("ActorCollision: onTop=true at Y=%s", test.Y)
|
bBottom = test.Y + test.H // bottom of falling actor
|
||||||
|
aTop = aHitbox.Y
|
||||||
|
aBottom = aHitbox.Y + aHitbox.H
|
||||||
|
bTop = test.Y
|
||||||
|
)
|
||||||
|
|
||||||
|
// Is the colliding actor on top? (B=player character)
|
||||||
|
if render.AbsInt(bBottom-aTop) < 4 {
|
||||||
|
log.Error("ActorCollision: onTop=true at Y=%d", test.Y)
|
||||||
onTop = true
|
onTop = true
|
||||||
onTopY = test.Y
|
onTopY = aHitbox.Y
|
||||||
|
}
|
||||||
|
|
||||||
|
// Or are they hitting from below?
|
||||||
|
if render.AbsInt(aBottom-bTop) < 4 {
|
||||||
|
log.Info("&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&")
|
||||||
|
log.Info("&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&")
|
||||||
|
log.Info("&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&")
|
||||||
|
log.Error("ActorCollision: hit the bottom at Y=%d", test.Y)
|
||||||
|
onBottom = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// What direction were we moving?
|
// What direction were we moving?
|
||||||
if test.Y != lastGoodBox.Y {
|
if test.Y != lastGoodBox.Y {
|
||||||
if lockY == 0 {
|
if lockY == 0 {
|
||||||
lockY = lastGoodBox.Y
|
lockY = lastGoodBox.Y
|
||||||
|
if onBottom {
|
||||||
|
lockY = lastGoodBox.Y - hitboxPadding.Y
|
||||||
}
|
}
|
||||||
|
log.Error("### Set LockY = %d", lockY)
|
||||||
|
}
|
||||||
|
|
||||||
if onTop {
|
if onTop {
|
||||||
// log.Error("ActorCollision: setGrounded(true)", test.Y)
|
log.Error("ActorCollision: setGrounded(true) at Y=%d", test.Y)
|
||||||
b.SetGrounded(true)
|
b.SetGrounded(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if test.X != lastGoodBox.X {
|
if test.X != lastGoodBox.X {
|
||||||
if !onTop {
|
if lockX == 0 && !(onTop || onBottom) {
|
||||||
|
// lockY = lastGoodBox.Y - (hitboxPadding.Y / 2)
|
||||||
lockX = lastGoodBox.X
|
lockX = lastGoodBox.X
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Move them back to the last good box.
|
// Move them back to the last good box.
|
||||||
lastGoodBox = test
|
lastGoodBox = render.Rect{
|
||||||
|
X: test.X - hitboxPadding.X,
|
||||||
|
Y: test.Y - hitboxPadding.Y,
|
||||||
|
W: test.W,
|
||||||
|
H: test.H,
|
||||||
|
}
|
||||||
if lockX != 0 {
|
if lockX != 0 {
|
||||||
lastGoodBox.X = lockX
|
// lockY = lastGoodBox.Y + hitboxPadding.Y
|
||||||
|
lastGoodBox.X = lockX - hitboxPadding.X
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -273,6 +330,9 @@ func (w *Canvas) loopActorCollision() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
if !b.noclip {
|
if !b.noclip {
|
||||||
|
log.Error("Move B to: %s", lastGoodBox.Point())
|
||||||
|
|
||||||
|
// The stationary doodad should move the moving one only.
|
||||||
b.MoveTo(lastGoodBox.Point())
|
b.MoveTo(lastGoodBox.Point())
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -313,6 +373,8 @@ func (w *Canvas) loopActorCollision() error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Warn("-- END BetweenBoxes")
|
||||||
|
|
||||||
// Check for lacks of collisions since last frame.
|
// Check for lacks of collisions since last frame.
|
||||||
for sourceActor, targetActor := range w.collidingActors {
|
for sourceActor, targetActor := range w.collidingActors {
|
||||||
if _, ok := collidingActors[sourceActor]; !ok {
|
if _, ok := collidingActors[sourceActor]; !ok {
|
||||||
|
|
Loading…
Reference in New Issue
Block a user