Make Collision Detection Flawless!
* Pixel perfect collision detection with level geometry. * New shell commands (echo, clear) and help commands
This commit is contained in:
parent
d560670b7b
commit
0efb2ab24f
47
commands.go
47
commands.go
|
@ -20,6 +20,9 @@ func (c Command) Run(d *Doodle) error {
|
|||
}
|
||||
|
||||
switch c.Command {
|
||||
case "echo":
|
||||
d.Flash(c.ArgsLiteral)
|
||||
return nil
|
||||
case "new":
|
||||
return c.New(d)
|
||||
case "save":
|
||||
|
@ -31,6 +34,8 @@ func (c Command) Run(d *Doodle) error {
|
|||
case "exit":
|
||||
case "quit":
|
||||
return c.Quit()
|
||||
case "help":
|
||||
return c.Help(d)
|
||||
default:
|
||||
return c.Default()
|
||||
}
|
||||
|
@ -39,11 +44,51 @@ func (c Command) Run(d *Doodle) error {
|
|||
|
||||
// New opens a new map in the editor mode.
|
||||
func (c Command) New(d *Doodle) error {
|
||||
d.shell.Write("Starting a new map")
|
||||
d.Flash("Starting a new map")
|
||||
d.NewMap()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Help prints the help info.
|
||||
func (c Command) Help(d *Doodle) error {
|
||||
if len(c.Args) == 0 {
|
||||
d.Flash("Available commands: new save edit play quit echo clear help")
|
||||
d.Flash("Type `help` and then the command, like: `help edit`")
|
||||
return nil
|
||||
}
|
||||
|
||||
switch c.Args[0] {
|
||||
case "new":
|
||||
d.Flash("Usage: new")
|
||||
d.Flash("Create a new drawing in Edit Mode")
|
||||
case "save":
|
||||
d.Flash("Usage: save [filename.json]")
|
||||
d.Flash("Save the map to disk (in Edit Mode only)")
|
||||
case "edit":
|
||||
d.Flash("Usage: edit <filename.json>")
|
||||
d.Flash("Open a file on disk in Edit Mode")
|
||||
case "play":
|
||||
d.Flash("Usage: play <filename.json>")
|
||||
d.Flash("Open a map from disk in Play Mode")
|
||||
case "echo":
|
||||
d.Flash("Usage: echo <message>")
|
||||
d.Flash("Flash a message back to the console")
|
||||
case "quit":
|
||||
case "exit":
|
||||
d.Flash("Usage: quit")
|
||||
d.Flash("Closes the dev console")
|
||||
case "clear":
|
||||
d.Flash("Usage: clear")
|
||||
d.Flash("Clears the terminal output history")
|
||||
case "help":
|
||||
d.Flash("Usage: help <command>")
|
||||
default:
|
||||
d.Flash("Unknown help topic.")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Save the current map to disk.
|
||||
func (c Command) Save(d *Doodle) error {
|
||||
if scene, ok := d.scene.(*EditorScene); ok {
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
package doodads
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"git.kirsle.net/apps/doodle/level"
|
||||
"git.kirsle.net/apps/doodle/render"
|
||||
)
|
||||
|
@ -41,6 +39,14 @@ type Collide struct {
|
|||
MoveTo render.Point
|
||||
}
|
||||
|
||||
// Reset a Collide struct flipping all the bools off, but keeping MoveTo.
|
||||
func (c *Collide) Reset() {
|
||||
c.Top = false
|
||||
c.Left = false
|
||||
c.Right = false
|
||||
c.Bottom = false
|
||||
}
|
||||
|
||||
// CollisionBox holds all of the coordinate pairs to draw the collision box
|
||||
// around a doodad.
|
||||
type CollisionBox struct {
|
||||
|
@ -70,6 +76,14 @@ func CollidesWithGrid(d Doodad, grid *render.Grid, target render.Point) (*Collid
|
|||
result = &Collide{
|
||||
MoveTo: P,
|
||||
}
|
||||
ceiling bool // Has hit a ceiling?
|
||||
capHeight int32 // Stop vertical movement thru a ceiling
|
||||
capLeft int32 // Stop movement thru a wall
|
||||
capRight int32
|
||||
hitLeft bool // Has hit an obstacle on the left
|
||||
hitRight bool // or right
|
||||
hitFloor bool
|
||||
capFloor int32
|
||||
)
|
||||
|
||||
// Test all of the bounding boxes for a collision with level geometry.
|
||||
|
@ -85,7 +99,7 @@ func CollidesWithGrid(d Doodad, grid *render.Grid, target render.Point) (*Collid
|
|||
d.SetGrounded(false)
|
||||
}
|
||||
if result.Top {
|
||||
P.Y++
|
||||
// Never seen it touch the top.
|
||||
}
|
||||
if result.Left {
|
||||
P.X++
|
||||
|
@ -112,15 +126,14 @@ func CollidesWithGrid(d Doodad, grid *render.Grid, target render.Point) (*Collid
|
|||
// Cap our horizontal movement if we're touching walls.
|
||||
if (result.Left && target.X < P.X) || (result.Right && target.X > P.X) {
|
||||
// If the step is short enough, try and jump up.
|
||||
relPoint := P.Y + S.H
|
||||
height := P.Y + S.H
|
||||
if result.Left && target.X < P.X {
|
||||
relPoint -= result.LeftPoint.Y
|
||||
height -= result.LeftPoint.Y
|
||||
} else {
|
||||
relPoint -= result.RightPoint.Y
|
||||
height -= result.RightPoint.Y
|
||||
}
|
||||
fmt.Printf("Touched a wall at %d pixels height (P=%s)\n", relPoint, P)
|
||||
if S.H-relPoint > S.H-8 {
|
||||
target.Y -= 12
|
||||
if height <= 8 {
|
||||
target.Y -= height
|
||||
if target.X < P.X {
|
||||
target.X-- // push along to the left
|
||||
} else if target.X > P.X {
|
||||
|
@ -131,24 +144,70 @@ func CollidesWithGrid(d Doodad, grid *render.Grid, target render.Point) (*Collid
|
|||
}
|
||||
}
|
||||
|
||||
// Cap our vertical movement if we're touching ceilings.
|
||||
if ceiling {
|
||||
// The existing box intersects a ceiling, this will almost never
|
||||
// happen because gravity will always pull you away at the last frame.
|
||||
// But if we do somehow get here, may as well cap it where it's at.
|
||||
capHeight = P.Y
|
||||
}
|
||||
|
||||
// Trace a line from where we are to where we wanna go.
|
||||
result.Reset()
|
||||
result.MoveTo = P
|
||||
for point := range render.IterLine2(P, target) {
|
||||
if ok := result.ScanBoundingBox(render.Rect{
|
||||
if has := result.ScanBoundingBox(render.Rect{
|
||||
X: point.X,
|
||||
Y: point.Y,
|
||||
W: S.W,
|
||||
H: S.H,
|
||||
}, grid); ok {
|
||||
if d.Grounded() {
|
||||
if !result.Bottom {
|
||||
d.SetGrounded(false)
|
||||
}, grid); has {
|
||||
if result.Bottom {
|
||||
if !hitFloor {
|
||||
hitFloor = true
|
||||
capFloor = result.BottomPoint.Y - S.H
|
||||
}
|
||||
} else if result.Bottom {
|
||||
d.SetGrounded(true)
|
||||
}
|
||||
|
||||
if result.Top && !ceiling {
|
||||
// This is a newly discovered ceiling.
|
||||
ceiling = true
|
||||
capHeight = result.TopPoint.Y
|
||||
}
|
||||
|
||||
if result.Left && !hitLeft {
|
||||
hitLeft = true
|
||||
capLeft = result.LeftPoint.X
|
||||
}
|
||||
if result.Right && !hitRight {
|
||||
hitRight = true
|
||||
capRight = result.RightPoint.X - S.W
|
||||
}
|
||||
}
|
||||
|
||||
// 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.
|
||||
if ceiling && result.MoveTo.Y < capHeight {
|
||||
result.Top = true
|
||||
result.MoveTo.Y = capHeight
|
||||
}
|
||||
if hitFloor && result.MoveTo.Y > capFloor {
|
||||
result.Bottom = true
|
||||
result.MoveTo.Y = capFloor
|
||||
}
|
||||
if hitLeft {
|
||||
result.Left = true
|
||||
result.MoveTo.X = capLeft
|
||||
}
|
||||
if hitRight {
|
||||
result.Right = true
|
||||
result.MoveTo.X = capRight
|
||||
}
|
||||
|
||||
return result, result.IsColliding()
|
||||
|
@ -159,7 +218,7 @@ func (c *Collide) IsColliding() bool {
|
|||
return c.Top || c.Bottom || c.Left || c.Right
|
||||
}
|
||||
|
||||
// GetCollisionBox computes the full pairs of points for the collision box
|
||||
// GetBoundingRect computes the full pairs of points for the collision box
|
||||
// around a doodad.
|
||||
func GetBoundingRect(d Doodad) render.Rect {
|
||||
var (
|
||||
|
@ -199,21 +258,21 @@ func GetCollisionBox(box render.Rect) CollisionBox {
|
|||
Left: []render.Point{
|
||||
{
|
||||
X: box.X,
|
||||
Y: box.Y + 1,
|
||||
Y: box.Y + box.H - 1,
|
||||
},
|
||||
{
|
||||
X: box.X,
|
||||
Y: box.Y + box.H - 1,
|
||||
Y: box.Y + 1,
|
||||
},
|
||||
},
|
||||
Right: []render.Point{
|
||||
{
|
||||
X: box.X + box.W,
|
||||
Y: box.Y + 1,
|
||||
Y: box.Y + box.H - 1,
|
||||
},
|
||||
{
|
||||
X: box.X + box.W,
|
||||
Y: box.Y + box.H - 1,
|
||||
Y: box.Y + 1,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -118,6 +118,8 @@ func (s *PlayScene) movePlayer(ev *events.State) {
|
|||
|
||||
// Apply gravity if not grounded.
|
||||
if !s.player.Grounded() {
|
||||
// Gravity has to pipe through the collision checker, too, so it
|
||||
// can't give us a cheated downward boost.
|
||||
delta.Y += gravity
|
||||
}
|
||||
|
||||
|
|
10
shell.go
10
shell.go
|
@ -59,9 +59,13 @@ func (s *Shell) Close() {
|
|||
// Execute a command in the shell.
|
||||
func (s *Shell) Execute(input string) {
|
||||
command := s.Parse(input)
|
||||
err := command.Run(s.parent)
|
||||
if err != nil {
|
||||
s.Write(err.Error())
|
||||
if command.Command == "clear" {
|
||||
s.Output = []string{}
|
||||
} else {
|
||||
err := command.Run(s.parent)
|
||||
if err != nil {
|
||||
s.Write(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
if command.Raw != "" {
|
||||
|
|
Loading…
Reference in New Issue
Block a user