Chunker size to uint8 and Rectangular Doodads #84

Merged
kirsle merged 3 commits from file-format-optimization into master 2023-02-18 05:49:49 +00:00
25 changed files with 273 additions and 209 deletions
Showing only changes of commit ddcad27485 - Show all commits

View File

@ -70,23 +70,22 @@ def main(fast=False):
def install_deps(fast): def install_deps(fast):
"""Install system dependencies.""" """Install system dependencies."""
if fast: fast = " -y" if fast else ""
fast = " -y"
if shell("which rpm") == 0 and shell("which dnf") == 0: if shell("which rpm") == 0 and shell("which dnf") == 0:
# Fedora-like. # Fedora-like.
if shell("rpm -q {}".format(' '.join(dep_fedora))) != 0: if shell("rpm -q {}".format(' '.join(dep_fedora))) != 0:
must_shell("sudo dnf install {}{}".format(' '.join(dep_fedora)), fast) must_shell("sudo dnf install {}{}".format(' '.join(dep_fedora), fast))
elif shell("which brew") == 0: elif shell("which brew") == 0:
# MacOS, as Catalina has an apt command now?? # MacOS, as Catalina has an apt command now??
must_shell("brew install {} {}".format(' '.join(dep_macos)), fast) must_shell("brew install {} {}".format(' '.join(dep_macos), fast))
elif shell("which apt") == 0: elif shell("which apt") == 0:
# Debian-like. # Debian-like.
if shell("dpkg-query -l {}".format(' '.join(dep_debian))) != 0: if shell("dpkg-query -l {}".format(' '.join(dep_debian))) != 0:
must_shell("sudo apt update && sudo apt install {}{}".format(' '.join(dep_debian)), fast) must_shell("sudo apt update && sudo apt install {}{}".format(' '.join(dep_debian), fast))
elif shell("which pacman") == 0: elif shell("which pacman") == 0:
# Arch-like. # Arch-like.
must_shell("sudo pacman -S{} {}{}".format(fast, ' '.join(dep_arch))) must_shell("sudo pacman -S{} {}".format(fast, ' '.join(dep_arch)))
else: else:
print("Warning: didn't detect your package manager to install SDL2 and other dependencies") print("Warning: didn't detect your package manager to install SDL2 and other dependencies")
@ -152,7 +151,7 @@ def must_shell(cmd):
if __name__ == "__main__": if __name__ == "__main__":
parser = argparse.ArgumentParser("doodle bootstrap") parser = argparse.ArgumentParser("doodle bootstrap")
parser.add_argument("fast", "f", parser.add_argument("--fast", "-f",
action="store_true", action="store_true",
help="Run the script non-interactively (yes to your package manager, git clone over https)", help="Run the script non-interactively (yes to your package manager, git clone over https)",
) )

View File

