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;
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 (state === stateFalling || state === stateFallen) {
@ -36,6 +32,11 @@ function main() {
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.
if (state === stateSolid) {
state = stateShaking;

View File

@ -9,6 +9,8 @@
* `import antigravity` - during Play Mode, disables gravity for the player
character and allows free movement in all directions with the arrow keys.
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.
## Bool Props

View File

@ -427,6 +427,12 @@ func (s *PlayScene) Draw(d *Doodle) error {
func (s *PlayScene) movePlayer(ev *event.State) {
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
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
// solid hitbox if A protests the movement. Trace a vector from
// 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.
var onTop = false
var (
lockX int
lockY int
)
for point := range render.IterLine(
origPoint,
b.Position(),
) {
point := point
test := render.Rect{
X: point.X,
Y: point.Y,
@ -158,11 +177,6 @@ func (w *Canvas) loopActorCollision() error {
H: rect.H,
}
var (
lockX bool
lockY bool
)
if info, err := collision.CompareBoxes(boxes[tuple.A], test); err == nil {
// B is overlapping A's box, call its OnCollide handler
// with Settled=false and see if it protests the overlap.
@ -176,39 +190,50 @@ func (w *Canvas) loopActorCollision() error {
// Did A protest?
if err == scripting.ErrReturnFalse {
// 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
onTopY = test.Y
}
// What direction were we moving?
if test.Y != lastGoodBox.Y {
lockY = true
b.SetGrounded(true)
if lockY == 0 {
lockY = lastGoodBox.Y
}
if onTop {
b.SetGrounded(true)
}
}
if test.X != lastGoodBox.X {
if !onTop {
lockX = true
lockX = lastGoodBox.X
}
}
// Move them back to the last good box, locking the
// axis they were moving from being able to enter
// this box.
tmp := lastGoodBox
// Move them back to the last good box.
lastGoodBox = test
if lockY {
lastGoodBox.Y = tmp.Y
}
if lockX {
lastGoodBox.X = tmp.X
break
if lockX != 0 {
lastGoodBox.X = lockX
}
} else {
// Move them back to the last good box.
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 {
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
// A's box and call its OnCollide handler one last time in
// Settled=true mode so it can run its actions.