diff --git a/game/about_scene.go b/game/about_scene.go new file mode 100644 index 0000000..55889d0 --- /dev/null +++ b/game/about_scene.go @@ -0,0 +1,89 @@ +package game + +import ( + "image/color" + "log/slog" + "openquell/assets" + "openquell/gameui" + + "github.com/ebitenui/ebitenui" + "github.com/ebitenui/ebitenui/widget" + + "github.com/hajimehoshi/ebiten/v2" +) + +type AboutScene struct { + Game *Game + Next SceneName + Whoami SceneName + UseCache bool + Ui *ebitenui.UI +} + +const ABOUT string = ` +This is a little Quell clone written by +Thomas von Dein . + +Download it on repo.daemon.de/openquell/. + +Copyright (c) 2024 by Thomas von Dein. +` + +func NewAboutScene(game *Game) Scene { + scene := &AboutScene{Whoami: About, Game: game, Next: About} + + scene.SetupUI() + + return scene +} + +func (scene *AboutScene) GetNext() SceneName { + return scene.Next +} + +func (scene *AboutScene) SetNext(next SceneName) { + slog.Debug("about setnext", "next", next) + scene.Next = next +} + +func (scene *AboutScene) ResetNext() { + scene.Next = scene.Whoami +} + +func (scene *AboutScene) Update() error { + scene.Ui.Update() + return nil +} + +func (scene *AboutScene) Draw(screen *ebiten.Image) { + scene.Ui.Draw(screen) +} + +func (scene *AboutScene) SetupUI() { + blue := color.RGBA{0, 255, 128, 255} + + rowContainer := gameui.NewRowContainer() + + buttonBack := gameui.NewMenuButton("Back", *assets.FontRenderer.FontNormal, + func(args *widget.ButtonClickedEventArgs) { + scene.SetNext(Select) + }) + + label := widget.NewText( + widget.TextOpts.Text("About this game", *assets.FontRenderer.FontBig, blue), + widget.TextOpts.Position(widget.TextPositionCenter, widget.TextPositionCenter), + ) + + about := widget.NewText( + widget.TextOpts.Text(ABOUT, *assets.FontRenderer.FontNormal, blue), + widget.TextOpts.Position(widget.TextPositionStart, widget.TextPositionCenter), + ) + + rowContainer.AddChild(label) + rowContainer.AddChild(about) + rowContainer.AddChild(buttonBack) + + scene.Ui = &ebitenui.UI{ + Container: rowContainer.Container(), + } +} diff --git a/game/game.go b/game/game.go index acad457..5243705 100644 --- a/game/game.go +++ b/game/game.go @@ -35,6 +35,7 @@ func NewGame(width, height, cellsize, startlevel int, startscene SceneName) *Gam game.Scenes[Welcome] = NewWelcomeScene(game) game.Scenes[Select] = NewSelectScene(game) + game.Scenes[About] = NewAboutScene(game) game.Scenes[Play] = NewLevelScene(game, startlevel) game.CurrentScene = startscene @@ -62,6 +63,7 @@ func (game *Game) Update() error { next := scene.GetNext() if next != game.CurrentScene { + scene.ResetNext() game.CurrentScene = next } diff --git a/game/level_scene.go b/game/level_scene.go index 27a43d9..418d171 100644 --- a/game/level_scene.go +++ b/game/level_scene.go @@ -42,6 +42,10 @@ func (scene *LevelScene) GetNext() SceneName { return scene.Next } +func (scene *LevelScene) ResetNext() { + scene.Next = scene.Whoami +} + func (scene *LevelScene) Update() error { if scene.CurrentLevel != scene.Game.Observer.CurrentLevel { slog.Debug("level", "current", scene.CurrentLevel, "next", scene.Game.Observer.CurrentLevel) diff --git a/game/scene.go b/game/scene.go index 5d64933..0f2c2c3 100644 --- a/game/scene.go +++ b/game/scene.go @@ -20,6 +20,7 @@ const ( type Scene interface { SetNext(SceneName) GetNext() SceneName + ResetNext() Update() error Draw(screen *ebiten.Image) } diff --git a/game/select_scene.go b/game/select_scene.go index 34c1db5..8ffbb40 100644 --- a/game/select_scene.go +++ b/game/select_scene.go @@ -2,10 +2,12 @@ package game import ( "image/color" + "log/slog" "openquell/assets" + "openquell/gameui" + "os" "github.com/ebitenui/ebitenui" - "github.com/ebitenui/ebitenui/image" "github.com/ebitenui/ebitenui/widget" "github.com/hajimehoshi/ebiten/v2" @@ -31,7 +33,12 @@ func (scene *SelectScene) GetNext() SceneName { return scene.Next } +func (scene *SelectScene) ResetNext() { + scene.Next = scene.Whoami +} + func (scene *SelectScene) SetNext(next SceneName) { + slog.Debug("select setnext", "next", next) scene.Next = next } @@ -45,68 +52,42 @@ func (scene *SelectScene) Draw(screen *ebiten.Image) { } func (scene *SelectScene) SetupUI() { - buttonImage, _ := loadButtonImage() + blue := color.RGBA{0, 255, 128, 255} - btnContainer := widget.NewContainer( - widget.ContainerOpts.Layout(widget.NewAnchorLayout()), - ) + rowContainer := gameui.NewRowContainer() - button := widget.NewButton( - widget.ButtonOpts.WidgetOpts( - widget.WidgetOpts.LayoutData(widget.AnchorLayoutData{ - HorizontalPosition: widget.AnchorLayoutPositionCenter, - VerticalPosition: widget.AnchorLayoutPositionCenter, - }), - ), - - widget.ButtonOpts.Image(buttonImage), - - widget.ButtonOpts.Text("Start new Game", *assets.FontRenderer.FontNormal, &widget.ButtonTextColor{ - Idle: color.NRGBA{0xdf, 0xf4, 0xff, 0xff}, - }), - - widget.ButtonOpts.TextPadding(widget.Insets{ - Left: 30, - Right: 30, - Top: 5, - Bottom: 5, - }), - - widget.ButtonOpts.ClickedHandler(func(args *widget.ButtonClickedEventArgs) { + buttonStartnew := gameui.NewMenuButton("Start new game", *assets.FontRenderer.FontNormal, + func(args *widget.ButtonClickedEventArgs) { scene.SetNext(Play) - }), + }) + + buttonSelectLevel := gameui.NewMenuButton("Select Level", *assets.FontRenderer.FontNormal, + func(args *widget.ButtonClickedEventArgs) { + scene.SetNext(Select) + }) + + buttonAbout := gameui.NewMenuButton("About this game", *assets.FontRenderer.FontNormal, + func(args *widget.ButtonClickedEventArgs) { + scene.SetNext(About) + }) + + buttonQuit := gameui.NewMenuButton("Quit Game", *assets.FontRenderer.FontNormal, + func(args *widget.ButtonClickedEventArgs) { + os.Exit(1) // FIXME: present another scene "are you sure and/or thank you" + }) + + label := widget.NewText( + widget.TextOpts.Text("Menu", *assets.FontRenderer.FontBig, blue), + widget.TextOpts.Position(widget.TextPositionCenter, widget.TextPositionCenter), ) - btnContainer.AddChild(button) - - rootContainer := widget.NewContainer( - widget.ContainerOpts.BackgroundImage( - image.NewNineSlice(assets.Assets["background-transparent"], [3]int{0, 1, 639}, [3]int{0, 1, 479})), - //widget.ContainerOpts.BackgroundImage(image.NewNineSliceColor(color.NRGBA{0x13, 0x1a, 0x22, 0xff})), - //widget.ContainerOpts.BackgroundImage(image.NewNineSliceColor(color.NRGBA{0xff, 0x80, 0x00, 0x00})), - widget.ContainerOpts.Layout( - widget.NewStackedLayout( - widget.StackedLayoutOpts.Padding( - widget.NewInsetsSimple(25)))), - ) - - rootContainer.AddChild(btnContainer) + rowContainer.AddChild(label) + rowContainer.AddChild(buttonStartnew) + rowContainer.AddChild(buttonSelectLevel) + rowContainer.AddChild(buttonAbout) + rowContainer.AddChild(buttonQuit) scene.Ui = &ebitenui.UI{ - Container: rootContainer, + Container: rowContainer.Container(), } } - -func loadButtonImage() (*widget.ButtonImage, error) { - idle := image.NewNineSliceColor(color.NRGBA{R: 170, G: 170, B: 180, A: 255}) - - hover := image.NewNineSliceColor(color.NRGBA{R: 130, G: 130, B: 150, A: 255}) - - pressed := image.NewNineSliceColor(color.NRGBA{R: 100, G: 100, B: 120, A: 255}) - - return &widget.ButtonImage{ - Idle: idle, - Hover: hover, - Pressed: pressed, - }, nil -} diff --git a/game/welcome_scene.go b/game/welcome_scene.go index 2521d0f..f6186d4 100644 --- a/game/welcome_scene.go +++ b/game/welcome_scene.go @@ -3,9 +3,9 @@ package game import ( "image/color" "openquell/assets" + "openquell/gameui" "github.com/ebitenui/ebitenui" - "github.com/ebitenui/ebitenui/image" "github.com/ebitenui/ebitenui/widget" "github.com/hajimehoshi/ebiten/v2" ) @@ -32,6 +32,10 @@ func (scene *WelcomeScene) GetNext() SceneName { return scene.Next } +func (scene *WelcomeScene) ResetNext() { + scene.Next = scene.Whoami +} + func (scene *WelcomeScene) Update() error { switch { case ebiten.IsKeyPressed(ebiten.KeyEnter): @@ -47,78 +51,17 @@ func (scene *WelcomeScene) Draw(screen *ebiten.Image) { screen.Clear() scene.Ui.Draw(screen) - /* - op := &ebiten.DrawImageOptions{} - - background := assets.Assets["background-lila"] - screen.DrawImage(background, op) - - blue := color.RGBA{0, 255, 128, 255} - - assets.FontRenderer.Renderer.SetTarget(screen) - assets.FontRenderer.Renderer.SetColor(blue) - assets.FontRenderer.Renderer.SetAlign(etxt.YCenter, etxt.XCenter) - assets.FontRenderer.Renderer.SetSizePx(45) - assets.FontRenderer.Renderer.Draw("Welcome to Open Quell!", 320, 200) - - assets.FontRenderer.Renderer.SetAlign(etxt.Top, etxt.Left) - assets.FontRenderer.Renderer.SetSizePx(32) - assets.FontRenderer.Renderer.Draw("[press enter to start]", 100, 300) - */ } func (scene *WelcomeScene) SetupUI() { - buttonImage, _ := loadButtonImage() - background := assets.Assets["background-lila"] blue := color.RGBA{0, 255, 128, 255} - uiContainer := widget.NewContainer( - widget.ContainerOpts.BackgroundImage( - image.NewNineSlice(background, [3]int{0, 1, 639}, [3]int{0, 1, 479})), - widget.ContainerOpts.Layout(widget.NewAnchorLayout()), - ) + rowContainer := gameui.NewRowContainer() - rowContainer := widget.NewContainer( - widget.ContainerOpts.WidgetOpts( - widget.WidgetOpts.LayoutData(widget.AnchorLayoutData{ - HorizontalPosition: widget.AnchorLayoutPositionCenter, - VerticalPosition: widget.AnchorLayoutPositionCenter, - }), - ), - widget.ContainerOpts.Layout(widget.NewRowLayout( - widget.RowLayoutOpts.Direction(widget.DirectionVertical), - widget.RowLayoutOpts.Padding(widget.NewInsetsSimple(20)), - widget.RowLayoutOpts.Spacing(20), - )), - ) - - button := widget.NewButton( - widget.ButtonOpts.WidgetOpts( - widget.WidgetOpts.LayoutData(widget.RowLayoutData{ - Position: widget.RowLayoutPositionCenter, - Stretch: false, - MaxWidth: 200, - MaxHeight: 100, - }), - ), - - widget.ButtonOpts.Image(buttonImage), - - widget.ButtonOpts.Text("Start", *assets.FontRenderer.FontNormal, &widget.ButtonTextColor{ - Idle: color.NRGBA{0xdf, 0xf4, 0xff, 0xff}, - }), - - widget.ButtonOpts.TextPadding(widget.Insets{ - Left: 30, - Right: 30, - Top: 5, - Bottom: 5, - }), - - widget.ButtonOpts.ClickedHandler(func(args *widget.ButtonClickedEventArgs) { - scene.SetNext(Play) - }), - ) + button := gameui.NewMenuButton("Start", *assets.FontRenderer.FontNormal, + func(args *widget.ButtonClickedEventArgs) { + scene.SetNext(Select) + }) label := widget.NewText( widget.TextOpts.Text("Welcome to OpenQuell", *assets.FontRenderer.FontBig, blue), @@ -128,9 +71,7 @@ func (scene *WelcomeScene) SetupUI() { rowContainer.AddChild(label) rowContainer.AddChild(button) - uiContainer.AddChild(rowContainer) - scene.Ui = &ebitenui.UI{ - Container: uiContainer, + Container: rowContainer.Container(), } } diff --git a/gameui/widgets.go b/gameui/widgets.go new file mode 100644 index 0000000..2e71674 --- /dev/null +++ b/gameui/widgets.go @@ -0,0 +1,98 @@ +package gameui + +import ( + "image/color" + "openquell/assets" + + "github.com/ebitenui/ebitenui/image" + "github.com/ebitenui/ebitenui/widget" + "golang.org/x/image/font" +) + +func NewMenuButton(text string, fase font.Face, action func(args *widget.ButtonClickedEventArgs)) *widget.Button { + buttonImage, _ := LoadButtonImage() + + return widget.NewButton( + widget.ButtonOpts.WidgetOpts( + widget.WidgetOpts.LayoutData(widget.RowLayoutData{ + Position: widget.RowLayoutPositionCenter, + Stretch: true, + MaxWidth: 200, + MaxHeight: 100, + }), + ), + + widget.ButtonOpts.Image(buttonImage), + + widget.ButtonOpts.Text(text, *assets.FontRenderer.FontNormal, &widget.ButtonTextColor{ + Idle: color.NRGBA{0xdf, 0xf4, 0xff, 0xff}, + }), + + widget.ButtonOpts.TextPadding(widget.Insets{ + Left: 30, + Right: 30, + Top: 5, + Bottom: 5, + }), + + widget.ButtonOpts.ClickedHandler(action), + ) +} + +type RowContainer struct { + Root *widget.Container + Row *widget.Container +} + +func (container *RowContainer) AddChild(child widget.PreferredSizeLocateableWidget) { + container.Row.AddChild(child) +} + +func (container *RowContainer) Container() *widget.Container { + return container.Root +} + +func NewRowContainer() *RowContainer { + background := assets.Assets["background-lila"] + + uiContainer := widget.NewContainer( + widget.ContainerOpts.BackgroundImage( + image.NewNineSlice(background, [3]int{0, 1, 639}, [3]int{0, 1, 479})), + widget.ContainerOpts.Layout(widget.NewAnchorLayout()), + ) + + rowContainer := widget.NewContainer( + widget.ContainerOpts.WidgetOpts( + widget.WidgetOpts.LayoutData(widget.AnchorLayoutData{ + HorizontalPosition: widget.AnchorLayoutPositionCenter, + VerticalPosition: widget.AnchorLayoutPositionCenter, + }), + ), + widget.ContainerOpts.Layout(widget.NewRowLayout( + widget.RowLayoutOpts.Direction(widget.DirectionVertical), + widget.RowLayoutOpts.Padding(widget.NewInsetsSimple(20)), + widget.RowLayoutOpts.Spacing(10), + )), + ) + + uiContainer.AddChild(rowContainer) + + return &RowContainer{ + Root: uiContainer, + Row: rowContainer, + } +} + +func LoadButtonImage() (*widget.ButtonImage, error) { + idle := image.NewNineSliceColor(color.NRGBA{R: 0x55, G: 0x00, B: 0xe2, A: 255}) + + hover := image.NewNineSliceColor(color.NRGBA{R: 130, G: 130, B: 150, A: 255}) + + pressed := image.NewNineSliceColor(color.NRGBA{R: 100, G: 100, B: 120, A: 255}) + + return &widget.ButtonImage{ + Idle: idle, + Hover: hover, + Pressed: pressed, + }, nil +}