diff --git a/assets/EmpireStateNF.ttf b/assets/EmpireStateNF.ttf new file mode 100644 index 0000000..20369a9 Binary files /dev/null and b/assets/EmpireStateNF.ttf differ diff --git a/assets/background.png b/assets/background.png new file mode 100644 index 0000000..d9924d6 Binary files /dev/null and b/assets/background.png differ diff --git a/assets/embed.go b/assets/embed.go new file mode 100644 index 0000000..cdaf2cb --- /dev/null +++ b/assets/embed.go @@ -0,0 +1,13 @@ +package assets + +import ( + _ "embed" +) + +var ( + //go:embed EmpireStateNF.ttf + EmpireStateNF_ttf []byte + + //go:embed background.png + Background []byte +) diff --git a/docs/GameDesignDoc.md b/docs/GameDesignDoc.md new file mode 100644 index 0000000..991e0db --- /dev/null +++ b/docs/GameDesignDoc.md @@ -0,0 +1,54 @@ +# Pong: Game Design Document + +## Introduction + +This document outlines the design for a classic Pong game. Pong is a simple yet addictive two-dimensional table tennis video game. The objective is to defeat your opponent (or the AI) by deflecting a ball with a paddle and preventing it from reaching your goal. + +## Gameplay + +**Core Mechanic:** Players control paddles on opposite sides of a rectangular playing field. They use the paddles to deflect a moving ball and prevent it from entering their goal area. + +**Scoring:** A point is awarded to the player who fails to deflect the ball, allowing it to pass their paddle and reach the goal area. + +**Winning:** The first player to reach a predetermined score wins the game. + +## Game Features + +**Singleplayer:** Play against a computer-controlled opponent with adjustable difficulty levels. + +**Multiplayer:** Option for two players to compete locally on the same device. + +**Controls:** Players use keyboards or joysticks to move their paddles up and down. Simple one-button controls (up/down or left/right for paddles) are ideal. + +**Visuals:** Simple 2D graphics with a clean and minimalist aesthetic. The playing field, paddles, and ball will be represented by basic shapes. + +**Audio:** Basic sound effects for ball collisions with paddles and walls, and scoring events. Optional background music with low intensity to avoid distraction. + +**Difficulty:** Adjustable AI difficulty for the single-player mode, allowing players of various skill levels to enjoy the game. + +## Technical Specifications + +**Platform:** This game can be developed for various platforms, including PC, mobile devices (iOS and Android), and web browsers. The chosen platform will determine the specific programming languages and tools used. + +**Graphics:** 2D vector graphics are ideal for a clean and scalable visual style. +Physics: Simple collision detection between the ball, paddles, and walls will be implemented to govern ball movement. + +## Target Audience + +Pong is a casual game targeted towards a broad audience. Its simple mechanics and easy-to-understand gameplay make it enjoyable for players of all ages and gaming experience levels. + +## Monetization (Optional) + +This version of Pong can be completely free-to-play. + +For future versions, optional in-app purchases for cosmetic customization of paddles or visual themes can be considered. + +## Future Considerations + +**Power-Ups:** Introduce temporary power-ups that appear on the field during gameplay, granting players temporary advantages like paddle size increase, speed boost, or ball deflection manipulation. + +**Game Modes:** Include additional game modes like variations in field size, multiple balls, or wrapped edges where the ball can bounce off the sides and continue play. + +**Online Multiplayer:** Implement online multiplayer functionality for players to compete across a network. + +This Game Design Document provides a basic framework for the development of a Pong game. The features and technical specifications can be expanded upon to create a more engaging and feature-rich experience. \ No newline at end of file diff --git a/docs/GameObjects.md b/docs/GameObjects.md new file mode 100644 index 0000000..b26c264 --- /dev/null +++ b/docs/GameObjects.md @@ -0,0 +1,47 @@ +```mermaid +--- +title: Game Objects +--- +classDiagram + class Game { + + Cfg Cfg + + + state state + + playerOne paddle + + playerTwo paddle + + ball ball + + + background *ebiten.Image + + + Update() error + + Draw(*ebiten.Image) + + Layout(int, int) int int + + Run() error + + Reset() + + drawMainMenu(screen *ebiten.Image) + } + + class Cfg { + +screenWidth int + +screenHeight int + +WindowTitle string + faceSource *text.GoTextFaceSource + } + + class paddle { + + int x + + int y + + int dx + + *ebiten.Image + + int score + } + + class ball { + + int x + + int y + + int dx + + int dy + + *ebiten.Image + } + +``` \ No newline at end of file diff --git a/docs/GameStates.md b/docs/GameStates.md new file mode 100644 index 0000000..e8d3da1 --- /dev/null +++ b/docs/GameStates.md @@ -0,0 +1,31 @@ +```mermaid +--- +title: Game State Document +--- +stateDiagram-v2 + state "Main Menu" as main + state "Game Loop" as gameLoop + state "Paused" as pause + state "Game Over" as gameOver + state numPlayers <> + state restartQuit <> + + [*] --> main + + main --> numPlayers + numPlayers --> OnePlayer + numPlayers --> TwoPlayer + + OnePlayer --> gameLoop + TwoPlayer --> gameLoop + + gameLoop --> pause + pause --> gameLoop + gameLoop --> gameOver + gameOver --> restartQuit + + restartQuit --> Quit + restartQuit --> Restart + + Restart --> main + Quit --> [*] \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..b4c1225 --- /dev/null +++ b/go.mod @@ -0,0 +1,17 @@ +module gtihub.com/KalebHawkins/pong + +go 1.22.3 + +require github.com/hajimehoshi/ebiten/v2 v2.7.5 + +require ( + github.com/ebitengine/gomobile v0.0.0-20240518074828-e86332849895 // indirect + github.com/ebitengine/hideconsole v1.0.0 // indirect + github.com/ebitengine/purego v0.7.0 // indirect + github.com/go-text/typesetting v0.1.1-0.20240325125605-c7936fe59984 // indirect + github.com/jezek/xgb v1.1.1 // indirect + golang.org/x/image v0.16.0 // indirect + golang.org/x/sync v0.7.0 // indirect + golang.org/x/sys v0.20.0 // indirect + golang.org/x/text v0.15.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..f294f1d --- /dev/null +++ b/go.sum @@ -0,0 +1,24 @@ +github.com/ebitengine/gomobile v0.0.0-20240518074828-e86332849895 h1:48bCqKTuD7Z0UovDfvpCn7wZ0GUZ+yosIteNDthn3FU= +github.com/ebitengine/gomobile v0.0.0-20240518074828-e86332849895/go.mod h1:XZdLv05c5hOZm3fM2NlJ92FyEZjnslcMcNRrhxs8+8M= +github.com/ebitengine/hideconsole v1.0.0 h1:5J4U0kXF+pv/DhiXt5/lTz0eO5ogJ1iXb8Yj1yReDqE= +github.com/ebitengine/hideconsole v1.0.0/go.mod h1:hTTBTvVYWKBuxPr7peweneWdkUwEuHuB3C1R/ielR1A= +github.com/ebitengine/purego v0.7.0 h1:HPZpl61edMGCEW6XK2nsR6+7AnJ3unUxpTZBkkIXnMc= +github.com/ebitengine/purego v0.7.0/go.mod h1:ah1In8AOtksoNK6yk5z1HTJeUkC1Ez4Wk2idgGslMwQ= +github.com/go-text/typesetting v0.1.1-0.20240325125605-c7936fe59984 h1:NwCC36eQsDf1xVZG9jD7ngXNNjsvk8KXky15ogA1Vo0= +github.com/go-text/typesetting v0.1.1-0.20240325125605-c7936fe59984/go.mod h1:2+owI/sxa73XA581LAzVuEBZ3WEEV2pXeDswCH/3i1I= +github.com/go-text/typesetting-utils v0.0.0-20240317173224-1986cbe96c66 h1:GUrm65PQPlhFSKjLPGOZNPNxLCybjzjYBzjfoBGaDUY= +github.com/go-text/typesetting-utils v0.0.0-20240317173224-1986cbe96c66/go.mod h1:DDxDdQEnB70R8owOx3LVpEFvpMK9eeH1o2r0yZhFI9o= +github.com/hajimehoshi/bitmapfont/v3 v3.0.0 h1:r2+6gYK38nfztS/et50gHAswb9hXgxXECYgE8Nczmi4= +github.com/hajimehoshi/bitmapfont/v3 v3.0.0/go.mod h1:+CxxG+uMmgU4mI2poq944i3uZ6UYFfAkj9V6WqmuvZA= +github.com/hajimehoshi/ebiten/v2 v2.7.5 h1:jN6FnhCd9NGYCsm5GtrweuikrlyVGCSUpH5YgL+7UKA= +github.com/hajimehoshi/ebiten/v2 v2.7.5/go.mod h1:H2pHVgq29rfm5yeQ7jzWOM3VHsjo7/AyucODNLOhsVY= +github.com/jezek/xgb v1.1.1 h1:bE/r8ZZtSv7l9gk6nU0mYx51aXrvnyb44892TwSaqS4= +github.com/jezek/xgb v1.1.1/go.mod h1:nrhwO0FX/enq75I7Y7G8iN1ubpSGZEiA3v9e9GyRFlk= +golang.org/x/image v0.16.0 h1:9kloLAKhUufZhA12l5fwnx2NZW39/we1UhBesW433jw= +golang.org/x/image v0.16.0/go.mod h1:ugSZItdV4nOxyqp56HmXwH0Ry0nBCpjnZdpDaIHdoPs= +golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= diff --git a/main.go b/main.go new file mode 100644 index 0000000..f936058 --- /dev/null +++ b/main.go @@ -0,0 +1,14 @@ +package main + +import "gtihub.com/KalebHawkins/pong/pong" + +func main() { + cfg := &pong.Cfg{ + ScreenWidth: 640, + ScreenHeight: 480, + WindowTitle: "Pong", + } + + g := pong.NewGame(cfg) + g.Run() +} diff --git a/pong/pong.go b/pong/pong.go new file mode 100644 index 0000000..78bebba --- /dev/null +++ b/pong/pong.go @@ -0,0 +1,118 @@ +package pong + +import ( + "bytes" + "fmt" + "image/color" + + "github.com/hajimehoshi/ebiten/v2" + "github.com/hajimehoshi/ebiten/v2/ebitenutil" + "github.com/hajimehoshi/ebiten/v2/text/v2" + "gtihub.com/KalebHawkins/pong/assets" +) + +const ( + fontSize = 48 + titleFontSize = fontSize * 1.5 +) + +// Game is a structure containing the game data and configuration. +type Game struct { + *Cfg + state +} + +// Cfg contains the Game's configuration data. +type Cfg struct { + // ScreenWidth represents the width of the window + ScreenWidth int + // ScreenHeight represents the height of the window + ScreenHeight int + // WindowTitle is the title displayed in the window's title bar + WindowTitle string + // faceSource is the font face used for the menu, scoreboard, etc + faceSource *text.GoTextFaceSource + // backgroundImage is the background image + backgroundImage *ebiten.Image +} + +// NewGame returns a Game instance to be ran. +func NewGame(cfg *Cfg) *Game { + fs, err := text.NewGoTextFaceSource(bytes.NewReader(assets.EmpireStateNF_ttf)) + if err != nil { + panic(fmt.Sprintf("failed to load font file: %v", err)) + } + + bgImg, _, err := ebitenutil.NewImageFromReader(bytes.NewReader(assets.Background)) + if err != nil { + panic(fmt.Sprintf("failed to background texture: %v", err)) + } + + cfg.faceSource = fs + cfg.backgroundImage = bgImg + + return &Game{ + Cfg: cfg, + state: mainMenu, + } +} + +// Update manages user input, handles physics processes and updates game states. +func (g *Game) Update() error { + switch g.state { + case mainMenu: + + } + + return nil +} + +// Draw draws the appropriate images to the screen based on game state. +func (g *Game) Draw(screen *ebiten.Image) { + switch g.state { + case mainMenu: + g.drawMainMenu(screen) + } +} + +// Layout returns the screen's logical width and height. +func (g *Game) Layout(w, h int) (int, int) { + return w, h +} + +// Run runs begins the game loop. +func (g *Game) Run() error { + ebiten.SetWindowSize(g.Cfg.ScreenWidth, g.Cfg.ScreenHeight) + ebiten.SetWindowTitle(g.Cfg.WindowTitle) + + return ebiten.RunGame(g) +} + +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, + } + menuTextFace := &text.GoTextFace{ + Source: g.Cfg.faceSource, + Size: fontSize, + } + + opts := &text.DrawOptions{} + opts.GeoM.Translate(float64(g.ScreenWidth)/2, titleFontSize) + opts.PrimaryAlign = text.AlignCenter + opts.LineSpacing = titleFontSize + opts.ColorScale.ScaleWithColor(color.Black) + text.Draw(screen, "Pong", titleTextFace, opts) + + opts = &text.DrawOptions{} + opts.GeoM.Translate(float64(g.ScreenWidth)/2, 3*titleFontSize) + opts.LineSpacing = fontSize + opts.PrimaryAlign = text.AlignCenter + opts.ColorScale.ScaleWithColor(color.Black) + text.Draw(screen, "Single Player\nMultiplayer", menuTextFace, opts) +} diff --git a/pong/states.go b/pong/states.go new file mode 100644 index 0000000..cdaa335 --- /dev/null +++ b/pong/states.go @@ -0,0 +1,11 @@ +package pong + +// state represents the games state +type state int + +const ( + mainMenu state = iota + gameLoop + paused + gameOver +)