• src/keyboard.rs
  • src/main.rs
  • src/components.rs
+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
#[derive(Component, Debug, Default)]
  14
#[storage(NullStorage)]
  15
pub struct KeyboardControlled;
  16

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

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

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

35 39
#[derive(Component, Debug)]
36 40
#[storage(VecStorage)]
37 41
pub struct MovementAnimation {
38 42
    // The current frame in the animation of the direction this entity is moving in
39 43
    pub current_frame: usize,
40 44
    pub up_frames: Vec<Sprite>,
41 45
    pub down_frames: Vec<Sprite>,
42 46
    pub left_frames: Vec<Sprite>,
43 47
    pub right_frames: Vec<Sprite>,
44 48
}
+36 -0src/keyboard.rs
  1
use specs::prelude::*;
  2

  3
use crate::components::*;
  4

  5
use super::MovementCommand;
  6

  7
const PLAYER_MOVEMENT_SPEED: i32 = 20;
  8

  9
pub struct Keyboard;
  10

  11
impl<'a> System<'a> for Keyboard {
  12
    type SystemData = (
  13
        ReadExpect<'a, Option<MovementCommand>>,
  14
        ReadStorage<'a, KeyboardControlled>,
  15
        WriteStorage<'a, Velocity>,
  16
    );
  17

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

  26
        for (_, vel) in (&data.1, &mut data.2).join() {
  27
            match movement_command {
  28
                &MovementCommand::Move(direction) => {
  29
                    vel.speed = PLAYER_MOVEMENT_SPEED;
  30
                    vel.direction = direction;
  31
                },
  32
                MovementCommand::Stop => vel.speed = 0,
  33
            }
  34
        }
  35
    }
  36
}
+22 -12src/main.rs
1 1
mod components;
2 2
mod physics;
3 3
mod animator;
  4
mod keyboard;
4 5

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

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

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

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

19  
const PLAYER_MOVEMENT_SPEED: i32 = 20;
  20
pub enum MovementCommand {
  21
    Stop,
  22
    Move(Direction),
  23
}
20 24

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

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

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

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

54 58
    frames
55 59
}
56 60

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

66 70
    let (width, height) = canvas.output_size()?;
67 71

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

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

81 85
    canvas.present();
82 86

83 87
    Ok(())
84 88
}
85 89

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

94 98
    let window = video_subsystem.window("game tutorial", 800, 600)
95 99
        .position_centered()
96 100
        .build()
97 101
        .expect("could not initialize video subsystem");
98 102

99 103
    let mut canvas = window.into_canvas().build()
100 104
        .expect("could not make a canvas");
101 105
    let texture_creator = canvas.texture_creator();
102 106

103 107
    let mut dispatcher = DispatcherBuilder::new()
104  
        .with(physics::Physics, "Physics", &[])
105  
        .with(animator::Animator, "Animator", &[])
  108
        .with(keyboard::Keyboard, "Keyboard", &[])
  109
        .with(physics::Physics, "Physics", &["Keyboard"])
  110
        .with(animator::Animator, "Animator", &["Keyboard"])
106 111
        .build();
107 112

108 113
    let mut world = World::new();
109 114
    dispatcher.setup(&mut world.res);
110 115

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

111 120
    let textures = [
112 121
        texture_creator.load_texture("assets/bardo.png")?,
113 122
    ];
114 123
    // First texture in textures array
115 124
    let player_spritesheet = 0;
116 125
    let player_top_left_frame = Rect::new(0, 0, 26, 36);
117 126

118 127
    let player_animation = MovementAnimation {
119 128
        current_frame: 0,
120 129
        up_frames: character_animation_frames(player_spritesheet, player_top_left_frame, Direction::Up),
121 130
        down_frames: character_animation_frames(player_spritesheet, player_top_left_frame, Direction::Down),
122 131
        left_frames: character_animation_frames(player_spritesheet, player_top_left_frame, Direction::Left),
123 132
        right_frames: character_animation_frames(player_spritesheet, player_top_left_frame, Direction::Right),
124 133
    };
125 134

126 135
    world.create_entity()
  136
        .with(KeyboardControlled)
127 137
        .with(Position(Point::new(0, 0)))
128 138
        .with(Velocity {speed: 0, direction: Direction::Right})
129 139
        .with(player_animation.right_frames[0].clone())
130 140
        .with(player_animation)
131 141
        .build();
132 142

133 143
    let mut event_pump = sdl_context.event_pump()?;
134 144
    let mut i = 0;
135 145
    'running: loop {
  146
        // None - no change, Some(MovementCommand) - perform movement
  147
        let mut movement_command = None;
