Add animated player sprite
This commit is contained in:
parent
aad5185f94
commit
843c254cec
|
@ -0,0 +1,45 @@
|
|||
package asset
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"image"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2"
|
||||
|
||||
_ "image/png"
|
||||
)
|
||||
|
||||
//go:embed image
|
||||
var FS embed.FS
|
||||
|
||||
const tileSize = 64
|
||||
|
||||
var ImgPlayer = LoadImage("image/player.png")
|
||||
|
||||
func LoadImage(p string) *ebiten.Image {
|
||||
f, err := FS.Open(p)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
baseImg, _, err := image.Decode(f)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return ebiten.NewImageFromImage(baseImg)
|
||||
}
|
||||
|
||||
func LoadBytes(p string) []byte {
|
||||
b, err := FS.ReadFile(p)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func FrameAt(img *ebiten.Image, x int, y int) *ebiten.Image {
|
||||
xPos, yPos := x*tileSize, y*tileSize
|
||||
return img.SubImage(image.Rect(xPos, yPos, xPos+tileSize, yPos+tileSize)).(*ebiten.Image)
|
||||
}
|
Binary file not shown.
Binary file not shown.
After Width: | Height: | Size: 3.1 KiB |
|
@ -4,6 +4,10 @@ import (
|
|||
"fmt"
|
||||
"image"
|
||||
"image/color"
|
||||
|
||||
"code.rocketnine.space/tslocum/boxbrawl/asset"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2"
|
||||
)
|
||||
|
||||
type PlayerAction int
|
||||
|
@ -18,7 +22,7 @@ const (
|
|||
type HitboxType int
|
||||
|
||||
const (
|
||||
HitboxInvalid HitboxType = iota
|
||||
HitboxNone HitboxType = iota
|
||||
HitboxNormal
|
||||
HitboxHurt
|
||||
)
|
||||
|
@ -26,22 +30,119 @@ const (
|
|||
type FrameData struct {
|
||||
T HitboxType
|
||||
R image.Rectangle
|
||||
S *ebiten.Image
|
||||
}
|
||||
|
||||
const PlayerSize = 20
|
||||
const PlayerSize = 16
|
||||
|
||||
const (
|
||||
PlayerHeight = 48
|
||||
PlayerWidth = 16
|
||||
)
|
||||
|
||||
// stdHit returns FrameData using a standard hitbox and the provided sprite.
|
||||
func stdHit(sprite *ebiten.Image) FrameData {
|
||||
return FrameData{
|
||||
T: HitboxNormal,
|
||||
R: image.Rect(0, 0, PlayerWidth, PlayerHeight),
|
||||
S: sprite,
|
||||
}
|
||||
}
|
||||
|
||||
// AllPlayerFrames defines all frame data for the game. Frames are defined in reverse order.
|
||||
var AllPlayerFrames = [][][]FrameData{
|
||||
{ // ActionIdle
|
||||
{
|
||||
{
|
||||
T: HitboxNormal,
|
||||
R: image.Rect(0, 0, PlayerSize, PlayerSize),
|
||||
},
|
||||
stdHit(asset.FrameAt(asset.ImgPlayer, 0, 0)),
|
||||
},
|
||||
{
|
||||
stdHit(asset.FrameAt(asset.ImgPlayer, 1, 0)),
|
||||
},
|
||||
{
|
||||
stdHit(asset.FrameAt(asset.ImgPlayer, 2, 0)),
|
||||
},
|
||||
{
|
||||
stdHit(asset.FrameAt(asset.ImgPlayer, 3, 0)),
|
||||
},
|
||||
{
|
||||
stdHit(asset.FrameAt(asset.ImgPlayer, 4, 0)),
|
||||
},
|
||||
{
|
||||
stdHit(asset.FrameAt(asset.ImgPlayer, 5, 0)),
|
||||
},
|
||||
{
|
||||
stdHit(asset.FrameAt(asset.ImgPlayer, 6, 0)),
|
||||
},
|
||||
{
|
||||
stdHit(asset.FrameAt(asset.ImgPlayer, 7, 0)),
|
||||
},
|
||||
{
|
||||
stdHit(asset.FrameAt(asset.ImgPlayer, 8, 0)),
|
||||
},
|
||||
{
|
||||
stdHit(asset.FrameAt(asset.ImgPlayer, 9, 0)),
|
||||
},
|
||||
{
|
||||
stdHit(asset.FrameAt(asset.ImgPlayer, 10, 0)),
|
||||
},
|
||||
{
|
||||
stdHit(asset.FrameAt(asset.ImgPlayer, 11, 0)),
|
||||
},
|
||||
{
|
||||
stdHit(asset.FrameAt(asset.ImgPlayer, 12, 0)),
|
||||
},
|
||||
{
|
||||
stdHit(asset.FrameAt(asset.ImgPlayer, 13, 0)),
|
||||
},
|
||||
{
|
||||
stdHit(asset.FrameAt(asset.ImgPlayer, 14, 0)),
|
||||
},
|
||||
{
|
||||
stdHit(asset.FrameAt(asset.ImgPlayer, 15, 0)),
|
||||
},
|
||||
{
|
||||
stdHit(asset.FrameAt(asset.ImgPlayer, 16, 0)),
|
||||
},
|
||||
{
|
||||
stdHit(asset.FrameAt(asset.ImgPlayer, 17, 0)),
|
||||
},
|
||||
{
|
||||
stdHit(asset.FrameAt(asset.ImgPlayer, 18, 0)),
|
||||
},
|
||||
{
|
||||
stdHit(asset.FrameAt(asset.ImgPlayer, 19, 0)),
|
||||
},
|
||||
{
|
||||
stdHit(asset.FrameAt(asset.ImgPlayer, 20, 0)),
|
||||
},
|
||||
{
|
||||
stdHit(asset.FrameAt(asset.ImgPlayer, 21, 0)),
|
||||
},
|
||||
{
|
||||
stdHit(asset.FrameAt(asset.ImgPlayer, 22, 0)),
|
||||
},
|
||||
{
|
||||
stdHit(asset.FrameAt(asset.ImgPlayer, 23, 0)),
|
||||
},
|
||||
{
|
||||
stdHit(asset.FrameAt(asset.ImgPlayer, 24, 0)),
|
||||
},
|
||||
{
|
||||
stdHit(asset.FrameAt(asset.ImgPlayer, 25, 0)),
|
||||
},
|
||||
{
|
||||
stdHit(asset.FrameAt(asset.ImgPlayer, 26, 0)),
|
||||
},
|
||||
{
|
||||
stdHit(asset.FrameAt(asset.ImgPlayer, 27, 0)),
|
||||
},
|
||||
}, { // ActionStunned
|
||||
{
|
||||
// No hitboxes or hurtboxes while stunned.
|
||||
FrameData{
|
||||
T: HitboxNone,
|
||||
R: image.Rect(0, 0, 0, 0),
|
||||
S: asset.FrameAt(asset.ImgPlayer, 0, 2),
|
||||
},
|
||||
},
|
||||
}, { // ActionBlock
|
||||
{
|
||||
|
@ -52,6 +153,7 @@ var AllPlayerFrames = [][][]FrameData{
|
|||
{
|
||||
T: HitboxNormal,
|
||||
R: image.Rect(0, 0, PlayerSize, PlayerSize),
|
||||
S: asset.FrameAt(asset.ImgPlayer, 12, 1),
|
||||
},
|
||||
{
|
||||
T: HitboxHurt,
|
||||
|
@ -59,34 +161,40 @@ var AllPlayerFrames = [][][]FrameData{
|
|||
},
|
||||
},
|
||||
{
|
||||
{
|
||||
T: HitboxNormal,
|
||||
R: image.Rect(0, 0, PlayerSize, PlayerSize),
|
||||
},
|
||||
stdHit(asset.FrameAt(asset.ImgPlayer, 11, 1)),
|
||||
},
|
||||
{
|
||||
{
|
||||
T: HitboxNormal,
|
||||
R: image.Rect(0, 0, PlayerSize, PlayerSize),
|
||||
},
|
||||
stdHit(asset.FrameAt(asset.ImgPlayer, 10, 1)),
|
||||
},
|
||||
{
|
||||
{
|
||||
T: HitboxNormal,
|
||||
R: image.Rect(0, 0, PlayerSize, PlayerSize),
|
||||
},
|
||||
stdHit(asset.FrameAt(asset.ImgPlayer, 9, 1)),
|
||||
},
|
||||
{
|
||||
{
|
||||
T: HitboxNormal,
|
||||
R: image.Rect(0, 0, PlayerSize, PlayerSize),
|
||||
},
|
||||
stdHit(asset.FrameAt(asset.ImgPlayer, 8, 1)),
|
||||
},
|
||||
{
|
||||
{
|
||||
T: HitboxNormal,
|
||||
R: image.Rect(0, 0, PlayerSize, PlayerSize),
|
||||
},
|
||||
stdHit(asset.FrameAt(asset.ImgPlayer, 7, 1)),
|
||||
},
|
||||
{
|
||||
stdHit(asset.FrameAt(asset.ImgPlayer, 6, 1)),
|
||||
},
|
||||
{
|
||||
stdHit(asset.FrameAt(asset.ImgPlayer, 5, 1)),
|
||||
},
|
||||
{
|
||||
stdHit(asset.FrameAt(asset.ImgPlayer, 4, 1)),
|
||||
},
|
||||
{
|
||||
stdHit(asset.FrameAt(asset.ImgPlayer, 3, 1)),
|
||||
},
|
||||
{
|
||||
stdHit(asset.FrameAt(asset.ImgPlayer, 2, 1)),
|
||||
},
|
||||
{
|
||||
stdHit(asset.FrameAt(asset.ImgPlayer, 1, 1)),
|
||||
},
|
||||
{
|
||||
stdHit(asset.FrameAt(asset.ImgPlayer, 0, 1)),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@ -94,6 +202,10 @@ var AllPlayerFrames = [][][]FrameData{
|
|||
func FrameDataForActionTick(a PlayerAction, tick int) []FrameData {
|
||||
actionFrames := AllPlayerFrames[a]
|
||||
if tick-1 >= len(actionFrames) {
|
||||
// Handle
|
||||
if a == ActionStunned {
|
||||
return AllPlayerFrames[ActionStunned][0]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return actionFrames[tick-1]
|
||||
|
|
18
game/game.go
18
game/game.go
|
@ -20,6 +20,10 @@ import (
|
|||
"github.com/hajimehoshi/ebiten/v2/inpututil"
|
||||
)
|
||||
|
||||
const (
|
||||
playerSpawnGap = 50
|
||||
)
|
||||
|
||||
type Game struct {
|
||||
Players []component.Player
|
||||
}
|
||||
|
@ -31,17 +35,17 @@ func NewGame() (*Game, error) {
|
|||
PlayerNum: 1,
|
||||
Color: color.RGBA{255, 0, 0, 255},
|
||||
Action: component.ActionIdle,
|
||||
ActionTicksLeft: 1,
|
||||
X: 50,
|
||||
Y: 50,
|
||||
ActionTicksLeft: len(component.AllPlayerFrames[component.ActionIdle]),
|
||||
X: -playerSpawnGap / 2,
|
||||
Y: 0,
|
||||
}
|
||||
player2 := component.Player{
|
||||
PlayerNum: 2,
|
||||
Color: color.RGBA{0, 0, 255, 255},
|
||||
Action: component.ActionIdle,
|
||||
ActionTicksLeft: 1,
|
||||
X: 150,
|
||||
Y: 50,
|
||||
ActionTicksLeft: len(component.AllPlayerFrames[component.ActionIdle]),
|
||||
X: playerSpawnGap / 2,
|
||||
Y: 0,
|
||||
}
|
||||
|
||||
g := &Game{
|
||||
|
@ -315,7 +319,7 @@ func (g *Game) UpdateByInputs(inputs []InputBits) {
|
|||
|
||||
// Stun the opponent.
|
||||
opponent.Action = component.ActionStunned
|
||||
opponent.ActionTicksLeft = 7
|
||||
opponent.ActionTicksLeft = 14
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,6 +37,8 @@ func (s *MapSystem) Draw(e gohan.Entity, screen *ebiten.Image) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
screen.Fill(color.RGBA{0, 100, 100, 255})
|
||||
|
||||
groundColor := color.RGBA{50, 50, 50, 255}
|
||||
|
||||
r := image.Rect(-world.ScreenWidth*2, -world.ScreenHeight, world.ScreenWidth*2, 0)
|
||||
|
|
|
@ -38,12 +38,40 @@ func (s *PlayerSystem) Draw(e gohan.Entity, screen *ebiten.Image) error {
|
|||
}
|
||||
|
||||
size := 20
|
||||
var p *component.Player
|
||||
var p, o *component.Player
|
||||
for i := 0; i < 2; i++ {
|
||||
if i == 0 {
|
||||
p = &world.Player1
|
||||
o = &world.Player2
|
||||
} else {
|
||||
p = &world.Player2
|
||||
o = &world.Player1
|
||||
}
|
||||
|
||||
var sprite *ebiten.Image
|
||||
frameData := component.FrameDataForActionTick(p.Action, p.ActionTicksLeft)
|
||||
for _, frame := range frameData {
|
||||
if frame.S == nil {
|
||||
continue
|
||||
}
|
||||
sprite = frame.S
|
||||
break
|
||||
}
|
||||
|
||||
if sprite != nil {
|
||||
x, y := world.GameCoordsToScreen(p.X-24, p.Y+64)
|
||||
op := &ebiten.DrawImageOptions{}
|
||||
|
||||
if component.PlayerOnRightSide(*p, *o) {
|
||||
op.GeoM.Scale(-1, 1)
|
||||
op.GeoM.Translate(64, 0)
|
||||
}
|
||||
|
||||
op.GeoM.Translate(float64(x), float64(y))
|
||||
op.ColorM.ScaleWithColor(p.Color)
|
||||
|
||||
screen.DrawImage(sprite, op)
|
||||
continue // TODO
|
||||
}
|
||||
|
||||
r := image.Rect(int(p.X), int(p.Y), int(p.X)+size, int(p.Y)+size)
|
||||
|
|
Loading…
Reference in New Issue