diff --git a/lib/render/interface.go b/lib/render/interface.go index 7d07503..092ff08 100644 --- a/lib/render/interface.go +++ b/lib/render/interface.go @@ -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 diff --git a/lib/render/point.go b/lib/render/point.go index 272238c..6ef757d 100644 --- a/lib/render/point.go +++ b/lib/render/point.go @@ -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) { diff --git a/lib/ui/label.go b/lib/ui/label.go index 7601562..d970637 100644 --- a/lib/ui/label.go +++ b/lib/ui/label.go @@ -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 = "" + } + 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 { diff --git a/pkg/balance/debug.go b/pkg/balance/debug.go index d9a20b2..aa7c110 100644 --- a/pkg/balance/debug.go +++ b/pkg/balance/debug.go @@ -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() { diff --git a/pkg/balance/numbers.go b/pkg/balance/numbers.go index 7b1a7d8..b5534ed 100644 --- a/pkg/balance/numbers.go +++ b/pkg/balance/numbers.go @@ -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 diff --git a/pkg/doodads/collision.go b/pkg/doodads/collision.go index 8981ba5..ac57713 100644 --- a/pkg/doodads/collision.go +++ b/pkg/doodads/collision.go @@ -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 - hitLeft bool // Has hit an obstacle on the left - hitRight bool // or right + 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. diff --git a/pkg/doodads/player.go b/pkg/doodads/player.go deleted file mode 100644 index 847d2a0..0000000 --- a/pkg/doodads/player.go +++ /dev/null @@ -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, - }) -} diff --git a/pkg/editor_scene.go b/pkg/editor_scene.go index a4380a8..455eff1 100644 --- a/pkg/editor_scene.go +++ b/pkg/editor_scene.go @@ -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) } } diff --git a/pkg/fps.go b/pkg/fps.go index f9f2261..6244c05 100644 --- a/pkg/fps.go +++ b/pkg/fps.go @@ -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 diff --git a/pkg/play_scene.go b/pkg/play_scene.go index e72ca6f..8610532 100644 --- a/pkg/play_scene.go +++ b/pkg/play_scene.go @@ -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. diff --git a/pkg/uix/actor.go b/pkg/uix/actor.go index 51b5dfb..0e79b5c 100644 --- a/pkg/uix/actor.go +++ b/pkg/uix/actor.go @@ -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 } diff --git a/pkg/uix/canvas.go b/pkg/uix/canvas.go index c1dbc00..d5eaa5e 100644 --- a/pkg/uix/canvas.go +++ b/pkg/uix/canvas.go @@ -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. diff --git a/pkg/uix/canvas_actors.go b/pkg/uix/canvas_actors.go index 2f8eb21..ea1b267 100644 --- a/pkg/uix/canvas_actors.go +++ b/pkg/uix/canvas_actors.go @@ -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() + can = a.Canvas // Canvas widget that draws the actor + 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 } diff --git a/pkg/uix/canvas_present.go b/pkg/uix/canvas_present.go index 711705d..43b2bbd 100644 --- a/pkg/uix/canvas_present.go +++ b/pkg/uix/canvas_present.go @@ -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{ diff --git a/pkg/uix/canvas_scrolling.go b/pkg/uix/canvas_scrolling.go index fcb62d3..ec69591 100644 --- a/pkg/uix/canvas_scrolling.go +++ b/pkg/uix/canvas_scrolling.go @@ -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) }