Browse Source

select by click and area

Bastien Sevajol 3 years ago
parent
commit
45412649f5
1 changed files with 241 additions and 26 deletions
  1. 241 26
      src/main.rs

+ 241 - 26
src/main.rs View File

@@ -1,11 +1,13 @@
1 1
 use ggez;
2
-use ggez::event::KeyCode;
2
+use ggez::event::{KeyCode, MouseButton};
3 3
 use ggez::graphics;
4
-use ggez::graphics::{DrawMode, MeshBuilder};
4
+use ggez::graphics::{Color, DrawMode, FillOptions, MeshBuilder, StrokeOptions};
5 5
 use ggez::timer::check_update_time;
6 6
 use ggez::{event, input};
7 7
 use ggez::{Context, GameResult};
8 8
 use glam::Vec2;
9
+use std::cmp;
10
+use std::collections::HashMap;
9 11
 use std::env;
10 12
 use std::path;
11 13
 
@@ -18,10 +20,26 @@ const PHYSICS_EACH: u32 = 10; // execute physics code each 10 frames
18 20
 const ANIMATE_EACH: u32 = 60; // execute animate code each 30 frames
19 21
 const SPRITE_EACH: u32 = 10; // change sprite animation tile 30 frames
20 22
 const MAX_FRAME_I: u32 = 4294967295; // max of frame_i used to calculate ticks
21
-const DISPLAY_OFFSET_BY: f32 = 3.0;
22
-const DISPLAY_OFFSET_BY_SPEED: f32 = 10.0;
23
-const SPRITE_SHEET_WIDTH: f32 = 800.0;
24
-const SPRITE_SHEET_HEIGHT: f32 = 600.0;
23
+const DISPLAY_OFFSET_BY: f32 = 3.0; // pixel offset by tick when player move screen display
24
+const DISPLAY_OFFSET_BY_SPEED: f32 = 10.0; // pixel offset by tick when player move screen display with speed
25
+const SPRITE_SHEET_WIDTH: f32 = 800.0; // Width of sprite sheet
26
+const SPRITE_SHEET_HEIGHT: f32 = 600.0; // Height of sprite sheet
27
+const GRID_TILE_WIDTH: f32 = 5.0; // Width of one grid tile
28
+const GRID_TILE_HEIGHT: f32 = 5.0; // Height of one grid tile
29
+const DEFAULT_SELECTED_SQUARE_SIDE: f32 = 14.0;
30
+const DEFAULT_SELECTED_SQUARE_SIDE_HALF: f32 = DEFAULT_SELECTED_SQUARE_SIDE / 2.0;
31
+
32
+#[derive(Eq, PartialEq, Hash)]
33
+pub struct GridPosition {
34
+    x: i32,
35
+    y: i32,
36
+}
37
+
38
+impl GridPosition {
39
+    pub fn new(x: i32, y: i32) -> Self {
40
+        Self { x, y }
41
+    }
42
+}
25 43
 
