Noah Petherbridge
6713dd7bfc
Implement scrolling behavior in Play Mode by allowing the Canvas to follow a specific actor and keep it in view. The Canvas has a FollowActor property which holds an ID of the actor to follow (if blank, no actor is being followed). In Play Mode the Player is followed and when they get too close to the left or right edges of the screen, the level will scroll to try and catch them. If the player is moving very fast they can outrun the camera. The bounded levels are enforced in Play Mode and the camera won't scroll to view pixels out-of-bounds and the Doodad actors inside the level aren't allowed to exit its boundaries. This is global, not only for the Player doodad but any Doodad that came with the level as well. Other changes: - Restructured Canvas widget code into many new files. The Canvas widget is shaping up to be where most of the magic happens, which is okay because it's close to the action and pulling the strings from outside would be harder, even tho as a UI element you think it should be lightweight. - Debug Overlay: added room for Scenes to insert their own custom Debug Overlay key/value pairs (the values are string pointers so the Scene can update them freely): - The core labels are FPS, Scene and Mouse. The Pixel (world coordinate under cursor) is removed from the core labels. - Edit Scene provides Pixel, Tool and Swatch - Play Scene provides Pixel, Player, Viewport, Scroll
116 lines
3.3 KiB
Go
116 lines
3.3 KiB
Go
package uix
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"git.kirsle.net/apps/doodle/doodads"
|
|
"git.kirsle.net/apps/doodle/level"
|
|
"git.kirsle.net/apps/doodle/pkg/userdir"
|
|
"git.kirsle.net/apps/doodle/render"
|
|
)
|
|
|
|
// InstallActors adds external Actors to the canvas to be superimposed on top
|
|
// of the drawing.
|
|
func (w *Canvas) InstallActors(actors level.ActorMap) error {
|
|
w.actors = make([]*Actor, 0)
|
|
for id, actor := range actors {
|
|
doodad, err := doodads.LoadJSON(userdir.DoodadPath(actor.Filename))
|
|
if err != nil {
|
|
return fmt.Errorf("InstallActors: %s", err)
|
|
}
|
|
|
|
w.actors = append(w.actors, NewActor(id, actor, doodad))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// AddActor injects additional actors into the canvas, such as a Player doodad.
|
|
func (w *Canvas) AddActor(actor *Actor) error {
|
|
w.actors = append(w.actors, actor)
|
|
return nil
|
|
}
|
|
|
|
// drawActors is a subroutine of Present() that superimposes the actors on top
|
|
// of the level drawing.
|
|
func (w *Canvas) drawActors(e render.Engine, p render.Point) {
|
|
var (
|
|
Viewport = w.ViewportRelative()
|
|
S = w.Size()
|
|
)
|
|
|
|
// See if each Actor is in range of the Viewport.
|
|
for i, a := range w.actors {
|
|
if a == nil {
|
|
log.Error("Canvas.drawActors: null actor at index %d (of %d actors)", i, len(w.actors))
|
|
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()
|
|
)
|
|
|
|
// Create a box of World Coordinates that this actor occupies. The
|
|
// Actor X,Y from level data is already a World Coordinate;
|
|
// accomodate for the size of the Actor.
|
|
actorBox := render.Rect{
|
|
X: actorPoint.X,
|
|
Y: actorPoint.Y,
|
|
W: actorSize.W,
|
|
H: actorSize.H,
|
|
}
|
|
|
|
// Is any part of the actor visible?
|
|
if !Viewport.Intersects(actorBox) {
|
|
continue // not visible on screen
|
|
}
|
|
|
|
drawAt := render.Point{
|
|
X: p.X + w.Scroll.X + actorPoint.X + w.BoxThickness(1),
|
|
Y: p.Y + w.Scroll.Y + actorPoint.Y + w.BoxThickness(1),
|
|
}
|
|
resizeTo := actorSize
|
|
|
|
// XXX TODO: when an Actor hits the left or top edge and shrinks,
|
|
// scrolling to offset that shrink is currently hard to solve.
|
|
scrollTo := render.Origin
|
|
|
|
// Handle cropping and scaling if this Actor's canvas can't be
|
|
// completely visible within the parent.
|
|
if drawAt.X+resizeTo.W > p.X+S.W {
|
|
// Hitting the right edge, shrunk the width now.
|
|
delta := (drawAt.X + resizeTo.W) - (p.X + S.W)
|
|
resizeTo.W -= delta
|
|
} else if drawAt.X < p.X {
|
|
// 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
|
|
resizeTo.W -= delta
|
|
}
|
|
|
|
if drawAt.Y+resizeTo.H > p.Y+S.H {
|
|
// Hitting the bottom edge, shrink the height.
|
|
delta := (drawAt.Y + resizeTo.H) - (p.Y + S.H)
|
|
resizeTo.H -= delta
|
|
} else if drawAt.Y < p.Y {
|
|
// Hitting the top edge. Cap the Y coord and shrink the height.
|
|
delta := p.Y - drawAt.Y
|
|
drawAt.Y = p.Y
|
|
// scrollTo.Y -= delta // TODO
|
|
resizeTo.H -= delta
|
|
}
|
|
|
|
if resizeTo != actorSize {
|
|
can.Resize(resizeTo)
|
|
can.ScrollTo(scrollTo)
|
|
}
|
|
can.Present(e, drawAt)
|
|
|
|
// Clean up the canvas size and offset.
|
|
can.Resize(actorSize) // restore original size in case cropped
|
|
can.ScrollTo(render.Origin)
|
|
}
|
|
}
|