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

View File

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

View File

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

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:
._._._._._._._._._._
._._#_#_#_._._._._._
._._#_._#B._._._._._
._._#_#_#_._._._._._
._._#_._#B._._._._._
._#_#_#_#_._._._._._
._#_#_._#B._._._._._
._#_#_#_#_._._._._._
._#_#_._#B._._._._._
._#@#_#_#_~_~_#_#X._
._._._._~_~_#_._._._
._._._._#_#_#_._._._
._#_#_~_~_~_#_#_#_._
._#_#_~_#_#_#_#_#_._
._._._._._._._._._._
: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
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
}

View File

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