Level Collision and Scrolling Fixes

* Fix the level collision bug that allowed clipping thru a ceiling while
  climbing up a wall.
* Fix the scrolling behavior to keep the character on-screen no matter
  how fast the character is moving, especially downwards.
* Increase player speed and gravity.
* New cheat: "ghost mode" disables clipping for the player character.
* Mark an actor as "grounded" if they fall and are stopped by the lower
  level border, so they may jump again.
physics
Noah 2020-01-02 20:23:27 -08:00
parent 7b3aec0fef
commit a43e45fad0
9 changed files with 77 additions and 40 deletions

View File

@ -9,6 +9,7 @@
* `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.
* `ghost mode` - during Play Mode, toggles noclip for the player character.
## Bool Props

View File

@ -10,14 +10,13 @@ var (
CanvasScrollSpeed = 8
// Window scrolling behavior in Play Mode.
ScrollboxHoz = 256 // horizontal px from window border to start scrol
ScrollboxVert = 128
ScrollMaxVelocity = 8 // 24
ScrollboxHoz = 256 // horizontal px from window border to start scrol
ScrollboxVert = 128
// Player speeds
PlayerMaxVelocity = 8
PlayerMaxVelocity = 10
PlayerAcceleration = 2
Gravity = 2
Gravity = 6
// Default chunk size for canvases.
ChunkSize = 128

View File

@ -84,7 +84,8 @@ func CollidesWithGrid(d doodads.Actor, grid *level.Chunker, target render.Point)
d.SetGrounded(false)
}
if result.Top {
// Never seen it touch the top.
ceiling = true
P.Y++
}
if result.Left {
P.X++
@ -141,6 +142,13 @@ func CollidesWithGrid(d doodads.Actor, grid *level.Chunker, target render.Point)
result.Reset()
result.MoveTo = P
for point := range render.IterLine(P, target) {
// Before we compute their next move, if we're already capping their
// height make sure the new point stays capped too. This prevents them
// clipping thru a ceiling if they were also holding right/left too.
if capHeight != 0 && point.Y < capHeight {
point.Y = capHeight
}
if has := result.ScanBoundingBox(render.Rect{
X: point.X,
Y: point.Y,
@ -174,7 +182,6 @@ func CollidesWithGrid(d doodads.Actor, grid *level.Chunker, target render.Point)
// So far so good, keep following the MoveTo to
// the last good point before a collision.
result.MoveTo = point
}
// If they hit the roof, cap them to the roof.
@ -225,10 +232,11 @@ func (c *Collide) ScanBoundingBox(box render.Rect, grid *level.Chunker) bool {
var wg sync.WaitGroup
for _, job := range jobs {
wg.Add(1)
go func(job jobSide) {
job := job
go func() {
defer wg.Done()
c.ScanGridLine(job.p1, job.p2, grid, job.side)
}(job)
}()
}
wg.Wait()
@ -239,6 +247,15 @@ func (c *Collide) ScanBoundingBox(box render.Rect, grid *level.Chunker) bool {
// 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) {
// If scanning the top or bottom line, offset the X coordinate by 1 pixel.
// This is because the 4 corners of the bounding box share their corner
// pixel with each side, so the Left and Right edges will check the
// left- and right-most point.
if side == Top || side == Bottom {
p1.X += 1
p2.X -= 1
}
for point := range render.IterLine(p1, p2) {
if swatch, err := grid.Get(point); err == nil {
// We're intersecting a pixel! If it's a solid one we'll return it

View File

@ -63,6 +63,23 @@ func (c Command) Run(d *Doodle) error {
d.Flash("Use this cheat in Play Mode to disable gravity for the player character.")
}
return nil
} else if c.Raw == "ghost mode" {
if playScene, ok := d.Scene.(*PlayScene); ok {
playScene.noclip = !playScene.noclip
playScene.Player.SetNoclip(playScene.noclip)
playScene.antigravity = playScene.noclip
playScene.Player.SetGravity(!playScene.antigravity)
if playScene.noclip {
d.Flash("Clipping disabled for player character.")
} else {
d.Flash("Clipping and gravity restored for player character.")
}
} else {
d.Flash("Use this cheat in Play Mode to disable clipping for the player character.")
}
return nil
}
switch c.Command {

View File

@ -51,6 +51,7 @@ type PlayScene struct {
// Player character
Player *uix.Actor
antigravity bool // Cheat: disable player gravity
noclip bool // Cheat: disable player clipping
playerJumpCounter int // limit jump length
}
@ -438,7 +439,7 @@ func (s *PlayScene) movePlayer(ev *event.State) {
velocity.Y = -playerSpeed
if s.Player.Grounded() {
s.playerJumpCounter = 20
s.playerJumpCounter = 12
}
}
if ev.Down && s.antigravity {

View File

@ -31,6 +31,7 @@ type Actor struct {
// Actor runtime variables.
hasGravity bool
isMobile bool // Mobile character, such as the player or an enemy
noclip bool // Disable collision detection
hitbox render.Rect
data map[string]string
@ -89,6 +90,12 @@ func (a *Actor) IsMobile() bool {
return a.isMobile
}
// SetNoclip sets the noclip setting for an actor. If true, the actor can
// clip through level geometry.
func (a *Actor) SetNoclip(v bool) {
a.noclip = v
}
// GetBoundingRect gets the bounding box of the actor's doodad.
func (a *Actor) GetBoundingRect() render.Rect {
return doodads.GetBoundingRect(a)

View File

@ -63,7 +63,7 @@ func (w *Canvas) loopActorCollision() error {
// Get the actor's velocity to see if it's moving this tick.
v := a.Velocity()
if a.hasGravity {
if a.hasGravity && v.Y >= 0 {
v.Y += balance.Gravity
}
@ -86,7 +86,11 @@ func (w *Canvas) loopActorCollision() error {
w.OnLevelCollision(a, info)
}
}
delta = info.MoveTo // Move us back where the collision check put us
// Move us back where the collision check put us
if !a.noclip {
delta = info.MoveTo
}
// Move the actor's World Position to the new location.
a.MoveTo(delta)
@ -121,7 +125,6 @@ func (w *Canvas) loopActorCollision() error {
W: boxes[tuple.B].W,
H: boxes[tuple.B].H,
}
// lastGoodBox = originalPositions[b.ID()] // boxes[tuple.B] // worst case scenario we get blocked right away
)
// Firstly we want to make sure B isn't able to clip through A's
@ -206,7 +209,9 @@ func (w *Canvas) loopActorCollision() error {
}
}
b.MoveTo(lastGoodBox.Point())
if !b.noclip {
b.MoveTo(lastGoodBox.Point())
}
} else {
log.Error(
"ERROR: Actors %s and %s overlap and the script returned false,"+

View File

@ -126,14 +126,11 @@ func (w *Canvas) loopFollowActor(ev *event.State) error {
)
// Scroll left
if APosition.X-VP.X <= balance.ScrollboxHoz {
var delta = APosition.X - VP.X
if delta > balance.ScrollMaxVelocity {
delta = balance.ScrollMaxVelocity
}
if APosition.X <= VP.X+balance.ScrollboxHoz {
var delta = VP.X + balance.ScrollboxHoz - APosition.X
// constrain in case they're FAR OFF SCREEN so we don't flip back around
if delta < 0 {
// constrain in case they're FAR OFF SCREEN so we don't flip back around
delta = -delta
}
scrollBy.X = delta
@ -141,22 +138,13 @@ func (w *Canvas) loopFollowActor(ev *event.State) error {
// Scroll right
if APosition.X >= VP.W-ASize.W-balance.ScrollboxHoz {
var delta = VP.W - ASize.W - balance.ScrollboxHoz
if delta > balance.ScrollMaxVelocity {
delta = balance.ScrollMaxVelocity
}
scrollBy.X = -delta
var delta = VP.W - ASize.W - APosition.X - balance.ScrollboxHoz
scrollBy.X = delta
}
// Scroll up
if APosition.Y-VP.Y <= balance.ScrollboxVert {
var delta = APosition.Y - VP.Y
if delta > balance.ScrollMaxVelocity {
delta = balance.ScrollMaxVelocity
}
// TODO: add gravity to counteract jitters on scrolling vertically
scrollBy.Y -= balance.Gravity
if APosition.Y <= VP.Y+balance.ScrollboxVert {
var delta = VP.Y + balance.ScrollboxVert - APosition.Y
if delta < 0 {
delta = -delta
@ -166,14 +154,13 @@ func (w *Canvas) loopFollowActor(ev *event.State) error {
// Scroll down
if APosition.Y >= VP.H-ASize.H-balance.ScrollboxVert {
var delta = VP.H - ASize.H - balance.ScrollboxVert
if delta > balance.ScrollMaxVelocity {
delta = balance.ScrollMaxVelocity
var delta = VP.H - ASize.H - APosition.Y - balance.ScrollboxVert
if delta > 300 {
delta = 300
} else if delta < -300 {
delta = -300
}
scrollBy.Y = -delta
// TODO: add gravity to counteract jitters on scrolling vertically
scrollBy.Y += balance.Gravity * 3
scrollBy.Y = delta
}
if scrollBy != render.Origin {

View File

@ -57,6 +57,9 @@ func (w *Canvas) loopContainActorsInsideLevel(a *Actor) {
if int64(orig.Y+size.H) > w.wallpaper.maxHeight {
var delta = w.wallpaper.maxHeight - int64(orig.Y+size.H)
moveBy.Y = int(delta)
// Allow them to jump from the bottom by marking them as grounded.
a.SetGrounded(true)
}
}
}