Add frame data system
This commit is contained in:
parent
85f6b3ebd1
commit
b340d821f7
10 changed files with 321 additions and 114 deletions
|
@ -2,6 +2,7 @@ package component
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"image"
|
||||
"image/color"
|
||||
)
|
||||
|
||||
|
@ -12,6 +13,74 @@ const (
|
|||
ActionPunch
|
||||
)
|
||||
|
||||
type HitboxType int
|
||||
|
||||
const (
|
||||
HitboxInvalid HitboxType = iota
|
||||
HitboxNormal
|
||||
HitboxHurt
|
||||
)
|
||||
|
||||
type FrameData struct {
|
||||
T HitboxType
|
||||
R image.Rectangle
|
||||
}
|
||||
|
||||
const playerSize = 20
|
||||
|
||||
// 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),
|
||||
},
|
||||
},
|
||||
}, { // ActionPunch
|
||||
{
|
||||
{
|
||||
T: HitboxNormal,
|
||||
R: image.Rect(0, 0, playerSize, playerSize),
|
||||
},
|
||||
{
|
||||
T: HitboxHurt,
|
||||
R: image.Rect(0, 0, playerSize, playerSize),
|
||||
},
|
||||
},
|
||||
{
|
||||
{
|
||||
T: HitboxNormal,
|
||||
R: image.Rect(0, 0, playerSize, playerSize),
|
||||
},
|
||||
},
|
||||
{
|
||||
{
|
||||
T: HitboxNormal,
|
||||
R: image.Rect(0, 0, playerSize, playerSize),
|
||||
},
|
||||
},
|
||||
{
|
||||
{
|
||||
T: HitboxNormal,
|
||||
R: image.Rect(0, 0, playerSize, playerSize),
|
||||
},
|
||||
},
|
||||
{
|
||||
{
|
||||
T: HitboxNormal,
|
||||
R: image.Rect(0, 0, playerSize, playerSize),
|
||||
},
|
||||
},
|
||||
{
|
||||
{
|
||||
T: HitboxNormal,
|
||||
R: image.Rect(0, 0, playerSize, playerSize),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
type Player struct {
|
||||
X float64
|
||||
Y float64
|
||||
|
@ -23,14 +92,17 @@ type Player struct {
|
|||
}
|
||||
|
||||
func (p *Player) String() string {
|
||||
return fmt.Sprintf("Player %d: X:%f Y:%f Color: %s", p.PlayerNum, p.X, p.Y, p.Color)
|
||||
return fmt.Sprintf("Player %d: X:%f Y:%f Action: %d", p.PlayerNum, p.X, p.Y, p.Action)
|
||||
}
|
||||
|
||||
func (p *Player) Clone() Player {
|
||||
result := Player{}
|
||||
result.X = p.X
|
||||
result.Y = p.Y
|
||||
result.Color = p.Color
|
||||
result.PlayerNum = p.PlayerNum
|
||||
result = *p
|
||||
return result
|
||||
}
|
||||
|
||||
func OffsetRect(r image.Rectangle, x int, y int) image.Rectangle {
|
||||
r.Min.X, r.Min.Y = r.Min.X+x, r.Min.Y+y
|
||||
r.Max.X, r.Max.Y = r.Max.X+x, r.Max.Y+y
|
||||
return r
|
||||
}
|
||||
|
|
1
flags.go
1
flags.go
|
@ -25,6 +25,7 @@ func parseFlags() {
|
|||
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)")
|
||||
flag.BoolVar(&printDebug, "debug", false, "enable printing debug messages")
|
||||
flag.IntVar(&world.TPS, "tps", world.DefaultTPS, "set ticks per second (this is not normally required)")
|
||||
flag.Parse()
|
||||
|
||||
if fullscreen {
|
||||
|
|
187
game/game.go
187
game/game.go
|
@ -19,10 +19,6 @@ import (
|
|||
"github.com/hajimehoshi/ebiten/v2/inpututil"
|
||||
)
|
||||
|
||||
var backend ggpo.Backend
|
||||
|
||||
var currentPlayer = 1
|
||||
|
||||
type Game struct {
|
||||
Players []component.Player
|
||||
}
|
||||
|
@ -30,18 +26,23 @@ type Game struct {
|
|||
var addedGame bool
|
||||
|
||||
func NewGame() (*Game, error) {
|
||||
var player1 = component.Player{
|
||||
X: 50,
|
||||
Y: 50,
|
||||
Color: color.RGBA{255, 0, 0, 255},
|
||||
PlayerNum: 1,
|
||||
player1 := component.Player{
|
||||
PlayerNum: 1,
|
||||
Color: color.RGBA{255, 0, 0, 255},
|
||||
Action: component.ActionIdle,
|
||||
ActionTicksLeft: 1,
|
||||
X: 50,
|
||||
Y: 50,
|
||||
}
|
||||
var player2 = component.Player{
|
||||
X: 150,
|
||||
Y: 50,
|
||||
Color: color.RGBA{0, 0, 255, 255},
|
||||
PlayerNum: 2,
|
||||
player2 := component.Player{
|
||||
PlayerNum: 2,
|
||||
Color: color.RGBA{0, 0, 255, 255},
|
||||
Action: component.ActionIdle,
|
||||
ActionTicksLeft: 1,
|
||||
X: 150,
|
||||
Y: 50,
|
||||
}
|
||||
|
||||
g := &Game{
|
||||
Players: []component.Player{player1, player2},
|
||||
}
|
||||
|
@ -78,68 +79,78 @@ func (g *Game) Layout(_, _ int) (screenWidth, screenHeight int) {
|
|||
return world.ScreenWidth, world.ScreenHeight
|
||||
}
|
||||
|
||||
func (g *Game) startLocalGame() {
|
||||
log.Println("Playing against the computer")
|
||||
|
||||
// TODO
|
||||
}
|
||||
|
||||
func (g *Game) startNetworkGame() {
|
||||
address := ""
|
||||
port := world.ConnectPromptText
|
||||
if strings.ContainsRune(port, ':') {
|
||||
split := strings.Split(port, ":")
|
||||
if len(split) == 2 {
|
||||
address = split[0]
|
||||
port = split[1]
|
||||
}
|
||||
}
|
||||
p, err := strconv.Atoi(port)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to read port: %s", err)
|
||||
}
|
||||
|
||||
localPort := world.LocalPort
|
||||
if localPort == 0 {
|
||||
localPort = p + 1
|
||||
}
|
||||
if world.LocalPort != 0 {
|
||||
log.Printf("Client port for connection: %d", world.LocalPort)
|
||||
}
|
||||
|
||||
numPlayers := 2
|
||||
playerSize := 20
|
||||
|
||||
players := make([]ggpo.Player, numPlayers)
|
||||
if world.ConnectPromptHost {
|
||||
log.Printf("Hosting at " + address + ":" + port + "...")
|
||||
|
||||
players[0] = ggpo.NewLocalPlayer(playerSize, 1)
|
||||
players[1] = ggpo.NewRemotePlayer(playerSize, 2, "127.0.0.1", localPort)
|
||||
} else {
|
||||
log.Printf("Connecting to " + address + ":" + port + "...")
|
||||
|
||||
world.CurrentPlayer = 2
|
||||
players[0] = ggpo.NewRemotePlayer(playerSize, 1, address, p)
|
||||
players[1] = ggpo.NewLocalPlayer(playerSize, 2)
|
||||
}
|
||||
|
||||
l := p
|
||||
if !world.ConnectPromptHost {
|
||||
l = localPort
|
||||
}
|
||||
|
||||
g.InitNetworking(l, numPlayers, players, 0)
|
||||
|
||||
world.ConnectionActive = true
|
||||
}
|
||||
func (g *Game) Update() error {
|
||||
if ebiten.IsWindowBeingClosed() || (!world.WASM && ebiten.IsKeyPressed(ebiten.KeyEscape)) {
|
||||
g.Exit()
|
||||
return nil
|
||||
}
|
||||
|
||||
if world.ConnectPromptConfirmed && !world.ConnectPromptActive {
|
||||
address := ""
|
||||
port := world.ConnectPromptText
|
||||
if strings.ContainsRune(port, ':') {
|
||||
split := strings.Split(port, ":")
|
||||
if len(split) == 2 {
|
||||
address = split[0]
|
||||
port = split[1]
|
||||
}
|
||||
}
|
||||
p, err := strconv.Atoi(port)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to read port: %s", err)
|
||||
}
|
||||
|
||||
log.Println("start networking")
|
||||
|
||||
localPort := world.LocalPort
|
||||
if localPort == 0 {
|
||||
localPort = p + 1
|
||||
}
|
||||
|
||||
numPlayers := 2
|
||||
playerSize := 20
|
||||
players := make([]ggpo.Player, numPlayers)
|
||||
if world.ConnectPromptHost {
|
||||
log.Printf("Hosting at " + address + ":" + port + "...")
|
||||
|
||||
players[0] = ggpo.NewLocalPlayer(playerSize, 1)
|
||||
players[1] = ggpo.NewRemotePlayer(playerSize, 2, "127.0.0.1", localPort)
|
||||
if world.ConnectPromptConfirmed && !world.ConnectionActive {
|
||||
if world.ConnectPromptText == "" {
|
||||
g.startLocalGame()
|
||||
} else {
|
||||
log.Printf("Connecting to " + address + ":" + port + "...")
|
||||
|
||||
players[0] = ggpo.NewRemotePlayer(playerSize, 1, address, p)
|
||||
players[1] = ggpo.NewLocalPlayer(playerSize, 2)
|
||||
currentPlayer = 2
|
||||
g.startNetworkGame()
|
||||
}
|
||||
|
||||
if world.LocalPort != 0 {
|
||||
log.Printf("Client port for connection: %d", world.LocalPort)
|
||||
}
|
||||
|
||||
l := p
|
||||
if !world.ConnectPromptHost {
|
||||
l = localPort
|
||||
}
|
||||
|
||||
g.InitNetworking(l, numPlayers, players, 0)
|
||||
|
||||
g.playerStateUpdated()
|
||||
|
||||
world.ConnectPromptActive = true
|
||||
}
|
||||
|
||||
if world.ConnectPromptActive {
|
||||
err := backend.Idle(0) // TODO Why 0?
|
||||
if world.ConnectionActive {
|
||||
err := world.Backend.Idle(0)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
@ -174,8 +185,8 @@ func (g *Game) InitNetworking(localPort int, numPlayers int, players []ggpo.Play
|
|||
|
||||
peer := ggpo.NewPeer(session, localPort, numPlayers, inputSize)
|
||||
//peer := ggpo.NewSyncTest(&session, numPlayers, 8, inputSize, true)
|
||||
backend = &peer
|
||||
session.backend = backend
|
||||
world.Backend = &peer
|
||||
session.backend = world.Backend
|
||||
peer.InitializeConnection()
|
||||
peer.Start()
|
||||
|
||||
|
@ -186,13 +197,13 @@ func (g *Game) InitNetworking(localPort int, numPlayers int, players []ggpo.Play
|
|||
var handle ggpo.PlayerHandle
|
||||
result = peer.AddPlayer(&players[i], &handle)
|
||||
if players[i].PlayerType == ggpo.PlayerTypeLocal {
|
||||
currentPlayer = int(handle)
|
||||
world.CurrentPlayer = int(handle)
|
||||
}
|
||||
if result != nil {
|
||||
log.Fatalf("There's an issue from AddPlayer")
|
||||
}
|
||||
if players[i].PlayerType == ggpo.PlayerTypeLocal {
|
||||
peer.SetFrameDelay(handle, FRAME_DELAY)
|
||||
peer.SetFrameDelay(handle, frameDelay)
|
||||
}
|
||||
}
|
||||
peer.SetDisconnectTimeout(3000)
|
||||
|
@ -204,7 +215,7 @@ func (g *Game) RunFrame() {
|
|||
buffer := encodeInputs(input)
|
||||
|
||||
//fmt.Println("Attempting to add local inputs")
|
||||
result := backend.AddLocalInput(ggpo.PlayerHandle(currentPlayer), buffer, len(buffer))
|
||||
result := world.Backend.AddLocalInput(ggpo.PlayerHandle(world.CurrentPlayer), buffer, len(buffer))
|
||||
|
||||
//fmt.Println("Attempt to add local inputs complete")
|
||||
if result == nil {
|
||||
|
@ -213,7 +224,7 @@ func (g *Game) RunFrame() {
|
|||
disconnectFlags := 0
|
||||
|
||||
//fmt.Println("Attempting to synchronize inputs")
|
||||
values, result = backend.SyncInput(&disconnectFlags)
|
||||
values, result = world.Backend.SyncInput(&disconnectFlags)
|
||||
if result == nil {
|
||||
//fmt.Println("Attempt synchronize inputs was sucessful")
|
||||
inputs := decodeInputs(values)
|
||||
|
@ -225,25 +236,17 @@ func (g *Game) RunFrame() {
|
|||
} else {
|
||||
//fmt.Printf("Attempt to add local inputs unsuccessful: %s\n", result)
|
||||
}
|
||||
|
||||
g.playerStateUpdated()
|
||||
}
|
||||
|
||||
func (g *Game) AdvanceFrame(inputs []InputBits, disconnectFlags int) {
|
||||
g.UpdateByInputs(inputs)
|
||||
err := backend.AdvanceFrame(uint32(g.Checksum()))
|
||||
|
||||
err := world.Backend.AdvanceFrame(uint32(g.Checksum()))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
for i := range g.Players {
|
||||
if g.Players[i].ActionTicksLeft != 0 {
|
||||
g.Players[i].ActionTicksLeft--
|
||||
if g.Players[i].ActionTicksLeft == 0 {
|
||||
g.Players[i].Action = component.ActionIdle
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
g.playerStateUpdated()
|
||||
}
|
||||
|
||||
func (g *Game) UpdateByInputs(inputs []InputBits) {
|
||||
|
@ -264,7 +267,23 @@ func (g *Game) UpdateByInputs(inputs []InputBits) {
|
|||
if g.Players[i].Action == component.ActionIdle {
|
||||
if input.isButtonOn(ButtonPunch) {
|
||||
g.Players[i].Action = component.ActionPunch
|
||||
g.Players[i].ActionTicksLeft = 25 // TODO
|
||||
g.Players[i].ActionTicksLeft = len(component.AllPlayerFrames[component.ActionPunch]) // TODO
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if g.Players[i].ActionTicksLeft != 0 {
|
||||
g.Players[i].ActionTicksLeft--
|
||||
if g.Players[i].ActionTicksLeft == 0 {
|
||||
g.Players[i].Action = component.ActionIdle
|
||||
g.Players[i].ActionTicksLeft = len(component.AllPlayerFrames[component.ActionIdle]) // TODO
|
||||
}
|
||||
|
||||
// TODO Apply hitboxes
|
||||
if g.Players[i].ActionTicksLeft != 0 {
|
||||
//frameNum := g.Players[i].ActionTicksLeft - 1
|
||||
//frame := component.AllPlayerFrames[g.Players[i].Action][frameNum]
|
||||
//log.Printf("frame %+v", frame)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ import (
|
|||
"github.com/assemblaj/ggpo"
|
||||
)
|
||||
|
||||
const FRAME_DELAY int = 2
|
||||
const frameDelay int = 2
|
||||
|
||||
type GameSession struct {
|
||||
backend ggpo.Backend
|
||||
|
@ -92,5 +92,17 @@ func (g *GameSession) OnEvent(info *ggpo.Event) {
|
|||
log.Println("Connection resumed")
|
||||
case ggpo.EventCodeDisconnectedFromPeer:
|
||||
log.Println("Connection lost")
|
||||
default:
|
||||
if world.Debug == 0 {
|
||||
return
|
||||
}
|
||||
switch info.Code {
|
||||
case ggpo.EventCodeConnectedToPeer:
|
||||
log.Println("Connected to peer")
|
||||
case ggpo.EventCodeSynchronizingWithPeer:
|
||||
log.Println("Synchronizing with peer")
|
||||
case ggpo.EventCodeSynchronizedWithPeer:
|
||||
log.Println("Synchronized with peer")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
6
go.mod
6
go.mod
|
@ -5,7 +5,7 @@ go 1.19
|
|||
require (
|
||||
code.rocketnine.space/tslocum/etk v0.0.0-20230103193701-368514415e01
|
||||
code.rocketnine.space/tslocum/gohan v1.0.0
|
||||
github.com/assemblaj/ggpo v0.0.0-20230105182823-b13b11d28a8e
|
||||
github.com/assemblaj/ggpo v0.0.0-20230106194913-0f5ca8313c51
|
||||
github.com/hajimehoshi/ebiten/v2 v2.4.15
|
||||
)
|
||||
|
||||
|
@ -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-20230105000112-eab7a2c85304 // indirect
|
||||
golang.org/x/exp/shiny v0.0.0-20230105000112-eab7a2c85304 // indirect
|
||||
golang.org/x/exp v0.0.0-20230108222341-4b8118a2686a // indirect
|
||||
golang.org/x/exp/shiny v0.0.0-20230108222341-4b8118a2686a // 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
|
||||
|
|
12
go.sum
12
go.sum
|
@ -5,8 +5,8 @@ code.rocketnine.space/tslocum/gohan v1.0.0/go.mod h1:12yOt5Ygl/RVwnnZSVZRuS1W6gC
|
|||
code.rocketnine.space/tslocum/messeji v1.0.2 h1:3/68FnXWaBDMhfUGb8FvNpVgAHY8DX+VL7pyA/CcY94=
|
||||
code.rocketnine.space/tslocum/messeji v1.0.2/go.mod h1:bSXsyjvKhFXQ7GsUxWZdO2JX83xOT/VTqFCR04thk+c=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/assemblaj/ggpo v0.0.0-20230105182823-b13b11d28a8e h1:d/uJpkRgHj5EJ06CX0TFFQLfvAeHlfWY+xfLUBkVeus=
|
||||
github.com/assemblaj/ggpo v0.0.0-20230105182823-b13b11d28a8e/go.mod h1:ZKiAYEZgxDlGHGeP/VZsv1+xIRo9kQpgUFmjP/PR0lQ=
|
||||
github.com/assemblaj/ggpo v0.0.0-20230106194913-0f5ca8313c51 h1:7utlNj3OWPUxtFgav/0Eu1EXOrsNS5lrq+NWnwTRnAs=
|
||||
github.com/assemblaj/ggpo v0.0.0-20230106194913-0f5ca8313c51/go.mod h1:ZKiAYEZgxDlGHGeP/VZsv1+xIRo9kQpgUFmjP/PR0lQ=
|
||||
github.com/ebitengine/purego v0.0.0-20220905075623-aeed57cda744/go.mod h1:Eh8I3yvknDYZeCuXH9kRNaPuHEwvXDCk378o9xszmHg=
|
||||
github.com/ebitengine/purego v0.1.1 h1:HI8nW+LniW9Yb34k34jBs8nz+PNzsw68o7JF8jWFHHE=
|
||||
github.com/ebitengine/purego v0.1.1/go.mod h1:Eh8I3yvknDYZeCuXH9kRNaPuHEwvXDCk378o9xszmHg=
|
||||
|
@ -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-20230105000112-eab7a2c85304 h1:YUqj+XKtfrn3kXjFIiZ8jwKROD7ioAOOHUuo3ZZ2opc=
|
||||
golang.org/x/exp v0.0.0-20230105000112-eab7a2c85304/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
|
||||
golang.org/x/exp/shiny v0.0.0-20230105000112-eab7a2c85304 h1:ezMmyIKsGPwRz+IHa53wCpw87I2TremhqQ8o79ytDEk=
|
||||
golang.org/x/exp/shiny v0.0.0-20230105000112-eab7a2c85304/go.mod h1:UH99kUObWAZkDnWqppdQe5ZhPYESUw8I0zVV1uWBR+0=
|
||||
golang.org/x/exp v0.0.0-20230108222341-4b8118a2686a h1:tlXy25amD5A7gOfbXdqCGN5k8ESEed/Ee1E5RcrYnqU=
|
||||
golang.org/x/exp v0.0.0-20230108222341-4b8118a2686a/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
|
||||
golang.org/x/exp/shiny v0.0.0-20230108222341-4b8118a2686a h1:mTWP1/jZnkR5zmffmGg9HfL6w81dS/u6ZJSOa4i8ot8=
|
||||
golang.org/x/exp/shiny v0.0.0-20230108222341-4b8118a2686a/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=
|
||||
|
|
3
main.go
3
main.go
|
@ -17,11 +17,12 @@ func main() {
|
|||
ebiten.SetWindowSize(world.DefaultScreenWidth, world.DefaultScreenHeight)
|
||||
ebiten.SetWindowClosingHandled(true)
|
||||
ebiten.SetFPSMode(ebiten.FPSModeVsyncOn)
|
||||
ebiten.SetTPS(world.TPS)
|
||||
ebiten.SetRunnableOnUnfocused(true)
|
||||
|
||||
parseFlags()
|
||||
|
||||
ebiten.SetTPS(world.TPS)
|
||||
|
||||
g, err := game.NewGame()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
|
|
|
@ -49,6 +49,13 @@ func (s *PlayerSystem) Draw(e gohan.Entity, screen *ebiten.Image) error {
|
|||
r := image.Rect(int(p.X), int(p.Y), int(p.X)+size, int(p.Y)+size)
|
||||
screen.SubImage(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:
|
||||
ebitenutil.DebugPrintAt(screen, "PUNCH", int(p.X), int(p.Y))
|
||||
|
|
106
system/ui.go
106
system/ui.go
|
@ -2,6 +2,10 @@ package system
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"image/color"
|
||||
"math"
|
||||
|
||||
"github.com/assemblaj/ggpo"
|
||||
|
||||
"code.rocketnine.space/tslocum/boxbrawl/component"
|
||||
"code.rocketnine.space/tslocum/boxbrawl/world"
|
||||
|
@ -31,6 +35,9 @@ type UISystem struct {
|
|||
|
||||
initialized bool
|
||||
buffer *etk.Text
|
||||
updateTicks int
|
||||
|
||||
hitboxImg *ebiten.Image
|
||||
}
|
||||
|
||||
func (u *UISystem) initialize() {
|
||||
|
@ -45,6 +52,8 @@ func (u *UISystem) initialize() {
|
|||
etk.SetRoot(inputDemo)
|
||||
etk.Layout(world.InternalScreenWidth, world.InternalScreenHeight)
|
||||
|
||||
u.hitboxImg = ebiten.NewImage(32, 32)
|
||||
|
||||
u.initialized = true
|
||||
}
|
||||
|
||||
|
@ -54,7 +63,7 @@ func (u *UISystem) updateBuffer() {
|
|||
if world.WASM {
|
||||
prompt = append(prompt, []byte("\n\n"+uiComputerPrompt)...)
|
||||
prompt = append(prompt, []byte("\n\n"+uiBrowserPrompt)...)
|
||||
} else if world.ConnectPromptActive {
|
||||
} else if world.ConnectionActive {
|
||||
if world.ConnectPromptHost {
|
||||
prompt = append(prompt, []byte("\n\n"+fmt.Sprintf(uiHostListeningPrompt, world.ConnectPromptText))...)
|
||||
} else {
|
||||
|
@ -63,7 +72,7 @@ func (u *UISystem) updateBuffer() {
|
|||
} else {
|
||||
promptEntered := len(world.ConnectPromptText) != 0
|
||||
if promptEntered || world.ConnectPromptHost {
|
||||
prompt = append(prompt, []byte("\n\n"+world.ConnectPromptText)...)
|
||||
prompt = append(prompt, []byte("\n\n"+world.ConnectPromptText+"_")...)
|
||||
if world.ConnectPromptHost {
|
||||
prompt = append(prompt, []byte("\n\n"+uiHostInfoPrompt)...)
|
||||
prompt = append(prompt, []byte("\n\n"+uiHostStartPrompt)...)
|
||||
|
@ -91,10 +100,13 @@ func (u *UISystem) Update(e gohan.Entity) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
var updated bool
|
||||
|
||||
if !world.WASM {
|
||||
if len(world.ConnectPromptText) == 0 && ebiten.IsKeyPressed(ebiten.KeyH) {
|
||||
world.ConnectPromptHost = true
|
||||
u.updateBuffer()
|
||||
updated = true
|
||||
}
|
||||
|
||||
var a string
|
||||
|
@ -137,14 +149,17 @@ func (u *UISystem) Update(e gohan.Entity) error {
|
|||
if a != "" {
|
||||
world.ConnectPromptText += a
|
||||
u.updateBuffer()
|
||||
updated = true
|
||||
}
|
||||
if inpututil.IsKeyJustPressed(ebiten.KeyBackspace) {
|
||||
if len(world.ConnectPromptText) != 0 {
|
||||
world.ConnectPromptText = world.ConnectPromptText[:len(world.ConnectPromptText)-1]
|
||||
u.updateBuffer()
|
||||
updated = true
|
||||
} else if world.ConnectPromptHost {
|
||||
world.ConnectPromptHost = false
|
||||
u.updateBuffer()
|
||||
updated = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -153,6 +168,14 @@ func (u *UISystem) Update(e gohan.Entity) error {
|
|||
world.ConnectPromptConfirmed = true
|
||||
}
|
||||
|
||||
if !updated {
|
||||
u.updateTicks++
|
||||
if u.updateTicks == 6 {
|
||||
u.updateBuffer()
|
||||
u.updateTicks = 0
|
||||
}
|
||||
}
|
||||
|
||||
return etk.Update()
|
||||
}
|
||||
|
||||
|
@ -161,17 +184,82 @@ func (u *UISystem) Draw(e gohan.Entity, screen *ebiten.Image) error {
|
|||
u.initialize()
|
||||
}
|
||||
|
||||
if !world.ConnectPromptVisible {
|
||||
return nil
|
||||
}
|
||||
if world.ConnectPromptVisible {
|
||||
err := etk.Draw(screen)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if world.Debug != 0 { // In-game and debug mode is enabled
|
||||
var p *component.Player
|
||||
for i := 0; i < 2; i++ {
|
||||
if i == 0 {
|
||||
p = &world.Player1
|
||||
} else {
|
||||
p = &world.Player2
|
||||
}
|
||||
if p.ActionTicksLeft != 0 {
|
||||
frameNum := p.ActionTicksLeft - 1
|
||||
allData := component.AllPlayerFrames[p.Action][frameNum]
|
||||
|
||||
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)
|
||||
|
||||
op := &ebiten.DrawImageOptions{}
|
||||
op.GeoM.Translate(p.X, p.Y)
|
||||
op.ColorM.Scale(1, 1, 1, 1)
|
||||
screen.DrawImage(u.hitboxImg, op)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
err := etk.Draw(screen)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if world.Debug != 0 {
|
||||
ebitenutil.DebugPrintAt(screen, fmt.Sprintf("ENT %d\nUPD %d\nDRA %d\nTPS %0.0f\nFPS %0.0f", gohan.CurrentEntities(), gohan.CurrentUpdates(), gohan.CurrentDraws(), ebiten.ActualTPS(), ebiten.ActualFPS()), 2, 0)
|
||||
var ping int64
|
||||
var framesBehind float64
|
||||
if !world.ConnectPromptVisible {
|
||||
p1Stats, err := world.Backend.GetNetworkStats(ggpo.PlayerHandle(1))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get network stats for player 1: %s", err)
|
||||
}
|
||||
p2Stats, err := world.Backend.GetNetworkStats(ggpo.PlayerHandle(2))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get network stats for player 2: %s", err)
|
||||
}
|
||||
|
||||
yourStats := p1Stats
|
||||
if world.CurrentPlayer == 1 {
|
||||
yourStats = p2Stats
|
||||
}
|
||||
|
||||
ping = yourStats.Network.Ping
|
||||
framesBehind = math.Round(float64(yourStats.Timesync.LocalFramesBehind))
|
||||
}
|
||||
|
||||
framesLabel := "AHEAD"
|
||||
if framesBehind > 0 {
|
||||
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)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -2,11 +2,12 @@ package world
|
|||
|
||||
import (
|
||||
"code.rocketnine.space/tslocum/boxbrawl/component"
|
||||
"github.com/assemblaj/ggpo"
|
||||
)
|
||||
|
||||
const TPS = 60
|
||||
|
||||
const (
|
||||
DefaultTPS = 60
|
||||
|
||||
DefaultScreenWidth = 1280
|
||||
DefaultScreenHeight = 720
|
||||
|
||||
|
@ -14,20 +15,26 @@ const (
|
|||
)
|
||||
|
||||
var (
|
||||
TPS = DefaultTPS
|
||||
|
||||
ScreenWidth, ScreenHeight = 0, 0
|
||||
|
||||
LocalPort int
|
||||
|
||||
ConnectPromptVisible = true
|
||||
ConnectPromptVisible = true // When false, we are connected
|
||||
ConnectPromptText string
|
||||
ConnectPromptHost bool
|
||||
ConnectPromptConfirmed bool
|
||||
ConnectPromptActive bool
|
||||
ConnectionActive bool
|
||||
|
||||
Debug = 1
|
||||
Debug = 1 // TODO
|
||||
|
||||
WASM bool
|
||||
|
||||
Player1 component.Player
|
||||
Player2 component.Player
|
||||
|
||||
CurrentPlayer = 1
|
||||
|
||||
Backend ggpo.Backend
|
||||
)
|
||||
|
|
Loading…
Reference in a new issue