From a43e45fad046d8930854cd356ef817f953f43231 Mon Sep 17 00:00:00 2001 From: Noah Petherbridge Date: Thu, 2 Jan 2020 20:23:27 -0800 Subject: [PATCH] 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. --- docs/Shell.md | 1 + pkg/balance/numbers.go | 9 ++++---- pkg/collision/collide_level.go | 25 ++++++++++++++++++---- pkg/commands.go | 17 +++++++++++++++ pkg/play_scene.go | 3 ++- pkg/uix/actor.go | 7 ++++++ pkg/uix/actor_collision.go | 13 ++++++++---- pkg/uix/canvas_scrolling.go | 39 ++++++++++++---------------------- pkg/uix/canvas_wallpaper.go | 3 +++ 9 files changed, 77 insertions(+), 40 deletions(-) diff --git a/docs/Shell.md b/docs/Shell.md index 334eab4..936c5c3 100644 --- a/docs/Shell.md +++ b/docs/Shell.md @@ -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 diff --git a/pkg/balance/numbers.go b/pkg/balance/numbers.go index 3013d59..d686043 100644 --- a/pkg/balance/numbers.go +++ b/pkg/balance/numbers.go @@ -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 diff --git a/pkg/collision/collide_level.go b/pkg/collision/collide_level.go index cf818df..b8376d2 100644 --- a/pkg/collision/collide_level.go +++ b/pkg/collision/collide_level.go @@ -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 diff --git a/pkg/commands.go b/pkg/commands.go index 57242a2..7c61058 100644 --- a/pkg/commands.go +++ b/pkg/commands.go @@ -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 { diff --git a/pkg/play_scene.go b/pkg/play_scene.go index bd29807..7326b90 100644 --- a/pkg/play_scene.go +++ b/pkg/play_scene.go @@ -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 { diff --git a/pkg/uix/actor.go b/pkg/uix/actor.go index 28ddc2a..1a6ffb1 100644 --- a/pkg/uix/actor.go +++ b/pkg/uix/actor.go @@ -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) diff --git a/pkg/uix/actor_collision.go b/pkg/uix/actor_collision.go index 9a5f867..78555c5 100644 --- a/pkg/uix/actor_collision.go +++ b/pkg/uix/actor_collision.go @@ -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,"+ diff --git a/pkg/uix/canvas_scrolling.go b/pkg/uix/canvas_scrolling.go index 1e354c2..3c9e376 100644 --- a/pkg/uix/canvas_scrolling.go +++ b/pkg/uix/canvas_scrolling.go @@ -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 { diff --git a/pkg/uix/canvas_wallpaper.go b/pkg/uix/canvas_wallpaper.go index dc59bb5..29c84d8 100644 --- a/pkg/uix/canvas_wallpaper.go +++ b/pkg/uix/canvas_wallpaper.go @@ -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) } } }