• src/main.rs
+1 -1src/animator.rs
1 1
use specs::prelude::*;
2 2

3 3
use crate::components::*;
4 4

5  
struct Animator;
  5
pub struct Animator;
6 6

7 7
impl<'a> System<'a> for Animator {
8 8
    type SystemData = (
9 9
        WriteStorage<'a, MovementAnimation>,
10 10
        WriteStorage<'a, Sprite>,
11 11
        ReadStorage<'a, Velocity>,
12 12
    );
13 13

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

23 23
            let frames = match vel.direction {
24 24
                Left => &anim.left_frames,
25 25
                Right => &anim.right_frames,
26 26
                Up => &anim.up_frames,
27 27
                Down => &anim.down_frames,
28 28
            };
29 29

30 30
            anim.current_frame = (anim.current_frame + 1) % frames.len();
31 31
            *sprite = frames[anim.current_frame].clone();
32 32
        }
33 33
    }
34 34
}
+10 -4src/main.rs
1 1
mod components;
2 2
mod physics;
3 3
mod animator;
4 4

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

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

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

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

19 19
const PLAYER_MOVEMENT_SPEED: i32 = 20;
20 20

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

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

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

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

54 54
    frames
55 55
}
56 56

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

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

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

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

81 81
    canvas.present();
82 82

83 83
    Ok(())
84 84
}
85 85

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

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

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

102 101
    let texture_creator = canvas.texture_creator();
103 102

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

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

104 111
    let textures = [
105 112
        texture_creator.load_texture("assets/bardo.png")?,
106 113
    ];
107 114
    // First texture in textures array
108 115
    let player_spritesheet = 0;
109 116
    let player_top_left_frame = Rect::new(0, 0, 26, 36);
110 117

111 118
    let player_animation = MovementAnimation {
112 119
        current_frame: 0,
113 120
        up_frames: character_animation_frames(player_spritesheet, player_top_left_frame, Direction::Up),
114 121
        down_frames: character_animation_frames(player_spritesheet, player_top_left_frame, Direction::Down),
115 122
        left_frames: character_animation_frames(player_spritesheet, player_top_left_frame, Direction::Left),
116 123
        right_frames: character_animation_frames(player_spritesheet, player_top_left_frame, Direction::Right),
117 124
    };
118 125

119  
    let mut world = World::new();
120  

121 126
    world.create_entity()
122 127
        .with(Position(Point::new(0, 0)))
123 128
        .with(Velocity {speed: 0, direction: Direction::Right})
124 129
        .with(player_animation.right_frames[0].clone())
125 130
        .with(player_animation)
126 131
        .build();
127 132

128 133
    let mut event_pump = sdl_context.event_pump()?;
129 134
    let mut i = 0;
130 135
    'running: loop {
131 136
        // Handle events
132 137
        for event in event_pump.poll_iter() {
133 138
            match event {
134 139
                Event::Quit {..} |
135 140
                Event::KeyDown { keycode: Some(Keycode::Escape), .. } => {
136 141
                    break 'running;
137 142
                },
138 143
                Event::KeyDown { keycode: Some(Keycode::Left), repeat: false, .. } => {
139 144
                    player.speed = PLAYER_MOVEMENT_SPEED;
140 145
                    player.direction = Direction::Left;
141 146
                },
142 147
                Event::KeyDown { keycode: Some(Keycode::Right), repeat: false, .. } => {
143 148
                    player.speed = PLAYER_MOVEMENT_SPEED;
144 149
                    player.direction = Direction::Right;
145 150
                },
146 151
                Event::KeyDown { keycode: Some(Keycode::Up), repeat: false, .. } => {
147 152
                    player.speed = PLAYER_MOVEMENT_SPEED;
148 153
                    player.direction = Direction::Up;
149 154
                },
150 155
                Event::KeyDown { keycode: Some(Keycode::Down), repeat: false, .. } => {
151 156
                    player.speed = PLAYER_MOVEMENT_SPEED;
152 157
                    player.direction = Direction::Down;
153 158
                },
154 159
                Event::KeyUp { keycode: Some(Keycode::Left), repeat: false, .. } |
155 160
                Event::KeyUp { keycode: Some(Keycode::Right), repeat: false, .. } |
156 161
                Event::KeyUp { keycode: Some(Keycode::Up), repeat: false, .. } |
157 162
                Event::KeyUp { keycode: Some(Keycode::Down), repeat: false, .. } => {
158 163
                    player.speed = 0;
159 164
                },
160 165
                _ => {}
161 166
            }
162 167
        }
163 168

164 169
        // Update
165 170
        i = (i + 1) % 255;
166  
        //TODO: Do with specs!
  171
        dispatcher.dispatch(&mut world.res);
  172
        world.maintain();
167 173

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

171 177
        // Time management!
172 178
        ::std::thread::sleep(Duration::new(0, 1_000_000_000u32 / 20));
173 179
    }
174 180

175 181
    Ok(())
176 182
}
+1 -1src/physics.rs
1 1
use specs::prelude::*;
2 2

3 3
use crate::components::*;
4 4

