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",
|
||||
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
|
||||
}
|
||||
|
|
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 {
|
||||
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,
|
||||
|
|