@ -104,7 +104,9 @@ func imageToDrawing(c *cli.Context, chroma render.Color, inputFiles []string, ou
// Read the source images. Ensure they all have the same boundaries. // Read the source images. Ensure they all have the same boundaries.
var ( var (
imageBounds image.Point imageBounds image.Point
chunkSize int // the square shape for the Doodad chunk size chunkSize uint8 // the square shape for the Doodad chunk size
width int // dimensions of the incoming image
height int
images []image.Image images []image.Image
) )
@ -130,9 +132,9 @@ func imageToDrawing(c *cli.Context, chroma render.Color, inputFiles []string, ou
if i == 0 { if i == 0 {
imageBounds = imageSize imageBounds = imageSize
if imageSize.X > imageSize.Y { if imageSize.X > imageSize.Y {
chunkSize = imageSize.X width = imageSize.X
} else { } else {
chunkSize = imageSize.Y height = imageSize.Y
} }
} else if imageSize != imageBounds { } else if imageSize != imageBounds {
return cli.Exit("your source images are not all the same dimensions", 1) return cli.Exit("your source images are not all the same dimensions", 1)
@ -151,7 +153,7 @@ func imageToDrawing(c *cli.Context, chroma render.Color, inputFiles []string, ou
switch strings.ToLower(filepath.Ext(outputFile)) { switch strings.ToLower(filepath.Ext(outputFile)) {
case extDoodad: case extDoodad:
log.Info("Output is a Doodad file (chunk size %d): %s", chunkSize, outputFile) log.Info("Output is a Doodad file (chunk size %d): %s", chunkSize, outputFile)
doodad := doodads.New(chunkSize) doodad := doodads.New(width, height)
doodad.GameVersion = branding.Version doodad.GameVersion = branding.Version
doodad.Title = c.String("title") doodad.Title = c.String("title")
if doodad.Title == "" { if doodad.Title == "" {
@ -188,6 +190,9 @@ func imageToDrawing(c *cli.Context, chroma render.Color, inputFiles []string, ou
lvl := level.New() lvl := level.New()
lvl.GameVersion = branding.Version lvl.GameVersion = branding.Version
lvl.MaxWidth = int64(width)
lvl.MaxHeight = int64(height)
lvl.PageType = level.Bounded
lvl.Title = c.String("title") lvl.Title = c.String("title")
if lvl.Title == "" { if lvl.Title == "" {
lvl.Title = "Converted Level" lvl.Title = "Converted Level"
@ -288,7 +293,7 @@ func drawingToImage(c *cli.Context, chroma render.Color, inputFiles []string, ou
// //
// img: input image like a PNG // img: input image like a PNG
// chroma: transparent color // chroma: transparent color
func imageToChunker(img image.Image, chroma render.Color, palette *level.Palette, chunkSize int) (*level.Palette, *level.Chunker) { func imageToChunker(img image.Image, chroma render.Color, palette *level.Palette, chunkSize uint8) (*level.Palette, *level.Chunker) {
var ( var (
chunker = level.NewChunker(chunkSize) chunker = level.NewChunker(chunkSize)
bounds = img.Bounds() bounds = img.Bounds()

View File

@ -4,6 +4,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"git.kirsle.net/SketchyMaze/doodle/pkg/balance"
"git.kirsle.net/SketchyMaze/doodle/pkg/level" "git.kirsle.net/SketchyMaze/doodle/pkg/level"
"git.kirsle.net/SketchyMaze/doodle/pkg/log" "git.kirsle.net/SketchyMaze/doodle/pkg/log"
"git.kirsle.net/go/render" "git.kirsle.net/go/render"
@ -220,7 +221,13 @@ func editLevel(c *cli.Context, filename string) error {
// Handles the deep operation of re-copying the old level into a new level // Handles the deep operation of re-copying the old level into a new level
// at the new chunk size. // at the new chunk size.
func rechunkLevel(c *cli.Context, filename string, lvl *level.Level) error { func rechunkLevel(c *cli.Context, filename string, lvl *level.Level) error {
var chunkSize = c.Int("resize") var chunkSize = balance.ChunkSize
if v := c.Int("resize"); v != 0 {
if v > 255 {
return errors.New("chunk size must be a uint8 <= 255")
}
chunkSize = uint8(v)
}
log.Info("Resizing the level's chunk size.") log.Info("Resizing the level's chunk size.")
log.Info("Current chunk size: %d", lvl.Chunker.Size) log.Info("Current chunk size: %d", lvl.Chunker.Size)
log.Info("Target chunk size: %d", chunkSize) log.Info("Target chunk size: %d", chunkSize)

View File

@ -205,6 +205,7 @@ func showDoodad(c *cli.Context, filename string) error {
fmt.Printf(" Game version: %s\n", dd.GameVersion) fmt.Printf(" Game version: %s\n", dd.GameVersion)
fmt.Printf(" Doodad title: %s\n", dd.Title) fmt.Printf(" Doodad title: %s\n", dd.Title)
fmt.Printf(" Author: %s\n", dd.Author) fmt.Printf(" Author: %s\n", dd.Author)
fmt.Printf(" Dimensions: %s\n", dd.Size)
fmt.Printf(" Hitbox: %s\n", dd.Hitbox) fmt.Printf(" Hitbox: %s\n", dd.Hitbox)
fmt.Printf(" Locked: %+v\n", dd.Locked) fmt.Printf(" Locked: %+v\n", dd.Locked)
fmt.Printf(" Hidden: %+v\n", dd.Hidden) fmt.Printf(" Hidden: %+v\n", dd.Hidden)
@ -256,9 +257,12 @@ func showPalette(pal *level.Palette) {
} }
func showChunker(c *cli.Context, ch *level.Chunker) { func showChunker(c *cli.Context, ch *level.Chunker) {
var worldSize = ch.WorldSize() var (
var width = worldSize.W - worldSize.X worldSize = ch.WorldSize()
var height = worldSize.H - worldSize.Y chunkSize = int(ch.Size)
width = worldSize.W - worldSize.X
height = worldSize.H - worldSize.Y
)
fmt.Println("Chunks:") fmt.Println("Chunks:")
fmt.Printf(" Pixels Per Chunk: %d^2\n", ch.Size) fmt.Printf(" Pixels Per Chunk: %d^2\n", ch.Size)
fmt.Printf(" Number Generated: %d\n", len(ch.Chunks)) fmt.Printf(" Number Generated: %d\n", len(ch.Chunks))
@ -277,10 +281,10 @@ func showChunker(c *cli.Context, ch *level.Chunker) {
fmt.Printf(" - Coord: %s\n", point) fmt.Printf(" - Coord: %s\n", point)
fmt.Printf(" Type: %s\n", chunkTypeToName(chunk.Type)) fmt.Printf(" Type: %s\n", chunkTypeToName(chunk.Type))
fmt.Printf(" Range: (%d,%d) ... (%d,%d)\n", fmt.Printf(" Range: (%d,%d) ... (%d,%d)\n",
int(point.X)*ch.Size, int(point.X)*chunkSize,
int(point.Y)*ch.Size, int(point.Y)*chunkSize,
(int(point.X)*ch.Size)+ch.Size, (int(point.X)*chunkSize)+chunkSize,
(int(point.Y)*ch.Size)+ch.Size, (int(point.Y)*chunkSize)+chunkSize,
) )
} }
} else { } else {

View File

@ -55,7 +55,7 @@ var (
FollowPlayerFirstTicks uint64 = 60 FollowPlayerFirstTicks uint64 = 60
// Default chunk size for canvases. // Default chunk size for canvases.
ChunkSize = 128 ChunkSize uint8 = 128
// Default size for a new Doodad. // Default size for a new Doodad.
DoodadSize = 100 DoodadSize = 100

View File

@ -14,6 +14,7 @@ type Doodad struct {
Filename string `json:"-"` // used internally, not saved in json Filename string `json:"-"` // used internally, not saved in json
Hidden bool `json:"hidden,omitempty"` Hidden bool `json:"hidden,omitempty"`
Palette *level.Palette `json:"palette"` Palette *level.Palette `json:"palette"`
Size render.Rect `json:"size"` // doodad dimensions
Script string `json:"script"` Script string `json:"script"`
Hitbox render.Rect `json:"hitbox"` Hitbox render.Rect `json:"hitbox"`
Layers []Layer `json:"layers"` Layers []Layer `json:"layers"`
@ -30,10 +31,41 @@ type Layer struct {
Chunker *level.Chunker `json:"chunks"` Chunker *level.Chunker `json:"chunks"`
} }
// New creates a new Doodad. /*
func New(size int) *Doodad { New creates a new Doodad.
if size == 0 {
size = balance.DoodadSize You can give it one or two values for dimensions:
- New(size int) creates a square doodad (classic)
- New(width, height int) lets you have a different width x height.
*/
func New(dimensions ...int) *Doodad {
var (
// Defaults
size = balance.DoodadSize
chunkSize = balance.ChunkSize
width int
height int
)
switch len(dimensions) {
case 1:
size = dimensions[0]
case 2:
width, height = dimensions[0], dimensions[1]
}
// If no width/height, make it a classic square doodad.
if width+height == 0 {
width = size
height = size
}
// Pick an optimal chunk size - if our doodad size
// is under 256 use only one chunk per layer by matching
// that size.
if size <= 255 {
chunkSize = uint8(size)
} }
return &Doodad{ return &Doodad{
@ -41,11 +73,12 @@ func New(size int) *Doodad {
Version: 1, Version: 1,
}, },
Palette: level.DefaultPalette(), Palette: level.DefaultPalette(),
Hitbox: render.NewRect(size, size), Hitbox: render.NewRect(width, height),
Size: render.NewRect(width, height),
Layers: []Layer{ Layers: []Layer{
{ {
Name: "main", Name: "main",
Chunker: level.NewChunker(size), Chunker: level.NewChunker(chunkSize),
}, },
}, },
Tags: map[string]string{}, Tags: map[string]string{},
@ -59,7 +92,7 @@ func New(size int) *Doodad {
// is optional - pass nil and a new blank chunker is created. // is optional - pass nil and a new blank chunker is created.
func (d *Doodad) AddLayer(name string, chunker *level.Chunker) Layer { func (d *Doodad) AddLayer(name string, chunker *level.Chunker) Layer {
if chunker == nil { if chunker == nil {
chunker = level.NewChunker(d.ChunkSize()) chunker = level.NewChunker(d.ChunkSize8())
} }
layer := Layer{ layer := Layer{
@ -107,12 +140,17 @@ func (d *Doodad) Tag(name string) string {
// ChunkSize returns the chunk size of the Doodad's first layer. // ChunkSize returns the chunk size of the Doodad's first layer.
func (d *Doodad) ChunkSize() int { func (d *Doodad) ChunkSize() int {
return int(d.Layers[0].Chunker.Size)
}
// ChunkSize8 returns the chunk size of the Doodad's first layer as its actual uint8 value.
func (d *Doodad) ChunkSize8() uint8 {
return d.Layers[0].Chunker.Size return d.Layers[0].Chunker.Size
} }
// Rect returns a rect of the ChunkSize for scaling a Canvas widget. // Rect returns a rect of the ChunkSize for scaling a Canvas widget.
func (d *Doodad) Rect() render.Rect { func (d *Doodad) Rect() render.Rect {
var size = d.ChunkSize() var size = int(d.ChunkSize())
return render.Rect{ return render.Rect{
W: size, W: size,
H: size, H: size,

View File

@ -1,6 +1,7 @@
package doodads package doodads
import ( import (
"git.kirsle.net/SketchyMaze/doodle/pkg/log"
"git.kirsle.net/go/render" "git.kirsle.net/go/render"
"github.com/google/uuid" "github.com/google/uuid"
) )
@ -24,6 +25,7 @@ func NewDrawing(id string, doodad *Doodad) *Drawing {
if id == "" { if id == "" {
id = uuid.Must(uuid.NewUUID()).String() id = uuid.Must(uuid.NewUUID()).String()
} }
log.Warn("NewDraging(%s): doodad.Rect=%s doodad.Size=%s", doodad.Title, doodad.Rect(), doodad.Size)
return &Drawing{ return &Drawing{
id: id, id: id,
Doodad: doodad, Doodad: doodad,

View File

@ -3,7 +3,6 @@ package doodle
import ( import (
"fmt" "fmt"
"path/filepath" "path/filepath"
"strconv"
"strings" "strings"
"time" "time"
@ -291,32 +290,16 @@ func (d *Doodle) NewMap() {
// NewDoodad loads a new Doodad in Edit Mode. // NewDoodad loads a new Doodad in Edit Mode.
// If size is zero, it prompts the user to select a size or accept the default size. // If size is zero, it prompts the user to select a size or accept the default size.
func (d *Doodle) NewDoodad(size int) { func (d *Doodle) NewDoodad(width, height int) {
if size == 0 { if width+height == 0 {
d.Prompt(fmt.Sprintf("Doodad size or %d>", balance.DoodadSize), func(answer string) { width = balance.DoodadSize
size := balance.DoodadSize height = width
if answer != "" {
i, err := strconv.Atoi(answer)
if err != nil {
d.FlashError("Error: Doodad size must be a number.")
return
}
size = i
}
// Recurse with the proper answer.
if size <= 0 {
d.FlashError("Error: Doodad size must be a positive number.")
}
d.NewDoodad(size)
})
return
} }
log.Info("Starting a new doodad") log.Info("Starting a new doodad")
scene := &EditorScene{ scene := &EditorScene{
DrawingType: enum.DoodadDrawing, DrawingType: enum.DoodadDrawing,
DoodadSize: size, DoodadSize: render.NewRect(width, height),
} }
d.Goto(scene) d.Goto(scene)
} }

View File

@ -32,7 +32,7 @@ type EditorScene struct {
DrawingType enum.DrawingType DrawingType enum.DrawingType
OpenFile bool OpenFile bool
Filename string Filename string
DoodadSize int DoodadSize render.Rect
RememberScrollPosition render.Point // Play mode remembers it for us RememberScrollPosition render.Point // Play mode remembers it for us
UI *EditorUI UI *EditorUI
@ -195,7 +195,7 @@ func (s *EditorScene) setupAsync(d *Doodle) error {
// No Doodad? // No Doodad?
if s.Doodad == nil { if s.Doodad == nil {
log.Debug("EditorScene.Setup: initializing a new Doodad") log.Debug("EditorScene.Setup: initializing a new Doodad")
s.Doodad = doodads.New(s.DoodadSize) s.Doodad = doodads.New(s.DoodadSize.W, s.DoodadSize.H)
s.UI.Canvas.LoadDoodad(s.Doodad) s.UI.Canvas.LoadDoodad(s.Doodad)
} }
@ -206,7 +206,7 @@ func (s *EditorScene) setupAsync(d *Doodle) error {
) )
// TODO: move inside the UI. Just an approximate position for now. // TODO: move inside the UI. Just an approximate position for now.
s.UI.Canvas.Resize(render.NewRect(s.DoodadSize, s.DoodadSize)) s.UI.Canvas.Resize(s.DoodadSize)
s.UI.Canvas.ScrollTo(render.Origin) s.UI.Canvas.ScrollTo(render.Origin)
s.UI.Canvas.Scrollable = false s.UI.Canvas.Scrollable = false
s.UI.Workspace.Compute(d.Engine) s.UI.Workspace.Compute(d.Engine)
@ -574,7 +574,7 @@ func (s *EditorScene) LoadDoodad(filename string) error {
s.DrawingType = enum.DoodadDrawing s.DrawingType = enum.DoodadDrawing
s.Doodad = doodad s.Doodad = doodad
s.DoodadSize = doodad.Layers[0].Chunker.Size s.DoodadSize = doodad.Size
s.UI.Canvas.LoadDoodad(s.Doodad) s.UI.Canvas.LoadDoodad(s.Doodad)
return nil return nil
} }

View File

@ -46,7 +46,7 @@ func (u *EditorUI) startDragActor(doodad *doodads.Doodad, actor *level.Actor) {
} }
// Size and scale this doodad according to the zoom level. // Size and scale this doodad according to the zoom level.
size := doodad.Rect() size := doodad.Size
size.W = u.Canvas.ZoomMultiply(size.W) size.W = u.Canvas.ZoomMultiply(size.W)
size.H = u.Canvas.ZoomMultiply(size.H) size.H = u.Canvas.ZoomMultiply(size.H)

View File

@ -52,12 +52,7 @@ func (u *EditorUI) SetupMenuBar(d *Doodle) *ui.MenuBar {
// File menu // File menu
fileMenu := menu.AddMenu("File") fileMenu := menu.AddMenu("File")
fileMenu.AddItemAccel("New level", "Ctrl-N", u.Scene.MenuNewLevel) fileMenu.AddItemAccel("New level", "Ctrl-N", u.Scene.MenuNewLevel)
fileMenu.AddItem("New doodad", func() { fileMenu.AddItem("New doodad", u.Scene.MenuNewDoodad)
u.Scene.ConfirmUnload(func() {
// New doodad size with prompt.
d.NewDoodad(0)
})
})
fileMenu.AddItemAccel("Save", "Ctrl-S", u.Scene.MenuSave(false)) fileMenu.AddItemAccel("Save", "Ctrl-S", u.Scene.MenuSave(false))
fileMenu.AddItemAccel("Save as...", "Shift-Ctrl-S", func() { fileMenu.AddItemAccel("Save as...", "Shift-Ctrl-S", func() {
d.Prompt("Save as filename>", func(answer string) { d.Prompt("Save as filename>", func(answer string) {
@ -285,6 +280,13 @@ func (s *EditorScene) MenuNewLevel() {
}) })
} }
func (s *EditorScene) MenuNewDoodad() {
s.ConfirmUnload(func() {
// New doodad size with prompt.
s.d.GotoNewDoodadMenu()
})
}
// File->Open, or Ctrl-O // File->Open, or Ctrl-O
func (s *EditorScene) MenuOpen() { func (s *EditorScene) MenuOpen() {
s.ConfirmUnload(func() { s.ConfirmUnload(func() {

View File

@ -83,11 +83,11 @@ func (u *EditorUI) setupPaletteFrame(window *ui.Window) *ui.Frame {
if u.Canvas != nil && u.Canvas.Palette != nil { if u.Canvas != nil && u.Canvas.Palette != nil {
for i, swatch := range u.Canvas.Palette.Swatches { for i, swatch := range u.Canvas.Palette.Swatches {
swatch := swatch swatch := swatch
var width = buttonSize var width = uint8(buttonSize) // TODO: dangerous - buttonSize must be small
// Drawing buttons in two-column mode? (default right-side palette layout) // Drawing buttons in two-column mode? (default right-side palette layout)
if twoColumn { if twoColumn {
width = buttonSize / 2 width /= 2
if row == nil || i%2 == 0 { if row == nil || i%2 == 0 {
row = ui.NewFrame(fmt.Sprintf("Swatch(%s) Button Frame", swatch.Name)) row = ui.NewFrame(fmt.Sprintf("Swatch(%s) Button Frame", swatch.Name))
frame.Pack(row, packConfig) frame.Pack(row, packConfig)
@ -101,7 +101,8 @@ func (u *EditorUI) setupPaletteFrame(window *ui.Window) *ui.Frame {
var ( var (
colorbox = uix.NewCanvas(width, false) colorbox = uix.NewCanvas(width, false)
chunker = level.NewChunker(width) chunker = level.NewChunker(width)
size = render.NewRect(width, width) iw = int(width)
size = render.NewRect(iw, iw)
) )
chunker.SetRect(size, swatch) chunker.SetRect(size, swatch)
colorbox.Resize(size) colorbox.Resize(size)

View File

@ -27,7 +27,7 @@ type Chunk struct {
// Values told to it from higher up, not stored in JSON. // Values told to it from higher up, not stored in JSON.
Point render.Point Point render.Point
Size int Size uint8
// Texture cache properties so we don't redraw pixel-by-pixel every frame. // Texture cache properties so we don't redraw pixel-by-pixel every frame.
uuid uuid.UUID uuid uuid.UUID
@ -157,14 +157,17 @@ func (c *Chunk) generateTexture(mask render.Color) (render.Texturer, error) {
// want a cached bitmap image that only generates itself once, and // want a cached bitmap image that only generates itself once, and
// again when marked dirty. // again when marked dirty.
func (c *Chunk) ToBitmap(mask render.Color) image.Image { func (c *Chunk) ToBitmap(mask render.Color) image.Image {
canvas := c.SizePositive() var (
imgSize := image.Rectangle{ size = int(c.Size)
Min: image.Point{}, canvas = c.SizePositive()
Max: image.Point{ imgSize = image.Rectangle{
X: c.Size, Min: image.Point{},
Y: c.Size, Max: image.Point{
}, X: size,
} Y: size,
},
}
)
if imgSize.Max.X == 0 { if imgSize.Max.X == 0 {
imgSize.Max.X = int(canvas.W) imgSize.Max.X = int(canvas.W)
@ -186,8 +189,8 @@ func (c *Chunk) ToBitmap(mask render.Color) image.Image {
// Pixel coordinate offset to map the Chunk World Position to the // Pixel coordinate offset to map the Chunk World Position to the
// smaller image boundaries. // smaller image boundaries.
pointOffset := render.Point{ pointOffset := render.Point{
X: c.Point.X * c.Size, X: c.Point.X * size,
Y: c.Point.Y * c.Size, Y: c.Point.Y * size,
} }
// Blot all the pixels onto it. // Blot all the pixels onto it.

View File

@ -20,8 +20,8 @@ type Chunker struct {
// Layer is optional for the caller, levels use only 0 and // Layer is optional for the caller, levels use only 0 and
// doodads use them for frames. When chunks are exported to // doodads use them for frames. When chunks are exported to
// zipfile the Layer keeps them from overlapping. // zipfile the Layer keeps them from overlapping.
Layer int `json:"-"` // internal use only Layer int `json:"-"` // internal use only
Size int `json:"size"` Size uint8 `json:"size"`
// A Zipfile reference for new-style levels and doodads which // A Zipfile reference for new-style levels and doodads which
// keep their chunks in external parts of a zip file. // keep their chunks in external parts of a zip file.
@ -51,7 +51,7 @@ type Chunker struct {
} }
// NewChunker creates a new chunk manager with a given chunk size. // NewChunker creates a new chunk manager with a given chunk size.
func NewChunker(size int) *Chunker { func NewChunker(size uint8) *Chunker {
return &Chunker{ return &Chunker{
Size: size, Size: size,
Chunks: ChunkMap{}, Chunks: ChunkMap{},
@ -186,10 +186,13 @@ func (c *Chunker) IterCachedChunks() <-chan *Chunk {
func (c *Chunker) IterViewportChunks(viewport render.Rect) <-chan render.Point { func (c *Chunker) IterViewportChunks(viewport render.Rect) <-chan render.Point {
pipe := make(chan render.Point) pipe := make(chan render.Point)
go func() { go func() {
sent := make(map[render.Point]interface{}) var (
sent = make(map[render.Point]interface{})
size = int(c.Size)
)
for x := viewport.X; x < viewport.W; x += (c.Size / 4) { for x := viewport.X; x < viewport.W; x += (size / 4) {
for y := viewport.Y; y < viewport.H; y += (c.Size / 4) { for y := viewport.Y; y < viewport.H; y += (size / 4) {
// Constrain this chunksize step to a point within the bounds // Constrain this chunksize step to a point within the bounds
// of the viewport. This can yield partial chunks on the edges // of the viewport. This can yield partial chunks on the edges
@ -243,12 +246,16 @@ func (c *Chunker) IterPixels() <-chan Pixel {
// manage: the lowest pixels from the lowest chunks to the highest pixels of // manage: the lowest pixels from the lowest chunks to the highest pixels of
// the highest chunks. // the highest chunks.
func (c *Chunker) WorldSize() render.Rect { func (c *Chunker) WorldSize() render.Rect {
chunkLowest, chunkHighest := c.Bounds() var (
size = int(c.Size)
chunkLowest, chunkHighest = c.Bounds()
)
return render.Rect{ return render.Rect{
X: chunkLowest.X * c.Size, X: chunkLowest.X * size,
Y: chunkLowest.Y * c.Size, Y: chunkLowest.Y * size,
W: (chunkHighest.X * c.Size) + (c.Size - 1), W: (chunkHighest.X * size) + (size - 1),
H: (chunkHighest.Y * c.Size) + (c.Size - 1), H: (chunkHighest.Y * size) + (size - 1),
} }
} }

View File

@ -44,7 +44,7 @@ func GiantScreenshot(lvl *level.Level) (image.Image, error) {
// How big will our image be? // How big will our image be?
var ( var (
size = lvl.Chunker.WorldSizePositive() size = lvl.Chunker.WorldSizePositive()
chunkSize = lvl.Chunker.Size chunkSize = int(lvl.Chunker.Size)
chunkLow, chunkHigh = lvl.Chunker.Bounds() chunkLow, chunkHigh = lvl.Chunker.Bounds()
worldSize = render.Rect{ worldSize = render.Rect{
X: chunkLow.X, X: chunkLow.X,

View File

@ -24,6 +24,7 @@ on the MainScene or elsewhere as wanted.
type MenuScene struct { type MenuScene struct {
// Configuration. // Configuration.
StartupMenu string StartupMenu string
NewDoodad bool
Supervisor *ui.Supervisor Supervisor *ui.Supervisor
@ -60,6 +61,17 @@ func (d *Doodle) GotoNewMenu() {
d.Goto(scene) d.Goto(scene)
} }
// GotoNewDoodadMenu loads the MenuScene and shows the "New" window,
// but selected on the Doodad tab by default.
func (d *Doodle) GotoNewDoodadMenu() {
log.Info("Loading the MenuScene to the New window")
scene := &MenuScene{
StartupMenu: "new",
NewDoodad: true,
}
d.Goto(scene)
}
// GotoLoadMenu loads the MenuScene and shows the "Load" window. // GotoLoadMenu loads the MenuScene and shows the "Load" window.
func (d *Doodle) GotoLoadMenu() { func (d *Doodle) GotoLoadMenu() {
log.Info("Loading the MenuScene to the Load window for Edit Mode") log.Info("Loading the MenuScene to the Load window for Edit Mode")
@ -152,6 +164,7 @@ func (s *MenuScene) setupNewWindow(d *Doodle) error {
window := windows.NewAddEditLevel(windows.AddEditLevel{ window := windows.NewAddEditLevel(windows.AddEditLevel{
Supervisor: s.Supervisor, Supervisor: s.Supervisor,
Engine: d.Engine, Engine: d.Engine,
NewDoodad: s.NewDoodad,
OnChangePageTypeAndWallpaper: func(pageType level.PageType, wallpaper string) { OnChangePageTypeAndWallpaper: func(pageType level.PageType, wallpaper string) {
log.Info("OnChangePageTypeAndWallpaper called: %+v, %+v", pageType, wallpaper) log.Info("OnChangePageTypeAndWallpaper called: %+v, %+v", pageType, wallpaper)
s.canvas.Destroy() // clean up old textures s.canvas.Destroy() // clean up old textures
@ -163,8 +176,8 @@ func (s *MenuScene) setupNewWindow(d *Doodle) error {
Level: lvl, Level: lvl,
}) })
}, },
OnCreateNewDoodad: func(size int) { OnCreateNewDoodad: func(width, height int) {
d.NewDoodad(size) d.NewDoodad(width, height)
}, },
OnCancel: func() { OnCancel: func() {
d.Goto(&MainScene{}) d.Goto(&MainScene{})

View File

@ -69,7 +69,7 @@ func (s *PlayScene) computeInventory() {
continue continue
} }
canvas := uix.NewCanvas(doodad.ChunkSize(), false) canvas := uix.NewCanvas(doodad.ChunkSize8(), false)
canvas.SetBackground(render.RGBA(1, 0, 0, 0)) canvas.SetBackground(render.RGBA(1, 0, 0, 0))
canvas.LoadDoodad(doodad) canvas.LoadDoodad(doodad)
canvas.Resize(render.NewRect( canvas.Resize(render.NewRect(

View File

@ -471,10 +471,10 @@ func (s *PlayScene) installPlayerDoodad(filename string, spawn render.Point, cen
// Center the player within the box of the doodad, for the Start Flag especially. // Center the player within the box of the doodad, for the Start Flag especially.
if !centerIn.IsZero() { if !centerIn.IsZero() {
spawn = render.NewPoint( spawn = render.NewPoint(
spawn.X+(centerIn.W/2)-(player.Layers[0].Chunker.Size/2), spawn.X+(centerIn.W/2)-(player.ChunkSize()/2),
// Y: the bottom of the flag, 4 pixels from the floor. // Y: the bottom of the flag, 4 pixels from the floor.
spawn.Y+centerIn.H-4-(player.Layers[0].Chunker.Size), spawn.Y+centerIn.H-4-(player.ChunkSize()),
) )
} else if spawn.IsZero() && !s.SpawnPoint.IsZero() { } else if spawn.IsZero() && !s.SpawnPoint.IsZero() {
spawn = s.SpawnPoint spawn = s.SpawnPoint

View File

@ -19,11 +19,11 @@ import (
// Actor is an object that marries together the three things that make a // Actor is an object that marries together the three things that make a
// Doodad instance "tick" while inside a Canvas: // Doodad instance "tick" while inside a Canvas:
// //
// - uix.Actor is a doodads.Drawing so it fulfills doodads.Actor to be a // - uix.Actor is a doodads.Drawing so it fulfills doodads.Actor to be a
// dynamic object during gameplay. // dynamic object during gameplay.
// - It has a pointer to the level.Actor indicating its static level data // - It has a pointer to the level.Actor indicating its static level data
// as defined in the map: its spawn coordinate and configuration. // as defined in the map: its spawn coordinate and configuration.
// - A uix.Canvas that can present the actor's graphics to the screen. // - A uix.Canvas that can present the actor's graphics to the screen.
type Actor struct { type Actor struct {
Drawing *doodads.Drawing Drawing *doodads.Drawing
Actor *level.Actor Actor *level.Actor
@ -67,8 +67,8 @@ func NewActor(id string, levelActor *level.Actor, doodad *doodads.Doodad) *Actor
id = uuid.Must(uuid.NewUUID()).String() id = uuid.Must(uuid.NewUUID()).String()
} }
size := doodad.Layers[0].Chunker.Size size := doodad.ChunkSize()
can := NewCanvas(int(size), false) can := NewCanvas(uint8(size), false)
can.Name = id can.Name = id
// TODO: if the Background is render.Invisible it gets defaulted to // TODO: if the Background is render.Invisible it gets defaulted to
@ -76,7 +76,7 @@ func NewActor(id string, levelActor *level.Actor, doodad *doodads.Doodad) *Actor
can.SetBackground(render.RGBA(0, 0, 1, 0)) can.SetBackground(render.RGBA(0, 0, 1, 0))
can.LoadDoodad(doodad) can.LoadDoodad(doodad)
can.Resize(render.NewRect(size, size)) can.Resize(doodad.Size)
actor := &Actor{ actor := &Actor{
Drawing: doodads.NewDrawing(id, doodad), Drawing: doodads.NewDrawing(id, doodad),

View File

@ -132,15 +132,17 @@ type Canvas struct {
// NewCanvas initializes a Canvas widget. // NewCanvas initializes a Canvas widget.
// //
// size is the Chunker size (uint8)
//
// If editable is true, Scrollable is also set to true, which means the arrow // If editable is true, Scrollable is also set to true, which means the arrow
// keys will scroll the canvas viewport which is desirable in Edit Mode. // keys will scroll the canvas viewport which is desirable in Edit Mode.
func NewCanvas(size int, editable bool) *Canvas { func NewCanvas(size uint8, editable bool) *Canvas {
w := &Canvas{ w := &Canvas{
Editable: editable, Editable: editable,
Scrollable: editable, Scrollable: editable,
Palette: level.NewPalette(), Palette: level.NewPalette(),
BrushSize: 1, BrushSize: 1,
chunks: level.NewChunker(size), chunks: level.NewChunker(uint8(size)),
actors: make([]*Actor, 0), actors: make([]*Actor, 0),
wallpaper: &Wallpaper{}, wallpaper: &Wallpaper{},
@ -372,7 +374,7 @@ func (w *Canvas) ViewportRelative() render.Rect {
// levels under control. // levels under control.
func (w *Canvas) LoadingViewport() render.Rect { func (w *Canvas) LoadingViewport() render.Rect {
var ( var (
chunkSize int chunkSize uint8
vp = w.Viewport() vp = w.Viewport()
margin = balance.LoadingViewportMarginChunks margin = balance.LoadingViewportMarginChunks
) )
@ -381,17 +383,18 @@ func (w *Canvas) LoadingViewport() render.Rect {
if w.level != nil { if w.level != nil {
chunkSize = w.level.Chunker.Size chunkSize = w.level.Chunker.Size
} else if w.doodad != nil { } else if w.doodad != nil {
chunkSize = w.doodad.ChunkSize() chunkSize = w.doodad.ChunkSize8()
} else { } else {
chunkSize = balance.ChunkSize chunkSize = balance.ChunkSize
log.Error("Canvas.LoadingViewport: no drawing to get chunk size from, default to %d", chunkSize) log.Error("Canvas.LoadingViewport: no drawing to get chunk size from, default to %d", chunkSize)
} }
var size = int(chunkSize)
return render.Rect{ return render.Rect{
X: vp.X - chunkSize*margin.X, X: vp.X - size*margin.X,
Y: vp.Y - chunkSize*margin.Y, Y: vp.Y - size*margin.Y,
W: vp.W + chunkSize*margin.X, W: vp.W + size*margin.X,
H: vp.H + chunkSize*margin.Y, H: vp.H + size*margin.Y,
} }
} }

View File

@ -220,7 +220,7 @@ func (w *Canvas) drawActors(e render.Engine, p render.Point) {
// Hitting the left edge. Cap the X coord and shrink the width. // Hitting the left edge. Cap the X coord and shrink the width.
delta := p.X - drawAt.X // positive number delta := p.X - drawAt.X // positive number
drawAt.X = p.X drawAt.X = p.X
// scrollTo.X -= delta / 2 // TODO // scrollTo.X -= delta // TODO
resizeTo.W -= delta resizeTo.W -= delta
} }

View File

@ -121,9 +121,10 @@ func (w *Canvas) Present(e render.Engine, p render.Point) {
src.H = S.H src.H = S.H
} }
var size = int(chunk.Size)
dst := render.Rect{ dst := render.Rect{
X: p.X + w.Scroll.X + w.BoxThickness(1) + w.ZoomMultiply(coord.X*chunk.Size), X: p.X + w.Scroll.X + w.BoxThickness(1) + w.ZoomMultiply(coord.X*size),
Y: p.Y + w.Scroll.Y + w.BoxThickness(1) + w.ZoomMultiply(coord.Y*chunk.Size), Y: p.Y + w.Scroll.Y + w.BoxThickness(1) + w.ZoomMultiply(coord.Y*size),
// src.W and src.H will be AT MOST the full width and height of // src.W and src.H will be AT MOST the full width and height of
// a Canvas widget. Subtract the scroll offset to keep it bounded // a Canvas widget. Subtract the scroll offset to keep it bounded

View File

@ -398,6 +398,14 @@ func (form Form) Create(into *ui.Frame, fields []Field) {
if row.OnSelect != nil { if row.OnSelect != nil {
row.OnSelect(selection.Value) row.OnSelect(selection.Value)
} }
// Update bound variables.
if v, ok := selection.Value.(int); ok && row.IntVariable != nil {
*row.IntVariable = v
}
if v, ok := selection.Value.(string); ok && row.TextVariable != nil {
*row.TextVariable = v
}
} }
return nil return nil
}) })

View File

@ -26,10 +26,13 @@ type AddEditLevel struct {
// Editing settings for an existing level? // Editing settings for an existing level?
EditLevel *level.Level EditLevel *level.Level
// Show the "New Doodad" tab by default?
NewDoodad bool
// Callback functions. // Callback functions.
OnChangePageTypeAndWallpaper func(pageType level.PageType, wallpaper string) OnChangePageTypeAndWallpaper func(pageType level.PageType, wallpaper string)
OnCreateNewLevel func(*level.Level) OnCreateNewLevel func(*level.Level)
OnCreateNewDoodad func(size int) OnCreateNewDoodad func(width, height int)
OnReload func() OnReload func()
OnCancel func() OnCancel func()
} }
@ -74,6 +77,11 @@ func NewAddEditLevel(config AddEditLevel) *ui.Window {
tabframe.Supervise(config.Supervisor) tabframe.Supervise(config.Supervisor)
// Show the doodad tab?
if config.NewDoodad {
tabframe.SetTab("doodad")
}
window.Hide() window.Hide()
return window return window
} }
@ -389,7 +397,8 @@ func (config AddEditLevel) setupLevelFrame(tf *ui.TabFrame) {
func (config AddEditLevel) setupDoodadFrame(tf *ui.TabFrame) { func (config AddEditLevel) setupDoodadFrame(tf *ui.TabFrame) {
// Default options. // Default options.
var ( var (
doodadSize = 64 doodadWidth = 64
doodadHeight = doodadWidth
) )
frame := tf.AddTab("doodad", ui.NewLabel(ui.Label{ frame := tf.AddTab("doodad", ui.NewLabel(ui.Label{
@ -401,110 +410,89 @@ func (config AddEditLevel) setupDoodadFrame(tf *ui.TabFrame) {
* Frame for selecting Page Type * Frame for selecting Page Type
******************/ ******************/
typeFrame := ui.NewFrame("Doodad Options Frame") var sizeOptions = []magicform.Option{
frame.Pack(typeFrame, ui.Pack{ {Label: "32", Value: 32},
Side: ui.N, {Label: "64", Value: 64},
FillX: true, {Label: "96", Value: 96},
}) {Label: "128", Value: 128},
{Label: "200", Value: 200},
label1 := ui.NewLabel(ui.Label{ {Label: "256", Value: 256},
Text: "Doodad sprite size (square):", {Label: "Custom...", Value: 0},
Font: balance.LabelFont,
})
typeFrame.Pack(label1, ui.Pack{
Side: ui.W,
})
// A selectbox to suggest some sizes or let the user enter a custom.
sizeBtn := ui.NewSelectBox("Size Select", ui.Label{
Font: ui.MenuFont,
})
typeFrame.Pack(sizeBtn, ui.Pack{
Side: ui.W,
Expand: true,
})
for _, row := range []struct {
Name string
Value int
}{
{"32", 32},
{"64", 64},
{"96", 96},
{"128", 128},
{"200", 200},
{"256", 256},
{"Custom...", 0},
} {
row := row
sizeBtn.AddItem(row.Name, row.Value, func() {})
} }
sizeBtn.SetValue(doodadSize) form := magicform.Form{
sizeBtn.Handle(ui.Change, func(ed ui.EventData) error { Supervisor: config.Supervisor,
if selection, ok := sizeBtn.GetValue(); ok { Engine: config.Engine,
if size, ok := selection.Value.(int); ok { Vertical: true,
if size == 0 { LabelWidth: 90,
shmem.Prompt("Enter a custom size for the doodad width and height: ", func(answer string) { }
form.Create(frame, []magicform.Field{
{
Label: "Width:",
Font: balance.LabelFont,
Type: magicform.Selectbox,
IntVariable: &doodadWidth,
Options: sizeOptions,
OnSelect: func(v interface{}) {
if v.(int) == 0 {
shmem.Prompt("Enter a custom size for the doodad width: ", func(answer string) {
if a, err := strconv.Atoi(answer); err == nil && a > 0 { if a, err := strconv.Atoi(answer); err == nil && a > 0 {
doodadSize = a doodadWidth = a
} else { } else {
shmem.FlashError("Doodad size should be a number greater than zero.") shmem.FlashError("Doodad size should be a number greater than zero.")
} }
}) })
} else {
doodadSize = size
} }
} },
} },
return nil {
Label: "Height:",
Font: balance.LabelFont,
Type: magicform.Selectbox,
IntVariable: &doodadHeight,
Options: sizeOptions,
OnSelect: func(v interface{}) {
if v.(int) == 0 {
shmem.Prompt("Enter a custom size for the doodad height: ", func(answer string) {
if a, err := strconv.Atoi(answer); err == nil && a > 0 {
doodadHeight = a
} else {
shmem.FlashError("Doodad size should be a number greater than zero.")
}
})
}
},
},
{
Buttons: []magicform.Field{
{
Label: "Continue",
Font: balance.UIFont,
ButtonStyle: &balance.ButtonPrimary,
OnClick: func() {
if config.OnCreateNewDoodad != nil {
config.OnCreateNewDoodad(doodadWidth, doodadHeight)
} else {
shmem.FlashError("OnCreateNewDoodad not attached")
}
},
},
{
Label: "Cancel",
Font: balance.UIFont,
ButtonStyle: &balance.ButtonPrimary,
OnClick: func() {
if config.OnCancel != nil {
config.OnCancel()
} else {
shmem.FlashError("OnCancel not attached")
}
},
},
},
},
}) })
sizeBtn.Supervise(config.Supervisor)
config.Supervisor.Add(sizeBtn)
/******************
* Confirm/cancel buttons.
******************/
bottomFrame := ui.NewFrame("Button Frame")
frame.Pack(bottomFrame, ui.Pack{
Side: ui.N,
FillX: true,
PadY: 8,
})
var buttons = []struct {
Label string
F func(ui.EventData) error
}{
{"Continue", func(ed ui.EventData) error {
if config.OnCreateNewDoodad != nil {
config.OnCreateNewDoodad(doodadSize)
} else {
shmem.FlashError("OnCreateNewDoodad not attached")
}
return nil
}},
{"Cancel", func(ed ui.EventData) error {
config.OnCancel()
return nil
}},
}
for _, t := range buttons {
btn := ui.NewButton(t.Label, ui.NewLabel(ui.Label{
Text: t.Label,
Font: balance.MenuFont,
}))
btn.Handle(ui.Click, t.F)
config.Supervisor.Add(btn)
bottomFrame.Pack(btn, ui.Pack{
Side: ui.W,
PadX: 4,
PadY: 8,
})
}
} }
// Creates the Game Rules frame for existing level (set difficulty, etc.) // Creates the Game Rules frame for existing level (set difficulty, etc.)

View File

@ -253,7 +253,7 @@ func makeDoodadTab(config DoodadDropper, frame *ui.Frame, size render.Rect, cate
lastColumn = 0 lastColumn = 0
} }
can := uix.NewCanvas(int(buttonSize), true) can := uix.NewCanvas(uint8(buttonSize), true) // TODO: dangerous - buttonSize must be small
can.Name = doodad.Title can.Name = doodad.Title
can.SetBackground(balance.DoodadButtonBackground) can.SetBackground(balance.DoodadButtonBackground)
can.LoadDoodad(doodad) can.LoadDoodad(doodad)