completed refactoring to use systems
This commit is contained in:
		
							parent
							
								
									bbbe873ff7
								
							
						
					
					
						commit
						ac643f49d3
					
				| @ -1,54 +0,0 @@ | |||||||
| package game |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"fmt" |  | ||||||
| 	"image" |  | ||||||
| 	. "openquell/components" |  | ||||||
| 
 |  | ||||||
| 	"github.com/mlange-42/arche/ecs" |  | ||||||
| 	"github.com/mlange-42/arche/generic" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| type CollectibleSystem struct { |  | ||||||
| 	World    *ecs.World |  | ||||||
| 	Selector *generic.Filter2[Position, Collectible] |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func NewCollectibleSystem(world *ecs.World) *CollectibleSystem { |  | ||||||
| 	system := &CollectibleSystem{ |  | ||||||
| 		Selector: generic.NewFilter2[Position, Collectible](), |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return system |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (system *CollectibleSystem) CheckPlayerCollision( |  | ||||||
| 	playerposition *Position, |  | ||||||
| 	playervelocity *Velocity) (bool, image.Point) { |  | ||||||
| 
 |  | ||||||
| 	toRemove := []ecs.Entity{} |  | ||||||
| 	particle_pos := image.Point{} |  | ||||||
| 	var bumped bool |  | ||||||
| 
 |  | ||||||
| 	query := system.Selector.Query(system.World) |  | ||||||
| 
 |  | ||||||
| 	for query.Next() { |  | ||||||
| 		colposition, collectible := query.Get() |  | ||||||
| 
 |  | ||||||
| 		ok, _ := playerposition.Intersects(colposition, playervelocity) |  | ||||||
| 		if ok { |  | ||||||
| 			fmt.Printf("bumped into collectible %v\n", collectible) |  | ||||||
| 			toRemove = append(toRemove, query.Entity()) |  | ||||||
| 			particle_pos.X = colposition.X |  | ||||||
| 			particle_pos.Y = colposition.Y |  | ||||||
| 			bumped = true |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// remove collectible if collected |  | ||||||
| 	for _, entity := range toRemove { |  | ||||||
| 		system.World.RemoveEntity(entity) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return bumped, particle_pos |  | ||||||
| } |  | ||||||
							
								
								
									
										237
									
								
								game/levels.go
									
									
									
									
									
								
							
							
						
						
									
										237
									
								
								game/levels.go
									
									
									
									
									
								
							| @ -1,60 +1,35 @@ | |||||||
| package game | package game | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"fmt" |  | ||||||
| 	"image" | 	"image" | ||||||
| 	"image/draw" |  | ||||||
| 	"log" | 	"log" | ||||||
| 	"openquell/assets" | 	"openquell/assets" | ||||||
| 	"openquell/components" | 	"openquell/components" | ||||||
| 	. "openquell/config" | 	"openquell/grid" | ||||||
|  | 	"openquell/systems" | ||||||
| 	"strings" | 	"strings" | ||||||
| 
 | 
 | ||||||
| 	"github.com/hajimehoshi/ebiten/v2" | 	"github.com/hajimehoshi/ebiten/v2" | ||||||
| 	"github.com/mlange-42/arche/ecs" | 	"github.com/mlange-42/arche/ecs" | ||||||
| 	"github.com/mlange-42/arche/filter" |  | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| type Level struct { | type Level struct { | ||||||
| 	Grid                    *Grid |  | ||||||
| 	Cellsize, Width, Height int | 	Cellsize, Width, Height int | ||||||
| 	World                   *ecs.World | 	World                   *ecs.World | ||||||
| 	Name                    string | 	Name                    string | ||||||
| 	Description             string | 	Description             string | ||||||
| 	Background              *ebiten.Image |  | ||||||
| 	Mapslice                map[image.Point]*assets.Tile | 	Mapslice                map[image.Point]*assets.Tile | ||||||
| 	UseCache                bool | 
 | ||||||
| 	Cache                   *ebiten.Image | 	Player     *systems.PlayerSystem | ||||||
| 	Selector                map[string]ecs.Mask | 	GridSystem *systems.GridSystem | ||||||
| 	Component               map[string]ecs.ID | 
 | ||||||
|  | 	Grid *grid.Grid | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func NewLevel(game *Game, cellsize int, plan *assets.RawLevel) *Level { | func NewLevel(game *Game, cellsize int, plan *assets.RawLevel) *Level { | ||||||
| 	cache := ebiten.NewImage(game.ScreenWidth, game.ScreenHeight) | 	gridsystem := systems.NewGridSystem(game.World, game.ScreenWidth, | ||||||
| 
 | 		game.ScreenHeight, cellsize, plan.Background) | ||||||
| 	positionid := ecs.ComponentID[components.Position](game.World) | 	playersystem := systems.NewPlayerSystem(game.World, gridsystem) | ||||||
| 	velocityid := ecs.ComponentID[components.Velocity](game.World) |  | ||||||
| 	playerid := ecs.ComponentID[components.Player](game.World) |  | ||||||
| 	colid := ecs.ComponentID[components.Collectible](game.World) |  | ||||||
| 	ptid := ecs.ComponentID[components.Particle](game.World) |  | ||||||
| 	sid := ecs.ComponentID[components.Solid](game.World) |  | ||||||
| 	renderid := ecs.ComponentID[components.Renderable](game.World) |  | ||||||
| 
 |  | ||||||
| 	selectors := map[string]ecs.Mask{} |  | ||||||
| 	selectors["player"] = filter.All(positionid, velocityid, playerid) |  | ||||||
| 	selectors["tile"] = filter.All(renderid, positionid, sid) |  | ||||||
| 	selectors["movable"] = filter.All(renderid, positionid) |  | ||||||
| 	selectors["collectible"] = filter.All(positionid, colid) |  | ||||||
| 	selectors["particle"] = filter.All(positionid, ptid) |  | ||||||
| 
 |  | ||||||
| 	components := map[string]ecs.ID{} |  | ||||||
| 	components["position"] = positionid |  | ||||||
| 	components["velocity"] = velocityid |  | ||||||
| 	components["player"] = playerid |  | ||||||
| 	components["collectible"] = colid |  | ||||||
| 	components["particle"] = ptid |  | ||||||
| 	components["solid"] = sid |  | ||||||
| 	components["renderable"] = renderid |  | ||||||
| 
 | 
 | ||||||
| 	return &Level{ | 	return &Level{ | ||||||
| 		Mapslice:    LevelToSlice(game, plan, cellsize), | 		Mapslice:    LevelToSlice(game, plan, cellsize), | ||||||
| @ -63,109 +38,19 @@ func NewLevel(game *Game, cellsize int, plan *assets.RawLevel) *Level { | |||||||
| 		Width:       game.ScreenWidth, | 		Width:       game.ScreenWidth, | ||||||
| 		Height:      game.ScreenHeight, | 		Height:      game.ScreenHeight, | ||||||
| 		Description: plan.Description, | 		Description: plan.Description, | ||||||
| 		Background:  plan.Background, | 		GridSystem:  gridsystem, | ||||||
| 		UseCache:    false, | 		Player:      playersystem, | ||||||
| 		Cache:       cache, |  | ||||||
| 		Selector:    selectors, |  | ||||||
| 		Component:   components, |  | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (level *Level) Update() { | func (level *Level) Update() { | ||||||
| 	query := level.World.Query(level.Selector["player"]) | 	level.GridSystem.Update() | ||||||
| 
 | 	level.Player.Update() | ||||||
| 	toRemove := []ecs.Entity{} | } | ||||||
| 	particle_pos := image.Point{} |  | ||||||
| 
 |  | ||||||
| 	for query.Next() { |  | ||||||
| 		playerposition := (*components.Position)(query.Get(level.Component["position"])) |  | ||||||
| 		velocity := (*components.Velocity)(query.Get(level.Component["velocity"])) |  | ||||||
| 
 |  | ||||||
| 		if !velocity.Moving() { |  | ||||||
| 			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 |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		if velocity.Moving() { |  | ||||||
| 			ok, newpos := level.Grid.BumpEdge(playerposition, velocity) |  | ||||||
| 			if ok { |  | ||||||
| 				fmt.Printf("falling off the edge, new pos: %v\n", newpos) |  | ||||||
| 				playerposition.Set(newpos) |  | ||||||
| 			} else { |  | ||||||
| 				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) |  | ||||||
| 					} |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 			colquery := level.World.Query(level.Selector["collectible"]) |  | ||||||
| 			for colquery.Next() { |  | ||||||
| 				collectible := (*components.Collectible)(colquery.Get(level.Component["collectible"])) |  | ||||||
| 				colposition := (*components.Position)(colquery.Get(level.Component["position"])) |  | ||||||
| 				ok, _ := playerposition.Intersects(colposition, velocity) |  | ||||||
| 				if ok { |  | ||||||
| 					fmt.Printf("bumped into collectible %v\n", collectible) |  | ||||||
| 					toRemove = append(toRemove, colquery.Entity()) |  | ||||||
| 					particle_pos.X = colposition.X |  | ||||||
| 					particle_pos.Y = colposition.Y |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		playerposition.Move(velocity) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// remove collectible if collected |  | ||||||
| 	for _, entity := range toRemove { |  | ||||||
| 		// FIXME: or keep them and prepare an animated death |  | ||||||
| 		level.World.RemoveEntity(entity) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// display debris after collecting |  | ||||||
| 	ptquery := level.World.Query(level.Selector["particle"]) |  | ||||||
| 	for ptquery.Next() { |  | ||||||
| 		// we loop, but it's only one anyway |  | ||||||
| 		particle := (*components.Particle)(ptquery.Get(level.Component["particle"])) |  | ||||||
| 		colposition := (*components.Position)(ptquery.Get(level.Component["position"])) |  | ||||||
| 
 |  | ||||||
| 		if len(toRemove) > 0 { |  | ||||||
| 			// particle appears |  | ||||||
| 			colposition.Update( |  | ||||||
| 				particle_pos.X-(level.Cellsize/2), |  | ||||||
| 				particle_pos.Y-(level.Cellsize/2), |  | ||||||
| 				64, |  | ||||||
| 			) |  | ||||||
| 
 |  | ||||||
| 			particle.Index = 0 // start displaying the particle |  | ||||||
| 		} else { |  | ||||||
| 			switch { |  | ||||||
| 			case particle.Index > -1 && particle.Index < len(particle.Particles)-1: |  | ||||||
| 				particle.Index++ |  | ||||||
| 			default: |  | ||||||
| 				// last sprite reached, remove it |  | ||||||
| 				particle.Index = -1 |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 | 
 | ||||||
|  | func (level *Level) Draw(screen *ebiten.Image) { | ||||||
|  | 	level.GridSystem.Draw(screen) | ||||||
|  | 	level.Player.Draw(screen) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (level *Level) Position2Point(position *components.Position) image.Point { | func (level *Level) Position2Point(position *components.Position) image.Point { | ||||||
| @ -175,90 +60,6 @@ func (level *Level) Position2Point(position *components.Position) image.Point { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // 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) { |  | ||||||
| 	level.DrawTiles(screen) |  | ||||||
| 	level.DrawMovables(screen) |  | ||||||
| 	level.DrawParticles(screen) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (level *Level) DrawTiles(screen *ebiten.Image) { |  | ||||||
| 	op := &ebiten.DrawImageOptions{} |  | ||||||
| 
 |  | ||||||
| 	if !level.UseCache { |  | ||||||
| 		// map not cached or cacheable, write it to the cache |  | ||||||
| 		draw.Draw(level.Cache, level.Background.Bounds(), level.Background, image.ZP, draw.Src) |  | ||||||
| 
 |  | ||||||
| 		query := level.World.Query(level.Selector["tile"]) |  | ||||||
| 		for query.Next() { |  | ||||||
| 			pos := (*components.Position)(query.Get(level.Component["position"])) |  | ||||||
| 			sprite := (*components.Renderable)(query.Get(level.Component["renderable"])) |  | ||||||
| 
 |  | ||||||
| 			draw.Draw( |  | ||||||
| 				level.Cache, |  | ||||||
| 				image.Rect(pos.X, pos.Y, pos.X+pos.Cellsize, pos.Y+pos.Cellsize), |  | ||||||
| 				sprite.Image, image.ZP, draw.Over) |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		op.GeoM.Reset() |  | ||||||
| 		screen.DrawImage(level.Cache, op) |  | ||||||
| 
 |  | ||||||
| 		level.UseCache = true |  | ||||||
| 	} else { |  | ||||||
| 		// use the cached map |  | ||||||
| 		op.GeoM.Reset() |  | ||||||
| 		screen.DrawImage(level.Cache, op) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (level *Level) DrawMovables(screen *ebiten.Image) { |  | ||||||
| 	// write the movable tiles |  | ||||||
| 	op := &ebiten.DrawImageOptions{} |  | ||||||
| 	selector := level.Selector["movable"].Without(level.Component["solid"]) |  | ||||||
| 	query := level.World.Query(&selector) |  | ||||||
| 
 |  | ||||||
| 	for query.Next() { |  | ||||||
| 		pos := (*components.Position)(query.Get(level.Component["position"])) |  | ||||||
| 		sprite := (*components.Renderable)(query.Get(level.Component["renderable"])) |  | ||||||
| 
 |  | ||||||
| 		op.GeoM.Reset() |  | ||||||
| 		op.GeoM.Translate(float64(pos.X), float64(pos.Y)) |  | ||||||
| 
 |  | ||||||
| 		screen.DrawImage(sprite.Image, op) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (level *Level) DrawParticles(screen *ebiten.Image) { |  | ||||||
| 	// write particles (these are no tiles!) |  | ||||||
| 	op := &ebiten.DrawImageOptions{} |  | ||||||
| 	query := level.World.Query(level.Selector["particle"]) |  | ||||||
| 
 |  | ||||||
| 	for query.Next() { |  | ||||||
| 		pos := (*components.Position)(query.Get(level.Component["position"])) |  | ||||||
| 		particle := (*components.Particle)(query.Get(level.Component["particle"])) |  | ||||||
| 
 |  | ||||||
| 		if particle.Index > -1 { |  | ||||||
| 			op.GeoM.Reset() |  | ||||||
| 			op.GeoM.Translate(float64(pos.X), float64(pos.Y)) |  | ||||||
| 			screen.DrawImage(particle.Particles[particle.Index], op) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (level *Level) SetupGrid(game *Game) { | func (level *Level) SetupGrid(game *Game) { | ||||||
| 	// generic variant does not work here: | 	// generic variant does not work here: | ||||||
| 	// selector := generic.NewFilter1[components.Position]() | 	// selector := generic.NewFilter1[components.Position]() | ||||||
| @ -271,7 +72,7 @@ func (level *Level) SetupGrid(game *Game) { | |||||||
| 	level.World.Batch().RemoveEntities(selector) | 	level.World.Batch().RemoveEntities(selector) | ||||||
| 
 | 
 | ||||||
| 	// setup world | 	// setup world | ||||||
| 	level.Grid = NewGrid(game, level.Cellsize, level.Mapslice) | 	level.GridSystem.SetGrid(grid.NewGrid(game.World, level.Cellsize, level.Width, level.Height, level.Mapslice)) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func LevelToSlice(game *Game, level *assets.RawLevel, tilesize int) map[image.Point]*assets.Tile { | func LevelToSlice(game *Game, level *assets.RawLevel, tilesize int) map[image.Point]*assets.Tile { | ||||||
|  | |||||||
| @ -1,4 +1,4 @@ | |||||||
| package game | package grid | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"fmt" | 	"fmt" | ||||||
| @ -6,9 +6,8 @@ import ( | |||||||
| 	"log" | 	"log" | ||||||
| 	"openquell/assets" | 	"openquell/assets" | ||||||
| 	"openquell/components" | 	"openquell/components" | ||||||
| 	. "openquell/config" |  | ||||||
| 
 | 
 | ||||||
| 	"github.com/alecthomas/repr" | 	"github.com/mlange-42/arche/ecs" | ||||||
| 	"github.com/mlange-42/arche/generic" | 	"github.com/mlange-42/arche/generic" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| @ -17,39 +16,39 @@ type Grid struct { | |||||||
| 	Height   int | 	Height   int | ||||||
| 	Size     int | 	Size     int | ||||||
| 	Tilesize int | 	Tilesize int | ||||||
| 	TilesX   int |  | ||||||
| 	TilesY   int |  | ||||||
| 	Map      map[image.Point]*assets.Tile | 	Map      map[image.Point]*assets.Tile | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // FIXME: put component addition into extra callable function, to be called | // FIXME: put component addition into extra callable function, to be called | ||||||
| // by game.go in a level-loop. Also remove components of previous level | // 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 { | func NewGrid(world *ecs.World, | ||||||
|  | 	tilesize, width, height int, mapslice map[image.Point]*assets.Tile) *Grid { | ||||||
|  | 
 | ||||||
| 	// we use this to turn  our tiles into iterable entities, used for | 	// we use this to turn  our tiles into iterable entities, used for | ||||||
| 	// collision detection, transformation and other things | 	// collision detection, transformation and other things | ||||||
| 	playermapper := generic.NewMap4[ | 	playermapper := generic.NewMap4[ | ||||||
| 		components.Position, | 		components.Position, | ||||||
| 		components.Velocity, | 		components.Velocity, | ||||||
| 		components.Renderable, | 		components.Renderable, | ||||||
| 		components.Player](game.World) | 		components.Player](world) | ||||||
| 
 | 
 | ||||||
| 	solidmapper := generic.NewMap4[ | 	solidmapper := generic.NewMap4[ | ||||||
| 		components.Position, | 		components.Position, | ||||||
| 		components.Renderable, | 		components.Renderable, | ||||||
| 		components.Tilish, | 		components.Tilish, | ||||||
| 		components.Solid](game.World) | 		components.Solid](world) | ||||||
| 
 | 
 | ||||||
| 	emptymapper := generic.NewMap2[components.Position, components.Tilish](game.World) | 	emptymapper := generic.NewMap2[components.Position, components.Tilish](world) | ||||||
| 
 | 
 | ||||||
| 	colmapper := generic.NewMap3[ | 	colmapper := generic.NewMap3[ | ||||||
| 		components.Position, | 		components.Position, | ||||||
| 		components.Renderable, | 		components.Renderable, | ||||||
| 		components.Collectible](game.World) | 		components.Collectible](world) | ||||||
| 
 | 
 | ||||||
| 	ptmapper := generic.NewMap2[ | 	ptmapper := generic.NewMap2[ | ||||||
| 		components.Position, | 		components.Position, | ||||||
| 		components.Particle, | 		components.Particle, | ||||||
| 	](game.World) | 	](world) | ||||||
| 
 | 
 | ||||||
| 	var pos *components.Position | 	var pos *components.Position | ||||||
| 	var render *components.Renderable | 	var render *components.Renderable | ||||||
| @ -95,91 +94,22 @@ func NewGrid(game *Game, tilesize int, mapslice map[image.Point]*assets.Tile) *G | |||||||
| 	return &Grid{ | 	return &Grid{ | ||||||
| 		Size:     len(mapslice), | 		Size:     len(mapslice), | ||||||
| 		Tilesize: tilesize, | 		Tilesize: tilesize, | ||||||
| 		Width:    game.ScreenWidth, | 		Width:    width, | ||||||
| 		Height:   game.ScreenHeight, | 		Height:   height, | ||||||
| 		TilesX:   game.ScreenWidth / tilesize, |  | ||||||
| 		TilesY:   game.ScreenHeight / tilesize, |  | ||||||
| 		Map:      mapslice, | 		Map:      mapslice, | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (grid *Grid) GetSolidNeighborPosition( | // return the tile the moving object would end up on if indeed moving | ||||||
|  | func (grid *Grid) GetTile( | ||||||
| 	position *components.Position, | 	position *components.Position, | ||||||
| 	velocity *components.Velocity, | 	velocity *components.Velocity) *assets.Tile { | ||||||
| 	solid bool) (bool, *components.Position) { |  | ||||||
| 
 | 
 | ||||||
| 	if !solid { | 	newpoint := image.Point{ | ||||||
| 		return false, nil | 		int(position.X+velocity.Data.X) / grid.Tilesize, | ||||||
|  | 		int(position.Y+velocity.Data.Y) / grid.Tilesize, | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// set to true,  ifwe are already on the last  tile in the current | 	tile := grid.Map[newpoint] | ||||||
| 	// direction, i.e. on the edge of the grid | 	return tile | ||||||
| 	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) |  | ||||||
| 	repr.Println(newpos) |  | ||||||
| 
 |  | ||||||
| 	if !edge && grid.Map[neighborpos].Solid { |  | ||||||
| 		return true, newpos |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return false, nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (grid *Grid) BumpEdge( |  | ||||||
| 	pos *components.Position, |  | ||||||
| 	velocity *components.Velocity) (bool, *components.Position) { |  | ||||||
| 
 |  | ||||||
| 	x := pos.X + velocity.Data.X |  | ||||||
| 	y := pos.Y + velocity.Data.Y |  | ||||||
| 
 |  | ||||||
| 	if x < 0 || x > grid.Width-grid.Tilesize || y < 0 || y > grid.Height-grid.Tilesize { |  | ||||||
| 		newpos := &components.Position{} |  | ||||||
| 		X := pos.X |  | ||||||
| 		Y := pos.Y |  | ||||||
| 
 |  | ||||||
| 		switch velocity.Direction { |  | ||||||
| 		case East: |  | ||||||
| 			X = 0 |  | ||||||
| 			fmt.Println("east X=0") |  | ||||||
| 		case West: |  | ||||||
| 			X = grid.Width - grid.Tilesize |  | ||||||
| 			fmt.Println("west X=max") |  | ||||||
| 		case South: |  | ||||||
| 			Y = 0 |  | ||||||
| 			fmt.Println("south y=0") |  | ||||||
| 		case North: |  | ||||||
| 			Y = grid.Height - grid.Tilesize |  | ||||||
| 			fmt.Println("north y=max") |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		newpos.Update(X, Y, grid.Tilesize) |  | ||||||
| 		return true, newpos |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return false, nil |  | ||||||
| } | } | ||||||
							
								
								
									
										75
									
								
								systems/collectible_system.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								systems/collectible_system.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,75 @@ | |||||||
|  | package systems | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  | 	"image" | ||||||
|  | 	. "openquell/components" | ||||||
|  | 
 | ||||||
|  | 	"github.com/hajimehoshi/ebiten/v2" | ||||||
|  | 	"github.com/mlange-42/arche/ecs" | ||||||
|  | 	"github.com/mlange-42/arche/generic" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | type CollectibleSystem struct { | ||||||
|  | 	World            *ecs.World | ||||||
|  | 	Selector         *generic.Filter3[Position, Collectible, Renderable] | ||||||
|  | 	EntitiesToRemove []ecs.Entity | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func NewCollectibleSystem(world *ecs.World) *CollectibleSystem { | ||||||
|  | 	system := &CollectibleSystem{ | ||||||
|  | 		Selector: generic.NewFilter3[Position, Collectible, Renderable](), | ||||||
|  | 		World:    world, | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return system | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (system *CollectibleSystem) CheckPlayerCollision( | ||||||
|  | 	playerposition *Position, | ||||||
|  | 	playervelocity *Velocity) (bool, image.Point) { | ||||||
|  | 
 | ||||||
|  | 	particle_pos := image.Point{} | ||||||
|  | 	var bumped bool | ||||||
|  | 
 | ||||||
|  | 	query := system.Selector.Query(system.World) | ||||||
|  | 
 | ||||||
|  | 	for query.Next() { | ||||||
|  | 		colposition, collectible, _ := query.Get() | ||||||
|  | 
 | ||||||
|  | 		ok, _ := playerposition.Intersects(colposition, playervelocity) | ||||||
|  | 		if ok { | ||||||
|  | 			fmt.Printf("bumped into collectible %v\n", collectible) | ||||||
|  | 			system.EntitiesToRemove = append(system.EntitiesToRemove, query.Entity()) | ||||||
|  | 			particle_pos.X = colposition.X | ||||||
|  | 			particle_pos.Y = colposition.Y | ||||||
|  | 			bumped = true | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return bumped, particle_pos | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (system *CollectibleSystem) Update() { | ||||||
|  | 	// remove collectible if collected | ||||||
|  | 	for _, entity := range system.EntitiesToRemove { | ||||||
|  | 		system.World.RemoveEntity(entity) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	system.EntitiesToRemove = []ecs.Entity{} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (system *CollectibleSystem) Draw(screen *ebiten.Image) { | ||||||
|  | 	// write the movable tiles | ||||||
|  | 	op := &ebiten.DrawImageOptions{} | ||||||
|  | 	query := system.Selector.Query(system.World) | ||||||
|  | 
 | ||||||
|  | 	for query.Next() { | ||||||
|  | 		pos, _, sprite := query.Get() | ||||||
|  | 
 | ||||||
|  | 		op.GeoM.Reset() | ||||||
|  | 		op.GeoM.Translate(float64(pos.X), float64(pos.Y)) | ||||||
|  | 
 | ||||||
|  | 		screen.DrawImage(sprite.Image, op) | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										163
									
								
								systems/grid_system.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										163
									
								
								systems/grid_system.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,163 @@ | |||||||
|  | package systems | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  | 	"image" | ||||||
|  | 	"image/draw" | ||||||
|  | 	"openquell/components" | ||||||
|  | 	. "openquell/components" | ||||||
|  | 	. "openquell/config" | ||||||
|  | 	"openquell/grid" | ||||||
|  | 
 | ||||||
|  | 	"github.com/alecthomas/repr" | ||||||
|  | 	"github.com/hajimehoshi/ebiten/v2" | ||||||
|  | 	"github.com/mlange-42/arche/ecs" | ||||||
|  | 	"github.com/mlange-42/arche/generic" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | type GridSystem struct { | ||||||
|  | 	World                                   *ecs.World | ||||||
|  | 	Selector                                *generic.Filter3[Renderable, Position, Solid] | ||||||
|  | 	UseCache                                bool | ||||||
|  | 	Cache                                   *ebiten.Image | ||||||
|  | 	Background                              *ebiten.Image | ||||||
|  | 	Width, Height, TilesX, TilesY, Tilesize int | ||||||
|  | 	Grid                                    *grid.Grid | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func NewGridSystem(world *ecs.World, width, height, | ||||||
|  | 	tilesize int, background *ebiten.Image) *GridSystem { | ||||||
|  | 
 | ||||||
|  | 	cache := ebiten.NewImage(width, height) | ||||||
|  | 
 | ||||||
|  | 	system := &GridSystem{ | ||||||
|  | 		Selector:   generic.NewFilter3[Renderable, Position, Solid](), | ||||||
|  | 		UseCache:   false, | ||||||
|  | 		Cache:      cache, | ||||||
|  | 		Width:      width, | ||||||
|  | 		Height:     height, | ||||||
|  | 		TilesX:     width / tilesize, | ||||||
|  | 		TilesY:     height / tilesize, | ||||||
|  | 		Tilesize:   tilesize, | ||||||
|  | 		Background: background, | ||||||
|  | 		World:      world, | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return system | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (system *GridSystem) SetGrid(grid *grid.Grid) { | ||||||
|  | 	system.Grid = grid | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (system *GridSystem) Update() {} | ||||||
|  | 
 | ||||||
|  | func (system *GridSystem) Draw(screen *ebiten.Image) { | ||||||
|  | 	op := &ebiten.DrawImageOptions{} | ||||||
|  | 
 | ||||||
|  | 	if !system.UseCache { | ||||||
|  | 		// map not cached or cacheable, write it to the cache | ||||||
|  | 		draw.Draw(system.Cache, system.Background.Bounds(), system.Background, image.ZP, draw.Src) | ||||||
|  | 
 | ||||||
|  | 		query := system.Selector.Query(system.World) | ||||||
|  | 
 | ||||||
|  | 		for query.Next() { | ||||||
|  | 			sprite, pos, _ := query.Get() | ||||||
|  | 
 | ||||||
|  | 			draw.Draw( | ||||||
|  | 				system.Cache, | ||||||
|  | 				image.Rect(pos.X, pos.Y, pos.X+pos.Cellsize, pos.Y+pos.Cellsize), | ||||||
|  | 				sprite.Image, image.ZP, draw.Over) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		op.GeoM.Reset() | ||||||
|  | 		screen.DrawImage(system.Cache, op) | ||||||
|  | 
 | ||||||
|  | 		system.UseCache = true | ||||||
|  | 	} else { | ||||||
|  | 		// use the cached map | ||||||
|  | 		op.GeoM.Reset() | ||||||
|  | 		screen.DrawImage(system.Cache, op) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (system *GridSystem) GetSolidNeighborPosition( | ||||||
|  | 	position *components.Position, | ||||||
|  | 	velocity *components.Velocity, | ||||||
|  | 	solid bool) (bool, *components.Position) { | ||||||
|  | 
 | ||||||
|  | 	if !solid { | ||||||
|  | 		return false, nil | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// 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 < system.TilesX { | ||||||
|  | 			neighborpos.X++ | ||||||
|  | 			edge = false | ||||||
|  | 		} | ||||||
|  | 	case West: | ||||||
|  | 		if neighborpos.X > 0 { | ||||||
|  | 			neighborpos.X-- | ||||||
|  | 			edge = false | ||||||
|  | 		} | ||||||
|  | 	case South: | ||||||
|  | 		if neighborpos.Y < system.TilesY { | ||||||
|  | 			neighborpos.Y++ | ||||||
|  | 			edge = false | ||||||
|  | 		} | ||||||
|  | 	case North: | ||||||
|  | 		if neighborpos.Y > 0 { | ||||||
|  | 			neighborpos.Y-- | ||||||
|  | 			edge = false | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	newpos := components.NewPosition(neighborpos, system.Tilesize) | ||||||
|  | 	repr.Println(newpos) | ||||||
|  | 
 | ||||||
|  | 	if !edge && system.Grid.Map[neighborpos].Solid { | ||||||
|  | 		return true, newpos | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return false, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (system *GridSystem) BumpEdge( | ||||||
|  | 	pos *components.Position, | ||||||
|  | 	velocity *components.Velocity) (bool, *components.Position) { | ||||||
|  | 
 | ||||||
|  | 	x := pos.X + velocity.Data.X | ||||||
|  | 	y := pos.Y + velocity.Data.Y | ||||||
|  | 
 | ||||||
|  | 	if x < 0 || x > system.Width-system.Tilesize || y < 0 || y > system.Height-system.Tilesize { | ||||||
|  | 		newpos := &components.Position{} | ||||||
|  | 		X := pos.X | ||||||
|  | 		Y := pos.Y | ||||||
|  | 
 | ||||||
|  | 		switch velocity.Direction { | ||||||
|  | 		case East: | ||||||
|  | 			X = 0 | ||||||
|  | 			fmt.Println("east X=0") | ||||||
|  | 		case West: | ||||||
|  | 			X = system.Width - system.Tilesize | ||||||
|  | 			fmt.Println("west X=max") | ||||||
|  | 		case South: | ||||||
|  | 			Y = 0 | ||||||
|  | 			fmt.Println("south y=0") | ||||||
|  | 		case North: | ||||||
|  | 			Y = system.Height - system.Tilesize | ||||||
|  | 			fmt.Println("north y=max") | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		newpos.Update(X, Y, system.Tilesize) | ||||||
|  | 		return true, newpos | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return false, nil | ||||||
|  | } | ||||||
| @ -1,9 +1,10 @@ | |||||||
| package game | package systems | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"image" | 	"image" | ||||||
| 	. "openquell/components" | 	. "openquell/components" | ||||||
| 
 | 
 | ||||||
|  | 	"github.com/hajimehoshi/ebiten/v2" | ||||||
| 	"github.com/mlange-42/arche/ecs" | 	"github.com/mlange-42/arche/ecs" | ||||||
| 	"github.com/mlange-42/arche/generic" | 	"github.com/mlange-42/arche/generic" | ||||||
| ) | ) | ||||||
| @ -14,9 +15,11 @@ type ParticleSystem struct { | |||||||
| 	Cellsize int | 	Cellsize int | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func NewParticleSystem(world *ecs.World) *ParticleSystem { | func NewParticleSystem(world *ecs.World, cellsize int) *ParticleSystem { | ||||||
| 	system := &ParticleSystem{ | 	system := &ParticleSystem{ | ||||||
| 		Selector: generic.NewFilter2[Position, Particle](), | 		Selector: generic.NewFilter2[Position, Particle](), | ||||||
|  | 		World:    world, | ||||||
|  | 		Cellsize: cellsize, | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	return system | 	return system | ||||||
| @ -51,3 +54,20 @@ func (system *ParticleSystem) Update(detonate bool, position *image.Point) { | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | func (system *ParticleSystem) Draw(screen *ebiten.Image) { | ||||||
|  | 	// write particles (these are no tiles!) | ||||||
|  | 	op := &ebiten.DrawImageOptions{} | ||||||
|  | 	query := system.Selector.Query(system.World) | ||||||
|  | 
 | ||||||
|  | 	for query.Next() { | ||||||
|  | 		pos, particle := query.Get() | ||||||
|  | 
 | ||||||
|  | 		if particle.Index > -1 { | ||||||
|  | 			op.GeoM.Reset() | ||||||
|  | 			op.GeoM.Translate(float64(pos.X), float64(pos.Y)) | ||||||
|  | 			screen.DrawImage(particle.Particles[particle.Index], op) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | } | ||||||
| @ -1,4 +1,4 @@ | |||||||
| package game | package systems | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"fmt" | 	"fmt" | ||||||
| @ -13,16 +13,19 @@ import ( | |||||||
| 
 | 
 | ||||||
| type PlayerSystem struct { | type PlayerSystem struct { | ||||||
| 	World       *ecs.World | 	World       *ecs.World | ||||||
| 	Grid        *Grid | 	Selector    *generic.Filter4[Position, Velocity, Player, Renderable] | ||||||
| 	Selector    *generic.Filter3[Position, Velocity, Player] |  | ||||||
| 	Particle    *ParticleSystem | 	Particle    *ParticleSystem | ||||||
| 	Collectible *CollectibleSystem | 	Collectible *CollectibleSystem | ||||||
|  | 	Grid        *GridSystem | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func NewPlayerSystem(world *ecs.World) *PlayerSystem { | func NewPlayerSystem(world *ecs.World, grid *GridSystem) *PlayerSystem { | ||||||
| 
 |  | ||||||
| 	system := &PlayerSystem{ | 	system := &PlayerSystem{ | ||||||
| 		Selector: generic.NewFilter3[Position, Velocity, Player](), | 		Selector:    generic.NewFilter4[Position, Velocity, Player, Renderable](), | ||||||
|  | 		Particle:    NewParticleSystem(world, grid.Tilesize), | ||||||
|  | 		Collectible: NewCollectibleSystem(world), | ||||||
|  | 		Grid:        grid, | ||||||
|  | 		World:       world, | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	return system | 	return system | ||||||
| @ -35,7 +38,7 @@ func (system PlayerSystem) Update() error { | |||||||
| 	var particle_pos image.Point | 	var particle_pos image.Point | ||||||
| 
 | 
 | ||||||
| 	for query.Next() { | 	for query.Next() { | ||||||
| 		playerposition, velocity, _ := query.Get() | 		playerposition, velocity, _, _ := query.Get() | ||||||
| 
 | 
 | ||||||
| 		if !velocity.Moving() { | 		if !velocity.Moving() { | ||||||
| 			switch { | 			switch { | ||||||
| @ -75,9 +78,29 @@ func (system PlayerSystem) Update() error { | |||||||
| 			bumped, particle_pos = system.Collectible.CheckPlayerCollision(playerposition, velocity) | 			bumped, particle_pos = system.Collectible.CheckPlayerCollision(playerposition, velocity) | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
|  | 		playerposition.Move(velocity) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	system.Particle.Update(bumped, &particle_pos) | 	system.Particle.Update(bumped, &particle_pos) | ||||||
|  | 	system.Collectible.Update() | ||||||
| 
 | 
 | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | func (system *PlayerSystem) Draw(screen *ebiten.Image) { | ||||||
|  | 	// write the movable tiles | ||||||
|  | 	op := &ebiten.DrawImageOptions{} | ||||||
|  | 	query := system.Selector.Query(system.World) | ||||||
|  | 
 | ||||||
|  | 	for query.Next() { | ||||||
|  | 		pos, _, _, sprite := query.Get() | ||||||
|  | 
 | ||||||
|  | 		op.GeoM.Reset() | ||||||
|  | 		op.GeoM.Translate(float64(pos.X), float64(pos.Y)) | ||||||
|  | 
 | ||||||
|  | 		screen.DrawImage(sprite.Image, op) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	system.Collectible.Draw(screen) | ||||||
|  | 	system.Particle.Draw(screen) | ||||||
|  | } | ||||||
| @ -1,4 +1,4 @@ | |||||||
| package system | package systems | ||||||
| 
 | 
 | ||||||
| import "github.com/hajimehoshi/ebiten/v2" | import "github.com/hajimehoshi/ebiten/v2" | ||||||
| 
 | 
 | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user