reimplement camera constraint
This commit is contained in:
parent
8f495888d9
commit
7c7fd781d2
5 changed files with 58 additions and 98 deletions
|
@ -25,14 +25,57 @@ type Camera struct {
|
|||
Child interface{}
|
||||
|
||||
// Camera controls
|
||||
// These directly manipulate the camera. If you want to restrict the camera
|
||||
// view area to the child's bounding rectangle, use PointAt.
|
||||
Centre image.Point // world coordinates
|
||||
Filter ebiten.Filter
|
||||
Rotation float64 // radians
|
||||
Zoom float64 // unitless
|
||||
Rotation float64 // radians
|
||||
Zoom float64 // unitless
|
||||
|
||||
game *Game
|
||||
}
|
||||
|
||||
// PointAt points the camera at a particular centre point and zoom, but adjusts
|
||||
// for the bounds of the child component (if available).
|
||||
func (c *Camera) PointAt(centre image.Point, zoom float64) {
|
||||
// Special sauce: if Child has a BoundingRect, make some adjustments
|
||||
bnd, ok := c.Child.(Bounder)
|
||||
if !ok {
|
||||
c.Centre, c.Zoom = centre, zoom
|
||||
return
|
||||
}
|
||||
|
||||
// The child has boundaries; respect them.
|
||||
br := bnd.BoundingRect()
|
||||
|
||||
// The lower bound on zoom is the larger of
|
||||
// { (ScreenWidth / BoundsWidth), (ScreenHeight / BoundsHeight) }
|
||||
sz := br.Size()
|
||||
if z := float64(c.game.ScreenSize.X) / float64(sz.X); zoom < z {
|
||||
zoom = z
|
||||
}
|
||||
if z := float64(c.game.ScreenSize.Y) / float64(sz.Y); zoom < z {
|
||||
zoom = z
|
||||
}
|
||||
|
||||
// If the configured centre puts the camera out of bounds, move it.
|
||||
// Camera frame currently Rectangle{ centre ± (screen/(2*zoom)) }.
|
||||
sw2, sh2 := float2(c.game.ScreenSize.Div(2))
|
||||
swz, shz := int(sw2/zoom), int(sh2/zoom)
|
||||
if centre.X-swz < br.Min.X {
|
||||
centre.X = br.Min.X + swz
|
||||
}
|
||||
if centre.Y-shz < br.Min.Y {
|
||||
centre.Y = br.Min.Y + shz
|
||||
}
|
||||
if centre.X+swz > br.Max.X {
|
||||
centre.X = br.Max.X - swz
|
||||
}
|
||||
if centre.Y+shz > br.Max.Y {
|
||||
centre.Y = br.Max.Y - shz
|
||||
}
|
||||
c.Centre, c.Zoom = centre, zoom
|
||||
}
|
||||
|
||||
// Prepare grabs a copy of game (needed for screen dimensions)
|
||||
func (c *Camera) Prepare(game *Game) error {
|
||||
c.game = game
|
||||
|
@ -47,6 +90,6 @@ func (c *Camera) Transform() (opts ebiten.DrawImageOptions) {
|
|||
opts.GeoM.Translate(float2(c.Centre.Mul(-1)))
|
||||
opts.GeoM.Scale(c.Zoom, c.Zoom)
|
||||
opts.GeoM.Rotate(c.Rotation)
|
||||
opts.GeoM.Translate(float64(c.game.ScreenWidth/2), float64(c.game.ScreenHeight/2))
|
||||
opts.GeoM.Translate(float2(c.game.ScreenSize.Div(2)))
|
||||
return opts
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"encoding/gob"
|
||||
"errors"
|
||||
"fmt"
|
||||
"image"
|
||||
"io/fs"
|
||||
"math"
|
||||
"reflect"
|
||||
|
@ -36,9 +37,8 @@ func init() {
|
|||
type Game struct {
|
||||
Disabled
|
||||
Hidden
|
||||
ScreenWidth int
|
||||
ScreenHeight int
|
||||
Root interface{} // typically a *Scene or SceneRef though
|
||||
ScreenSize image.Point
|
||||
Root interface{} // typically a *Scene or SceneRef though
|
||||
|
||||
dbmu sync.RWMutex
|
||||
byID map[string]Identifier // Named components by ID
|
||||
|
@ -110,7 +110,7 @@ func (g *Game) Draw(screen *ebiten.Image) {
|
|||
|
||||
// Layout returns the configured screen width/height.
|
||||
func (g *Game) Layout(outsideWidth, outsideHeight int) (w, h int) {
|
||||
return g.ScreenWidth, g.ScreenHeight
|
||||
return g.ScreenSize.X, g.ScreenSize.Y
|
||||
}
|
||||
|
||||
// Update updates everything.
|
||||
|
|
|
@ -11,7 +11,7 @@ func init() {
|
|||
gob.Register(&Scene{})
|
||||
}
|
||||
|
||||
// Scene manages drawing and updating a bunch of components.
|
||||
// Scene just contains a bunch of components.
|
||||
type Scene struct {
|
||||
ID
|
||||
Bounds // world coordinates
|
||||
|
@ -20,87 +20,5 @@ type Scene struct {
|
|||
Hidden
|
||||
}
|
||||
|
||||
/*
|
||||
// Draw draws all components in order.
|
||||
func (s *Scene) Draw(screen *ebiten.Image, opts ebiten.DrawImageOptions) {
|
||||
if s.Hidden {
|
||||
return
|
||||
}
|
||||
if s.Camera == nil {
|
||||
// Draw everything, no camera transforms.
|
||||
for _, i := range s.Components {
|
||||
if d, ok := i.(Drawer); ok {
|
||||
d.Draw(screen, opts)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// There is a camera; apply camera transforms.
|
||||
br := s.BoundingRect()
|
||||
|
||||
// The lower bound on zoom is the larger of
|
||||
// { (ScreenWidth / BoundsWidth), (ScreenHeight / BoundsHeight) }
|
||||
zoom := s.Camera.Zoom
|
||||
sz := br.Size()
|
||||
if z := float64(s.Camera.game.ScreenWidth) / float64(sz.X); zoom < z {
|
||||
zoom = z
|
||||
}
|
||||
if z := float64(s.Camera.game.ScreenHeight) / float64(sz.Y); zoom < z {
|
||||
zoom = z
|
||||
}
|
||||
|
||||
// If the configured centre puts the camera out of bounds, move it.
|
||||
centre := s.Camera.Centre
|
||||
// Camera frame currently Rectangle{ centre ± (screen/(2*zoom)) }.
|
||||
sw2, sh2 := float64(s.Camera.game.ScreenWidth/2), float64(s.Camera.game.ScreenHeight/2)
|
||||
swz, shz := int(sw2/zoom), int(sh2/zoom)
|
||||
if centre.X-swz < br.Min.X {
|
||||
centre.X = br.Min.X + swz
|
||||
}
|
||||
if centre.Y-shz < br.Min.Y {
|
||||
centre.Y = br.Min.Y + shz
|
||||
}
|
||||
if centre.X+swz > br.Max.X {
|
||||
centre.X = br.Max.X - swz
|
||||
}
|
||||
if centre.Y+shz > br.Max.Y {
|
||||
centre.Y = br.Max.Y - shz
|
||||
}
|
||||
|
||||
// Apply other options
|
||||
opts.Filter = s.Camera.Filter
|
||||
|
||||
// Compute common matrix (parts independent of parallax, which is step 1).
|
||||
// Moving centre to the origin happens per component.
|
||||
var comm ebiten.GeoM
|
||||
// 2. Zoom and rotate
|
||||
comm.Scale(zoom, zoom)
|
||||
comm.Rotate(s.Camera.Rotation)
|
||||
// 3. Move the origin to the centre of screen space.
|
||||
comm.Translate(sw2, sh2)
|
||||
// 4. Apply transforms from the caller.
|
||||
comm.Concat(opts.GeoM)
|
||||
|
||||
// Draw everything.
|
||||
for _, i := range s.Components {
|
||||
d, ok := i.(Drawer)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
pf := 1.0
|
||||
if s, ok := i.(ParallaxScaler); ok {
|
||||
pf = s.ParallaxFactor()
|
||||
}
|
||||
var geom ebiten.GeoM
|
||||
// 1. Move centre to the origin, subject to parallax factor
|
||||
geom.Translate(-float64(centre.X)*pf, -float64(centre.Y)*pf)
|
||||
geom.Concat(comm)
|
||||
opts.GeoM = geom
|
||||
d.Draw(screen, opts)
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
// Scan returns all immediate subcomponents (including the camera, if not nil).
|
||||
func (s *Scene) Scan() []interface{} { return s.Components }
|
||||
|
|
10
game/aw.go
10
game/aw.go
|
@ -64,12 +64,12 @@ func (aw *Awakeman) Update() error {
|
|||
}
|
||||
|
||||
// Update the camera
|
||||
aw.camera.Zoom = 1
|
||||
if ebiten.IsKeyPressed(ebiten.KeyShift) {
|
||||
aw.camera.Zoom = 2
|
||||
}
|
||||
// aw.Pos is top-left corner, so add half size to get centre
|
||||
aw.camera.Centre = aw.Sprite.Actor.Pos.Add(aw.Sprite.Actor.Size.Div(2))
|
||||
z := 1.0
|
||||
if ebiten.IsKeyPressed(ebiten.KeyShift) {
|
||||
z = 2.0
|
||||
}
|
||||
aw.camera.PointAt(aw.Sprite.Actor.Pos.Add(aw.Sprite.Actor.Size.Div(2)), z)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
3
main.go
3
main.go
|
@ -38,8 +38,7 @@ func main() {
|
|||
}
|
||||
|
||||
g := &engine.Game{
|
||||
ScreenHeight: 240,
|
||||
ScreenWidth: 320,
|
||||
ScreenSize: image.Pt(320, 240),
|
||||
Root: &engine.Scene{
|
||||
ID: "root",
|
||||
Components: []interface{}{
|
||||
|
|
Loading…
Reference in a new issue