1st Round of Doodad Sprites + Improve Doodad Tool

* Improve the `doodad convert` command to convert a series of input
  images into multiple Frames of a Doodad:
  `doodad convert frame1.png frame2.png frameN.png output.doodad`
* Add the initial round of dev-asset sprites for the default Doodads:
  * Button, Button-TypeB and Sticky Button
  * Red, Blue, Green and Yellow Locked Doors and Keys
  * Electric Door
  * Trapdoor Down
* Add dev-assets/palette.json that defines our default doodad color
  palette. Eventually the JSON will be used by the `doodad` tool to give
  the layers meaningful names.
This commit is contained in:
Noah 2019-04-17 00:02:41 -07:00
parent c70add17e4
commit 81cb3bd617
34 changed files with 172 additions and 36 deletions

View File

@ -35,11 +35,19 @@ func init() {
Usage: "chroma key color for transparency on input image files", Usage: "chroma key color for transparency on input image files",
Value: "#ffffff", Value: "#ffffff",
}, },
cli.StringFlag{
Name: "title, t",
Usage: "set the title of the level or doodad being created",
},
cli.StringFlag{
Name: "palette, p",
Usage: "use a palette JSON to define color swatch properties",
},
}, },
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
if c.NArg() != 2 { if c.NArg() < 2 {
return cli.NewExitError( return cli.NewExitError(
"Usage: doodad convert <input.png> <output.level>\n"+ "Usage: doodad convert <input.png...> <output.doodad>\n"+
" Image file types: png, bmp\n"+ " Image file types: png, bmp\n"+
" Drawing file types: level, doodad", " Drawing file types: level, doodad",
1, 1,
@ -57,15 +65,15 @@ func init() {
args := c.Args() args := c.Args()
var ( var (
inputFile = args[0] inputFiles = args[:len(args)-1]
inputType = strings.ToLower(filepath.Ext(inputFile)) inputType = strings.ToLower(filepath.Ext(inputFiles[0]))
outputFile = args[1] outputFile = args[len(args)-1]
outputType = strings.ToLower(filepath.Ext(outputFile)) outputType = strings.ToLower(filepath.Ext(outputFile))
) )
if inputType == extPNG || inputType == extBMP { if inputType == extPNG || inputType == extBMP {
if outputType == extLevel || outputType == extDoodad { if outputType == extLevel || outputType == extDoodad {
if err := imageToDrawing(c, chroma, inputFile, outputFile); err != nil { if err := imageToDrawing(c, chroma, inputFiles, outputFile); err != nil {
return cli.NewExitError(err.Error(), 1) return cli.NewExitError(err.Error(), 1)
} }
return nil return nil
@ -73,7 +81,7 @@ func init() {
return cli.NewExitError("Image inputs can only output to Doodle drawings", 1) return cli.NewExitError("Image inputs can only output to Doodle drawings", 1)
} else if inputType == extLevel || inputType == extDoodad { } else if inputType == extLevel || inputType == extDoodad {
if outputType == extPNG || outputType == extBMP { if outputType == extPNG || outputType == extBMP {
if err := drawingToImage(c, chroma, inputFile, outputFile); err != nil { if err := drawingToImage(c, chroma, inputFiles, outputFile); err != nil {
return cli.NewExitError(err.Error(), 1) return cli.NewExitError(err.Error(), 1)
} }
return nil return nil
@ -86,29 +94,45 @@ func init() {
} }
} }
func imageToDrawing(c *cli.Context, chroma render.Color, inputFile, outputFile string) error { func imageToDrawing(c *cli.Context, chroma render.Color, inputFiles []string, outputFile string) error {
reader, err := os.Open(inputFile) // Read the source images. Ensure they all have the same boundaries.
if err != nil {
return cli.NewExitError(err.Error(), 1)
}
img, format, err := image.Decode(reader)
log.Info("format: %s", format)
_ = img
if err != nil {
return cli.NewExitError(err.Error(), 1)
}
// Get the bounding box information of the source image.
var ( var (
bounds = img.Bounds() imageBounds image.Point
imageSize = bounds.Size() chunkSize int // the square shape for the Doodad chunk size
chunkSize int // the square shape for Doodad chunk size images []image.Image
) )
if imageSize.X > imageSize.Y {
chunkSize = imageSize.X for i, filename := range inputFiles {
} else { reader, err := os.Open(filename)
chunkSize = imageSize.Y if err != nil {
return cli.NewExitError(err.Error(), 1)
}
img, format, err := image.Decode(reader)
log.Info("Parsed image %d of %d. Format: %s", i+1, len(inputFiles), format)
if err != nil {
return cli.NewExitError(err.Error(), 1)
}
// Get the bounding box information of the source image.
var (
bounds = img.Bounds()
imageSize = bounds.Size()
)
// Validate all images are the same size.
if i == 0 {
imageBounds = imageSize
if imageSize.X > imageSize.Y {
chunkSize = imageSize.X
} else {
chunkSize = imageSize.Y
}
} else if imageSize != imageBounds {
return cli.NewExitError("your source images are not all the same dimensions", 1)
}
images = append(images, img)
} }
// Generate the output drawing file. // Generate the output drawing file.
@ -117,9 +141,27 @@ func imageToDrawing(c *cli.Context, chroma render.Color, inputFile, outputFile s
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(chunkSize)
doodad.GameVersion = doodle.Version doodad.GameVersion = doodle.Version
doodad.Title = "Converted Doodad" doodad.Title = c.String("title")
if doodad.Title == "" {
doodad.Title = "Converted Doodad"
}
doodad.Author = os.Getenv("USER") doodad.Author = os.Getenv("USER")
doodad.Palette = imageToChunker(img, chroma, doodad.Layers[0].Chunker)
// Write the first layer and gather its palette.
palette, layer0 := imageToChunker(images[0], chroma, chunkSize)
doodad.Palette = palette
doodad.Layers[0].Chunker = layer0
// Write any additional layers.
if len(images) > 1 {
for i, img := range images[1:] {
_, chunker := imageToChunker(img, chroma, chunkSize)
doodad.Layers = append(doodad.Layers, doodads.Layer{
Name: fmt.Sprintf("layer-%d", i+1),
Chunker: chunker,
})
}
}
err := doodad.WriteJSON(outputFile) err := doodad.WriteJSON(outputFile)
if err != nil { if err != nil {
@ -127,12 +169,20 @@ func imageToDrawing(c *cli.Context, chroma render.Color, inputFile, outputFile s
} }
case extLevel: case extLevel:
log.Info("Output is a Level file: %s", outputFile) log.Info("Output is a Level file: %s", outputFile)
if len(images) > 1 {
log.Warn("Notice: levels only support one layer so only your first image will be used")
}
lvl := level.New() lvl := level.New()
lvl.GameVersion = doodle.Version lvl.GameVersion = doodle.Version
lvl.Title = "Converted Level" lvl.Title = c.String("title")
if lvl.Title == "" {
lvl.Title = "Converted Level"
}
lvl.Author = os.Getenv("USER") lvl.Author = os.Getenv("USER")
lvl.Palette = imageToChunker(img, chroma, lvl.Chunker) palette, chunker := imageToChunker(images[0], chroma, lvl.Chunker.Size)
lvl.Palette = palette
lvl.Chunker = chunker
err := lvl.WriteJSON(outputFile) err := lvl.WriteJSON(outputFile)
if err != nil { if err != nil {
@ -145,9 +195,10 @@ func imageToDrawing(c *cli.Context, chroma render.Color, inputFile, outputFile s
return nil return nil
} }
func drawingToImage(c *cli.Context, chroma render.Color, inputFile, outputFile string) error { func drawingToImage(c *cli.Context, chroma render.Color, inputFiles []string, outputFile string) error {
var palette *level.Palette var palette *level.Palette
var chunker *level.Chunker var chunker *level.Chunker
inputFile := inputFiles[0]
switch strings.ToLower(filepath.Ext(inputFile)) { switch strings.ToLower(filepath.Ext(inputFile)) {
case extLevel: case extLevel:
@ -224,9 +275,10 @@ func drawingToImage(c *cli.Context, chroma render.Color, inputFile, outputFile s
// //
// 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, chunker *level.Chunker) *level.Palette { func imageToChunker(img image.Image, chroma render.Color, chunkSize int) (*level.Palette, *level.Chunker) {
var ( var (
palette = level.NewPalette() palette = level.NewPalette()
chunker = level.NewChunker(chunkSize)
bounds = img.Bounds() bounds = img.Bounds()
) )
@ -265,6 +317,7 @@ func imageToChunker(img image.Image, chroma render.Color, chunker *level.Chunker
for _, hex := range sortedColors { for _, hex := range sortedColors {
palette.Swatches = append(palette.Swatches, uniqueColor[hex]) palette.Swatches = append(palette.Swatches, uniqueColor[hex])
} }
palette.Inflate()
return palette return palette, chunker
} }

View File

@ -0,0 +1,12 @@
# Button Doodads
```bash
doodad convert -t "Sticky Button" sticky1.png sticky2.png sticky-button.doodad
doodad install-script sticky.js sticky-button.doodad
doodad convert -t "Button" button1.png button2.png button.doodad
doodad install-script button.js button.doodad
doodad convert -t "Button Type B" typeB1.png typeB2.png button-typeB.doodad
doodad install-script button.js button-typeB.doodad
```

View File

@ -0,0 +1,8 @@
function main() {
console.log("Sticky Button initialized!");
Events.OnCollide( function() {
console.log("Touched!");
Self.Canvas.SetBackground(RGBA(255, 153, 0, 153))
})
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 769 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 728 B

View File

@ -0,0 +1,8 @@
function main() {
console.log("Sticky Button initialized!");
Events.OnCollide( function() {
console.log("Touched!");
Self.Canvas.SetBackground(RGBA(255, 153, 0, 153))
})
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 767 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 726 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 736 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 695 B

View File

@ -0,0 +1,15 @@
# Button Doodads
```bash
doodad convert -t "Red Door" red1.png red2.png red-door.doodad
doodad convert -t "Blue Door" blue1.png blue2.png blue-door.doodad
doodad convert -t "Green Door" green1.png green2.png green-door.doodad
doodad convert -t "Yellow Door" yellow1.png yellow2.png yellow-door.doodad
doodad convert -t "Red Key" red-key.png red-key.doodad
doodad convert -t "Blue Key" blue-key.png blue-key.doodad
doodad convert -t "Green Key" green-key.png green-key.doodad
doodad convert -t "Yellow Key" yellow-key.png yellow-key.doodad
doodad convert -t "Electric Door" electric{1,2,3,4}.png electric-door.doodad
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 732 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 787 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 743 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 977 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 989 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 879 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 714 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 734 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 742 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 699 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 739 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 733 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 728 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 778 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 746 B

View File

@ -0,0 +1,35 @@
{
"#000000": {
"name": "black"
},
"#666666": {
"name": "dark-grey"
},
"#999999": {
"name": "grey"
},
"#CCCCCC": {
"name": "light-grey"
},
"#FF0000": {
"name": "red"
},
"#0099FF": {
"name": "light-blue"
},
"#0000FF": {
"name": "blue"
},
"#009900": {
"name": "green"
},
"#999900": {
"name": "gold"
},
"#4D391B": {
"name": "brown"
},
"#8B652C": {
"name": "light-brown"
}
}

View File

@ -0,0 +1,5 @@
# Button Doodads
```bash
doodad convert -t "Trapdoor Down" down{1,2,3}.png trapdoor-down.doodad
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 1020 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 970 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 859 B

View File

@ -22,7 +22,7 @@ func (a MapAccessor) Inflate(pal *Palette) error {
for point, swatch := range a { for point, swatch := range a {
if swatch.IsSparse() { if swatch.IsSparse() {
// Replace this with the correct swatch from the palette. // Replace this with the correct swatch from the palette.
if len(pal.Swatches) < swatch.paletteIndex { if swatch.paletteIndex >= len(pal.Swatches) {
return fmt.Errorf("MapAccessor.Inflate: swatch for point %s has paletteIndex %d but palette has only %d colors", return fmt.Errorf("MapAccessor.Inflate: swatch for point %s has paletteIndex %d but palette has only %d colors",
point, point,
swatch.paletteIndex, swatch.paletteIndex,