1st commit
This commit is contained in:
51
game/game.go
Normal file
51
game/game.go
Normal file
@@ -0,0 +1,51 @@
|
||||
package game
|
||||
|
||||
import (
|
||||
"image"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2"
|
||||
"github.com/mlange-42/arche/ecs"
|
||||
)
|
||||
|
||||
type Game struct {
|
||||
World *ecs.World
|
||||
Bounds image.Rectangle
|
||||
ScreenWidth, ScreenHeight int
|
||||
CurrentLevel int
|
||||
Scenes []Scene
|
||||
}
|
||||
|
||||
func NewGame(width, height, startlevel int) *Game {
|
||||
world := ecs.NewWorld()
|
||||
|
||||
game := &Game{
|
||||
Bounds: image.Rectangle{},
|
||||
CurrentLevel: startlevel,
|
||||
World: &world,
|
||||
ScreenWidth: width,
|
||||
ScreenHeight: height,
|
||||
}
|
||||
|
||||
levelscene := NewLevelScene(game, startlevel)
|
||||
game.Scenes = append(game.Scenes, levelscene)
|
||||
|
||||
return game
|
||||
}
|
||||
|
||||
func (game *Game) Update() error {
|
||||
for _, scene := range game.Scenes {
|
||||
scene.Update()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (game *Game) Draw(screen *ebiten.Image) {
|
||||
for _, scene := range game.Scenes {
|
||||
scene.Draw(screen)
|
||||
}
|
||||
}
|
||||
|
||||
func (g *Game) Layout(newWidth, newHeight int) (int, int) {
|
||||
return g.ScreenWidth, g.ScreenHeight
|
||||
}
|
||||
130
game/grid.go
Normal file
130
game/grid.go
Normal file
@@ -0,0 +1,130 @@
|
||||
package game
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image"
|
||||
"log"
|
||||
"openquell/assets"
|
||||
"openquell/components"
|
||||
. "openquell/config"
|
||||
|
||||
"github.com/mlange-42/arche/generic"
|
||||
)
|
||||
|
||||
type Grid struct {
|
||||
Width int
|
||||
Height int
|
||||
Size int
|
||||
Tilesize int
|
||||
TilesX int
|
||||
TilesY int
|
||||
Map map[image.Point]*assets.Tile
|
||||
}
|
||||
|
||||
// FIXME: put component addition into extra callable function, to be called
|
||||
// by game.go in a level-loop. Also remove components of previous level
|
||||
func NewGrid(game *Game, tilesize int, mapslice map[image.Point]*assets.Tile) *Grid {
|
||||
// we use this to turn our tiles into iterable entities, used for
|
||||
// collision detection, transformation and other things
|
||||
playermapper := generic.NewMap4[
|
||||
components.Position,
|
||||
components.Velocity,
|
||||
components.Renderable,
|
||||
components.Player](game.World)
|
||||
solidmapper := generic.NewMap4[
|
||||
components.Position,
|
||||
components.Renderable,
|
||||
components.Tilish,
|
||||
components.Solid](game.World)
|
||||
emptymapper := generic.NewMap2[components.Position, components.Tilish](game.World)
|
||||
|
||||
var pos *components.Position
|
||||
var render *components.Renderable
|
||||
|
||||
for point, tile := range mapslice {
|
||||
switch tile.Renderable {
|
||||
case true:
|
||||
switch {
|
||||
case tile.Solid:
|
||||
entity := solidmapper.New()
|
||||
pos, render, _, _ = solidmapper.Get(entity)
|
||||
case tile.Player:
|
||||
entity := playermapper.New()
|
||||
pos, _, render, _ = playermapper.Get(entity)
|
||||
fmt.Printf("player start pos: %d,%d\n", point.X*tilesize, point.Y*tilesize)
|
||||
default:
|
||||
log.Fatalln("unsupported tile type encountered")
|
||||
}
|
||||
|
||||
render.Image = tile.Sprite
|
||||
|
||||
default:
|
||||
// empty cell, this is where the player[s] move. No
|
||||
// sprite required since every level has a background
|
||||
// image.
|
||||
entity := emptymapper.New()
|
||||
pos, _ = emptymapper.Get(entity)
|
||||
}
|
||||
|
||||
// however, every tile has a position
|
||||
pos.Update(point.X*tilesize, point.Y*tilesize, tilesize)
|
||||
}
|
||||
|
||||
return &Grid{
|
||||
Size: len(mapslice),
|
||||
Tilesize: tilesize,
|
||||
Width: game.ScreenWidth,
|
||||
Height: game.ScreenHeight,
|
||||
TilesX: game.ScreenWidth / tilesize,
|
||||
TilesY: game.ScreenHeight / tilesize,
|
||||
Map: mapslice,
|
||||
}
|
||||
}
|
||||
|
||||
func (grid *Grid) GetSolidNeighborPosition(
|
||||
position *components.Position,
|
||||
velocity *components.Velocity,
|
||||
solid bool) (bool, *components.Position) {
|
||||
|
||||
if !solid {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
fmt.Println(position)
|
||||
// set to true, ifwe are already on the last tile in the current
|
||||
// direction, i.e. on the edge of the grid
|
||||
edge := true
|
||||
neighborpos := position.Point()
|
||||
|
||||
switch velocity.Direction {
|
||||
case East:
|
||||
if neighborpos.X < grid.TilesX {
|
||||
neighborpos.X++
|
||||
edge = false
|
||||
}
|
||||
case West:
|
||||
if neighborpos.X > 0 {
|
||||
neighborpos.X--
|
||||
edge = false
|
||||
}
|
||||
case South:
|
||||
if neighborpos.Y < grid.TilesY {
|
||||
neighborpos.Y++
|
||||
edge = false
|
||||
}
|
||||
case North:
|
||||
if neighborpos.Y > 0 {
|
||||
neighborpos.Y--
|
||||
edge = false
|
||||
}
|
||||
}
|
||||
|
||||
newpos := components.NewPosition(neighborpos, grid.Tilesize)
|
||||
fmt.Println(newpos, edge)
|
||||
|
||||
if !edge && grid.Map[*neighborpos].Solid {
|
||||
return true, newpos
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
172
game/levels.go
Normal file
172
game/levels.go
Normal file
@@ -0,0 +1,172 @@
|
||||
package game
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image"
|
||||
"log"
|
||||
"openquell/assets"
|
||||
"openquell/components"
|
||||
. "openquell/config"
|
||||
"strings"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2"
|
||||
"github.com/mlange-42/arche/ecs"
|
||||
"github.com/mlange-42/arche/filter"
|
||||
)
|
||||
|
||||
type Level struct {
|
||||
Grid *Grid
|
||||
Cellsize, Width, Height int
|
||||
World *ecs.World
|
||||
Name string
|
||||
Description string
|
||||
Background *ebiten.Image
|
||||
Mapslice map[image.Point]*assets.Tile
|
||||
}
|
||||
|
||||
func NewLevel(game *Game, cellsize int, plan *assets.RawLevel) *Level {
|
||||
//grid := NewGrid(game, cellsize, plan)
|
||||
|
||||
return &Level{
|
||||
//Grid: grid,
|
||||
Mapslice: LevelToSlice(game, plan, cellsize),
|
||||
Cellsize: cellsize,
|
||||
World: game.World,
|
||||
Width: game.ScreenWidth,
|
||||
Height: game.ScreenHeight,
|
||||
Description: plan.Description,
|
||||
Background: plan.Background,
|
||||
}
|
||||
}
|
||||
|
||||
func (level *Level) Update() {
|
||||
//playermapper := generic.NewMap3[components.Position, components.Velocity, components.Renderable](level.World)
|
||||
positionid := ecs.ComponentID[components.Position](level.World)
|
||||
velocityid := ecs.ComponentID[components.Velocity](level.World)
|
||||
playerid := ecs.ComponentID[components.Player](level.World)
|
||||
//solidid := ecs.ComponentID[components.Solid](level.World)
|
||||
//renderid := ecs.ComponentID[components.Renderable](level.World)
|
||||
|
||||
selector := filter.All(positionid, velocityid, playerid)
|
||||
query := level.World.Query(selector)
|
||||
|
||||
//tileselector := filter.All(positionid, solidid, renderid)
|
||||
//tilequery := level.World.Query(tileselector)
|
||||
|
||||
for query.Next() {
|
||||
playerposition := (*components.Position)(query.Get(positionid))
|
||||
velocity := (*components.Velocity)(query.Get(velocityid))
|
||||
|
||||
switch {
|
||||
case ebiten.IsKeyPressed(ebiten.KeyRight):
|
||||
velocity.Change(East)
|
||||
case ebiten.IsKeyPressed(ebiten.KeyLeft):
|
||||
velocity.Change(West)
|
||||
case ebiten.IsKeyPressed(ebiten.KeyDown):
|
||||
velocity.Change(South)
|
||||
case ebiten.IsKeyPressed(ebiten.KeyUp):
|
||||
velocity.Change(North)
|
||||
// other keys: <tab>: switch player, etc
|
||||
}
|
||||
|
||||
/*
|
||||
// Use this to check against obstacles, collectibles etc
|
||||
for tilequery.Next() {
|
||||
tilepos := (*components.Position)(tilequery.Get(positionid))
|
||||
|
||||
if velocity.Moving() {
|
||||
intersects, newpos := tilepos.Intersects(playerposition, velocity)
|
||||
if intersects {
|
||||
fmt.Printf("collision detected. tile: %s\n", tilepos)
|
||||
fmt.Printf(" player: %s\n", playerposition)
|
||||
fmt.Printf(" new: %s\n", newpos)
|
||||
|
||||
playerposition.Set(newpos)
|
||||
fmt.Printf(" player new: %s\n", playerposition)
|
||||
velocity.Change(Stop)
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
if velocity.Moving() {
|
||||
ok, tilepos := level.Grid.GetSolidNeighborPosition(playerposition, velocity, true)
|
||||
if ok {
|
||||
intersects, newpos := tilepos.Intersects(playerposition, velocity)
|
||||
if intersects {
|
||||
fmt.Printf("collision detected. tile: %s\n", tilepos)
|
||||
fmt.Printf(" player: %s\n", playerposition)
|
||||
fmt.Printf(" new: %s\n", newpos)
|
||||
|
||||
playerposition.Set(newpos)
|
||||
fmt.Printf(" player new: %s\n", playerposition)
|
||||
velocity.Change(Stop)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
playerposition.Move(velocity)
|
||||
}
|
||||
}
|
||||
|
||||
func (level *Level) Position2Point(position *components.Position) image.Point {
|
||||
return image.Point{
|
||||
int(position.X) / level.Cellsize,
|
||||
int(position.Y) / level.Cellsize,
|
||||
}
|
||||
}
|
||||
|
||||
// return the tile the moving object would end up on if indeed moving
|
||||
func (level *Level) GetTile(position *components.Position, velocity *components.Velocity) *assets.Tile {
|
||||
newpoint := image.Point{
|
||||
int(position.X+velocity.Data.X) / level.Cellsize,
|
||||
int(position.Y+velocity.Data.Y) / level.Cellsize,
|
||||
}
|
||||
|
||||
tile := level.Mapslice[newpoint]
|
||||
return tile
|
||||
}
|
||||
|
||||
func (level *Level) Draw(screen *ebiten.Image) {
|
||||
// FIXME: move out
|
||||
op := &ebiten.DrawImageOptions{}
|
||||
screen.DrawImage(level.Background, op)
|
||||
|
||||
rid := ecs.ComponentID[components.Renderable](level.World)
|
||||
pid := ecs.ComponentID[components.Position](level.World)
|
||||
|
||||
selector := filter.All(rid, pid)
|
||||
|
||||
query := level.World.Query(selector)
|
||||
for query.Next() {
|
||||
pos := (*components.Position)(query.Get(pid))
|
||||
sprite := (*components.Renderable)(query.Get(rid))
|
||||
|
||||
op := &ebiten.DrawImageOptions{}
|
||||
op.GeoM.Translate(float64(pos.X), float64(pos.Y))
|
||||
|
||||
screen.DrawImage(sprite.Image, op)
|
||||
}
|
||||
}
|
||||
|
||||
func (level *Level) SetupGrid(game *Game) {
|
||||
level.Grid = NewGrid(game, level.Cellsize, level.Mapslice)
|
||||
}
|
||||
|
||||
func LevelToSlice(game *Game, level *assets.RawLevel, tilesize int) map[image.Point]*assets.Tile {
|
||||
size := game.ScreenWidth * game.ScreenHeight
|
||||
mapslice := make(map[image.Point]*assets.Tile, size)
|
||||
|
||||
for y, line := range strings.Split(string(level.Data), "\n") {
|
||||
if len(line) != game.ScreenWidth/tilesize && y < game.ScreenHeight/tilesize {
|
||||
log.Fatalf("line %d doesn't contain %d tiles, but %d",
|
||||
y, game.ScreenWidth/tilesize, len(line))
|
||||
}
|
||||
|
||||
for x, char := range line {
|
||||
mapslice[image.Point{x, y}] = assets.Tiles[byte(char)]
|
||||
}
|
||||
}
|
||||
|
||||
return mapslice
|
||||
}
|
||||
51
game/scene.go
Normal file
51
game/scene.go
Normal file
@@ -0,0 +1,51 @@
|
||||
package game
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"openquell/assets"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2"
|
||||
)
|
||||
|
||||
// Wrapper for different screens to be shown, as Welcome, Options,
|
||||
// About, Select Level and of course the actual Levels.
|
||||
// Scenes are responsible for screen clearing! That way a scene is able
|
||||
// to render its content onto the running level, e.g. the options scene
|
||||
// etc.
|
||||
type Scene interface {
|
||||
Update() error
|
||||
Draw(screen *ebiten.Image)
|
||||
}
|
||||
|
||||
type LevelScene struct {
|
||||
Game *Game
|
||||
CurrentLevel int
|
||||
Levels []*Level
|
||||
}
|
||||
|
||||
// Implements the actual playing Scene
|
||||
func NewLevelScene(game *Game, startlevel int) Scene {
|
||||
scene := &LevelScene{Game: game, CurrentLevel: startlevel}
|
||||
|
||||
scene.GenerateLevels()
|
||||
scene.Levels[game.CurrentLevel].SetupGrid(game)
|
||||
fmt.Println(game.World.Stats().String())
|
||||
|
||||
return scene
|
||||
}
|
||||
|
||||
func (scene *LevelScene) GenerateLevels() {
|
||||
for _, level := range assets.Levels {
|
||||
scene.Levels = append(scene.Levels, NewLevel(scene.Game, 32, &level))
|
||||
}
|
||||
}
|
||||
|
||||
func (scene *LevelScene) Update() error {
|
||||
scene.Levels[scene.CurrentLevel].Update()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (scene *LevelScene) Draw(screen *ebiten.Image) {
|
||||
screen.Clear()
|
||||
scene.Levels[scene.CurrentLevel].Draw(screen)
|
||||
}
|
||||
Reference in New Issue
Block a user