From 356b510286a90cd394ef624451835087b459da0f Mon Sep 17 00:00:00 2001 From: Sander Schobers Date: Sun, 29 Dec 2019 10:01:34 +0100 Subject: [PATCH] Added win condition. Renamed entities on disk & added missing sprites. Moved entity and animations to separate code unit. --- alui/margins.go | 4 +- cmd/krampus19/changesettings.go | 2 +- cmd/krampus19/entity.go | 42 +++++++ cmd/krampus19/game.go | 7 +- cmd/krampus19/levelpack.go | 17 +++ cmd/krampus19/levelselect.go | 1 - cmd/krampus19/playlevel.go | 106 ++++++++---------- cmd/krampus19/playlevelstate.go | 32 ++++-- ...y_main_character.png => entity_dragon.png} | Bin ...llain_character.png => entity_villain.png} | Bin .../{main_character.txt => dragon.txt} | 4 +- cmd/krampus19/res/sprites/villain.txt | 11 ++ 12 files changed, 146 insertions(+), 80 deletions(-) create mode 100644 cmd/krampus19/entity.go rename cmd/krampus19/res/{entity_main_character.png => entity_dragon.png} (100%) rename cmd/krampus19/res/{villain_character.png => entity_villain.png} (100%) rename cmd/krampus19/res/sprites/{main_character.txt => dragon.txt} (63%) create mode 100644 cmd/krampus19/res/sprites/villain.txt diff --git a/alui/margins.go b/alui/margins.go index 8fd1ad7..0b84940 100644 --- a/alui/margins.go +++ b/alui/margins.go @@ -21,10 +21,12 @@ func NewMargins(target Control, margins ...float32) *Margins { m.Top, m.Left, m.Bottom, m.Right = margins[0], margins[0], margins[0], margins[0] case 2: m.Top, m.Left, m.Bottom, m.Right = margins[0], margins[1], margins[0], margins[1] + case 3: + m.Top, m.Left, m.Bottom, m.Right = margins[0], margins[1], margins[2], margins[1] case 4: m.Top, m.Left, m.Bottom, m.Right = margins[0], margins[1], margins[2], margins[3] default: - panic("expected 1 (all same), 2 (vertical, horizontal) or 4 margins (all separately specified)") + panic("expected 1 (all same), 2 (vertical, horizontal), 3 (top, horizontal, bottom) or 4 margins (all separately specified)") } return m } diff --git a/cmd/krampus19/changesettings.go b/cmd/krampus19/changesettings.go index d2c2777..ad52b5d 100644 --- a/cmd/krampus19/changesettings.go +++ b/cmd/krampus19/changesettings.go @@ -182,7 +182,7 @@ func newSettingsHeader(label string) alui.Control { header := &settingsHeader{} header.Font = "header" header.Text = label - return alui.NewMargins(header, 3*margin, 0, 2*margin, 0) + return alui.NewMargins(header, 3*margin, 0, 2*margin) } type settingsRow struct { diff --git a/cmd/krampus19/entity.go b/cmd/krampus19/entity.go new file mode 100644 index 0000000..818e2cc --- /dev/null +++ b/cmd/krampus19/entity.go @@ -0,0 +1,42 @@ +package main + +import ( + "time" + + "opslag.de/schobers/geom" +) + +type entity struct { + typ entityType + pos geom.Point + scr geom.PointF32 +} + +func newEntity(typ entityType, pos geom.Point) *entity { + return &entity{typ, pos, pos.ToF32()} +} + +type entityMoveAnimation struct { + e *entity + from, to geom.Point + pos geom.PointF32 +} + +func newEntityMoveAnimation(e *entity, to geom.Point) *entityMoveAnimation { + ani := &entityMoveAnimation{e: e, from: e.pos, to: to, pos: e.pos.ToF32()} + ani.e.pos = to + return ani +} + +func (a *entityMoveAnimation) Animate(start, now time.Duration) bool { + const duration = 270 * time.Millisecond + + progress := float32((now-start)*1000/duration) * .001 + from, to := a.from.ToF32(), a.to.ToF32() + if progress >= 1 { + a.e.scr = to + return false + } + a.e.scr = to.Sub(from).Mul(progress).Add(from) + return true +} diff --git a/cmd/krampus19/game.go b/cmd/krampus19/game.go index 0e92c39..8177c0c 100644 --- a/cmd/krampus19/game.go +++ b/cmd/krampus19/game.go @@ -149,8 +149,9 @@ func (g *game) loadSprites(names ...string) error { func (g *game) loadAssets() error { log.Println("Loading textures...") err := g.loadTextures(map[string]string{ - "entity_brick.png": "brick", - "entity_main_character.png": "main_character", + "entity_brick.png": "brick", + "entity_dragon.png": "dragon", + "entity_villain.png": "villain", "tile_lava_brick.png": "lava_brick", "tile_magma.png": "magma", @@ -177,7 +178,7 @@ func (g *game) loadAssets() error { log.Printf("Loaded %d fonts.\n", g.ui.Fonts().Len()) log.Println("Loading sprites") - err = g.loadSprites("brick", "lava_brick", "magma", "main_character", "ui") + err = g.loadSprites("brick", "dragon", "lava_brick", "magma", "ui", "villain") if err != nil { return err } diff --git a/cmd/krampus19/levelpack.go b/cmd/krampus19/levelpack.go index 0f19044..2cb087b 100644 --- a/cmd/krampus19/levelpack.go +++ b/cmd/krampus19/levelpack.go @@ -12,6 +12,23 @@ type levelPack struct { levels map[string]level } +func (p levelPack) find(level string) int { + for i, l := range p.order { + if l == level { + return i + } + } + return -1 +} + +func (p levelPack) FindNext(level string) (string, bool) { + idx := p.find(level) + if idx == -1 || idx == len(p.order)-1 { + return "", false + } + return p.order[idx+1], true +} + type parseLevelPackContext struct { name string levels []string diff --git a/cmd/krampus19/levelselect.go b/cmd/krampus19/levelselect.go index 08e7055..42ccc79 100644 --- a/cmd/krampus19/levelselect.go +++ b/cmd/krampus19/levelselect.go @@ -21,7 +21,6 @@ func (s *levelSelect) Enter(ctx *Context) error { s.Init() name := func(id string) string { return fmt.Sprintf("Level %s", id) } for _, id := range s.pack.order { - // level := s.pack[id] levelID := id s.Add(name(levelID), func() { s.ctx.Navigation.PlayLevel(s.packID, levelID) diff --git a/cmd/krampus19/playlevel.go b/cmd/krampus19/playlevel.go index ef4ee57..1f8387e 100644 --- a/cmd/krampus19/playlevel.go +++ b/cmd/krampus19/playlevel.go @@ -3,7 +3,6 @@ package main import ( "fmt" "sort" - "time" "opslag.de/schobers/allg5" "opslag.de/schobers/geom" @@ -17,6 +16,7 @@ type playLevel struct { init bool menu *alui.Menu + end alui.Control showMenu bool packID string @@ -39,43 +39,6 @@ func (s keyPressedState) CountPressed(keys ...allg5.Key) int { return cnt } -type entity struct { - typ entityType - pos geom.Point - scr geom.PointF32 -} - -func newEntity(typ entityType, pos geom.Point) *entity { - return &entity{typ, pos, pos.ToF32()} -} - -type posToScrFn func(geom.Point) geom.PointF32 - -type entityMoveAnimation struct { - e *entity - from, to geom.Point - pos geom.PointF32 -} - -func newEntityMoveAnimation(e *entity, to geom.Point) *entityMoveAnimation { - ani := &entityMoveAnimation{e: e, from: e.pos, to: to, pos: e.pos.ToF32()} - ani.e.pos = to - return ani -} - -func (a *entityMoveAnimation) Animate(start, now time.Duration) bool { - const duration = 210 * time.Millisecond - - progress := float32((now-start)*1000/duration) * .001 - from, to := a.from.ToF32(), a.to.ToF32() - if progress >= 1 { - a.e.scr = to - return false - } - a.e.scr = to.Sub(from).Mul(progress).Add(from) - return true -} - func (l *playLevel) Enter(ctx *Context) error { l.ctx = ctx @@ -88,12 +51,27 @@ func (l *playLevel) Enter(ctx *Context) error { l.menu.OnEscape = func() { l.showMenu = false } l.init = true - l.state.Init(l.ctx, l.packID, l.levelID) + l.state.Init(l.ctx, l.packID, l.levelID, l.onComplete) return nil } func (l *playLevel) Leave() {} +func (l *playLevel) onComplete() { + menu := alui.NewMenu() + menu.AddChild(alui.NewMargins(&alui.Label{ControlBase: alui.ControlBase{Font: "header"}, Text: "Congratulations", TextAlign: allg5.AlignCenter}, 3*margin, 0, 2*margin)) + menu.AddChild(alui.NewMargins(&alui.Label{Text: fmt.Sprintf("You've completed the level in %d steps", l.state.steps), TextAlign: allg5.AlignCenter}, 0, 0, 2*margin)) + + nextID, ok := l.state.pack.FindNext(l.levelID) + if ok { + menu.Add("Continue with next", func() { l.ctx.Navigation.PlayLevel(l.packID, nextID) }) + } + menu.Add("Select level", func() { l.ctx.Navigation.SelectLevel(l.packID) }) + menu.Add("Go to main menu", func() { l.ctx.Navigation.ShowMainMenu() }) + + l.end = menu +} + func (l *playLevel) posToScreenF32(p geom.PointF32, z float32) geom.PointF32 { pos := l.posToCabinet(p.Add2D(.5, .5)).Add2D(0, z) return pos.Mul(l.scale).Add(l.offset) @@ -156,25 +134,27 @@ func (l *playLevel) Handle(e allg5.Event) { l.state.ReleaseKey(e.KeyCode) } - if l.showMenu { + switch { + case l.showMenu: l.menu.Handle(e) - return - } - - switch e := e.(type) { - case *allg5.KeyCharEvent: - switch e.KeyCode { - case allg5.KeyEscape: - l.showMenu = true - l.menu.Activate(0) - case l.ctx.Settings.Controls.MoveUp: - l.state.TryPlayerMove(geom.Pt(0, -1), e.KeyCode) - case l.ctx.Settings.Controls.MoveRight: - l.state.TryPlayerMove(geom.Pt(1, 0), e.KeyCode) - case l.ctx.Settings.Controls.MoveDown: - l.state.TryPlayerMove(geom.Pt(0, 1), e.KeyCode) - case l.ctx.Settings.Controls.MoveLeft: - l.state.TryPlayerMove(geom.Pt(-1, 0), e.KeyCode) + case l.state.complete: + l.end.Handle(e) + default: + switch e := e.(type) { + case *allg5.KeyCharEvent: + switch e.KeyCode { + case allg5.KeyEscape: + l.showMenu = true + l.menu.Activate(0) + case l.ctx.Settings.Controls.MoveUp: + l.state.TryPlayerMove(geom.Pt(0, -1), e.KeyCode) + case l.ctx.Settings.Controls.MoveRight: + l.state.TryPlayerMove(geom.Pt(1, 0), e.KeyCode) + case l.ctx.Settings.Controls.MoveDown: + l.state.TryPlayerMove(geom.Pt(0, 1), e.KeyCode) + case l.ctx.Settings.Controls.MoveLeft: + l.state.TryPlayerMove(geom.Pt(-1, 0), e.KeyCode) + } } } } @@ -217,10 +197,12 @@ func (l *playLevel) Render(ctx *alui.Context, bounds geom.RectangleF32) { for _, e := range entities { switch e.typ { - case entityTypeCharacter: - l.drawSprite("main_character", "main_character", e.scr) case entityTypeBrick: l.drawSprite("brick", "brick", e.scr) + case entityTypeCharacter: + l.drawSprite("dragon", "dragon", e.scr) + case entityTypeVillain: + l.drawSprite("villain", "villain", e.scr) } } @@ -228,8 +210,12 @@ func (l *playLevel) Render(ctx *alui.Context, bounds geom.RectangleF32) { steps := fmt.Sprintf("STEPS: %d", l.state.Steps()) ctx.Fonts.DrawAlignFont(font, bounds.Min.X, 24, bounds.Max.X, ctx.Palette.Text, allg5.AlignCenter, steps) - if l.showMenu { + switch { + case l.showMenu: allg5.DrawFilledRectangle(bounds.Min.X, bounds.Min.Y, bounds.Max.X, bounds.Max.Y, allg5.NewColorAlpha(0, 0, 0, 0xaf)) l.menu.Render(ctx, bounds) + case l.state.complete: + allg5.DrawFilledRectangle(bounds.Min.X, bounds.Min.Y, bounds.Max.X, bounds.Max.Y, allg5.NewColorAlpha(0, 0, 0, 0xaf)) + l.end.Render(ctx, bounds) } } diff --git a/cmd/krampus19/playlevelstate.go b/cmd/krampus19/playlevelstate.go index 10e3f81..6db5166 100644 --- a/cmd/krampus19/playlevelstate.go +++ b/cmd/krampus19/playlevelstate.go @@ -29,13 +29,17 @@ func findEntityIdx(entities []*entity, pos geom.Point) int { type playLevelState struct { ctx *Context - pack levelPack - level level - player *entity - villain *entity - bricks []*entity - sunken []*entity - steps int + pack levelPack + level level + player *entity + villain *entity + bricks []*entity + sunken []*entity + + steps int + complete bool + onComplete func() + tick time.Duration ani gut.Animations keysDown keyPressedState @@ -44,9 +48,7 @@ type playLevelState struct { func (s *playLevelState) Entities() []*entity { var entities []*entity entities = append(entities, s.player) - if s.villain != nil { - entities = append(entities, s.villain) - } + entities = append(entities, s.villain) entities = append(entities, s.bricks...) return entities } @@ -62,7 +64,7 @@ func (s *playLevelState) IsNextToMagma(pos geom.Point) bool { s.checkTile(pos.Add2D(0, 1), s.isMagma) } -func (s *playLevelState) Init(ctx *Context, pack, level string) { +func (s *playLevelState) Init(ctx *Context, pack, level string, onComplete func()) { s.ctx = ctx s.pack = ctx.Levels[pack] s.level = s.pack.levels[level] @@ -79,6 +81,7 @@ func (s *playLevelState) Init(ctx *Context, pack, level string) { } } s.keysDown = keyPressedState{} + s.onComplete = onComplete } func (s *playLevelState) Level() level { return s.level } @@ -112,7 +115,12 @@ func (s *playLevelState) TryPlayerMove(dir geom.Point, key allg5.Key) { log.Printf("Moving player to %s", to) s.ani.StartFn(s.ctx.Tick, newEntityMoveAnimation(s.player, to), func() { log.Printf("Player movement finished") - if s.keysDown[key] && s.keysDown.CountPressed(s.ctx.Settings.Controls.MovementKeys()...) == 1 { + if s.player.pos == s.villain.pos { + s.complete = true + if onComplete := s.onComplete; onComplete != nil { + onComplete() + } + } else if s.keysDown[key] && s.keysDown.CountPressed(s.ctx.Settings.Controls.MovementKeys()...) == 1 { log.Printf("Key %s is still down, moving further", gut.KeyToString(key)) s.TryPlayerMove(dir, key) } diff --git a/cmd/krampus19/res/entity_main_character.png b/cmd/krampus19/res/entity_dragon.png similarity index 100% rename from cmd/krampus19/res/entity_main_character.png rename to cmd/krampus19/res/entity_dragon.png diff --git a/cmd/krampus19/res/villain_character.png b/cmd/krampus19/res/entity_villain.png similarity index 100% rename from cmd/krampus19/res/villain_character.png rename to cmd/krampus19/res/entity_villain.png diff --git a/cmd/krampus19/res/sprites/main_character.txt b/cmd/krampus19/res/sprites/dragon.txt similarity index 63% rename from cmd/krampus19/res/sprites/main_character.txt rename to cmd/krampus19/res/sprites/dragon.txt index f602196..ca12d9c 100644 --- a/cmd/krampus19/res/sprites/main_character.txt +++ b/cmd/krampus19/res/sprites/dragon.txt @@ -1,8 +1,8 @@ sprite: -texture: main_character +texture: dragon part: -name: main_character +name: dragon sub_texture: 0,0,200,400 anchor: 100,350 scale: 2 diff --git a/cmd/krampus19/res/sprites/villain.txt b/cmd/krampus19/res/sprites/villain.txt new file mode 100644 index 0000000..f319284 --- /dev/null +++ b/cmd/krampus19/res/sprites/villain.txt @@ -0,0 +1,11 @@ +sprite: +texture: villain + +part: +name: villain +sub_texture: 0,0,200,400 +anchor: 100,350 +scale: 2 +:part + +:sprite \ No newline at end of file