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:
parent
a6cb582254
commit
0cd5cb4ad1
@ -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)
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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() {
|
||||
|
@ -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() }
|
||||
|
@ -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()))
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
40
cmd/krampus19/navigation.go
Normal file
40
cmd/krampus19/navigation.go
Normal 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)
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
|
@ -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}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user