• src/main.rs
  • src/physics.rs
+1 -16src/main.rs
1 1
mod components;
  2
mod physics;
2 3

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

11 12
use specs::prelude::*;
12 13

13 14
use std::time::Duration;
14 15

15 16
use crate::components::*;
16 17

17 18
const PLAYER_MOVEMENT_SPEED: i32 = 20;
18 19

19 20
/// Returns the row of the spritesheet corresponding to the given direction
20 21
fn direction_spritesheet_row(direction: Direction) -> i32 {
21 22
    use self::Direction::*;
22 23
    match direction {
23 24
        Up => 3,
24 25
        Down => 0,
25 26
        Left => 1,
26 27
        Right => 2,
27 28
    }
28 29
}
29 30

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

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

39 40
    let mut frames = Vec::new();
40 41
    for i in 0..3 {
41 42
        frames.push(Sprite {
42 43
            spritesheet,
43 44
            region: Rect::new(
44 45
                top_left_frame.x() + frame_width as i32 * i,
45 46
                y_offset,
46 47
                frame_width,
47 48
                frame_height,
48 49
            ),
49 50
        })
50 51
    }
51 52

52 53
    frames
53 54
}
54 55

55 56
fn render(
56 57
    canvas: &mut WindowCanvas,
57 58
    color: Color,
58 59
    texture: &Texture,
59 60
    player: &Player,
60 61
) -> Result<(), String> {
61 62
    canvas.set_draw_color(color);
62 63
    canvas.clear();
63 64

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

66 67
    let (frame_width, frame_height) = player.sprite.size();
67 68
    let current_frame = Rect::new(
68 69
        player.sprite.x() + frame_width as i32 * player.current_frame,
69 70
        player.sprite.y() + frame_height as  i32 * direction_spritesheet_row(player.direction),
70 71
        frame_width,
71 72
        frame_height,
72 73
    );
73 74

74 75
    // Treat the center of the screen as the (0, 0) coordinate
75 76
    let screen_position = player.position + Point::new(width as i32 / 2, height as i32 / 2);
76 77
    let screen_rect = Rect::from_center(screen_position, frame_width, frame_height);
77 78
    canvas.copy(texture, current_frame, screen_rect)?;
78 79

79 80
    canvas.present();
80 81

81 82
    Ok(())
82 83
}
83 84

84 85
// Update player a fixed amount based on their speed.
85 86
// WARNING: Calling this function too often or at a variable speed will cause the player's speed
86 87
// to be unpredictable!
87 88
fn update_player(player: &mut Player) {
88  
    use self::Direction::*;
89  
    match player.direction {
90  
        Left => {
91  
            player.position = player.position.offset(-player.speed, 0);
92  
        },
93  
        Right => {
94  
            player.position = player.position.offset(player.speed, 0);
95  
        },
96  
        Up => {
97  
            player.position = player.position.offset(0, -player.speed);
98  
        },
99  
        Down => {
100  
            player.position = player.position.offset(0, player.speed);
101  
        },
102  
    }
103  

104 89
    // Only continue to animate if the player is moving
105 90
    if player.speed != 0 {
106 91
        // Cheat: using the fact that all animations are 3 frames (NOT extensible)
107 92
        player.current_frame = (player.current_frame + 1) % 3;
108 93
    }
109 94
}
110 95

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

119 104
    let window = video_subsystem.window("game tutorial", 800, 600)
120 105
        .position_centered()
121 106
        .build()
122 107
        .expect("could not initialize video subsystem");
123 108

124 109
    let mut canvas = window.into_canvas().build()
125 110
        .expect("could not make a canvas");
126 111

127 112
    let texture_creator = canvas.texture_creator();
128 113

129 114
    let textures = [
130 115
        texture_creator.load_texture("assets/bardo.png")?,
131 116
    ];
132 117
    // First texture in textures array
133 118
    let player_spritesheet = 0;
134 119
    let player_top_left_frame = Rect::new(0, 0, 26, 36);
135 120

