Add simple walk animation from opengameart

main
Sean Hickey 2022-10-13 14:37:25 -07:00
parent f10ffa739d
commit 9de48f0bab
12 changed files with 348 additions and 203 deletions

View File

@ -0,0 +1,23 @@
# A Platformer in the Forest
These sprites are from opengameart.org:
https://opengameart.org/content/a-platformer-in-the-forest
They were created by Buch:
http://blog-buch.rhcloud.com
They are licensed as CC0 so no attribution is required, but I'd still like to give them credit.
We're using this temporarily to test sprites and walk cycles before creating our own art.
## Notes
Each sprite seems to be 32x32
* WALK: frames 1,2,3,4, cycle
* JUMP: frame 5 for "jump preparation", frame 6 for moving upwards, frame 7 for moving downards and frame 8 for landing
* HIT: frames 9,10,9
* SLASH: frames 11,12,13 (you might use them in the order 12,11,12,13 if you want an extra "preparation" frame before the actual slash)
* PUNCH: 14,12 (again, you might use them in the order 12,14,12)
* RUN: 15-18
* CLIMB: 19-22
* a single frame with character's back (frame 23)

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

@ -1,79 +1,55 @@
package entity
import (
"gitea.wisellama.rocks/Project-Ely/project-ely/internal/game/sprite"
"github.com/veandco/go-sdl2/sdl"
)
type Sprite interface {
Draw(section *sdl.Rect, placement *sdl.Rect) error
}
type penguin struct {
renderer *sdl.Renderer
position *sdl.Point
sprite Sprite
worldPosition *sdl.Point
animationStep int32
}
func NewPenguin(renderer *sdl.Renderer) *penguin {
sprite := sprite.GetSprite("assets/images/penguin.png")
pos := sdl.Point{X: 0, Y: 0}
position := sdl.Point{}
p := penguin{
renderer: renderer,
sprite: sprite,
position: &pos,
worldPosition: &position,
}
return &p
}
func (p *penguin) Draw() error {
// This is where on the screen to plop that sprite sheet chunk
placement := &sdl.Rect{X: p.position.X, Y: p.position.Y, W: 200, H: 200}
// Select the section of the spritesheet to draw
// TODO this will depend on animation cycle
section := &sdl.Rect{X: 0, Y: 0, W: 100, H: 100}
err := p.sprite.Draw(section, placement)
a := penguinAnimations[PENGUIN_WALK_RIGHT]
step := p.animationStep / 10
err := a.Draw(step, p.worldPosition)
if err != nil {
return err
}
return nil
}
func (p *penguin) SdlDrawNoTexture() error {
sdl.Do(func() {
p.renderer.SetDrawColor(0, 0, 255, 255)
rect := &sdl.Rect{X: p.position.X, Y: p.position.Y, W: 200, H: 200}
p.renderer.DrawRect(rect)
p.renderer.FillRect(rect)
})
p.animationStep += 1
return nil
}
func (p *penguin) MoveTo(point *sdl.Point) {
p.position = point
func (p *penguin) SetPosition(point *sdl.Point) {
p.worldPosition = point
}
func (p *penguin) MoveRight() {
p.position.X += 1
p.worldPosition.X += 1
}
func (p *penguin) MoveLeft() {
p.position.X -= 1
p.worldPosition.X -= 1
}
func (p *penguin) MoveUp() {
// (0,0) is the top left, so negative y moves up
p.position.Y -= 1
p.worldPosition.Y -= 1
}
func (p *penguin) MoveDown() {
// positive y moves down
p.position.Y += 1
p.worldPosition.Y += 1
}

View File

@ -0,0 +1,33 @@
package entity
import (
"gitea.wisellama.rocks/Project-Ely/project-ely/internal/game/sprite"
"github.com/veandco/go-sdl2/sdl"
)
type Animation interface {
Draw(frame int32, worldPosition *sdl.Point) error
}
var penguinAnimations map[string]Animation
const (
PENGUIN_WALK_RIGHT = "walk-right"
)
func DefinePenguinAnimations() {
filename := sprite.PLATFORMER_FOREST_CHARACTERS
var (
dimensions sdl.Point
offset sdl.Point
length int32
)
dimensions = sdl.Point{X: 32, Y: 32}
penguinAnimations = make(map[string]Animation)
offset = sdl.Point{X: 0, Y: 2}
length = 4
penguinAnimations[PENGUIN_WALK_RIGHT] = sprite.NewAnimation(filename, dimensions, offset, length)
}

View File

@ -0,0 +1,98 @@
package sprite
import (
"fmt"
"github.com/veandco/go-sdl2/sdl"
)
// animation defines a specific for an entity that references a sequence of sections of a sprite sheet.
// For example, walking to the left could be defined by 4 subsections of a sprite sheet.
type animation struct {
spritesheet *spritesheet
dimensions sdl.Point
offset sdl.Point
length int32
}
func NewAnimation(
filename string,
dimensions sdl.Point,
offset sdl.Point,
length int32,
) *animation {
spritesheet := GetSpritesheet(filename)
a := animation{
spritesheet: spritesheet,
dimensions: dimensions,
offset: offset,
length: length,
}
return &a
}
func (a *animation) Draw(frame int32, worldPosition *sdl.Point) error {
width := a.dimensions.X
height := a.dimensions.Y
base := sdl.Point{
X: width * a.offset.X,
Y: height * a.offset.Y,
}
// Assuming all frames are horizontal going left to right
f := frame % a.length
section := sdl.Rect{
X: base.X + f*width,
Y: base.Y,
W: width,
H: height,
}
err := a.checkBounds(&section)
if err != nil {
return err
}
// TODO convert to window position eventually
placement := sdl.Rect{
X: worldPosition.X,
Y: worldPosition.Y,
W: width,
H: height,
}
err = a.spritesheet.Draw(&section, &placement)
if err != nil {
return err
}
return nil
}
func (a *animation) checkBounds(section *sdl.Rect) error {
width := a.spritesheet.surface.W
height := a.spritesheet.surface.H
outOfBounds := false
if section.X < 0 {
outOfBounds = true
}
if section.Y < 0 {
outOfBounds = true
}
if section.X+section.W > width {
outOfBounds = true
}
if section.Y+section.H > height {
outOfBounds = true
}
if outOfBounds {
return fmt.Errorf("draw section was out of bounds - section: %v, image: %v", *section, a.spritesheet.surface.Bounds())
}
return nil
}

View File

@ -1,84 +0,0 @@
package sprite
import (
"fmt"
"log"
"github.com/veandco/go-sdl2/img"
"github.com/veandco/go-sdl2/sdl"
)
type sprite struct {
filename string
renderer *sdl.Renderer
image *sdl.Surface // the original png file
spriteSheet *sdl.Texture // the SDL texture created from the image
}
func NewSprite(renderer *sdl.Renderer, filename string) (*sprite, error) {
var err error
// Load the image file
var image *sdl.Surface
sdl.Do(func() {
image, err = img.Load(filename)
})
if err != nil {
err = fmt.Errorf("failed to load image: %w", err)
return nil, err
}
// Create the sprite sheet texture from the image
var spriteSheet *sdl.Texture
sdl.Do(func() {
spriteSheet, err = renderer.CreateTextureFromSurface(image)
})
if err != nil {
err = fmt.Errorf("failed to create texture: %w", err)
return nil, err
}
s := sprite{
filename: filename,
renderer: renderer,
image: image,
spriteSheet: spriteSheet,
}
return &s, nil
}
func (s *sprite) Cleanup() {
// Clean up image
defer func() {
if s.image != nil {
sdl.Do(func() {
s.image.Free()
})
}
}()
// Clean up spritesheet
defer func() {
if s.spriteSheet != nil {
sdl.Do(func() {
err := s.spriteSheet.Destroy()
if err != nil {
log.Printf("error destroying spritesheet %v: %v\n", s.filename, err)
}
})
}
}()
}
func (s *sprite) Draw(section *sdl.Rect, placement *sdl.Rect) error {
var err error
sdl.Do(func() {
err = s.renderer.Copy(s.spriteSheet, section, placement)
})
if err != nil {
return err
}
return nil
}

View File

@ -1,80 +0,0 @@
package sprite
import (
"log"
"github.com/veandco/go-sdl2/sdl"
)
var spriteFileList []string = []string{
"assets/images/penguin.png",
}
type Sprite interface {
Draw(section *sdl.Rect, placement *sdl.Rect) error
Cleanup()
}
var SpriteCache map[string]Sprite
var DefaultSprite Sprite
func InitSpriteCache(renderer *sdl.Renderer) error {
var err error
DefaultSprite, err = createDefaultSprite(renderer)
if err != nil {
log.Printf("failed to create DefaultSprite: %v", err)
return err
}
SpriteCache = make(map[string]Sprite)
for _, filename := range spriteFileList {
s, err := NewSprite(renderer, filename)
if err != nil {
log.Printf("error creating sprite %v, using DefaultSprite: %v", filename, err)
SpriteCache[filename] = DefaultSprite
} else {
SpriteCache[filename] = s
}
}
return nil
}
func GetSprite(filename string) Sprite {
s, exists := SpriteCache[filename]
if !exists {
log.Printf("no sprite found for %v, using DefaultSprite", filename)
return DefaultSprite
}
return s
}
func CleanupSpriteCache() {
for _, v := range SpriteCache {
defer v.Cleanup()
}
}
func createDefaultSprite(renderer *sdl.Renderer) (*sprite, error) {
var err error
surface, err := sdl.CreateRGBSurface(0, 100, 100, 32, 0, 0, 0, 0)
if err != nil {
return nil, err
}
texture, err := renderer.CreateTextureFromSurface(surface)
if err != nil {
return nil, err
}
s := sprite{
renderer: renderer,
spriteSheet: texture,
}
return &s, nil
}

View File

@ -0,0 +1,92 @@
package sprite
import (
"fmt"
"log"
"github.com/veandco/go-sdl2/img"
"github.com/veandco/go-sdl2/sdl"
)
type spritesheet struct {
filename string
renderer *sdl.Renderer
surface *sdl.Surface // the original png file
texture *sdl.Texture // the SDL texture created from the image
}
func NewSprite(renderer *sdl.Renderer, filename string) (*spritesheet, error) {
var err error
// Load the surface file
var surface *sdl.Surface
sdl.Do(func() {
surface, err = img.Load(filename)
})
if err != nil {
err = fmt.Errorf("failed to load image: %w", err)
return nil, err
}
// Create the sprite sheet texture from the image
var texture *sdl.Texture
sdl.Do(func() {
texture, err = renderer.CreateTextureFromSurface(surface)
})
if err != nil {
err = fmt.Errorf("failed to create texture: %w", err)
return nil, err
}
s := spritesheet{
filename: filename,
renderer: renderer,
surface: surface,
texture: texture,
}
return &s, nil
}
func (s *spritesheet) Cleanup() {
// Clean up image
defer func() {
if s.surface != nil {
sdl.Do(func() {
s.surface.Free()
})
}
}()
// Clean up spritesheet
defer func() {
if s.texture != nil {
sdl.Do(func() {
err := s.texture.Destroy()
if err != nil {
log.Printf("error destroying spritesheet %v: %v\n", s.filename, err)
}
})
}
}()
}
func (s *spritesheet) Draw(section *sdl.Rect, placement *sdl.Rect) error {
var err error
sdl.Do(func() {
err = s.renderer.Copy(s.texture, section, placement)
})
if err != nil {
return err
}
return nil
}
func (s *spritesheet) Bounds() *sdl.Point {
p := sdl.Point{
X: s.surface.W,
Y: s.surface.H,
}
return &p
}

View File

@ -0,0 +1,85 @@
package sprite
import (
"log"
"github.com/veandco/go-sdl2/sdl"
)
const (
PENGUIN = "assets/images/penguin.png"
PLATFORMER_FOREST_CHARACTERS = "assets/images/a-platformer-in-the-forest/characters.png"
)
var fileList []string = []string{
PENGUIN,
PLATFORMER_FOREST_CHARACTERS,
}
var (
spriteCache map[string]*spritesheet
defaultSprite *spritesheet
)
func InitSpriteCache(renderer *sdl.Renderer) error {
var err error
defaultSprite, err = createDefaultSprite(renderer)
if err != nil {
log.Printf("failed to create DefaultSprite: %v", err)
return err
}
spriteCache = make(map[string]*spritesheet)
for _, filename := range fileList {
s, err := NewSprite(renderer, filename)
if err != nil {
log.Printf("error creating sprite %v, using DefaultSprite: %v", filename, err)
spriteCache[filename] = defaultSprite
} else {
spriteCache[filename] = s
}
}
return nil
}
func GetSpritesheet(filename string) *spritesheet {
s, exists := spriteCache[filename]
if !exists {
log.Printf("no sprite found for %v, using DefaultSprite", filename)
return defaultSprite
}
return s
}
func CleanupSpriteCache() {
for _, v := range spriteCache {
defer v.Cleanup()
}
}
// The DefaultSprite is just a 100x100 black square
func createDefaultSprite(renderer *sdl.Renderer) (*spritesheet, error) {
var err error
surface, err := sdl.CreateRGBSurface(0, 100, 100, 32, 0, 0, 0, 0)
if err != nil {
return nil, err
}
texture, err := renderer.CreateTextureFromSurface(surface)
if err != nil {
return nil, err
}
s := spritesheet{
renderer: renderer,
texture: texture,
surface: surface,
}
return &s, nil
}

View File

@ -86,9 +86,11 @@ func run(configMap gosimpleconf.ConfigMap) error {
}
defer sprite.CleanupSpriteCache()
entity.DefinePenguinAnimations()
penguin := entity.NewPenguin(renderer)
p2 := entity.NewPenguin(renderer)
p2.MoveTo(&sdl.Point{X: 100, Y: 100})
p2.SetPosition(&sdl.Point{X: 100, Y: 100})
keystates := make(map[sdl.Keycode]bool)