Fix Actor Collision Checks Again

* Recent collision update caused a regression where the player would get
  "stuck" while standing on top of a solid doodad, unable to walk left
  or right.
* When deciding if the actor is on top of a doodad, use the doodad's
  Hitbox (if available) instead of the bounding box. This fixes the
  upside-down trapdoor acting solid when landed on from the top, since
  its Hitbox Y coordinate is not the same as the top of its sprite.
* Cheats: when using the noclip cheat in Play Mode, you can hold down
  the Shift key while moving to only move one pixel at a time.
physics
Noah 2020-01-02 22:05:49 -08:00
parent a43e45fad0
commit 0e3a30e633
4 changed files with 61 additions and 23 deletions

View File

@ -19,10 +19,6 @@ function main() {
var startedAnimation = false; var startedAnimation = false;
Events.OnCollide(function(e) { Events.OnCollide(function(e) {
// Only trigger for mobile characters.
if (!e.Actor.IsMobile()) {
return;
}
// If the floor is falling, the player passes right thru. // If the floor is falling, the player passes right thru.
if (state === stateFalling || state === stateFallen) { if (state === stateFalling || state === stateFallen) {
@ -36,6 +32,11 @@ function main() {
return false; return false;
} }
// If movement is not settled, be solid.
if (!e.Settled) {
return false;
}
// Begin the animation sequence if we're in the solid state. // Begin the animation sequence if we're in the solid state.
if (state === stateSolid) { if (state === stateSolid) {
state = stateShaking; state = stateShaking;

View File

@ -9,6 +9,8 @@
* `import antigravity` - during Play Mode, disables gravity for the player * `import antigravity` - during Play Mode, disables gravity for the player
character and allows free movement in all directions with the arrow keys. character and allows free movement in all directions with the arrow keys.
Enter the cheat again to restore gravity to normal. Enter the cheat again to restore gravity to normal.
* Note: under antigravity, hold down the Shift key to lower the player
speed to only one pixel per tick.
* `ghost mode` - during Play Mode, toggles noclip for the player character. * `ghost mode` - during Play Mode, toggles noclip for the player character.
## Bool Props ## Bool Props

View File

@ -427,6 +427,12 @@ func (s *PlayScene) Draw(d *Doodle) error {
func (s *PlayScene) movePlayer(ev *event.State) { func (s *PlayScene) movePlayer(ev *event.State) {
var playerSpeed = balance.PlayerMaxVelocity var playerSpeed = balance.PlayerMaxVelocity
// If antigravity enabled and the Shift key is pressed down, move the
// player by only one pixel per tick.
if s.antigravity && ev.Shift {
playerSpeed = 1
}
var velocity render.Point var velocity render.Point
if ev.Left { if ev.Left {

View File

@ -127,6 +127,19 @@ func (w *Canvas) loopActorCollision() error {
} }
) )
// HACK: below, when we determine the moving actor is "onTop" of
// the doodad's solid hitbox, we lockY their movement so they don't
// fall down further; but sometimes there's an off-by-one error if
// the actor fell a distance before landing, and so the final
// Settled collision check doesn't fire (i.e. if they fell onto a
// Crumbly Floor which should begin shaking when walked on).
//
// When we decide they're onTop, record the Y position, and then
// use it for collision-check purposes but DON'T physically move
// the character by it (moving the character may clip them thru
// other solid hitboxes like the upside-down trapdoor)
var onTopY int
// Firstly we want to make sure B isn't able to clip through A's // Firstly we want to make sure B isn't able to clip through A's
// solid hitbox if A protests the movement. Trace a vector from // solid hitbox if A protests the movement. Trace a vector from
// B's original position to their current one and ping A's // B's original position to their current one and ping A's
@ -147,10 +160,16 @@ func (w *Canvas) loopActorCollision() error {
// 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 (
lockX int
lockY int
)
for point := range render.IterLine( for point := range render.IterLine(
origPoint, origPoint,
b.Position(), b.Position(),
) { ) {
point := point
test := render.Rect{ test := render.Rect{
X: point.X, X: point.X,
Y: point.Y, Y: point.Y,
@ -158,11 +177,6 @@ func (w *Canvas) loopActorCollision() error {
H: rect.H, H: rect.H,
} }
var (
lockX bool
lockY bool
)
if info, err := collision.CompareBoxes(boxes[tuple.A], test); err == nil { if info, err := collision.CompareBoxes(boxes[tuple.A], test); err == nil {
// 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.
@ -176,39 +190,50 @@ func (w *Canvas) loopActorCollision() error {
// Did A protest? // Did A protest?
if err == scripting.ErrReturnFalse { if err == scripting.ErrReturnFalse {
// Are they on top? // Are they on top?
if render.AbsInt(lastGoodBox.Y+lastGoodBox.H-boxes[tuple.A].Y) <= 2 { aHitbox := doodads.GetBoundingRectHitbox(a, a.Hitbox())
if render.AbsInt(test.Y+test.H-aHitbox.Y) == 0 {
onTop = true onTop = true
onTopY = test.Y
} }
// What direction were we moving? // What direction were we moving?
if test.Y != lastGoodBox.Y { if test.Y != lastGoodBox.Y {
lockY = true if lockY == 0 {
b.SetGrounded(true) lockY = lastGoodBox.Y
}
if onTop {
b.SetGrounded(true)
}
} }
if test.X != lastGoodBox.X { if test.X != lastGoodBox.X {
if !onTop { if !onTop {
lockX = true lockX = lastGoodBox.X
} }
} }
// Move them back to the last good box, locking the // Move them back to the last good box.
// axis they were moving from being able to enter
// this box.
tmp := lastGoodBox
lastGoodBox = test lastGoodBox = test
if lockY { if lockX != 0 {
lastGoodBox.Y = tmp.Y lastGoodBox.X = lockX
}
if lockX {
lastGoodBox.X = tmp.X
break
} }
} else { } else {
// Move them back to the last good box.
lastGoodBox = test lastGoodBox = test
} }
} else {
// No collision between boxes, increment the lastGoodBox
lastGoodBox = test
} }
} }
// Did we lock their X or Y coordinate from moving further?
if lockY != 0 {
lastGoodBox.Y = lockY
}
if lockX != 0 {
lastGoodBox.X = lockX
}
if !b.noclip { if !b.noclip {
b.MoveTo(lastGoodBox.Point()) b.MoveTo(lastGoodBox.Point())
} }
@ -220,6 +245,10 @@ func (w *Canvas) loopActorCollision() error {
) )
} }
if onTopY != 0 && lastGoodBox.Y-onTopY <= 1 {
lastGoodBox.Y = onTopY
}
// Movement has been settled. Check if B's point is still invading // Movement has been settled. Check if B's point is still invading
// A's box and call its OnCollide handler one last time in // A's box and call its OnCollide handler one last time in
// Settled=true mode so it can run its actions. // Settled=true mode so it can run its actions.