diff --git a/component/sprite.go b/component/sprite.go index c97cdcd..1f24c52 100644 --- a/component/sprite.go +++ b/component/sprite.go @@ -3,5 +3,7 @@ package component import "github.com/hajimehoshi/ebiten/v2" type Sprite struct { - Image *ebiten.Image + Image *ebiten.Image + Width float64 + Height float64 } diff --git a/component/unit.go b/component/unit.go index 4de5a21..a43626c 100644 --- a/component/unit.go +++ b/component/unit.go @@ -1,7 +1,8 @@ package component type Unit struct { - Type UnitType + Type UnitType + Selected bool } type UnitType int diff --git a/entity/unit.go b/entity/unit.go index 3df99dc..cf387aa 100644 --- a/entity/unit.go +++ b/entity/unit.go @@ -18,7 +18,7 @@ func unitSprite(unitType component.UnitType) *ebiten.Image { img = ebiten.NewImage(16, 16) img.Fill(color.RGBA{0, 255, 0, 255}) } else { - img = ebiten.NewImage(16, 16) + img = ebiten.NewImage(4, 4) img.Fill(color.RGBA{0, 0, 255, 255}) } return img @@ -28,6 +28,9 @@ func NewUnit(unitType component.UnitType, x float64, y float64) gohan.Entity { hitPoints := 10 e := gohan.NewEntity() + e.AddComponent(&component.Unit{ + Selected: true, + }) e.AddComponent(&component.Position{ X: x, Y: y, @@ -37,8 +40,11 @@ func NewUnit(unitType component.UnitType, x float64, y float64) gohan.Entity { } else { e.AddComponent(&component.Movable{}) } + img := unitSprite(unitType) e.AddComponent(&component.Sprite{ - Image: unitSprite(unitType), + Image: img, + Width: float64(img.Bounds().Dx()), + Height: float64(img.Bounds().Dy()), }) e.AddComponent(&component.Health{ Current: hitPoints, diff --git a/game/game.go b/game/game.go index 8e82646..9f0ac55 100644 --- a/game/game.go +++ b/game/game.go @@ -28,8 +28,10 @@ func NewGame() (*Game, error) { greenSprite.Fill(color.RGBA{0, 255, 0, 255}) gohan.AddSystem(&system.HandleInput{}) + gohan.AddSystem(&system.HandleSelection{}) gohan.AddSystem(&system.RenderEnvironment{}) gohan.AddSystem(&system.RenderUnit{}) + gohan.AddSystem(&system.RenderSelection{}) gohan.AddSystem(&system.RenderDebug{}) // Create singleton entity for systems that run one time each tick. @@ -49,7 +51,7 @@ func (g *Game) Layout(outsideWidth, outsideHeight int) (screenWidth, screenHeigh if float64(outsideWidth) != world.ScreenWidth || float64(outsideHeight) != world.ScreenHeight { world.ScreenWidth, world.ScreenHeight = float64(outsideWidth), float64(outsideHeight) - fullSize := float64(world.TileSize * world.MapSize) + fullSize := float64(world.TileSizeEnvironment * world.MapSize) minZoomWidth := float64(world.ScreenWidth) / fullSize minZoomHeight := float64(world.ScreenHeight) / fullSize world.MinCamScale = math.Ceil(math.Max(minZoomWidth, minZoomHeight)) diff --git a/go.mod b/go.mod index 7f4042e..e9262d0 100644 --- a/go.mod +++ b/go.mod @@ -5,15 +5,15 @@ 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 + github.com/hajimehoshi/ebiten/v2 v2.4.13 ) require ( - github.com/ebitengine/purego v0.1.0 // indirect + github.com/ebitengine/purego v0.1.1 // indirect github.com/go-gl/glfw/v3.3/glfw v0.0.0-20221017161538-93cebf72946b // indirect github.com/hajimehoshi/file2byteslice v1.0.0 // indirect github.com/jezek/xgb v1.1.0 // indirect - golang.org/x/exp/shiny v0.0.0-20221109205753-fc8884afc316 // indirect + golang.org/x/exp/shiny v0.0.0-20221114191408-850992195362 // indirect golang.org/x/image v0.1.0 // indirect golang.org/x/mobile v0.0.0-20221110043201-43a038452099 // indirect golang.org/x/sys v0.2.0 // indirect diff --git a/go.sum b/go.sum index 07bfdca..426c4c1 100644 --- a/go.sum +++ b/go.sum @@ -4,14 +4,14 @@ github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym 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= +github.com/ebitengine/purego v0.1.1 h1:HI8nW+LniW9Yb34k34jBs8nz+PNzsw68o7JF8jWFHHE= +github.com/ebitengine/purego v0.1.1/go.mod h1:Eh8I3yvknDYZeCuXH9kRNaPuHEwvXDCk378o9xszmHg= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20220806181222-55e207c401ad/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20221017161538-93cebf72946b h1:GgabKamyOYguHqHjSkDACcgoPIz3w0Dis/zJ1wyHHHU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20221017161538-93cebf72946b/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/hajimehoshi/bitmapfont/v2 v2.2.2/go.mod h1:Ua/x9Dkz7M9CU4zr1VHWOqGwjKdXbOTRsH7lWfb1Co0= -github.com/hajimehoshi/ebiten/v2 v2.4.12 h1:exd4SRImAKJkoRGV3nlYUeFGmM6U/rVD3vWlgnO2mUo= -github.com/hajimehoshi/ebiten/v2 v2.4.12/go.mod h1:BZcqCU4XHmScUi+lsKexocWcf4offMFwfp8dVGIB/G4= +github.com/hajimehoshi/ebiten/v2 v2.4.13 h1:ZZ5y+bFkAbUeD2WGquHF+xSbg83SIbcsxCwEVeZgHWM= +github.com/hajimehoshi/ebiten/v2 v2.4.13/go.mod h1:BZcqCU4XHmScUi+lsKexocWcf4offMFwfp8dVGIB/G4= github.com/hajimehoshi/file2byteslice v0.0.0-20210813153925-5340248a8f41/go.mod h1:CqqAHp7Dk/AqQiwuhV1yT2334qbA/tFWQW0MD2dGqUE= github.com/hajimehoshi/file2byteslice v1.0.0 h1:ljd5KTennqyJ4vG9i/5jS8MD1prof97vlH5JOdtw3WU= github.com/hajimehoshi/file2byteslice v1.0.0/go.mod h1:CqqAHp7Dk/AqQiwuhV1yT2334qbA/tFWQW0MD2dGqUE= @@ -33,8 +33,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4= -golang.org/x/exp/shiny v0.0.0-20221109205753-fc8884afc316 h1:mMxhcMO09jLo8wt0+KPABfDvlLCZabwVIhP678cNdIg= -golang.org/x/exp/shiny v0.0.0-20221109205753-fc8884afc316/go.mod h1:VjAR7z0ngyATZTELrBSkxOOHhhlnVUxDye4mcjx5h/8= +golang.org/x/exp/shiny v0.0.0-20221114191408-850992195362 h1:klJAUGTRrnTvp2+ongrNqLxrl/415DPs2iR9xn/k0ME= +golang.org/x/exp/shiny v0.0.0-20221114191408-850992195362/go.mod h1:VjAR7z0ngyATZTELrBSkxOOHhhlnVUxDye4mcjx5h/8= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.1.0 h1:r8Oj8ZA2Xy12/b5KZYj3tuv7NG/fBz3TwQVvpJ9l8Rk= diff --git a/system/handleinput.go b/system/handleinput.go index cf575e7..7d6a1fe 100644 --- a/system/handleinput.go +++ b/system/handleinput.go @@ -78,13 +78,13 @@ func (r *HandleInput) Update(e gohan.Entity) error { halfHeight := (world.ScreenHeight/2)/world.CamScale - padding if world.CamX < halfWidth { world.CamX = halfWidth - } else if world.CamX > (world.TileSize*world.MapSize)-halfWidth { - world.CamX = (world.TileSize * world.MapSize) - halfWidth + } else if world.CamX > (world.TileSizeEnvironment*world.MapSize)-halfWidth { + world.CamX = (world.TileSizeEnvironment * world.MapSize) - halfWidth } if world.CamY < halfHeight { world.CamY = halfHeight - } else if world.CamY > (world.TileSize*world.MapSize)-halfHeight { - world.CamY = (world.TileSize * world.MapSize) - halfHeight + } else if world.CamY > (world.TileSizeEnvironment*world.MapSize)-halfHeight { + world.CamY = (world.TileSizeEnvironment * world.MapSize) - halfHeight } if inpututil.IsKeyJustPressed(ebiten.KeyV) && ebiten.IsKeyPressed(ebiten.KeyControl) { @@ -95,7 +95,10 @@ func (r *HandleInput) Update(e gohan.Entity) error { } if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonLeft) { - tx, ty := world.ScreenCoordinatesToPath(world.CursorX, world.CursorY) + tx, ty := world.ScreenCoordinatesToLevel(world.CursorX, world.CursorY) + + world.SelectX, world.SelectY = tx, ty + from := world.Map[world.PathTileIndex(1, 1)] to := world.Map[world.PathTileIndex(tx, ty)] @@ -105,6 +108,8 @@ func (r *HandleInput) Update(e gohan.Entity) error { t := p.(world.Tile) world.MapPath = append(world.MapPath, [2]float64{t.X, t.Y}) } + + // TODO Select unit, if shift is held, add to selection } return nil diff --git a/system/handleselection.go b/system/handleselection.go new file mode 100644 index 0000000..51cddeb --- /dev/null +++ b/system/handleselection.go @@ -0,0 +1,31 @@ +package system + +import ( + "log" + + "code.rocketnine.space/tslocum/commandeuropa/component" + "code.rocketnine.space/tslocum/commandeuropa/world" + "code.rocketnine.space/tslocum/gohan" + "github.com/hajimehoshi/ebiten/v2" +) + +type HandleSelection struct { + Position *component.Position + Unit *component.Unit +} + +func (s *HandleSelection) Update(e gohan.Entity) error { + return gohan.ErrUnregister +} + +func (s *HandleSelection) Draw(e gohan.Entity, screen *ebiten.Image) error { + if world.SelectX == -1 && world.SelectY == -1 { + return nil + } + + if s.Position.X == world.SelectX && s.Position.Y == world.SelectY { + log.Println("FOUND UNIT") + } + log.Println(s.Position.X, s.Position.Y, world.SelectX, world.SelectY) + return nil +} diff --git a/system/renderdebug.go b/system/renderdebug.go index b45bda9..c252995 100644 --- a/system/renderdebug.go +++ b/system/renderdebug.go @@ -50,7 +50,7 @@ func (s *RenderDebug) Draw(e gohan.Entity, screen *ebiten.Image) error { fillColor = color.RGBA{0, 255, 0, 255} } - drawX, drawY := world.PathCoordinatesToScreen(px, py) + drawX, drawY := world.LevelCoordinatesToScreen(px, py) // Skip drawing off-screen tiles. if world.PixelCoordinatesOffScreen(drawX, drawY) { @@ -66,7 +66,7 @@ func (s *RenderDebug) Draw(e gohan.Entity, screen *ebiten.Image) error { if len(world.MapPath) > 0 { for _, xy := range world.MapPath { x, y := xy[0], xy[1] - drawX, drawY := world.PathCoordinatesToScreen(x, y) + drawX, drawY := world.LevelCoordinatesToScreen(x, y) // Skip drawing off-screen tiles. if world.PixelCoordinatesOffScreen(drawX, drawY) { diff --git a/system/renderenvironment.go b/system/renderenvironment.go index e2f0d63..1b9fdca 100644 --- a/system/renderenvironment.go +++ b/system/renderenvironment.go @@ -30,6 +30,9 @@ func (r *RenderEnvironment) Draw(e gohan.Entity, screen *ebiten.Image) error { r.Initialize() } + // Clear selection after HandleSelection runs. + world.SelectX, world.SelectY = -1, -1 + for x := 0.0; x < world.MapSize; x++ { for y := 0.0; y < world.MapSize; y++ { i := world.TileIndex(x, y) @@ -39,14 +42,14 @@ func (r *RenderEnvironment) Draw(e gohan.Entity, screen *ebiten.Image) error { continue } - drawX, drawY := world.LevelCoordinatesToScreen(x, y) + drawX, drawY := world.LevelCoordinatesToScreen(x*4, y*4) // Skip drawing off-screen tiles. if world.PixelCoordinatesOffScreen(drawX, drawY) { continue } - renderSprite(screen, t.Sprite, x, y, r.op) + renderSprite(screen, t.Sprite, x*4, y*4, r.op) } } @@ -79,7 +82,7 @@ func renderSprite(target *ebiten.Image, img *ebiten.Image, x float64, y float64, op.GeoM.Reset() // Move to current position. - op.GeoM.Translate(x*world.TileSize, y*world.TileSize) + op.GeoM.Translate(x*4, y*4) // Translate camera position. op.GeoM.Translate(float64(-world.CamX), float64(-world.CamY)) // Zoom. diff --git a/system/renderselection.go b/system/renderselection.go new file mode 100644 index 0000000..cc6318a --- /dev/null +++ b/system/renderselection.go @@ -0,0 +1,60 @@ +package system + +import ( + "image/color" + + "code.rocketnine.space/tslocum/commandeuropa/component" + "code.rocketnine.space/tslocum/commandeuropa/world" + "code.rocketnine.space/tslocum/gohan" + "github.com/hajimehoshi/ebiten/v2" +) + +type RenderSelection struct { + Unit *component.Unit + Position *component.Position + Sprite *component.Sprite + + op *ebiten.DrawImageOptions + initialized bool +} + +func (r *RenderSelection) Initialize() { + r.op = &ebiten.DrawImageOptions{} +} + +func (r *RenderSelection) Update(e gohan.Entity) error { + return gohan.ErrUnregister +} + +func (r *RenderSelection) Draw(e gohan.Entity, screen *ebiten.Image) error { + if !r.initialized { + r.Initialize() + } + + if !r.Unit.Selected { + return nil + } + + dx, dy := world.LevelCoordinatesToScreen(r.Position.X, r.Position.Y) + drawHighlight(screen, dx, dy, r.Sprite.Width, r.Sprite.Height) + return nil +} + +func drawHighlight(target *ebiten.Image, x, y, w, h float64) { + right, bottom := x+w*world.CamScale, y+h*world.CamScale + + outerColor := color.RGBA{0, 0, 0, 255} + outerSize := 3 * world.CamScale + target.SubImage(floatRect(x, y, right, y+outerSize)).(*ebiten.Image).Fill(outerColor) + target.SubImage(floatRect(x, bottom-outerSize, right, bottom)).(*ebiten.Image).Fill(outerColor) + target.SubImage(floatRect(x, y, x+outerSize, bottom)).(*ebiten.Image).Fill(outerColor) + target.SubImage(floatRect(right-outerSize, y, right, bottom)).(*ebiten.Image).Fill(outerColor) + + innerColor := color.RGBA{255, 255, 255, 255} + innerPadding := 1 * world.CamScale + innerSize := 1 * world.CamScale + target.SubImage(floatRect(x+innerPadding, y+innerPadding, right-innerPadding, y+innerPadding+innerSize)).(*ebiten.Image).Fill(innerColor) + target.SubImage(floatRect(x+innerPadding, bottom-innerPadding-innerSize, right-innerPadding, bottom-innerPadding)).(*ebiten.Image).Fill(innerColor) + target.SubImage(floatRect(x+innerPadding, y+innerPadding, x+innerPadding+innerSize, bottom-innerPadding)).(*ebiten.Image).Fill(innerColor) + target.SubImage(floatRect(right-innerPadding-innerSize, y+innerPadding, right-innerPadding, bottom-innerPadding)).(*ebiten.Image).Fill(innerColor) +} diff --git a/world/map.go b/world/map.go index d1dac86..c55f48f 100644 --- a/world/map.go +++ b/world/map.go @@ -11,31 +11,31 @@ 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) +func environmentTile(r, g, b uint8) *ebiten.Image { + img := ebiten.NewImage(TileSizeEnvironment, TileSizeEnvironment) img.Fill(color.RGBA{r, g, b, 255}) return img } var ( - lightBlueTile1 = colorTile(174, 208, 218) - lightBlueTile2 = colorTile(203, 234, 229) - lightBlueTile3 = colorTile(154, 202, 206) + lightBlueTile1 = environmentTile(174, 208, 218) + lightBlueTile2 = environmentTile(203, 234, 229) + lightBlueTile3 = environmentTile(154, 202, 206) lightBlueTiles = []*ebiten.Image{lightBlueTile1, lightBlueTile1, lightBlueTile2, lightBlueTile2, lightBlueTile2, lightBlueTile2, lightBlueTile2, lightBlueTile2, lightBlueTile2, lightBlueTile3} - mediumBlueTile1 = colorTile(0, 38, 117) - mediumBlueTile2 = colorTile(9, 69, 131) - mediumBlueTile3 = colorTile(25, 71, 148) + mediumBlueTile1 = environmentTile(0, 38, 117) + mediumBlueTile2 = environmentTile(9, 69, 131) + mediumBlueTile3 = environmentTile(25, 71, 148) mediumBlueTiles = []*ebiten.Image{mediumBlueTile1, mediumBlueTile2, mediumBlueTile3} - darkBlueTile1 = colorTile(0, 9, 88) - darkBlueTile2 = colorTile(2, 3, 67) - darkBlueTile3 = colorTile(0, 0, 72) + darkBlueTile1 = environmentTile(0, 9, 88) + darkBlueTile2 = environmentTile(2, 3, 67) + darkBlueTile3 = environmentTile(0, 0, 72) darkBlueTiles = []*ebiten.Image{darkBlueTile1, darkBlueTile1, darkBlueTile2, darkBlueTile3} - redTile1 = colorTile(109, 5, 0) - redTile2 = colorTile(135, 13, 8) - redTile3 = colorTile(118, 0, 6) + redTile1 = environmentTile(109, 5, 0) + redTile2 = environmentTile(135, 13, 8) + redTile3 = environmentTile(118, 0, 6) redTiles = []*ebiten.Image{redTile1, redTile1, redTile2, redTile3} ) diff --git a/world/tile.go b/world/tile.go index cadac85..0624cd6 100644 --- a/world/tile.go +++ b/world/tile.go @@ -5,7 +5,8 @@ import ( "github.com/hajimehoshi/ebiten/v2" ) -const TileSize = 16 +const TileSize = 4 +const TileSizeEnvironment = 16 type Tile struct { X float64 diff --git a/world/world.go b/world/world.go index 9e5178e..966bb27 100644 --- a/world/world.go +++ b/world/world.go @@ -2,6 +2,7 @@ package world import ( "image/color" + "math" "github.com/hajimehoshi/ebiten/v2" ) @@ -25,6 +26,9 @@ var ( MapPath [][2]float64 + SelectX float64 = -1 + SelectY float64 = -1 + Debug int StartMuted bool @@ -41,25 +45,17 @@ func init() { } func LevelCoordinatesToScreen(x, y float64) (float64, float64) { - return (x*TileSize-CamX)*CamScale + ScreenWidth/2, (y*TileSize-CamY)*CamScale + ScreenHeight/2 + return (x*tileDivisions-CamX)*CamScale + ScreenWidth/2, (y*tileDivisions-CamY)*CamScale + ScreenHeight/2 } -func PathCoordinatesToScreen(x, y float64) (float64, float64) { - return (x*tileDivisions-CamX)*CamScale + ScreenWidth/2, (y*tileDivisions-CamY)*CamScale + ScreenHeight/2 +func ScreenCoordinatesToLevel(x, y float64) (float64, float64) { + return math.Floor((((x - ScreenWidth/2) / CamScale) + CamX) / tileDivisions), math.Floor((((y - ScreenHeight/2) / CamScale) + CamY) / tileDivisions) } func PixelCoordinatesOffScreen(x, y float64) bool { - padding := TileSize * CamScale - left, right := x, x+TileSize*CamScale - top, bottom := y, y+TileSize*CamScale + padding := TileSizeEnvironment * CamScale + left, right := x, x+TileSizeEnvironment*CamScale + top, bottom := y, y+TileSizeEnvironment*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 float64) (float64, float64) { - return (((x - ScreenWidth/2) / CamScale) + CamX) / TileSize, (((y - ScreenHeight/2) / CamScale) + CamY) / TileSize -} - -func ScreenCoordinatesToPath(x, y float64) (float64, float64) { - return (((x - ScreenWidth/2) / CamScale) + CamX) / tileDivisions, (((y - ScreenHeight/2) / CamScale) + CamY) / tileDivisions -}