diff --git a/internal/config/config.go b/internal/config/config.go index 1e8ee5f..cb06a8d 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -33,6 +33,22 @@ const ( GroundY = 600 ) +type Resolution struct { + Width int + Height int + Label string +} + +var Resolutions = []Resolution{ + {640, 480, "640 x 480"}, + {800, 600, "800 x 600"}, + {1024, 768, "1024 x 768"}, + {1280, 960, "1280 x 960"}, + {1600, 1200, "1600 x 1200"}, +} + +const DefaultResolutionIdx = 1 + var ( BaseWidth = float32(ScreenWidth) / 10.0 BaseHeight = float32(ScreenHeight) / 10.0 diff --git a/internal/game/game.go b/internal/game/game.go index c7cd184..dd725e0 100644 --- a/internal/game/game.go +++ b/internal/game/game.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/vector" "github.com/ystepanoff/paragopher/internal/audio" "github.com/ystepanoff/paragopher/internal/config" @@ -24,6 +25,11 @@ type Game struct { showExitDialog bool showGameOverDialog bool + showResolutionMenu bool + + resolutionMenuIdx int + currentResolutionIdx int + fullscreen bool barrelAngle float64 barrelImage *ebiten.Image @@ -47,11 +53,13 @@ func NewGame() *Game { gameData = &utils.GameData{} } game := &Game{ - bullets: make([]*Bullet, 0), - lastShot: time.Now(), - gameData: gameData, - soundProfile: audio.NewSoundProfile(), - showIntro: true, + bullets: make([]*Bullet, 0), + lastShot: time.Now(), + gameData: gameData, + soundProfile: audio.NewSoundProfile(), + showIntro: true, + resolutionMenuIdx: config.DefaultResolutionIdx, + currentResolutionIdx: config.DefaultResolutionIdx, } game.initTurretImage() game.initBarrelImage() @@ -69,6 +77,9 @@ func NewGame() *Game { func (g *Game) Draw(screen *ebiten.Image) { if g.showIntro { g.drawIntro(screen) + if g.showResolutionMenu { + g.drawResolutionMenu(screen) + } return } g.drawTurret(screen) @@ -89,9 +100,23 @@ func (g *Game) Draw(screen *ebiten.Image) { if g.showGameOverDialog { showYesNoDialog(screen, "GAME OVER!\nWould you like to start again?") } + + if g.showResolutionMenu { + g.drawResolutionMenu(screen) + } } func (g *Game) Update() error { + if g.showResolutionMenu { + g.updateResolutionMenu() + return nil + } + if inpututil.IsKeyJustPressed(ebiten.KeyR) && + !g.showExitDialog && !g.showGameOverDialog { + g.resolutionMenuIdx = g.currentResolutionIdx + g.showResolutionMenu = true + return nil + } if g.showIntro { return nil } diff --git a/internal/game/intro.go b/internal/game/intro.go index 3f43671..83a27a5 100644 --- a/internal/game/intro.go +++ b/internal/game/intro.go @@ -16,10 +16,11 @@ import ( const ( introText = "P A R A G O P H E R" - introSkipText = "ENTER: skip intro" - rotateText = "LEFT (←), RIGHT (→): rotate barrel" - shootText = "SPACE: shoot bullets" - exitText = "ESCAPE: exit the game" + introSkipText = "ENTER: skip intro" + rotateText = "LEFT (←), RIGHT (→): rotate barrel" + shootText = "SPACE: shoot bullets" + resolutionText = "R: change resolution" + exitText = "ESCAPE: exit the game" scaleFactor = 4 ) @@ -67,6 +68,7 @@ func (g *Game) drawIntro(screen *ebiten.Image) { introSkipText, rotateText, shootText, + resolutionText, exitText, } @@ -99,7 +101,7 @@ func (g *Game) drawIntro(screen *ebiten.Image) { g.lastIntroStep = time.Now() } - if ebiten.IsKeyPressed(ebiten.KeyEnter) || g.isIntroFinished() { + if !g.showResolutionMenu && (ebiten.IsKeyPressed(ebiten.KeyEnter) || g.isIntroFinished()) { g.soundProfile.IntroPlayer.Close() g.showIntro = false } diff --git a/internal/game/menu.go b/internal/game/menu.go new file mode 100644 index 0000000..d7db946 --- /dev/null +++ b/internal/game/menu.go @@ -0,0 +1,96 @@ +package game + +import ( + "fmt" + + "github.com/hajimehoshi/ebiten/v2" + "github.com/hajimehoshi/ebiten/v2/ebitenutil" + "github.com/hajimehoshi/ebiten/v2/inpututil" + "github.com/hajimehoshi/ebiten/v2/vector" + "github.com/ystepanoff/paragopher/internal/config" +) + +func (g *Game) updateResolutionMenu() { + if inpututil.IsKeyJustPressed(ebiten.KeyUp) { + if g.resolutionMenuIdx > 0 { + g.resolutionMenuIdx-- + } + } + if inpututil.IsKeyJustPressed(ebiten.KeyDown) { + if g.resolutionMenuIdx < len(config.Resolutions)-1 { + g.resolutionMenuIdx++ + } + } + if inpututil.IsKeyJustPressed(ebiten.KeyEnter) { + res := config.Resolutions[g.resolutionMenuIdx] + ebiten.SetWindowSize(res.Width, res.Height) + g.currentResolutionIdx = g.resolutionMenuIdx + if g.fullscreen { + g.fullscreen = false + ebiten.SetFullscreen(false) + ebiten.SetCursorMode(ebiten.CursorModeVisible) + } + g.showResolutionMenu = false + } + if inpututil.IsKeyJustPressed(ebiten.KeyF) { + g.fullscreen = !g.fullscreen + ebiten.SetFullscreen(g.fullscreen) + if g.fullscreen { + ebiten.SetCursorMode(ebiten.CursorModeHidden) + } else { + ebiten.SetCursorMode(ebiten.CursorModeVisible) + } + g.showResolutionMenu = false + } + if inpututil.IsKeyJustPressed(ebiten.KeyEscape) || + inpututil.IsKeyJustPressed(ebiten.KeyR) { + g.showResolutionMenu = false + } +} + +func (g *Game) drawResolutionMenu(screen *ebiten.Image) { + overlay := ebiten.NewImage(screen.Bounds().Dx(), screen.Bounds().Dy()) + overlay.Fill(config.SemiTransparentBlack) + screen.DrawImage(overlay, nil) + + dialogWidth := 300 + dialogHeight := 50 + len(config.Resolutions)*20 + 60 + dialogX := (screen.Bounds().Dx() - dialogWidth) / 2 + dialogY := (screen.Bounds().Dy() - dialogHeight) / 2 + + dialog := ebiten.NewImage(dialogWidth, dialogHeight) + dialog.Fill(config.ColourDarkGrey) + + borderSize := float32(5) + vector.DrawFilledRect(dialog, 0, 0, float32(dialogWidth), borderSize, config.ColourBlack, false) + vector.DrawFilledRect(dialog, 0, float32(dialogHeight)-borderSize, float32(dialogWidth), borderSize, config.ColourBlack, false) + vector.DrawFilledRect(dialog, 0, 0, borderSize, float32(dialogHeight), config.ColourBlack, false) + vector.DrawFilledRect(dialog, float32(dialogWidth)-borderSize, 0, borderSize, float32(dialogHeight), config.ColourBlack, false) + + op := &ebiten.DrawImageOptions{} + op.GeoM.Translate(float64(dialogX), float64(dialogY)) + screen.DrawImage(dialog, op) + + ebitenutil.DebugPrintAt(screen, "RESOLUTION", dialogX+105, dialogY+15) + + for i, res := range config.Resolutions { + cursor := " " + if i == g.resolutionMenuIdx { + cursor = "> " + } + active := "" + if i == g.currentResolutionIdx && !g.fullscreen { + active = " *" + } + label := fmt.Sprintf("%s%s%s", cursor, res.Label, active) + ebitenutil.DebugPrintAt(screen, label, dialogX+70, dialogY+45+i*20) + } + + fullscreenLabel := "F: Toggle Fullscreen" + if g.fullscreen { + fullscreenLabel = "F: Toggle Fullscreen *" + } + footerY := dialogY + 45 + len(config.Resolutions)*20 + 10 + ebitenutil.DebugPrintAt(screen, fullscreenLabel, dialogX+55, footerY) + ebitenutil.DebugPrintAt(screen, "ENTER: Select ESC: Back", dialogX+40, footerY+20) +}