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.
This commit is contained in:
parent
3c185528f9
commit
e25869644c
|
@ -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:
|
||||
|
|
|
@ -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
12
enum/enum.go
Normal 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
|
||||
)
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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{
|
||||
|
|
Loading…
Reference in New Issue
Block a user