diff --git a/assets/ball.png b/assets/ball.png new file mode 100644 index 0000000..ac2dba8 Binary files /dev/null and b/assets/ball.png differ diff --git a/assets/embed.go b/assets/embed.go index cdaf2cb..93dcefc 100644 --- a/assets/embed.go +++ b/assets/embed.go @@ -9,5 +9,11 @@ var ( EmpireStateNF_ttf []byte //go:embed background.png - Background []byte + Background_png []byte + + //go:embed paddle.png + Paddle_png []byte + + //go:embed ball.png + Ball_png []byte ) diff --git a/assets/paddle.png b/assets/paddle.png new file mode 100644 index 0000000..a9c7607 Binary files /dev/null and b/assets/paddle.png differ diff --git a/docs/GameObjects.md b/docs/GameObjects.md index 45c036d..0ae1a96 100644 --- a/docs/GameObjects.md +++ b/docs/GameObjects.md @@ -18,6 +18,7 @@ classDiagram + Run() error + Reset() + drawMainMenu(screen *ebiten.Image) + + drawGameLoop(screen *ebiten.Image) } class Cfg { @@ -25,7 +26,9 @@ classDiagram + screenHeight int + WindowTitle string + faceSource *text.GoTextFaceSource - + background *ebiten.Image + + backgroundImage *ebiten.Image + + paddleImage *ebiten.Image + + ballImage *ebiten.Image } class paddle { diff --git a/pong/ball.go b/pong/ball.go new file mode 100644 index 0000000..d5cf056 --- /dev/null +++ b/pong/ball.go @@ -0,0 +1,9 @@ +package pong + +import "github.com/hajimehoshi/ebiten/v2" + +type ball struct { + x, y int + dx, dy int + sprite *ebiten.Image +} diff --git a/pong/paddle.go b/pong/paddle.go new file mode 100644 index 0000000..58aecf1 --- /dev/null +++ b/pong/paddle.go @@ -0,0 +1,10 @@ +package pong + +import "github.com/hajimehoshi/ebiten/v2" + +type paddle struct { + x, y int + dx int + score int + sprite *ebiten.Image +} diff --git a/pong/pong.go b/pong/pong.go index 78bebba..88ce5dd 100644 --- a/pong/pong.go +++ b/pong/pong.go @@ -7,6 +7,7 @@ import ( "github.com/hajimehoshi/ebiten/v2" "github.com/hajimehoshi/ebiten/v2/ebitenutil" + "github.com/hajimehoshi/ebiten/v2/inpututil" "github.com/hajimehoshi/ebiten/v2/text/v2" "gtihub.com/KalebHawkins/pong/assets" ) @@ -14,12 +15,17 @@ import ( const ( fontSize = 48 titleFontSize = fontSize * 1.5 + paddleWidth = 20 ) // Game is a structure containing the game data and configuration. type Game struct { *Cfg state + + playerOne paddle + playerTwo paddle + ball } // Cfg contains the Game's configuration data. @@ -34,6 +40,15 @@ type Cfg struct { faceSource *text.GoTextFaceSource // backgroundImage is the background image backgroundImage *ebiten.Image + // paddleImage is the image used for the paddle + paddleImage *ebiten.Image + // ballImage is the image used for the ball + ballImage *ebiten.Image + + // verticalLine is used as the central delimiter of the screen vertically.. + verticalLine *ebiten.Image + // horizontalLine is used as the central delimiter of the screen horizontally. + horizontalLine *ebiten.Image } // NewGame returns a Game instance to be ran. @@ -43,17 +58,52 @@ func NewGame(cfg *Cfg) *Game { panic(fmt.Sprintf("failed to load font file: %v", err)) } - bgImg, _, err := ebitenutil.NewImageFromReader(bytes.NewReader(assets.Background)) + bgImg, _, err := ebitenutil.NewImageFromReader(bytes.NewReader(assets.Background_png)) if err != nil { panic(fmt.Sprintf("failed to background texture: %v", err)) } + paddleImg, _, err := ebitenutil.NewImageFromReader(bytes.NewReader(assets.Paddle_png)) + if err != nil { + panic(fmt.Sprintf("failed to load paddle texture: %v", err)) + } + + ballImg, _, err := ebitenutil.NewImageFromReader(bytes.NewReader(assets.Ball_png)) + if err != nil { + panic(fmt.Sprintf("failed to load ball texture: %v", err)) + } + cfg.faceSource = fs cfg.backgroundImage = bgImg + cfg.paddleImage = paddleImg + cfg.ballImage = ballImg + cfg.verticalLine = ebiten.NewImage(1, cfg.ScreenHeight) + cfg.horizontalLine = ebiten.NewImage(cfg.ScreenWidth, 1) return &Game{ Cfg: cfg, - state: mainMenu, + state: gameLoop, + playerOne: paddle{ + x: 10 + paddleWidth/2, + y: cfg.ScreenHeight / 2, + dx: 0, + score: 0, + sprite: ebiten.NewImageFromImage(cfg.paddleImage), + }, + playerTwo: paddle{ + x: cfg.ScreenWidth - paddleWidth/2 - 10, + y: cfg.ScreenHeight / 2, + dx: 0, + score: 0, + sprite: ebiten.NewImageFromImage(cfg.paddleImage), + }, + ball: ball{ + x: cfg.ScreenWidth / 2, + y: cfg.ScreenHeight / 2, + dx: 0, + dy: 0, + sprite: ebiten.NewImageFromImage(cfg.ballImage), + }, } } @@ -61,6 +111,10 @@ func NewGame(cfg *Cfg) *Game { func (g *Game) Update() error { switch g.state { case mainMenu: + if inpututil.IsKeyJustPressed(ebiten.KeyEnter) { + g.state = gameLoop + } + case gameLoop: } @@ -69,10 +123,29 @@ func (g *Game) Update() error { // Draw draws the appropriate images to the screen based on game state. func (g *Game) Draw(screen *ebiten.Image) { + imgOpts := &ebiten.DrawImageOptions{} + imgOpts.GeoM.Scale(3, 2) + screen.DrawImage(g.backgroundImage, imgOpts) + + op := &ebiten.DrawImageOptions{} + g.Cfg.verticalLine.Fill(color.White) + op.GeoM.Translate(-float64(g.Cfg.verticalLine.Bounds().Dx())/2, 0) + op.GeoM.Translate(float64(g.Cfg.ScreenWidth/2), 0) + screen.DrawImage(g.Cfg.verticalLine, op) + + op = &ebiten.DrawImageOptions{} + g.Cfg.horizontalLine.Fill(color.White) + op.GeoM.Translate(0, -float64(g.Cfg.horizontalLine.Bounds().Dy()/2)) + op.GeoM.Translate(0, float64(g.Cfg.ScreenHeight)/2) + screen.DrawImage(g.Cfg.horizontalLine, op) + switch g.state { case mainMenu: g.drawMainMenu(screen) + case gameLoop: + g.drawGameLoop(screen) } + } // Layout returns the screen's logical width and height. @@ -89,10 +162,6 @@ func (g *Game) Run() error { } func (g *Game) drawMainMenu(screen *ebiten.Image) { - imgOpts := &ebiten.DrawImageOptions{} - imgOpts.GeoM.Scale(3, 2) - screen.DrawImage(g.backgroundImage, imgOpts) - titleTextFace := &text.GoTextFace{ Source: g.Cfg.faceSource, Size: titleFontSize, @@ -116,3 +185,20 @@ func (g *Game) drawMainMenu(screen *ebiten.Image) { opts.ColorScale.ScaleWithColor(color.Black) text.Draw(screen, "Single Player\nMultiplayer", menuTextFace, opts) } + +func (g *Game) drawGameLoop(screen *ebiten.Image) { + op := &ebiten.DrawImageOptions{} + op.GeoM.Translate(-float64(g.playerOne.sprite.Bounds().Dx())/2, -float64(g.playerOne.sprite.Bounds().Dy())/2) + op.GeoM.Translate(float64(g.playerOne.x), float64(g.playerOne.y)) + screen.DrawImage(g.playerOne.sprite, op) + + op = &ebiten.DrawImageOptions{} + op.GeoM.Translate(-float64(g.playerTwo.sprite.Bounds().Dx())/2, -float64(g.playerTwo.sprite.Bounds().Dy())/2) + op.GeoM.Translate(float64(g.playerTwo.x), float64(g.playerTwo.y)) + screen.DrawImage(g.playerTwo.sprite, op) + + op = &ebiten.DrawImageOptions{} + op.GeoM.Translate(-float64(g.ball.sprite.Bounds().Dx())/2, -float64(g.ball.sprite.Bounds().Dy())/2) + op.GeoM.Translate(float64(g.ball.x), float64(g.ball.y)) + screen.DrawImage(g.ball.sprite, op) +}