Play Mode: Fix Level Collision w/ Scrolling
Fixes: * Move the call to CollidesWithGrid() inside the Canvas instead of outside in the PlayScene.movePlayer() so it can apply to all Actors in motion. * PlayScene.movePlayer() in turn just sets the player's Velocity so the Canvas.Loop() can move the actor itself. * When keeping the player inside the level boundaries: previously it was assuming the player Position was relative to the window, and was checking the WorldIndexAt and getting wrong results. * Canvas scrolling (loopFollowActor): check that the actor is getting close to the screen edge using the Viewport into the world, NOT the screen-relative coordinates of the Canvas bounding boxes.
This commit is contained in:
parent
5c08577214
commit
241186209c
|
@ -135,6 +135,19 @@ func (r Rect) AddPoint(other Point) Rect {
|
|||
}
|
||||
}
|
||||
|
||||
// SubtractPoint is the inverse of AddPoint. Use this only if you need to invert
|
||||
// the Point being added.
|
||||
//
|
||||
// This does r.X - other.X, r.Y - other.Y and keeps the width/height the same.
|
||||
func (r Rect) SubtractPoint(other Point) Rect {
|
||||
return Rect{
|
||||
X: r.X - other.X,
|
||||
Y: r.Y - other.Y,
|
||||
W: r.W,
|
||||
H: r.H,
|
||||
}
|
||||
}
|
||||
|
||||
// Text holds information for drawing text.
|
||||
type Text struct {
|
||||
Text string
|
||||
|
|
|
@ -75,6 +75,12 @@ func (p *Point) Add(other Point) {
|
|||
p.Y += other.Y
|
||||
}
|
||||
|
||||
// Subtract the other point from your current point.
|
||||
func (p *Point) Subtract(other Point) {
|
||||
p.X -= other.X
|
||||
p.Y -= other.Y
|
||||
}
|
||||
|
||||
// MarshalText to convert the point into text so that a render.Point may be used
|
||||
// as a map key and serialized to JSON.
|
||||
func (p *Point) MarshalText() ([]byte, error) {
|
||||
|
|
|
@ -68,11 +68,14 @@ func (w *Label) Compute(e render.Engine) {
|
|||
// Max rect to encompass all lines of text.
|
||||
var maxRect = render.Rect{}
|
||||
for _, line := range lines {
|
||||
if line == "" {
|
||||
line = "<empty>"
|
||||
}
|
||||
|
||||
text.Text = line // only this line at this time.
|
||||
rect, err := e.ComputeTextRect(text)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("%s: failed to compute text rect: %s", w, err)) // TODO return an error
|
||||
return
|
||||
}
|
||||
|
||||
if rect.W > maxRect.W {
|
||||
|
|
|
@ -29,7 +29,7 @@ var (
|
|||
|
||||
// Put a border around all Canvas widgets.
|
||||
DebugCanvasBorder = render.Invisible
|
||||
DebugCanvasLabel = false // Tag the canvas with a label.
|
||||
DebugCanvasLabel = true // Tag the canvas with a label.
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
|
|
@ -12,7 +12,7 @@ var (
|
|||
// Window scrolling behavior in Play Mode.
|
||||
ScrollboxHoz = 64 // horizontal px from window border to start scrol
|
||||
ScrollboxVert = 128
|
||||
ScrollMaxVelocity = 24
|
||||
ScrollMaxVelocity = 8 // 24
|
||||
|
||||
// Player speeds
|
||||
PlayerMaxVelocity = 8
|
||||
|
|
|
@ -96,7 +96,11 @@ const (
|
|||
Right
|
||||
)
|
||||
|
||||
// CollidesWithGrid checks if a Doodad collides with level geometry.
|
||||
/*
|
||||
CollidesWithGrid checks if a Doodad collides with level geometry.
|
||||
|
||||
The `target` is the point the actor wants to move to on this tick.
|
||||
*/
|
||||
func CollidesWithGrid(d Actor, grid *level.Chunker, target render.Point) (*Collide, bool) {
|
||||
var (
|
||||
P = d.Position()
|
||||
|
@ -109,10 +113,10 @@ func CollidesWithGrid(d Actor, grid *level.Chunker, target render.Point) (*Colli
|
|||
capHeight int32 // Stop vertical movement thru a ceiling
|
||||
capLeft int32 // Stop movement thru a wall
|
||||
capRight int32
|
||||
capFloor int32 // Stop movement thru the floor
|
||||
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.
|
||||
|
|
|
@ -1,79 +0,0 @@
|
|||
package doodads
|
||||
|
||||
import "git.kirsle.net/apps/doodle/lib/render"
|
||||
|
||||
// PlayerID is the Doodad ID for the player character.
|
||||
const PlayerID = "PLAYER"
|
||||
|
||||
// Player is a special doodad for the player character.
|
||||
type Player struct {
|
||||
point render.Point
|
||||
velocity render.Point
|
||||
size render.Rect
|
||||
grounded bool
|
||||
}
|
||||
|
||||
// NewPlayer creates the special Player Character doodad.
|
||||
func NewPlayer() *Player {
|
||||
return &Player{
|
||||
point: render.Point{
|
||||
X: 100,
|
||||
Y: 100,
|
||||
},
|
||||
size: render.Rect{
|
||||
W: 32,
|
||||
H: 32,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// ID of the Player singleton.
|
||||
func (p *Player) ID() string {
|
||||
return PlayerID
|
||||
}
|
||||
|
||||
// Position of the player.
|
||||
func (p *Player) Position() render.Point {
|
||||
return p.point
|
||||
}
|
||||
|
||||
// MoveBy a relative delta position.
|
||||
func (p *Player) MoveBy(by render.Point) {
|
||||
p.point.X += by.X
|
||||
p.point.Y += by.Y
|
||||
}
|
||||
|
||||
// MoveTo an absolute position.
|
||||
func (p *Player) MoveTo(to render.Point) {
|
||||
p.point = to
|
||||
}
|
||||
|
||||
// Velocity returns the player's current velocity.
|
||||
func (p *Player) Velocity() render.Point {
|
||||
return p.velocity
|
||||
}
|
||||
|
||||
// Size returns the player's size.
|
||||
func (p *Player) Size() render.Rect {
|
||||
return p.size
|
||||
}
|
||||
|
||||
// Grounded returns if the player is grounded.
|
||||
func (p *Player) Grounded() bool {
|
||||
return p.grounded
|
||||
}
|
||||
|
||||
// SetGrounded sets if the player is grounded.
|
||||
func (p *Player) SetGrounded(v bool) {
|
||||
p.grounded = v
|
||||
}
|
||||
|
||||
// Draw the player sprite.
|
||||
func (p *Player) Draw(e render.Engine) {
|
||||
e.DrawBox(render.RGBA(255, 255, 153, 255), render.Rect{
|
||||
X: p.point.X,
|
||||
Y: p.point.Y,
|
||||
W: p.size.W,
|
||||
H: p.size.H,
|
||||
})
|
||||
}
|
|
@ -76,10 +76,13 @@ func (s *EditorScene) Setup(d *Doodle) error {
|
|||
if s.Level != nil {
|
||||
log.Debug("EditorScene.Setup: received level from scene caller")
|
||||
s.UI.Canvas.LoadLevel(d.Engine, s.Level)
|
||||
s.UI.Canvas.InstallActors(s.Level.Actors)
|
||||
} else if s.filename != "" && s.OpenFile {
|
||||
log.Debug("EditorScene.Setup: Loading map from filename at %s", s.filename)
|
||||
if err := s.LoadLevel(s.filename); err != nil {
|
||||
d.Flash("LoadLevel error: %s", err)
|
||||
} else {
|
||||
s.UI.Canvas.InstallActors(s.Level.Actors)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -131,6 +131,10 @@ func (d *Doodle) DrawDebugOverlay() {
|
|||
}
|
||||
|
||||
// DrawCollisionBox draws the collision box around a Doodad.
|
||||
//
|
||||
// TODO: move inside the Canvas. Currently it takes an actor's World Position
|
||||
// and draws the box as if it were a relative (to the window) position, so the
|
||||
// hitbox drifts off when the level scrolls away from 0,0
|
||||
func (d *Doodle) DrawCollisionBox(actor doodads.Actor) {
|
||||
if !DebugCollision {
|
||||
return
|
||||
|
|
|
@ -6,7 +6,6 @@ import (
|
|||
"git.kirsle.net/apps/doodle/lib/events"
|
||||
"git.kirsle.net/apps/doodle/lib/render"
|
||||
"git.kirsle.net/apps/doodle/pkg/balance"
|
||||
"git.kirsle.net/apps/doodle/pkg/doodads"
|
||||
"git.kirsle.net/apps/doodle/pkg/doodads/dummy"
|
||||
"git.kirsle.net/apps/doodle/pkg/level"
|
||||
"git.kirsle.net/apps/doodle/pkg/log"
|
||||
|
@ -56,6 +55,7 @@ func (s *PlayScene) Setup(d *Doodle) error {
|
|||
|
||||
// Initialize the drawing canvas.
|
||||
s.drawing = uix.NewCanvas(balance.ChunkSize, false)
|
||||
s.drawing.Name = "play-canvas"
|
||||
s.drawing.MoveTo(render.Origin)
|
||||
s.drawing.Resize(render.NewRect(int32(d.width), int32(d.height)))
|
||||
s.drawing.Compute(d.Engine)
|
||||
|
@ -92,7 +92,7 @@ func (s *PlayScene) Setup(d *Doodle) error {
|
|||
func (s *PlayScene) Loop(d *Doodle, ev *events.State) error {
|
||||
// Update debug overlay values.
|
||||
*s.debWorldIndex = s.drawing.WorldIndexAt(render.NewPoint(ev.CursorX.Now, ev.CursorY.Now)).String()
|
||||
*s.debPosition = s.Player.Position().String()
|
||||
*s.debPosition = s.Player.Position().String() + " vel " + s.Player.Velocity().String()
|
||||
*s.debViewport = s.drawing.Viewport().String()
|
||||
*s.debScroll = s.drawing.Scroll.String()
|
||||
|
||||
|
@ -134,8 +134,10 @@ func (s *PlayScene) Draw(d *Doodle) error {
|
|||
// Draw the level.
|
||||
s.drawing.Present(d.Engine, s.drawing.Point())
|
||||
|
||||
// Draw our hero.
|
||||
d.Engine.DrawBox(render.RGBA(255, 255, 153, 255), render.Rect{
|
||||
// Draw our hero. TODO: this draws a yellow box using the player's World
|
||||
// Position as tho it were Screen Position. The player has its own canvas
|
||||
// currently drawn in red
|
||||
d.Engine.DrawBox(render.RGBA(255, 255, 153, 64), render.Rect{
|
||||
X: s.Player.Position().X,
|
||||
Y: s.Player.Position().Y,
|
||||
W: s.Player.Size().W,
|
||||
|
@ -150,48 +152,37 @@ func (s *PlayScene) Draw(d *Doodle) error {
|
|||
|
||||
// movePlayer updates the player's X,Y coordinate based on key pressed.
|
||||
func (s *PlayScene) movePlayer(ev *events.State) {
|
||||
delta := s.Player.Position()
|
||||
var playerSpeed = int32(balance.PlayerMaxVelocity)
|
||||
var gravity = int32(balance.Gravity)
|
||||
|
||||
var velocity render.Point
|
||||
|
||||
if ev.Down.Now {
|
||||
delta.Y += playerSpeed
|
||||
velocity.Y = playerSpeed
|
||||
}
|
||||
if ev.Left.Now {
|
||||
delta.X -= playerSpeed
|
||||
velocity.X = -playerSpeed
|
||||
}
|
||||
if ev.Right.Now {
|
||||
delta.X += playerSpeed
|
||||
velocity.X = playerSpeed
|
||||
}
|
||||
if ev.Up.Now {
|
||||
delta.Y -= playerSpeed
|
||||
velocity.Y = -playerSpeed
|
||||
}
|
||||
|
||||
// Apply gravity.
|
||||
// var onFloor bool
|
||||
|
||||
info, ok := doodads.CollidesWithGrid(s.Player, s.Level.Chunker, delta)
|
||||
if ok {
|
||||
// Collision happened with world.
|
||||
}
|
||||
delta = info.MoveTo
|
||||
|
||||
// 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
|
||||
velocity.Y += gravity
|
||||
}
|
||||
|
||||
// s.Player.SetVelocity(velocity)
|
||||
s.Player.MoveTo(delta)
|
||||
s.Player.SetVelocity(velocity)
|
||||
}
|
||||
|
||||
// Drawing returns the private world drawing, for debugging with the console.
|
||||
func (s *PlayScene) Drawing() *uix.Canvas {
|
||||
return s.drawing
|
||||
}
|
||||
|
||||
// LoadLevel loads a level from disk.
|
||||
|
|
|
@ -31,7 +31,6 @@ func NewActor(id string, levelActor *level.Actor, doodad *doodads.Doodad) *Actor
|
|||
size := int32(doodad.Layers[0].Chunker.Size)
|
||||
can := NewCanvas(int(size), false)
|
||||
can.Name = id
|
||||
can.actor = levelActor
|
||||
|
||||
// TODO: if the Background is render.Invisible it gets defaulted to
|
||||
// White somewhere and the Doodad masks the level drawing behind it.
|
||||
|
@ -40,9 +39,15 @@ func NewActor(id string, levelActor *level.Actor, doodad *doodads.Doodad) *Actor
|
|||
can.LoadDoodad(doodad)
|
||||
can.Resize(render.NewRect(size, size))
|
||||
|
||||
return &Actor{
|
||||
actor := &Actor{
|
||||
Drawing: doodads.NewDrawing(id, doodad),
|
||||
Actor: levelActor,
|
||||
Canvas: can,
|
||||
}
|
||||
|
||||
// Give the Canvas a pointer to its (parent) Actor so it can draw its debug
|
||||
// label and show the World Position of the actor within the world.
|
||||
can.actor = actor
|
||||
|
||||
return actor
|
||||
}
|
||||
|
|
|
@ -45,8 +45,8 @@ type Canvas struct {
|
|||
chunks *level.Chunker
|
||||
|
||||
// Actors to superimpose on top of the drawing.
|
||||
actor *level.Actor // if this canvas IS an actor
|
||||
actors []*Actor
|
||||
actor *Actor // if this canvas IS an actor
|
||||
actors []*Actor // if this canvas CONTAINS actors (i.e., is a level)
|
||||
|
||||
// Wallpaper settings.
|
||||
wallpaper *Wallpaper
|
||||
|
@ -163,20 +163,35 @@ func (w *Canvas) Loop(ev *events.State) error {
|
|||
if err := w.loopFollowActor(ev); err != nil {
|
||||
log.Error("Follow actor: %s", err) // not fatal but nice to know
|
||||
}
|
||||
w.loopConstrainScroll()
|
||||
if err := w.loopConstrainScroll(); err != nil {
|
||||
log.Debug("loopConstrainScroll: %s", err)
|
||||
}
|
||||
|
||||
// Move any actors.
|
||||
for _, a := range w.actors {
|
||||
if v := a.Velocity(); v != render.Origin {
|
||||
// orig := a.Drawing.Position()
|
||||
a.MoveBy(v)
|
||||
// Create a delta point from their current location to where they
|
||||
// want to move to this tick.
|
||||
delta := a.Position()
|
||||
delta.Add(v)
|
||||
|
||||
// Check collision with level geometry.
|
||||
info, ok := doodads.CollidesWithGrid(a, w.chunks, delta)
|
||||
if ok {
|
||||
// Collision happened with world.
|
||||
log.Error("COLLIDE %+v", info)
|
||||
}
|
||||
delta = info.MoveTo // Move us back where the collision check put us
|
||||
|
||||
// Move the actor's World Position to the new location.
|
||||
a.MoveTo(delta)
|
||||
|
||||
// Keep them contained inside the level.
|
||||
if w.wallpaper.pageType > level.Unbounded {
|
||||
var (
|
||||
orig = w.WorldIndexAt(a.Drawing.Position())
|
||||
orig = a.Position() // Actor's World Position
|
||||
moveBy render.Point
|
||||
size = a.Canvas.Size()
|
||||
size = a.Size()
|
||||
)
|
||||
|
||||
// Bound it on the top left edges.
|
||||
|
|
|
@ -20,7 +20,12 @@ func (w *Canvas) InstallActors(actors level.ActorMap) error {
|
|||
return fmt.Errorf("InstallActors: %s", err)
|
||||
}
|
||||
|
||||
w.actors = append(w.actors, NewActor(id, actor, doodad))
|
||||
// Create the "live" Actor to exist in the world, and set its world
|
||||
// position to the Point defined in the level data.
|
||||
liveActor := NewActor(id, actor, doodad)
|
||||
liveActor.MoveTo(actor.Point)
|
||||
|
||||
w.actors = append(w.actors, liveActor)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -46,10 +51,9 @@ func (w *Canvas) drawActors(e render.Engine, p render.Point) {
|
|||
continue
|
||||
}
|
||||
var (
|
||||
actor = a.Actor // Static Actor instance from Level file, DO NOT CHANGE
|
||||
can = a.Canvas // Canvas widget that draws the actor
|
||||
actorPoint = actor.Point // XXX TODO: DO NOT CHANGE
|
||||
actorSize = can.Size()
|
||||
actorPoint = a.Position()
|
||||
actorSize = a.Size()
|
||||
)
|
||||
|
||||
// Create a box of World Coordinates that this actor occupies. The
|
||||
|
@ -87,7 +91,7 @@ func (w *Canvas) drawActors(e render.Engine, p render.Point) {
|
|||
// Hitting the left edge. Cap the X coord and shrink the width.
|
||||
delta := p.X - drawAt.X // positive number
|
||||
drawAt.X = p.X
|
||||
// scrollTo.X -= delta // TODO
|
||||
// scrollTo.X -= delta / 2 // TODO
|
||||
resizeTo.W -= delta
|
||||
}
|
||||
|
||||
|
|
|
@ -149,11 +149,17 @@ func (w *Canvas) Present(e render.Engine, p render.Point) {
|
|||
// Viewport.W, Viewport.H,
|
||||
// ),
|
||||
}
|
||||
|
||||
// Draw the actor's position details.
|
||||
// LP = Level Position, where the Actor starts at in the level data
|
||||
// WP = World Position, the Actor's current position in the level
|
||||
if w.actor != nil {
|
||||
rows = append(rows,
|
||||
fmt.Sprintf("WP=%s", w.actor.Point),
|
||||
fmt.Sprintf("LP=%s", w.actor.Actor.Point),
|
||||
fmt.Sprintf("WP=%s", w.actor.Position()),
|
||||
)
|
||||
}
|
||||
|
||||
label := ui.NewLabel(ui.Label{
|
||||
Text: strings.Join(rows, "\n"),
|
||||
Font: render.Text{
|
||||
|
|
|
@ -110,8 +110,7 @@ func (w *Canvas) loopFollowActor(ev *events.State) error {
|
|||
}
|
||||
|
||||
var (
|
||||
P = w.Point()
|
||||
S = w.Size()
|
||||
VP = w.Viewport()
|
||||
)
|
||||
|
||||
// Find the actor.
|
||||
|
@ -121,19 +120,18 @@ func (w *Canvas) loopFollowActor(ev *events.State) error {
|
|||
}
|
||||
|
||||
actor.Canvas.SetBorderSize(2)
|
||||
actor.Canvas.SetBorderColor(render.Cyan)
|
||||
actor.Canvas.SetBorderColor(render.Red)
|
||||
actor.Canvas.SetBorderStyle(ui.BorderSolid)
|
||||
|
||||
var (
|
||||
APosition = actor.Position() // relative to screen
|
||||
APoint = actor.Drawing.Position()
|
||||
APosition = actor.Position() // absolute world position
|
||||
ASize = actor.Drawing.Size()
|
||||
scrollBy render.Point
|
||||
)
|
||||
|
||||
// Scroll left
|
||||
if APosition.X-P.X <= int32(balance.ScrollboxHoz) {
|
||||
var delta = APoint.X - P.X
|
||||
if APosition.X-VP.X <= int32(balance.ScrollboxHoz) {
|
||||
var delta = APosition.X - VP.X
|
||||
if delta > int32(balance.ScrollMaxVelocity) {
|
||||
delta = int32(balance.ScrollMaxVelocity)
|
||||
}
|
||||
|
@ -146,8 +144,8 @@ func (w *Canvas) loopFollowActor(ev *events.State) error {
|
|||
}
|
||||
|
||||
// Scroll right
|
||||
if APosition.X >= S.W-ASize.W-int32(balance.ScrollboxHoz) {
|
||||
var delta = S.W - ASize.W - int32(balance.ScrollboxHoz)
|
||||
if APosition.X >= VP.W-ASize.W-int32(balance.ScrollboxHoz) {
|
||||
var delta = VP.W - ASize.W - int32(balance.ScrollboxHoz)
|
||||
if delta > int32(balance.ScrollMaxVelocity) {
|
||||
delta = int32(balance.ScrollMaxVelocity)
|
||||
}
|
||||
|
@ -155,8 +153,8 @@ func (w *Canvas) loopFollowActor(ev *events.State) error {
|
|||
}
|
||||
|
||||
// Scroll up
|
||||
if APosition.Y-P.Y <= int32(balance.ScrollboxVert) {
|
||||
var delta = APoint.Y - P.Y
|
||||
if APosition.Y-VP.Y <= int32(balance.ScrollboxVert) {
|
||||
var delta = APosition.Y - VP.Y
|
||||
if delta > int32(balance.ScrollMaxVelocity) {
|
||||
delta = int32(balance.ScrollMaxVelocity)
|
||||
}
|
||||
|
@ -168,8 +166,8 @@ func (w *Canvas) loopFollowActor(ev *events.State) error {
|
|||
}
|
||||
|
||||
// Scroll down
|
||||
if APosition.Y >= S.H-ASize.H-int32(balance.ScrollboxVert) {
|
||||
var delta = S.H - ASize.H - int32(balance.ScrollboxVert)
|
||||
if APosition.Y >= VP.H-ASize.H-int32(balance.ScrollboxVert) {
|
||||
var delta = VP.H - ASize.H - int32(balance.ScrollboxVert)
|
||||
if delta > int32(balance.ScrollMaxVelocity) {
|
||||
delta = int32(balance.ScrollMaxVelocity)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user