+1 -0Cargo.toml
1 1
[package]
2 2
name = "game-tutorial"
3 3
version = "0.1.0"
4 4
authors = ["Sunjay Varma <[email protected]>"]
5 5
edition = "2018"
6 6

7 7
[dependencies]
8 8
specs = "0.14"
9 9
specs-derive = "0.4"
  10
rand = "0.6"
10 11

11 12
[dependencies.sdl2]
12 13
version = "0.32.1"
13 14
default-features = false
14 15
features = ["image"]
+33 -0src/ai.rs
  1
use specs::prelude::*;
  2
use rand::prelude::*;
  3

  4
use crate::components::*;
  5

  6
const ENEMY_MOVEMENT_SPEED: i32 = 10;
  7

  8
pub struct AI;
  9

  10
impl<'a> System<'a> for AI {
  11
    type SystemData = (
  12
        ReadStorage<'a, Enemy>,
  13
        WriteStorage<'a, Velocity>,
  14
    );
  15

  16
    fn run(&mut self, mut data: Self::SystemData) {
  17
        //TODO: This code can be made nicer and more idiomatic using more pattern matching.
  18
        // Look up "rust irrefutable patterns" and use them here.
  19
        let mut rng = thread_rng();
  20
        for (_, vel) in (&data.0, &mut data.1).join() {
  21
            if rng.gen_range(0, 10) == 0 {
  22
                vel.speed = ENEMY_MOVEMENT_SPEED;
  23
                vel.direction = match rng.gen_range(0, 4) {
  24
                    0 => Direction::Up,
  25
                    1 => Direction::Down,
  26
                    2 => Direction::Left,
  27
                    3 => Direction::Right,
  28
                    _ => unreachable!(),
  29
                }
  30
            }
  31
        }
  32
    }
  33
}
+4 -0src/components.rs
1 1
use specs::prelude::*;
2 2
use specs_derive::Component;
3 3
use sdl2::rect::{Point, Rect};
4 4

5 5
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6 6
pub enum Direction {
7 7
    Up,
8 8
    Down,
9 9
    Left,
10 10
    Right,
11 11
}
12 12

13 13
#[derive(Component, Debug, Default)]
14 14
#[storage(NullStorage)]
15 15
pub struct KeyboardControlled;
16 16

  17
#[derive(Component, Debug, Default)]
  18
#[storage(NullStorage)]
  19
pub struct Enemy;
  20

17 21
/// The current position of a given entity
18 22
#[derive(Component, Debug)]
19 23
#[storage(VecStorage)]
20 24
pub struct Position(pub Point);
21 25

22 26
/// The current speed and direction of a given entity
23 27
#[derive(Component, Debug)]
24 28
#[storage(VecStorage)]
25 29
pub struct Velocity {
26 30
    pub speed: i32,
27 31
    pub direction: Direction,
28 32
}
29 33

30 34
#[derive(Component, Debug, Clone)]
31 35
#[storage(VecStorage)]
32 36
pub struct Sprite {
33 37
    /// The specific spritesheet to render from
34 38
    pub spritesheet: usize,
35 39
    /// The current region of the spritesheet to be rendered
36 40
    pub region: Rect,
37 41
}
38 42

39 43
#[derive(Component, Debug)]
40 44
#[storage(VecStorage)]
41 45
pub struct MovementAnimation {
42 46
    // The current frame in the animation of the direction this entity is moving in
43 47
    pub current_frame: usize,
44 48
    pub up_frames: Vec<Sprite>,
45 49
    pub down_frames: Vec<Sprite>,
46 50
    pub left_frames: Vec<Sprite>,
47 51
    pub right_frames: Vec<Sprite>,
48 52
}
+5 -2src/main.rs
1 1
mod components;
2 2
mod physics;
3 3
mod animator;
4 4
mod keyboard;
5 5
mod renderer;
  6
mod ai;
6 7

7 8
use sdl2::event::Event;
8 9
use sdl2::keyboard::Keycode;
9 10
use sdl2::pixels::Color;
10 11
use sdl2::rect::{Point, Rect};
11 12
// "self" imports the "image" module itself as well as everything else we listed
12 13
use sdl2::image::{self, LoadTexture, InitFlag};
13 14

14 15
use specs::prelude::*;
15 16

16 17
use std::time::Duration;
17 18

18 19
use crate::components::*;
19 20

20 21
pub enum MovementCommand {
21 22
    Stop,
22 23
    Move(Direction),
23 24
}
24 25

25 26
/// Returns the row of the spritesheet corresponding to the given direction
26 27
fn direction_spritesheet_row(direction: Direction) -> i32 {
27 28
    use self::Direction::*;
28 29
    match direction {
29 30
        Up => 3,
30 31
        Down => 0,
31 32
        Left => 1,
32 33
        Right => 2,
33 34
    }
34 35
}
35 36

36 37
/// Create animation frames for the standard character spritesheet
37 38
fn character_animation_frames(spritesheet: usize, top_left_frame: Rect, direction: Direction) -> Vec<Sprite> {
38 39
    // All assumptions about the spritesheets are now encapsulated in this function instead of in
39 40
    // the design of our entire system. We can always replace this function, but replacing the
40 41
    // entire system is harder.
41 42

42 43
    let (frame_width, frame_height) = top_left_frame.size();
43 44
    let y_offset = top_left_frame.y() + frame_height as i32 * direction_spritesheet_row(direction);
44 45

45 46
    let mut frames = Vec::new();
46 47
    for i in 0..3 {
47 48
        frames.push(Sprite {
48 49
            spritesheet,
49 50
            region: Rect::new(
50 51
                top_left_frame.x() + frame_width as i32 * i,
51 52
                y_offset,
52 53
                frame_width,
53 54
                frame_height,
54 55
            ),
55 56
        })
56 57
    }
57 58

58 59
    frames
59 60
}
60 61

