Browse Source

Unify coordinate systems

main
Trevor Slocum 3 weeks ago
parent
commit
39a46bbcea
  1. 4
      component/sprite.go
  2. 3
      component/unit.go
  3. 10
      entity/unit.go
  4. 4
      game/game.go
  5. 6
      go.mod
  6. 12
      go.sum
  7. 15
      system/handleinput.go
  8. 31
      system/handleselection.go
  9. 4
      system/renderdebug.go
  10. 9
      system/renderenvironment.go
  11. 60
      system/renderselection.go
  12. 28
      world/map.go
  13. 3
      world/tile.go
  14. 24
      world/world.go

4
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
}

3
component/unit.go

@ -1,7 +1,8 @@
package component
type Unit struct {
Type UnitType
Type UnitType
Selected bool
}
type UnitType int

10
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,

4
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))

6
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

12
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=

15
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

31
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
}

4
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) {

9
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.

60
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)
}

28
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}
)

3
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

24
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
}

Loading…
Cancel
Save