Play: Autoscrolling and Bounded Level Support
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
2018-10-29 00:33:24 +00:00
|
|
|
package uix
|
|
|
|
|
|
|
|
import (
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
|
|
|
|
"git.kirsle.net/apps/doodle/balance"
|
|
|
|
"git.kirsle.net/apps/doodle/events"
|
|
|
|
"git.kirsle.net/apps/doodle/level"
|
|
|
|
"git.kirsle.net/apps/doodle/render"
|
|
|
|
"git.kirsle.net/apps/doodle/ui"
|
|
|
|
)
|
|
|
|
|
|
|
|
/*
|
|
|
|
Loop() subroutine to scroll the canvas using arrow keys (for edit mode).
|
|
|
|
|
|
|
|
If w.Scrollable is false this function won't do anything.
|
|
|
|
|
|
|
|
Cursor keys will scroll the drawing by balance.CanvasScrollSpeed per tick.
|
|
|
|
If the level pageType is constrained, the scrollable viewport will be
|
|
|
|
constrained to fit the bounds of the level.
|
|
|
|
|
|
|
|
The debug boolean `NoLimitScroll=true` will override the bounded level scroll
|
|
|
|
restriction and allow scrolling into out-of-bounds areas of the level.
|
|
|
|
*/
|
|
|
|
func (w *Canvas) loopEditorScroll(ev *events.State) error {
|
|
|
|
if !w.Scrollable {
|
|
|
|
return errors.New("canvas not scrollable")
|
|
|
|
}
|
|
|
|
|
|
|
|
// Arrow keys to scroll the view.
|
|
|
|
scrollBy := render.Point{}
|
|
|
|
if ev.Right.Now {
|
|
|
|
scrollBy.X -= balance.CanvasScrollSpeed
|
|
|
|
} else if ev.Left.Now {
|
|
|
|
scrollBy.X += balance.CanvasScrollSpeed
|
|
|
|
}
|
|
|
|
if ev.Down.Now {
|
|
|
|
scrollBy.Y -= balance.CanvasScrollSpeed
|
|
|
|
} else if ev.Up.Now {
|
|
|
|
scrollBy.Y += balance.CanvasScrollSpeed
|
|
|
|
}
|
|
|
|
if !scrollBy.IsZero() {
|
|
|
|
w.ScrollBy(scrollBy)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
Loop() subroutine to constrain the scrolled view to within a bounded level.
|
|
|
|
*/
|
|
|
|
func (w *Canvas) loopConstrainScroll() error {
|
|
|
|
if w.NoLimitScroll {
|
|
|
|
return errors.New("NoLimitScroll enabled")
|
|
|
|
}
|
|
|
|
|
|
|
|
var capped bool
|
|
|
|
|
|
|
|
// Constrain the top and left edges.
|
|
|
|
if w.wallpaper.pageType > level.Unbounded {
|
|
|
|
if w.Scroll.X > 0 {
|
|
|
|
w.Scroll.X = 0
|
|
|
|
capped = true
|
|
|
|
}
|
|
|
|
if w.Scroll.Y > 0 {
|
|
|
|
w.Scroll.Y = 0
|
|
|
|
capped = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Constrain the bottom and right for limited world sizes.
|
|
|
|
if w.wallpaper.maxWidth+w.wallpaper.maxHeight > 0 {
|
|
|
|
var (
|
|
|
|
// TODO: downcast from int64!
|
|
|
|
mw = int32(w.wallpaper.maxWidth)
|
|
|
|
mh = int32(w.wallpaper.maxHeight)
|
|
|
|
Viewport = w.Viewport()
|
|
|
|
)
|
|
|
|
if Viewport.W > mw {
|
|
|
|
delta := Viewport.W - mw
|
|
|
|
w.Scroll.X += delta
|
|
|
|
capped = true
|
|
|
|
}
|
|
|
|
if Viewport.H > mh {
|
|
|
|
delta := Viewport.H - mh
|
|
|
|
w.Scroll.Y += delta
|
|
|
|
capped = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if capped {
|
|
|
|
return errors.New("scroll limited by level constraint")
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
Loop() subroutine for Play Mode to follow an actor in the camera's view.
|
|
|
|
|
|
|
|
Does nothing if w.FollowActor is an empty string. Set it to the ID of an Actor
|
|
|
|
to follow. If the actor exists, the Canvas will scroll to keep it on the
|
|
|
|
screen.
|
|
|
|
*/
|
|
|
|
func (w *Canvas) loopFollowActor(ev *events.State) error {
|
|
|
|
// Are we following an actor?
|
|
|
|
if w.FollowActor == "" {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
var (
|
|
|
|
P = w.Point()
|
|
|
|
S = w.Size()
|
|
|
|
)
|
|
|
|
|
|
|
|
// Find the actor.
|
|
|
|
for _, actor := range w.actors {
|
|
|
|
if actor.ID() != w.FollowActor {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
actor.Canvas.SetBorderSize(2)
|
|
|
|
actor.Canvas.SetBorderColor(render.Yellow)
|
|
|
|
actor.Canvas.SetBorderStyle(ui.BorderSolid)
|
|
|
|
|
|
|
|
var (
|
|
|
|
APosition = actor.Position() // relative to screen
|
|
|
|
APoint = actor.Drawing.Position()
|
2018-12-30 21:50:24 +00:00
|
|
|
ASize = actor.Drawing.Size()
|
Play: Autoscrolling and Bounded Level Support
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
2018-10-29 00:33:24 +00:00
|
|
|
scrollBy render.Point
|
|
|
|
)
|
|
|
|
|
|
|
|
// Scroll left
|
|
|
|
if APosition.X-P.X <= int32(balance.ScrollboxHoz) {
|
|
|
|
var delta = APoint.X - P.X
|
|
|
|
if delta > int32(balance.ScrollMaxVelocity) {
|
|
|
|
delta = int32(balance.ScrollMaxVelocity)
|
|
|
|
}
|
|
|
|
|
|
|
|
if delta < 0 {
|
|
|
|
// constrain in case they're FAR OFF SCREEN so we don't flip back around
|
|
|
|
delta = -delta
|
|
|
|
}
|
|
|
|
scrollBy.X = delta
|
|
|
|
}
|
|
|
|
|
|
|
|
// Scroll right
|
|
|
|
if APosition.X >= S.W-ASize.W-int32(balance.ScrollboxHoz) {
|
|
|
|
var delta = S.W - ASize.W - int32(balance.ScrollboxHoz)
|
|
|
|
if delta > int32(balance.ScrollMaxVelocity) {
|
|
|
|
delta = int32(balance.ScrollMaxVelocity)
|
|
|
|
}
|
|
|
|
scrollBy.X = -delta
|
|
|
|
}
|
|
|
|
|
|
|
|
// Scroll up
|
|
|
|
if APosition.Y-P.Y <= int32(balance.ScrollboxVert) {
|
|
|
|
var delta = APoint.Y - P.Y
|
|
|
|
if delta > int32(balance.ScrollMaxVelocity) {
|
|
|
|
delta = int32(balance.ScrollMaxVelocity)
|
|
|
|
}
|
|
|
|
|
|
|
|
if delta < 0 {
|
|
|
|
delta = -delta
|
|
|
|
}
|
|
|
|
scrollBy.Y = delta
|
|
|
|
}
|
|
|
|
|
|
|
|
// Scroll down
|
|
|
|
if APosition.Y >= S.H-ASize.H-int32(balance.ScrollboxVert) {
|
|
|
|
var delta = S.H - ASize.H - int32(balance.ScrollboxVert)
|
|
|
|
if delta > int32(balance.ScrollMaxVelocity) {
|
|
|
|
delta = int32(balance.ScrollMaxVelocity)
|
|
|
|
}
|
|
|
|
scrollBy.Y = -delta
|
|
|
|
}
|
|
|
|
|
|
|
|
if scrollBy != render.Origin {
|
|
|
|
w.ScrollBy(scrollBy)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return fmt.Errorf("actor ID '%s' not found in level", w.FollowActor)
|
|
|
|
}
|