136 148
        // Handle events
137 149
        for event in event_pump.poll_iter() {
138 150
            match event {
139 151
                Event::Quit {..} |
140 152
                Event::KeyDown { keycode: Some(Keycode::Escape), .. } => {
141 153
                    break 'running;
142 154
                },
143 155
                Event::KeyDown { keycode: Some(Keycode::Left), repeat: false, .. } => {
144  
                    player.speed = PLAYER_MOVEMENT_SPEED;
145  
                    player.direction = Direction::Left;
  156
                    movement_command = Some(MovementCommand::Move(Direction::Left));
146 157
                },
147 158
                Event::KeyDown { keycode: Some(Keycode::Right), repeat: false, .. } => {
148  
                    player.speed = PLAYER_MOVEMENT_SPEED;
149  
                    player.direction = Direction::Right;
  159
                    movement_command = Some(MovementCommand::Move(Direction::Right));
150 160
                },
151 161
                Event::KeyDown { keycode: Some(Keycode::Up), repeat: false, .. } => {
152  
                    player.speed = PLAYER_MOVEMENT_SPEED;
153  
                    player.direction = Direction::Up;
  162
                    movement_command = Some(MovementCommand::Move(Direction::Up));
154 163
                },
155 164
                Event::KeyDown { keycode: Some(Keycode::Down), repeat: false, .. } => {
156  
                    player.speed = PLAYER_MOVEMENT_SPEED;
157  
                    player.direction = Direction::Down;
  165
                    movement_command = Some(MovementCommand::Move(Direction::Down));
158 166
                },
159 167
                Event::KeyUp { keycode: Some(Keycode::Left), repeat: false, .. } |
160 168
                Event::KeyUp { keycode: Some(Keycode::Right), repeat: false, .. } |
161 169
                Event::KeyUp { keycode: Some(Keycode::Up), repeat: false, .. } |
162 170
                Event::KeyUp { keycode: Some(Keycode::Down), repeat: false, .. } => {
163  
                    player.speed = 0;
  171
                    movement_command = Some(MovementCommand::Stop);
164 172
                },
165 173
                _ => {}
166 174
            }
167 175
        }
168 176

  177
        *world.write_resource() = movement_command;
  178

169 179
        // Update
170 180
        i = (i + 1) % 255;
171 181
        dispatcher.dispatch(&mut world.res);
172 182
        world.maintain();
173 183

174 184
        // Render
175 185
        render(&mut canvas, Color::RGB(i, 64, 255 - i), &texture, &player)?;
176 186

177 187
        // Time management!
178 188
        ::std::thread::sleep(Duration::new(0, 1_000_000_000u32 / 20));
179 189
    }
180 190

181 191
    Ok(())
182 192
}
+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)]
    14
#[storage(NullStorage)]
    15
pub struct KeyboardControlled;
    16

13
/// The current position of a given entity
17
/// The current position of a given entity
14
#[derive(Component, Debug)]
18
#[derive(Component, Debug)]
15
#[storage(VecStorage)]
19
#[storage(VecStorage)]
16
pub struct Position(pub Point);
20
pub struct Position(pub Point);
17

21

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

29

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

38

35
#[derive(Component, Debug)]
39
#[derive(Component, Debug)]
36
#[storage(VecStorage)]
40
#[storage(VecStorage)]
37
pub struct MovementAnimation {
41
pub struct MovementAnimation {
38
    // The current frame in the animation of the direction this entity is moving in
42
    // The current frame in the animation of the direction this entity is moving in
39
    pub current_frame: usize,
43
    pub current_frame: usize,
40
    pub up_frames: Vec<Sprite>,
44
    pub up_frames: Vec<Sprite>,
41
    pub down_frames: Vec<Sprite>,
45
    pub down_frames: Vec<Sprite>,
42
    pub left_frames: Vec<Sprite>,
46
    pub left_frames: Vec<Sprite>,
43
    pub right_frames: Vec<Sprite>,
47
    pub right_frames: Vec<Sprite>,
44
}
48
}
+36 -0src/keyboard.rs
    1
use specs::prelude::*;
    2

    3
use crate::components::*;
    4

    5
use super::MovementCommand;
    6

    7
const PLAYER_MOVEMENT_SPEED: i32 = 20;
    8

    9
pub struct Keyboard;
    10

    11
impl<'a> System<'a> for Keyboard {
    12
    type SystemData = (
    13
        ReadExpect<'a, Option<MovementCommand>>,
    14
        ReadStorage<'a, KeyboardControlled>,
    15
        WriteStorage<'a, Velocity>,
    16
    );
    17

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

