Added new tile graphics.

Added sprites for all textures.
Removed splash screen animation.
This commit is contained in:
Sander Schobers 2019-12-27 18:48:16 +01:00
parent 9e757c4fc9
commit cc59236689
21 changed files with 364 additions and 283 deletions

View File

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

View File

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

View File

@ -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 {
@ -23,18 +21,9 @@ type playLevel struct {
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) drawSpritePart(name, partName string, pos geom.PointF32) {
l.drawSpritePartOffset(name, partName, pos, geom.PointF32{})
}
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) { func (l *playLevel) Render(ctx *alui.Context, bounds geom.RectangleF32) {
basicTile := l.ctx.Textures["basic_tile"] level := l.state.level
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())))}
level := l.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 {

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

View File

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View File

@ -1,11 +1,11 @@
level: level:
._._._._._._._._._._ ._._._._._._._._._._
._._#_#_#_._._._._._ ._#_#_#_#_._._._._._
._._#_._#B._._._._._ ._#_#_._#B._._._._._
._._#_#_#_._._._._._ ._#_#_#_#_._._._._._
._._#_._#B._._._._._ ._#_#_._#B._._._._._
._#@#_#_#_~_~_#_#X._ ._#@#_#_#_~_~_#_#X._
._._._._~_~_#_._._._ ._#_#_~_~_~_#_#_#_._
._._._._#_#_#_._._._ ._#_#_~_#_#_#_#_#_._
._._._._._._._._._._ ._._._._._._._._._._
:level :level

View File

@ -0,0 +1,11 @@
sprite:
texture: brick
part:
name: brick
sub_texture: 0,0,1020,960
anchor: 510,780
scale: 6
:part
:sprite

View File

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

View 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

View 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

View 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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 310 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 474 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.0 KiB

View File

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

View File

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