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.
bitmap-cache
Noah 2018-09-25 09:40:34 -07:00
parent 3c185528f9
commit e25869644c
10 changed files with 117 additions and 115 deletions

View File

@ -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:

View File

@ -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()

12
enum/enum.go Normal file
View File

@ -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
)

View File

@ -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.

View File

@ -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
}
}
}
}

View File

@ -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,
})
}
}

View File

@ -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

View File

@ -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 {

View File

@ -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
}

View File

@ -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{