136 121
    let player_animation = MovementAnimation {
137 122
        current_frame: 0,
138 123
        up_frames: character_animation_frames(player_spritesheet, player_top_left_frame, Direction::Up),
139 124
        down_frames: character_animation_frames(player_spritesheet, player_top_left_frame, Direction::Down),
140 125
        left_frames: character_animation_frames(player_spritesheet, player_top_left_frame, Direction::Left),
141 126
        right_frames: character_animation_frames(player_spritesheet, player_top_left_frame, Direction::Right),
142 127
    };
143 128

144 129
    let mut world = World::new();
145 130

146 131
    world.create_entity()
147 132
        .with(Position(Point::new(0, 0)))
148 133
        .with(Velocity {speed: 0, direction: Direction::Right})
149 134
        .with(player_animation.right_frames[0].clone())
150 135
        .with(player_animation)
151 136
        .build();
152 137

153 138
    let mut event_pump = sdl_context.event_pump()?;
154 139
    let mut i = 0;
155 140
    'running: loop {
156 141
        // Handle events
157 142
        for event in event_pump.poll_iter() {
158 143
            match event {
159 144
                Event::Quit {..} |
160 145
                Event::KeyDown { keycode: Some(Keycode::Escape), .. } => {
161 146
                    break 'running;
162 147
                },
163 148
                Event::KeyDown { keycode: Some(Keycode::Left), repeat: false, .. } => {
164 149
                    player.speed = PLAYER_MOVEMENT_SPEED;
165 150
                    player.direction = Direction::Left;
166 151
                },
167 152
                Event::KeyDown { keycode: Some(Keycode::Right), repeat: false, .. } => {
168 153
                    player.speed = PLAYER_MOVEMENT_SPEED;
169 154
                    player.direction = Direction::Right;
170 155
                },
171 156
                Event::KeyDown { keycode: Some(Keycode::Up), repeat: false, .. } => {
172 157
                    player.speed = PLAYER_MOVEMENT_SPEED;
173 158
                    player.direction = Direction::Up;
174 159
                },
175 160
                Event::KeyDown { keycode: Some(Keycode::Down), repeat: false, .. } => {
176 161
                    player.speed = PLAYER_MOVEMENT_SPEED;
177 162
                    player.direction = Direction::Down;
178 163
                },
179 164
                Event::KeyUp { keycode: Some(Keycode::Left), repeat: false, .. } |
180 165
                Event::KeyUp { keycode: Some(Keycode::Right), repeat: false, .. } |
181 166
                Event::KeyUp { keycode: Some(Keycode::Up), repeat: false, .. } |
182 167
                Event::KeyUp { keycode: Some(Keycode::Down), repeat: false, .. } => {
183 168
                    player.speed = 0;
184 169
                },
185 170
                _ => {}
186 171
            }
187 172
        }
188 173

189 174
        // Update
190 175
        i = (i + 1) % 255;
191 176
        update_player(&mut player);
192 177

193 178
        // Render
194 179
        render(&mut canvas, Color::RGB(i, 64, 255 - i), &texture, &player)?;
195 180

196 181
        // Time management!
197 182
        ::std::thread::sleep(Duration::new(0, 1_000_000_000u32 / 20));
198 183
    }
199 184

200 185
    Ok(())
201 186
}
+31 -0src/physics.rs
  1
use specs::prelude::*;
  2

  3
use crate::components::*;
  4

  5
struct Physics;
  6

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

  10
    fn run(&mut self, mut data: Self::SystemData) {
  11
        use self::Direction::*;
  12
        //TODO: This code can be made nicer and more idiomatic using more pattern matching.
  13
        // Look up "rust irrefutable patterns" and use them here.
  14
        for (pos, vel) in (&mut data.0, &data.1).join() {
  15
            match vel.direction {
  16
                Left => {
  17
                    pos.0 = pos.0.offset(-vel.speed, 0);
  18
                },
  19
                Right => {
  20
                    pos.0 = pos.0.offset(vel.speed, 0);
  21
                },
  22
                Up => {
  23
                    pos.0 = pos.0.offset(0, -vel.speed);
  24
                },
  25
                Down => {
  26
                    pos.0 = pos.0.offset(0, vel.speed);
  27
                },
  28
            }
  29
        }
  30
    }
  31
}
+1 -16src/main.rs
1
mod components;
1
mod components;
    2
mod physics;
2

3