26 44
 fn vec_from_angle(angle: f32) -> Vector2 {
27 45
     let vx = angle.sin();
@@ -29,6 +47,13 @@ fn vec_from_angle(angle: f32) -> Vector2 {
29 47
     Vector2::new(vx, vy)
30 48
 }
31 49
 
50
+fn grid_position_from_position(position: &Point2) -> GridPosition {
51
+    GridPosition::new(
52
+        (position.x / GRID_TILE_WIDTH) as i32,
53
+        (position.y / GRID_TILE_HEIGHT) as i32,
54
+    )
55
+}
56
+
32 57
 struct SpriteInfo {
33 58
     relative_start_y: f32,
34 59
     relative_tile_width: f32,
@@ -36,6 +61,8 @@ struct SpriteInfo {
36 61
     tile_count: u16,
37 62
     tile_width: f32,
38 63
     tile_height: f32,
64
+    _half_tile_width: f32,
65
+    _half_tile_height: f32,
39 66
 }
40 67
 
41 68
 impl SpriteInfo {
@@ -54,6 +81,8 @@ impl SpriteInfo {
54 81
             tile_count,
55 82
             tile_width,
56 83
             tile_height,
84
+            _half_tile_width: tile_width / 2.0,
85
+            _half_tile_height: tile_height / 2.0,
57 86
         }
58 87
     }
59 88
 }
@@ -74,22 +103,18 @@ struct ItemState {
74 103
     current_behavior: ItemBehavior,
75 104
 }
76 105
 
106
+enum SceneItemType {
107
+    Soldier,
108
+}
109
+
77 110
 impl ItemState {
78 111
     pub fn new(current_behavior: ItemBehavior) -> Self {
79 112
         Self { current_behavior }
80 113
     }
81
-
82
-    pub fn sprite_type(&self) -> SpriteType {
83
-        // Here some logical about state and current behavior to determine sprite type
84
-        match self.current_behavior {
85
-            ItemBehavior::Crawling => SpriteType::CrawlingSoldier,
86
-            ItemBehavior::Walking(_) => SpriteType::WalkingSoldier,
87
-            ItemBehavior::Standing(_) => SpriteType::StandingSoldier,
88
-        }
89
-    }
90 114
 }
91 115
 
92 116
 struct SceneItem {
117
+    type_: SceneItemType,
93 118
     position: Point2,
94 119
     state: ItemState,
95 120
     meta_events: Vec<MetaEvent>,
@@ -97,9 +122,9 @@ struct SceneItem {
97 122
 }
98 123
 
99 124
 impl SceneItem {
100
-    pub fn new(position: Point2, state: ItemState) -> Self {
101
-        let sprite_type = state.sprite_type();
125
+    pub fn new(type_: SceneItemType, position: Point2, state: ItemState) -> Self {
102 126
         Self {
127
+            type_,
103 128
             position,
104 129
             state,
105 130
             meta_events: vec![],
@@ -108,7 +133,7 @@ impl SceneItem {
108 133
     }
109 134
 
110 135
     pub fn sprite_info(&self) -> SpriteInfo {
111
-        SpriteInfo::from_type(&self.state.sprite_type())
136
+        SpriteInfo::from_type(&self.sprite_type())
112 137
     }
113 138
 
114 139
     pub fn tick_sprite(&mut self) {
@@ -131,23 +156,55 @@ impl SceneItem {
131 156
             .rotation(90.0f32.to_radians())
132 157
             .offset(Point2::new(0.5, 0.5))
133 158
     }
159
+
160
+    pub fn sprite_type(&self) -> SpriteType {
161
+        // Here some logical about state, nature (soldier, tank, ...) and current behavior to
162
+        // determine sprite type
163
+        match self.state.current_behavior {
164
+            ItemBehavior::Crawling => SpriteType::CrawlingSoldier,
165
+            ItemBehavior::Walking(_) => SpriteType::WalkingSoldier,
166
+            ItemBehavior::Standing(_) => SpriteType::StandingSoldier,
167
+        }
168
+    }
134 169
 }
135 170
 
171
+#[derive(Debug)]
136 172
 enum PhysicEvent {
137 173
     Explosion,
138 174
 }
139 175
 
176
+#[derive(Debug)]
140 177
 enum MetaEvent {
141 178
     FearAboutExplosion,
142 179
 }
143 180
 
181
+#[derive(Debug)]
182
+enum UserEvent {
183
+    Click(Point2),                 // Window coordinates
184
+    AreaSelection(Point2, Point2), // Window coordinates
185
+}
186
+
144 187
 struct MainState {
188
+    // time
145 189
     frame_i: u32,
190
+
191
+    // display
192
+    display_offset: Point2,
146 193
     sprite_sheet_batch: graphics::spritebatch::SpriteBatch,
147 194
     map_batch: graphics::spritebatch::SpriteBatch,
195
+
196
+    // scene items
148 197
     scene_items: Vec<SceneItem>,
198
+    scene_items_by_grid_position: HashMap<GridPosition, Vec<usize>>,
199
+
200
+    // events
149 201
     physics_events: Vec<PhysicEvent>,
150
-    display_offset: Point2,
202
+
203
+    // user interactions
204
+    left_click_down: Option<Point2>,
205
+    current_cursor_position: Point2,
206
+    user_events: Vec<UserEvent>,
207
+    selected_scene_items: Vec<usize>,
151 208
 }
152 209
 
153 210
 impl MainState {
@@ -167,21 +224,37 @@ impl MainState {
167 224
                 };
168 225
 
169 226
                 scene_items.push(SceneItem::new(
227
+                    SceneItemType::Soldier,
170 228
                     Point2::new((x as f32 * 24.0) + 100.0, (y as f32 * 24.0) + 100.0),
171 229
                     ItemState::new(current_behavior),
172 230
                 ));
173 231
             }
174 232
         }
175 233
 
176
-        let s = MainState {
234
+        let mut main_state = MainState {
177 235
             frame_i: 0,
236
+            display_offset: Point2::new(0.0, 0.0),
178 237
             sprite_sheet_batch,
179 238
             map_batch,
180 239
             scene_items,
240
+            scene_items_by_grid_position: HashMap::new(),
181 241
             physics_events: vec![],
182
-            display_offset: Point2::new(0.0, 0.0),
242
+            left_click_down: None,
243
+            current_cursor_position: Point2::new(0.0, 0.0),
244
+            user_events: vec![],
245
+            selected_scene_items: vec![],
183 246
         };
184
-        Ok(s)
247
+
248
+        for (i, scene_item) in main_state.scene_items.iter().enumerate() {
249
+            let grid_position = grid_position_from_position(&scene_item.position);
250
+            main_state
251
+                .scene_items_by_grid_position
252
+                .entry(grid_position)
253
+                .or_default()
254
+                .push(i);
255
+        }
256
+
257
+        Ok(main_state)
185 258
     }
186 259
 
187 260
     fn inputs(&mut self, ctx: &Context) {
@@ -204,6 +277,34 @@ impl MainState {
204 277
         if input::keyboard::is_key_pressed(ctx, KeyCode::Down) {
205 278
             self.display_offset.y -= display_offset_by;
206 279
         }
280
+
281
+        while let Some(user_event) = self.user_events.pop() {
282
+            match user_event {
283
+                UserEvent::Click(position) => {
284
+                    let scene_position = Point2::new(
285
+                        position.x - self.display_offset.x,
286
+                        position.y - self.display_offset.y,
287
+                    );
288
+                    self.selected_scene_items.drain(..);
289
+                    if let Some(scene_item_usize) =
290
+                        self.get_first_scene_item_for_position(&scene_position)
291
+                    {
292
+                        self.selected_scene_items.push(scene_item_usize);
293
+                    }
294
+                }
295
+                UserEvent::AreaSelection(from, to) => {
296
+                    let scene_from = Point2::new(
297
+                        from.x - self.display_offset.x,
298
+                        from.y - self.display_offset.y,
299
+                    );
300
+                    let scene_to =
301
+                        Point2::new(to.x - self.display_offset.x, to.y - self.display_offset.y);
302
+                    self.selected_scene_items.drain(..);
303
+                    self.selected_scene_items
304
+                        .extend(self.get_scene_items_for_area(&scene_from, &scene_to));
305
+                }
306
+            }
307
+        }
207 308
     }
208 309
 
209 310
     // TODO: manage errors
@@ -280,6 +381,38 @@ impl MainState {
280 381
             position.y + self.display_offset.y,
281 382
         )
282 383
     }
384
+
385
+    fn get_first_scene_item_for_position(&self, position: &Point2) -> Option<usize> {
386
+        // TODO: if found multiple: select nearest
387
+        for (i, scene_item) in self.scene_items.iter().enumerate() {
388
+            let sprite_info = scene_item.sprite_info();
389
+            if scene_item.position.x >= position.x - sprite_info.tile_width
390
+                && scene_item.position.x <= position.x + sprite_info.tile_width
391
+                && scene_item.position.y >= position.y - sprite_info.tile_height
392
+                && scene_item.position.y <= position.y + sprite_info.tile_height
393
+            {
394
+                return Some(i);
395
+            }
396
+        }
397
+
398
+        None
399
+    }
400
+
401
+    fn get_scene_items_for_area(&self, from: &Point2, to: &Point2) -> Vec<usize> {
402
+        let mut selection = vec![];
403
+
404
+        for (i, scene_item) in self.scene_items.iter().enumerate() {
405
+            if scene_item.position.x >= from.x
406
+                && scene_item.position.x <= to.x
407
+                && scene_item.position.y >= from.y
408
+                && scene_item.position.y <= to.y
409
+            {
410
+                selection.push(i);
411
+            }
412
+        }
413
+
414
+        selection
415
+    }
283 416
 }
284 417
 
285 418
 impl event::EventHandler for MainState {
@@ -330,7 +463,7 @@ impl event::EventHandler for MainState {
330 463
     fn draw(&mut self, ctx: &mut Context) -> GameResult {
331 464
         graphics::clear(ctx, graphics::BLACK);
332 465
 
333
-        let mut mesh_builder = MeshBuilder::new();
466
+        let mut scene_mesh_builder = MeshBuilder::new();
334 467
 
335 468
         for scene_item in self.scene_items.iter() {
336 469
             self.sprite_sheet_batch.add(
@@ -338,7 +471,7 @@ impl event::EventHandler for MainState {
338 471
                     .as_draw_param(scene_item.current_frame as f32)
339 472
                     .dest(scene_item.position.clone()),
340 473
             );
341
-            mesh_builder.circle(
474
+            scene_mesh_builder.circle(
342 475
                 DrawMode::fill(),
343 476
                 scene_item.position.clone(),
344 477
                 2.0,
@@ -346,13 +479,54 @@ impl event::EventHandler for MainState {
346 479
                 graphics::WHITE,
347 480
             )?;
348 481
         }
482
+
483
+        for i in &self.selected_scene_items {
484
+            let selected_scene_item = self
485
+                .scene_items
486
+                .get(*i)
487
+                .expect("scene_items content change !");
488
+            scene_mesh_builder.rectangle(
489
+                DrawMode::Stroke(StrokeOptions::default()),
490
+                graphics::Rect::new(
491
+                    selected_scene_item.position.x - DEFAULT_SELECTED_SQUARE_SIDE_HALF,
492
+                    selected_scene_item.position.y - DEFAULT_SELECTED_SQUARE_SIDE_HALF,
493
+                    DEFAULT_SELECTED_SQUARE_SIDE,
494
+                    DEFAULT_SELECTED_SQUARE_SIDE,
495
+                ),
496
+                graphics::GREEN,
497
+            )?;
498
+        }
499
+
500
+        if let Some(left_click_down) = self.left_click_down {
501
+            if left_click_down != self.current_cursor_position {
502
+                scene_mesh_builder.rectangle(
503
+                    DrawMode::fill(),
504
+                    graphics::Rect::new(
505
+                        left_click_down.x - self.display_offset.x,
506
+                        left_click_down.y - self.display_offset.y,
507
+                        self.current_cursor_position.x - left_click_down.x,
508
+                        self.current_cursor_position.y - left_click_down.y,
509
+                    ),
510
+                    graphics::GREEN,
511
+                )?;
512
+            }
513
+
514
+            scene_mesh_builder.circle(
515
+                DrawMode::fill(),
516
+                left_click_down,
517
+                2.0,
518
+                2.0,
519
+                graphics::YELLOW,
520
+            )?;
521
+        }
522
+
349 523
         self.map_batch.add(
350 524
             graphics::DrawParam::new()
351 525
                 .src(graphics::Rect::new(0.0, 0.0, 1.0, 1.0))
352 526
                 .dest(Point2::new(0.0, 0.0)),
353 527
         );
354 528
 
355
-        let mesh = mesh_builder.build(ctx)?;
529
+        let scene_mesh = scene_mesh_builder.build(ctx)?;
356 530
         graphics::draw(
357 531
             ctx,
358 532
             &self.map_batch,
@@ -367,7 +541,7 @@ impl event::EventHandler for MainState {
367 541
         )?;
368 542
         graphics::draw(
369 543
             ctx,
370
-            &mesh,
544
+            &scene_mesh,
371 545
             graphics::DrawParam::new()
372 546
                 .dest(self.position_with_display_offset(&Point2::new(0.0, 0.0))),
373 547
         )?;
@@ -379,6 +553,47 @@ impl event::EventHandler for MainState {
379 553
         println!("FPS: {}", ggez::timer::fps(ctx));
380 554
         Ok(())
381 555
     }
556
+
557
+    fn mouse_button_down_event(&mut self, _ctx: &mut Context, button: MouseButton, x: f32, y: f32) {
558
+        match button {
559
+            MouseButton::Left => {
560
+                self.left_click_down = Some(Point2::new(x, y));
561
+            }
562
+            MouseButton::Right => {}
563
+            MouseButton::Middle => {}
564
+            MouseButton::Other(_) => {}
565
+        }
566
+    }
567
+
568
+    fn mouse_button_up_event(&mut self, _ctx: &mut Context, button: MouseButton, x: f32, y: f32) {
569
+        match button {
570
+            MouseButton::Left => {
571
+                if let Some(left_click_down) = self.left_click_down {
572
+                    if left_click_down == Point2::new(x, y) {
573
+                        self.user_events.push(UserEvent::Click(left_click_down));
574
+                    } else {
575
+                        let from = Point2::new(
576
+                            cmp::min(left_click_down.x as i32, x as i32) as f32,
577
+                            cmp::min(left_click_down.y as i32, y as i32) as f32,
578
+                        );
579
+                        let to = Point2::new(
580
+                            cmp::max(left_click_down.x as i32, x as i32) as f32,
581
+                            cmp::max(left_click_down.y as i32, y as i32) as f32,
582
+                        );
583
+                        self.user_events.push(UserEvent::AreaSelection(from, to));
584
+                    }
585
+                }
586
+                self.left_click_down = None;
587
+            }
588
+            MouseButton::Right => {}
589
+            MouseButton::Middle => {}
590
+            MouseButton::Other(_) => {}
591
+        }
592
+    }
593
+
594
+    fn mouse_motion_event(&mut self, _ctx: &mut Context, x: f32, y: f32, _dx: f32, _dy: f32) {
595
+        self.current_cursor_position = Point2::new(x, y);
596
+    }
382 597
 }
383 598
 
384 599
 pub fn main() -> GameResult {