diff --git a/TODO.md b/TODO.md index 4c040da..db8ccc6 100644 --- a/TODO.md +++ b/TODO.md @@ -27,3 +27,7 @@ if inpututil.IsKeyJustPressed(ebiten.KeyS) { - Add some final message when the player reaches the last level, start from scratch or a message to buy me some beer, whatever + +- Calculate obstacle collision the same as solid collision with future + velocity and included snap in so that the player ends up right on + the edge of the obstacle and not inside as it is now diff --git a/assets/levels/0-own.lvl b/assets/levels/0-own.lvl index 78add7e..5d50ad3 100644 --- a/assets/levels/0-own.lvl +++ b/assets/levels/0-own.lvl @@ -5,13 +5,13 @@ Background: background-lila ######## - # o # # - # S # # + #v o # + # S<# # # #### # # #### # # # - # # + #> ^# ######## diff --git a/assets/loader-levels.go b/assets/loader-levels.go index c6b6a0a..2d5d1ce 100644 --- a/assets/loader-levels.go +++ b/assets/loader-levels.go @@ -6,9 +6,11 @@ import ( "io/fs" "log" "log/slog" + "openquell/config" "openquell/util" "os" "path/filepath" + "sort" "strings" "github.com/hajimehoshi/ebiten/v2" @@ -28,9 +30,10 @@ type Tile struct { Renderable bool Velocity bool Collectible bool - Obstacle bool Particle int // -1=unused, 0-3 = show image of slice Particles []*ebiten.Image + Obstacle bool + Direction int // obstacles } func NewTilePlayer() *Tile { @@ -65,7 +68,7 @@ func NewTileCollectible(class string) *Tile { } } -func NewTileObstacle(class string) *Tile { +func NewTileObstacle(class string, direction int) *Tile { return &Tile{ Id: '+', Sprite: Assets[class], @@ -73,6 +76,7 @@ func NewTileObstacle(class string) *Tile { Solid: false, Renderable: true, Obstacle: true, + Direction: direction, } } @@ -121,7 +125,11 @@ func InitTiles() TileRegistry { '#': NewTileBlock("block-grey32"), 'S': NewTilePlayer(), 'o': NewTileCollectible("collectible-orange"), - '+': NewTileObstacle("obstacle-star"), + '+': NewTileObstacle("obstacle-star", config.All), + '^': NewTileObstacle("obstacle-north", config.North), + 'v': NewTileObstacle("obstacle-south", config.South), + '<': NewTileObstacle("obstacle-east", config.East), + '>': NewTileObstacle("obstacle-west", config.West), '*': NewTileParticle([]string{ //"particle-ring-1", "particle-ring-2", @@ -143,6 +151,10 @@ func LoadLevels(dir string) []RawLevel { log.Fatalf("failed to read level dir %s: %s", dir, err) } + sort.Slice(entries, func(i, j int) bool { + return entries[i].Name() < entries[j].Name() + }) + for _, levelfile := range entries { if levelfile.Type().IsRegular() && strings.Contains(levelfile.Name(), ".lvl") { path := filepath.Join("assets", dir) diff --git a/assets/sprites/obstacle-east.png b/assets/sprites/obstacle-east.png new file mode 100644 index 0000000..73c0f97 Binary files /dev/null and b/assets/sprites/obstacle-east.png differ diff --git a/assets/sprites/obstacle-north.png b/assets/sprites/obstacle-north.png new file mode 100644 index 0000000..cdee7c8 Binary files /dev/null and b/assets/sprites/obstacle-north.png differ diff --git a/assets/sprites/obstacle-south.png b/assets/sprites/obstacle-south.png new file mode 100644 index 0000000..c34d6c5 Binary files /dev/null and b/assets/sprites/obstacle-south.png differ diff --git a/assets/sprites/obstacle-west.png b/assets/sprites/obstacle-west.png new file mode 100644 index 0000000..d60dc75 Binary files /dev/null and b/assets/sprites/obstacle-west.png differ diff --git a/components/position.go b/components/position.go index b1aefb8..b968c4e 100644 --- a/components/position.go +++ b/components/position.go @@ -72,7 +72,13 @@ func (position *Position) String() string { } func (position *Position) Move(velocity *Velocity, speed *Speed) { - position.Update(position.X+(velocity.Data.X*speed.Value), position.Y+(velocity.Data.Y*speed.Value)) + if velocity.Direction != 0 { + slog.Debug("moving", "velocity", velocity, "speed", speed) + } + position.Update( + position.X+(velocity.Data.X*speed.Value), + position.Y+(velocity.Data.Y*speed.Value), + ) } func (position *Position) Set(newpos *Position) { diff --git a/components/velocity.go b/components/velocity.go index 3881d78..3da8c58 100644 --- a/components/velocity.go +++ b/components/velocity.go @@ -35,3 +35,21 @@ func (velocity *Velocity) Change(direction int) { func (velocity *Velocity) Moving() bool { return velocity.Data.X != 0 || velocity.Data.Y != 0 } + +func (velocity *Velocity) InvertDirection() int { + switch velocity.Direction { + case East: + return West + case West: + return East + case South: + return North + case North: + return South + case All: + return Stop + } + + // should not happen + return All +} diff --git a/config/static.go b/config/static.go index 580e1a1..fc645dd 100644 --- a/config/static.go +++ b/config/static.go @@ -8,6 +8,7 @@ const ( West South North + All ) const PLAYERSPEED int = 4 diff --git a/grid/grid.go b/grid/grid.go index 568ec1b..869a2bb 100644 --- a/grid/grid.go +++ b/grid/grid.go @@ -48,12 +48,14 @@ func NewGrid(world *ecs.World, components.Renderable, components.Collectible](world) - obsmapper := generic.NewMap3[ + obsmapper := generic.NewMap4[ components.Position, + components.Velocity, components.Renderable, components.Obstacle](world) var pos *components.Position + var vel *components.Velocity var render *components.Renderable var speed *components.Speed @@ -78,7 +80,8 @@ func NewGrid(world *ecs.World, pos, render, _ = colmapper.Get(entity) case tile.Obstacle: entity := obsmapper.New() - pos, render, _ = obsmapper.Get(entity) + pos, vel, render, _ = obsmapper.Get(entity) + vel.Direction = tile.Direction default: log.Fatalln("unsupported tile type encountered") } diff --git a/src/star.xcf b/src/star.xcf index 2b5c5c2..144d035 100644 Binary files a/src/star.xcf and b/src/star.xcf differ diff --git a/systems/obstacle_system.go b/systems/obstacle_system.go index 56ecfd5..8ebb615 100644 --- a/systems/obstacle_system.go +++ b/systems/obstacle_system.go @@ -5,9 +5,9 @@ import ( "openquell/assets" "openquell/components" . "openquell/components" - "openquell/config" . "openquell/config" "openquell/observers" + "openquell/util" "github.com/hajimehoshi/ebiten/v2" "github.com/mlange-42/arche/ecs" @@ -15,13 +15,14 @@ import ( ) type ObstacleSystem struct { - World *ecs.World - Selector *generic.Filter3[Position, Obstacle, Renderable] + World *ecs.World + Selector *generic.Filter4[Position, Velocity, Obstacle, Renderable] + PreviousFreePos *components.Position } func NewObstacleSystem(world *ecs.World) *ObstacleSystem { system := &ObstacleSystem{ - Selector: generic.NewFilter3[Position, Obstacle, Renderable](), + Selector: generic.NewFilter4[Position, Velocity, Obstacle, Renderable](), World: world, } @@ -45,17 +46,25 @@ func (system *ObstacleSystem) Update() { gameover := false for query.Next() { - obsposition, obstacle, _ := query.Get() + obsposition, obsvelocity, obstacle, _ := query.Get() for player := range playerobserver.Entities { playerposition := (*Position)(system.World.Get(player, posID)) playervelocity := (*Velocity)(system.World.Get(player, veloID)) - ok, _ := playerposition.Intersects(obsposition, playervelocity) + ok, newpos := playerposition.Intersects(obsposition, playervelocity) if ok { slog.Debug("bumped into obstacle", "obstacle", obstacle) - EntitiesToRemove = append(EntitiesToRemove, player) - gameover = true + + if CheckObstacleSide(playervelocity, obsvelocity.Direction) { + EntitiesToRemove = append(EntitiesToRemove, player) + gameover = true + } else { + playervelocity.Change(Stop) + slog.Debug("bump not die", "originalpos", playerposition) + playerposition.Set(newpos) + slog.Debug("bump not die", "newpos", newpos) + } } } } @@ -82,7 +91,7 @@ func (system *ObstacleSystem) Draw(screen *ebiten.Image) { query := system.Selector.Query(system.World) for query.Next() { - pos, _, sprite := query.Get() + pos, _, _, sprite := query.Get() op.GeoM.Reset() op.GeoM.Translate(float64(pos.X), float64(pos.Y)) @@ -113,5 +122,21 @@ func (system *ObstacleSystem) AddParticle(position *components.Position) { 64, ) - timer.Start(config.PARTICLE_LOOPWAIT) + timer.Start(PARTICLE_LOOPWAIT) +} + +// return true if obstacle weapon points into the direction the player is moving +func CheckObstacleSide(playervelocity *Velocity, obsdirection int) bool { + movingdirection := playervelocity.InvertDirection() + + if movingdirection == Stop || movingdirection == obsdirection { + slog.Debug("Damaging obstacle collision", + "playerdirection", util.DirectionStr(playervelocity.Direction), + "obsdirection", util.DirectionStr(obsdirection), + "movingdirection", util.DirectionStr(movingdirection), + ) + return true + } + + return false } diff --git a/systems/player_system.go b/systems/player_system.go index b8a80b2..1e66c8f 100644 --- a/systems/player_system.go +++ b/systems/player_system.go @@ -76,8 +76,8 @@ func (system PlayerSystem) Update() error { } system.Particle.Update() - system.Collectible.Update() system.Obstacle.Update() + system.Collectible.Update() return nil } @@ -96,7 +96,7 @@ func (system *PlayerSystem) Draw(screen *ebiten.Image) { screen.DrawImage(sprite.Image, op) } - system.Obstacle.Draw(screen) system.Collectible.Draw(screen) system.Particle.Draw(screen) + system.Obstacle.Draw(screen) } diff --git a/util/generics.go b/util/generics.go index b14dd19..c51ac71 100644 --- a/util/generics.go +++ b/util/generics.go @@ -1,5 +1,9 @@ package util +import ( + . "openquell/config" +) + // find an item in a list, generic variant func Contains[E comparable](s []E, v E) bool { for _, vs := range s { @@ -19,3 +23,21 @@ func Exists[K comparable, V any](m map[K]V, v K) bool { return false } + +func DirectionStr(dir int) string { + str := "Stop" + switch dir { + case East: + str = "East" + case West: + str = "West" + case South: + str = "South" + case North: + str = "North" + case All: + str = "All" + } + + return str +}