From 8685a3e37c45338747339794667f059164e8017f Mon Sep 17 00:00:00 2001 From: Trevor Slocum Date: Tue, 31 Jan 2023 17:29:47 -0800 Subject: [PATCH] Add crouching --- asset/image-source/player.aseprite | Bin 54648 -> 58580 bytes asset/image/player.png | Bin 19526 -> 22446 bytes component/player.go | 29 +++++++++++++++++++++++++++++ game/game.go | 21 +++++++++++++++------ go.mod | 4 ++-- go.sum | 8 ++++---- system/player.go | 13 ++++++++++++- system/ui.go | 3 +-- 8 files changed, 63 insertions(+), 15 deletions(-) diff --git a/asset/image-source/player.aseprite b/asset/image-source/player.aseprite index cc7b83649b1d1d9de3f5e15e57ac2421d65be800..6a13cab806756ac49a7fefc75779534b8219aebc 100644 GIT binary patch delta 1857 zcmeydiuuY(X7(#j7#JQb)t$&*$z;m1vFSJ?`#wen1_=d$%>hh}j7HLoDnKq0U}JDD z$}dgMP-WC%WI_^W;DSnQKFhrNBIAk4g7T7+Id*9?T2FRl6IJG9kYK2olbn$7<9vhk zANR%WlV%-!u`JMZ$5kG+?E${L4DAk+<9WOoPlNO^N=+7&)tYQ|n#)9s;Xm9KZU!-! z84ePEjvtiE%wq4hZku$gaohZ`EcRPwZ-PSzYBhWtNtej8Y!j<-nxoPS* z5oRia%{&8ku`I|;FMGI)b&*WwWe|p&e1lQ1(CO2UFT8BKGZf8lyj?Wy#|l~JBYX^Z zzNy|u7_EflTsgRN^^gqZV-SKHx`WZq((4Of%UtHAX;Q%kPd8%OZu7QJ85oBJ7 zCp<_Ekz6Xsz>nh6oUFo?$JO0e?PY$s?BEl7ud@=Terfi0FMLy;^&*{tq3{!O;HrTw zxdXOD31mr>Kim={6ifK9S<)A|gdrwv;w!bN8LnLxThA`IqCaI<{fb?+3l8@(GCUU2 zxPb^Cb$VO%0O6~+V5qNT8IXO&&j9fiI7>g6>>SqYEo=De<4ngjb-?J^*&CMU5bobq zm-FgmCv(zY-*wUKyQN>s%JDD+<*D65_t_(abqS$Zt>Z(ruKDESAZHJKyT3aaz?& zNuPI0*ZESU^cfg3pONE19uyBw`t9$)H^1`5ryo}yUvlq}E2%y5>R_6@;j7NA%#pf^ zUzg1CUbFjTptV4n5d%ZuQ{?;!@!(U02a_YPdXN`kA;ZBN40?qrl{r})FAp!9E@6A) z%cMp8v-fVf=G|rO$tzq^nYHTpGcl)ECrxKdd_B@}kdc9P1E!tN5O$_SLhY2nnrMKz zIiQF4fza_~e6hM4s|sg2uBuBhK5@}o*Z80E#U(QueRU0go$RzOdOYV=QOg}ei8~%1 zeT)nR0V;RUlg=xIr5UkMOQlgP6@+@U0cI)dhMBIbByXHiy3SLwS;MqFD($a(^5kSO kGutDxTs>qJjoE_cNo>D-D*s4}zG|6p2s6VGc8n+m02s+W@Bjb+ delta 82 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 diff --git a/component/player.go b/component/player.go index a6d3caf..55baf94 100644 --- a/component/player.go +++ b/component/player.go @@ -107,6 +107,9 @@ type Player struct { WalkFrame int WalkFrameReverse bool + Crouching bool + CrouchFrame int + NoPunch bool NoKick bool @@ -145,3 +148,29 @@ var WalkFrames = []*ebiten.Image{ asset.FrameAt(asset.ImgPlayer, 6, 13), asset.FrameAt(asset.ImgPlayer, 7, 13), } + +// CrouchFrames are defined in chronological order. +var CrouchFrames = []*ebiten.Image{ + asset.FrameAt(asset.ImgPlayer, 0, 14), + asset.FrameAt(asset.ImgPlayer, 1, 14), + asset.FrameAt(asset.ImgPlayer, 2, 14), + asset.FrameAt(asset.ImgPlayer, 3, 14), + asset.FrameAt(asset.ImgPlayer, 4, 14), + asset.FrameAt(asset.ImgPlayer, 5, 14), + asset.FrameAt(asset.ImgPlayer, 6, 14), + asset.FrameAt(asset.ImgPlayer, 7, 14), + asset.FrameAt(asset.ImgPlayer, 8, 14), + asset.FrameAt(asset.ImgPlayer, 9, 14), +} + +// CrouchWalkFrames are defined in chronological order. +var CrouchWalkFrames = []*ebiten.Image{ + asset.FrameAt(asset.ImgPlayer, 0, 15), + asset.FrameAt(asset.ImgPlayer, 1, 15), + asset.FrameAt(asset.ImgPlayer, 2, 15), + asset.FrameAt(asset.ImgPlayer, 3, 15), + asset.FrameAt(asset.ImgPlayer, 4, 15), + asset.FrameAt(asset.ImgPlayer, 5, 15), + asset.FrameAt(asset.ImgPlayer, 6, 15), + asset.FrameAt(asset.ImgPlayer, 7, 15), +} diff --git a/game/game.go b/game/game.go index d146d30..fc5d949 100644 --- a/game/game.go +++ b/game/game.go @@ -375,12 +375,20 @@ func (g *Game) UpdateByInputs(inputs []InputBits) { } _ = oppFlipped // TODO - playerRect := world.FloatRect(g.Players[i].X, g.Players[i].Y, g.Players[i].X+float64(component.PlayerSize), g.Players[i].Y+float64(component.PlayerSize)) oppRect := world.FloatRect(g.Players[opp].X, g.Players[opp].Y, g.Players[opp].X+float64(component.PlayerSize), g.Players[opp].Y+float64(component.PlayerSize)) g.Players[i].VX = g.Players[i].VX * 0.8 g.Players[i].VY = g.Players[i].VY * 0.8 + // Advance crouching animation frame. + const crouchFrames = 10 + if player.Crouching && player.CrouchFrame < crouchFrames-1 { + player.CrouchFrame++ + } else if !player.Crouching && player.CrouchFrame != 0 { + player.CrouchFrame-- + } + + g.Players[i].Crouching = false if g.Players[i].Action == component.ActionIdle { if input.isButtonOn(ButtonBlock) { g.Players[i].Action = component.ActionBlock @@ -432,16 +440,17 @@ func (g *Game) UpdateByInputs(inputs []InputBits) { if input.isButtonOn(ButtonUp) && g.Players[i].Grounded { g.Players[i].VY = world.JumpVelocity } - if input.isButtonOn(ButtonDown) && !component.TranslateRect(playerRect, 0, 1).Overlaps(oppRect) { - //g.Players[i].VY = -1 - // TODO crouch + if input.isButtonOn(ButtonDown) { + g.Players[i].Crouching = true } - if input.isButtonOn(ButtonLeft) && !component.TranslateRect(playerRect, -1, 0).Overlaps(oppRect) { + if input.isButtonOn(ButtonLeft) { g.Players[i].VX = -1 } - if input.isButtonOn(ButtonRight) && !component.TranslateRect(playerRect, 1, 0).Overlaps(oppRect) { + if input.isButtonOn(ButtonRight) { g.Players[i].VX = 1 } + } else { + g.Players[i].CrouchFrame = 0 } // TODO player starts in idle action? diff --git a/go.mod b/go.mod index 8a65535..5609992 100644 --- a/go.mod +++ b/go.mod @@ -15,8 +15,8 @@ require ( github.com/go-gl/glfw/v3.3/glfw v0.0.0-20221017161538-93cebf72946b // indirect github.com/hajimehoshi/file2byteslice v1.0.0 // indirect github.com/jezek/xgb v1.1.0 // indirect - golang.org/x/exp v0.0.0-20230129154200-a960b3787bd2 // indirect - golang.org/x/exp/shiny v0.0.0-20230129154200-a960b3787bd2 // indirect + golang.org/x/exp v0.0.0-20230131160201-f062dba9d201 // indirect + golang.org/x/exp/shiny v0.0.0-20230131160201-f062dba9d201 // indirect golang.org/x/image v0.3.0 // indirect golang.org/x/mobile v0.0.0-20221110043201-43a038452099 // indirect golang.org/x/sys v0.4.0 // indirect diff --git a/go.sum b/go.sum index 8cb6b07..25d56c2 100644 --- a/go.sum +++ b/go.sum @@ -38,10 +38,10 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4= -golang.org/x/exp v0.0.0-20230129154200-a960b3787bd2 h1:5sPMf9HJXrvBWIamTw+rTST0bZ3Mho2n1p58M0+W99c= -golang.org/x/exp v0.0.0-20230129154200-a960b3787bd2/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= -golang.org/x/exp/shiny v0.0.0-20230129154200-a960b3787bd2 h1:xiw7QDza+jS0x7vU7fwNbRSDE/QreNk9f9CZmXHs+Uw= -golang.org/x/exp/shiny v0.0.0-20230129154200-a960b3787bd2/go.mod h1:UH99kUObWAZkDnWqppdQe5ZhPYESUw8I0zVV1uWBR+0= +golang.org/x/exp v0.0.0-20230131160201-f062dba9d201 h1:BEABXpNXLEz0WxtA+6CQIz2xkg80e+1zrhWyMcq8VzE= +golang.org/x/exp v0.0.0-20230131160201-f062dba9d201/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= +golang.org/x/exp/shiny v0.0.0-20230131160201-f062dba9d201 h1:Y5QA5ZjU4BIgfpTmSB+07A4sIMFk6nLx0Q0/mJlIduE= +golang.org/x/exp/shiny v0.0.0-20230131160201-f062dba9d201/go.mod h1:UH99kUObWAZkDnWqppdQe5ZhPYESUw8I0zVV1uWBR+0= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.1.0/go.mod h1:iyPr49SD/G/TBxYVB/9RRtGUT5eNbo2u4NamWeQcD5c= diff --git a/system/player.go b/system/player.go index 552ddac..e9ca115 100644 --- a/system/player.go +++ b/system/player.go @@ -58,8 +58,19 @@ func (s *PlayerSystem) Draw(e gohan.Entity, screen *ebiten.Image) error { break } + drawCrouch := p.Crouching || (p.Action == component.ActionIdle && p.CrouchFrame != 0) if p.Walking() { - sprite = component.WalkFrames[p.WalkFrame] + if drawCrouch { + if p.CrouchFrame == 9 { + sprite = component.CrouchWalkFrames[p.WalkFrame] + } else { + sprite = component.CrouchFrames[p.CrouchFrame] + } + } else { + sprite = component.WalkFrames[p.WalkFrame] + } + } else if drawCrouch { + sprite = component.CrouchFrames[p.CrouchFrame] } if sprite != nil { diff --git a/system/ui.go b/system/ui.go index 6a1bde6..15ec6ba 100644 --- a/system/ui.go +++ b/system/ui.go @@ -6,12 +6,11 @@ import ( "image/color" "math" - "github.com/assemblaj/ggpo" - "code.rocketnine.space/tslocum/boxbrawl/component" "code.rocketnine.space/tslocum/boxbrawl/world" "code.rocketnine.space/tslocum/etk" "code.rocketnine.space/tslocum/gohan" + "github.com/assemblaj/ggpo" "github.com/hajimehoshi/ebiten/v2" "github.com/hajimehoshi/ebiten/v2/ebitenutil" "github.com/hajimehoshi/ebiten/v2/inpututil"