diff --git a/alui/button.go b/alui/button.go index 8fac85f..e0d6336 100644 --- a/alui/button.go +++ b/alui/button.go @@ -10,22 +10,22 @@ var _ Control = &Button{} type Button struct { ControlBase - Text string + Text string + TextAlign allg5.HorizontalAlignment } func (b *Button) DesiredSize(ctx *Context) geom.PointF32 { font := ctx.Fonts.Get(b.Font) - _, _, w, h := font.TextDimensions(b.Text) - return geom.PtF32(w+8, h+8) + w := font.TextWidth(b.Text) + return geom.PtF32(w+8, font.Height()+8) } func (b *Button) Render(ctx *Context, bounds geom.RectangleF32) { - font := ctx.Fonts.Get(b.Font) - fore := ctx.Palette.Primary if b.Over { fore = ctx.Palette.Dark ctx.Cursor = allg5.MouseCursorLink } - font.Draw(bounds.Min.X+4, bounds.Min.Y+4, fore, allg5.AlignLeft, b.Text) + font := ctx.Fonts.Get(b.Font) + ctx.Fonts.DrawAlignFont(font, bounds.Min.X+4, bounds.Min.Y+4, bounds.Max.X-4, fore, b.TextAlign, b.Text) } diff --git a/alui/fonts.go b/alui/fonts.go index 1ded3b8..3ae288d 100644 --- a/alui/fonts.go +++ b/alui/fonts.go @@ -2,6 +2,7 @@ package alui import ( "opslag.de/schobers/allg5" + "opslag.de/schobers/geom" ) type Fonts struct { @@ -25,6 +26,41 @@ func (f *Fonts) Destroy() { f.fonts = nil } +func (f *Fonts) Draw(font string, left, top float32, color allg5.Color, text string) { + f.DrawFont(f.Get(font), left, top, color, text) +} + +func (f *Fonts) DrawAlign(font string, left, top, right float32, color allg5.Color, align allg5.HorizontalAlignment, text string) { + f.DrawAlignFont(f.Get(font), left, top, right, color, align, text) +} + +func (f *Fonts) DrawAlignFont(font *allg5.Font, left, top, right float32, color allg5.Color, align allg5.HorizontalAlignment, text string) { + switch align { + case allg5.AlignCenter: + center, top := geom.Round32(.5*(left+right)), geom.Round32(top) + font.Draw(center, top, color, allg5.AlignCenter, text) + case allg5.AlignRight: + right, top = geom.Round32(right), geom.Round32(top) + font.Draw(right, top, color, allg5.AlignRight, text) + default: + left, top = geom.Round32(left), geom.Round32(top) + font.Draw(left, top, color, allg5.AlignRight, text) + } +} + +func (f *Fonts) DrawCenter(font string, center, top float32, color allg5.Color, text string) { + f.DrawCenterFont(f.Get(font), center, top, color, text) +} + +func (f *Fonts) DrawCenterFont(font *allg5.Font, center, top float32, color allg5.Color, text string) { + f.DrawAlignFont(font, center, top, center, color, allg5.AlignCenter, text) +} + +func (f *Fonts) DrawFont(font *allg5.Font, left, top float32, color allg5.Color, text string) { + left, top = geom.Round32(left), geom.Round32(top) + font.Draw(left, top, color, allg5.AlignLeft, text) +} + func (f *Fonts) Len() int { return len(f.fonts) } func (f *Fonts) Load(path string, size int, name string) error { diff --git a/alui/label.go b/alui/label.go index 73d2fa3..617a9b8 100644 --- a/alui/label.go +++ b/alui/label.go @@ -20,8 +20,6 @@ func (l *Label) DesiredSize(ctx *Context) geom.PointF32 { } func (l *Label) Render(ctx *Context, bounds geom.RectangleF32) { - font := ctx.Fonts.Get(l.Font) - back := l.Background fore := l.Foreground if fore == nil { @@ -31,5 +29,5 @@ func (l *Label) Render(ctx *Context, bounds geom.RectangleF32) { if back != nil { allg5.DrawFilledRectangle(bounds.Min.X, bounds.Min.Y, bounds.Max.X, bounds.Max.Y, *back) } - font.Draw(bounds.Min.X+4, bounds.Min.Y+4, *fore, allg5.AlignLeft, l.Text) + ctx.Fonts.Draw(l.Font, bounds.Min.X+4, bounds.Min.Y+4, *fore, l.Text) } diff --git a/alui/stackpanel.go b/alui/stackpanel.go index 37bf23a..dc5e0bc 100644 --- a/alui/stackpanel.go +++ b/alui/stackpanel.go @@ -78,8 +78,8 @@ func (s *StackPanel) DesiredSize(ctx *Context) geom.PointF32 { func (s *StackPanel) Layout(ctx *Context, bounds geom.RectangleF32) { s.Container.Layout(ctx, bounds) - desired, size := s.CalculateLayout(ctx) - width := s.asWidth(size) + desired, _ := s.CalculateLayout(ctx) + width := s.asWidth(bounds.Size()) var offset = bounds.Min for i, child := range s.Children { diff --git a/cmd/krampus19/console.go b/cmd/krampus19/console.go index 2357989..31ee202 100644 --- a/cmd/krampus19/console.go +++ b/cmd/krampus19/console.go @@ -27,7 +27,6 @@ func newConsole(cons *gut.Console) *console { func (c *console) Render(ctx *alui.Context, bounds geom.RectangleF32) { back := allg5.NewColorAlpha(0, 0, 0, 0x7f) line := allg5.NewColor(0x7f, 0x7f, 0x7f) - fore := allg5.NewColor(0xff, 0xff, 0xff) c.height = geom.Min32(c.height, bounds.Dy()) top := geom.Max32(bounds.Max.Y-c.height, bounds.Min.Y) @@ -65,7 +64,7 @@ func (c *console) Render(ctx *alui.Context, bounds geom.RectangleF32) { messageTop := size.Y - totalHeight - c.offset for _, m := range messages { if messageTop <= size.Y || (messageTop+lineHeight) >= 0 { - font.Draw(0, messageTop, fore, allg5.AlignLeft, m) + ctx.Fonts.DrawFont(font, 0, messageTop, ctx.Palette.Text, m) } messageTop += lineHeight } diff --git a/cmd/krampus19/context.go b/cmd/krampus19/context.go index 6837d6d..2a36498 100644 --- a/cmd/krampus19/context.go +++ b/cmd/krampus19/context.go @@ -3,28 +3,32 @@ package main import ( "time" + "opslag.de/schobers/krampus19/alui" + "opslag.de/schobers/allg5" "opslag.de/schobers/fs/vfs" ) -type Texture struct { +type texture struct { *allg5.Bitmap Subs map[string]*allg5.Bitmap } -func NewTexture(bmp *allg5.Bitmap) Texture { - return Texture{Bitmap: bmp, Subs: map[string]*allg5.Bitmap{}} +func newTexture(bmp *allg5.Bitmap) texture { + return texture{Bitmap: bmp, Subs: map[string]*allg5.Bitmap{}} } type Context struct { Resources vfs.CopyDir - Textures map[string]Texture + Textures map[string]texture Levels map[string]level Sprites map[string]sprite - Settings Settings + Settings settings + Palette *alui.Palette - Tick time.Duration + Tick time.Duration + Navigation navigation } func (c *Context) Destroy() { diff --git a/cmd/krampus19/game.go b/cmd/krampus19/game.go index 6ebc35b..796642e 100644 --- a/cmd/krampus19/game.go +++ b/cmd/krampus19/game.go @@ -12,7 +12,7 @@ import ( "opslag.de/schobers/krampus19/gut" ) -type Game struct { +type game struct { ctx *Context ui *alui.UI main alui.Container @@ -22,10 +22,17 @@ type Game struct { scene *alui.Overlay } -func (g *Game) initUI(disp *allg5.Display, cons *gut.Console, fps *gut.FPS) error { +func (g *game) initUI(disp *allg5.Display, cons *gut.Console, fps *gut.FPS) error { disp.SetWindowTitle("Krampushack '19 - Title TBD - by Tharro") ui := alui.NewUI(disp, &g.main) + ui.SetPalette(alui.Palette{ + Icon: allg5.NewColor(0x21, 0x21, 0x21), + Dark: allg5.NewColor(0xf7, 0xd1, 0x56), + Primary: allg5.NewColor(0xf7, 0xbd, 0x00), + Text: allg5.NewColor(0xff, 0xff, 0xff), + }) g.ui = ui + g.ctx.Palette = &g.ui.Context().Palette g.info = &alui.Overlay{Proxy: &info{fps: fps}} g.cons = &alui.Overlay{Proxy: newConsole(cons)} g.scene = &alui.Overlay{Proxy: &splash{}, Visible: true} @@ -33,7 +40,7 @@ func (g *Game) initUI(disp *allg5.Display, cons *gut.Console, fps *gut.FPS) erro return nil } -func (g *Game) loadBitmap(path, name string) error { +func (g *game) loadBitmap(path, name string) error { f, err := g.ctx.Resources.Open(path) if err != nil { return err @@ -47,11 +54,11 @@ func (g *Game) loadBitmap(path, name string) error { if err != nil { return err } - g.ctx.Textures[name] = NewTexture(bmp) + g.ctx.Textures[name] = newTexture(bmp) return nil } -func (g *Game) loadFonts() error { +func (g *game) loadFonts() error { openSansPath, err := g.ctx.Resources.Retrieve("fonts/OpenSans-Regular.ttf") if err != nil { return err @@ -60,11 +67,7 @@ func (g *Game) loadFonts() error { if err != nil { return err } - err = g.ui.Fonts().LoadFonts(alui.FontDescription{Path: openSansPath, Name: "default", Size: 12}) - if err != nil { - return err - } - err = g.ui.Fonts().LoadFonts(alui.FontDescription{Path: openSansPath, Name: "steps", Size: 36}) + err = g.ui.Fonts().LoadFonts(alui.FontDescription{Path: openSansPath, Name: "default", Size: 36}) if err != nil { return err } @@ -75,7 +78,7 @@ func (g *Game) loadFonts() error { return nil } -func (g *Game) loadTextures(pathToName map[string]string) error { +func (g *game) loadTextures(pathToName map[string]string) error { for path, name := range pathToName { err := g.loadBitmap(path, name) if err != nil { @@ -85,7 +88,7 @@ func (g *Game) loadTextures(pathToName map[string]string) error { return nil } -func (g *Game) loadLevels(names ...string) error { +func (g *game) loadLevels(names ...string) error { g.ctx.Levels = map[string]level{} for _, name := range names { fileName := fmt.Sprintf("levels/level%s.txt", name) @@ -103,7 +106,7 @@ func (g *Game) loadLevels(names ...string) error { return nil } -func (g *Game) loadSprites(names ...string) error { +func (g *game) loadSprites(names ...string) error { g.ctx.Sprites = map[string]sprite{} for _, name := range names { fileName := fmt.Sprintf("sprites/%s.txt", name) @@ -130,7 +133,7 @@ func (g *Game) loadSprites(names ...string) error { return nil } -func (g *Game) loadAssets() error { +func (g *game) loadAssets() error { log.Println("Loading textures...") err := g.loadTextures(map[string]string{ "basic_tile.png": "basic_tile", @@ -171,15 +174,14 @@ func (g *Game) loadAssets() error { return nil } -func (g *Game) Destroy() { +func (g *game) Destroy() { g.ui.Destroy() g.ctx.Destroy() } -func (g *Game) Init(disp *allg5.Display, res vfs.CopyDir, cons *gut.Console, fps *gut.FPS) error { +func (g *game) Init(disp *allg5.Display, res vfs.CopyDir, cons *gut.Console, fps *gut.FPS) error { log.Print("Initializing game...") - g.ctx = &Context{Resources: res, Textures: map[string]Texture{}} - g.ctx.Settings = newDefaultSettings() + g.ctx = &Context{Resources: res, Textures: map[string]texture{}, Settings: newDefaultSettings(), Navigation: navigation{game: g}} if err := g.initUI(disp, cons, fps); err != nil { return err } @@ -188,11 +190,13 @@ func (g *Game) Init(disp *allg5.Display, res vfs.CopyDir, cons *gut.Console, fps return err } log.Print("Loaded assets.") - g.playLevel("1") + + g.ctx.Navigation.showMainMenu() + return nil } -func (g *Game) Handle(e allg5.Event) { +func (g *game) Handle(e allg5.Event) { switch e := e.(type) { case *allg5.KeyDownEvent: if e.KeyCode == allg5.KeyF3 { @@ -205,18 +209,4 @@ func (g *Game) Handle(e allg5.Event) { g.ui.Handle(e) } -func (g *Game) playLevel(l string) { - play := &playLevel{ctx: g.ctx} - play.loadLevel(l) - g.scene.Proxy = play -} - -func (g *Game) Render() { - switch scene := g.scene.Proxy.(type) { - case *splash: - if scene.atEnd { - g.playLevel("1") - } - } - g.ui.Render() -} +func (g *game) Render() { g.ui.Render() } diff --git a/cmd/krampus19/info.go b/cmd/krampus19/info.go index 399ab60..22bbfbe 100644 --- a/cmd/krampus19/info.go +++ b/cmd/krampus19/info.go @@ -3,7 +3,6 @@ package main import ( "fmt" - "opslag.de/schobers/allg5" "opslag.de/schobers/geom" "opslag.de/schobers/krampus19/alui" "opslag.de/schobers/krampus19/gut" @@ -16,5 +15,5 @@ type info struct { } func (i *info) Render(ctx *alui.Context, bounds geom.RectangleF32) { - ctx.Fonts.Get("default").Draw(4, 4, allg5.NewColor(0xff, 0xff, 0xff), allg5.AlignLeft, fmt.Sprintf("FPS: %d", i.fps.Current())) + ctx.Fonts.Draw("console", 4, 4, ctx.Palette.Text, fmt.Sprintf("FPS: %d", i.fps.Current())) } diff --git a/cmd/krampus19/krampus19.go b/cmd/krampus19/krampus19.go index 229db76..d57437f 100644 --- a/cmd/krampus19/krampus19.go +++ b/cmd/krampus19/krampus19.go @@ -29,16 +29,17 @@ func resources() (vfs.CopyDir, error) { } func run() error { + defer log.Printf("All cleaned up, bye bye!") cons := &gut.Console{} log.SetOutput(io.MultiWriter(log.Writer(), cons)) - log.Println("Initializing Allegro") + log.Printf("Initializing Allegro") err := allg5.Init(allg5.InitAll) if err != nil { return err } - log.Println("Creating display") + log.Printf("Creating display") disp, err := allg5.NewDisplay(1440, 900, allg5.NewDisplayOptions{Maximized: false, Windowed: true, Resizable: true, Vsync: true}) if err != nil { return err @@ -64,20 +65,20 @@ func run() error { fps := gut.NewFPS() defer fps.Destroy() - game := &Game{} + game := &game{} err = game.Init(disp, res, cons, fps) if err != nil { return err } defer game.Destroy() - log.Println("Starting game loop") - back := allg5.NewColor(0x21, 0x21, 0x21) + log.Printf("Starting game loop") + defer log.Printf("Stopped game loop, cleaning up") start := time.Now() for { game.ctx.Tick = time.Now().Sub(start) - allg5.ClearToColor(back) + allg5.ClearToColor(game.ctx.Palette.Icon) game.Render() disp.Flip() fps.Count() @@ -86,14 +87,21 @@ func run() error { for e != nil { switch e := e.(type) { case *allg5.DisplayCloseEvent: + log.Printf("Stopping game loop, user closed display") return nil - case *allg5.KeyDownEvent: - if e.KeyCode == allg5.KeyEscape { + case *allg5.KeyCharEvent: + if e.KeyCode == allg5.KeyF4 && e.Modifiers&allg5.KeyModAlt != 0 { + log.Printf("Stopping game loop, user pressed Alt+F4") return nil } } game.Handle(e) e = eq.Get() } + + if game.scene.Proxy == nil { + log.Printf("Stopping game loop, user quit via an in-game option") + return nil + } } } diff --git a/cmd/krampus19/mainmenu.go b/cmd/krampus19/mainmenu.go index 41c5a68..7d58d73 100644 --- a/cmd/krampus19/mainmenu.go +++ b/cmd/krampus19/mainmenu.go @@ -1,3 +1,41 @@ package main -type mainMenu struct{} +import ( + "opslag.de/schobers/allg5" + "opslag.de/schobers/geom" + "opslag.de/schobers/krampus19/alui" +) + +type mainMenu struct { + alui.ControlBase + + ctx *Context + + buttons alui.StackPanel +} + +func (m *mainMenu) newButton(text string, onClick func()) alui.Control { + button := &alui.Button{Text: text, TextAlign: allg5.AlignCenter} + button.OnClick = onClick + return button +} + +func (m *mainMenu) Enter(ctx *Context) error { + m.ctx = ctx + m.buttons.Orientation = alui.OrientationVertical + m.buttons.Children = append(m.buttons.Children, m.newButton("Play", func() { m.ctx.Navigation.playLevel("1") })) + m.buttons.Children = append(m.buttons.Children, m.newButton("Quit", func() { m.ctx.Navigation.quit() })) + return nil +} + +func (m *mainMenu) Leave() {} + +func (m *mainMenu) Handle(e allg5.Event) { m.buttons.Handle(e) } + +func (m *mainMenu) Render(ctx *alui.Context, bounds geom.RectangleF32) { + buttonsHeight := m.buttons.DesiredSize(ctx).Y + width, center := bounds.Dx(), bounds.Center() + buttonsBounds := geom.RectF32(.25*width, center.Y-.5*buttonsHeight, .75*width, center.Y+.5*buttonsHeight) + m.buttons.Layout(ctx, buttonsBounds) + m.buttons.Render(ctx, buttonsBounds) +} diff --git a/cmd/krampus19/navigation.go b/cmd/krampus19/navigation.go new file mode 100644 index 0000000..ede6605 --- /dev/null +++ b/cmd/krampus19/navigation.go @@ -0,0 +1,40 @@ +package main + +import ( + "opslag.de/schobers/krampus19/alui" +) + +type navigation struct { + game *game + curr scene +} + +type scene interface { + alui.Control + + Enter(ctx *Context) error + Leave() +} + +func (n *navigation) playLevel(l string) { + n.switchTo(&playLevel{name: l}) +} + +func (n *navigation) quit() { + n.switchTo(nil) +} + +func (n *navigation) showMainMenu() { + n.switchTo(&mainMenu{}) +} + +func (n *navigation) switchTo(s scene) { + if n.curr != nil { + n.curr.Leave() + } + n.curr = s + n.game.scene.Proxy = s + if n.curr != nil { + n.curr.Enter(n.game.ctx) + } +} diff --git a/cmd/krampus19/playlevel.go b/cmd/krampus19/playlevel.go index 71ed6c1..b71ff25 100644 --- a/cmd/krampus19/playlevel.go +++ b/cmd/krampus19/playlevel.go @@ -15,6 +15,7 @@ import ( type playLevel struct { alui.ControlBase + name string ctx *Context offset geom.PointF32 scale float32 @@ -80,9 +81,9 @@ func (a *entityMoveAnimation) Animate(start, now time.Duration) bool { return true } -func (s *playLevel) idxToPos(i int) geom.PointF32 { return s.level.idxToPos(i).ToF32() } +func (l *playLevel) idxToPos(i int) geom.PointF32 { return l.level.idxToPos(i).ToF32() } -func (s *playLevel) isIdle() bool { return s.ani.Idle() } +func (l *playLevel) isIdle() bool { return l.ani.Idle() } func findEntityAt(entities []*entity, pos geom.Point) *entity { idx := findEntityIdx(entities, pos) @@ -101,48 +102,48 @@ func findEntityIdx(entities []*entity, pos geom.Point) int { return -1 } -func (s *playLevel) findEntityAt(pos geom.Point) *entity { - if s.player.pos == pos { - return s.player +func (l *playLevel) findEntityAt(pos geom.Point) *entity { + if l.player.pos == pos { + return l.player } - brick := findEntityAt(s.bricks, pos) + brick := findEntityAt(l.bricks, pos) if brick != nil { return brick } - return findEntityAt(s.sunken, pos) + return findEntityAt(l.sunken, pos) } -func (s *playLevel) checkTile(pos geom.Point, check func(pos geom.Point, idx int, t tile) bool) bool { - return s.checkTileNotFound(pos, check, false) +func (l *playLevel) checkTile(pos geom.Point, check func(pos geom.Point, idx int, t tile) bool) bool { + return l.checkTileNotFound(pos, check, false) } -func (s *playLevel) checkTileNotFound(pos geom.Point, check func(pos geom.Point, idx int, t tile) bool, notFound bool) bool { - idx := s.level.posToIdx(pos) +func (l *playLevel) checkTileNotFound(pos geom.Point, check func(pos geom.Point, idx int, t tile) bool, notFound bool) bool { + idx := l.level.posToIdx(pos) if idx == -1 { return notFound } - return check(pos, idx, s.level.tiles[idx]) + return check(pos, idx, l.level.tiles[idx]) } -func (s *playLevel) isSolidTile(pos geom.Point, idx int, t tile) bool { +func (l *playLevel) isSolidTile(pos geom.Point, idx int, t tile) bool { switch t { case tileBasic: return true case tileWater: - return findEntityAt(s.sunken, pos) != nil + return findEntityAt(l.sunken, pos) != nil } return false } -func (s *playLevel) wouldBrickSink(pos geom.Point, idx int, t tile) bool { - return t == tileWater && findEntityAt(s.sunken, pos) == nil +func (l *playLevel) wouldBrickSink(pos geom.Point, idx int, t tile) bool { + return t == tileWater && findEntityAt(l.sunken, pos) == nil } -func (s *playLevel) isObstructed(pos geom.Point, idx int, t tile) bool { - if findEntityAt(s.bricks, pos) != nil { +func (l *playLevel) isObstructed(pos geom.Point, idx int, t tile) bool { + if findEntityAt(l.bricks, pos) != nil { return true // brick } - switch s.level.tiles[idx] { + switch l.level.tiles[idx] { case tileWater: return false case tileBasic: @@ -151,45 +152,50 @@ func (s *playLevel) isObstructed(pos geom.Point, idx int, t tile) bool { return true } -func (s *playLevel) canMove(from, dir geom.Point) bool { +func (l *playLevel) canMove(from, dir geom.Point) bool { to := from.Add(dir) - if !s.checkTile(to, s.isSolidTile) { + if !l.checkTile(to, l.isSolidTile) { return false } - brick := findEntityAt(s.bricks, to) + brick := findEntityAt(l.bricks, to) if brick != nil { brickTo := to.Add(dir) - return !s.checkTileNotFound(brickTo, s.isObstructed, true) + return !l.checkTileNotFound(brickTo, l.isObstructed, true) } return true } -func (s *playLevel) loadLevel(name string) { - s.keysDown = keyPressedState{} - s.level = s.ctx.Levels[name] - s.bricks = nil - s.sunken = nil - for i, e := range s.level.entities { +func (l *playLevel) Enter(ctx *Context) error { + l.ctx = ctx + + l.keysDown = keyPressedState{} + l.level = l.ctx.Levels[l.name] + l.bricks = nil + l.sunken = nil + for i, e := range l.level.entities { switch e { case entityTypeBrick: - s.bricks = append(s.bricks, newEntity(e, s.level.idxToPos(i))) + l.bricks = append(l.bricks, newEntity(e, l.level.idxToPos(i))) case entityTypeCharacter: - s.player = newEntity(e, s.level.idxToPos(i)) + l.player = newEntity(e, l.level.idxToPos(i)) case entityTypeVillain: - s.villain = newEntity(e, s.level.idxToPos(i)) + l.villain = newEntity(e, l.level.idxToPos(i)) } } + return nil } -func (s *playLevel) posToScreen(p geom.Point) geom.PointF32 { - return s.posToScreenF32(p.ToF32()) +func (l *playLevel) Leave() {} + +func (l *playLevel) posToScreen(p geom.Point) geom.PointF32 { + return l.posToScreenF32(p.ToF32()) } -func (s *playLevel) posToScreenF32(p geom.PointF32) geom.PointF32 { +func (l *playLevel) posToScreenF32(p geom.PointF32) geom.PointF32 { pos := p.Add2D(.5, .5) pos = geom.PtF32(pos.X*148-pos.Y*46, pos.Y*82) - pos = geom.PtF32(pos.X*s.scale, pos.Y*s.scale) - return pos.Add(s.offset) + pos = geom.PtF32(pos.X*l.scale, pos.Y*l.scale) + return pos.Add(l.offset) } // <- 168-> @@ -205,90 +211,90 @@ func (s *playLevel) posToScreenF32(p geom.PointF32) geom.PointF32 { // Offset between horizontal tiles: 148,0 // Offset between vertical tiles: -46,82 -func (s *playLevel) Layout(ctx *alui.Context, bounds geom.RectangleF32) { - s.scale = bounds.Dy() / 1080 +func (l *playLevel) Layout(ctx *alui.Context, bounds geom.RectangleF32) { + l.scale = bounds.Dy() / 1080 - tilesCenter := geom.PtF32(.5*float32(s.level.width), .5*float32(s.level.height)) + tilesCenter := geom.PtF32(.5*float32(l.level.width), .5*float32(l.level.height)) tilesCenter = geom.PtF32(tilesCenter.X*148-tilesCenter.Y*46, tilesCenter.Y*82) - tilesCenter = geom.PtF32(tilesCenter.X*s.scale, tilesCenter.Y*s.scale) + tilesCenter = geom.PtF32(tilesCenter.X*l.scale, tilesCenter.Y*l.scale) center := bounds.Center() - s.offset = geom.PtF32(center.X-tilesCenter.X, center.Y-tilesCenter.Y) + l.offset = geom.PtF32(center.X-tilesCenter.X, center.Y-tilesCenter.Y) - s.ani.Animate(s.ctx.Tick) + l.ani.Animate(l.ctx.Tick) } -func (s *playLevel) tryPlayerMove(dir geom.Point, key allg5.Key) { - if !s.isIdle() { +func (l *playLevel) tryPlayerMove(dir geom.Point, key allg5.Key) { + if !l.isIdle() { return } - to := s.player.pos.Add(dir) - if !s.canMove(s.player.pos, dir) { + to := l.player.pos.Add(dir) + if !l.canMove(l.player.pos, dir) { log.Printf("Move is not allowed (tried out move to %s after key '%s' was pressed)", to, gut.KeyToString(key)) return } - s.steps++ + l.steps++ log.Printf("Moving player to %s", to) - s.ani.StartFn(s.ctx.Tick, newEntityMoveAnimation(s.player, to), func() { + l.ani.StartFn(l.ctx.Tick, newEntityMoveAnimation(l.player, to), func() { log.Printf("Player movement finished") - if s.keysDown[key] && s.keysDown.CountPressed(s.ctx.Settings.Controls.MovementKeys()...) == 1 { + if l.keysDown[key] && l.keysDown.CountPressed(l.ctx.Settings.Controls.MovementKeys()...) == 1 { log.Printf("Key %s is still down, moving further", gut.KeyToString(key)) - s.tryPlayerMove(dir, key) + l.tryPlayerMove(dir, key) } }) - if brick := findEntityAt(s.bricks, to); brick != nil { + if brick := findEntityAt(l.bricks, to); brick != nil { log.Printf("Pushing brick at %s", to) brickTo := to.Add(dir) - s.ani.StartFn(s.ctx.Tick, newEntityMoveAnimation(brick, brickTo), func() { + l.ani.StartFn(l.ctx.Tick, newEntityMoveAnimation(brick, brickTo), func() { log.Printf("Brick movement finished") - if s.checkTile(brickTo, s.wouldBrickSink) { + if l.checkTile(brickTo, l.wouldBrickSink) { log.Printf("Sinking brick at %s", brickTo) - idx := findEntityIdx(s.bricks, brickTo) - s.bricks = append(s.bricks[:idx], s.bricks[idx+1:]...) - s.sunken = append(s.sunken, brick) + idx := findEntityIdx(l.bricks, brickTo) + l.bricks = append(l.bricks[:idx], l.bricks[idx+1:]...) + l.sunken = append(l.sunken, brick) } }) } } -func (s *playLevel) Handle(e allg5.Event) { +func (l *playLevel) Handle(e allg5.Event) { switch e := e.(type) { case *allg5.KeyDownEvent: - s.keysDown[e.KeyCode] = true + l.keysDown[e.KeyCode] = true switch e.KeyCode { - case s.ctx.Settings.Controls.MoveUp: - s.tryPlayerMove(geom.Pt(0, -1), e.KeyCode) - case s.ctx.Settings.Controls.MoveRight: - s.tryPlayerMove(geom.Pt(1, 0), e.KeyCode) - case s.ctx.Settings.Controls.MoveDown: - s.tryPlayerMove(geom.Pt(0, 1), e.KeyCode) - case s.ctx.Settings.Controls.MoveLeft: - s.tryPlayerMove(geom.Pt(-1, 0), e.KeyCode) + case l.ctx.Settings.Controls.MoveUp: + l.tryPlayerMove(geom.Pt(0, -1), e.KeyCode) + case l.ctx.Settings.Controls.MoveRight: + l.tryPlayerMove(geom.Pt(1, 0), e.KeyCode) + case l.ctx.Settings.Controls.MoveDown: + l.tryPlayerMove(geom.Pt(0, 1), e.KeyCode) + case l.ctx.Settings.Controls.MoveLeft: + l.tryPlayerMove(geom.Pt(-1, 0), e.KeyCode) } case *allg5.KeyUpEvent: - s.keysDown[e.KeyCode] = false + l.keysDown[e.KeyCode] = false } } -func (s *playLevel) Render(ctx *alui.Context, bounds geom.RectangleF32) { - basicTile := s.ctx.Textures["basic_tile"] - waterTile := s.ctx.Textures["water_tile"] - sunkenBrickTile := s.ctx.Textures["sunken_brick_tile"] +func (l *playLevel) Render(ctx *alui.Context, bounds geom.RectangleF32) { + basicTile := l.ctx.Textures["basic_tile"] + waterTile := l.ctx.Textures["water_tile"] + sunkenBrickTile := l.ctx.Textures["sunken_brick_tile"] - opts := allg5.DrawOptions{Center: true, Scale: allg5.NewUniformScale(s.scale * (168 / float32(basicTile.Width())))} - level := s.level + opts := allg5.DrawOptions{Center: true, Scale: allg5.NewUniformScale(l.scale * (168 / float32(basicTile.Width())))} + level := l.level for i, t := range level.tiles { pos := geom.Pt(i%level.width, i/level.width) - scrPos := s.posToScreen(pos) + scrPos := l.posToScreen(pos) switch t { case tileBasic: basicTile.DrawOptions(scrPos.X, scrPos.Y, opts) case tileWater: - scrPos := s.posToScreenF32(pos.ToF32().Add2D(0, .2*s.scale)) - if findEntityAt(s.sunken, pos) == nil { + scrPos := l.posToScreenF32(pos.ToF32().Add2D(0, .2*l.scale)) + if findEntityAt(l.sunken, pos) == nil { waterTile.DrawOptions(scrPos.X, scrPos.Y, opts) } else { sunkenBrickTile.DrawOptions(scrPos.X, scrPos.Y, opts) @@ -296,15 +302,15 @@ func (s *playLevel) Render(ctx *alui.Context, bounds geom.RectangleF32) { } } - character := s.ctx.Textures["main_character"] - villain := s.ctx.Textures["villain_character"] - brick := s.ctx.Textures["brick"] - crate := s.ctx.Textures["crate"] + character := l.ctx.Textures["main_character"] + villain := l.ctx.Textures["villain_character"] + brick := l.ctx.Textures["brick"] + crate := l.ctx.Textures["crate"] var entities []*entity - entities = append(entities, s.player) - entities = append(entities, s.villain) - entities = append(entities, s.bricks...) + entities = append(entities, l.player) + entities = append(entities, l.villain) + entities = append(entities, l.bricks...) sort.Slice(entities, func(i, j int) bool { if entities[i].scr.Y == entities[j].scr.Y { @@ -314,27 +320,26 @@ func (s *playLevel) Render(ctx *alui.Context, bounds geom.RectangleF32) { }) for _, e := range entities { - scrPos := s.posToScreenF32(e.scr) + scrPos := l.posToScreenF32(e.scr) switch e.typ { case entityTypeCharacter: - scrPos := s.posToScreenF32(e.scr.Add2D(-.2*s.scale, -.9*s.scale)) + scrPos := l.posToScreenF32(e.scr.Add2D(-.2*l.scale, -.9*l.scale)) character.DrawOptions(scrPos.X, scrPos.Y, opts) case entityTypeVillain: - scrPos := s.posToScreenF32(e.scr.Add2D(-.2*s.scale, -.9*s.scale)) + scrPos := l.posToScreenF32(e.scr.Add2D(-.2*l.scale, -.9*l.scale)) villain.DrawOptions(scrPos.X, scrPos.Y, opts) case entityTypeBrick: - if findEntityAt(s.bricks, e.pos) == nil { + if findEntityAt(l.bricks, e.pos) == nil { break } - scrPos := s.posToScreenF32(e.scr.Add2D(-.2*s.scale, -.7*s.scale)) + scrPos := l.posToScreenF32(e.scr.Add2D(-.2*l.scale, -.7*l.scale)) brick.DrawOptions(scrPos.X, scrPos.Y, opts) case entityTypeCrate: crate.DrawOptions(scrPos.X, scrPos.Y, opts) } } - steps := fmt.Sprintf("STEPS: %d", s.steps) - stepsFont := ctx.Fonts.Get("steps") - stepsWidth := stepsFont.TextWidth(steps) - stepsFont.Draw(.5*(bounds.Dx()-stepsWidth), 24, allg5.NewColor(255, 255, 255), allg5.AlignCenter, steps) + font := ctx.Fonts.Get("default") + steps := fmt.Sprintf("STEPS: %d", l.steps) + ctx.Fonts.DrawAlignFont(font, bounds.Min.X, 24, bounds.Max.X, ctx.Palette.Text, allg5.AlignCenter, steps) } diff --git a/cmd/krampus19/settings.go b/cmd/krampus19/settings.go index 97a5211..13b2440 100644 --- a/cmd/krampus19/settings.go +++ b/cmd/krampus19/settings.go @@ -2,13 +2,13 @@ package main import "opslag.de/schobers/allg5" -type Settings struct { - Controls Controls +type settings struct { + Controls controls } -func newDefaultSettings() Settings { - return Settings{ - Controls: Controls{ +func newDefaultSettings() settings { + return settings{ + Controls: controls{ MoveUp: allg5.KeyUp, MoveRight: allg5.KeyRight, MoveDown: allg5.KeyDown, @@ -17,13 +17,13 @@ func newDefaultSettings() Settings { } } -type Controls struct { +type controls struct { MoveUp allg5.Key MoveRight allg5.Key MoveDown allg5.Key MoveLeft allg5.Key } -func (c Controls) MovementKeys() []allg5.Key { +func (c controls) MovementKeys() []allg5.Key { return []allg5.Key{c.MoveUp, c.MoveRight, c.MoveDown, c.MoveLeft} }