diff --git a/assets/fonts/x12y20pxScanLine.ttf b/assets/fonts/x12y20pxScanLine.ttf new file mode 100644 index 0000000..46900c5 Binary files /dev/null and b/assets/fonts/x12y20pxScanLine.ttf differ diff --git a/assets/loader-fonts.go b/assets/loader-fonts.go new file mode 100644 index 0000000..adee5fe --- /dev/null +++ b/assets/loader-fonts.go @@ -0,0 +1,59 @@ +package assets + +import ( + "log" + + "github.com/tinne26/etxt" +) + +var FontRenderer = LoadFonts("fonts") + +const ( + GameFont string = "x12y20pxScanLine" + FontSize int = 16 +) + +type Texter struct { + Renderer *etxt.Renderer +} + +func LoadFonts(dir string) *Texter { + fontlib := etxt.NewFontLibrary() + _, _, err := fontlib.ParseEmbedDirFonts(dir, assetfs) + if err != nil { + log.Fatalf("Error while loading fonts: %s", err.Error()) + } + + if !fontlib.HasFont(GameFont) { + log.Fatal("missing font: " + GameFont) + } + + err = fontlib.EachFont(checkMissingRunes) + if err != nil { + log.Fatal(err) + } + + renderer := etxt.NewStdRenderer() + + glyphsCache := etxt.NewDefaultCache(10 * 1024 * 1024) // 10MB + renderer.SetCacheHandler(glyphsCache.NewHandler()) + renderer.SetFont(fontlib.GetFont(GameFont)) + + return &Texter{renderer} +} + +// helper function used with FontLibrary.EachFont to make sure +// all loaded fonts contain the characters or alphabet we want +func checkMissingRunes(name string, font *etxt.Font) error { + const letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" + const symbols = "0123456789 .,;:!?-()[]{}_&#@" + + missing, err := etxt.GetMissingRunes(font, letters+symbols) + if err != nil { + return err + } + if len(missing) > 0 { + log.Fatalf("Font '%s' missing runes: %s", name, string(missing)) + } + return nil +} diff --git a/assets/loader-sprites.go b/assets/loader-sprites.go index 6429ee3..7a6fa4c 100644 --- a/assets/loader-sprites.go +++ b/assets/loader-sprites.go @@ -16,7 +16,7 @@ import ( // Maps image name to image data type AssetRegistry map[string]*ebiten.Image -//go:embed levels/*.lvl sprites/*.png +//go:embed levels/*.lvl sprites/*.png fonts/*.ttf var assetfs embed.FS var Assets = LoadImages("sprites") diff --git a/game/game.go b/game/game.go index 2acb0ce..f4e8493 100644 --- a/game/game.go +++ b/game/game.go @@ -13,7 +13,8 @@ type Game struct { World *ecs.World Bounds image.Rectangle ScreenWidth, ScreenHeight int - Scenes map[int]Scene + Scenes map[SceneName]Scene + CurrentScene SceneName Observer *observers.GameObserver } @@ -25,20 +26,26 @@ func NewGame(width, height, cellsize, startlevel int, startscene int) *Game { World: &world, ScreenWidth: width, ScreenHeight: height, - Scenes: map[int]Scene{}, + Scenes: map[SceneName]Scene{}, } observers.NewPlayerObserver(&world) observers.NewParticleObserver(&world) game.Observer = observers.NewGameObserver(&world, startlevel, width, height, cellsize) + game.Scenes[Welcome] = NewWelcomeScene(game) game.Scenes[Play] = NewLevelScene(game, startlevel) + game.CurrentScene = Welcome fmt.Println(game.World.Stats().String()) return game } +func (game *Game) GetCurrentScene() Scene { + return game.Scenes[game.CurrentScene] +} + func (game *Game) Update() error { gameobserver := observers.GetGameObserver(game.World) timer := gameobserver.StopTimer @@ -49,8 +56,12 @@ func (game *Game) Update() error { gameobserver.Score++ // FIXME: use level.Score(), see TODO } - for _, scene := range game.Scenes { - scene.Update() + scene := game.GetCurrentScene() + scene.Update() + + next := scene.GetNext() + if next != game.CurrentScene { + game.CurrentScene = next } timer.Update() @@ -59,9 +70,7 @@ func (game *Game) Update() error { } func (game *Game) Draw(screen *ebiten.Image) { - for _, scene := range game.Scenes { - scene.Draw(screen) - } + game.GetCurrentScene().Draw(screen) } func (g *Game) Layout(newWidth, newHeight int) (int, int) { diff --git a/game/levelscene.go b/game/levelscene.go index a9a4fed..27a43d9 100644 --- a/game/levelscene.go +++ b/game/levelscene.go @@ -11,14 +11,14 @@ type LevelScene struct { Game *Game CurrentLevel int Levels []*Level - Next int - Whoami int + Next SceneName + Whoami SceneName UseCache bool } // Implements the actual playing Scene func NewLevelScene(game *Game, startlevel int) Scene { - scene := &LevelScene{CurrentLevel: startlevel, Whoami: Play, Game: game} + scene := &LevelScene{CurrentLevel: startlevel, Whoami: Play, Next: Play, Game: game} scene.GenerateLevels(game) scene.Levels[scene.CurrentLevel].SetupGrid(game) @@ -33,12 +33,13 @@ func (scene *LevelScene) GenerateLevels(game *Game) { } // Interface methods -func (scene *LevelScene) SetNext() int { - if scene.Whoami != scene.Next { - return scene.Next - } +func (scene *LevelScene) SetNext(next SceneName) { + scene.Next = next +} - return 0 +func (scene *LevelScene) GetNext() SceneName { + // FIXME: set to winner or options screen + return scene.Next } func (scene *LevelScene) Update() error { diff --git a/game/scene.go b/game/scene.go index fdc7c50..5d64933 100644 --- a/game/scene.go +++ b/game/scene.go @@ -18,7 +18,10 @@ const ( // to render its content onto the running level, e.g. the options scene // etc. type Scene interface { - SetNext() int + SetNext(SceneName) + GetNext() SceneName Update() error Draw(screen *ebiten.Image) } + +type SceneName int diff --git a/game/welcome_scene.go b/game/welcome_scene.go new file mode 100644 index 0000000..4085ad0 --- /dev/null +++ b/game/welcome_scene.go @@ -0,0 +1,62 @@ +package game + +import ( + "image/color" + "log/slog" + "openquell/assets" + + "github.com/hajimehoshi/ebiten/v2" + "github.com/tinne26/etxt" +) + +type WelcomeScene struct { + Game *Game + Next SceneName + Whoami SceneName + UseCache bool +} + +func NewWelcomeScene(game *Game) Scene { + scene := &WelcomeScene{Whoami: Welcome, Game: game, Next: Welcome} + return scene +} + +func (scene *WelcomeScene) SetNext(next SceneName) { + scene.Next = next +} + +func (scene *WelcomeScene) GetNext() SceneName { + return scene.Next +} + +func (scene *WelcomeScene) Update() error { + switch { + case ebiten.IsKeyPressed(ebiten.KeyEnter): + slog.Debug("welcome.Update() next") + scene.SetNext(Play) + } + + return nil +} + +func (scene *WelcomeScene) Draw(screen *ebiten.Image) { + screen.Clear() + + 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) + +} diff --git a/go.mod b/go.mod index 3eb6fa8..0aaed6c 100644 --- a/go.mod +++ b/go.mod @@ -14,11 +14,13 @@ require ( github.com/jezek/xgb v1.1.0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/tlinden/yadu v0.1.2 // indirect + github.com/tinne26/etxt v0.0.8 // indirect + github.com/tlinden/yadu v0.1.3 // indirect golang.org/x/exp/shiny v0.0.0-20230817173708-d852ddb80c63 // indirect golang.org/x/image v0.12.0 // indirect golang.org/x/mobile v0.0.0-20230922142353-e2f452493d57 // indirect golang.org/x/sync v0.3.0 // indirect golang.org/x/sys v0.14.0 // indirect + golang.org/x/text v0.13.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 8a3bfcb..ea49675 100644 --- a/go.sum +++ b/go.sum @@ -15,8 +15,12 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mlange-42/arche v0.10.0 h1:fEFDAYMAnWa+xHc1oq4gVcA4PuEQOCGSRXSKITXawMw= github.com/mlange-42/arche v0.10.0/go.mod h1:gJ5J8vBreqrf4TcBomBFPGnWdE5P3qa4LtxYHn1gDcg= +github.com/tinne26/etxt v0.0.8 h1:rjb58jkMkapRGLmhBMWnT76E/nMTXC5P1Q956BRZkoc= +github.com/tinne26/etxt v0.0.8/go.mod h1:QM/hlNkstsKC39elTFNKAR34xsMb9QoVosf+g9wlYxM= github.com/tlinden/yadu v0.1.2 h1:TYYVnUJwziRJ9YPbIbRf9ikmDw0Q8Ifixm+J/kBQFh8= github.com/tlinden/yadu v0.1.2/go.mod h1:l3bRmHKL9zGAR6pnBHY2HRPxBecf7L74BoBgOOpTcUA= +github.com/tlinden/yadu v0.1.3 h1:5cRCUmj+l5yvlM2irtpFBIJwVV2DPEgYSaWvF19FtcY= +github.com/tlinden/yadu v0.1.3/go.mod h1:l3bRmHKL9zGAR6pnBHY2HRPxBecf7L74BoBgOOpTcUA= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= @@ -56,6 +60,7 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=