    26
        for (_, vel) in (&data.1, &mut data.2).join() {
    27
            match movement_command {
    28
                &MovementCommand::Move(direction) => {
    29
                    vel.speed = PLAYER_MOVEMENT_SPEED;
    30
                    vel.direction = direction;
    31
                },
    32
                MovementCommand::Stop => vel.speed = 0,
    33
            }
    34
        }
    35
    }
    36
}
+22 -12src/main.rs
1
mod components;
1
mod components;
2
mod physics;
2
mod physics;
3
mod animator;
3
mod animator;
    4
mod keyboard;
4

5

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

13

13
use specs::prelude::*;
14
use specs::prelude::*;
14

15

15
use std::time::Duration;
16
use std::time::Duration;
16

17

17
use crate::components::*;
18
use crate::components::*;
18

19

19
const PLAYER_MOVEMENT_SPEED: i32 = 20;
20
pub enum MovementCommand {
    21
    Stop,
    22
    Move(Direction),
    23
}
20

24

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

35

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

41

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

44

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

57

54
    frames
58
    frames
55
}
59
}
56

60

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

69

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

71

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

79

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

84

81
    canvas.present();
85
    canvas.present();
82

86

83
    Ok(())
87
    Ok(())
84
}
88
}
85

89

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

97

94
    let window = video_subsystem.window("game tutorial", 800, 600)
98
    let window = video_subsystem.window("game tutorial", 800, 600)
95
        .position_centered()
99
        .position_centered()
96
        .build()
100
        .build()
97
        .expect("could not initialize video subsystem");
101
        .expect("could not initialize video subsystem");
98

102

99
    let mut canvas = window.into_canvas().build()
103
    let mut canvas = window.into_canvas().build()
100
        .expect("could not make a canvas");
104
        .expect("could not make a canvas");
101
    let texture_creator = canvas.texture_creator();
105
    let texture_creator = canvas.texture_creator();
102

106

103
    let mut dispatcher = DispatcherBuilder::new()
107
    let mut dispatcher = DispatcherBuilder::new()
104
        .with(physics::Physics, "Physics", &[])
108
        .with(keyboard::Keyboard, "Keyboard", &[])
105
        .with(animator::Animator, "Animator", &[])
109
        .with(physics::Physics, "Physics", &["Keyboard"])
    110
        .with(animator::Animator, "Animator", &["Keyboard"])
106
        .build();
111
        .build();
107

112

108
    let mut world = World::new();
113
    let mut world = World::new();
109
    dispatcher.setup(&mut world.res);
114
    dispatcher.setup(&mut world.res);
110

115

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

111
    let textures = [
120
    let textures = [
112
        texture_creator.load_texture("assets/bardo.png")?,
121
        texture_creator.load_texture("assets/bardo.png")?,
113
    ];
122
    ];
114
    // First texture in textures array
123
    // First texture in textures array
115
    let player_spritesheet = 0;
124
    let player_spritesheet = 0;
116
    let player_top_left_frame = Rect::new(0, 0, 26, 36);
125
    let player_top_left_frame = Rect::new(0, 0, 26, 36);
117

126

118
    let player_animation = MovementAnimation {
127
    let player_animation = MovementAnimation {
119
        current_frame: 0,
128
        current_frame: 0,
120
        up_frames: character_animation_frames(player_spritesheet, player_top_left_frame, Direction::Up),
129
        up_frames: character_animation_frames(player_spritesheet, player_top_left_frame, Direction::Up),
121
        down_frames: character_animation_frames(player_spritesheet, player_top_left_frame, Direction::Down),
130
        down_frames: character_animation_frames(player_spritesheet, player_top_left_frame, Direction::Down),
122
        left_frames: character_animation_frames(player_spritesheet, player_top_left_frame, Direction::Left),
131
        left_frames: character_animation_frames(player_spritesheet, player_top_left_frame, Direction::Left),
123
        right_frames: character_animation_frames(player_spritesheet, player_top_left_frame, Direction::Right),
132
        right_frames: character_animation_frames(player_spritesheet, player_top_left_frame, Direction::Right),
124
    };
133
    };
125

134

126
    world.create_entity()
135
    world.create_entity()
    136
        .with(KeyboardControlled)
127
        .with(Position(Point::new(0, 0)))
137
        .with(Position(Point::new(0, 0)))
128
        .with(Velocity {speed: 0, direction: Direction::Right})
138
        .with(Velocity {speed: 0, direction: Direction::Right})
129
        .with(player_animation.right_frames[0].clone())
139
        .with(player_animation.right_frames[0].clone())
130
        .with(player_animation)
