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.
This commit is contained in:
parent
7b3aec0fef
commit
a43e45fad0
|
@ -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
|
||||
|
||||
|
|
|
@ -12,12 +12,11 @@ var (
|
|||
// Window scrolling behavior in Play Mode.
|
||||
ScrollboxHoz = 256 // horizontal px from window border to start scrol
|
||||
ScrollboxVert = 128
|
||||
ScrollMaxVelocity = 8 // 24
|
||||
|
||||
// Player speeds
|
||||
PlayerMaxVelocity = 8
|
||||
PlayerMaxVelocity = 10
|
||||
PlayerAcceleration = 2
|
||||
Gravity = 2
|
||||
Gravity = 6
|
||||
|
||||
// Default chunk size for canvases.
|
||||
ChunkSize = 128
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 {
|
|||
}
|
||||
}
|
||||
|
||||
if !b.noclip {
|
||||
b.MoveTo(lastGoodBox.Point())
|
||||
}
|
||||
} else {
|
||||
log.Error(
|
||||
"ERROR: Actors %s and %s overlap and the script returned false,"+
|
||||
|
|
|
@ -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
|
||||
|
||||
if delta < 0 {
|
||||
// constrain in case they're FAR OFF SCREEN so we don't flip back around
|
||||
if delta < 0 {
|
||||
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 {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user