3
use sdl2::pixels::Color;
4
use sdl2::pixels::Color;
4
use sdl2::event::Event;
5
use sdl2::event::Event;
5
use sdl2::keyboard::Keycode;
6
use sdl2::keyboard::Keycode;
6
use sdl2::render::{WindowCanvas, Texture};
7
use sdl2::render::{WindowCanvas, Texture};
7
use sdl2::rect::{Point, Rect};
8
use sdl2::rect::{Point, Rect};
8
// "self" imports the "image" module itself as well as everything else we listed
9
// "self" imports the "image" module itself as well as everything else we listed
9
use sdl2::image::{self, LoadTexture, InitFlag};
10
use sdl2::image::{self, LoadTexture, InitFlag};
10

11

11
use specs::prelude::*;
12
use specs::prelude::*;
12

13

13
use std::time::Duration;
14
use std::time::Duration;
14

15

15
use crate::components::*;
16
use crate::components::*;
16

17

17
const PLAYER_MOVEMENT_SPEED: i32 = 20;
18
const PLAYER_MOVEMENT_SPEED: i32 = 20;
18

19

19
/// Returns the row of the spritesheet corresponding to the given direction
20
/// Returns the row of the spritesheet corresponding to the given direction
20
fn direction_spritesheet_row(direction: Direction) -> i32 {
21
fn direction_spritesheet_row(direction: Direction) -> i32 {
21
    use self::Direction::*;
22
    use self::Direction::*;
22
    match direction {
23
    match direction {
23
        Up => 3,
24
        Up => 3,
24
        Down => 0,
25
        Down => 0,
25
        Left => 1,
26
        Left => 1,
26
        Right => 2,
27
        Right => 2,
27
    }
28
    }
28
}
29
}
29

30

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

36

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

39

39
    let mut frames = Vec::new();
40
    let mut frames = Vec::new();
40
    for i in 0..3 {
41
    for i in 0..3 {
41
        frames.push(Sprite {
42
        frames.push(Sprite {
42
            spritesheet,
43
            spritesheet,
43
            region: Rect::new(
44
            region: Rect::new(
44
                top_left_frame.x() + frame_width as i32 * i,
45
                top_left_frame.x() + frame_width as i32 * i,
45
                y_offset,
46
                y_offset,
46
                frame_width,
47
                frame_width,
47
                frame_height,
48
                frame_height,
48
            ),
49
            ),
49
        })
50
        })
50
    }
51
    }
51

52

52
    frames
53
    frames
53
}
54
}
54

55

55
fn render(
56
fn render(
56
    canvas: &mut WindowCanvas,
57
    canvas: &mut WindowCanvas,
57
    color: Color,
58
    color: Color,
58
    texture: &Texture,
59
    texture: &Texture,
59
    player: &Player,
60
    player: &Player,
60
) -> Result<(), String> {
61
) -> Result<(), String> {
61
    canvas.set_draw_color(color);
62
    canvas.set_draw_color(color);
62
    canvas.clear();
63
    canvas.clear();
63

64

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

66

66
    let (frame_width, frame_height) = player.sprite.size();
67
    let (frame_width, frame_height) = player.sprite.size();
67
    let current_frame = Rect::new(
68
    let current_frame = Rect::new(
68
        player.sprite.x() + frame_width as i32 * player.current_frame,
69
        player.sprite.x() + frame_width as i32 * player.current_frame,
69
        player.sprite.y() + frame_height as  i32 * direction_spritesheet_row(player.direction),
70
        player.sprite.y() + frame_height as  i32 * direction_spritesheet_row(player.direction),
70
        frame_width,
71
        frame_width,
71
        frame_height,
72
        frame_height,
72
    );
73
    );
73

74

74
    // Treat the center of the screen as the (0, 0) coordinate
75
    // Treat the center of the screen as the (0, 0) coordinate
75
    let screen_position = player.position + Point::new(width as i32 / 2, height as i32 / 2);
76
    let screen_position = player.position + Point::new(width as i32 / 2, height as i32 / 2);
76
    let screen_rect = Rect::from_center(screen_position, frame_width, frame_height);
77
    let screen_rect = Rect::from_center(screen_position, frame_width, frame_height);
77
    canvas.copy(texture, current_frame, screen_rect)?;
78
    canvas.copy(texture, current_frame, screen_rect)?;
78

79

79
    canvas.present();
80
    canvas.present();
80

81

81
    Ok(())
82
    Ok(())
82
}
83
}
83