61 62
fn initialize_player(world: &mut World, player_spritesheet: usize) {
62 63
    let player_top_left_frame = Rect::new(0, 0, 26, 36);
63 64

64 65
    let player_animation = MovementAnimation {
65 66
        current_frame: 0,
66 67
        up_frames: character_animation_frames(player_spritesheet, player_top_left_frame, Direction::Up),
67 68
        down_frames: character_animation_frames(player_spritesheet, player_top_left_frame, Direction::Down),
68 69
        left_frames: character_animation_frames(player_spritesheet, player_top_left_frame, Direction::Left),
69 70
        right_frames: character_animation_frames(player_spritesheet, player_top_left_frame, Direction::Right),
70 71
    };
71 72

72 73
    world.create_entity()
73 74
        .with(KeyboardControlled)
74 75
        .with(Position(Point::new(0, 0)))
75 76
        .with(Velocity {speed: 0, direction: Direction::Right})
76 77
        .with(player_animation.right_frames[0].clone())
77 78
        .with(player_animation)
78 79
        .build();
79 80
}
80 81

81 82
fn initialize_enemy(world: &mut World, enemy_spritesheet: usize, position: Point) {
82 83
    let enemy_top_left_frame = Rect::new(0, 0, 32, 36);
83 84

84 85
    let enemy_animation = MovementAnimation {
85 86
        current_frame: 0,
86 87
        up_frames: character_animation_frames(enemy_spritesheet, enemy_top_left_frame, Direction::Up),
87 88
        down_frames: character_animation_frames(enemy_spritesheet, enemy_top_left_frame, Direction::Down),
88 89
        left_frames: character_animation_frames(enemy_spritesheet, enemy_top_left_frame, Direction::Left),
89 90
        right_frames: character_animation_frames(enemy_spritesheet, enemy_top_left_frame, Direction::Right),
90 91
    };
91 92

92 93
    world.create_entity()
  94
        .with(Enemy)
93 95
        .with(Position(position))
94 96
        .with(Velocity {speed: 0, direction: Direction::Right})
95 97
        .with(enemy_animation.right_frames[0].clone())
96 98
        .with(enemy_animation)
97 99
        .build();
98 100
}
99 101

100 102
fn main() -> Result<(), String> {
101 103
    let sdl_context = sdl2::init()?;
102 104
    let video_subsystem = sdl_context.video()?;
103 105
    // Leading "_" tells Rust that this is an unused variable that we don't care about. It has to
104 106
    // stay unused because if we don't have any variable at all then Rust will treat it as a
105 107
    // temporary value and drop it right away!
106 108
    let _image_context = image::init(InitFlag::PNG | InitFlag::JPG)?;
107 109

108 110
    let window = video_subsystem.window("game tutorial", 800, 600)
109 111
        .position_centered()
110 112
        .build()
111 113
        .expect("could not initialize video subsystem");
112 114

113 115
    let mut canvas = window.into_canvas().build()
114 116
        .expect("could not make a canvas");
115 117
    let texture_creator = canvas.texture_creator();
116 118

117 119
    let mut dispatcher = DispatcherBuilder::new()
118 120
        .with(keyboard::Keyboard, "Keyboard", &[])
119  
        .with(physics::Physics, "Physics", &["Keyboard"])
120  
        .with(animator::Animator, "Animator", &["Keyboard"])
  121
        .with(ai::AI, "AI", &[])
  122
        .with(physics::Physics, "Physics", &["Keyboard", "AI"])
  123
        .with(animator::Animator, "Animator", &["Keyboard", "AI"])
121 124
        .build();
122 125

123 126
    let mut world = World::new();
124 127
    dispatcher.setup(&mut world.res);
125 128
    renderer::SystemData::setup(&mut world.res);
126 129

127 130
    // Initialize resource
128 131
    let movement_command: Option<MovementCommand> = None;
129 132
    world.add_resource(movement_command);
130 133

131 134
    let textures = [
132 135
        texture_creator.load_texture("assets/bardo.png")?,
133 136
        texture_creator.load_texture("assets/reaper.png")?,
134 137
    ];
135 138
    // First texture in textures array
136 139
    let player_spritesheet = 0;
137 140
    // Second texture in the textures array
138 141
    let enemy_spritesheet = 1;
139 142

140 143
    initialize_player(&mut world, player_spritesheet);
141 144

142 145
    initialize_enemy(&mut world, enemy_spritesheet, Point::new(-150, -150));
143 146
    initialize_enemy(&mut world, enemy_spritesheet, Point::new(150, -190));
144 147
    initialize_enemy(&mut world, enemy_spritesheet, Point::new(-150, 170));
145 148

146 149
    let mut event_pump = sdl_context.event_pump()?;
147 150
    let mut i = 0;
148 151
    'running: loop {
149 152
        // None - no change, Some(MovementCommand) - perform movement
150 153
        let mut movement_command = None;
151 154
        // Handle events
152 155
        for event in event_pump.poll_iter() {
153 156
            match event {
154 157
                Event::Quit {..} |
155 158
                Event::KeyDown { keycode: Some(Keycode::Escape), .. } => {
156 159
                    break 'running;
157 160
                },
158 161
                Event::KeyDown { keycode: Some(Keycode::Left), repeat: false, .. } => {
159 162
                    movement_command = Some(MovementCommand::Move(Direction::Left));
160 163
                },
161 164
                Event::KeyDown { keycode: Some(Keycode::Right), repeat: false, .. } => {
162 165
                    movement_command = Some(MovementCommand::Move(Direction::Right));
163 166
                },
164 167
                Event::KeyDown { keycode: Some(Keycode::Up), repeat: false, .. } => {
165 168
                    movement_command = Some(MovementCommand::Move(Direction::Up));
166 169
                },
167 170
                Event::KeyDown { keycode: Some(Keycode::Down), repeat: false, .. } => {
168 171
                    movement_command = Some(MovementCommand::Move(Direction::Down));
169 172
                },
170 173
                Event::KeyUp { keycode: Some(Keycode::Left), repeat: false, .. } |
171 174
                Event::KeyUp { keycode: Some(Keycode::Right), repeat: false, .. } |
172 175
                Event::KeyUp { keycode: Some(Keycode::Up), repeat: false, .. } |
173 176
                Event::KeyUp { keycode: Some(Keycode::Down), repeat: false, .. } => {
174 177
                    movement_command = Some(MovementCommand::Stop);
175 178
                },
176 179
                _ => {}
177 180
            }
178 181
        }
179 182

180 183
        *world.write_resource() = movement_command;
181 184

182 185
        // Update
183 186
        i = (i + 1) % 255;
184 187
        dispatcher.dispatch(&mut world.res);
185 188
        world.maintain();
186 189

187 190
        // Render
188 191
        renderer::render(&mut canvas, Color::RGB(i, 64, 255 - i), &textures, world.system_data())?;
189 192

190 193
        // Time management!
191 194
        ::std::thread::sleep(Duration::new(0, 1_000_000_000u32 / 20));
192 195
    }
