|
@@ -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 {
|