5  
struct Physics;
  5
pub struct Physics;
6 6

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

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

2

3
use crate::components::*;
3
use crate::components::*;
4

4

5
struct Animator;
5
pub struct Animator;
6

6

7
impl<'a> System<'a> for Animator {
7
impl<'a> System<'a> for Animator {
8
    type SystemData = (
8
    type SystemData = (
9
        WriteStorage<'a, MovementAnimation>,
9
        WriteStorage<'a, MovementAnimation>,
10
        WriteStorage<'a, Sprite>,
10
        WriteStorage<'a, Sprite>,
11
        ReadStorage<'a, Velocity>,
11
        ReadStorage<'a, Velocity>,
12
    );
12
    );
13

13

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

22

23
            let frames = match vel.direction {
23
            let frames = match vel.direction {
24
                Left => &anim.left_frames,
24
                Left => &anim.left_frames,
25
                Right => &anim.right_frames,
25
                Right => &anim.right_frames,
26
                Up => &anim.up_frames,
26
                Up => &anim.up_frames,
27
                Down => &anim.down_frames,
27
                Down => &anim.down_frames,
28
            };
28
            };
29

29

30
            anim.current_frame = (anim.current_frame + 1) % frames.len();
30
            anim.current_frame = (anim.current_frame + 1) % frames.len();
31
            *sprite = frames[anim.current_frame].clone();
31
            *sprite = frames[anim.current_frame].clone();
32
        }
32
        }
33
    }
33
    }
34
}
34
}
+10 -4src/main.rs
1
mod components;
1
mod components;
2
mod physics;
2
mod physics;
3
mod animator;
3
mod animator;
4

4

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

12

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

14

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

16

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

18

19
const PLAYER_MOVEMENT_SPEED: i32 = 20;
19
const PLAYER_MOVEMENT_SPEED: i32 = 20;
20

20

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

31

32
/// Create animation frames for the standard character spritesheet
32
/// Create animation frames for the standard character spritesheet
33
fn character_animation_frames(spritesheet: usize, top_left_frame: Rect, direction: Direction) -> Vec<Sprite> {
33
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
34
    // 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
35
    // the design of our entire system. We can always replace this function, but replacing the
36
    // entire system is harder.
36
    // entire system is harder.
37

37

38
    let (frame_width, frame_height) = top_left_frame.size();
38
    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);
39
    let y_offset = top_left_frame.y() + frame_height as i32 * direction_spritesheet_row(direction);
40

40

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

53

54
    frames
54
    frames
55
}
55
}
56

56

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

65

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

67

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

75

76
    // Treat the center of the screen as the (0, 0) coordinate
76
    // 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);
77
    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);
78
    let screen_rect = Rect::from_center(screen_position, frame_width, frame_height);
79
    canvas.copy(texture, current_frame, screen_rect)?;
79
    canvas.copy(texture, current_frame, screen_rect)?;
80

80

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

82

83
    Ok(())
83
    Ok(())
84
}
84
}
85

85

