diff --git a/component/player.go b/component/player.go index 3797188..1339b96 100644 --- a/component/player.go +++ b/component/player.go @@ -99,6 +99,17 @@ func FrameDataForActionTick(a PlayerAction, tick int) []FrameData { return actionFrames[tick-1] } +func FlipRect(r image.Rectangle, flip bool) image.Rectangle { + if !flip { + return r + } + return image.Rect(-r.Min.X+PlayerSize, r.Min.Y, -r.Max.X+PlayerSize, r.Max.Y) +} + +func PlayerOnRightSide(p Player, o Player) bool { + return p.X > o.X +} + type Player struct { X, Y float64 VX, VY float64 diff --git a/flags.go b/flags.go index 9d553ab..ad64ef0 100644 --- a/flags.go +++ b/flags.go @@ -21,6 +21,7 @@ func parseFlags() { printDebug bool ) flag.BoolVar(&fullscreen, "fullscreen", false, "run in fullscreen mode") + flag.BoolVar(&world.DisableVsync, "no-vsync", false, "turn off vsync and run at maximum fps") flag.StringVar(&hostAddress, "host", "", "start hosting a match on specified address:port") flag.StringVar(&connectAddress, "connect", "", "connect to a match at specified address:port") flag.IntVar(&world.LocalPort, "local", 0, "set local port (this is not normally required)") diff --git a/game/game.go b/game/game.go index 7c5b17b..c4f18b3 100644 --- a/game/game.go +++ b/game/game.go @@ -4,6 +4,7 @@ import ( "crypto/sha1" "image/color" "log" + "math" "os" "strconv" "strings" @@ -210,7 +211,7 @@ func (g *Game) applyPhysics() { // Apply gravity. if p.VY > -world.Gravity { - p.VY -= 1 + p.VY -= math.Max(math.Abs(p.VY/2.5), 0.1) if p.VY < -world.Gravity { p.VY = -world.Gravity } @@ -240,6 +241,7 @@ func (g *Game) applyPhysics() { func (g *Game) UpdateByInputs(inputs []InputBits) { var player, opponent *component.Player + var playerFlipped, oppFlipped bool for i, input := range inputs { opp := 0 if i == 0 { @@ -248,6 +250,15 @@ func (g *Game) UpdateByInputs(inputs []InputBits) { player = &g.Players[i] opponent = &g.Players[opp] + if component.PlayerOnRightSide(*player, *opponent) { // Player is on the right side. + playerFlipped = true + oppFlipped = false + } else { // Opponent is on the right side. + playerFlipped = false + oppFlipped = true + } + _ = 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)) @@ -256,10 +267,9 @@ func (g *Game) UpdateByInputs(inputs []InputBits) { if player.Action != component.ActionStunned { if input.isButtonOn(ButtonUp) && !component.TranslateRect(playerRect, 0, -1).Overlaps(oppRect) { - grounded := g.Players[i].Y == float64(component.PlayerSize) - // TODO check when last jump, grounded + grounded := g.Players[i].Y > -world.FloatValueThreshold && g.Players[i].Y < world.FloatValueThreshold if grounded { - g.Players[i].VY = 20 + g.Players[i].VY = world.JumpVelocity } } if input.isButtonOn(ButtonDown) && !component.TranslateRect(playerRect, 0, 1).Overlaps(oppRect) { @@ -290,10 +300,12 @@ func (g *Game) UpdateByInputs(inputs []InputBits) { allFrameData := component.FrameDataForActionTick(g.Players[i].Action, g.Players[i].ActionTicksLeft) for _, frame := range allFrameData { + frameRect := component.FlipRect(frame.R, playerFlipped) + // Apply hitbox. if frame.T == component.HitboxHurt { // Hit opponent. - if oppRect.Overlaps(component.TranslateRect(frame.R, int(player.X), int(player.Y))) { + if oppRect.Overlaps(component.TranslateRect(frameRect, int(player.X), int(player.Y))) { // Send the opponent flying in some direction. if opponent.X <= player.X { // Opponent is to the left of the player. opponent.VX = -4 @@ -439,7 +451,11 @@ func (g *Game) Update() error { g.RunFrame() } - return gohan.Update() + err := gohan.Update() + if err != nil { + return err + } + return nil } func (g *Game) Draw(screen *ebiten.Image) { diff --git a/main.go b/main.go index 03193c4..7b46003 100644 --- a/main.go +++ b/main.go @@ -16,13 +16,18 @@ func main() { ebiten.SetWindowResizingMode(ebiten.WindowResizingModeEnabled) ebiten.SetWindowSize(world.DefaultScreenWidth, world.DefaultScreenHeight) ebiten.SetWindowClosingHandled(true) - ebiten.SetFPSMode(ebiten.FPSModeVsyncOn) ebiten.SetRunnableOnUnfocused(true) parseFlags() ebiten.SetTPS(world.TPS) + if world.DisableVsync { + ebiten.SetFPSMode(ebiten.FPSModeVsyncOffMaximum) + } else { + ebiten.SetFPSMode(ebiten.FPSModeVsyncOn) + } + g, err := game.NewGame() if err != nil { log.Fatal(err) diff --git a/system/ui.go b/system/ui.go index b22a67c..57236a3 100644 --- a/system/ui.go +++ b/system/ui.go @@ -180,17 +180,17 @@ func (u *UISystem) Update(e gohan.Entity) error { return etk.Update() } -func (u *UISystem) drawBox(screen *ebiten.Image, fillColor color.Color, x float64, y float64, w int, h int) { +func (u *UISystem) drawBox(screen *ebiten.Image, fillColor color.Color, r image.Rectangle) { bounds := u.hitboxImg.Bounds() - if bounds.Dx() != w || bounds.Dy() != h { - u.hitboxImg = ebiten.NewImage(w, h) + if bounds.Dx() != r.Dx() || bounds.Dy() != r.Dy() { + u.hitboxImg = ebiten.NewImage(r.Dx(), r.Dy()) } u.hitboxImg.Clear() u.hitboxImg.Fill(color.RGBA{255, 255, 255, 255}) - u.hitboxImg.SubImage(image.Rect(2, 2, w-2, h-2)).(*ebiten.Image).Fill(fillColor) + u.hitboxImg.SubImage(image.Rect(2, 2, r.Dx()-2, r.Dy()-2)).(*ebiten.Image).Fill(fillColor) // Get screen position of top left corner of player. - drawX, drawY := world.GameCoordsToScreen(x, y) + drawX, drawY := world.GameCoordsToScreen(float64(r.Min.X), float64(r.Min.Y+r.Dy())) op := &ebiten.DrawImageOptions{} op.GeoM.Translate(float64(drawX), float64(drawY)) @@ -209,31 +209,38 @@ func (u *UISystem) Draw(e gohan.Entity, screen *ebiten.Image) error { return err } } else if world.Debug > 1 { // In-game and debug mode is enabled - 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 } if p.ActionTicksLeft != 0 { // Draw a rect over stunned players. if p.Action == component.ActionStunned { + playerRect := world.FloatRect(p.X, p.Y, p.X+float64(component.PlayerSize), p.Y+float64(component.PlayerSize)) + fillColor := color.RGBA{123, 30, 255, 255} - u.drawBox(screen, fillColor, p.X, p.Y+component.PlayerSize, component.PlayerSize, component.PlayerSize) + u.drawBox(screen, fillColor, playerRect) continue } // Draw frame data rects. allData := component.FrameDataForActionTick(p.Action, p.ActionTicksLeft) - for _, data := range allData { + for _, frame := range allData { + frameRect := component.FlipRect(frame.R, component.PlayerOnRightSide(*p, *o)) + frameRect = component.TranslateRect(frameRect, int(p.X), int(p.Y)) + fillColor := color.RGBA{0, 255, 0, 255} - switch data.T { + switch frame.T { case component.HitboxHurt: fillColor = color.RGBA{0, 0, 255, 255} } - u.drawBox(screen, fillColor, p.X, p.Y+float64(data.R.Dy()), data.R.Dx(), data.R.Dy()) + u.drawBox(screen, fillColor, frameRect) } } } diff --git a/world/world.go b/world/world.go index e0c906f..1797b46 100644 --- a/world/world.go +++ b/world/world.go @@ -15,16 +15,22 @@ const ( InternalScreenWidth, InternalScreenHeight = 854, 480 - Gravity = 6.0 + Gravity = 3 - GroundHeight = 100 // TODO + JumpVelocity = 30 + + GroundHeight = 100 MaxDebug = 2 + + FloatValueThreshold = 0.00000001 ) var ( TPS = DefaultTPS + DisableVsync bool + ScreenWidth, ScreenHeight = 0, 0 CamX, CamY = 0, 0 // TODO currently static