• src/main.rs
  • src/renderer.rs
+4 -32src/main.rs
1 1
mod components;
2 2
mod physics;
3 3
mod animator;
4 4
mod keyboard;
  5
mod renderer;
5 6

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

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

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

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

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

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

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

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

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

58 58
    frames
59 59
}
60 60

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

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

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

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

85  
    canvas.present();
86  

87  
    Ok(())
88  
}
89  

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

98 69
    let window = video_subsystem.window("game tutorial", 800, 600)
99 70
        .position_centered()
100 71
        .build()
101 72
        .expect("could not initialize video subsystem");
102 73

103 74
    let mut canvas = window.into_canvas().build()
104 75
        .expect("could not make a canvas");
105 76
    let texture_creator = canvas.texture_creator();
106 77

107 78
    let mut dispatcher = DispatcherBuilder::new()
108 79
        .with(keyboard::Keyboard, "Keyboard", &[])
109 80
        .with(physics::Physics, "Physics", &["Keyboard"])
110 81
        .with(animator::Animator, "Animator", &["Keyboard"])
111 82
        .build();
112 83

113 84
    let mut world = World::new();
114 85
    dispatcher.setup(&mut world.res);
  86
    renderer::SystemData::setup(&mut world.res);
115 87

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

120 92
    let textures = [
121 93
        texture_creator.load_texture("assets/bardo.png")?,
122 94
    ];
123 95
    // First texture in textures array
124 96
    let player_spritesheet = 0;
125 97
    let player_top_left_frame = Rect::new(0, 0, 26, 36);
126 98

127 99
    let player_animation = MovementAnimation {
128 100
        current_frame: 0,
129 101
        up_frames: character_animation_frames(player_spritesheet, player_top_left_frame, Direction::Up),
130 102
        down_frames: character_animation_frames(player_spritesheet, player_top_left_frame, Direction::Down),
131 103
        left_frames: character_animation_frames(player_spritesheet, player_top_left_frame, Direction::Left),
132 104
        right_frames: character_animation_frames(player_spritesheet, player_top_left_frame, Direction::Right),
133 105
    };
134 106

135 107
    world.create_entity()
136 108
        .with(KeyboardControlled)
137 109
        .with(Position(Point::new(0, 0)))
138 110
        .with(Velocity {speed: 0, direction: Direction::Right})
139 111
        .with(player_animation.right_frames[0].clone())
140 112
        .with(player_animation)
141 113
        .build();
142 114

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

177 149
        *world.write_resource() = movement_command;
178 150

179 151
        // Update
180 152
        i = (i + 1) % 255;
181 153
        dispatcher.dispatch(&mut world.res);
182 154
        world.maintain();
183 155

184 156
        // Render
185  
        render(&mut canvas, Color::RGB(i, 64, 255 - i), &texture, &player)?;
  157
        renderer::render(&mut canvas, Color::RGB(i, 64, 255 - i), &textures, world.system_data())?;
186 158

187 159
        // Time management!
188 160
        ::std::thread::sleep(Duration::new(0, 1_000_000_000u32 / 20));
189 161
    }
190 162

191 163
    Ok(())
192 164
}
+37 -0src/renderer.rs
  1
use specs::prelude::*;
  2
use sdl2::rect::{Point, Rect};
  3
use sdl2::pixels::Color;
  4
use sdl2::render::{WindowCanvas, Texture};
  5

  6
use crate::components::*;
  7

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

  14
pub fn render(
  15
    canvas: &mut WindowCanvas,
  16
    background: Color,
  17
    textures: &[Texture],
  18
    data: SystemData,
  19
) -> Result<(), String> {
  20
    canvas.set_draw_color(background);
  21
    canvas.clear();
  22

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

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

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

  34
    canvas.present();
  35

  36
    Ok(())
  37
}
+4 -32src/main.rs
1
mod components;
1
mod components;
2
mod physics;
2
mod physics;
3
mod animator;
3
mod animator;
4
mod keyboard;
4
mod keyboard;
    5
mod renderer;
5

6

6
use sdl2::pixels::Color;
   
7
use sdl2::event::Event;
7
use sdl2::event::Event;
8
use sdl2::keyboard::Keycode;
8
use sdl2::keyboard::Keycode;
9
use sdl2::render::{WindowCanvas, Texture};
9
use sdl2::pixels::Color;
10
use sdl2::rect::{Point, Rect};
10
use sdl2::rect::{Point, Rect};
11
// "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
12
use sdl2::image::{self, LoadTexture, InitFlag};
12
use sdl2::image::{self, LoadTexture, InitFlag};
13

13

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

15

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

17

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

19

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

24

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

35

36
/// Create animation frames for the standard character spritesheet
36
/// Create animation frames for the standard character spritesheet
37
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> {
38
    // 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
39
    // 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
40
    // entire system is harder.
40
    // entire system is harder.
41

41

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

44

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

57

58
    frames
58
    frames
59
}
59
}
60

60

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

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

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

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

   
85
    canvas.present();
   
86

   
87
    Ok(())
   
88
}
   
89

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

68

98
    let window = video_subsystem.window("game tutorial", 800, 600)
69
    let window = video_subsystem.window("game tutorial", 800, 600)
99
        .position_centered()
70
        .position_centered()
100
        .build()
71
        .build()
101
        .expect("could not initialize video subsystem");
72
        .expect("could not initialize video subsystem");
102

73

103
    let mut canvas = window.into_canvas().build()