193 196

194 197
    Ok(())
195 198
}
+1 -0Cargo.toml
1
[package]
1
[package]
2
name = "game-tutorial"
2
name = "game-tutorial"
3
version = "0.1.0"
3
version = "0.1.0"
4
authors = ["Sunjay Varma <[email protected]>"]
4
authors = ["Sunjay Varma <[email protected]>"]
5
edition = "2018"
5
edition = "2018"
6

6

7
[dependencies]
7
[dependencies]
8
specs = "0.14"
8
specs = "0.14"
9
specs-derive = "0.4"
9
specs-derive = "0.4"
    10
rand = "0.6"
10

11

11
[dependencies.sdl2]
12
[dependencies.sdl2]
12
version = "0.32.1"
13
version = "0.32.1"
13
default-features = false
14
default-features = false
14
features = ["image"]
15
features = ["image"]
+33 -0src/ai.rs
    1
use specs::prelude::*;
    2
use rand::prelude::*;
    3

    4
use crate::components::*;
    5

    6
const ENEMY_MOVEMENT_SPEED: i32 = 10;
    7

    8
pub struct AI;
    9

    10
impl<'a> System<'a> for AI {
    11
    type SystemData = (
    12
        ReadStorage<'a, Enemy>,
    13
        WriteStorage<'a, Velocity>,
    14
    );
    15

    16
    fn run(&mut self, mut data: Self::SystemData) {
    17
        //TODO: This code can be made nicer and more idiomatic using more pattern matching.
    18
        // Look up "rust irrefutable patterns" and use them here.
    19
        let mut rng = thread_rng();
    20
        for (_, vel) in (&data.0, &mut data.1).join() {
    21
            if rng.gen_range(0, 10) == 0 {
    22
                vel.speed = ENEMY_MOVEMENT_SPEED;
    23
                vel.direction = match rng.gen_range(0, 4) {
    24
                    0 => Direction::Up,
    25
                    1 => Direction::Down,
    26
                    2 => Direction::Left,
    27
                    3 => Direction::Right,
    28
                    _ => unreachable!(),
    29
                }
    30
            }
    31
        }
    32
    }
    33
}
+4 -0src/components.rs
1
use specs::prelude::*;
1
use specs::prelude::*;
2
use specs_derive::Component;
2
use specs_derive::Component;
3
use sdl2::rect::{Point, Rect};
3
use sdl2::rect::{Point, Rect};
4

4

5
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6
pub enum Direction {
6
pub enum Direction {
7
    Up,
7
    Up,
8
    Down,
8
    Down,
9
    Left,
9
    Left,
10
    Right,
10
    Right,
11
}
11
}
12

12

13
#[derive(Component, Debug, Default)]
13
#[derive(Component, Debug, Default)]
14
#[storage(NullStorage)]
14
#[storage(NullStorage)]
15
pub struct KeyboardControlled;
15
pub struct KeyboardControlled;
16

16

    17
#[derive(Component, Debug, Default)]
    18
#[storage(NullStorage)]
    19
pub struct Enemy;
    20

17
/// The current position of a given entity
21
/// The current position of a given entity
18
#[derive(Component, Debug)]
22
#[derive(Component, Debug)]
19
#[storage(VecStorage)]
23
#[storage(VecStorage)]
20
pub struct Position(pub Point);
24
pub struct Position(pub Point);
21

25

22
/// The current speed and direction of a given entity
26
/// The current speed and direction of a given entity
23
#[derive(Component, Debug)]
27
#[derive(Component, Debug)]
24
#[storage(VecStorage)]
28
#[storage(VecStorage)]
25
pub struct Velocity {
29
pub struct Velocity {
26
    pub speed: i32,
30
    pub speed: i32,
27
    pub direction: Direction,
31
    pub direction: Direction,
28
}
32
}
29

33

30
#[derive(Component, Debug, Clone)]
34
#[derive(Component, Debug, Clone)]
31
#[storage(VecStorage)]
35
#[storage(VecStorage)]
32
pub struct Sprite {
36
pub struct Sprite {
33
    /// The specific spritesheet to render from
37
    /// The specific spritesheet to render from
34
    pub spritesheet: usize,
38
    pub spritesheet: usize,
35
    /// The current region of the spritesheet to be rendered
39
    /// The current region of the spritesheet to be rendered
36
    pub region: Rect,
40
    pub region: Rect,
37
}
41
}
38

42

39
#[derive(Component, Debug)]
43
#[derive(Component, Debug)]
40
#[storage(VecStorage)]
44
#[storage(VecStorage)]
41
pub struct MovementAnimation {
45
pub struct MovementAnimation {
42
    // The current frame in the animation of the direction this entity is moving in
46
    // The current frame in the animation of the direction this entity is moving in
43
    pub current_frame: usize,
47
    pub current_frame: usize,
44
    pub up_frames: Vec<Sprite>,
48
    pub up_frames: Vec<Sprite>,
45
    pub down_frames: Vec<Sprite>,
49
    pub down_frames: Vec<Sprite>,
46
    pub left_frames: Vec<Sprite>,
50
    pub left_frames: Vec<Sprite>,
47
    pub right_frames: Vec<Sprite>,
51
    pub right_frames: Vec<Sprite>,
48
}
52
}
+5 -2src/main.rs
1
mod components;
1
mod components;
2
mod physics;
2
mod physics;
3
mod animator;
3
mod animator;
4
mod keyboard;
4
mod keyboard;
5
mod renderer;
5
mod renderer;
    6
