From e25869644c01b3c6197f4f22f360d65d85c65037 Mon Sep 17 00:00:00 2001 From: Noah Petherbridge Date: Tue, 25 Sep 2018 09:40:34 -0700 Subject: [PATCH] Fix Play Mode, Level Handover & Collision Detection * Edit Mode now uses the Level object itself to keep the drawing data rather than pull its Palette and Chunks out, so it can hang on to more information. The Canvas widget is given references to the Level.Palette and Level.Chunker via Canvas.LoadLevel() * Fix the handoff between Edit Mode and Play Mode. They pass the Level object back and forth and the Filename, because it's not part of the Level. You can save the map with its original settings after returning from Play Mode. * Fix the collision detection in Play Mode. It broke previously when palettes were added because of the difference between a render.Point and a level.Pixel and it couldn't easily look up coordinates. The new Chunker system provides a render.Point lookup API. * All pixels are solid for collision right now, TODO is to return Swatch information from the pixels touching the player character and react accordingly (non-solid, fire flag, etc.) * Remove the level.Grid type as it has been replaced by the Chunker. * Clean up some unused variables and functions. --- doodads/doodads.go | 15 +++++---- editor_scene.go | 82 +++++++++++++++++++++++++++------------------- enum/enum.go | 12 +++++++ level/canvas.go | 23 ++++--------- level/chunker.go | 8 +++-- level/grid.go | 27 --------------- level/json.go | 1 - level/types.go | 3 +- play_scene.go | 56 ++++++++++++++++--------------- render/point.go | 5 +++ 10 files changed, 117 insertions(+), 115 deletions(-) create mode 100644 enum/enum.go delete mode 100644 level/grid.go diff --git a/doodads/doodads.go b/doodads/doodads.go index 6667b68..c906034 100644 --- a/doodads/doodads.go +++ b/doodads/doodads.go @@ -30,12 +30,16 @@ type Doodad interface { type Collide struct { Top bool TopPoint render.Point + TopPixel *level.Swatch Left bool LeftPoint render.Point + LeftPixel *level.Swatch Right bool RightPoint render.Point + RightPixel *level.Swatch Bottom bool BottomPoint render.Point + BottomPixel *level.Swatch MoveTo render.Point } @@ -68,7 +72,7 @@ const ( ) // CollidesWithGrid checks if a Doodad collides with level geometry. -func CollidesWithGrid(d Doodad, grid *level.Grid, target render.Point) (*Collide, bool) { +func CollidesWithGrid(d Doodad, grid *level.Chunker, target render.Point) (*Collide, bool) { var ( P = d.Position() S = d.Size() @@ -280,7 +284,7 @@ func GetCollisionBox(box render.Rect) CollisionBox { // ScanBoundingBox scans all of the pixels in a bounding box on the grid and // returns if any of them intersect with level geometry. -func (c *Collide) ScanBoundingBox(box render.Rect, grid *level.Grid) bool { +func (c *Collide) ScanBoundingBox(box render.Rect, grid *level.Chunker) bool { col := GetCollisionBox(box) c.ScanGridLine(col.Top[0], col.Top[1], grid, Top) @@ -293,12 +297,9 @@ func (c *Collide) ScanBoundingBox(box render.Rect, grid *level.Grid) bool { // ScanGridLine scans all of the pixels between p1 and p2 on the grid and tests // for any pixels to be set, implying a collision between level geometry and the // bounding boxes of the doodad. -func (c *Collide) ScanGridLine(p1, p2 render.Point, grid *level.Grid, side Side) { +func (c *Collide) ScanGridLine(p1, p2 render.Point, grid *level.Chunker, side Side) { for point := range render.IterLine2(p1, p2) { - if grid.Exists(&level.Pixel{ - X: point.X, - Y: point.Y, - }) { + if _, err := grid.Get(point); err == nil { // A hit! switch side { case Top: diff --git a/editor_scene.go b/editor_scene.go index 5997a63..8d70f02 100644 --- a/editor_scene.go +++ b/editor_scene.go @@ -1,10 +1,12 @@ package doodle import ( + "fmt" "io/ioutil" "os" "git.kirsle.net/apps/doodle/balance" + "git.kirsle.net/apps/doodle/enum" "git.kirsle.net/apps/doodle/events" "git.kirsle.net/apps/doodle/level" "git.kirsle.net/apps/doodle/render" @@ -15,20 +17,19 @@ type EditorScene struct { // Configuration for the scene initializer. OpenFile bool Filename string - Canvas *level.Chunker UI *EditorUI + // The current level being edited. + DrawingType enum.DrawingType + Level *level.Level + // The canvas widget that contains the map we're working on. // XXX: in dev builds this is available at $ d.Scene.GetDrawing() drawing *level.Canvas - // History of all the pixels placed by the user. - filename string // Last saved filename. - - // Canvas size - width int32 - height int32 + // Last saved filename by the user. + filename string } // Name of the scene. @@ -39,27 +40,37 @@ func (s *EditorScene) Name() string { // Setup the editor scene. func (s *EditorScene) Setup(d *Doodle) error { s.drawing = level.NewCanvas(balance.ChunkSize, true) - s.drawing.Palette = level.DefaultPalette() if len(s.drawing.Palette.Swatches) > 0 { s.drawing.SetSwatch(s.drawing.Palette.Swatches[0]) } - // Were we given configuration data? + // TODO: move inside the UI. Just an approximate position for now. + s.drawing.MoveTo(render.NewPoint(0, 19)) + s.drawing.Resize(render.NewRect(d.width-150, d.height-44)) + s.drawing.Compute(d.Engine) + + // // Were we given configuration data? if s.Filename != "" { - log.Debug("EditorScene: Set filename to %s", s.Filename) + log.Debug("EditorScene.Setup: Set filename to %s", s.Filename) s.filename = s.Filename s.Filename = "" - if s.OpenFile { - log.Debug("EditorScene: Loading map from filename at %s", s.filename) - if err := s.LoadLevel(s.filename); err != nil { - d.Flash("LoadLevel error: %s", err) - } + } + if s.Level != nil { + log.Debug("EditorScene.Setup: received level from scene caller") + s.drawing.LoadLevel(s.Level) + } 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) } } - if s.Canvas != nil { - log.Debug("EditorScene: Received Canvas from caller") - s.drawing.Load(s.drawing.Palette, s.Canvas) - s.Canvas = nil + + // No level? + if s.Level == nil { + log.Debug("EditorScene.Setup: initializing a new Level") + s.Level = level.New() + s.Level.Palette = level.DefaultPalette() + s.drawing.LoadLevel(s.Level) } // Initialize the user interface. It references the palette and such so it @@ -67,8 +78,6 @@ func (s *EditorScene) Setup(d *Doodle) error { s.UI = NewEditorUI(d, s) d.Flash("Editor Mode. Press 'P' to play this map.") - s.width = d.width // TODO: canvas width = copy the window size - s.height = d.height return nil } @@ -81,7 +90,8 @@ func (s *EditorScene) Loop(d *Doodle, ev *events.State) error { if ev.KeyName.Read() == "p" { log.Info("Play Mode, Go!") d.Goto(&PlayScene{ - // Canvas: s.drawing.Grid(), XXX + Filename: s.filename, + Level: s.Level, }) return nil } @@ -95,11 +105,6 @@ func (s *EditorScene) Draw(d *Doodle) error { d.Engine.Clear(render.Magenta) s.UI.Present(d.Engine) - - // TODO: move inside the UI. Just an approximate position for now. - s.drawing.MoveTo(render.NewPoint(0, 19)) - s.drawing.Resize(render.NewRect(d.width-150, d.height-44)) - s.drawing.Compute(d.Engine) s.drawing.Present(d.Engine, s.drawing.Point()) return nil @@ -108,8 +113,16 @@ func (s *EditorScene) Draw(d *Doodle) error { // LoadLevel loads a level from disk. func (s *EditorScene) LoadLevel(filename string) error { s.filename = filename - return s.drawing.LoadFilename(filename) + level, err := level.LoadJSON(filename) + if err != nil { + return fmt.Errorf("EditorScene.LoadLevel(%s): %s", filename, err) + } + + s.DrawingType = enum.LevelDrawing + s.Level = level + s.drawing.LoadLevel(s.Level) + return nil } // SaveLevel saves the level to disk. @@ -117,11 +130,14 @@ func (s *EditorScene) LoadLevel(filename string) error { func (s *EditorScene) SaveLevel(filename string) { s.filename = filename - m := level.New() - m.Title = "Alpha" - m.Author = os.Getenv("USER") - m.Width = s.width - m.Height = s.height + m := s.Level + if m.Title == "" { + m.Title = "Alpha" + } + if m.Author == "" { + m.Author = os.Getenv("USER") + } + m.Palette = s.drawing.Palette m.Chunker = s.drawing.Chunker() diff --git a/enum/enum.go b/enum/enum.go new file mode 100644 index 0000000..e71074c --- /dev/null +++ b/enum/enum.go @@ -0,0 +1,12 @@ +// Package enum defines all the little enum types used throughout Doodle. +package enum + +// DrawingType tells the EditorScene whether the currently open drawing is +// a Level or a Doodad. +type DrawingType int + +// EditorType values. +const ( + LevelDrawing DrawingType = iota + DoodadDrawing +) diff --git a/level/canvas.go b/level/canvas.go index 3bc592d..7b936ae 100644 --- a/level/canvas.go +++ b/level/canvas.go @@ -38,23 +38,15 @@ func NewCanvas(size int, editable bool) *Canvas { func (w *Canvas) Load(p *Palette, g *Chunker) { w.Palette = p w.chunks = g -} - -// LoadFilename initializes the Canvas using a file on disk. -func (w *Canvas) LoadFilename(filename string) error { - m, err := LoadJSON(filename) - if err != nil { - return err - } - - w.Palette = m.Palette - w.chunks = m.Chunker if len(w.Palette.Swatches) > 0 { w.SetSwatch(w.Palette.Swatches[0]) } +} - return nil +// LoadLevel initializes a Canvas from a Level object. +func (w *Canvas) LoadLevel(level *Level) { + w.Load(level.Palette, level.Chunker) } // SetSwatch changes the currently selected swatch for editing. @@ -117,10 +109,9 @@ func (w *Canvas) Loop(ev *events.State) error { Y: ev.CursorY.Now - P.Y + w.Scroll.Y, } pixel := &Pixel{ - X: cursor.X, - Y: cursor.Y, - Palette: w.Palette, - Swatch: w.Palette.ActiveSwatch, + X: cursor.X, + Y: cursor.Y, + Swatch: w.Palette.ActiveSwatch, } // Append unique new pixels. diff --git a/level/chunker.go b/level/chunker.go index c2b0afb..e4312b3 100644 --- a/level/chunker.go +++ b/level/chunker.go @@ -29,7 +29,7 @@ func NewChunker(size int) *Chunker { // on disk) to connect references to the swatches in the palette. func (c *Chunker) Inflate(pal *Palette) error { for coord, chunk := range c.Chunks { - log.Debug("Chunker.Inflate: expanding chunk %s %+v", coord, chunk) + log.Debug("Chunker.Inflate: expanding chunk %s", coord) chunk.Inflate(pal) } return nil @@ -52,7 +52,11 @@ func (c *Chunker) IterViewport(viewport render.Rect) <-chan Pixel { for cy := topLeft.Y; cy <= bottomRight.Y; cy++ { if chunk, ok := c.GetChunk(render.NewPoint(cx, cy)); ok { for px := range chunk.Iter() { - pipe <- px + + // Verify this pixel is also in range. + if px.Point().Inside(viewport) { + pipe <- px + } } } } diff --git a/level/grid.go b/level/grid.go deleted file mode 100644 index f774476..0000000 --- a/level/grid.go +++ /dev/null @@ -1,27 +0,0 @@ -package level - -import ( - "git.kirsle.net/apps/doodle/render" -) - -// Grid is a 2D grid of pixels in X,Y notation. -type Grid map[*Pixel]interface{} - -// Exists returns true if the point exists on the grid. -func (g *Grid) Exists(p *Pixel) bool { - if _, ok := (*g)[p]; ok { - return true - } - return false -} - -// Draw the grid efficiently. -func (g *Grid) Draw(e render.Engine) { - for pixel := range *g { - color := pixel.Swatch.Color - e.DrawPoint(color, render.Point{ - X: pixel.X, - Y: pixel.Y, - }) - } -} diff --git a/level/json.go b/level/json.go index 324bbf1..3ea821b 100644 --- a/level/json.go +++ b/level/json.go @@ -57,7 +57,6 @@ func LoadJSON(filename string) (*Level, error) { px, px.PaletteIndex, len(m.Palette.Swatches), ) } - px.Palette = m.Palette px.Swatch = m.Palette.Swatches[px.PaletteIndex] } return m, err diff --git a/level/types.go b/level/types.go index 40e8f96..794abb0 100644 --- a/level/types.go +++ b/level/types.go @@ -50,8 +50,7 @@ type Pixel struct { PaletteIndex int32 `json:"p"` // Private runtime values. - Palette *Palette `json:"-"` // pointer to its palette, TODO: needed? - Swatch *Swatch `json:"-"` // pointer to its swatch, for when rendered. + Swatch *Swatch `json:"-"` // pointer to its swatch, for when rendered. } func (p Pixel) String() string { diff --git a/play_scene.go b/play_scene.go index c4082d3..b5b8bee 100644 --- a/play_scene.go +++ b/play_scene.go @@ -1,6 +1,9 @@ package doodle import ( + "fmt" + + "git.kirsle.net/apps/doodle/balance" "git.kirsle.net/apps/doodle/doodads" "git.kirsle.net/apps/doodle/events" "git.kirsle.net/apps/doodle/level" @@ -11,14 +14,10 @@ import ( type PlayScene struct { // Configuration attributes. Filename string - Canvas *level.Grid + Level *level.Level // Private variables. - canvas *level.Grid - - // Canvas size - width int32 - height int32 + drawing *level.Canvas // Player character Player doodads.Doodad @@ -31,27 +30,28 @@ func (s *PlayScene) Name() string { // Setup the play scene. func (s *PlayScene) Setup(d *Doodle) error { - // Given a filename or map data to play? - if s.Canvas != nil { - log.Debug("PlayScene.Setup: loading map from given canvas") - s.canvas = s.Canvas + s.drawing = level.NewCanvas(balance.ChunkSize, false) + s.drawing.MoveTo(render.Origin) + s.drawing.Resize(render.NewRect(d.width, d.height)) + s.drawing.Compute(d.Engine) + // Given a filename or map data to play? + if s.Level != nil { + log.Debug("PlayScene.Setup: received level from scene caller") + s.drawing.LoadLevel(s.Level) } else if s.Filename != "" { log.Debug("PlayScene.Setup: loading map from file %s", s.Filename) s.LoadLevel(s.Filename) - s.Filename = "" } s.Player = doodads.NewPlayer() - if s.canvas == nil { + if s.Level == nil { log.Debug("PlayScene.Setup: no grid given, initializing empty grid") - s.canvas = &level.Grid{} + s.Level = level.New() + s.drawing.LoadLevel(s.Level) } - s.width = d.width // TODO: canvas width = copy the window size - s.height = d.height - d.Flash("Entered Play Mode. Press 'E' to edit this map.") return nil @@ -63,11 +63,13 @@ func (s *PlayScene) Loop(d *Doodle, ev *events.State) error { if ev.KeyName.Read() == "e" { log.Info("Edit Mode, Go!") d.Goto(&EditorScene{ - // Canvas: s.canvas, + Filename: s.Filename, + Level: s.Level, }) return nil } + // s.drawing.Loop(ev) s.movePlayer(ev) return nil } @@ -77,7 +79,8 @@ func (s *PlayScene) Draw(d *Doodle) error { // Clear the canvas and fill it with white. d.Engine.Clear(render.White) - s.canvas.Draw(d.Engine) + // Draw the level. + s.drawing.Present(d.Engine, s.drawing.Point()) // Draw our hero. s.Player.Draw(d.Engine) @@ -110,7 +113,7 @@ func (s *PlayScene) movePlayer(ev *events.State) { // Apply gravity. // var onFloor bool - info, ok := doodads.CollidesWithGrid(s.Player, s.canvas, delta) + info, ok := doodads.CollidesWithGrid(s.Player, s.Level.Chunker, delta) if ok { // Collision happened with world. } @@ -128,16 +131,15 @@ func (s *PlayScene) movePlayer(ev *events.State) { // LoadLevel loads a level from disk. func (s *PlayScene) LoadLevel(filename string) error { - s.canvas = &level.Grid{} + s.Filename = filename - // m, err := level.LoadJSON(filename) - // if err != nil { - // return err - // } + level, err := level.LoadJSON(filename) + if err != nil { + return fmt.Errorf("PlayScene.LoadLevel(%s): %s", filename, err) + } - // for _, pixel := range m.Pixels { - // // *s.canvas[pixel] = nil - // } + s.Level = level + s.drawing.LoadLevel(s.Level) return nil } diff --git a/render/point.go b/render/point.go index dcb16cf..ac10d12 100644 --- a/render/point.go +++ b/render/point.go @@ -12,6 +12,11 @@ type Point struct { Y int32 } +// Common points. +var ( + Origin Point +) + // NewPoint makes a new Point at an X,Y coordinate. func NewPoint(x, y int32) Point { return Point{