aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md2
-rw-r--r--draw.go3
-rw-r--r--entity.go12
-rw-r--r--main.go24
-rw-r--r--selection.go40
-rw-r--r--selection/selection.go91
6 files changed, 111 insertions, 61 deletions
diff --git a/README.md b/README.md
index 121bf0d..3fa232a 100644
--- a/README.md
+++ b/README.md
@@ -5,12 +5,12 @@
## TODO
- time must come from engine, not now
-- selection priority: selecting units + building -> select only units
- see the current action (debug?)
- keyboard config
- bigger map
- move the map view
- ecs array: on delete, replace by last and update id->index map
+- `ecs.Component[*ebiten.Image]` drop the pointer
- perf: look for nil comparison -> more data oriented
- perf: keep a short array of selected
diff --git a/draw.go b/draw.go
index ba4f651..528955e 100644
--- a/draw.go
+++ b/draw.go
@@ -2,6 +2,7 @@ package main
import (
"age_of_empires/physics"
+ "age_of_empires/selection"
"fmt"
"image/color"
@@ -19,7 +20,7 @@ func (e *Entity) Draw(g *Game, screen *ebiten.Image) {
screen.DrawImage(e.Image.Value, op)
if e.Selection.IsEnabled && e.Selection.Value.IsSelected {
opt := &ebiten.DrawImageOptions{}
- opt.GeoM.Translate(float64(e.Position.Value.X-SELECTION_HALO_WIDTH/2), float64(e.Position.Value.Y-SELECTION_HALO_WIDTH/2))
+ opt.GeoM.Translate(float64(e.Position.Value.X-selection.HALO_WIDTH/2), float64(e.Position.Value.Y-selection.HALO_WIDTH/2))
screen.DrawImage(e.Selection.Value.Halo, opt)
if e.ResourceSource.IsEnabled {
resourceText := fmt.Sprintf("%d", e.ResourceSource.Value.Remaining)
diff --git a/entity.go b/entity.go
index cf3f4ac..2b120f5 100644
--- a/entity.go
+++ b/entity.go
@@ -3,6 +3,7 @@ package main
import (
"age_of_empires/ecs"
"age_of_empires/physics"
+ "age_of_empires/selection"
"image/color"
"github.com/hajimehoshi/ebiten/v2"
@@ -11,7 +12,7 @@ import (
type Entity struct {
Position ecs.Component[physics.Point]
Image ecs.Component[*ebiten.Image]
- Selection ecs.Component[Selection]
+ Selection ecs.Component[selection.Selection]
Move ecs.Component[physics.Move]
Order ecs.Component[Order]
ResourceGatherer ecs.Component[ResourceGatherer]
@@ -49,16 +50,17 @@ func (b EntityBuilder) WithImage(image *ebiten.Image) EntityBuilder {
var red = color.RGBA{0xff, 0x00, 0x00, 0xff}
-func (b EntityBuilder) WithSelection(haloKind string) EntityBuilder {
- b.entity.Selection = ecs.C(Selection{
+func (b EntityBuilder) WithSelection(haloKind string, priority selection.Priority) EntityBuilder {
+ b.entity.Selection = ecs.C(selection.Selection{
IsSelected: false,
Halo: nil,
+ Priority: priority,
})
switch haloKind {
case "round":
- b.entity.Selection.Value.Halo = NewStrokeCircleImage(110, SELECTION_HALO_WIDTH, red)
+ b.entity.Selection.Value.Halo = NewStrokeCircleImage(110, selection.HALO_WIDTH, red)
case "square":
- b.entity.Selection.Value.Halo = NewStrokeRectangleImage(physics.Point{X: 110, Y: 110}, SELECTION_HALO_WIDTH, red)
+ b.entity.Selection.Value.Halo = NewStrokeRectangleImage(physics.Point{X: 110, Y: 110}, selection.HALO_WIDTH, red)
}
return b
}
diff --git a/main.go b/main.go
index d119c43..3d014bf 100644
--- a/main.go
+++ b/main.go
@@ -3,6 +3,7 @@ package main
import (
"age_of_empires/ecs"
"age_of_empires/physics"
+ "age_of_empires/selection"
"bytes"
"image"
"image/color"
@@ -28,15 +29,10 @@ const (
var soilColor = color.RGBA{0x60, 0x40, 0x20, 0xff}
-type GlobalSelection struct {
- IsActive bool
- Start physics.Point
-}
-
type Game struct {
Entities []*Entity
CurrentAction Action
- Selection GlobalSelection
+ Selection selection.GlobalSelection
ResourceAmount int
FaceSource *text.GoTextFaceSource
UnitBuilder EntityBuilder
@@ -80,15 +76,15 @@ func (g *Game) updateSelecting(cursor physics.Point, moveMap physics.MoveMap) {
}
if inpututil.IsMouseButtonJustReleased(ebiten.MouseButtonLeft) {
if g.Selection.IsActive && physics.Distance(g.Selection.Start, cursor) > 10 {
+ selector := selection.NewMultiple(g.Selection.Start, cursor)
for _, e := range g.Entities {
- e.SelectMultiple(cursor, g.Selection)
+ selector.Preselect(e.Position, e.Bounds(), &e.Selection)
}
+ selector.Select()
} else {
- canBeSelected := true
+ selector := selection.NewSingle(cursor)
for _, e := range g.Entities {
- if e.SelectSingle(cursor, canBeSelected) {
- canBeSelected = false
- }
+ selector.Select(e.Position, e.Bounds(), &e.Selection)
}
}
g.Selection.IsActive = false
@@ -168,11 +164,11 @@ func main() {
s, err := text.NewGoTextFaceSource(bytes.NewReader(goregular.TTF))
kcore.Expect(err, "failed to create font source")
game.FaceSource = s
- ironMine := NewEntityBuilder().WithPosition(physics.Point{X: 1000, Y: 1000}).WithImage(NewFilledRectangleImage(physics.Point{X: 100, Y: 100}, color.RGBA{0x80, 0x80, 0x80, 0xff})).WithResourceSource(1000).WithSelection("square").Build()
+ ironMine := NewEntityBuilder().WithPosition(physics.Point{X: 1000, Y: 1000}).WithImage(NewFilledRectangleImage(physics.Point{X: 100, Y: 100}, color.RGBA{0x80, 0x80, 0x80, 0xff})).WithResourceSource(1000).WithSelection("square", selection.Building).Build()
game.Entities = append(game.Entities, &ironMine)
- townCenter := NewEntityBuilder().WithPosition(physics.Point{X: 1000, Y: 2000}).WithImage(NewFilledRectangleImage(physics.Point{X: 100, Y: 100}, color.RGBA{0x0, 0x0, 0xff, 0xff})).WithResourceStorage().WithSelection("square").WithSpawn(NewSpawn(50, 1*time.Second)).Build()
+ townCenter := NewEntityBuilder().WithPosition(physics.Point{X: 1000, Y: 2000}).WithImage(NewFilledRectangleImage(physics.Point{X: 100, Y: 100}, color.RGBA{0x0, 0x0, 0xff, 0xff})).WithResourceStorage().WithSelection("square", selection.Building).WithSpawn(NewSpawn(50, 1*time.Second)).Build()
game.Entities = append(game.Entities, &townCenter)
- game.UnitBuilder = NewEntityBuilder().WithImage(NewFilledCircleImage(100, color.White)).WithSelection("round").WithMove().WithOrder().WithResourceGatherer(15)
+ game.UnitBuilder = NewEntityBuilder().WithImage(NewFilledCircleImage(100, color.White)).WithSelection("round", selection.Unit).WithMove().WithOrder().WithResourceGatherer(15)
for i := 0; i < 0; i++ {
spawnPosition, _ := physics.Closest(townCenter.Position.Value, physics.AdjacentPoints(townCenter.Position.Value), game.getMoveMap())
person := game.UnitBuilder.Build()
diff --git a/selection.go b/selection.go
deleted file mode 100644
index 496d7f6..0000000
--- a/selection.go
+++ /dev/null
@@ -1,40 +0,0 @@
-package main
-
-import (
- "age_of_empires/physics"
-
- "github.com/hajimehoshi/ebiten/v2"
- "golang.org/x/exp/slog"
-)
-
-const SELECTION_HALO_WIDTH = 10
-
-type Selection struct {
- IsSelected bool
- Halo *ebiten.Image
-}
-
-func (e *Entity) SelectMultiple(cursor physics.Point, selection GlobalSelection) {
- if !e.Selection.IsEnabled {
- return
- }
- e.Selection.Value.IsSelected = false
- selectionBounds := physics.Rectangle{Min: selection.Start, Max: cursor}.Canon()
- if selectionBounds.Overlaps(e.Bounds()) {
- e.Selection.Value.IsSelected = true
- slog.Info("entity selected", slog.String("position", e.Position.Value.String()))
- }
-}
-
-func (e *Entity) SelectSingle(cursor physics.Point, canBeSelected bool) bool {
- if !e.Selection.IsEnabled {
- return false
- }
- e.Selection.Value.IsSelected = false
- if !canBeSelected || !cursor.In(e.Bounds()) {
- return false
- }
- e.Selection.Value.IsSelected = true
- slog.Info("entity selected", slog.String("position", e.Position.Value.String()))
- return true
-}
diff --git a/selection/selection.go b/selection/selection.go
new file mode 100644
index 0000000..2755110
--- /dev/null
+++ b/selection/selection.go
@@ -0,0 +1,91 @@
+package selection
+
+import (
+ "age_of_empires/ecs"
+ "age_of_empires/physics"
+ "math"
+
+ "github.com/hajimehoshi/ebiten/v2"
+)
+
+const HALO_WIDTH = 10
+
+type Priority int
+
+const (
+ Unit Priority = iota
+ Building
+)
+
+type GlobalSelection struct {
+ IsActive bool
+ Start physics.Point
+}
+
+type Selection struct {
+ IsSelected bool
+ Halo *ebiten.Image
+ Priority Priority
+}
+
+type Multiple struct {
+ Bounds physics.Rectangle
+ Preselected []*ecs.Component[Selection]
+}
+
+func NewMultiple(start physics.Point, end physics.Point) Multiple {
+ return Multiple{
+ Bounds: physics.Rectangle{Min: start, Max: end}.Canon(),
+ Preselected: []*ecs.Component[Selection]{},
+ }
+}
+
+func (m *Multiple) Preselect(position ecs.Component[physics.Point], bounds physics.Rectangle, selection *ecs.Component[Selection]) {
+ if !selection.IsEnabled {
+ return
+ }
+ selection.Value.IsSelected = false
+ if m.Bounds.Overlaps(bounds) {
+ m.Preselected = append(m.Preselected, selection)
+ }
+}
+
+func (m *Multiple) Select() {
+ if len(m.Preselected) == 0 {
+ return
+ }
+ highestPriority := Priority(math.MaxInt)
+ for _, selection := range m.Preselected {
+ if selection.Value.Priority < highestPriority {
+ highestPriority = selection.Value.Priority
+ }
+ }
+ for _, selection := range m.Preselected {
+ if selection.Value.Priority == highestPriority {
+ selection.Value.IsSelected = true
+ }
+ }
+}
+
+type Single struct {
+ Cursor physics.Point
+ HasSelected bool
+}
+
+func NewSingle(cursor physics.Point) Single {
+ return Single{
+ Cursor: cursor,
+ HasSelected: false,
+ }
+}
+
+func (s *Single) Select(position ecs.Component[physics.Point], bounds physics.Rectangle, selection *ecs.Component[Selection]) {
+ if !selection.IsEnabled {
+ return
+ }
+ selection.Value.IsSelected = false
+ if s.HasSelected || !s.Cursor.In(bounds) {
+ return
+ }
+ selection.Value.IsSelected = true
+}