package gfx import ( "image" "github.com/hajimehoshi/ebiten/v2" ) type Sprite struct { image *ebiten.Image tileSize [2]int animationFrames []int animationFPS int effects []Effect loop bool } func LoadSpriteFromImage(image image.Image, tileSize [2]int) (*Sprite, error) { return &Sprite{ image: ebiten.NewImageFromImage(image), tileSize: tileSize, }, nil } func (s *Sprite) TileSize() (int, int) { return s.tileSize[0], s.tileSize[1] } func (s *Sprite) FrameCount() int { tw, th := s.TileSize() imgSize := s.image.Bounds().Size() return int(imgSize.X/tw) * int(imgSize.Y/th) } func (s *Sprite) GetFrame(index int) *ebiten.Image { tw, th := s.TileSize() imgSize := s.image.Bounds().Size() framesPerRow := int(imgSize.X / tw) row := index / framesPerRow col := index % framesPerRow x1 := col * tw y1 := row * th x2 := col*tw + tw y2 := row*th + th return s.image.SubImage(image.Rect(x1, y1, x2, y2)).(*ebiten.Image) } func (s *Sprite) GetAnimationFrame(time float32) *ebiten.Image { if s.FrameCount() == 0 { // fallback to using the entire image if there are no frames return s.image } // if no animation frames are set then just show first frame frameIndex := 0 if len(s.animationFrames) > 0 { if s.loop { frameIndex = s.animationFrames[int(time*float32(s.animationFPS))%len(s.animationFrames)] } else { // if loop is false then hold on last frame frameIndex = s.animationFrames[min(len(s.animationFrames)-1, int(time*float32(s.animationFPS))/len(s.animationFrames))] } } return s.GetFrame(frameIndex) } func (s *Sprite) ApplyEffects(time float32, op *ebiten.DrawImageOptions) (bool, error) { return ApplyEffects(s.effects, time, op) } func (s *Sprite) Draw(time float32, screen *ebiten.Image, op *ebiten.DrawImageOptions) (bool, error) { if op == nil { op = &ebiten.DrawImageOptions{} } frame := s.GetAnimationFrame(time) hasPlaying, err := s.ApplyEffects(time, op) if err != nil { return hasPlaying, err } for _, effect := range s.effects { switch effect := effect.(type) { case *ShaderEffect: if time < effect.Duration() { playing, err := effect.Draw(time, screen, frame, op) if err != nil { return false, err } return hasPlaying || playing, nil } } } screen.DrawImage(frame, op) return hasPlaying, nil }