84

84
// Update player a fixed amount based on their speed.
85
// Update player a fixed amount based on their speed.
85
// WARNING: Calling this function too often or at a variable speed will cause the player's speed
86
// WARNING: Calling this function too often or at a variable speed will cause the player's speed
86
// to be unpredictable!
87
// to be unpredictable!
87
fn update_player(player: &mut Player) {
88
fn update_player(player: &mut Player) {
88
    use self::Direction::*;
   
89
    match player.direction {
   
90
        Left => {
   
91
            player.position = player.position.offset(-player.speed, 0);
   
92
        },
   
93
        Right => {
   
94
            player.position = player.position.offset(player.speed, 0);
   
95
        },
   
96
        Up => {
   
97
            player.position = player.position.offset(0, -player.speed);
   
98
        },
   
99
        Down => {
   
100
            player.position = player.position.offset(0, player.speed);
   
101
        },
   
102
    }
   
103

   
104
    // Only continue to animate if the player is moving
89
    // Only continue to animate if the player is moving
105
    if player.speed != 0 {
90
    if player.speed != 0 {
106
        // Cheat: using the fact that all animations are 3 frames (NOT extensible)
91
        // Cheat: using the fact that all animations are 3 frames (NOT extensible)
107
        player.current_frame = (player.current_frame + 1) % 3;
92
        player.current_frame = (player.current_frame + 1) % 3;
108
    }
93
    }
109
}
94
}
110

95

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

103

119
    let window = video_subsystem.window("game tutorial", 800, 600)
104
    let window = video_subsystem.window("game tutorial", 800, 600)
120
        .position_centered()
105
        .position_centered()
121
        .build()
106
        .build()
122
        .expect("could not initialize video subsystem");
107
        .expect("could not initialize video subsystem");
123

108

124
    let mut canvas = window.into_canvas().build()
109
    let mut canvas = window.into_canvas().build()
125
        .expect("could not make a canvas");
110
        .expect("could not make a canvas");
126

111

127
    let texture_creator = canvas.texture_creator();
112
    let texture_creator = canvas.texture_creator();
128

113

129
    let textures = [
114
    let textures = [
130
        texture_creator.load_texture("assets/bardo.png")?,
115
        texture_creator.load_texture("assets/bardo.png")?,
131
    ];
116
    ];
132
    // First texture in textures array
117
    // First texture in textures array
133
    let player_spritesheet = 0;
118
    let player_spritesheet = 0;
134
    let player_top_left_frame = Rect::new(0, 0, 26, 36);
119
    let player_top_left_frame = Rect::new(0, 0, 26, 36);
135

120

136
    let player_animation = MovementAnimation {
121
    let player_animation = MovementAnimation {
137
        current_frame: 0,
122
        current_frame: 0,
138
        up_frames: character_animation_frames(player_spritesheet, player_top_left_frame, Direction::Up),
123
        up_frames: character_animation_frames(player_spritesheet, player_top_left_frame, Direction::Up),
139
        down_frames: character_animation_frames(player_spritesheet, player_top_left_frame, Direction::Down),
124
        down_frames: character_animation_frames(player_spritesheet, player_top_left_frame, Direction::Down),
140
        left_frames: character_animation_frames(player_spritesheet, player_top_left_frame, Direction::Left),
125
        left_frames: character_animation_frames(player_spritesheet, player_top_left_frame, Direction::Left),
141
        right_frames: character_animation_frames(player_spritesheet, player_top_left_frame, Direction::Right),
126
        right_frames: character_animation_frames(player_spritesheet, player_top_left_frame, Direction::Right),
142
    };
127
    };
143

128

144
    let mut world = World::new();
129
    let mut world = World::new();
145

130

146
    world.create_entity()
131
    world.create_entity()
147
        .with(Position(Point::new(0, 0)))
132
        .with(Position(Point::new(0, 0)))
148
        .with(Velocity {speed: 0, direction: Direction::Right})
133
        .with(Velocity {speed: 0, direction: Direction::Right})
149
        .with(player_animation.right_frames[0].clone())
134
        .with(player_animation.right_frames[0].clone())
