Files
games/common/sprites/v1/sprite.go
2025-08-28 08:33:31 -04:00

148 lines
2.5 KiB
Go

package sprites
import (
"image"
"git.vezzani.net/ben/games/common/ux/v1"
"github.com/hajimehoshi/ebiten/v2"
)
var (
updateCount int
)
func Update() {
updateCount++
}
type animation struct {
RowNumber uint8
TickCount uint8
Scale float32
}
type subImager interface {
image.Image
SubImage(r image.Rectangle) image.Image
}
type Sprite struct {
width, height int
imgData subImager
animations map[string]animation
baseAnim *animation
currentAnim *animation
animationStartedAt int
animationStopAt int
animateOnce bool
}
func (s *Sprite) baseAnimation() animation {
if s.baseAnim != nil {
return *s.baseAnim
}
for i := range s.animations {
if s.animations[i].RowNumber == 0 {
a := s.animations[i]
s.baseAnim = &a
return a
}
}
return animation{}
}
func (s *Sprite) getAnimation() animation {
if s.currentAnim != nil {
return *s.currentAnim
}
return s.baseAnimation()
}
func (s *Sprite) Image(ops imageOptions) image.Image {
anim := s.getAnimation()
xOffset := updateCount % int(anim.TickCount) * s.width
yOffset := int(anim.RowNumber) * s.height
r := image.Rect(xOffset, yOffset, s.width, s.height).Intersect(s.imgData.Bounds())
return s.imgData.SubImage(r)
}
func (s *Sprite) Draw(screen *ebiten.Image, options ...ImageOption) {
if s.getAnimation().RowNumber == 0 {
}
ops := imageOptions{
scaleX: ux.Scale,
scaleY: ux.Scale,
}
for _, o := range options {
o(&ops)
}
geom := ebiten.GeoM{}
geom.Translate(ops.x, ops.y)
geom.Scale(ops.scaleX, ops.scaleY)
geom.Rotate(ops.rotateTheta)
screen.DrawImage(
ebiten.NewImageFromImage(
s.Image(ops),
),
&ebiten.DrawImageOptions{
GeoM: geom,
},
)
}
func (s *Sprite) StartAnimation(id string, once bool) {
if a, ok := s.animations[id]; ok {
s.currentAnim = &a
}
if once {
s.animateOnce = true
s.animationStopAt = updateCount + int(s.currentAnim.TickCount)
}
s.animationStartedAt = updateCount
}
func (s *Sprite) StopAnimation() {
s.currentAnim = nil
s.animationStartedAt = 0
s.animationStopAt = 0
s.animateOnce = false
}
type imageOptions struct {
x, y float64
scaleX, scaleY float64
rotateTheta float64
}
type ImageOption func(options *imageOptions)
func ToScale(x, y float64) ImageOption {
return func(o *imageOptions) {
o.scaleX *= x
o.scaleY *= y
}
}
func AtPosition(x, y float64) ImageOption {
return func(o *imageOptions) {
o.x, o.y = x, y
}
}
func Rotate(theta float64) ImageOption {
return func(o *imageOptions) {
o.rotateTheta = theta
}
}