mod ai;
6

7

7
use sdl2::event::Event;
8
use sdl2::event::Event;
8
use sdl2::keyboard::Keycode;
9
use sdl2::keyboard::Keycode;
9
use sdl2::pixels::Color;
10
use sdl2::pixels::Color;
10
use sdl2::rect::{Point, Rect};
11
use sdl2::rect::{Point, Rect};
11
// "self" imports the "image" module itself as well as everything else we listed
12
// "self" imports the "image" module itself as well as everything else we listed
12
use sdl2::image::{self, LoadTexture, InitFlag};
13
use sdl2::image::{self, LoadTexture, InitFlag};
13

14

14
use specs::prelude::*;
15
use specs::prelude::*;
15

16

16
use std::time::Duration;
17
use std::time::Duration;
17

18

18
use crate::components::*;
19
use crate::components::*;
19

20

20
pub enum MovementCommand {
21
pub enum MovementCommand {
21
    Stop,
22
    Stop,
22
    Move(Direction),
23
    Move(Direction),
23
}
24
}
24

25

25
/// Returns the row of the spritesheet corresponding to the given direction
26
/// Returns the row of the spritesheet corresponding to the given direction
26
fn direction_spritesheet_row(direction: Direction) -> i32 {
27
fn direction_spritesheet_row(direction: Direction) -> i32 {
27
    use self::Direction::*;
28
    use self::Direction::*;
28
    match direction {
29
    match direction {
29
        Up => 3,
30
        Up => 3,
30
        Down => 0,
31
        Down => 0,
31
        Left => 1,
32
        Left => 1,
32
        Right => 2,
33
        Right => 2,
33
    }
34
    }
34
}
35
}
35

36

36
/// Create animation frames for the standard character spritesheet
37
/// Create animation frames for the standard character spritesheet
37
fn character_animation_frames(spritesheet: usize, top_left_frame: Rect, direction: Direction) -> Vec<Sprite> {
38
fn character_animation_frames(spritesheet: usize, top_left_frame: Rect, direction: Direction) -> Vec<Sprite> {
38
    // All assumptions about the spritesheets are now encapsulated in this function instead of in
39
    // All assumptions about the spritesheets are now encapsulated in this function instead of in
39
    // the design of our entire system. We can always replace this function, but replacing the
40
    // the design of our entire system. We can always replace this function, but replacing the
40
    // entire system is harder.
41
    // entire system is harder.
41

42

42
    let (frame_width, frame_height) = top_left_frame.size();
43
    let (frame_width, frame_height) = top_left_frame.size();
43
    let y_offset = top_left_frame.y() + frame_height as i32 * direction_spritesheet_row(direction);
44
    let y_offset = top_left_frame.y() + frame_height as i32 * direction_spritesheet_row(direction);
44

45

45
    let mut frames = Vec::new();
46
    let mut frames = Vec::new();
46
    for i in 0..3 {
47
    for i in 0..3 {
47
        frames.push(Sprite {
48
        frames.push(Sprite {
48
            spritesheet,
49
            spritesheet,
49
            region: Rect::new(
50
            region: Rect::new(
50
                top_left_frame.x() + frame_width as i32 * i,
51
                top_left_frame.x() + frame_width as i32 * i,
51
                y_offset,
52
                y_offset,
52
                frame_width,
53
                frame_width,
53
                frame_height,
54
                frame_height,
54
            ),
55
            ),
55
        })
56
        })
56
    }
57
    }
57

58

58
    frames
59
    frames
59
}
60
}
60

61

61
fn initialize_player(world: &mut World, player_spritesheet: usize) {
62
fn initialize_player(world: &mut World, player_spritesheet: usize) {
62
    let player_top_left_frame = Rect::new(0, 0, 26, 36);
63
    let player_top_left_frame = Rect::new(0, 0, 26, 36);
63

64

64
    let player_animation = MovementAnimation {
65
    let player_animation = MovementAnimation {
65
        current_frame: 0,
66
        current_frame: 0,
66
        up_frames: character_animation_frames(player_spritesheet, player_top_left_frame, Direction::Up),
67
        up_frames: character_animation_frames(player_spritesheet, player_top_left_frame, Direction::Up),
67
        down_frames: character_animation_frames(player_spritesheet, player_top_left_frame, Direction::Down),
68
        down_frames: character_animation_frames(player_spritesheet, player_top_left_frame, Direction::Down),
68
        left_frames: character_animation_frames(player_spritesheet, player_top_left_frame, Direction::Left),
69
        left_frames: character_animation_frames(player_spritesheet, player_top_left_frame, Direction::Left),
69
        right_frames: character_animation_frames(player_spritesheet, player_top_left_frame, Direction::Right),
70
        right_frames: character_animation_frames(player_spritesheet, player_top_left_frame, Direction::Right),
70
    };
71
    };
71

72

72
    world.create_entity()
73
    world.create_entity()
73
        .with(KeyboardControlled)
74
        .with(KeyboardControlled)
74
        .with(Position(Point::new(0, 0)))
75
        .with(Position(Point::new(0, 0)))
75
        .with(Velocity {speed: 0, direction: Direction::Right})
76
        .with(Velocity {speed: 0, direction: Direction::Right})
76
        .with(player_animation.right_frames[0].clone())
77
        .with(player_animation.right_frames[0].clone())
77
        .with(player_animation)
78
        .with(player_animation)
78
        .build();
79
        .build();
79
}
80
}
80

81

