Allow changing TPS during runtime

This commit is contained in:
Trevor Slocum 2023-01-18 16:41:13 -08:00
parent 68124b7728
commit 92c1c72668
7 changed files with 125 additions and 67 deletions

View File

@ -55,7 +55,7 @@ var AllPlayerFrames = [][][]FrameData{
},
{
T: HitboxHurt,
R: image.Rect(0, 0, PlayerSize, PlayerSize),
R: image.Rect(-5, -5, PlayerSize+10, PlayerSize+10),
},
},
{

View File

@ -137,37 +137,43 @@ func (g *Game) startNetworkGame() {
}
func (g *Game) InitNetworking(localPort int, numPlayers int, players []ggpo.Player, numSpectators int) {
var result error
var inputBits InputBits = 0
var inputSize int = len(encodeInputs(inputBits))
session := NewGameSession()
peer := ggpo.NewPeer(session, localPort, numPlayers, inputSize)
//peer := ggpo.NewSyncTest(&session, numPlayers, 8, inputSize, true)
world.Backend = &peer
session.backend = world.Backend
peer.InitializeConnection()
peer.Start()
var inputBits InputBits = 0
inputSize := len(encodeInputs(inputBits))
//session.SetDisconnectTimeout(3000)
//session.SetDisconnectNotifyStart(1000)
peer := ggpo.NewPeer(session, localPort, numPlayers, inputSize)
world.Backend = &peer
session.backend = &peer
err := peer.InitializeConnection()
if err != nil {
log.Fatalf("failed to initialize connection: %s", err)
}
err = peer.SetDisconnectTimeout(3000)
if err != nil {
log.Fatalf("failed to set disconnect timeout: %s", err)
}
err = peer.SetDisconnectNotifyStart(1000)
if err != nil {
log.Fatalf("failed to set disconnect notify start: %s", err)
}
peer.Start()
for i := 0; i < numPlayers+numSpectators; i++ {
var handle ggpo.PlayerHandle
result = peer.AddPlayer(&players[i], &handle)
err = peer.AddPlayer(&players[i], &handle)
if err != nil {
log.Fatalf("failed to add player: %s", err)
}
if players[i].PlayerType == ggpo.PlayerTypeLocal {
world.CurrentPlayer = int(handle)
}
if result != nil {
log.Fatalf("There's an issue from AddPlayer")
}
if players[i].PlayerType == ggpo.PlayerTypeLocal {
peer.SetFrameDelay(handle, frameDelay)
err = peer.SetFrameDelay(handle, frameDelay)
if err != nil {
log.Fatalf("failed to set frame delay: %s", err)
}
}
}
peer.SetDisconnectTimeout(3000)
peer.SetDisconnectNotifyStart(1000)
}
func (g *Game) ReadInputs() InputBits {
@ -195,7 +201,12 @@ func (g *Game) ReadInputs() InputBits {
func (g *Game) applyPhysics() {
for i := 0; i < 2; i++ {
opp := 0
if i == 0 {
opp = 1
}
p := &g.Players[i]
o := &g.Players[opp]
// Apply gravity.
if p.VY > -world.Gravity {
@ -208,11 +219,21 @@ func (g *Game) applyPhysics() {
// Apply velocity.
p.X, p.Y = p.X+p.VX, p.Y+p.VY
// TODO check player collision.
// Apply player collision.
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))
if playerRect.Overlaps(oppRect) {
if playerRect.Min.X < oppRect.Min.X {
p.X = o.X - component.PlayerSize
} else {
p.X = o.X + component.PlayerSize
}
}
// Apply ground collision.
if p.Y-component.PlayerSize < 0 {
p.Y = component.PlayerSize
if p.Y < 0 {
p.Y = 0
}
}
}
@ -240,7 +261,6 @@ func (g *Game) UpdateByInputs(inputs []InputBits) {
if grounded {
g.Players[i].VY = 20
}
log.Println("JUMP", grounded)
}
if input.isButtonOn(ButtonDown) && !component.TranslateRect(playerRect, 0, 1).Overlaps(oppRect) {
//g.Players[i].VY = -1
@ -265,12 +285,12 @@ func (g *Game) UpdateByInputs(inputs []InputBits) {
// TODO player starts in idle action?
if g.Players[i].ActionTicksLeft != 0 {
// Run current frame logic.
log.Printf("apply hitbox for player %d action %d ticks left %d", i, g.Players[i].Action, g.Players[i].ActionTicksLeft)
// TODO handle invulnerability and blocking
allFrameData := component.FrameDataForActionTick(g.Players[i].Action, g.Players[i].ActionTicksLeft)
for _, frame := range allFrameData {
// Apply hitbox.
if frame.T == component.HitboxHurt {
// Hit opponent.
if oppRect.Overlaps(component.TranslateRect(frame.R, int(player.X), int(player.Y))) {
@ -284,8 +304,6 @@ func (g *Game) UpdateByInputs(inputs []InputBits) {
// Stun the opponent.
opponent.Action = component.ActionStunned
opponent.ActionTicksLeft = 7
log.Println("HIT")
}
}
}
@ -377,6 +395,32 @@ func (g *Game) Update() error {
}
}
if inpututil.IsKeyJustPressed(ebiten.KeyMinus) && ebiten.IsKeyPressed(ebiten.KeyControl) {
for i, preset := range world.TPSPresets {
if preset == world.TPS {
if i > 0 {
world.TPS = world.TPSPresets[i-1]
ebiten.SetTPS(world.TPS)
log.Printf("Set TPS to %d", world.TPS)
break
}
}
}
}
if inpututil.IsKeyJustPressed(ebiten.KeyEqual) && ebiten.IsKeyPressed(ebiten.KeyControl) {
for i, preset := range world.TPSPresets {
if preset == world.TPS {
if i < len(world.TPSPresets)-1 {
world.TPS = world.TPSPresets[i+1]
ebiten.SetTPS(world.TPS)
log.Printf("Set TPS to %d", world.TPS)
break
}
}
}
}
if world.ConnectPromptConfirmed && !world.ConnectionActive {
if world.ConnectPromptText == "" {
g.startLocalGame()

View File

@ -28,7 +28,7 @@ func (i *InputBits) isButtonOn(button InputButton) bool {
}
func (i *InputBits) setButton(button InputButton) {
*i |= (1 << button)
*i |= 1 << button
}
func readI32(b []byte) int32 {
@ -48,7 +48,7 @@ func (i *Input) isButtonOn(button InputButton) bool {
}
func (i *Input) setButton(button InputButton) {
i.ButtonMap |= (1 << button)
i.ButtonMap |= 1 << button
}
func (i Input) String() string {
@ -79,9 +79,7 @@ func decodeInputsGob(buffer [][]byte) []Input {
err := dec.Decode(&inputs[i])
if err != nil {
log.Printf("decode error: %s. Returning empty input\n", err)
// hack
inputs[i] = NewInput()
//panic("eof")
} else {
log.Printf("inputs properly decoded: %s\n", inputs[i])
}

View File

@ -37,11 +37,16 @@ func (s *MapSystem) Draw(e gohan.Entity, screen *ebiten.Image) error {
return nil
}
const groundSize = 10 // TODO
groundColor := color.RGBA{255, 255, 255, 255}
groundColor := color.RGBA{50, 50, 50, 255}
r := image.Rect(-world.ScreenWidth*2, 0, world.ScreenWidth*2, groundSize)
r := image.Rect(-world.ScreenWidth*2, -world.ScreenHeight, world.ScreenWidth*2, 0)
screen.SubImage(world.GameRectToScreen(r)).(*ebiten.Image).Fill(groundColor)
const groundTopHeight = 10
groundTopColor := color.RGBA{100, 100, 100, 255}
r = image.Rect(-world.ScreenWidth*2, -groundTopHeight, world.ScreenWidth*2, 0)
screen.SubImage(world.GameRectToScreen(r)).(*ebiten.Image).Fill(groundTopColor)
return nil
}

View File

@ -46,16 +46,11 @@ func (s *PlayerSystem) Draw(e gohan.Entity, screen *ebiten.Image) error {
p = &world.Player2
}
r := image.Rect(int(p.X), -int(p.Y), int(p.X)+size, -int(p.Y)+size)
r := image.Rect(int(p.X), int(p.Y), int(p.X)+size, int(p.Y)+size)
screen.SubImage(world.GameRectToScreen(r)).(*ebiten.Image).Fill(p.Color)
// TODO animate
/*if p.ActionTicksLeft != 0 {
frameNum := p.ActionTicksLeft - 1
frame := component.AllPlayerFrames[p.Action][frameNum]
log.Printf("frame %+v", frame)
}*/
switch p.Action {
case component.ActionPunch:

View File

@ -2,6 +2,7 @@ package system
import (
"fmt"
"image"
"image/color"
"math"
@ -179,6 +180,24 @@ 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) {
bounds := u.hitboxImg.Bounds()
if bounds.Dx() != w || bounds.Dy() != h {
u.hitboxImg = ebiten.NewImage(w, h)
}
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)
// Get screen position of top left corner of player.
drawX, drawY := world.GameCoordsToScreen(x, y)
op := &ebiten.DrawImageOptions{}
op.GeoM.Translate(float64(drawX), float64(drawY))
op.ColorM.Scale(1, 1, 1, 1)
screen.DrawImage(u.hitboxImg, op)
}
func (u *UISystem) Draw(e gohan.Entity, screen *ebiten.Image) error {
if !u.initialized {
u.initialize()
@ -197,30 +216,24 @@ func (u *UISystem) Draw(e gohan.Entity, screen *ebiten.Image) error {
} else {
p = &world.Player2
}
if p.ActionTicksLeft != 0 {
frameNum := p.ActionTicksLeft - 1
allData := component.AllPlayerFrames[p.Action][frameNum]
if p.ActionTicksLeft != 0 {
// Draw a rect over stunned players.
if p.Action == component.ActionStunned {
fillColor := color.RGBA{123, 30, 255, 255}
u.drawBox(screen, fillColor, p.X, p.Y+component.PlayerSize, component.PlayerSize, component.PlayerSize)
continue
}
// Draw frame data rects.
allData := component.FrameDataForActionTick(p.Action, p.ActionTicksLeft)
for _, data := range allData {
fillColor := color.RGBA{0, 255, 0, 255}
switch data.T {
case component.HitboxHurt:
fillColor = color.RGBA{0, 0, 255, 255}
}
bounds := u.hitboxImg.Bounds()
if bounds.Dx() != data.R.Dx() || bounds.Dy() != data.R.Dy() {
u.hitboxImg = ebiten.NewImage(data.R.Dx(), data.R.Dy())
}
u.hitboxImg.Clear()
u.hitboxImg.Fill(fillColor)
drawX, drawY := world.GameCoordsToScreen(p.X, p.Y)
op := &ebiten.DrawImageOptions{}
op.GeoM.Translate(float64(drawX), float64(drawY))
op.ColorM.Scale(1, 1, 1, 1)
screen.DrawImage(u.hitboxImg, op)
u.drawBox(screen, fillColor, p.X, p.Y+float64(data.R.Dy()), data.R.Dx(), data.R.Dy())
}
}
}
@ -254,14 +267,13 @@ func (u *UISystem) Draw(e gohan.Entity, screen *ebiten.Image) error {
framesLabel = "BEHIND"
}
ebitenutil.DebugPrintAt(screen,
fmt.Sprintf("FRAMES %s %.0f\nPING %d\nTPS %0.0f\nFPS %0.0f",
framesLabel,
math.Abs(framesBehind),
ping,
ebiten.ActualTPS(),
ebiten.ActualFPS()),
2, 0)
debugText := fmt.Sprintf("FRAMES %s %.0f\nPING %d\nTPS %0.0f\nFPS %0.0f",
framesLabel,
math.Abs(framesBehind),
ping,
ebiten.ActualTPS(),
ebiten.ActualFPS())
ebitenutil.DebugPrintAt(screen, debugText, 2, 0)
}
return nil
}

View File

@ -49,11 +49,15 @@ var (
Backend ggpo.Backend
)
var TPSPresets = []int{1, 2, 4, 10, 30, 60, 120}
func GameCoordsToScreen(x, y float64) (int, int) {
return int(x) - CamX + (ScreenWidth / 2), -int(y) - CamY + (ScreenHeight - GroundHeight)
}
func GameRectToScreen(r image.Rectangle) image.Rectangle {
r = image.Rect(r.Min.X, -r.Min.Y, r.Max.X, -r.Max.Y)
return component.TranslateRect(r, -CamX+(ScreenWidth/2), -CamY+(ScreenHeight-GroundHeight))
}