Restructure the app to be scene-based
This commit is contained in:
parent
a4fc6ec231
commit
ede3d58e1d
107
doodle.go
107
doodle.go
|
@ -33,8 +33,7 @@ type Doodle struct {
|
|||
width int32
|
||||
height int32
|
||||
|
||||
nextSecond time.Time
|
||||
canvas Grid
|
||||
scene Scene
|
||||
|
||||
window *sdl.Window
|
||||
renderer *sdl.Renderer
|
||||
|
@ -49,9 +48,6 @@ func New(debug bool) *Doodle {
|
|||
running: true,
|
||||
width: 800,
|
||||
height: 600,
|
||||
canvas: Grid{},
|
||||
|
||||
nextSecond: time.Now().Add(1 * time.Second),
|
||||
}
|
||||
|
||||
if !debug {
|
||||
|
@ -102,13 +98,25 @@ func (d *Doodle) Run() error {
|
|||
render.Renderer = renderer
|
||||
defer renderer.Destroy()
|
||||
|
||||
// Set up the default scene.
|
||||
if d.scene == nil {
|
||||
d.Goto(&EditorScene{})
|
||||
}
|
||||
|
||||
log.Info("Enter Main Loop")
|
||||
for d.running {
|
||||
d.ticks++
|
||||
|
||||
// Poll for events.
|
||||
_, err := d.events.Poll(d.ticks)
|
||||
if err != nil {
|
||||
log.Error("event poll error: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Draw a frame and log how long it took.
|
||||
start := time.Now()
|
||||
err = d.Loop()
|
||||
err = d.scene.Loop(d)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -131,6 +139,18 @@ func (d *Doodle) Run() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// LoadLevel loads a map from JSON into the EditorScene.
|
||||
func (d *Doodle) LoadLevel(filename string) error {
|
||||
log.Info("Loading level from file: %s", filename)
|
||||
scene := &EditorScene{}
|
||||
err := scene.LoadLevel(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
d.Goto(scene)
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO: not a global
|
||||
type Pixel struct {
|
||||
start bool
|
||||
|
@ -149,78 +169,3 @@ func (p Pixel) String() string {
|
|||
|
||||
// Grid is a 2D grid of pixels in X,Y notation.
|
||||
type Grid map[Pixel]interface{}
|
||||
|
||||
// TODO: a linked list instead of a slice
|
||||
var pixelHistory []Pixel
|
||||
|
||||
// Loop runs one loop of the game engine.
|
||||
func (d *Doodle) Loop() error {
|
||||
// Poll for events.
|
||||
ev, err := d.events.Poll(d.ticks)
|
||||
if err != nil {
|
||||
log.Error("event poll error: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Taking a screenshot?
|
||||
if ev.ScreenshotKey.Pressed() {
|
||||
log.Info("Taking a screenshot")
|
||||
d.Screenshot()
|
||||
d.SaveLevel()
|
||||
}
|
||||
|
||||
// Clear the canvas and fill it with white.
|
||||
d.renderer.SetDrawColor(255, 255, 255, 255)
|
||||
d.renderer.Clear()
|
||||
|
||||
// Clicking? Log all the pixels while doing so.
|
||||
if ev.Button1.Now {
|
||||
pixel := Pixel{
|
||||
start: ev.Button1.Pressed(),
|
||||
x: ev.CursorX.Now,
|
||||
y: ev.CursorY.Now,
|
||||
dx: ev.CursorX.Now,
|
||||
dy: ev.CursorY.Now,
|
||||
}
|
||||
|
||||
// Append unique new pixels.
|
||||
if len(pixelHistory) == 0 || pixelHistory[len(pixelHistory)-1] != pixel {
|
||||
// If not a start pixel, make the delta coord the previous one.
|
||||
if !pixel.start && len(pixelHistory) > 0 {
|
||||
prev := pixelHistory[len(pixelHistory)-1]
|
||||
pixel.dx = prev.x
|
||||
pixel.dy = prev.y
|
||||
}
|
||||
|
||||
pixelHistory = append(pixelHistory, pixel)
|
||||
|
||||
// Save in the pixel canvas map.
|
||||
d.canvas[pixel] = nil
|
||||
}
|
||||
}
|
||||
|
||||
d.renderer.SetDrawColor(0, 0, 0, 255)
|
||||
for i, pixel := range pixelHistory {
|
||||
if !pixel.start && i > 0 {
|
||||
prev := pixelHistory[i-1]
|
||||
if prev.x == pixel.x && prev.y == pixel.y {
|
||||
d.renderer.DrawPoint(pixel.x, pixel.y)
|
||||
} else {
|
||||
d.renderer.DrawLine(
|
||||
pixel.x,
|
||||
pixel.y,
|
||||
prev.x,
|
||||
prev.y,
|
||||
)
|
||||
}
|
||||
}
|
||||
d.renderer.DrawPoint(pixel.x, pixel.y)
|
||||
}
|
||||
|
||||
// Draw the FPS.
|
||||
d.DrawDebugOverlay()
|
||||
|
||||
d.renderer.Present()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
221
editor_scene.go
Normal file
221
editor_scene.go
Normal file
|
@ -0,0 +1,221 @@
|
|||
package doodle
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image"
|
||||
"image/png"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"git.kirsle.net/apps/doodle/draw"
|
||||
"git.kirsle.net/apps/doodle/level"
|
||||
)
|
||||
|
||||
// EditorScene manages the "Edit Level" game mode.
|
||||
type EditorScene struct {
|
||||
// History of all the pixels placed by the user.
|
||||
pixelHistory []Pixel
|
||||
canvas Grid
|
||||
|
||||
// Canvas size
|
||||
width int32
|
||||
height int32
|
||||
}
|
||||
|
||||
// Setup the editor scene.
|
||||
func (e *EditorScene) Setup(d *Doodle) error {
|
||||
if e.pixelHistory == nil {
|
||||
e.pixelHistory = []Pixel{}
|
||||
}
|
||||
if e.canvas == nil {
|
||||
e.canvas = Grid{}
|
||||
}
|
||||
e.width = d.width // TODO: canvas width = copy the window size
|
||||
e.height = d.height
|
||||
return nil
|
||||
}
|
||||
|
||||
// Loop the editor scene.
|
||||
func (e *EditorScene) Loop(d *Doodle) error {
|
||||
ev := d.events
|
||||
|
||||
// Taking a screenshot?
|
||||
if ev.ScreenshotKey.Pressed() {
|
||||
log.Info("Taking a screenshot")
|
||||
e.Screenshot()
|
||||
e.SaveLevel()
|
||||
}
|
||||
|
||||
// Clear the canvas and fill it with white.
|
||||
d.renderer.SetDrawColor(255, 255, 255, 255)
|
||||
d.renderer.Clear()
|
||||
|
||||
// Clicking? Log all the pixels while doing so.
|
||||
if ev.Button1.Now {
|
||||
pixel := Pixel{
|
||||
start: ev.Button1.Pressed(),
|
||||
x: ev.CursorX.Now,
|
||||
y: ev.CursorY.Now,
|
||||
dx: ev.CursorX.Now,
|
||||
dy: ev.CursorY.Now,
|
||||
}
|
||||
|
||||
// Append unique new pixels.
|
||||
if len(e.pixelHistory) == 0 || e.pixelHistory[len(e.pixelHistory)-1] != pixel {
|
||||
// If not a start pixel, make the delta coord the previous one.
|
||||
if !pixel.start && len(e.pixelHistory) > 0 {
|
||||
prev := e.pixelHistory[len(e.pixelHistory)-1]
|
||||
pixel.dx = prev.x
|
||||
pixel.dy = prev.y
|
||||
}
|
||||
|
||||
e.pixelHistory = append(e.pixelHistory, pixel)
|
||||
|
||||
// Save in the pixel canvas map.
|
||||
e.canvas[pixel] = nil
|
||||
}
|
||||
}
|
||||
|
||||
d.renderer.SetDrawColor(0, 0, 0, 255)
|
||||
for i, pixel := range e.pixelHistory {
|
||||
if !pixel.start && i > 0 {
|
||||
prev := e.pixelHistory[i-1]
|
||||
if prev.x == pixel.x && prev.y == pixel.y {
|
||||
d.renderer.DrawPoint(pixel.x, pixel.y)
|
||||
} else {
|
||||
d.renderer.DrawLine(
|
||||
pixel.x,
|
||||
pixel.y,
|
||||
prev.x,
|
||||
prev.y,
|
||||
)
|
||||
}
|
||||
}
|
||||
d.renderer.DrawPoint(pixel.x, pixel.y)
|
||||
}
|
||||
|
||||
// Draw the FPS.
|
||||
d.DrawDebugOverlay()
|
||||
|
||||
d.renderer.Present()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// LoadLevel loads a level from disk.
|
||||
func (e *EditorScene) LoadLevel(filename string) error {
|
||||
e.pixelHistory = []Pixel{}
|
||||
e.canvas = Grid{}
|
||||
|
||||
m, err := level.LoadJSON(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, point := range m.Pixels {
|
||||
pixel := Pixel{
|
||||
start: true,
|
||||
x: point.X,
|
||||
y: point.Y,
|
||||
dx: point.X,
|
||||
dy: point.Y,
|
||||
}
|
||||
e.pixelHistory = append(e.pixelHistory, pixel)
|
||||
e.canvas[pixel] = nil
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SaveLevel saves the level to disk.
|
||||
func (e *EditorScene) SaveLevel() {
|
||||
m := level.Level{
|
||||
Version: 1,
|
||||
Title: "Alpha",
|
||||
Author: os.Getenv("USER"),
|
||||
Width: e.width,
|
||||
Height: e.height,
|
||||
Palette: []level.Palette{
|
||||
level.Palette{
|
||||
Color: "#000000",
|
||||
Solid: true,
|
||||
},
|
||||
},
|
||||
Pixels: []level.Pixel{},
|
||||
}
|
||||
|
||||
for pixel := range e.canvas {
|
||||
for point := range draw.Line(pixel.x, pixel.y, pixel.dx, pixel.dy) {
|
||||
m.Pixels = append(m.Pixels, level.Pixel{
|
||||
X: point.X,
|
||||
Y: point.Y,
|
||||
Palette: 0,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
json, err := m.ToJSON()
|
||||
if err != nil {
|
||||
log.Error("SaveLevel error: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
filename := fmt.Sprintf("./map-%s.json",
|
||||
time.Now().Format("2006-01-02T15-04-05"),
|
||||
)
|
||||
err = ioutil.WriteFile(filename, json, 0644)
|
||||
if err != nil {
|
||||
log.Error("Create map file error: %s", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Screenshot saves the level canvas to disk as a PNG image.
|
||||
func (e *EditorScene) Screenshot() {
|
||||
screenshot := image.NewRGBA(image.Rect(0, 0, int(e.width), int(e.height)))
|
||||
|
||||
// White-out the image.
|
||||
for x := 0; x < int(e.width); x++ {
|
||||
for y := 0; y < int(e.height); y++ {
|
||||
screenshot.Set(x, y, image.White)
|
||||
}
|
||||
}
|
||||
|
||||
// Fill in the dots we drew.
|
||||
for pixel := range e.canvas {
|
||||
// A line or a dot?
|
||||
if pixel.x == pixel.dx && pixel.y == pixel.dy {
|
||||
screenshot.Set(int(pixel.x), int(pixel.y), image.Black)
|
||||
} else {
|
||||
for point := range draw.Line(pixel.x, pixel.y, pixel.dx, pixel.dy) {
|
||||
screenshot.Set(int(point.X), int(point.Y), image.Black)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create the screenshot directory.
|
||||
if _, err := os.Stat("./screenshots"); os.IsNotExist(err) {
|
||||
log.Info("Creating directory: ./screenshots")
|
||||
err = os.Mkdir("./screenshots", 0755)
|
||||
if err != nil {
|
||||
log.Error("Can't create ./screenshots: %s", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
filename := fmt.Sprintf("./screenshots/screenshot-%s.png",
|
||||
time.Now().Format("2006-01-02T15-04-05"),
|
||||
)
|
||||
fh, err := os.Create(filename)
|
||||
if err != nil {
|
||||
log.Error(err.Error())
|
||||
return
|
||||
}
|
||||
defer fh.Close()
|
||||
|
||||
if err := png.Encode(fh, screenshot); err != nil {
|
||||
log.Error(err.Error())
|
||||
return
|
||||
}
|
||||
}
|
3
fps.go
3
fps.go
|
@ -27,12 +27,11 @@ func (d *Doodle) DrawDebugOverlay() {
|
|||
}
|
||||
|
||||
text := fmt.Sprintf(
|
||||
"FPS: %d (%dms) (%d,%d) size=%d F12=screenshot",
|
||||
"FPS: %d (%dms) (%d,%d) F12=screenshot",
|
||||
fpsCurrent,
|
||||
fpsSkipped,
|
||||
d.events.CursorX.Now,
|
||||
d.events.CursorY.Now,
|
||||
len(pixelHistory),
|
||||
)
|
||||
render.StrokedText(render.TextConfig{
|
||||
Text: text,
|
||||
|
|
17
scene.go
Normal file
17
scene.go
Normal file
|
@ -0,0 +1,17 @@
|
|||
package doodle
|
||||
|
||||
// Scene is an abstraction for a game mode in Doodle. The app points to one
|
||||
// scene at a time and that scene has control over the main loop, and its own
|
||||
// state information.
|
||||
type Scene interface {
|
||||
Setup(*Doodle) error
|
||||
Loop(*Doodle) error
|
||||
}
|
||||
|
||||
// Goto a scene. First it unloads the current scene.
|
||||
func (d *Doodle) Goto(scene Scene) error {
|
||||
// d.scene.Destroy()
|
||||
log.Info("Goto Scene")
|
||||
d.scene = scene
|
||||
return d.scene.Setup(d)
|
||||
}
|
131
screenshot.go
131
screenshot.go
|
@ -1,131 +0,0 @@
|
|||
package doodle
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image"
|
||||
"image/png"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"git.kirsle.net/apps/doodle/draw"
|
||||
"git.kirsle.net/apps/doodle/level"
|
||||
)
|
||||
|
||||
// SaveLevel saves the level to disk.
|
||||
func (d *Doodle) SaveLevel() {
|
||||
m := level.Level{
|
||||
Version: 1,
|
||||
Title: "Alpha",
|
||||
Author: os.Getenv("USER"),
|
||||
Width: d.width,
|
||||
Height: d.height,
|
||||
Palette: []level.Palette{
|
||||
level.Palette{
|
||||
Color: "#000000",
|
||||
Solid: true,
|
||||
},
|
||||
},
|
||||
Pixels: []level.Pixel{},
|
||||
}
|
||||
|
||||
for pixel := range d.canvas {
|
||||
for point := range draw.Line(pixel.x, pixel.y, pixel.dx, pixel.dy) {
|
||||
m.Pixels = append(m.Pixels, level.Pixel{
|
||||
X: point.X,
|
||||
Y: point.Y,
|
||||
Palette: 0,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
json, err := m.ToJSON()
|
||||
if err != nil {
|
||||
log.Error("SaveLevel error: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
filename := fmt.Sprintf("./map-%s.json",
|
||||
time.Now().Format("2006-01-02T15-04-05"),
|
||||
)
|
||||
err = ioutil.WriteFile(filename, json, 0644)
|
||||
if err != nil {
|
||||
log.Error("Create map file error: %s", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// LoadLevel loads a map from JSON.
|
||||
func (d *Doodle) LoadLevel(filename string) error {
|
||||
log.Info("Loading level from file: %s", filename)
|
||||
pixelHistory = []Pixel{}
|
||||
d.canvas = Grid{}
|
||||
|
||||
m, err := level.LoadJSON(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, point := range m.Pixels {
|
||||
pixel := Pixel{
|
||||
start: true,
|
||||
x: point.X,
|
||||
y: point.Y,
|
||||
dx: point.X,
|
||||
dy: point.Y,
|
||||
}
|
||||
pixelHistory = append(pixelHistory, pixel)
|
||||
d.canvas[pixel] = nil
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Screenshot saves the level canvas to disk as a PNG image.
|
||||
func (d *Doodle) Screenshot() {
|
||||
screenshot := image.NewRGBA(image.Rect(0, 0, int(d.width), int(d.height)))
|
||||
|
||||
// White-out the image.
|
||||
for x := 0; x < int(d.width); x++ {
|
||||
for y := 0; y < int(d.height); y++ {
|
||||
screenshot.Set(x, y, image.White)
|
||||
}
|
||||
}
|
||||
|
||||
// Fill in the dots we drew.
|
||||
for pixel := range d.canvas {
|
||||
// A line or a dot?
|
||||
if pixel.x == pixel.dx && pixel.y == pixel.dy {
|
||||
screenshot.Set(int(pixel.x), int(pixel.y), image.Black)
|
||||
} else {
|
||||
for point := range draw.Line(pixel.x, pixel.y, pixel.dx, pixel.dy) {
|
||||
screenshot.Set(int(point.X), int(point.Y), image.Black)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create the screenshot directory.
|
||||
if _, err := os.Stat("./screenshots"); os.IsNotExist(err) {
|
||||
log.Info("Creating directory: ./screenshots")
|
||||
err = os.Mkdir("./screenshots", 0755)
|
||||
if err != nil {
|
||||
log.Error("Can't create ./screenshots: %s", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
filename := fmt.Sprintf("./screenshots/screenshot-%s.png",
|
||||
time.Now().Format("2006-01-02T15-04-05"),
|
||||
)
|
||||
fh, err := os.Create(filename)
|
||||
if err != nil {
|
||||
log.Error(err.Error())
|
||||
return
|
||||
}
|
||||
defer fh.Close()
|
||||
|
||||
if err := png.Encode(fh, screenshot); err != nil {
|
||||
log.Error(err.Error())
|
||||
return
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user