Add pathfinding system
This commit is contained in:
parent
60fc3a544c
commit
868cfe9aa3
|
@ -33,7 +33,7 @@ func NewGame() (*Game, error) {
|
|||
once := gohan.NewEntity()
|
||||
once.AddComponent(&component.Once{})
|
||||
|
||||
world.Map = world.NewGameMap(time.Now().UnixNano(), world.MapSize, world.MapSize)
|
||||
world.Map = world.NewGameMap(time.Now().UnixNano())
|
||||
|
||||
return g, nil
|
||||
}
|
||||
|
|
1
go.mod
1
go.mod
|
@ -4,6 +4,7 @@ go 1.19
|
|||
|
||||
require (
|
||||
code.rocketnine.space/tslocum/gohan v1.0.0
|
||||
github.com/beefsack/go-astar v0.0.0-20200827232313-4ecf9e304482
|
||||
github.com/hajimehoshi/ebiten/v2 v2.4.12
|
||||
)
|
||||
|
||||
|
|
2
go.sum
2
go.sum
|
@ -1,6 +1,8 @@
|
|||
code.rocketnine.space/tslocum/gohan v1.0.0 h1:WBcJq7nVfmr1EB8bew6xWlB5Q1714yWJ3a9/q6aBBrY=
|
||||
code.rocketnine.space/tslocum/gohan v1.0.0/go.mod h1:12yOt5Ygl/RVwnnZSVZRuS1W6gCaHJgezcvg8+THk10=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/beefsack/go-astar v0.0.0-20200827232313-4ecf9e304482 h1:p4g4uok3+r6Tg6fxXEQUAcMAX/WdK6WhkQW9s0jaT7k=
|
||||
github.com/beefsack/go-astar v0.0.0-20200827232313-4ecf9e304482/go.mod h1:Cu3t5VeqE8kXjUBeNXWQprfuaP5UCIc5ggGjgMx9KFc=
|
||||
github.com/ebitengine/purego v0.0.0-20220905075623-aeed57cda744/go.mod h1:Eh8I3yvknDYZeCuXH9kRNaPuHEwvXDCk378o9xszmHg=
|
||||
github.com/ebitengine/purego v0.1.0 h1:vAEo1FvmbjA050QKsbDbcHj03hhMMvh0fmr9LSehpnU=
|
||||
github.com/ebitengine/purego v0.1.0/go.mod h1:Eh8I3yvknDYZeCuXH9kRNaPuHEwvXDCk378o9xszmHg=
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"code.rocketnine.space/tslocum/commandeuropa/component"
|
||||
"code.rocketnine.space/tslocum/commandeuropa/world"
|
||||
"code.rocketnine.space/tslocum/gohan"
|
||||
"github.com/beefsack/go-astar"
|
||||
"github.com/hajimehoshi/ebiten/v2"
|
||||
"github.com/hajimehoshi/ebiten/v2/inpututil"
|
||||
)
|
||||
|
@ -86,13 +87,25 @@ func (r *HandleInput) Update(e gohan.Entity) error {
|
|||
}
|
||||
|
||||
if inpututil.IsKeyJustPressed(ebiten.KeyV) && ebiten.IsKeyPressed(ebiten.KeyShift) {
|
||||
if world.Debug == 0 {
|
||||
world.Debug = 1
|
||||
} else {
|
||||
world.Debug++
|
||||
if world.Debug > 2 {
|
||||
world.Debug = 0
|
||||
}
|
||||
}
|
||||
|
||||
if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonLeft) {
|
||||
tx, ty := world.ScreenCoordinatesToPath(world.CursorX, world.CursorY)
|
||||
from := world.Map[world.PathTileIndex(1, 1)]
|
||||
to := world.Map[world.PathTileIndex(tx, ty)]
|
||||
|
||||
path, _, _ := astar.Path(from, to)
|
||||
world.MapPath = nil
|
||||
for _, p := range path {
|
||||
t := p.(world.Tile)
|
||||
world.MapPath = append(world.MapPath, [2]int{t.X, t.Y})
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ package system
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"image"
|
||||
"image/color"
|
||||
|
||||
"code.rocketnine.space/tslocum/commandeuropa/component"
|
||||
|
@ -39,6 +40,46 @@ func (s *RenderDebug) Draw(e gohan.Entity, screen *ebiten.Image) error {
|
|||
s.Initialize()
|
||||
}
|
||||
|
||||
if world.Debug > 1 {
|
||||
for x := 0; x < world.MapSize; x++ {
|
||||
for y := 0; y < world.MapSize; y++ {
|
||||
for ox := 0; ox < 4; ox++ {
|
||||
for oy := 0; oy < 4; oy++ {
|
||||
px, py := x*4+ox, y*4+oy
|
||||
fillColor := color.RGBA{255, 0, 0, 255}
|
||||
if world.Map[world.PathTileIndex(px, py)].Walkable {
|
||||
fillColor = color.RGBA{0, 255, 0, 255}
|
||||
}
|
||||
|
||||
drawX, drawY := world.PathCoordinatesToScreen(px, py)
|
||||
|
||||
// Skip drawing off-screen tiles.
|
||||
if world.PixelCoordinatesOffScreen(drawX, drawY) {
|
||||
continue
|
||||
}
|
||||
|
||||
screen.SubImage(image.Rect(drawX, drawY, drawX+4*world.CamScale, drawY+4*world.CamScale)).(*ebiten.Image).Fill(fillColor)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(world.MapPath) > 0 {
|
||||
for _, xy := range world.MapPath {
|
||||
x, y := xy[0], xy[1]
|
||||
drawX, drawY := world.PathCoordinatesToScreen(x, y)
|
||||
|
||||
// Skip drawing off-screen tiles.
|
||||
if world.PixelCoordinatesOffScreen(drawX, drawY) {
|
||||
continue
|
||||
}
|
||||
|
||||
fillColor := color.RGBA{255, 255, 255, 255}
|
||||
screen.SubImage(image.Rect(drawX, drawY, drawX+4*world.CamScale, drawY+4*world.CamScale)).(*ebiten.Image).Fill(fillColor)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
s.debugImg.Fill(color.RGBA{0, 0, 0, 80})
|
||||
ebitenutil.DebugPrintAt(s.debugImg, fmt.Sprintf("ENT %d\nUPD %d\nDRA %d\nTPS %0.0f\nFPS %0.0f", gohan.CurrentEntities(), gohan.CurrentUpdates(), gohan.CurrentDraws(), ebiten.CurrentTPS(), ebiten.CurrentFPS()), 2, 0)
|
||||
screen.DrawImage(s.debugImg, s.op)
|
||||
|
|
|
@ -30,30 +30,24 @@ func (r *RenderEnvironment) Draw(e gohan.Entity, screen *ebiten.Image) error {
|
|||
r.Initialize()
|
||||
}
|
||||
|
||||
x, y := -1, 0
|
||||
for _, t := range world.Map {
|
||||
x++
|
||||
if x == world.MapSize {
|
||||
y++
|
||||
x = 0
|
||||
for x := 0; x < world.MapSize; x++ {
|
||||
for y := 0; y < world.MapSize; y++ {
|
||||
i := world.TileIndex(x, y)
|
||||
t := world.Map[i]
|
||||
|
||||
if t.Sprite == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
drawX, drawY := world.LevelCoordinatesToScreen(x, y)
|
||||
|
||||
// Skip drawing off-screen tiles.
|
||||
if world.PixelCoordinatesOffScreen(drawX, drawY) {
|
||||
continue
|
||||
}
|
||||
|
||||
r.renderTile(t, x, y, screen)
|
||||
}
|
||||
|
||||
drawX, drawY := world.LevelCoordinatesToScreen(x, y)
|
||||
|
||||
// Skip drawing off-screen tiles.
|
||||
padding := world.TileSize * world.CamScale
|
||||
width, height := world.TileSize*world.CamScale, world.TileSize*world.CamScale
|
||||
left := drawX
|
||||
right := drawX + width
|
||||
top := drawY
|
||||
bottom := drawY + height
|
||||
if (left < -padding || left > world.ScreenWidth+padding) || (top < -padding || top > world.ScreenHeight+padding) ||
|
||||
(right < -padding || right > world.ScreenWidth+padding) || (bottom < -padding || bottom > world.ScreenHeight+padding) {
|
||||
continue
|
||||
|
||||
}
|
||||
|
||||
r.renderTile(t, x, y, screen)
|
||||
}
|
||||
|
||||
highlightX, highlightY := -1, -1
|
||||
|
@ -81,7 +75,7 @@ func (r *RenderEnvironment) Draw(e gohan.Entity, screen *ebiten.Image) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (r *RenderEnvironment) renderTile(t world.MapTile, x int, y int, target *ebiten.Image) int {
|
||||
func (r *RenderEnvironment) renderTile(t world.Tile, x int, y int, target *ebiten.Image) int {
|
||||
r.op.GeoM.Reset()
|
||||
|
||||
// Move to current position.
|
||||
|
|
85
world/map.go
85
world/map.go
|
@ -7,6 +7,10 @@ import (
|
|||
"github.com/hajimehoshi/ebiten/v2"
|
||||
)
|
||||
|
||||
const MapSize = 128
|
||||
const tileDivisions = 4
|
||||
const numTiles = MapSize * MapSize * tileDivisions * tileDivisions
|
||||
|
||||
func colorTile(r, g, b uint8) *ebiten.Image {
|
||||
img := ebiten.NewImage(TileSize, TileSize)
|
||||
img.Fill(color.RGBA{r, g, b, 255})
|
||||
|
@ -35,16 +39,36 @@ var (
|
|||
redTiles = []*ebiten.Image{redTile1, redTile1, redTile2, redTile3}
|
||||
)
|
||||
|
||||
type MapTile struct {
|
||||
Sprite *ebiten.Image
|
||||
}
|
||||
|
||||
func NewGameMap(seed int64, w, h int) []MapTile {
|
||||
func NewGameMap(seed int64) []Tile {
|
||||
r := rand.New(rand.NewSource(seed))
|
||||
|
||||
tiles := make([]MapTile, w*h)
|
||||
for i := range tiles {
|
||||
tiles[i].Sprite = lightBlueTiles[r.Intn(len(lightBlueTiles))]
|
||||
tiles := make([]Tile, numTiles)
|
||||
{
|
||||
tx, ty := 0, 0
|
||||
for i := range tiles {
|
||||
tiles[i].X, tiles[i].Y = tx, ty
|
||||
tiles[i].Walkable = true
|
||||
|
||||
tx++
|
||||
if tx == MapSize*4 {
|
||||
tx = 0
|
||||
ty++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for x := 0; x < MapSize; x++ {
|
||||
for y := 0; y < MapSize; y++ {
|
||||
index := TileIndex(x, y)
|
||||
tiles[index].Sprite = lightBlueTiles[r.Intn(len(lightBlueTiles))]
|
||||
}
|
||||
}
|
||||
|
||||
setWalkable := func(tx, ty int, walkable bool) {
|
||||
tileIndexes := PathTilesAtMapTile(tx, ty)
|
||||
for _, index := range tileIndexes {
|
||||
tiles[index].Walkable = walkable
|
||||
}
|
||||
}
|
||||
|
||||
numMediumBlue := r.Intn(10) + 7
|
||||
|
@ -54,10 +78,12 @@ func NewGameMap(seed int64, w, h int) []MapTile {
|
|||
bSizeY := r.Intn(TileSize) + 7
|
||||
for offsetX := 0; offsetX < bSizeX; offsetX++ {
|
||||
for offsetY := 0; offsetY < bSizeY; offsetY++ {
|
||||
index := TileIndex(bx+offsetX, by+offsetY)
|
||||
tx, ty := bx+offsetX, by+offsetY
|
||||
index := TileIndex(tx, ty)
|
||||
if index == -1 {
|
||||
continue
|
||||
}
|
||||
setWalkable(tx, ty, false)
|
||||
tiles[index].Sprite = mediumBlueTiles[r.Intn(len(mediumBlueTiles))]
|
||||
}
|
||||
}
|
||||
|
@ -70,10 +96,12 @@ func NewGameMap(seed int64, w, h int) []MapTile {
|
|||
bSizeY := r.Intn(12) + 7
|
||||
for offsetX := 0; offsetX < bSizeX; offsetX++ {
|
||||
for offsetY := 0; offsetY < bSizeY; offsetY++ {
|
||||
index := TileIndex(bx+offsetX, by+offsetY)
|
||||
tx, ty := bx+offsetX, by+offsetY
|
||||
index := TileIndex(tx, ty)
|
||||
if index == -1 {
|
||||
continue
|
||||
}
|
||||
setWalkable(tx, ty, false)
|
||||
tiles[index].Sprite = darkBlueTiles[r.Intn(len(darkBlueTiles))]
|
||||
}
|
||||
}
|
||||
|
@ -86,15 +114,17 @@ func NewGameMap(seed int64, w, h int) []MapTile {
|
|||
|
||||
bSize := rand.Intn(14) + 32
|
||||
for offset := 0; offset < bSize; offset++ {
|
||||
var index int
|
||||
var tx, ty int
|
||||
if vertical {
|
||||
index = TileIndex(bx, by+offset)
|
||||
tx, ty = bx, by+offset
|
||||
} else {
|
||||
index = TileIndex(bx+offset, by)
|
||||
tx, ty = bx+offset, by
|
||||
}
|
||||
if index == -1 {
|
||||
index := TileIndex(tx, ty)
|
||||
if index == -1 || !tiles[index].Walkable {
|
||||
continue
|
||||
}
|
||||
setWalkable(tx, ty, false)
|
||||
tiles[index].Sprite = redTiles[r.Intn(len(redTiles))]
|
||||
}
|
||||
}
|
||||
|
@ -102,9 +132,34 @@ func NewGameMap(seed int64, w, h int) []MapTile {
|
|||
return tiles
|
||||
}
|
||||
|
||||
func PathTileIndex(x, y int) int {
|
||||
if x < 0 || y < 0 || x >= MapSize*tileDivisions || y >= MapSize*tileDivisions {
|
||||
return -1
|
||||
}
|
||||
return y*MapSize*tileDivisions + x
|
||||
}
|
||||
|
||||
func TileIndex(x, y int) int {
|
||||
if x < 0 || y < 0 || x >= MapSize || y >= MapSize {
|
||||
return -1
|
||||
}
|
||||
return y*MapSize + x
|
||||
return y*tileDivisions*MapSize*tileDivisions + x*tileDivisions
|
||||
}
|
||||
|
||||
func ValidXY(x, y int) bool {
|
||||
return x >= 0 && y >= 0 && x < MapSize && y < MapSize
|
||||
}
|
||||
|
||||
func PathTilesAtMapTile(tx, ty int) []int {
|
||||
pi := TileIndex(tx, ty)
|
||||
|
||||
tiles := make([]int, 16)
|
||||
i := 0
|
||||
for offsetY := 0; offsetY < 4; offsetY++ {
|
||||
for offsetX := 0; offsetX < 4; offsetX++ {
|
||||
tiles[i] = pi + (offsetY * MapSize * 4) + offsetX
|
||||
i++
|
||||
}
|
||||
}
|
||||
return tiles
|
||||
}
|
||||
|
|
|
@ -0,0 +1,78 @@
|
|||
package world
|
||||
|
||||
import (
|
||||
"github.com/beefsack/go-astar"
|
||||
"github.com/hajimehoshi/ebiten/v2"
|
||||
)
|
||||
|
||||
const TileSize = 16
|
||||
|
||||
type Tile struct {
|
||||
X int
|
||||
Y int
|
||||
Walkable bool
|
||||
|
||||
Sprite *ebiten.Image
|
||||
}
|
||||
|
||||
func (t Tile) Up() int {
|
||||
tx, ty := t.X, t.Y-1
|
||||
return PathTileIndex(tx, ty)
|
||||
}
|
||||
|
||||
func (t Tile) Down() int {
|
||||
tx, ty := t.X, t.Y+1
|
||||
return PathTileIndex(tx, ty)
|
||||
}
|
||||
|
||||
func (t Tile) Left() int {
|
||||
tx, ty := t.X-1, t.Y
|
||||
return PathTileIndex(tx, ty)
|
||||
}
|
||||
|
||||
func (t Tile) Right() int {
|
||||
tx, ty := t.X+1, t.Y
|
||||
return PathTileIndex(tx, ty)
|
||||
}
|
||||
|
||||
func (t Tile) PathNeighbors() []astar.Pather {
|
||||
var neighbors []astar.Pather
|
||||
i := t.Up()
|
||||
if i != -1 && Map[i].Walkable {
|
||||
neighbors = append(neighbors, Map[i])
|
||||
}
|
||||
i = t.Down()
|
||||
if i != -1 && Map[i].Walkable {
|
||||
neighbors = append(neighbors, Map[i])
|
||||
}
|
||||
i = t.Left()
|
||||
if i != -1 && Map[i].Walkable {
|
||||
neighbors = append(neighbors, Map[i])
|
||||
}
|
||||
i = t.Right()
|
||||
if i != -1 && Map[i].Walkable {
|
||||
neighbors = append(neighbors, Map[i])
|
||||
}
|
||||
return neighbors
|
||||
}
|
||||
|
||||
func (t Tile) PathNeighborCost(to astar.Pather) float64 {
|
||||
toT := to.(Tile)
|
||||
if !toT.Walkable {
|
||||
return 0
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
||||
func (t Tile) PathEstimatedCost(to astar.Pather) float64 {
|
||||
toT := to.(Tile)
|
||||
x := toT.X - t.X
|
||||
if x < 0 {
|
||||
x = -x
|
||||
}
|
||||
y := toT.Y - t.Y
|
||||
if y < 0 {
|
||||
y = -y
|
||||
}
|
||||
return float64(x + y)
|
||||
}
|
|
@ -8,10 +8,6 @@ import (
|
|||
|
||||
const TPS = 144
|
||||
|
||||
const TileSize = 16
|
||||
|
||||
const MapSize = 128
|
||||
|
||||
var (
|
||||
ScreenWidth, ScreenHeight = 800, 600
|
||||
|
||||
|
@ -22,7 +18,9 @@ var (
|
|||
|
||||
CursorX, CursorY = 0, 0
|
||||
|
||||
Map []MapTile
|
||||
Map []Tile
|
||||
|
||||
MapPath [][2]int
|
||||
|
||||
Debug int
|
||||
|
||||
|
@ -43,6 +41,22 @@ func LevelCoordinatesToScreen(x, y int) (int, int) {
|
|||
return (x*TileSize-CamX)*CamScale + ScreenWidth/2, (y*TileSize-CamY)*CamScale + ScreenHeight/2
|
||||
}
|
||||
|
||||
func PathCoordinatesToScreen(x, y int) (int, int) {
|
||||
return (x*tileDivisions-CamX)*CamScale + ScreenWidth/2, (y*tileDivisions-CamY)*CamScale + ScreenHeight/2
|
||||
}
|
||||
|
||||
func PixelCoordinatesOffScreen(x, y int) bool {
|
||||
padding := TileSize * CamScale
|
||||
left, right := x, x+TileSize*CamScale
|
||||
top, bottom := y, y+TileSize*CamScale
|
||||
return (left < -padding || left > ScreenWidth+padding) || (top < -padding || top > ScreenHeight+padding) ||
|
||||
(right < -padding || right > ScreenWidth+padding) || (bottom < -padding || bottom > ScreenHeight+padding)
|
||||
}
|
||||
|
||||
func ScreenCoordinatesToLevel(x, y int) (int, int) {
|
||||
return (((x - ScreenWidth/2) / CamScale) + CamX) / TileSize, (((y - ScreenHeight/2) / CamScale) + CamY) / TileSize
|
||||
}
|
||||
|
||||
func ScreenCoordinatesToPath(x, y int) (int, int) {
|
||||
return (((x - ScreenWidth/2) / CamScale) + CamX) / tileDivisions, (((y - ScreenHeight/2) / CamScale) + CamY) / tileDivisions
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue