aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMartin Kagamino Lehoux <martin@lehoux.net>2025-09-25 23:00:15 +0200
committerMartin Kagamino Lehoux <martin@lehoux.net>2025-09-25 23:00:15 +0200
commit7e94ae0184b94f247c16a775b4d6b0172e8b3027 (patch)
tree583abab8cdcdf1eecec185bb6060297768c95f94
parentbd750fa2605eecd14ab821f630a7c1b51c48c982 (diff)
Unify selection code and unselect
-rw-r--r--README.md4
-rw-r--r--main.go23
-rw-r--r--selection/selection.go113
3 files changed, 73 insertions, 67 deletions
diff --git a/README.md b/README.md
index 675895a..dcd553b 100644
--- a/README.md
+++ b/README.md
@@ -10,11 +10,11 @@
- keyboard config
- bigger map
- move the map view
-- Esc when selection => unselect
- ecs array: on delete, replace by last and update id->index map
- collision based move w smaller accuracy
- perf: look for nil comparison -> more data oriented
-- perf: keep a short array of selected
+- perf: keep a short array of selected and use it instead of `Value.IsSelected`
+- perf: component columns and systems
## Bugs
diff --git a/main.go b/main.go
index eb6bcaa..ce69fee 100644
--- a/main.go
+++ b/main.go
@@ -67,7 +67,7 @@ const (
func (g *Game) updateSelecting(cursor physics.Point, moveMap physics.MoveMap) {
if inpututil.IsKeyJustReleased(ebiten.KeyEscape) {
- g.Selection.Clear()
+ g.Selection.Unselect()
}
if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonLeft) {
g.Selection.Start(cursor)
@@ -84,18 +84,21 @@ func (g *Game) updateSelecting(cursor physics.Point, moveMap physics.MoveMap) {
}
if inpututil.IsMouseButtonJustReleased(ebiten.MouseButtonLeft) {
if g.Selection.IsActive(cursor) {
- selector := g.Selection.Select(cursor)
- for _, e := range g.Entities {
- selector.Preselect(e.Position, e.RelBounds, &e.Selection)
+ // TODO: simplify with component columns
+ targets := make([]selection.SelectTarget, len(g.Entities))
+ for i, e := range g.Entities {
+ targets[i] = selection.SelectTarget{
+ Selection: &e.Selection,
+ Position: e.Position,
+ Bounds: e.RelBounds,
+ }
}
- selector.Select()
- } else {
- selector := selection.NewSingle(cursor)
- for _, e := range g.Entities {
- selector.Select(e.Position, e.RelBounds, &e.Selection)
+ if g.Selection.IsArea(cursor) {
+ g.Selection.SelectMultiple(cursor, targets)
+ } else {
+ g.Selection.SelectSingle(cursor, targets)
}
}
- g.Selection.Clear()
}
if inpututil.IsKeyJustReleased(KeySpawnRequest) {
for _, e := range g.Entities {
diff --git a/selection/selection.go b/selection/selection.go
index d4b8cd9..482199c 100644
--- a/selection/selection.go
+++ b/selection/selection.go
@@ -20,13 +20,25 @@ const (
Building
)
+type Selection struct {
+ IsSelected bool
+ Halo *ebiten.Image
+ Priority Priority
+}
+
type GlobalSelection struct {
- isActive bool
- start physics.Point
+ isActive bool
+ start physics.Point
+ buffer []*ecs.Component[Selection]
+ selection []*ecs.Component[Selection]
}
func (s GlobalSelection) IsActive(cursor physics.Point) bool {
- return s.isActive && physics.Distance(s.start, cursor) > 10
+ return s.isActive
+}
+
+func (s GlobalSelection) IsArea(cursor physics.Point) bool {
+ return physics.Distance(s.start, cursor) > 10
}
func (s *GlobalSelection) Clear() {
@@ -39,79 +51,70 @@ func (s *GlobalSelection) Start(point physics.Point) {
s.start = point
}
-func (s *GlobalSelection) Select(cursor physics.Point) Multiple {
- s.isActive = false
- return NewMultiple(s.start, cursor)
-}
-
-func (s *GlobalSelection) Draw(dst *ebiten.Image, cursor physics.Point) {
- vector.StrokeRect(dst, float32(s.start.X), float32(s.start.Y), float32(cursor.X-s.start.X), float32(cursor.Y-s.start.Y), 10.0, color.RGBA{256 * 3 / 16, 256 * 3 / 16, 256 * 3 / 16, 256 / 4}, true)
+type SelectTarget struct {
+ Selection *ecs.Component[Selection]
+ Position ecs.Component[physics.Point]
+ Bounds ecs.Component[physics.Rectangle]
}
-type Selection struct {
- IsSelected bool
- Halo *ebiten.Image
- Priority Priority
+func (s GlobalSelection) Bounds(cursor physics.Point) physics.Rectangle {
+ return physics.Rectangle{
+ Min: s.start,
+ Max: cursor,
+ }.Canon()
}
-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], relBounds ecs.Component[physics.Rectangle], selection *ecs.Component[Selection]) {
- if !selection.IsEnabled || !position.IsEnabled || !relBounds.IsEnabled {
- return
- }
- selection.Value.IsSelected = false
- if m.Bounds.Overlaps(physics.Translate(relBounds.Value, position.Value)) {
- m.Preselected = append(m.Preselected, selection)
+func (s *GlobalSelection) SelectMultiple(cursor physics.Point, components []SelectTarget) {
+ s.buffer = make([]*ecs.Component[Selection], 0)
+ s.selection = make([]*ecs.Component[Selection], 0)
+ for _, target := range components {
+ if target.Selection.IsEnabled && target.Position.IsEnabled && target.Bounds.IsEnabled {
+ target.Selection.Value.IsSelected = false
+ if s.Bounds(cursor).Overlaps(physics.Translate(target.Bounds.Value, target.Position.Value)) {
+ s.buffer = append(s.buffer, target.Selection)
+ }
+ }
}
-}
-
-func (m *Multiple) Select() {
- if len(m.Preselected) == 0 {
+ if len(s.buffer) == 0 {
return
}
highestPriority := Priority(math.MaxInt)
- for _, selection := range m.Preselected {
+ for _, selection := range s.buffer {
if selection.Value.Priority < highestPriority {
highestPriority = selection.Value.Priority
}
}
- for _, selection := range m.Preselected {
+ for _, selection := range s.buffer {
if selection.Value.Priority == highestPriority {
selection.Value.IsSelected = true
+ s.selection = append(s.selection, selection)
}
}
+ s.Clear()
}
-type Single struct {
- Cursor physics.Point
- HasSelected bool
+func (s *GlobalSelection) SelectSingle(cursor physics.Point, components []SelectTarget) {
+ s.selection = []*ecs.Component[Selection]{}
+ for _, target := range components {
+ if target.Selection.IsEnabled && target.Position.IsEnabled && target.Bounds.IsEnabled {
+ target.Selection.Value.IsSelected = false
+ if cursor.In(physics.Translate(target.Bounds.Value, target.Position.Value)) {
+ if len(s.selection) == 0 || target.Selection.Value.Priority > s.selection[0].Value.Priority {
+ s.selection = []*ecs.Component[Selection]{target.Selection}
+ }
+ }
+ }
+ }
+ s.Clear()
}
-func NewSingle(cursor physics.Point) Single {
- return Single{
- Cursor: cursor,
- HasSelected: false,
+func (s *GlobalSelection) Unselect() {
+ for _, selection := range s.selection {
+ selection.Value.IsSelected = false
}
+ s.selection = []*ecs.Component[Selection]{}
}
-func (s *Single) Select(position ecs.Component[physics.Point], relBounds ecs.Component[physics.Rectangle], selection *ecs.Component[Selection]) {
- if !selection.IsEnabled || !position.IsEnabled || !relBounds.IsEnabled {
- return
- }
- selection.Value.IsSelected = false
- if s.HasSelected || !s.Cursor.In(physics.Translate(relBounds.Value, position.Value)) {
- return
- }
- selection.Value.IsSelected = true
+func (s *GlobalSelection) Draw(dst *ebiten.Image, cursor physics.Point) {
+ vector.StrokeRect(dst, float32(s.start.X), float32(s.start.Y), float32(cursor.X-s.start.X), float32(cursor.Y-s.start.Y), 10.0, color.RGBA{256 * 3 / 16, 256 * 3 / 16, 256 * 3 / 16, 256 / 4}, true)
}