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
|
* `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.
|
||||||
|
* `ghost mode` - during Play Mode, toggles noclip for the player character.
|
||||||
|
|
||||||
## Bool Props
|
## Bool Props
|
||||||
|
|
||||||
|
|
|
@ -12,12 +12,11 @@ var (
|
||||||
// Window scrolling behavior in Play Mode.
|
// Window scrolling behavior in Play Mode.
|
||||||
ScrollboxHoz = 256 // horizontal px from window border to start scrol
|
ScrollboxHoz = 256 // horizontal px from window border to start scrol
|
||||||
ScrollboxVert = 128
|
ScrollboxVert = 128
|
||||||
ScrollMaxVelocity = 8 // 24
|
|
||||||
|
|
||||||
// Player speeds
|
// Player speeds
|
||||||
PlayerMaxVelocity = 8
|
PlayerMaxVelocity = 10
|
||||||
PlayerAcceleration = 2
|
PlayerAcceleration = 2
|
||||||
Gravity = 2
|
Gravity = 6
|
||||||
|
|
||||||
// Default chunk size for canvases.
|
// Default chunk size for canvases.
|
||||||
ChunkSize = 128
|
ChunkSize = 128
|
||||||
|
|
|
@ -84,7 +84,8 @@ func CollidesWithGrid(d doodads.Actor, grid *level.Chunker, target render.Point)
|
||||||
d.SetGrounded(false)
|
d.SetGrounded(false)
|
||||||
}
|
}
|
||||||
if result.Top {
|
if result.Top {
|
||||||
// Never seen it touch the top.
|
ceiling = true
|
||||||
|
P.Y++
|
||||||
}
|
}
|
||||||
if result.Left {
|
if result.Left {
|
||||||
P.X++
|
P.X++
|
||||||
|
@ -141,6 +142,13 @@ func CollidesWithGrid(d doodads.Actor, grid *level.Chunker, target render.Point)
|
||||||
result.Reset()
|
result.Reset()
|
||||||
result.MoveTo = P
|
result.MoveTo = P
|
||||||
for point := range render.IterLine(P, target) {
|
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{
|
if has := result.ScanBoundingBox(render.Rect{
|
||||||
X: point.X,
|
X: point.X,
|
||||||
Y: point.Y,
|
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
|
// So far so good, keep following the MoveTo to
|
||||||
// the last good point before a collision.
|
// the last good point before a collision.
|
||||||
result.MoveTo = point
|
result.MoveTo = point
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If they hit the roof, cap them to the roof.
|
// 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
|
var wg sync.WaitGroup
|
||||||
for _, job := range jobs {
|
for _, job := range jobs {
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go func(job jobSide) {
|
job := job
|
||||||
|
go func() {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
c.ScanGridLine(job.p1, job.p2, grid, job.side)
|
c.ScanGridLine(job.p1, job.p2, grid, job.side)
|
||||||
}(job)
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
wg.Wait()
|
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
|
// for any pixels to be set, implying a collision between level geometry and the
|
||||||
// bounding boxes of the doodad.
|
// bounding boxes of the doodad.
|
||||||
func (c *Collide) ScanGridLine(p1, p2 render.Point, grid *level.Chunker, side Side) {
|
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) {
|
for point := range render.IterLine(p1, p2) {
|
||||||
if swatch, err := grid.Get(point); err == nil {
|
if swatch, err := grid.Get(point); err == nil {
|
||||||
// We're intersecting a pixel! If it's a solid one we'll return it
|
// 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.")
|
d.Flash("Use this cheat in Play Mode to disable gravity for the player character.")
|
||||||
}
|
}
|
||||||
return nil
|
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 {
|
switch c.Command {
|
||||||
|
|
|
@ -51,6 +51,7 @@ type PlayScene struct {
|
||||||
// Player character
|
// Player character
|
||||||
Player *uix.Actor
|
Player *uix.Actor
|
||||||
antigravity bool // Cheat: disable player gravity
|
antigravity bool // Cheat: disable player gravity
|
||||||
|
noclip bool // Cheat: disable player clipping
|
||||||
playerJumpCounter int // limit jump length
|
playerJumpCounter int // limit jump length
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -438,7 +439,7 @@ func (s *PlayScene) movePlayer(ev *event.State) {
|
||||||
velocity.Y = -playerSpeed
|
velocity.Y = -playerSpeed
|
||||||
|
|
||||||
if s.Player.Grounded() {
|
if s.Player.Grounded() {
|
||||||
s.playerJumpCounter = 20
|
s.playerJumpCounter = 12
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ev.Down && s.antigravity {
|
if ev.Down && s.antigravity {
|
||||||
|
|
|
@ -31,6 +31,7 @@ type Actor struct {
|
||||||
// Actor runtime variables.
|
// Actor runtime variables.
|
||||||
hasGravity bool
|
hasGravity bool
|
||||||
isMobile bool // Mobile character, such as the player or an enemy
|
isMobile bool // Mobile character, such as the player or an enemy
|
||||||
|
noclip bool // Disable collision detection
|
||||||
hitbox render.Rect
|
hitbox render.Rect
|
||||||
data map[string]string
|
data map[string]string
|
||||||
|
|
||||||
|
@ -89,6 +90,12 @@ func (a *Actor) IsMobile() bool {
|
||||||
return a.isMobile
|
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.
|
// GetBoundingRect gets the bounding box of the actor's doodad.
|
||||||
func (a *Actor) GetBoundingRect() render.Rect {
|
func (a *Actor) GetBoundingRect() render.Rect {
|
||||||
return doodads.GetBoundingRect(a)
|
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.
|
// Get the actor's velocity to see if it's moving this tick.
|
||||||
v := a.Velocity()
|
v := a.Velocity()
|
||||||
if a.hasGravity {
|
if a.hasGravity && v.Y >= 0 {
|
||||||
v.Y += balance.Gravity
|
v.Y += balance.Gravity
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -86,7 +86,11 @@ func (w *Canvas) loopActorCollision() error {
|
||||||
w.OnLevelCollision(a, info)
|
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.
|
// Move the actor's World Position to the new location.
|
||||||
a.MoveTo(delta)
|
a.MoveTo(delta)
|
||||||
|
@ -121,7 +125,6 @@ func (w *Canvas) loopActorCollision() error {
|
||||||
W: boxes[tuple.B].W,
|
W: boxes[tuple.B].W,
|
||||||
H: boxes[tuple.B].H,
|
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
|
// 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())
|
b.MoveTo(lastGoodBox.Point())
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
log.Error(
|
log.Error(
|
||||||
"ERROR: Actors %s and %s overlap and the script returned false,"+
|
"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
|
// Scroll left
|
||||||
if APosition.X-VP.X <= balance.ScrollboxHoz {
|
if APosition.X <= VP.X+balance.ScrollboxHoz {
|
||||||
var delta = APosition.X - VP.X
|
var delta = VP.X + balance.ScrollboxHoz - APosition.X
|
||||||
if delta > balance.ScrollMaxVelocity {
|
|
||||||
delta = balance.ScrollMaxVelocity
|
|
||||||
}
|
|
||||||
|
|
||||||
if delta < 0 {
|
|
||||||
// constrain in case they're FAR OFF SCREEN so we don't flip back around
|
// constrain in case they're FAR OFF SCREEN so we don't flip back around
|
||||||
|
if delta < 0 {
|
||||||
delta = -delta
|
delta = -delta
|
||||||
}
|
}
|
||||||
scrollBy.X = delta
|
scrollBy.X = delta
|
||||||
|
@ -141,22 +138,13 @@ func (w *Canvas) loopFollowActor(ev *event.State) error {
|
||||||
|
|
||||||
// Scroll right
|
// Scroll right
|
||||||
if APosition.X >= VP.W-ASize.W-balance.ScrollboxHoz {
|
if APosition.X >= VP.W-ASize.W-balance.ScrollboxHoz {
|
||||||
var delta = VP.W - ASize.W - balance.ScrollboxHoz
|
var delta = VP.W - ASize.W - APosition.X - balance.ScrollboxHoz
|
||||||
if delta > balance.ScrollMaxVelocity {
|
scrollBy.X = delta
|
||||||
delta = balance.ScrollMaxVelocity
|
|
||||||
}
|
|
||||||
scrollBy.X = -delta
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Scroll up
|
// Scroll up
|
||||||
if APosition.Y-VP.Y <= balance.ScrollboxVert {
|
if APosition.Y <= VP.Y+balance.ScrollboxVert {
|
||||||
var delta = APosition.Y - VP.Y
|
var delta = VP.Y + balance.ScrollboxVert - APosition.Y
|
||||||
if delta > balance.ScrollMaxVelocity {
|
|
||||||
delta = balance.ScrollMaxVelocity
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: add gravity to counteract jitters on scrolling vertically
|
|
||||||
scrollBy.Y -= balance.Gravity
|
|
||||||
|
|
||||||
if delta < 0 {
|
if delta < 0 {
|
||||||
delta = -delta
|
delta = -delta
|
||||||
|
@ -166,14 +154,13 @@ func (w *Canvas) loopFollowActor(ev *event.State) error {
|
||||||
|
|
||||||
// Scroll down
|
// Scroll down
|
||||||
if APosition.Y >= VP.H-ASize.H-balance.ScrollboxVert {
|
if APosition.Y >= VP.H-ASize.H-balance.ScrollboxVert {
|
||||||
var delta = VP.H - ASize.H - balance.ScrollboxVert
|
var delta = VP.H - ASize.H - APosition.Y - balance.ScrollboxVert
|
||||||
if delta > balance.ScrollMaxVelocity {
|
if delta > 300 {
|
||||||
delta = balance.ScrollMaxVelocity
|
delta = 300
|
||||||
|
} else if delta < -300 {
|
||||||
|
delta = -300
|
||||||
}
|
}
|
||||||
scrollBy.Y = -delta
|
scrollBy.Y = delta
|
||||||
|
|
||||||
// TODO: add gravity to counteract jitters on scrolling vertically
|
|
||||||
scrollBy.Y += balance.Gravity * 3
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if scrollBy != render.Origin {
|
if scrollBy != render.Origin {
|
||||||
|
|
|
@ -57,6 +57,9 @@ func (w *Canvas) loopContainActorsInsideLevel(a *Actor) {
|
||||||
if int64(orig.Y+size.H) > w.wallpaper.maxHeight {
|
if int64(orig.Y+size.H) > w.wallpaper.maxHeight {
|
||||||
var delta = w.wallpaper.maxHeight - int64(orig.Y+size.H)
|
var delta = w.wallpaper.maxHeight - int64(orig.Y+size.H)
|
||||||
moveBy.Y = int(delta)
|
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