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 {
|
||||
log.Println("Loading textures...")
|
||||
err := g.loadTextures(map[string]string{
|
||||
"basic_tile.png": "basic_tile",
|
||||
"sunken_brick_tile.png": "sunken_brick_tile",
|
||||
"water_tile.png": "water_tile",
|
||||
"entity_brick.png": "brick",
|
||||
"entity_main_character.png": "main_character",
|
||||
|
||||
"main_character.png": "main_character",
|
||||
"villain_character.png": "villain_character",
|
||||
|
||||
"brick.png": "brick",
|
||||
"crate.png": "crate",
|
||||
"tile_lava_brick.png": "lava_brick",
|
||||
"tile_magma.png": "magma",
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
@ -166,10 +162,10 @@ func (g *game) loadAssets() error {
|
||||
log.Printf("Loaded %d fonts.\n", g.ui.Fonts().Len())
|
||||
|
||||
log.Println("Loading sprites")
|
||||
// err = g.loadSprites("dragon")
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
err = g.loadSprites("brick", "lava_brick", "magma", "main_character")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Printf("Loaded %d sprites.\n", len(g.ctx.Sprites))
|
||||
return nil
|
||||
}
|
||||
|
@ -17,7 +17,6 @@ const (
|
||||
entityTypeCharacter = '@'
|
||||
entityTypeVillain = 'X'
|
||||
entityTypeBrick = 'B'
|
||||
entityTypeCrate = 'C'
|
||||
)
|
||||
|
||||
func (e entityType) IsValid() bool {
|
||||
@ -26,7 +25,6 @@ func (e entityType) IsValid() bool {
|
||||
case entityTypeCharacter:
|
||||
case entityTypeVillain:
|
||||
case entityTypeBrick:
|
||||
case entityTypeCrate:
|
||||
default:
|
||||
return false
|
||||
}
|
||||
@ -37,14 +35,14 @@ const (
|
||||
tileInvalid tile = tile(0)
|
||||
tileNothing = '.'
|
||||
tileBasic = '#'
|
||||
tileWater = '~'
|
||||
tileMagma = '~'
|
||||
)
|
||||
|
||||
func (t tile) IsValid() bool {
|
||||
switch t {
|
||||
case tileNothing:
|
||||
case tileBasic:
|
||||
case tileWater:
|
||||
case tileMagma:
|
||||
default:
|
||||
return false
|
||||
}
|
||||
|
@ -2,14 +2,12 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"opslag.de/schobers/allg5"
|
||||
"opslag.de/schobers/geom"
|
||||
"opslag.de/schobers/krampus19/alui"
|
||||
"opslag.de/schobers/krampus19/gut"
|
||||
)
|
||||
|
||||
type playLevel struct {
|
||||
@ -20,21 +18,12 @@ type playLevel struct {
|
||||
menu *alui.Menu
|
||||
showMenu bool
|
||||
|
||||
name string
|
||||
offset geom.PointF32
|
||||
scale float32
|
||||
keysDown keyPressedState
|
||||
name string
|
||||
offset geom.PointF32
|
||||
scale float32
|
||||
|
||||
level level
|
||||
player *entity
|
||||
villain *entity
|
||||
bricks []*entity
|
||||
sunken []*entity
|
||||
steps int
|
||||
tick time.Duration
|
||||
ani gut.Animations
|
||||
state playLevelState
|
||||
}
|
||||
|
||||
type keyPressedState map[allg5.Key]bool
|
||||
|
||||
func (s keyPressedState) CountPressed(keys ...allg5.Key) int {
|
||||
@ -84,90 +73,6 @@ func (a *entityMoveAnimation) Animate(start, now time.Duration) bool {
|
||||
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 {
|
||||
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 desktop", func() { l.ctx.Navigation.quit() })
|
||||
|
||||
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:
|
||||
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))
|
||||
}
|
||||
}
|
||||
l.state.Init(l.ctx, l.name)
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -201,78 +93,43 @@ func (l *playLevel) posToScreen(p geom.Point) 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*l.scale, pos.Y*l.scale)
|
||||
return pos.Add(l.offset)
|
||||
pos = geom.PtF32(pos.X*100-pos.Y*50, pos.Y*40)
|
||||
return pos.Mul(l.scale).Add(l.offset)
|
||||
}
|
||||
|
||||
// <- 168->
|
||||
// <-128->
|
||||
// <- 150->
|
||||
// <-100->
|
||||
//
|
||||
// /----/| ^ ^
|
||||
// / / / | 72
|
||||
// /----/ / 92 v ^
|
||||
// |----|/ v v 20
|
||||
// / / | | 40
|
||||
// /----/ | 140 v ^
|
||||
// | | | | |
|
||||
// | | / | 100
|
||||
// |----|/ v v
|
||||
//
|
||||
// Gap: 20
|
||||
// Center: 84,46
|
||||
// Offset between horizontal tiles: 148,0
|
||||
// Offset between vertical tiles: -46,82
|
||||
// Center: 74,19
|
||||
// Offset between horizontal tiles: 100,0
|
||||
// Offset between vertical tiles: -25,40
|
||||
|
||||
func (l *playLevel) Layout(ctx *alui.Context, bounds geom.RectangleF32) {
|
||||
l.scale = bounds.Dy() / 1080
|
||||
|
||||
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)
|
||||
level := l.state.Level()
|
||||
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)
|
||||
center := bounds.Center()
|
||||
l.offset = geom.PtF32(center.X-tilesCenter.X, center.Y-tilesCenter.Y)
|
||||
|
||||
l.ani.Animate(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)
|
||||
}
|
||||
})
|
||||
}
|
||||
l.state.Tick(l.ctx.Tick)
|
||||
}
|
||||
|
||||
func (l *playLevel) Handle(e allg5.Event) {
|
||||
switch e := e.(type) {
|
||||
case *allg5.KeyDownEvent:
|
||||
l.keysDown[e.KeyCode] = true
|
||||
l.state.PressKey(e.KeyCode)
|
||||
case *allg5.KeyUpEvent:
|
||||
l.keysDown[e.KeyCode] = false
|
||||
l.state.ReleaseKey(e.KeyCode)
|
||||
}
|
||||
|
||||
if l.showMenu {
|
||||
@ -294,50 +151,66 @@ func (l *playLevel) Handle(e allg5.Event) {
|
||||
l.showMenu = true
|
||||
l.menu.Activate(0)
|
||||
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:
|
||||
l.tryPlayerMove(geom.Pt(1, 0), e.KeyCode)
|
||||
l.state.TryPlayerMove(geom.Pt(1, 0), e.KeyCode)
|
||||
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:
|
||||
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) {
|
||||
basicTile := l.ctx.Textures["basic_tile"]
|
||||
waterTile := l.ctx.Textures["water_tile"]
|
||||
sunkenBrickTile := l.ctx.Textures["sunken_brick_tile"]
|
||||
func (l *playLevel) drawSpritePart(name, partName string, pos geom.PointF32) {
|
||||
l.drawSpritePartOffset(name, partName, pos, geom.PointF32{})
|
||||
}
|
||||
|
||||
opts := allg5.DrawOptions{Center: true, Scale: allg5.NewUniformScale(l.scale * (168 / float32(basicTile.Width())))}
|
||||
level := l.level
|
||||
func (l *playLevel) drawSpritePartOffset(name, partName string, pos, offset geom.PointF32) {
|
||||
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 {
|
||||
pos := geom.Pt(i%level.width, i/level.width)
|
||||
scrPos := l.posToScreen(pos)
|
||||
switch t {
|
||||
case tileBasic:
|
||||
basicTile.DrawOptions(scrPos.X, scrPos.Y, opts)
|
||||
case tileWater:
|
||||
scrPos := l.posToScreenF32(pos.ToF32().Add2D(0, .2*l.scale))
|
||||
if findEntityAt(l.sunken, pos) == nil {
|
||||
waterTile.DrawOptions(scrPos.X, scrPos.Y, opts)
|
||||
if l.state.IsNextToMagma(pos) {
|
||||
l.drawSpritePart("lava_brick", "magma", pos.ToF32())
|
||||
} 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"]
|
||||
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...)
|
||||
|
||||
entities := l.state.Entities()
|
||||
sort.Slice(entities, func(i, j int) bool {
|
||||
if entities[i].scr.Y == entities[j].scr.Y {
|
||||
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 {
|
||||
scrPos := l.posToScreenF32(e.scr)
|
||||
switch e.typ {
|
||||
case entityTypeCharacter:
|
||||
scrPos := l.posToScreenF32(e.scr.Add2D(-.2*l.scale, -.9*l.scale))
|
||||
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)
|
||||
l.drawSpritePart("main_character", "main_character", e.scr)
|
||||
case entityTypeBrick:
|
||||
if findEntityAt(l.bricks, e.pos) == nil {
|
||||
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)
|
||||
l.drawSpritePart("brick", "brick", e.scr)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
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:
|
||||
._._._._._._._._._._
|
||||
._._#_#_#_._._._._._
|
||||
._._#_._#B._._._._._
|
||||
._._#_#_#_._._._._._
|
||||
._._#_._#B._._._._._
|
||||
._#_#_#_#_._._._._._
|
||||
._#_#_._#B._._._._._
|
||||
._#_#_#_#_._._._._._
|
||||
._#_#_._#B._._._._._
|
||||
._#@#_#_#_~_~_#_#X._
|
||||
._._._._~_~_#_._._._
|
||||
._._._._#_#_#_._._._
|
||||
._#_#_~_~_~_#_#_#_._
|
||||
._#_#_~_#_#_#_#_#_._
|
||||
._._._._._._._._._._
|
||||
: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
|
||||
|
||||
import (
|
||||
"math"
|
||||
"time"
|
||||
|
||||
"opslag.de/schobers/allg5"
|
||||
|
||||
"opslag.de/schobers/geom"
|
||||
"opslag.de/schobers/krampus19/alui"
|
||||
)
|
||||
@ -20,26 +17,5 @@ type splash struct {
|
||||
}
|
||||
|
||||
func (s *splash) Render(ctx *alui.Context, bounds geom.RectangleF32) {
|
||||
var dragon = s.ctx.Sprites["dragon"]
|
||||
|
||||
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)})
|
||||
s.atEnd = true
|
||||
}
|
||||
|
@ -14,10 +14,20 @@ type sprite struct {
|
||||
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 {
|
||||
name string
|
||||
sub geom.Rectangle
|
||||
anchor geom.Point
|
||||
scale float32
|
||||
}
|
||||
|
||||
func loadSpriteAsset(r io.Reader) (sprite, error) {
|
||||
@ -79,7 +89,7 @@ func (c *spriteContext) parsePart(p *lineParser) parseLineFn {
|
||||
for i, s := range s {
|
||||
res[i], err = strconv.Atoi(s)
|
||||
if err != nil {
|
||||
panic("string does not represent an integer")
|
||||
panic("string does not represent an integer number")
|
||||
}
|
||||
}
|
||||
return res
|
||||
@ -88,9 +98,17 @@ func (c *spriteContext) parsePart(p *lineParser) parseLineFn {
|
||||
coords := strings.Split(strings.TrimSpace(s), ",")
|
||||
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 subTextureTag = "sub_texture:"
|
||||
const anchorTag = "anchor:"
|
||||
const scaleTag = "scale:"
|
||||
const partEndTag = ":part"
|
||||
if p.skipSpaceEOF() {
|
||||
return p.emitErr(errUnexpectedEnd)
|
||||
@ -117,6 +135,10 @@ func (c *spriteContext) parsePart(p *lineParser) parseLineFn {
|
||||
c.part.anchor = geom.Pt(coords[0], coords[1])
|
||||
p.next()
|
||||
return c.parsePart
|
||||
case strings.HasPrefix(line, scaleTag):
|
||||
c.part.scale = mustAtof(line[len(scaleTag):])
|
||||
p.next()
|
||||
return c.parsePart
|
||||
case line == partEndTag:
|
||||
c.sprite.parts = append(c.sprite.parts, *c.part)
|
||||
p.next()
|
||||
|