140
        .with(player_animation)
131
        .build();
141
        .build();
132

142

133
    let mut event_pump = sdl_context.event_pump()?;
143
    let mut event_pump = sdl_context.event_pump()?;
134
    let mut i = 0;
144
    let mut i = 0;
135
    'running: loop {
145
    'running: loop {
    146
        // None - no change, Some(MovementCommand) - perform movement
    147
        let mut movement_command = None;
136
        // Handle events
148
        // Handle events
137
        for event in event_pump.poll_iter() {
149
        for event in event_pump.poll_iter() {
138
            match event {
150
            match event {
139
                Event::Quit {..} |
151
                Event::Quit {..} |
140
                Event::KeyDown { keycode: Some(Keycode::Escape), .. } => {
152
                Event::KeyDown { keycode: Some(Keycode::Escape), .. } => {
141
                    break 'running;
153
                    break 'running;
142
                },
154
                },
143
                Event::KeyDown { keycode: Some(Keycode::Left), repeat: false, .. } => {
155
                Event::KeyDown { keycode: Some(Keycode::Left), repeat: false, .. } => {
144
                    player.speed = PLAYER_MOVEMENT_SPEED;
156
                    movement_command = Some(MovementCommand::Move(Direction::Left));
145
                    player.direction = Direction::Left;
   
146
                },
157
                },
147
                Event::KeyDown { keycode: Some(Keycode::Right), repeat: false, .. } => {
158
                Event::KeyDown { keycode: Some(Keycode::Right), repeat: false, .. } => {
148
                    player.speed = PLAYER_MOVEMENT_SPEED;
159
                    movement_command = Some(MovementCommand::Move(Direction::Right));
149
                    player.direction = Direction::Right;
   
150
                },
160
                },
151
                Event::KeyDown { keycode: Some(Keycode::Up), repeat: false, .. } => {
161
                Event::KeyDown { keycode: Some(Keycode::Up), repeat: false, .. } => {
152
                    player.speed = PLAYER_MOVEMENT_SPEED;
162
                    movement_command = Some(MovementCommand::Move(Direction::Up));
153
                    player.direction = Direction::Up;
   
154
                },
163
                },
155
                Event::KeyDown { keycode: Some(Keycode::Down), repeat: false, .. } => {
164
                Event::KeyDown { keycode: Some(Keycode::Down), repeat: false, .. } => {
156
                    player.speed = PLAYER_MOVEMENT_SPEED;
165
                    movement_command = Some(MovementCommand::Move(Direction::Down));
157
                    player.direction = Direction::Down;
   
158
                },
166
                },
159
                Event::KeyUp { keycode: Some(Keycode::Left), repeat: false, .. } |
167
                Event::KeyUp { keycode: Some(Keycode::Left), repeat: false, .. } |
160
                Event::KeyUp { keycode: Some(Keycode::Right), repeat: false, .. } |
168
                Event::KeyUp { keycode: Some(Keycode::Right), repeat: false, .. } |
161
                Event::KeyUp { keycode: Some(Keycode::Up), repeat: false, .. } |
169
                Event::KeyUp { keycode: Some(Keycode::Up), repeat: false, .. } |
162
                Event::KeyUp { keycode: Some(Keycode::Down), repeat: false, .. } => {
170
                Event::KeyUp { keycode: Some(Keycode::Down), repeat: false, .. } => {
163
                    player.speed = 0;
171
                    movement_command = Some(MovementCommand::Stop);
164
                },
172
                },
165
                _ => {}
173
                _ => {}
166
            }
174
            }
167
        }
175
        }
168

176

    177
        *world.write_resource() = movement_command;
    178

169
        // Update
179
        // Update
170
        i = (i + 1) % 255;
180
        i = (i + 1) % 255;
171
        dispatcher.dispatch(&mut world.res);
181
        dispatcher.dispatch(&mut world.res);
172
        world.maintain();
182
        world.maintain();
173

183

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

186

177
        // Time management!
187
        // Time management!
178
        ::std::thread::sleep(Duration::new(0, 1_000_000_000u32 / 20));
188
        ::std::thread::sleep(Duration::new(0, 1_000_000_000u32 / 20));
179
    }
189
    }
180

190

181
    Ok(())
191
    Ok(())
182
}
192
}
/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 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;

/// 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;

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::*;

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 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(())
}

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(physics::Physics, "Physics", &["Keyboard"])
        .with(animator::Animator, "Animator", &["Keyboard"])
        .build();

    let mut world = World::new();
    dispatcher.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")?,
    ];
    // 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),
    };

    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();

    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
        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::*;

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