Add simple walk animation from opengameart
parent
f10ffa739d
commit
9de48f0bab
|
@ -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 |
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -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(§ion)
|
||||
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(§ion, &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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
4
main.go
4
main.go
|
@ -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)
|
||||
|
||||
|
|
Loading…
Reference in New Issue