150
        .with(player_animation)
135
        .with(player_animation)
151
        .build();
136
        .build();
152

137

153
    let mut event_pump = sdl_context.event_pump()?;
138
    let mut event_pump = sdl_context.event_pump()?;
154
    let mut i = 0;
139
    let mut i = 0;
155
    'running: loop {
140
    'running: loop {
156
        // Handle events
141
        // Handle events
157
        for event in event_pump.poll_iter() {
142
        for event in event_pump.poll_iter() {
158
            match event {
143
            match event {
159
                Event::Quit {..} |
144
                Event::Quit {..} |
160
                Event::KeyDown { keycode: Some(Keycode::Escape), .. } => {
145
                Event::KeyDown { keycode: Some(Keycode::Escape), .. } => {
161
                    break 'running;
146
                    break 'running;
162
                },
147
                },
163
                Event::KeyDown { keycode: Some(Keycode::Left), repeat: false, .. } => {
148
                Event::KeyDown { keycode: Some(Keycode::Left), repeat: false, .. } => {
164
                    player.speed = PLAYER_MOVEMENT_SPEED;
149
                    player.speed = PLAYER_MOVEMENT_SPEED;
165
                    player.direction = Direction::Left;
150
                    player.direction = Direction::Left;
166
                },
151
                },
167
                Event::KeyDown { keycode: Some(Keycode::Right), repeat: false, .. } => {
152
                Event::KeyDown { keycode: Some(Keycode::Right), repeat: false, .. } => {
168
                    player.speed = PLAYER_MOVEMENT_SPEED;
153
                    player.speed = PLAYER_MOVEMENT_SPEED;
169
                    player.direction = Direction::Right;
154
                    player.direction = Direction::Right;
170
                },
155
                },
171
                Event::KeyDown { keycode: Some(Keycode::Up), repeat: false, .. } => {
156
                Event::KeyDown { keycode: Some(Keycode::Up), repeat: false, .. } => {
172
                    player.speed = PLAYER_MOVEMENT_SPEED;
157
                    player.speed = PLAYER_MOVEMENT_SPEED;
173
                    player.direction = Direction::Up;
158
                    player.direction = Direction::Up;
174
                },
159
                },
175
                Event::KeyDown { keycode: Some(Keycode::Down), repeat: false, .. } => {
160
                Event::KeyDown { keycode: Some(Keycode::Down), repeat: false, .. } => {
176
                    player.speed = PLAYER_MOVEMENT_SPEED;
161
                    player.speed = PLAYER_MOVEMENT_SPEED;
177
                    player.direction = Direction::Down;
162
                    player.direction = Direction::Down;
178
                },
163
                },
179
                Event::KeyUp { keycode: Some(Keycode::Left), repeat: false, .. } |
164
                Event::KeyUp { keycode: Some(Keycode::Left), repeat: false, .. } |
180
                Event::KeyUp { keycode: Some(Keycode::Right), repeat: false, .. } |
165
                Event::KeyUp { keycode: Some(Keycode::Right), repeat: false, .. } |
181
                Event::KeyUp { keycode: Some(Keycode::Up), repeat: false, .. } |
166
                Event::KeyUp { keycode: Some(Keycode::Up), repeat: false, .. } |
182
                Event::KeyUp { keycode: Some(Keycode::Down), repeat: false, .. } => {
167
                Event::KeyUp { keycode: Some(Keycode::Down), repeat: false, .. } => {
183
                    player.speed = 0;
168
                    player.speed = 0;
184
                },
169
                },
185
                _ => {}
170
                _ => {}
186
            }
171
            }
187
        }
172
        }
188

173

189
        // Update
174
        // Update
190
        i = (i + 1) % 255;
175
        i = (i + 1) % 255;
191
        update_player(&mut player);
176
        update_player(&mut player);
192

177

193
        // Render
178
        // Render
194
        render(&mut canvas, Color::RGB(i, 64, 255 - i), &texture, &player)?;
179
        render(&mut canvas, Color::RGB(i, 64, 255 - i), &texture, &player)?;
195

180

196
        // Time management!
181
        // Time management!
197
        ::std::thread::sleep(Duration::new(0, 1_000_000_000u32 / 20));