81
fn initialize_enemy(world: &mut World, enemy_spritesheet: usize, position: Point) {
82
fn initialize_enemy(world: &mut World, enemy_spritesheet: usize, position: Point) {
82
    let enemy_top_left_frame = Rect::new(0, 0, 32, 36);
83
    let enemy_top_left_frame = Rect::new(0, 0, 32, 36);
83

84

84
    let enemy_animation = MovementAnimation {
85
    let enemy_animation = MovementAnimation {
85
        current_frame: 0,
86
        current_frame: 0,
86
        up_frames: character_animation_frames(enemy_spritesheet, enemy_top_left_frame, Direction::Up),
87
        up_frames: character_animation_frames(enemy_spritesheet, enemy_top_left_frame, Direction::Up),
87
        down_frames: character_animation_frames(enemy_spritesheet, enemy_top_left_frame, Direction::Down),
88
        down_frames: character_animation_frames(enemy_spritesheet, enemy_top_left_frame, Direction::Down),
88
        left_frames: character_animation_frames(enemy_spritesheet, enemy_top_left_frame, Direction::Left),
89
        left_frames: character_animation_frames(enemy_spritesheet, enemy_top_left_frame, Direction::Left),
89
        right_frames: character_animation_frames(enemy_spritesheet, enemy_top_left_frame, Direction::Right),
90
        right_frames: character_animation_frames(enemy_spritesheet, enemy_top_left_frame, Direction::Right),
90
    };
91
    };
91

92

92
    world.create_entity()
93
    world.create_entity()
    94
        .with(Enemy)
93
        .with(Position(position))
95
        .with(Position(position))
94
        .with(Velocity {speed: 0, direction: Direction::Right})
96
        .with(Velocity {speed: 0, direction: Direction::Right})
95
        .with(enemy_animation.right_frames[0].clone())
97
        .with(enemy_animation.right_frames[0].clone())
96
        .with(enemy_animation)
98
        .with(enemy_animation)
97
        .build();
99
        .build();
98
}
100
}
99

101

100
fn main() -> Result<(), String> {
102
fn main() -> Result<(), String> {
101
    let sdl_context = sdl2::init()?;
103
    let sdl_context = sdl2::init()?;
102
    let video_subsystem = sdl_context.video()?;
104
    let video_subsystem = sdl_context.video()?;
103
    // Leading "_" tells Rust that this is an unused variable that we don't care about. It has to
105
    // Leading "_" tells Rust that this is an unused variable that we don't care about. It has to
104
    // stay unused because if we don't have any variable at all then Rust will treat it as a
106
    // stay unused because if we don't have any variable at all then Rust will treat it as a
105
    // temporary value and drop it right away!
107
    // temporary value and drop it right away!
106
    let _image_context = image::init(InitFlag::PNG | InitFlag::JPG)?;
108
    let _image_context = image::init(InitFlag::PNG | InitFlag::JPG)?;
107

109

108
    let window = video_subsystem.window("game tutorial", 800, 600)
110
    let window = video_subsystem.window("game tutorial", 800, 600)
109
        .position_centered()
111
        .position_centered()
110
        .build()
112
        .build()
111
        .expect("could not initialize video subsystem");
113
        .expect("could not initialize video subsystem");
112

114

113
    let mut canvas = window.into_canvas().build()
115
    let mut canvas = window.into_canvas().build()
114
        .expect("could not make a canvas");
116
        .expect("could not make a canvas");
115
    let texture_creator = canvas.texture_creator();
117
    let texture_creator = canvas.texture_creator();
116

118

117
    let mut dispatcher = DispatcherBuilder::new()
119
    let mut dispatcher = DispatcherBuilder::new()
118
        .with(keyboard::Keyboard, "Keyboard", &[])
120
        .with(keyboard::Keyboard, "Keyboard", &[])
119
        .with(physics::Physics, "Physics", &["Keyboard"])
121
        .with(ai::AI, "AI", &[])
120
        .with(animator::Animator, "Animator", &["Keyboard"])
122
        .with(physics::Physics, "Physics", &["Keyboard", "AI"])
    123
        .with(animator::Animator, "Animator", &["Keyboard", "AI"])
121
        .build();
124
        .build();
122

125

123
    let mut world = World::new();
126
    let mut world = World::new();
124
    dispatcher.setup(&mut world.res);
127
    dispatcher.setup(&mut world.res);
125
    renderer::SystemData::setup(&mut world.res);
128
    renderer::SystemData::setup(&mut world.res);
126

129

127
    // Initialize resource
130
    // Initialize resource
128
    let movement_command: Option<MovementCommand> = None;
131
    let movement_command: Option<MovementCommand> = None;
129
    world.add_resource(movement_command);
132
    world.add_resource(movement_command);
130

133

131
    let textures = [
134
    let textures = [
132
        texture_creator.load_texture("assets/bardo.png")?,
135
        texture_creator.load_texture("assets/bardo.png")?,
133
        texture_creator.load_texture("assets/reaper.png")?,
136
        texture_creator.load_texture("assets/reaper.png")?,
134
    ];
137
    ];
135
    // First texture in textures array
138
    // First texture in textures array
136
    let player_spritesheet = 0;
139
    let player_spritesheet = 0;
137
    // Second texture in the textures array
140
    // Second texture in the textures array
138
    let enemy_spritesheet = 1;
141
    let enemy_spritesheet = 1;
139

142

140
    initialize_player(&mut world, player_spritesheet);
143
    initialize_player(&mut world, player_spritesheet);
141

144

142
    initialize_enemy(&mut world, enemy_spritesheet, Point::new(-150, -150));
145
    initialize_enemy(&mut world, enemy_spritesheet, Point::new(-150, -150));
143
    initialize_enemy(&mut world, enemy_spritesheet, Point::new(150, -190));
146
    initialize_enemy(&mut world, enemy_spritesheet, Point::new(150, -190));
144
    initialize_enemy(&mut world, enemy_spritesheet, Point::new(-150, 170));
147
    initialize_enemy(&mut world, enemy_spritesheet, Point::new(-150, 170));
145

148

146
    let mut event_pump = sdl_context.event_pump()?;
149
    let mut event_pump = sdl_context.event_pump()?;
147
    let mut i = 0;
150
    let mut i = 0;
148
    'running: loop {
151
    'running: loop {
149
        // None - no change, Some(MovementCommand) - perform movement
152
        // None - no change, Some(MovementCommand) - perform movement
150
        let mut movement_command = None;
153
        let mut movement_command = None;
151
        // Handle events
154
        // Handle events
152
        for event in event_pump.poll_iter() {
155
        for event in event_pump.poll_iter() {
153
            match event {
156
            match event {
154
                Event::Quit {..} |
157
                Event::Quit {..} |
155
                Event::KeyDown { keycode: Some(Keycode::Escape), .. } => {
158
                Event::KeyDown { keycode: Some(Keycode::Escape), .. } => {
156
                    break 'running;
159
                    break 'running;
157
                },
160
                },
158
                Event::KeyDown { keycode: Some(Keycode::Left), repeat: false, .. } => {
161
                Event::KeyDown { keycode: Some(Keycode::Left), repeat: false, .. } => {
159
                    movement_command = Some(MovementCommand::Move(Direction::Left));
162
                    movement_command = Some(MovementCommand::Move(Direction::Left));
160
                },
163
                },
161
                Event::KeyDown { keycode: Some(Keycode::Right), repeat: false, .. } => {
164
                Event::KeyDown { keycode: Some(Keycode::Right), repeat: false, .. } => {
162
                    movement_command = Some(MovementCommand::Move(Direction::Right));
165
                    movement_command = Some(MovementCommand::Move(Direction::Right));
163
                },
166
                },
164
                Event::KeyDown { keycode: Some(Keycode::Up), repeat: false, .. } => {
167
                Event::KeyDown { keycode: Some(Keycode::Up), repeat: false, .. } => {
165
                    movement_command = Some(MovementCommand::Move(Direction::Up));
168
                    movement_command = Some(MovementCommand::Move(Direction::Up));
166
                },
169
                },
167
                Event::KeyDown { keycode: Some(Keycode::Down), repeat: false, .. } => {
170
                Event::KeyDown { keycode: Some(Keycode::Down), repeat: false, .. } => {
168
                    movement_command = Some(MovementCommand::Move(Direction::Down));
171
                    movement_command = Some(MovementCommand::Move(Direction::Down));
169
                },
172
                },
170
                Event::KeyUp { keycode: Some(Keycode::Left), repeat: false, .. } |
173
                Event::KeyUp { keycode: Some(Keycode::Left), repeat: false, .. } |
171
                Event::KeyUp { keycode: Some(Keycode::Right), repeat: false, .. } |
174
                Event::KeyUp { keycode: Some(Keycode::Right), repeat: false, .. } |
172
                Event::KeyUp { keycode: Some(Keycode::Up), repeat: false, .. } |
175
                Event::KeyUp { keycode: Some(Keycode::Up), repeat: false, .. } |
173
                Event::KeyUp { keycode: Some(Keycode::Down), repeat: false, .. } => {
176
                Event::KeyUp { keycode: Some(Keycode::Down), repeat: false, .. } => {
174
                    movement_command = Some(MovementCommand::Stop);
177
                    movement_command = Some(MovementCommand::Stop);
175
                },
178
                },
176
                _ => {}
179
                _ => {}
177
            }
180
            }
178
        }
181
        }
179

182

180
        *world.write_resource() = movement_command;
183
        *world.write_resource() = movement_command;
181

184

182
        // Update
185
        // Update
183
        i = (i + 1) % 255;
186
        i = (i + 1) % 255;
184
        dispatcher.dispatch(&mut world.res);
187
        dispatcher.dispatch(&mut world.res);
185
        world.maintain();
188
        world.maintain();
186

189

187
        // Render
190
        // Render
188
        renderer::render(&mut canvas, Color::RGB(i, 64, 255 - i), &textures, world.system_data())?;
191
        renderer::render(&mut canvas, Color::RGB(i, 64, 255 - i), &textures, world.system_data())?;
189

192

190
        // Time management!
193
        // Time management!
191
        ::std::thread::sleep(Duration::new(0, 1_000_000_000u32 / 20));
194
        ::std::thread::sleep(Duration::new(0, 1_000_000_000u32 / 20));
192
    }
195
    }
