-
Notifications
You must be signed in to change notification settings - Fork 0
/
spritesheet.go
133 lines (111 loc) · 2.77 KB
/
spritesheet.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
package spritesheet
import (
"bytes"
"errors"
"fmt"
"image"
"io"
"io/ioutil"
"math"
"os"
"strings"
"gopkg.in/yaml.v3"
)
// Sprite represents a single sprite within a sprite sheet.
type Sprite struct {
Name string
Row int
Col int
Sheet *SpriteSheet
}
// Rect returns the area where this sprite is in the sprite sheet.
func (s *Sprite) Rect() image.Rectangle {
p0 := image.Pt(s.Col*s.Sheet.Size, s.Row*s.Sheet.Size)
p1 := image.Pt(p0.X+s.Sheet.Size, p0.Y+s.Sheet.Size)
return image.Rectangle{Min: p0, Max: p1}
}
// SpriteSheet represents a sprite sheet config file loaded from YAML.
type SpriteSheet struct {
Rows, Cols int
Size int
Image string
Names []string `yaml:"sprites"`
}
// Sprites returns a map of all the sprites declared in the sprite sheet.
// The map keys are the sprite names.
func (ss *SpriteSheet) Sprites() map[string]*Sprite {
m := make(map[string]*Sprite)
for i, name := range ss.Names {
if name == "_" {
continue
}
m[name] = &Sprite{
Name: name,
Row: int(math.Floor(float64(i) / float64(ss.Cols))),
Col: i % ss.Cols,
Sheet: ss,
}
}
return m
}
// OpenAndRead reads and returns the sprite sheet config file at the given path.
func OpenAndRead(path string) (*SpriteSheet, error) {
f, err := os.Open(path)
if err != nil {
return nil, err
}
defer f.Close()
data, err := ioutil.ReadAll(f)
if err != nil {
return nil, err
}
return Read(bytes.NewReader(data))
}
// Read reads a sprite sheet config file, parses it, and returns it.
func Read(r io.Reader) (*SpriteSheet, error) {
sheet := &SpriteSheet{}
decoder := yaml.NewDecoder(r)
decoder.KnownFields(true)
if err := decoder.Decode(sheet); err != nil {
return nil, err
}
if sheet.Rows < 1 {
return nil, errors.New("rows must be at least 1")
} else if sheet.Cols < 1 {
return nil, errors.New("cols must be at least 1")
} else if sheet.Size < 1 {
return nil, errors.New("size must be at least 1")
} else if sheet.Image == "" {
return nil, errors.New("missing image field")
} else if sheet.Names == nil {
return nil, errors.New("missing sprites field")
} else if len(sheet.Names) > sheet.Cols*sheet.Rows {
return nil, fmt.Errorf(
"sprites field has too many entries (%d entries, max is %d)",
len(sheet.Names),
sheet.Cols*sheet.Rows,
)
}
// Check that all of the sprite names are unique
if len(sheet.Names) > 0 {
dupes := []string{}
names := make(map[string]struct{})
for _, name := range sheet.Names {
if name == "_" {
continue
}
if _, exists := names[name]; exists {
dupes = append(dupes, name)
} else {
names[name] = struct{}{}
}
}
if len(dupes) > 0 {
return nil, fmt.Errorf(
"sprite names must be unique (duplicated: %s)",
strings.Join(dupes, ", "),
)
}
}
return sheet, nil
}