182
        ::std::thread::sleep(Duration::new(0, 1_000_000_000u32 / 20));
198
    }
183
    }
199

184

200
    Ok(())
185
    Ok(())
201
}
186
}
+31 -0src/physics.rs
    1
use specs::prelude::*;
    2

    3
use crate::components::*;
    4

    5
struct Physics;
    6

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

    10
    fn run(&mut self, mut data: Self::SystemData) {
    11
        use self::Direction::*;
    12
        //TODO: This code can be made nicer and more idiomatic using more pattern matching.
    13
        // Look up "rust irrefutable patterns" and use them here.
    14
        for (pos, vel) in (&mut data.0, &data.1).join() {
    15
            match vel.direction {
    16
                Left => {
    17
                    pos.0 = pos.0.offset(-vel.speed, 0);
    18
                },
    19
                Right => {
    20
                    pos.0 = pos.0.offset(vel.speed, 0);
    21
                },
    22
                Up => {
    23
                    pos.0 = pos.0.offset(0, -vel.speed);
    24
                },
    25
                Down => {
    26
                    pos.0 = pos.0.offset(0, vel.speed);
    27
                },
    28
            }
    29
        }
    30
    }
    31
}
/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"

[dependencies.sdl2]
version = "0.32.1"
default-features = false
features = ["image"]
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,
}

/// 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>,
}
mod components;
mod physics;

use sdl2::pixels::Color;
use sdl2::event::Event;
use sdl2::keyboard::Keycode;
use sdl2::render::{WindowCanvas, Texture};
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::*;

const PLAYER_MOVEMENT_SPEED: i32 = 20;

/// 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 render(
    canvas: &mut WindowCanvas,
    color: Color,
    texture: &Texture,
    player: &Player,
) -> Result<(), String> {
    canvas.set_draw_color(color);
    canvas.clear();

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

    let (frame_width, frame_height) = player.sprite.size();
    let current_frame = Rect::new(
        player.sprite.x() + frame_width as i32 * player.current_frame,
        player.sprite.y() + frame_height as  i32 * direction_spritesheet_row(player.direction),
        frame_width,
        frame_height,
    );

    // Treat the center of the screen as the (0, 0) coordinate
    let screen_position = player.position + Point::new(width as i32 / 2, height as i32 / 2);
    let screen_rect = Rect::from_center(screen_position, frame_width, frame_height);
    canvas.copy(texture, current_frame, screen_rect)?;

    canvas.present();

    Ok(())
}

// Update player a fixed amount based on their speed.
// WARNING: Calling this function too often or at a variable speed will cause the player's speed
// to be unpredictable!
fn update_player(player: &mut Player) {
    // Only continue to animate if the player is moving
    if player.speed != 0 {
        // Cheat: using the fact that all animations are 3 frames (NOT extensible)
        player.current_frame = (player.current_frame + 1) % 3;
    }
}

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 textures = [
        texture_creator.load_texture("assets/bardo.png")?,
    ];
    // First texture in textures array
    let player_spritesheet = 0;
    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),
    };

    let mut world = World::new();

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

    let mut event_pump = sdl_context.event_pump()?;
    let mut i = 0;
    'running: loop {
        // 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, .. } => {
                    player.speed = PLAYER_MOVEMENT_SPEED;
                    player.direction = Direction::Left;
                },
                Event::KeyDown { keycode: Some(Keycode::Right), repeat: false, .. } => {
                    player.speed = PLAYER_MOVEMENT_SPEED;
                    player.direction = Direction::Right;
                },
                Event::KeyDown { keycode: Some(Keycode::Up), repeat: false, .. } => {
                    player.speed = PLAYER_MOVEMENT_SPEED;
                    player.direction = Direction::Up;
                },
                Event::KeyDown { keycode: Some(Keycode::Down), repeat: false, .. } => {
                    player.speed = PLAYER_MOVEMENT_SPEED;
                    player.direction = 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, .. } => {
                    player.speed = 0;
                },
                _ => {}
            }
        }

        // Update
        i = (i + 1) % 255;
        update_player(&mut player);

        // Render
        render(&mut canvas, Color::RGB(i, 64, 255 - i), &texture, &player)?;

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

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

use crate::components::*;

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);
                },
            }
        }
    }
}