Added new tile graphics.
Added sprites for all textures. Removed splash screen animation.
@ -136,15 +136,11 @@ func (g *game) loadSprites(names ...string) error {
|
|||||||
func (g *game) loadAssets() error {
|
func (g *game) loadAssets() error {
|
||||||
log.Println("Loading textures...")
|
log.Println("Loading textures...")
|
||||||
err := g.loadTextures(map[string]string{
|
err := g.loadTextures(map[string]string{
|
||||||
"basic_tile.png": "basic_tile",
|
"entity_brick.png": "brick",
|
||||||
"sunken_brick_tile.png": "sunken_brick_tile",
|
"entity_main_character.png": "main_character",
|
||||||
"water_tile.png": "water_tile",
|
|
||||||
|
|
||||||
"main_character.png": "main_character",
|
"tile_lava_brick.png": "lava_brick",
|
||||||
"villain_character.png": "villain_character",
|
"tile_magma.png": "magma",
|
||||||
|
|
||||||
"brick.png": "brick",
|
|
||||||
"crate.png": "crate",
|
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -166,10 +162,10 @@ func (g *game) loadAssets() error {
|
|||||||
log.Printf("Loaded %d fonts.\n", g.ui.Fonts().Len())
|
log.Printf("Loaded %d fonts.\n", g.ui.Fonts().Len())
|
||||||
|
|
||||||
log.Println("Loading sprites")
|
log.Println("Loading sprites")
|
||||||
// err = g.loadSprites("dragon")
|
err = g.loadSprites("brick", "lava_brick", "magma", "main_character")
|
||||||
// if err != nil {
|
if err != nil {
|
||||||
// return err
|
return err
|
||||||
// }
|
}
|
||||||
log.Printf("Loaded %d sprites.\n", len(g.ctx.Sprites))
|
log.Printf("Loaded %d sprites.\n", len(g.ctx.Sprites))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,6 @@ const (
|
|||||||
entityTypeCharacter = '@'
|
entityTypeCharacter = '@'
|
||||||
entityTypeVillain = 'X'
|
entityTypeVillain = 'X'
|
||||||
entityTypeBrick = 'B'
|
entityTypeBrick = 'B'
|
||||||
entityTypeCrate = 'C'
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (e entityType) IsValid() bool {
|
func (e entityType) IsValid() bool {
|
||||||
@ -26,7 +25,6 @@ func (e entityType) IsValid() bool {
|
|||||||
case entityTypeCharacter:
|
case entityTypeCharacter:
|
||||||
case entityTypeVillain:
|
case entityTypeVillain:
|
||||||
case entityTypeBrick:
|
case entityTypeBrick:
|
||||||
case entityTypeCrate:
|
|
||||||
default:
|
default:
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -37,14 +35,14 @@ const (
|
|||||||
tileInvalid tile = tile(0)
|
tileInvalid tile = tile(0)
|
||||||
tileNothing = '.'
|
tileNothing = '.'
|
||||||
tileBasic = '#'
|
tileBasic = '#'
|
||||||
tileWater = '~'
|
tileMagma = '~'
|
||||||
)
|
)
|
||||||
|
|
||||||
func (t tile) IsValid() bool {
|
func (t tile) IsValid() bool {
|
||||||
switch t {
|
switch t {
|
||||||
case tileNothing:
|
case tileNothing:
|
||||||
case tileBasic:
|
case tileBasic:
|
||||||
case tileWater:
|
case tileMagma:
|
||||||
default:
|
default:
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -2,14 +2,12 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"sort"
|
"sort"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"opslag.de/schobers/allg5"
|
"opslag.de/schobers/allg5"
|
||||||
"opslag.de/schobers/geom"
|
"opslag.de/schobers/geom"
|
||||||
"opslag.de/schobers/krampus19/alui"
|
"opslag.de/schobers/krampus19/alui"
|
||||||
"opslag.de/schobers/krampus19/gut"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type playLevel struct {
|
type playLevel struct {
|
||||||
@ -20,21 +18,12 @@ type playLevel struct {
|
|||||||
menu *alui.Menu
|
menu *alui.Menu
|
||||||
showMenu bool
|
showMenu bool
|
||||||
|
|
||||||
name string
|
name string
|
||||||
offset geom.PointF32
|
offset geom.PointF32
|
||||||
scale float32
|
scale float32
|
||||||
keysDown keyPressedState
|
|
||||||
|
|
||||||
level level
|
state playLevelState
|
||||||
player *entity
|
|
||||||
villain *entity
|
|
||||||
bricks []*entity
|
|
||||||
sunken []*entity
|
|
||||||
steps int
|
|
||||||
tick time.Duration
|
|
||||||
ani gut.Animations
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type keyPressedState map[allg5.Key]bool
|
type keyPressedState map[allg5.Key]bool
|
||||||
|
|
||||||
func (s keyPressedState) CountPressed(keys ...allg5.Key) int {
|
func (s keyPressedState) CountPressed(keys ...allg5.Key) int {
|
||||||
@ -84,90 +73,6 @@ func (a *entityMoveAnimation) Animate(start, now time.Duration) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *playLevel) idxToPos(i int) geom.PointF32 { return l.level.idxToPos(i).ToF32() }
|
|
||||||
|
|
||||||
func (l *playLevel) isIdle() bool { return l.ani.Idle() }
|
|
||||||
|
|
||||||
func findEntityAt(entities []*entity, pos geom.Point) *entity {
|
|
||||||
idx := findEntityIdx(entities, pos)
|
|
||||||
if idx == -1 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return entities[idx]
|
|
||||||
}
|
|
||||||
|
|
||||||
func findEntityIdx(entities []*entity, pos geom.Point) int {
|
|
||||||
for i, e := range entities {
|
|
||||||
if e.pos == pos {
|
|
||||||
return i
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *playLevel) findEntityAt(pos geom.Point) *entity {
|
|
||||||
if l.player.pos == pos {
|
|
||||||
return l.player
|
|
||||||
}
|
|
||||||
brick := findEntityAt(l.bricks, pos)
|
|
||||||
if brick != nil {
|
|
||||||
return brick
|
|
||||||
}
|
|
||||||
return findEntityAt(l.sunken, pos)
|
|
||||||
}
|
|
||||||
|
|
||||||
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 (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, l.level.tiles[idx])
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *playLevel) isSolidTile(pos geom.Point, idx int, t tile) bool {
|
|
||||||
switch t {
|
|
||||||
case tileBasic:
|
|
||||||
return true
|
|
||||||
case tileWater:
|
|
||||||
return findEntityAt(l.sunken, pos) != nil
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *playLevel) wouldBrickSink(pos geom.Point, idx int, t tile) bool {
|
|
||||||
return t == tileWater && findEntityAt(l.sunken, pos) == nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *playLevel) isObstructed(pos geom.Point, idx int, t tile) bool {
|
|
||||||
if findEntityAt(l.bricks, pos) != nil {
|
|
||||||
return true // brick
|
|
||||||
}
|
|
||||||
switch l.level.tiles[idx] {
|
|
||||||
case tileWater:
|
|
||||||
return false
|
|
||||||
case tileBasic:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *playLevel) canMove(from, dir geom.Point) bool {
|
|
||||||
to := from.Add(dir)
|
|
||||||
if !l.checkTile(to, l.isSolidTile) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
brick := findEntityAt(l.bricks, to)
|
|
||||||
if brick != nil {
|
|
||||||
brickTo := to.Add(dir)
|
|
||||||
return !l.checkTileNotFound(brickTo, l.isObstructed, true)
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *playLevel) Enter(ctx *Context) error {
|
func (l *playLevel) Enter(ctx *Context) error {
|
||||||
l.ctx = ctx
|
l.ctx = ctx
|
||||||
|
|
||||||
@ -176,20 +81,7 @@ func (l *playLevel) Enter(ctx *Context) error {
|
|||||||
l.menu.Add("Quit to main menu", func() { l.ctx.Navigation.showMainMenu() })
|
l.menu.Add("Quit to main menu", func() { l.ctx.Navigation.showMainMenu() })
|
||||||
l.menu.Add("Quit to desktop", func() { l.ctx.Navigation.quit() })
|
l.menu.Add("Quit to desktop", func() { l.ctx.Navigation.quit() })
|
||||||
|
|
||||||
l.keysDown = keyPressedState{}
|
l.state.Init(l.ctx, l.name)
|
||||||
l.level = l.ctx.Levels[l.name]
|
|
||||||
l.bricks = nil
|
|
||||||
l.sunken = nil
|
|
||||||
for i, e := range l.level.entities {
|
|
||||||
switch e {
|
|
||||||
case entityTypeBrick:
|
|
||||||
l.bricks = append(l.bricks, newEntity(e, l.level.idxToPos(i)))
|
|
||||||
case entityTypeCharacter:
|
|
||||||
l.player = newEntity(e, l.level.idxToPos(i))
|
|
||||||
case entityTypeVillain:
|
|
||||||
l.villain = newEntity(e, l.level.idxToPos(i))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -201,78 +93,43 @@ func (l *playLevel) posToScreen(p geom.Point) geom.PointF32 {
|
|||||||
|
|
||||||
func (l *playLevel) posToScreenF32(p geom.PointF32) geom.PointF32 {
|
func (l *playLevel) posToScreenF32(p geom.PointF32) geom.PointF32 {
|
||||||
pos := p.Add2D(.5, .5)
|
pos := p.Add2D(.5, .5)
|
||||||
pos = geom.PtF32(pos.X*148-pos.Y*46, pos.Y*82)
|
pos = geom.PtF32(pos.X*100-pos.Y*50, pos.Y*40)
|
||||||
pos = geom.PtF32(pos.X*l.scale, pos.Y*l.scale)
|
return pos.Mul(l.scale).Add(l.offset)
|
||||||
return pos.Add(l.offset)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// <- 168->
|
// <- 150->
|
||||||
// <-128->
|
// <-100->
|
||||||
//
|
//
|
||||||
// /----/| ^ ^
|
// /----/| ^ ^
|
||||||
// / / / | 72
|
// / / | | 40
|
||||||
// /----/ / 92 v ^
|
// /----/ | 140 v ^
|
||||||
// |----|/ v v 20
|
// | | | | |
|
||||||
|
// | | / | 100
|
||||||
|
// |----|/ v v
|
||||||
//
|
//
|
||||||
// Gap: 20
|
// Center: 74,19
|
||||||
// Center: 84,46
|
// Offset between horizontal tiles: 100,0
|
||||||
// Offset between horizontal tiles: 148,0
|
// Offset between vertical tiles: -25,40
|
||||||
// Offset between vertical tiles: -46,82
|
|
||||||
|
|
||||||
func (l *playLevel) Layout(ctx *alui.Context, bounds geom.RectangleF32) {
|
func (l *playLevel) Layout(ctx *alui.Context, bounds geom.RectangleF32) {
|
||||||
l.scale = bounds.Dy() / 1080
|
l.scale = bounds.Dy() / 1080
|
||||||
|
|
||||||
tilesCenter := geom.PtF32(.5*float32(l.level.width), .5*float32(l.level.height))
|
level := l.state.Level()
|
||||||
tilesCenter = geom.PtF32(tilesCenter.X*148-tilesCenter.Y*46, tilesCenter.Y*82)
|
tilesCenter := geom.PtF32(.5*float32(level.width), .5*float32(level.height))
|
||||||
|
tilesCenter = geom.PtF32(tilesCenter.X*100-tilesCenter.Y*25, tilesCenter.Y*40)
|
||||||
tilesCenter = geom.PtF32(tilesCenter.X*l.scale, tilesCenter.Y*l.scale)
|
tilesCenter = geom.PtF32(tilesCenter.X*l.scale, tilesCenter.Y*l.scale)
|
||||||
center := bounds.Center()
|
center := bounds.Center()
|
||||||
l.offset = geom.PtF32(center.X-tilesCenter.X, center.Y-tilesCenter.Y)
|
l.offset = geom.PtF32(center.X-tilesCenter.X, center.Y-tilesCenter.Y)
|
||||||
|
|
||||||
l.ani.Animate(l.ctx.Tick)
|
l.state.Tick(l.ctx.Tick)
|
||||||
}
|
|
||||||
|
|
||||||
func (l *playLevel) tryPlayerMove(dir geom.Point, key allg5.Key) {
|
|
||||||
if !l.isIdle() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
l.steps++
|
|
||||||
log.Printf("Moving player to %s", to)
|
|
||||||
l.ani.StartFn(l.ctx.Tick, newEntityMoveAnimation(l.player, to), func() {
|
|
||||||
log.Printf("Player movement finished")
|
|
||||||
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))
|
|
||||||
l.tryPlayerMove(dir, key)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
if brick := findEntityAt(l.bricks, to); brick != nil {
|
|
||||||
log.Printf("Pushing brick at %s", to)
|
|
||||||
brickTo := to.Add(dir)
|
|
||||||
l.ani.StartFn(l.ctx.Tick, newEntityMoveAnimation(brick, brickTo), func() {
|
|
||||||
log.Printf("Brick movement finished")
|
|
||||||
if l.checkTile(brickTo, l.wouldBrickSink) {
|
|
||||||
log.Printf("Sinking brick at %s", brickTo)
|
|
||||||
idx := findEntityIdx(l.bricks, brickTo)
|
|
||||||
l.bricks = append(l.bricks[:idx], l.bricks[idx+1:]...)
|
|
||||||
l.sunken = append(l.sunken, brick)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *playLevel) Handle(e allg5.Event) {
|
func (l *playLevel) Handle(e allg5.Event) {
|
||||||
switch e := e.(type) {
|
switch e := e.(type) {
|
||||||
case *allg5.KeyDownEvent:
|
case *allg5.KeyDownEvent:
|
||||||
l.keysDown[e.KeyCode] = true
|
l.state.PressKey(e.KeyCode)
|
||||||
case *allg5.KeyUpEvent:
|
case *allg5.KeyUpEvent:
|
||||||
l.keysDown[e.KeyCode] = false
|
l.state.ReleaseKey(e.KeyCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
if l.showMenu {
|
if l.showMenu {
|
||||||
@ -294,50 +151,66 @@ func (l *playLevel) Handle(e allg5.Event) {
|
|||||||
l.showMenu = true
|
l.showMenu = true
|
||||||
l.menu.Activate(0)
|
l.menu.Activate(0)
|
||||||
case l.ctx.Settings.Controls.MoveUp:
|
case l.ctx.Settings.Controls.MoveUp:
|
||||||
l.tryPlayerMove(geom.Pt(0, -1), e.KeyCode)
|
l.state.TryPlayerMove(geom.Pt(0, -1), e.KeyCode)
|
||||||
case l.ctx.Settings.Controls.MoveRight:
|
case l.ctx.Settings.Controls.MoveRight:
|
||||||
l.tryPlayerMove(geom.Pt(1, 0), e.KeyCode)
|
l.state.TryPlayerMove(geom.Pt(1, 0), e.KeyCode)
|
||||||
case l.ctx.Settings.Controls.MoveDown:
|
case l.ctx.Settings.Controls.MoveDown:
|
||||||
l.tryPlayerMove(geom.Pt(0, 1), e.KeyCode)
|
l.state.TryPlayerMove(geom.Pt(0, 1), e.KeyCode)
|
||||||
case l.ctx.Settings.Controls.MoveLeft:
|
case l.ctx.Settings.Controls.MoveLeft:
|
||||||
l.tryPlayerMove(geom.Pt(-1, 0), e.KeyCode)
|
l.state.TryPlayerMove(geom.Pt(-1, 0), e.KeyCode)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *playLevel) Render(ctx *alui.Context, bounds geom.RectangleF32) {
|
func (l *playLevel) drawSpritePart(name, partName string, pos geom.PointF32) {
|
||||||
basicTile := l.ctx.Textures["basic_tile"]
|
l.drawSpritePartOffset(name, partName, pos, geom.PointF32{})
|
||||||
waterTile := l.ctx.Textures["water_tile"]
|
}
|
||||||
sunkenBrickTile := l.ctx.Textures["sunken_brick_tile"]
|
|
||||||
|
|
||||||
opts := allg5.DrawOptions{Center: true, Scale: allg5.NewUniformScale(l.scale * (168 / float32(basicTile.Width())))}
|
func (l *playLevel) drawSpritePartOffset(name, partName string, pos, offset geom.PointF32) {
|
||||||
level := l.level
|
sprite, ok := l.ctx.Sprites[name]
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
text, ok := l.ctx.Textures[sprite.texture]
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
partText, ok := text.Subs[partName]
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
part := sprite.FindPartByName(partName)
|
||||||
|
scale := l.scale
|
||||||
|
if part.scale != 0 {
|
||||||
|
scale *= 1. / part.scale
|
||||||
|
}
|
||||||
|
anchor := part.sub.Min.Sub(part.anchor).ToF32().Mul(scale)
|
||||||
|
scrPos := l.posToScreenF32(pos).Add(anchor).Add(offset.Mul(100 * l.scale))
|
||||||
|
left, top := scrPos.X, scrPos.Y
|
||||||
|
partText.DrawOptions(left, top, allg5.DrawOptions{Scale: allg5.NewUniformScale(scale)})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *playLevel) Render(ctx *alui.Context, bounds geom.RectangleF32) {
|
||||||
|
level := l.state.level
|
||||||
for i, t := range level.tiles {
|
for i, t := range level.tiles {
|
||||||
pos := geom.Pt(i%level.width, i/level.width)
|
pos := geom.Pt(i%level.width, i/level.width)
|
||||||
scrPos := l.posToScreen(pos)
|
|
||||||
switch t {
|
switch t {
|
||||||
case tileBasic:
|
case tileBasic:
|
||||||
basicTile.DrawOptions(scrPos.X, scrPos.Y, opts)
|
if l.state.IsNextToMagma(pos) {
|
||||||
case tileWater:
|
l.drawSpritePart("lava_brick", "magma", pos.ToF32())
|
||||||
scrPos := l.posToScreenF32(pos.ToF32().Add2D(0, .2*l.scale))
|
|
||||||
if findEntityAt(l.sunken, pos) == nil {
|
|
||||||
waterTile.DrawOptions(scrPos.X, scrPos.Y, opts)
|
|
||||||
} else {
|
} else {
|
||||||
sunkenBrickTile.DrawOptions(scrPos.X, scrPos.Y, opts)
|
l.drawSpritePart("lava_brick", "lava_brick", pos.ToF32())
|
||||||
|
}
|
||||||
|
case tileMagma:
|
||||||
|
l.drawSpritePart("magma", "magma", pos.ToF32())
|
||||||
|
if l.state.IsFilledUp(pos) {
|
||||||
|
l.drawSpritePartOffset("brick", "brick", pos.ToF32(), geom.PtF32(0, .8))
|
||||||
|
l.drawSpritePart("magma", "sunken_overlay", pos.ToF32())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
character := l.ctx.Textures["main_character"]
|
entities := l.state.Entities()
|
||||||
villain := l.ctx.Textures["villain_character"]
|
|
||||||
brick := l.ctx.Textures["brick"]
|
|
||||||
crate := l.ctx.Textures["crate"]
|
|
||||||
|
|
||||||
var entities []*entity
|
|
||||||
entities = append(entities, l.player)
|
|
||||||
entities = append(entities, l.villain)
|
|
||||||
entities = append(entities, l.bricks...)
|
|
||||||
|
|
||||||
sort.Slice(entities, func(i, j int) bool {
|
sort.Slice(entities, func(i, j int) bool {
|
||||||
if entities[i].scr.Y == entities[j].scr.Y {
|
if entities[i].scr.Y == entities[j].scr.Y {
|
||||||
return entities[i].scr.X < entities[j].scr.X
|
return entities[i].scr.X < entities[j].scr.X
|
||||||
@ -346,27 +219,16 @@ func (l *playLevel) Render(ctx *alui.Context, bounds geom.RectangleF32) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
for _, e := range entities {
|
for _, e := range entities {
|
||||||
scrPos := l.posToScreenF32(e.scr)
|
|
||||||
switch e.typ {
|
switch e.typ {
|
||||||
case entityTypeCharacter:
|
case entityTypeCharacter:
|
||||||
scrPos := l.posToScreenF32(e.scr.Add2D(-.2*l.scale, -.9*l.scale))
|
l.drawSpritePart("main_character", "main_character", e.scr)
|
||||||
character.DrawOptions(scrPos.X, scrPos.Y, opts)
|
|
||||||
case entityTypeVillain:
|
|
||||||
scrPos := l.posToScreenF32(e.scr.Add2D(-.2*l.scale, -.9*l.scale))
|
|
||||||
villain.DrawOptions(scrPos.X, scrPos.Y, opts)
|
|
||||||
case entityTypeBrick:
|
case entityTypeBrick:
|
||||||
if findEntityAt(l.bricks, e.pos) == nil {
|
l.drawSpritePart("brick", "brick", e.scr)
|
||||||
break
|
|
||||||
}
|
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
font := ctx.Fonts.Get("default")
|
font := ctx.Fonts.Get("default")
|
||||||
steps := fmt.Sprintf("STEPS: %d", l.steps)
|
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)
|
ctx.Fonts.DrawAlignFont(font, bounds.Min.X, 24, bounds.Max.X, ctx.Palette.Text, allg5.AlignCenter, steps)
|
||||||
|
|
||||||
if l.showMenu {
|
if l.showMenu {
|
||||||
|
197
cmd/krampus19/playlevelstate.go
Normal file
@ -0,0 +1,197 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"opslag.de/schobers/allg5"
|
||||||
|
"opslag.de/schobers/geom"
|
||||||
|
"opslag.de/schobers/krampus19/gut"
|
||||||
|
)
|
||||||
|
|
||||||
|
func findEntityAt(entities []*entity, pos geom.Point) *entity {
|
||||||
|
idx := findEntityIdx(entities, pos)
|
||||||
|
if idx == -1 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return entities[idx]
|
||||||
|
}
|
||||||
|
|
||||||
|
func findEntityIdx(entities []*entity, pos geom.Point) int {
|
||||||
|
for i, e := range entities {
|
||||||
|
if e.pos == pos {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
type playLevelState struct {
|
||||||
|
ctx *Context
|
||||||
|
|
||||||
|
level level
|
||||||
|
player *entity
|
||||||
|
villain *entity
|
||||||
|
bricks []*entity
|
||||||
|
sunken []*entity
|
||||||
|
steps int
|
||||||
|
tick time.Duration
|
||||||
|
ani gut.Animations
|
||||||
|
keysDown keyPressedState
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *playLevelState) Entities() []*entity {
|
||||||
|
var entities []*entity
|
||||||
|
entities = append(entities, s.player)
|
||||||
|
entities = append(entities, s.villain)
|
||||||
|
entities = append(entities, s.bricks...)
|
||||||
|
return entities
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *playLevelState) IsFilledUp(pos geom.Point) bool {
|
||||||
|
return findEntityAt(s.sunken, pos) != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *playLevelState) IsNextToMagma(pos geom.Point) bool {
|
||||||
|
return s.checkTile(pos.Add2D(1, 0), s.isMagma) ||
|
||||||
|
s.checkTile(pos.Add2D(-1, 0), s.isMagma) ||
|
||||||
|
s.checkTile(pos.Add2D(0, -1), s.isMagma) ||
|
||||||
|
s.checkTile(pos.Add2D(0, 1), s.isMagma)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *playLevelState) Init(ctx *Context, levelName string) {
|
||||||
|
s.ctx = ctx
|
||||||
|
s.level = ctx.Levels[levelName]
|
||||||
|
s.bricks = nil
|
||||||
|
s.sunken = nil
|
||||||
|
for i, e := range s.level.entities {
|
||||||
|
switch e {
|
||||||
|
case entityTypeBrick:
|
||||||
|
s.bricks = append(s.bricks, newEntity(e, s.level.idxToPos(i)))
|
||||||
|
case entityTypeCharacter:
|
||||||
|
s.player = newEntity(e, s.level.idxToPos(i))
|
||||||
|
case entityTypeVillain:
|
||||||
|
s.villain = newEntity(e, s.level.idxToPos(i))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.keysDown = keyPressedState{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *playLevelState) Level() level { return s.level }
|
||||||
|
|
||||||
|
func (s *playLevelState) PressKey(key allg5.Key) {
|
||||||
|
s.keysDown[key] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *playLevelState) ReleaseKey(key allg5.Key) {
|
||||||
|
s.keysDown[key] = false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *playLevelState) Steps() int { return s.steps }
|
||||||
|
|
||||||
|
func (s *playLevelState) Tick(now time.Duration) {
|
||||||
|
s.ani.Animate(now)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *playLevelState) TryPlayerMove(dir geom.Point, key allg5.Key) {
|
||||||
|
if !s.isIdle() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
to := s.player.pos.Add(dir)
|
||||||
|
if !s.canMove(s.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++
|
||||||
|
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 {
|
||||||
|
log.Printf("Key %s is still down, moving further", gut.KeyToString(key))
|
||||||
|
s.TryPlayerMove(dir, key)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if brick := findEntityAt(s.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() {
|
||||||
|
log.Printf("Brick movement finished")
|
||||||
|
if s.checkTile(brickTo, s.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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *playLevelState) canMove(from, dir geom.Point) bool {
|
||||||
|
to := from.Add(dir)
|
||||||
|
if !s.checkTile(to, s.isSolidTile) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
brick := findEntityAt(s.bricks, to)
|
||||||
|
if brick != nil {
|
||||||
|
brickTo := to.Add(dir)
|
||||||
|
return !s.checkTileNotFound(brickTo, s.isObstructed, true)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *playLevelState) checkTile(pos geom.Point, check func(pos geom.Point, idx int, t tile) bool) bool {
|
||||||
|
return s.checkTileNotFound(pos, check, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *playLevelState) checkTileNotFound(pos geom.Point, check func(pos geom.Point, idx int, t tile) bool, notFound bool) bool {
|
||||||
|
idx := s.level.posToIdx(pos)
|
||||||
|
if idx == -1 {
|
||||||
|
return notFound
|
||||||
|
}
|
||||||
|
return check(pos, idx, s.level.tiles[idx])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *playLevelState) findEntityAt(pos geom.Point) *entity {
|
||||||
|
if s.player.pos == pos {
|
||||||
|
return s.player
|
||||||
|
}
|
||||||
|
brick := findEntityAt(s.bricks, pos)
|
||||||
|
if brick != nil {
|
||||||
|
return brick
|
||||||
|
}
|
||||||
|
return findEntityAt(s.sunken, pos)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *playLevelState) isIdle() bool { return s.ani.Idle() }
|
||||||
|
|
||||||
|
func (s *playLevelState) isObstructed(pos geom.Point, idx int, t tile) bool {
|
||||||
|
if findEntityAt(s.bricks, pos) != nil {
|
||||||
|
return true // brick
|
||||||
|
}
|
||||||
|
switch s.level.tiles[idx] {
|
||||||
|
case tileMagma:
|
||||||
|
return false
|
||||||
|
case tileBasic:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *playLevelState) isMagma(pos geom.Point, idx int, t tile) bool { return t == tileMagma }
|
||||||
|
|
||||||
|
func (s *playLevelState) isSolidTile(pos geom.Point, idx int, t tile) bool {
|
||||||
|
switch t {
|
||||||
|
case tileBasic:
|
||||||
|
return true
|
||||||
|
case tileMagma:
|
||||||
|
return findEntityAt(s.sunken, pos) != nil
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *playLevelState) wouldBrickSink(pos geom.Point, idx int, t tile) bool {
|
||||||
|
return t == tileMagma && findEntityAt(s.sunken, pos) == nil
|
||||||
|
}
|
Before Width: | Height: | Size: 8.6 KiB |
Before Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 26 KiB |
BIN
cmd/krampus19/res/entity_brick.png
Normal file
After Width: | Height: | Size: 120 KiB |
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
@ -1,11 +1,11 @@
|
|||||||
level:
|
level:
|
||||||
._._._._._._._._._._
|
._._._._._._._._._._
|
||||||
._._#_#_#_._._._._._
|
._#_#_#_#_._._._._._
|
||||||
._._#_._#B._._._._._
|
._#_#_._#B._._._._._
|
||||||
._._#_#_#_._._._._._
|
._#_#_#_#_._._._._._
|
||||||
._._#_._#B._._._._._
|
._#_#_._#B._._._._._
|
||||||
._#@#_#_#_~_~_#_#X._
|
._#@#_#_#_~_~_#_#X._
|
||||||
._._._._~_~_#_._._._
|
._#_#_~_~_~_#_#_#_._
|
||||||
._._._._#_#_#_._._._
|
._#_#_~_#_#_#_#_#_._
|
||||||
._._._._._._._._._._
|
._._._._._._._._._._
|
||||||
:level
|
:level
|
11
cmd/krampus19/res/sprites/brick.txt
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
sprite:
|
||||||
|
texture: brick
|
||||||
|
|
||||||
|
part:
|
||||||
|
name: brick
|
||||||
|
sub_texture: 0,0,1020,960
|
||||||
|
anchor: 510,780
|
||||||
|
scale: 6
|
||||||
|
:part
|
||||||
|
|
||||||
|
:sprite
|
@ -1,28 +0,0 @@
|
|||||||
sprite:
|
|
||||||
texture: dragon
|
|
||||||
|
|
||||||
part:
|
|
||||||
name: body
|
|
||||||
sub_texture: 0,0,2107,1284
|
|
||||||
anchor: 1065,594
|
|
||||||
:part
|
|
||||||
|
|
||||||
part:
|
|
||||||
name: foot
|
|
||||||
sub_texture: 42,1345,529,856
|
|
||||||
anchor: 386,1283
|
|
||||||
:part
|
|
||||||
|
|
||||||
part:
|
|
||||||
name: upper_wing
|
|
||||||
sub_texture: 2112,39,1080,651
|
|
||||||
anchor: 2413,644
|
|
||||||
:part
|
|
||||||
|
|
||||||
part:
|
|
||||||
name: lower_wing
|
|
||||||
sub_texture: 2170,764,1030,736
|
|
||||||
anchor: 2658,799
|
|
||||||
:part
|
|
||||||
|
|
||||||
:sprite
|
|
18
cmd/krampus19/res/sprites/lava_brick.txt
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
sprite:
|
||||||
|
texture: lava_brick
|
||||||
|
|
||||||
|
part:
|
||||||
|
name: lava_brick
|
||||||
|
sub_texture: 0,0,1020,960
|
||||||
|
anchor: 510,180
|
||||||
|
scale: 6
|
||||||
|
:part
|
||||||
|
|
||||||
|
part:
|
||||||
|
name: magma
|
||||||
|
sub_texture: 0,960,1020,960
|
||||||
|
anchor: 510,1140
|
||||||
|
scale: 6
|
||||||
|
:part
|
||||||
|
|
||||||
|
:sprite
|
18
cmd/krampus19/res/sprites/magma.txt
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
sprite:
|
||||||
|
texture: magma
|
||||||
|
|
||||||
|
part:
|
||||||
|
name: magma
|
||||||
|
sub_texture: 0,0,1020,960
|
||||||
|
anchor: 510,180
|
||||||
|
scale: 6
|
||||||
|
:part
|
||||||
|
|
||||||
|
part:
|
||||||
|
name: sunken_overlay
|
||||||
|
sub_texture: 0,960,1020,960
|
||||||
|
anchor: 510,1140
|
||||||
|
scale: 6
|
||||||
|
:part
|
||||||
|
|
||||||
|
:sprite
|
11
cmd/krampus19/res/sprites/main_character.txt
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
sprite:
|
||||||
|
texture: main_character
|
||||||
|
|
||||||
|
part:
|
||||||
|
name: main_character
|
||||||
|
sub_texture: 0,0,200,400
|
||||||
|
anchor: 100,350
|
||||||
|
scale: 2
|
||||||
|
:part
|
||||||
|
|
||||||
|
:sprite
|
Before Width: | Height: | Size: 17 KiB |
BIN
cmd/krampus19/res/tile_lava_brick.png
Normal file
After Width: | Height: | Size: 310 KiB |
BIN
cmd/krampus19/res/tile_magma.png
Normal file
After Width: | Height: | Size: 474 KiB |
Before Width: | Height: | Size: 7.0 KiB |
@ -1,11 +1,8 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"math"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"opslag.de/schobers/allg5"
|
|
||||||
|
|
||||||
"opslag.de/schobers/geom"
|
"opslag.de/schobers/geom"
|
||||||
"opslag.de/schobers/krampus19/alui"
|
"opslag.de/schobers/krampus19/alui"
|
||||||
)
|
)
|
||||||
@ -20,26 +17,5 @@ type splash struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *splash) Render(ctx *alui.Context, bounds geom.RectangleF32) {
|
func (s *splash) Render(ctx *alui.Context, bounds geom.RectangleF32) {
|
||||||
var dragon = s.ctx.Sprites["dragon"]
|
s.atEnd = true
|
||||||
|
|
||||||
var texture = s.ctx.Textures[dragon.texture]
|
|
||||||
var body = texture.Subs["body"]
|
|
||||||
var upperWing = texture.Subs["upper_wing"]
|
|
||||||
var lowerWing = texture.Subs["lower_wing"]
|
|
||||||
var foot = texture.Subs["foot"]
|
|
||||||
|
|
||||||
scale := bounds.Dy() / 1600
|
|
||||||
|
|
||||||
var repeat = 2 * time.Second / time.Millisecond
|
|
||||||
var tick = float64((s.ctx.Tick / time.Millisecond) % repeat)
|
|
||||||
flap := .5*float32(math.Cos(tick*2*math.Pi/float64(repeat))) + .5 // [0..1]
|
|
||||||
upperFlap := flap*.75 + .25 // [.25..1]
|
|
||||||
lowerFlap := flap*.5 + .5 // [.5..1]
|
|
||||||
|
|
||||||
body.DrawOptions(0, 0, allg5.DrawOptions{Scale: allg5.NewUniformScale(scale)})
|
|
||||||
foot.DrawOptions(750*scale, 500*scale, allg5.DrawOptions{Scale: allg5.NewUniformScale(scale)})
|
|
||||||
|
|
||||||
var upperWingOffset = float32(upperWing.Height()) * scale * (1 - flap) * .8
|
|
||||||
upperWing.DrawOptions(750*scale, -100*scale+upperWingOffset, allg5.DrawOptions{Scale: allg5.NewScale(scale, scale*upperFlap)})
|
|
||||||
lowerWing.DrawOptions(750*scale, -100*scale+upperWingOffset*.95, allg5.DrawOptions{Scale: allg5.NewScale(scale, scale*lowerFlap)})
|
|
||||||
}
|
}
|
||||||
|
@ -14,10 +14,20 @@ type sprite struct {
|
|||||||
parts []spritePart
|
parts []spritePart
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s sprite) FindPartByName(name string) spritePart {
|
||||||
|
for _, part := range s.parts {
|
||||||
|
if part.name == name {
|
||||||
|
return part
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return spritePart{}
|
||||||
|
}
|
||||||
|
|
||||||
type spritePart struct {
|
type spritePart struct {
|
||||||
name string
|
name string
|
||||||
sub geom.Rectangle
|
sub geom.Rectangle
|
||||||
anchor geom.Point
|
anchor geom.Point
|
||||||
|
scale float32
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadSpriteAsset(r io.Reader) (sprite, error) {
|
func loadSpriteAsset(r io.Reader) (sprite, error) {
|
||||||
@ -79,7 +89,7 @@ func (c *spriteContext) parsePart(p *lineParser) parseLineFn {
|
|||||||
for i, s := range s {
|
for i, s := range s {
|
||||||
res[i], err = strconv.Atoi(s)
|
res[i], err = strconv.Atoi(s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic("string does not represent an integer")
|
panic("string does not represent an integer number")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return res
|
return res
|
||||||
@ -88,9 +98,17 @@ func (c *spriteContext) parsePart(p *lineParser) parseLineFn {
|
|||||||
coords := strings.Split(strings.TrimSpace(s), ",")
|
coords := strings.Split(strings.TrimSpace(s), ",")
|
||||||
return mustAtois(coords...)
|
return mustAtois(coords...)
|
||||||
}
|
}
|
||||||
|
mustAtof := func(s string) float32 {
|
||||||
|
f, err := strconv.ParseFloat(strings.TrimSpace(s), 32)
|
||||||
|
if err != nil {
|
||||||
|
panic("string does not represent an floating point number")
|
||||||
|
}
|
||||||
|
return float32(f)
|
||||||
|
}
|
||||||
const nameTag = "name:"
|
const nameTag = "name:"
|
||||||
const subTextureTag = "sub_texture:"
|
const subTextureTag = "sub_texture:"
|
||||||
const anchorTag = "anchor:"
|
const anchorTag = "anchor:"
|
||||||
|
const scaleTag = "scale:"
|
||||||
const partEndTag = ":part"
|
const partEndTag = ":part"
|
||||||
if p.skipSpaceEOF() {
|
if p.skipSpaceEOF() {
|
||||||
return p.emitErr(errUnexpectedEnd)
|
return p.emitErr(errUnexpectedEnd)
|
||||||
@ -117,6 +135,10 @@ func (c *spriteContext) parsePart(p *lineParser) parseLineFn {
|
|||||||
c.part.anchor = geom.Pt(coords[0], coords[1])
|
c.part.anchor = geom.Pt(coords[0], coords[1])
|
||||||
p.next()
|
p.next()
|
||||||
return c.parsePart
|
return c.parsePart
|
||||||
|
case strings.HasPrefix(line, scaleTag):
|
||||||
|
c.part.scale = mustAtof(line[len(scaleTag):])
|
||||||
|
p.next()
|
||||||
|
return c.parsePart
|
||||||
case line == partEndTag:
|
case line == partEndTag:
|
||||||
c.sprite.parts = append(c.sprite.parts, *c.part)
|
c.sprite.parts = append(c.sprite.parts, *c.part)
|
||||||
p.next()
|
p.next()
|
||||||
|