Added basic movement/animation.
Refactored entity to entityType.
This commit is contained in:
parent
b85ac17d8a
commit
7bd737af94
3
.gitignore
vendored
3
.gitignore
vendored
@ -5,3 +5,6 @@
|
|||||||
__debug_bin
|
__debug_bin
|
||||||
rice-box.go
|
rice-box.go
|
||||||
*.rice-box.*
|
*.rice-box.*
|
||||||
|
|
||||||
|
# Application
|
||||||
|
cmd/krampus19/res/_sandbox/
|
||||||
|
@ -4,27 +4,29 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
|
"opslag.de/schobers/geom"
|
||||||
)
|
)
|
||||||
|
|
||||||
type entity byte
|
type entityType byte
|
||||||
type tile byte
|
type tile byte
|
||||||
|
|
||||||
const (
|
const (
|
||||||
entityInvalid entity = entity(0)
|
entityTypeInvalid entityType = entityType(0)
|
||||||
entityNone = '_'
|
entityTypeNone = '_'
|
||||||
entityCharacter = '@'
|
entityTypeCharacter = '@'
|
||||||
entityVillain = 'X'
|
entityTypeVillain = 'X'
|
||||||
entityBrick = 'B'
|
entityTypeBrick = 'B'
|
||||||
entityCrate = 'C'
|
entityTypeCrate = 'C'
|
||||||
)
|
)
|
||||||
|
|
||||||
func (e entity) IsValid() bool {
|
func (e entityType) IsValid() bool {
|
||||||
switch e {
|
switch e {
|
||||||
case entityNone:
|
case entityTypeNone:
|
||||||
case entityCharacter:
|
case entityTypeCharacter:
|
||||||
case entityVillain:
|
case entityTypeVillain:
|
||||||
case entityBrick:
|
case entityTypeBrick:
|
||||||
case entityCrate:
|
case entityTypeCrate:
|
||||||
default:
|
default:
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -53,7 +55,16 @@ type level struct {
|
|||||||
width int
|
width int
|
||||||
height int
|
height int
|
||||||
tiles []tile
|
tiles []tile
|
||||||
entities []entity
|
entities []entityType
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l level) idxToPos(i int) geom.Point { return geom.Pt(i%l.width, i/l.width) }
|
||||||
|
|
||||||
|
func (l level) posToIdx(p geom.Point) int {
|
||||||
|
if p.X < 0 || p.Y < 0 || p.X >= l.width || p.Y >= l.height {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
return p.Y*l.width + p.X
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadLevelAsset(r io.Reader) (level, error) {
|
func loadLevelAsset(r io.Reader) (level, error) {
|
||||||
@ -108,10 +119,10 @@ func (c *levelContext) parseRow(p *lineParser) parseLineFn {
|
|||||||
|
|
||||||
func (c *levelContext) addRow(p *lineParser, line string) parseLineFn {
|
func (c *levelContext) addRow(p *lineParser, line string) parseLineFn {
|
||||||
var tiles []tile
|
var tiles []tile
|
||||||
var entities []entity
|
var entities []entityType
|
||||||
for i := 0; i < len(line); i += 2 {
|
for i := 0; i < len(line); i += 2 {
|
||||||
tiles = append(tiles, tile(line[i]))
|
tiles = append(tiles, tile(line[i]))
|
||||||
entities = append(entities, entity(line[i+1]))
|
entities = append(entities, entityType(line[i+1]))
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, t := range tiles {
|
for i, t := range tiles {
|
||||||
@ -121,7 +132,7 @@ func (c *levelContext) addRow(p *lineParser, line string) parseLineFn {
|
|||||||
}
|
}
|
||||||
for i, e := range entities {
|
for i, e := range entities {
|
||||||
if !e.IsValid() {
|
if !e.IsValid() {
|
||||||
return p.emitErr(fmt.Errorf("level contains invalid entity at (%d, %d)", i, c.level.height))
|
return p.emitErr(fmt.Errorf("level contains invalid entity type at (%d, %d)", i, c.level.height))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,9 +1,13 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"sort"
|
||||||
|
"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 {
|
||||||
@ -14,16 +18,84 @@ type playLevel struct {
|
|||||||
scale float32
|
scale float32
|
||||||
|
|
||||||
level level
|
level level
|
||||||
player geom.PointF32
|
player *entity
|
||||||
moving bool
|
bricks []*entity
|
||||||
|
sunken []*entity
|
||||||
|
steps int
|
||||||
|
|
||||||
|
tick time.Duration
|
||||||
|
ani gut.Animations
|
||||||
|
}
|
||||||
|
|
||||||
|
type entity struct {
|
||||||
|
typ entityType
|
||||||
|
pos geom.Point
|
||||||
|
scr geom.PointF32
|
||||||
|
}
|
||||||
|
|
||||||
|
func newEntity(typ entityType, pos geom.Point) *entity {
|
||||||
|
return &entity{typ, pos, pos.ToF32()}
|
||||||
|
}
|
||||||
|
|
||||||
|
type posToScrFn func(geom.Point) geom.PointF32
|
||||||
|
|
||||||
|
type entityMoveAnimation struct {
|
||||||
|
e *entity
|
||||||
|
from, to geom.Point
|
||||||
|
pos geom.PointF32
|
||||||
|
}
|
||||||
|
|
||||||
|
func newEntityMoveAnimation(e *entity, to geom.Point) *entityMoveAnimation {
|
||||||
|
ani := &entityMoveAnimation{e: e, from: e.pos, to: to, pos: e.pos.ToF32()}
|
||||||
|
ani.e.pos = to
|
||||||
|
return ani
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *entityMoveAnimation) Animate(start, now time.Duration) bool {
|
||||||
|
const duration = 240 * time.Millisecond
|
||||||
|
|
||||||
|
progress := float32((now-start)*1000/duration) * .001
|
||||||
|
from, to := a.from.ToF32(), a.to.ToF32()
|
||||||
|
if progress >= 1 {
|
||||||
|
a.e.scr = to
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
a.e.scr = to.Sub(from).Mul(progress).Add(from)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *playLevel) idxToPos(i int) geom.PointF32 { return s.level.idxToPos(i).ToF32() }
|
||||||
|
|
||||||
|
func (s *playLevel) isIdle() bool { return s.ani.Idle() }
|
||||||
|
|
||||||
|
func (s *playLevel) canMove(to geom.Point) bool {
|
||||||
|
idx := s.level.posToIdx(to)
|
||||||
|
if idx == -1 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *playLevel) loadLevel(name string) {
|
func (s *playLevel) loadLevel(name string) {
|
||||||
s.level = s.ctx.Levels[name]
|
s.level = s.ctx.Levels[name]
|
||||||
|
s.bricks = nil
|
||||||
|
s.sunken = nil
|
||||||
|
for i, e := range s.level.entities {
|
||||||
|
switch e {
|
||||||
|
case entityTypeCharacter:
|
||||||
|
s.player = newEntity(e, s.level.idxToPos(i))
|
||||||
|
case entityTypeBrick:
|
||||||
|
s.bricks = append(s.bricks, newEntity(e, s.level.idxToPos(i)))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *playLevel) toScreenPos(p geom.Point) geom.PointF32 {
|
func (s *playLevel) posToScreen(p geom.Point) geom.PointF32 {
|
||||||
pos := geom.PtF32(float32(p.X)+.5, float32(p.Y)+.5)
|
return s.posToScreenF32(p.ToF32())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *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*148-pos.Y*46, pos.Y*82)
|
||||||
pos = geom.PtF32(pos.X*s.scale, pos.Y*s.scale)
|
pos = geom.PtF32(pos.X*s.scale, pos.Y*s.scale)
|
||||||
return pos.Add(s.offset)
|
return pos.Add(s.offset)
|
||||||
@ -50,9 +122,34 @@ func (s *playLevel) Layout(ctx *alui.Context, bounds geom.RectangleF32) {
|
|||||||
tilesCenter = geom.PtF32(tilesCenter.X*s.scale, tilesCenter.Y*s.scale)
|
tilesCenter = geom.PtF32(tilesCenter.X*s.scale, tilesCenter.Y*s.scale)
|
||||||
center := bounds.Center()
|
center := bounds.Center()
|
||||||
s.offset = geom.PtF32(center.X-tilesCenter.X, center.Y-tilesCenter.Y)
|
s.offset = geom.PtF32(center.X-tilesCenter.X, center.Y-tilesCenter.Y)
|
||||||
|
|
||||||
|
s.ani.Animate(s.ctx.Tick)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *playLevel) tryPlayerMove(to geom.Point) {
|
||||||
|
if s.isIdle() && s.canMove(to) {
|
||||||
|
s.ani.Start(s.ctx.Tick, newEntityMoveAnimation(s.player, to))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *playLevel) Handle(e allg5.Event) {
|
||||||
|
switch e := e.(type) {
|
||||||
|
case *allg5.KeyCharEvent:
|
||||||
|
switch e.KeyCode {
|
||||||
|
case allg5.KeyUp:
|
||||||
|
s.tryPlayerMove(s.player.pos.Add2D(0, -1))
|
||||||
|
case allg5.KeyRight:
|
||||||
|
s.tryPlayerMove(s.player.pos.Add2D(1, 0))
|
||||||
|
case allg5.KeyDown:
|
||||||
|
s.tryPlayerMove(s.player.pos.Add2D(0, 1))
|
||||||
|
case allg5.KeyLeft:
|
||||||
|
s.tryPlayerMove(s.player.pos.Add2D(-1, 0))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *playLevel) Render(ctx *alui.Context, bounds geom.RectangleF32) {
|
func (s *playLevel) Render(ctx *alui.Context, bounds geom.RectangleF32) {
|
||||||
|
|
||||||
basicTile := s.ctx.Textures["basic_tile"]
|
basicTile := s.ctx.Textures["basic_tile"]
|
||||||
waterTile := s.ctx.Textures["water_tile"]
|
waterTile := s.ctx.Textures["water_tile"]
|
||||||
tileBmp := func(t tile) Texture {
|
tileBmp := func(t tile) Texture {
|
||||||
@ -70,15 +167,15 @@ func (s *playLevel) Render(ctx *alui.Context, bounds geom.RectangleF32) {
|
|||||||
villain := s.ctx.Textures["villain_character"]
|
villain := s.ctx.Textures["villain_character"]
|
||||||
brick := s.ctx.Textures["brick"]
|
brick := s.ctx.Textures["brick"]
|
||||||
crate := s.ctx.Textures["crate"]
|
crate := s.ctx.Textures["crate"]
|
||||||
entityBmp := func(e entity) Texture {
|
entityBmp := func(e entityType) Texture {
|
||||||
switch e {
|
switch e {
|
||||||
case entityCharacter:
|
case entityTypeCharacter:
|
||||||
return character
|
return character
|
||||||
case entityVillain:
|
case entityTypeVillain:
|
||||||
return villain
|
return villain
|
||||||
case entityBrick:
|
case entityTypeBrick:
|
||||||
return brick
|
return brick
|
||||||
case entityCrate:
|
case entityTypeCrate:
|
||||||
return crate
|
return crate
|
||||||
default:
|
default:
|
||||||
return Texture{}
|
return Texture{}
|
||||||
@ -96,25 +193,31 @@ func (s *playLevel) Render(ctx *alui.Context, bounds geom.RectangleF32) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
pos := geom.Pt(i%level.width, i/level.width)
|
pos := geom.Pt(i%level.width, i/level.width)
|
||||||
screenPos := s.toScreenPos(pos)
|
srcPos := s.posToScreen(pos)
|
||||||
if t == tileWater {
|
if t == tileWater {
|
||||||
screenPos.Y += 8 * s.scale
|
srcPos.Y += 8 * s.scale
|
||||||
}
|
}
|
||||||
tile.DrawOptions(screenPos.X, screenPos.Y, allg5.DrawOptions{Center: true, Scale: allg5.NewUniformScale(s.scale * scale)})
|
tile.DrawOptions(srcPos.X, srcPos.Y, allg5.DrawOptions{Center: true, Scale: allg5.NewUniformScale(s.scale * scale)})
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, e := range level.entities {
|
var entities []*entity
|
||||||
bmp := entityBmp(e)
|
entities = append(entities, s.player)
|
||||||
|
entities = append(entities, s.bricks...)
|
||||||
|
entities = append(entities, s.sunken...)
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
return entities[i].scr.Y < entities[j].scr.Y
|
||||||
|
})
|
||||||
|
|
||||||
|
for _, e := range entities {
|
||||||
|
bmp := entityBmp(e.typ)
|
||||||
if bmp.Bitmap == nil {
|
if bmp.Bitmap == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
pos := geom.Pt(i%level.width, i/level.width)
|
scrPos := s.posToScreenF32(e.scr)
|
||||||
screenPos := s.toScreenPos(pos)
|
bmp.DrawOptions(scrPos.X, scrPos.Y, allg5.DrawOptions{Center: true, Scale: allg5.NewUniformScale(s.scale * scale)})
|
||||||
screenPos.Y -= 48 * s.scale
|
|
||||||
// scale := scale
|
|
||||||
// if e == entityCharacter {
|
|
||||||
// scale *= .4
|
|
||||||
// }
|
|
||||||
bmp.DrawOptions(screenPos.X, screenPos.Y, allg5.DrawOptions{Center: true, Scale: allg5.NewUniformScale(s.scale * scale)})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
34
gut/animation.go
Normal file
34
gut/animation.go
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
package gut
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
type Animation interface {
|
||||||
|
Animate(start, now time.Duration) bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type Animations struct {
|
||||||
|
anis []animation
|
||||||
|
}
|
||||||
|
|
||||||
|
type animation struct {
|
||||||
|
start time.Duration
|
||||||
|
ani Animation
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Animations) Idle() bool {
|
||||||
|
return len(a.anis) == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Animations) Start(now time.Duration, ani Animation) {
|
||||||
|
a.anis = append(a.anis, animation{now, ani})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Animations) Animate(now time.Duration) {
|
||||||
|
active := make([]animation, 0, len(a.anis))
|
||||||
|
for _, ani := range a.anis {
|
||||||
|
if ani.ani.Animate(ani.start, now) {
|
||||||
|
active = append(active, ani)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
a.anis = active
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user