Add player action and tick system

This commit is contained in:
Trevor Slocum 2023-01-05 14:28:24 -08:00
parent ea3c90eb46
commit 85f6b3ebd1
7 changed files with 96 additions and 80 deletions

View File

@ -5,11 +5,21 @@ import (
"image/color"
)
type PlayerAction int
const (
ActionIdle PlayerAction = iota
ActionPunch
)
type Player struct {
X float64
Y float64
Color color.Color
PlayerNum int
Action PlayerAction
ActionTicksLeft int
}
func (p *Player) String() string {

View File

@ -16,6 +16,7 @@ import (
"code.rocketnine.space/tslocum/gohan"
"github.com/assemblaj/ggpo"
"github.com/hajimehoshi/ebiten/v2"
"github.com/hajimehoshi/ebiten/v2/inpututil"
)
var backend ggpo.Backend
@ -66,12 +67,13 @@ func (g *Game) clone() (result *Game) {
return
}
func (g *Game) Layout(outsideWidth, outsideHeight int) (screenWidth, screenHeight int) {
if outsideWidth != world.ScreenWidth || outsideHeight != world.ScreenHeight {
func (g *Game) Layout(_, _ int) (screenWidth, screenHeight int) {
// Maintain constant internal resolution.
if world.InternalScreenWidth != world.ScreenWidth || world.InternalScreenHeight != world.ScreenHeight {
if world.ScreenWidth != 0 || world.ScreenHeight != 0 {
etk.Layout(outsideWidth, outsideHeight)
etk.Layout(world.InternalScreenWidth, world.InternalScreenHeight)
}
world.ScreenWidth, world.ScreenHeight = outsideWidth, outsideHeight
world.ScreenWidth, world.ScreenHeight = world.InternalScreenWidth, world.InternalScreenHeight
}
return world.ScreenWidth, world.ScreenHeight
}
@ -101,7 +103,7 @@ func (g *Game) Update() error {
localPort := world.LocalPort
if localPort == 0 {
localPort = p
localPort = p + 1
}
numPlayers := 2
@ -131,6 +133,8 @@ func (g *Game) Update() error {
g.InitNetworking(l, numPlayers, players, 0)
g.playerStateUpdated()
world.ConnectPromptActive = true
}
@ -139,15 +143,14 @@ func (g *Game) Update() error {
if err != nil {
panic(err)
}
g.RunFrame()
g.updatePlayerState() // TODO only after advanceframe?
g.RunFrame()
}
return gohan.Update()
}
func (g *Game) updatePlayerState() {
func (g *Game) playerStateUpdated() {
world.Player1, world.Player2 = g.Players[0], g.Players[1]
}
@ -225,45 +228,44 @@ func (g *Game) RunFrame() {
}
func (g *Game) AdvanceFrame(inputs []InputBits, disconnectFlags int) {
if world.ConnectPromptVisible {
// We are connected now.
world.ConnectPromptVisible = false
log.Println("Connected successfully")
}
g.UpdateByInputs(inputs)
err := 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) {
for i, input := range inputs {
if input.isButtonOn(int(ebiten.KeyArrowUp)) {
if input.isButtonOn(ButtonUp) {
g.Players[i].Y--
}
if input.isButtonOn(int(ebiten.KeyArrowDown)) {
if input.isButtonOn(ButtonDown) {
g.Players[i].Y++
}
if input.isButtonOn(int(ebiten.KeyArrowLeft)) {
if input.isButtonOn(ButtonLeft) {
g.Players[i].X--
}
if input.isButtonOn(int(ebiten.KeyArrowRight)) {
if input.isButtonOn(ButtonRight) {
g.Players[i].X++
}
if input.isButtonOn(int(ebiten.KeyW)) {
g.Players[i].Y--
}
if input.isButtonOn(int(ebiten.KeyS)) {
g.Players[i].Y++
}
if input.isButtonOn(int(ebiten.KeyA)) {
g.Players[i].X--
}
if input.isButtonOn(int(ebiten.KeyD)) {
g.Players[i].X++
if g.Players[i].Action == component.ActionIdle {
if input.isButtonOn(ButtonPunch) {
g.Players[i].Action = component.ActionPunch
g.Players[i].ActionTicksLeft = 25 // TODO
}
}
}
}
@ -271,36 +273,31 @@ func (g *Game) UpdateByInputs(inputs []InputBits) {
func (g *Game) ReadInputs() InputBits {
var in InputBits
if ebiten.IsKeyPressed(ebiten.KeyArrowUp) {
in.setButton(int(ebiten.KeyArrowUp))
if ebiten.IsKeyPressed(ebiten.KeyArrowUp) || ebiten.IsKeyPressed(ebiten.KeyW) {
in.setButton(ButtonUp)
}
if ebiten.IsKeyPressed(ebiten.KeyArrowDown) {
in.setButton(int(ebiten.KeyArrowDown))
if ebiten.IsKeyPressed(ebiten.KeyArrowDown) || ebiten.IsKeyPressed(ebiten.KeyS) {
in.setButton(ButtonDown)
}
if ebiten.IsKeyPressed(ebiten.KeyArrowLeft) {
in.setButton(int(ebiten.KeyArrowLeft))
if ebiten.IsKeyPressed(ebiten.KeyArrowLeft) || ebiten.IsKeyPressed(ebiten.KeyA) {
in.setButton(ButtonLeft)
}
if ebiten.IsKeyPressed(ebiten.KeyArrowRight) {
in.setButton(int(ebiten.KeyArrowRight))
if ebiten.IsKeyPressed(ebiten.KeyArrowRight) || ebiten.IsKeyPressed(ebiten.KeyD) {
in.setButton(ButtonRight)
}
if inpututil.IsKeyJustPressed(ebiten.KeyH) {
in.setButton(ButtonPunch)
}
return in
}
func (g *Game) ReadInputsP2() InputBits {
var in InputBits
if ebiten.IsKeyPressed(ebiten.KeyW) {
in.setButton(int(ebiten.KeyW))
}
if ebiten.IsKeyPressed(ebiten.KeyS) {
in.setButton(int(ebiten.KeyS))
}
if ebiten.IsKeyPressed(ebiten.KeyA) {
in.setButton(int(ebiten.KeyA))
}
if ebiten.IsKeyPressed(ebiten.KeyD) {
in.setButton(int(ebiten.KeyD))
}
// TODO Support local multiplayer?
return in
}

View File

@ -13,11 +13,21 @@ type Input struct {
type InputBits int
func (i *InputBits) isButtonOn(button int) bool {
type InputButton int
const (
ButtonLeft InputButton = iota + 1
ButtonRight
ButtonDown
ButtonUp
ButtonPunch
)
func (i *InputBits) isButtonOn(button InputButton) bool {
return *i&(1<<button) > 0
}
func (i *InputBits) setButton(button int) {
func (i *InputBits) setButton(button InputButton) {
*i |= (1 << button)
}
@ -33,11 +43,11 @@ func writeI32(i32 int32) []byte {
return b
}
func (i *Input) isButtonOn(button int) bool {
func (i *Input) isButtonOn(button InputButton) bool {
return i.ButtonMap&(1<<button) > 0
}
func (i *Input) setButton(button int) {
func (i *Input) setButton(button InputButton) {
i.ButtonMap |= (1 << button)
}

View File

@ -7,6 +7,8 @@ import (
"fmt"
"log"
"code.rocketnine.space/tslocum/boxbrawl/world"
"github.com/assemblaj/ggpo"
)
@ -64,35 +66,31 @@ func (g *Game) String() string {
}
func (g *GameSession) AdvanceFrame(flags int) {
fmt.Println("Advancing frame from callback. ")
var discconectFlags int
//fmt.Println("Advancing frame from callback.")
var disconnectFlags int
// Make sure we fetch the inputs from GGPO and use these to update
// the game state instead of reading from the keyboard.
inputs, result := g.backend.SyncInput(&discconectFlags)
inputs, result := g.backend.SyncInput(&disconnectFlags)
if result == nil {
input := decodeInputs(inputs)
g.game.AdvanceFrame(input, discconectFlags)
g.game.AdvanceFrame(input, disconnectFlags)
}
}
func (g *GameSession) OnEvent(info *ggpo.Event) {
switch info.Code {
case ggpo.EventCodeConnectedToPeer:
log.Println("EventCodeConnectedToPeer")
case ggpo.EventCodeSynchronizingWithPeer:
log.Println("EventCodeSynchronizingWithPeer")
case ggpo.EventCodeSynchronizedWithPeer:
log.Println("EventCodeSynchronizedWithPeer")
case ggpo.EventCodeRunning:
log.Println("EventCodeRunning")
case ggpo.EventCodeDisconnectedFromPeer:
log.Println("EventCodeDisconnectedFromPeer")
case ggpo.EventCodeTimeSync:
log.Println("EventCodeTimeSync")
// We are fully connected now.
if world.ConnectPromptVisible {
log.Println("Connection established")
world.ConnectPromptVisible = false
}
case ggpo.EventCodeConnectionInterrupted:
log.Println("EventCodeconnectionInterrupted")
log.Println("Connection interrupted")
case ggpo.EventCodeConnectionResumed:
log.Println("EventCodeconnectionInterrupted")
log.Println("Connection resumed")
case ggpo.EventCodeDisconnectedFromPeer:
log.Println("Connection lost")
}
}

View File

@ -7,6 +7,7 @@ import (
"code.rocketnine.space/tslocum/boxbrawl/world"
"code.rocketnine.space/tslocum/gohan"
"github.com/hajimehoshi/ebiten/v2"
"github.com/hajimehoshi/ebiten/v2/ebitenutil"
)
type PlayerSystem struct {
@ -47,6 +48,11 @@ 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)
switch p.Action {
case component.ActionPunch:
ebitenutil.DebugPrintAt(screen, "PUNCH", int(p.X), int(p.Y))
}
}
return nil

View File

@ -31,7 +31,6 @@ type UISystem struct {
initialized bool
buffer *etk.Text
debugImg *ebiten.Image
}
func (u *UISystem) initialize() {
@ -44,9 +43,7 @@ func (u *UISystem) initialize() {
inputDemo.AddChild(u.buffer)
etk.SetRoot(inputDemo)
etk.Layout(world.ScreenWidth, world.ScreenHeight)
u.debugImg = ebiten.NewImage(128, 128)
etk.Layout(world.InternalScreenWidth, world.InternalScreenHeight)
u.initialized = true
}
@ -174,11 +171,7 @@ func (u *UISystem) Draw(e gohan.Entity, screen *ebiten.Image) error {
}
if world.Debug != 0 {
u.debugImg.Clear()
ebitenutil.DebugPrintAt(u.debugImg, 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)
op := &ebiten.DrawImageOptions{}
op.GeoM.Scale(2, 2)
screen.DrawImage(u.debugImg, op)
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)
}
return nil
}

View File

@ -9,6 +9,8 @@ const TPS = 60
const (
DefaultScreenWidth = 1280
DefaultScreenHeight = 720
InternalScreenWidth, InternalScreenHeight = 854, 480
)
var (