86
fn main() -> Result<(), String> {
86
fn main() -> Result<(), String> {
87
    let sdl_context = sdl2::init()?;
87
    let sdl_context = sdl2::init()?;
88
    let video_subsystem = sdl_context.video()?;
88
    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
89
    // 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
90
    // 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!
91
    // temporary value and drop it right away!
92
    let _image_context = image::init(InitFlag::PNG | InitFlag::JPG)?;
92
    let _image_context = image::init(InitFlag::PNG | InitFlag::JPG)?;
93

93

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

98

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

   
102
    let texture_creator = canvas.texture_creator();
101
    let texture_creator = canvas.texture_creator();
103

102

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

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

104
    let textures = [
111
    let textures = [
105
        texture_creator.load_texture("assets/bardo.png")?,
112
        texture_creator.load_texture("assets/bardo.png")?,
106
    ];
113
    ];
107
    // First texture in textures array
114
    // First texture in textures array
108
    let player_spritesheet = 0;
115
    let player_spritesheet = 0;
109
    let player_top_left_frame = Rect::new(0, 0, 26, 36);
116
    let player_top_left_frame = Rect::new(0, 0, 26, 36);
110

117

111
    let player_animation = MovementAnimation {
118
    let player_animation = MovementAnimation {
112
        current_frame: 0,
119
        current_frame: 0,
113
        up_frames: character_animation_frames(player_spritesheet, player_top_left_frame, Direction::Up),
120
        up_frames: character_animation_frames(player_spritesheet, player_top_left_frame, Direction::Up),
114
        down_frames: character_animation_frames(player_spritesheet, player_top_left_frame, Direction::Down),
121
        down_frames: character_animation_frames(player_spritesheet, player_top_left_frame, Direction::Down),
115
        left_frames: character_animation_frames(player_spritesheet, player_top_left_frame, Direction::Left),
122
        left_frames: character_animation_frames(player_spritesheet, player_top_left_frame, Direction::Left),
116
        right_frames: character_animation_frames(player_spritesheet, player_top_left_frame, Direction::Right),
123
        right_frames: character_animation_frames(player_spritesheet, player_top_left_frame, Direction::Right),
117
    };
124
    };
118

125

119
    let mut world = World::new();
   
120

   
121
    world.create_entity()
126
    world.create_entity()
122
        .with(Position(Point::new(0, 0)))
127
        .with(Position(Point::new(0, 0)))
123
        .with(Velocity {speed: 0, direction: Direction::Right})
128
        .with(Velocity {speed: 0, direction: Direction::Right})
124
        .with(player_animation.right_frames[0].clone())
129
        .with(player_animation.right_frames[0].clone())
125
        .with(player_animation)
130
        .with(player_animation)
126
        .build();
131
        .build();
127

132

128
    let mut event_pump = sdl_context.event_pump()?;
133
    let mut event_pump = sdl_context.event_pump()?;
129
    let mut i = 0;
134
    let mut i = 0;
130
    'running: loop {
135
    'running: loop {
131
        // Handle events
136
        // Handle events
132
        for event in event_pump.poll_iter() {
137
        for event in event_pump.poll_iter() {
133
            match event {
138
            match event {
134
                Event::Quit {..} |
139
                Event::Quit {..} |
135
                Event::KeyDown { keycode: Some(Keycode::Escape), .. } => {
140
                Event::KeyDown { keycode: Some(Keycode::Escape), .. } => {
136
                    break 'running;
141
                    break 'running;
137
                },
142
                },
138
                Event::KeyDown { keycode: Some(Keycode::Left), repeat: false, .. } => {
143
                Event::KeyDown { keycode: Some(Keycode::Left), repeat: false, .. } => {
139
                    player.speed = PLAYER_MOVEMENT_SPEED;
144
                    player.speed = PLAYER_MOVEMENT_SPEED;
140
                    player.direction = Direction::Left;
145
                    player.direction = Direction::Left;
141
                },
146
                },
142
                Event::KeyDown { keycode: Some(Keycode::Right), repeat: false, .. } => {
147
                Event::KeyDown { keycode: Some(Keycode::Right), repeat: false, .. } => {
143
                    player.speed = PLAYER_MOVEMENT_SPEED;
148
                    player.speed = PLAYER_MOVEMENT_SPEED;
144
                    player.direction = Direction::Right;
149
                    player.direction = Direction::Right;
145
                },
150
                },
146
                Event::KeyDown { keycode: Some(Keycode::Up), repeat: false, .. } => {
151
                Event::KeyDown { keycode: Some(Keycode::Up), repeat: false, .. } => {
147
                    player.speed = PLAYER_MOVEMENT_SPEED;
152
                    player.speed = PLAYER_MOVEMENT_SPEED;
148
                    player.direction = Direction::Up;
153
                    player.direction = Direction::Up;
149
                },
154
                },
150
                Event::KeyDown { keycode: Some(Keycode::Down), repeat: false, .. } => {
155
                Event::KeyDown { keycode: Some(Keycode::Down), repeat: false, .. } => {
151
                    player.speed = PLAYER_MOVEMENT_SPEED;
156
                    player.speed = PLAYER_MOVEMENT_SPEED;
152
                    player.direction = Direction::Down;
157
                    player.direction = Direction::Down;
153
                },
158
                },
154
                Event::KeyUp { keycode: Some(Keycode::Left), repeat: false, .. } |
159
                Event::KeyUp { keycode: Some(Keycode::Left), repeat: false, .. } |
155
                Event::KeyUp { keycode: Some(Keycode::Right), repeat: false, .. } |
160
                Event::KeyUp { keycode: Some(Keycode::Right), repeat: false, .. } |
156
                Event::KeyUp { keycode: Some(Keycode::Up), repeat: false, .. } |
161
                Event::KeyUp { keycode: Some(Keycode::Up), repeat: false, .. } |
157
                Event::KeyUp { keycode: Some(Keycode::Down), repeat: false, .. } => {
162
                Event::KeyUp { keycode: Some(Keycode::Down), repeat: false, .. } => {
158
                    player.speed = 0;
163
                    player.speed = 0;
159
                },
164
                },
160
                _ => {}
165
                _ => {}
161
            }
166
            }
162
        }
167
        }
163

168

164
        // Update
169
        // Update
165
        i = (i + 1) % 255;
170
        i = (i + 1) % 255;
166
        //TODO: Do with specs!
171
        dispatcher.dispatch(&mut world.res);
    172
        world.maintain();
167

173

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

176

171
        // Time management!
177
        // Time management!
172
        ::std::thread::sleep(Duration::new(0, 1_000_000_000u32 / 20));
178
        ::std::thread::sleep(Duration::new(0, 1_000_000_000u32 / 20));
173
    }
179
    }
174

180

175
    Ok(())
181
    Ok(())
176
}
182
}
+1 -1src/physics.rs
1
use specs::prelude::*;
1
use specs::prelude::*;
2

2

3
use crate::components::*;
3
use crate::components::*;
4

4

5
struct Physics;
5
pub struct Physics;
6

6

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

9

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

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

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

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

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

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