193

196

194
    Ok(())
197
    Ok(())
195
}
198
}
/target
**/*.rs.bk
[package]
name = "game-tutorial"
version = "0.1.0"
authors = ["Sunjay Varma <[email protected]>"]
edition = "2018"

[dependencies]
specs = "0.14"
specs-derive = "0.4"
rand = "0.6"

[dependencies.sdl2]
version = "0.32.1"
default-features = false
features = ["image"]
use specs::prelude::*;
use rand::prelude::*;

use crate::components::*;

const ENEMY_MOVEMENT_SPEED: i32 = 10;

pub struct AI;

impl<'a> System<'a> for AI {
    type SystemData = (
        ReadStorage<'a, Enemy>,
        WriteStorage<'a, Velocity>,
    );

    fn run(&mut self, mut data: Self::SystemData) {
        //TODO: This code can be made nicer and more idiomatic using more pattern matching.
        // Look up "rust irrefutable patterns" and use them here.
        let mut rng = thread_rng();
        for (_, vel) in (&data.0, &mut data.1).join() {
            if rng.gen_range(0, 10) == 0 {
                vel.speed = ENEMY_MOVEMENT_SPEED;
                vel.direction = match rng.gen_range(0, 4) {
                    0 => Direction::Up,
                    1 => Direction::Down,
                    2 => Direction::Left,
                    3 => Direction::Right,
                    _ => unreachable!(),
                }
            }
        }
    }
}
use specs::prelude::*;

use crate::components::*;

pub struct Animator;

impl<'a> System<'a> for Animator {
    type SystemData = (
        WriteStorage<'a, MovementAnimation>,
        WriteStorage<'a, Sprite>,
        ReadStorage<'a, Velocity>,
    );

    fn run(&mut self, mut data: Self::SystemData) {
        use self::Direction::*;
        //TODO: This code can be made nicer and more idiomatic using more pattern matching.
        // Look up "rust irrefutable patterns" and use them here.
        for (anim, sprite, vel) in (&mut data.0, &mut data.1, &data.2).join() {
            if vel.speed == 0 {
                continue;
            }

            let frames = match vel.direction {
                Left => &anim.left_frames,
                Right => &anim.right_frames,
                Up => &anim.up_frames,
                Down => &anim.down_frames,
            };

            anim.current_frame = (anim.current_frame + 1) % frames.len();
            *sprite = frames[anim.current_frame].clone();
        }
    }
}
use specs::prelude::*;
use specs_derive::Component;
use sdl2::rect::{Point, Rect};

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Direction {
    Up,
    Down,
    Left,
    Right,
}

#[derive(Component, Debug, Default)]
#[storage(NullStorage)]
pub struct KeyboardControlled;

#[derive(Component, Debug, Default)]
#[storage(NullStorage)]
pub struct Enemy;

/// The current position of a given entity
#[derive(Component, Debug)]
#[storage(VecStorage)]
pub struct Position(pub Point);

/// The current speed and direction of a given entity
#[derive(Component, Debug)]
#[storage(VecStorage)]
pub struct Velocity {
    pub speed: i32,
    pub direction: Direction,
}

#[derive(Component, Debug, Clone)]
#[storage(VecStorage)]
pub struct Sprite {
    /// The specific spritesheet to render from
    pub spritesheet: usize,
    /// The current region of the spritesheet to be rendered
    pub region: Rect,
}

#[derive(Component, Debug)]
#[storage(VecStorage)]
pub struct MovementAnimation {
    // The current frame in the animation of the direction this entity is moving in
    pub current_frame: usize,
    pub up_frames: Vec<Sprite>,
    pub down_frames: Vec<Sprite>,
    pub left_frames: Vec<Sprite>,
    pub right_frames: Vec<Sprite>,
}
use specs::prelude::*;

use crate::components::*;

use super::MovementCommand;

const PLAYER_MOVEMENT_SPEED: i32 = 20;

pub struct Keyboard;

impl<'a> System<'a> for Keyboard {
    type SystemData = (
        ReadExpect<'a, Option<MovementCommand>>,
        ReadStorage<'a, KeyboardControlled>,
        WriteStorage<'a, Velocity>,
    );

    fn run(&mut self, mut data: Self::SystemData) {
        //TODO: This code can be made nicer and more idiomatic using more pattern matching.
        // Look up "rust irrefutable patterns" and use them here.
        let movement_command = match &*data.0 {
            Some(movement_command) => movement_command,
            None => return, // no change
        };

        for (_, vel) in (&data.1, &mut data.2).join() {
            match movement_command {
                &MovementCommand::Move(direction) => {
                    vel.speed = PLAYER_MOVEMENT_SPEED;
                    vel.direction = direction;
                },
                MovementCommand::Stop => vel.speed = 0,
            }
        }
    }
}
mod components;
mod physics;
mod animator;
mod keyboard;
mod renderer;
mod ai;

use sdl2::event::Event;
use sdl2::keyboard::Keycode;
use sdl2::pixels::Color;
use sdl2::rect::{Point, Rect};
// "self" imports the "image" module itself as well as everything else we listed
use sdl2::image::{self, LoadTexture, InitFlag};

use specs::prelude::*;

use std::time::Duration;

use crate::components::*;

pub enum MovementCommand {
    Stop,
    Move(Direction),
}

/// Returns the row of the spritesheet corresponding to the given direction
fn direction_spritesheet_row(direction: Direction) -> i32 {
    use self::Direction::*;
    match direction {
        Up => 3,
        Down => 0,
        Left => 1,
        Right => 2,
    }
}

/// Create animation frames for the standard character spritesheet
fn character_animation_frames(spritesheet: usize, top_left_frame: Rect, direction: Direction) -> Vec<Sprite> {
    // All assumptions about the spritesheets are now encapsulated in this function instead of in
    // the design of our entire system. We can always replace this function, but replacing the
    // entire system is harder.

    let (frame_width, frame_height) = top_left_frame.size();
    let y_offset = top_left_frame.y() + frame_height as i32 * direction_spritesheet_row(direction);

    let mut frames = Vec::new();
    for i in 0..3 {
        frames.push(Sprite {
            spritesheet,
            region: Rect::new(
                top_left_frame.x() + frame_width as i32 * i,
                y_offset,
                frame_width,
                frame_height,
            ),
        })
    }

    frames
}

fn initialize_player(world: &mut World, player_spritesheet: usize) {
    let player_top_left_frame = Rect::new(0, 0, 26, 36);

    let player_animation = MovementAnimation {
        current_frame: 0,
        up_frames: character_animation_frames(player_spritesheet, player_top_left_frame, Direction::Up),
        down_frames: character_animation_frames(player_spritesheet, player_top_left_frame, Direction::Down),
        left_frames: character_animation_frames(player_spritesheet, player_top_left_frame, Direction::Left),
        right_frames: character_animation_frames(player_spritesheet, player_top_left_frame, Direction::Right),
    };

    world.create_entity()
        .with(KeyboardControlled)
        .with(Position(Point::new(0, 0)))
        .with(Velocity {speed: 0, direction: Direction::Right})
        .with(player_animation.right_frames[0].clone())
        .with(player_animation)
        .build();
}

fn initialize_enemy(world: &mut World, enemy_spritesheet: usize, position: Point) {
    let enemy_top_left_frame = Rect::new(0, 0, 32, 36);

    let enemy_animation = MovementAnimation {
        current_frame: 0,
        up_frames: character_animation_frames(enemy_spritesheet, enemy_top_left_frame, Direction::Up),
        down_frames: character_animation_frames(enemy_spritesheet, enemy_top_left_frame, Direction::Down),
        left_frames: character_animation_frames(enemy_spritesheet, enemy_top_left_frame, Direction::Left),
        right_frames: character_animation_frames(enemy_spritesheet, enemy_top_left_frame, Direction::Right),
    };

    world.create_entity()
        .with(Enemy)
        .with(Position(position))
        .with(Velocity {speed: 0, direction: Direction::Right})
        .with(enemy_animation.right_frames[0].clone())
        .with(enemy_animation)
        .build();
}

fn main() -> Result<(), String> {
    let sdl_context = sdl2::init()?;
    let video_subsystem = sdl_context.video()?;
    // Leading "_" tells Rust that this is an unused variable that we don't care about. It has to
    // stay unused because if we don't have any variable at all then Rust will treat it as a
    // temporary value and drop it right away!
    let _image_context = image::init(InitFlag::PNG | InitFlag::JPG)?;

    let window = video_subsystem.window("game tutorial", 800, 600)
        .position_centered()
        .build()
        .expect("could not initialize video subsystem");

    let mut canvas = window.into_canvas().build()
        .expect("could not make a canvas");
    let texture_creator = canvas.texture_creator();

    let mut dispatcher = DispatcherBuilder::new()
        .with(keyboard::Keyboard, "Keyboard", &[])
        .with(ai::AI, "AI", &[])
        .with(physics::Physics, "Physics", &["Keyboard", "AI"])
        .with(animator::Animator, "Animator", &["Keyboard", "AI"])
        .build();

    let mut world = World::new();
    dispatcher.setup(&mut world.res);
    renderer::SystemData::setup(&mut world.res);

    // Initialize resource
    let movement_command: Option<MovementCommand> = None;
    world.add_resource(movement_command);

    let textures = [
        texture_creator.load_texture("assets/bardo.png")?,
        texture_creator.load_texture("assets/reaper.png")?,
    ];
    // First texture in textures array
    let player_spritesheet = 0;
    // Second texture in the textures array
    let enemy_spritesheet = 1;

    initialize_player(&mut world, player_spritesheet);

    initialize_enemy(&mut world, enemy_spritesheet, Point::new(-150, -150));
    initialize_enemy(&mut world, enemy_spritesheet, Point::new(150, -190));
    initialize_enemy(&mut world, enemy_spritesheet, Point::new(-150, 170));

    let mut event_pump = sdl_context.event_pump()?;
    let mut i = 0;
    'running: loop {
        // None - no change, Some(MovementCommand) - perform movement
        let mut movement_command = None;
        // Handle events
        for event in event_pump.poll_iter() {
            match event {
                Event::Quit {..} |
                Event::KeyDown { keycode: Some(Keycode::Escape), .. } => {
                    break 'running;
                },
                Event::KeyDown { keycode: Some(Keycode::Left), repeat: false, .. } => {
                    movement_command = Some(MovementCommand::Move(Direction::Left));
                },
                Event::KeyDown { keycode: Some(Keycode::Right), repeat: false, .. } => {
                    movement_command = Some(MovementCommand::Move(Direction::Right));
                },
                Event::KeyDown { keycode: Some(Keycode::Up), repeat: false, .. } => {
                    movement_command = Some(MovementCommand::Move(Direction::Up));
                },
                Event::KeyDown { keycode: Some(Keycode::Down), repeat: false, .. } => {
                    movement_command = Some(MovementCommand::Move(Direction::Down));
                },
                Event::KeyUp { keycode: Some(Keycode::Left), repeat: false, .. } |
                Event::KeyUp { keycode: Some(Keycode::Right), repeat: false, .. } |
                Event::KeyUp { keycode: Some(Keycode::Up), repeat: false, .. } |
                Event::KeyUp { keycode: Some(Keycode::Down), repeat: false, .. } => {
                    movement_command = Some(MovementCommand::Stop);
                },
                _ => {}
            }
        }

        *world.write_resource() = movement_command;

        // Update
        i = (i + 1) % 255;
        dispatcher.dispatch(&mut world.res);
        world.maintain();

        // Render
        renderer::render(&mut canvas, Color::RGB(i, 64, 255 - i), &textures, world.system_data())?;

        // Time management!
        ::std::thread::sleep(Duration::new(0, 1_000_000_000u32 / 20));
    }

    Ok(())
}
use specs::prelude::*;

use crate::components::*;

pub struct Physics;

impl<'a> System<'a> for Physics {
    type SystemData = (WriteStorage<'a, Position>, ReadStorage<'a, Velocity>);

    fn run(&mut self, mut data: Self::SystemData) {
        use self::Direction::*;
        //TODO: This code can be made nicer and more idiomatic using more pattern matching.
        // Look up "rust irrefutable patterns" and use them here.
        for (pos, vel) in (&mut data.0, &data.1).join() {
            match vel.direction {
                Left => {
                    pos.0 = pos.0.offset(-vel.speed, 0);
                },
                Right => {
                    pos.0 = pos.0.offset(vel.speed, 0);
                },
                Up => {
                    pos.0 = pos.0.offset(0, -vel.speed);
                },
                Down => {
                    pos.0 = pos.0.offset(0, vel.speed);
                },
            }
        }
    }
}
use specs::prelude::*;
use sdl2::rect::{Point, Rect};
use sdl2::pixels::Color;
use sdl2::render::{WindowCanvas, Texture};

use crate::components::*;

// Type alias for the data needed by the renderer
pub type SystemData<'a> = (
    ReadStorage<'a, Position>,
    ReadStorage<'a, Sprite>,
);

pub fn render(
    canvas: &mut WindowCanvas,
    background: Color,
    textures: &[Texture],
    data: SystemData,
) -> Result<(), String> {
    canvas.set_draw_color(background);
    canvas.clear();

    let (width, height) = canvas.output_size()?;

    for (pos, sprite) in (&data.0, &data.1).join() {
        let current_frame = sprite.region;

        // Treat the center of the screen as the (0, 0) coordinate
        let screen_position = pos.0 + Point::new(width as i32 / 2, height as i32 / 2);
        let screen_rect = Rect::from_center(screen_position, current_frame.width(), current_frame.height());
        canvas.copy(&textures[sprite.spritesheet], current_frame, screen_rect)?;
    }

    canvas.present();

    Ok(())
}