Added simple main menu.

Refactored some structs from public to internal.
Separated navigation from game.
Added utility methods for drawing text.
Stackpanel will uses all available width when layouting.
This commit is contained in:
Sander Schobers 2019-12-24 01:27:05 +01:00
parent a6cb582254
commit 0cd5cb4ad1
13 changed files with 282 additions and 165 deletions

View File

@ -11,21 +11,21 @@ type Button struct {
ControlBase
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)
}

View File

@ -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 {

View File

@ -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)
}

View File

@ -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 {

View File

@ -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
}

View File

@ -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
Navigation navigation
}
func (c *Context) Destroy() {

View File

@ -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() }

View File

@ -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()))
}

View File

@ -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
}
}
}

View File

@ -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)
}

View File

@ -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)
}
}

View File

@ -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)
}

View File

@ -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}
}