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{}
|
Child interface{}
|
||||||
|
|
||||||
// Camera controls
|
// 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
|
Centre image.Point // world coordinates
|
||||||
Filter ebiten.Filter
|
Rotation float64 // radians
|
||||||
Rotation float64 // radians
|
Zoom float64 // unitless
|
||||||
Zoom float64 // unitless
|
|
||||||
|
|
||||||
game *Game
|
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)
|
// Prepare grabs a copy of game (needed for screen dimensions)
|
||||||
func (c *Camera) Prepare(game *Game) error {
|
func (c *Camera) Prepare(game *Game) error {
|
||||||
c.game = game
|
c.game = game
|
||||||
|
@ -47,6 +90,6 @@ func (c *Camera) Transform() (opts ebiten.DrawImageOptions) {
|
||||||
opts.GeoM.Translate(float2(c.Centre.Mul(-1)))
|
opts.GeoM.Translate(float2(c.Centre.Mul(-1)))
|
||||||
opts.GeoM.Scale(c.Zoom, c.Zoom)
|
opts.GeoM.Scale(c.Zoom, c.Zoom)
|
||||||
opts.GeoM.Rotate(c.Rotation)
|
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
|
return opts
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"encoding/gob"
|
"encoding/gob"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"image"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"math"
|
"math"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
@ -36,9 +37,8 @@ func init() {
|
||||||
type Game struct {
|
type Game struct {
|
||||||
Disabled
|
Disabled
|
||||||
Hidden
|
Hidden
|
||||||
ScreenWidth int
|
ScreenSize image.Point
|
||||||
ScreenHeight int
|
Root interface{} // typically a *Scene or SceneRef though
|
||||||
Root interface{} // typically a *Scene or SceneRef though
|
|
||||||
|
|
||||||
dbmu sync.RWMutex
|
dbmu sync.RWMutex
|
||||||
byID map[string]Identifier // Named components by ID
|
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.
|
// Layout returns the configured screen width/height.
|
||||||
func (g *Game) Layout(outsideWidth, outsideHeight int) (w, h int) {
|
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.
|
// Update updates everything.
|
||||||
|
|
|
@ -11,7 +11,7 @@ func init() {
|
||||||
gob.Register(&Scene{})
|
gob.Register(&Scene{})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Scene manages drawing and updating a bunch of components.
|
// Scene just contains a bunch of components.
|
||||||
type Scene struct {
|
type Scene struct {
|
||||||
ID
|
ID
|
||||||
Bounds // world coordinates
|
Bounds // world coordinates
|
||||||
|
@ -20,87 +20,5 @@ type Scene struct {
|
||||||
Hidden
|
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).
|
// Scan returns all immediate subcomponents (including the camera, if not nil).
|
||||||
func (s *Scene) Scan() []interface{} { return s.Components }
|
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
|
// 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.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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
3
main.go
3
main.go
|
@ -38,8 +38,7 @@ func main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
g := &engine.Game{
|
g := &engine.Game{
|
||||||
ScreenHeight: 240,
|
ScreenSize: image.Pt(320, 240),
|
||||||
ScreenWidth: 320,
|
|
||||||
Root: &engine.Scene{
|
Root: &engine.Scene{
|
||||||
ID: "root",
|
ID: "root",
|
||||||
Components: []interface{}{
|
Components: []interface{}{
|
||||||
|
|
Loading…
Reference in a new issue