diff --git a/asset/asset.go b/asset/asset.go new file mode 100644 index 0000000..0e0c9c3 --- /dev/null +++ b/asset/asset.go @@ -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) +} diff --git a/asset/image-source/player.aseprite b/asset/image-source/player.aseprite new file mode 100644 index 0000000..536a747 Binary files /dev/null and b/asset/image-source/player.aseprite differ diff --git a/asset/image/player.png b/asset/image/player.png new file mode 100644 index 0000000..82861b5 Binary files /dev/null and b/asset/image/player.png differ diff --git a/component/player.go b/component/player.go index 1339b96..0122b96 100644 --- a/component/player.go +++ b/component/player.go @@ -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] diff --git a/game/game.go b/game/game.go index c4f18b3..20b7861 100644 --- a/game/game.go +++ b/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 } } } diff --git a/system/map.go b/system/map.go index 7e02e48..ecac063 100644 --- a/system/map.go +++ b/system/map.go @@ -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) diff --git a/system/player.go b/system/player.go index 434773b..91756c5 100644 --- a/system/player.go +++ b/system/player.go @@ -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)