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.
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
12
dev-assets/doodads/buttons/README.md
Normal 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
|
||||||
|
```
|
8
dev-assets/doodads/buttons/button.js
Normal 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))
|
||||||
|
})
|
||||||
|
}
|
BIN
dev-assets/doodads/buttons/button1.png
Normal file
After Width: | Height: | Size: 769 B |
BIN
dev-assets/doodads/buttons/button2.png
Normal file
After Width: | Height: | Size: 728 B |
8
dev-assets/doodads/buttons/sticky.js
Normal 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))
|
||||||
|
})
|
||||||
|
}
|
BIN
dev-assets/doodads/buttons/sticky1.png
Normal file
After Width: | Height: | Size: 767 B |
BIN
dev-assets/doodads/buttons/sticky2.png
Normal file
After Width: | Height: | Size: 726 B |
BIN
dev-assets/doodads/buttons/typeB1.png
Normal file
After Width: | Height: | Size: 736 B |
BIN
dev-assets/doodads/buttons/typeB2.png
Normal file
After Width: | Height: | Size: 695 B |
15
dev-assets/doodads/doors/README.md
Normal 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
|
||||||
|
```
|
BIN
dev-assets/doodads/doors/blue-key.png
Normal file
After Width: | Height: | Size: 732 B |
BIN
dev-assets/doodads/doors/blue1.png
Normal file
After Width: | Height: | Size: 787 B |
BIN
dev-assets/doodads/doors/blue2.png
Normal file
After Width: | Height: | Size: 743 B |
BIN
dev-assets/doodads/doors/electric1.png
Normal file
After Width: | Height: | Size: 977 B |
BIN
dev-assets/doodads/doors/electric2.png
Normal file
After Width: | Height: | Size: 1.0 KiB |
BIN
dev-assets/doodads/doors/electric3.png
Normal file
After Width: | Height: | Size: 989 B |
BIN
dev-assets/doodads/doors/electric4.png
Normal file
After Width: | Height: | Size: 879 B |
BIN
dev-assets/doodads/doors/green-key.png
Normal file
After Width: | Height: | Size: 714 B |
BIN
dev-assets/doodads/doors/green1.png
Normal file
After Width: | Height: | Size: 734 B |
BIN
dev-assets/doodads/doors/green2.png
Normal file
After Width: | Height: | Size: 742 B |
BIN
dev-assets/doodads/doors/red-key.png
Normal file
After Width: | Height: | Size: 699 B |
BIN
dev-assets/doodads/doors/red1.png
Normal file
After Width: | Height: | Size: 739 B |
BIN
dev-assets/doodads/doors/red2.png
Normal file
After Width: | Height: | Size: 733 B |
BIN
dev-assets/doodads/doors/yellow-key.png
Normal file
After Width: | Height: | Size: 728 B |
BIN
dev-assets/doodads/doors/yellow1.png
Normal file
After Width: | Height: | Size: 778 B |
BIN
dev-assets/doodads/doors/yellow2.png
Normal file
After Width: | Height: | Size: 746 B |
35
dev-assets/doodads/palette.json
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
5
dev-assets/doodads/trapdoors/README.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
# Button Doodads
|
||||||
|
|
||||||
|
```bash
|
||||||
|
doodad convert -t "Trapdoor Down" down{1,2,3}.png trapdoor-down.doodad
|
||||||
|
```
|
BIN
dev-assets/doodads/trapdoors/down1.png
Normal file
After Width: | Height: | Size: 1020 B |
BIN
dev-assets/doodads/trapdoors/down2.png
Normal file
After Width: | Height: | Size: 970 B |
BIN
dev-assets/doodads/trapdoors/down3.png
Normal file
After Width: | Height: | Size: 1.0 KiB |
BIN
dev-assets/doodads/trapdoors/down4.png
Normal file
After Width: | Height: | Size: 859 B |
|
@ -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,
|
||||||
|
|