74
    let mut canvas = window.into_canvas().build()
104
        .expect("could not make a canvas");
75
        .expect("could not make a canvas");
105
    let texture_creator = canvas.texture_creator();
76
    let texture_creator = canvas.texture_creator();
106

77

107
    let mut dispatcher = DispatcherBuilder::new()
78
    let mut dispatcher = DispatcherBuilder::new()
108
        .with(keyboard::Keyboard, "Keyboard", &[])
79
        .with(keyboard::Keyboard, "Keyboard", &[])
109
        .with(physics::Physics, "Physics", &["Keyboard"])
80
        .with(physics::Physics, "Physics", &["Keyboard"])
110
        .with(animator::Animator, "Animator", &["Keyboard"])
81
        .with(animator::Animator, "Animator", &["Keyboard"])
111
        .build();
82
        .build();
112

83

113
    let mut world = World::new();
84
    let mut world = World::new();
114
    dispatcher.setup(&mut world.res);
85
    dispatcher.setup(&mut world.res);
    86
    renderer::SystemData::setup(&mut world.res);
115

87

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

91

120
    let textures = [
92
    let textures = [
121
        texture_creator.load_texture("assets/bardo.png")?,
93
        texture_creator.load_texture("assets/bardo.png")?,
122
    ];
94
    ];
123
    // First texture in textures array
95
    // First texture in textures array
124
    let player_spritesheet = 0;
96
    let player_spritesheet = 0;
125
    let player_top_left_frame = Rect::new(0, 0, 26, 36);
97
    let player_top_left_frame = Rect::new(0, 0, 26, 36);
126

98

127
    let player_animation = MovementAnimation {
99
    let player_animation = MovementAnimation {
128
        current_frame: 0,
100
        current_frame: 0,
129
        up_frames: character_animation_frames(player_spritesheet, player_top_left_frame, Direction::Up),
101
        up_frames: character_animation_frames(player_spritesheet, player_top_left_frame, Direction::Up),
130
        down_frames: character_animation_frames(player_spritesheet, player_top_left_frame, Direction::Down),
102
        down_frames: character_animation_frames(player_spritesheet, player_top_left_frame, Direction::Down),
131
        left_frames: character_animation_frames(player_spritesheet, player_top_left_frame, Direction::Left),
103
        left_frames: character_animation_frames(player_spritesheet, player_top_left_frame, Direction::Left),
132
        right_frames: character_animation_frames(player_spritesheet, player_top_left_frame, Direction::Right),
104
        right_frames: character_animation_frames(player_spritesheet, player_top_left_frame, Direction::Right),
133
    };
105
    };
134

106

135
    world.create_entity()
107
    world.create_entity()
136
        .with(KeyboardControlled)
108
        .with(KeyboardControlled)
137
        .with(Position(Point::new(0, 0)))
109
        .with(Position(Point::new(0, 0)))
138
        .with(Velocity {speed: 0, direction: Direction::Right})
110
        .with(Velocity {speed: 0, direction: Direction::Right})
139
        .with(player_animation.right_frames[0].clone())
111
        .with(player_animation.right_frames[0].clone())
140
        .with(player_animation)
112
        .with(player_animation)
141
        .build();
113
        .build();
142

114

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

148

177
        *world.write_resource() = movement_command;
149
        *world.write_resource() = movement_command;
178

150

179
        // Update
151
        // Update
180
        i = (i + 1) % 255;
152
        i = (i + 1) % 255;
181
        dispatcher.dispatch(&mut world.res);
153
        dispatcher.dispatch(&mut world.res);
182
        world.maintain();
154
        world.maintain();
183

155

184
        // Render
156
        // Render
185
        render(&mut canvas, Color::RGB(i, 64, 255 - i), &texture, &player)?;
157
        renderer::render(&mut canvas, Color::RGB(i, 64, 255 - i), &textures, world.system_data())?;
186

158

187
        // Time management!
159
        // Time management!
188
        ::std::thread::sleep(Duration::new(0, 1_000_000_000u32 / 20));
160
        ::std::thread::sleep(Duration::new(0, 1_000_000_000u32 / 20));
189
    }
161
    }
190

162

191
    Ok(())
163
    Ok(())
192
}
164
}
+37 -0src/renderer.rs
    1
use specs::prelude::*;
    2
use sdl2::rect::{Point, Rect};
    3
use sdl2::pixels::Color;
    4
use sdl2::render::{WindowCanvas, Texture};
    5

    6
use crate::components::*;
    7

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

    14
pub fn render(
    15
    canvas: &mut WindowCanvas,
    16
    background: Color,
    17
    textures: &[Texture],
    18
    data: SystemData,
    19
) -> Result<(), String> {
    20
    canvas.set_draw_color(background);
    21
    canvas.clear();
    22

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

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

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

    34
    canvas.present();
    35

    36
    Ok(())
    37
}
/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;
mod renderer;

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

use specs::prelude::*;

use std::time::Duration;

use crate::components::*;

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

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

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

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

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

    frames
}

fn 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);
    renderer::SystemData::setup(&mut world.res);

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

    let textures = [
        texture_creator.load_texture("assets/bardo.png")?,
    ];
    // 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
        renderer::render(&mut canvas, Color::RGB(i, 64, 255 - i), &textures, world.system_data())?;

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

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

use crate::components::*;

pub struct Physics;

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

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

use crate::components::*;

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

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

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

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

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

    canvas.present();

    Ok(())
}