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",
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 {
if c.NArg() != 2 {
if c.NArg() < 2 {
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"+
" Drawing file types: level, doodad",
1,
@ -57,15 +65,15 @@ func init() {
args := c.Args()
var (
inputFile = args[0]
inputType = strings.ToLower(filepath.Ext(inputFile))
outputFile = args[1]
inputFiles = args[:len(args)-1]
inputType = strings.ToLower(filepath.Ext(inputFiles[0]))
outputFile = args[len(args)-1]
outputType = strings.ToLower(filepath.Ext(outputFile))
)
if inputType == extPNG || inputType == extBMP {
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 nil
@ -73,7 +81,7 @@ func init() {
return cli.NewExitError("Image inputs can only output to Doodle drawings", 1)
} else if inputType == extLevel || inputType == extDoodad {
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 nil
@ -86,15 +94,22 @@ func init() {
}
}
func imageToDrawing(c *cli.Context, chroma render.Color, inputFile, outputFile string) error {
reader, err := os.Open(inputFile)
func imageToDrawing(c *cli.Context, chroma render.Color, inputFiles []string, outputFile string) error {
// Read the source images. Ensure they all have the same boundaries.
var (
imageBounds image.Point
chunkSize int // the square shape for the Doodad chunk size
images []image.Image
)
for i, filename := range inputFiles {
reader, err := os.Open(filename)
if err != nil {
return cli.NewExitError(err.Error(), 1)
}
img, format, err := image.Decode(reader)
log.Info("format: %s", format)
_ = img
log.Info("Parsed image %d of %d. Format: %s", i+1, len(inputFiles), format)
if err != nil {
return cli.NewExitError(err.Error(), 1)
}
@ -103,13 +118,22 @@ func imageToDrawing(c *cli.Context, chroma render.Color, inputFile, outputFile s
var (
bounds = img.Bounds()
imageSize = bounds.Size()
chunkSize int // the square shape for Doodad chunk 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.
switch strings.ToLower(filepath.Ext(outputFile)) {
@ -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)
doodad := doodads.New(chunkSize)
doodad.GameVersion = doodle.Version
doodad.Title = c.String("title")
if doodad.Title == "" {
doodad.Title = "Converted Doodad"
}
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)
if err != nil {
@ -127,12 +169,20 @@ func imageToDrawing(c *cli.Context, chroma render.Color, inputFile, outputFile s
}
case extLevel:
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.GameVersion = doodle.Version
lvl.Title = c.String("title")
if lvl.Title == "" {
lvl.Title = "Converted Level"
}
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)
if err != nil {
@ -145,9 +195,10 @@ func imageToDrawing(c *cli.Context, chroma render.Color, inputFile, outputFile s
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 chunker *level.Chunker
inputFile := inputFiles[0]
switch strings.ToLower(filepath.Ext(inputFile)) {
case extLevel:
@ -224,9 +275,10 @@ func drawingToImage(c *cli.Context, chroma render.Color, inputFile, outputFile s
//
// img: input image like a PNG
// 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 (
palette = level.NewPalette()
chunker = level.NewChunker(chunkSize)
bounds = img.Bounds()
)
@ -265,6 +317,7 @@ func imageToChunker(img image.Image, chroma render.Color, chunker *level.Chunker
for _, hex := range sortedColors {
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 {
if swatch.IsSparse() {
// 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",
point,
swatch.paletteIndex,