Skip to content

Commit f7c7f69

Browse files
authored
Write the tutorial (#25)
* write the tutorial
1 parent 20ba77d commit f7c7f69

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+1174
-117
lines changed

.github/workflows/tutorial.yml

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
name: Publish Tutorial
2+
on:
3+
push:
4+
branches:
5+
- main
6+
jobs:
7+
build-and-deploy:
8+
runs-on: ubuntu-latest
9+
steps:
10+
- name: Checkout
11+
uses: actions/checkout@v2
12+
13+
- name: install stable Rust toolchain
14+
id: install_toolchain
15+
uses: actions-rs/toolchain@v1
16+
with:
17+
toolchain: stable
18+
profile: minimal
19+
# Don't use a 'components:' entry--we don't need them with beta/nightly, plus nightly often doesn't have them
20+
override: true
21+
22+
- name: install mdbook
23+
run: cargo install mdbook
24+
25+
- name: build tutorial 🔧
26+
run: mdbook build tutorial
27+
28+
- name: Deploy to GitHub Pages 🚀
29+
uses: JamesIves/github-pages-deploy-action@4
30+
with:
31+
branch: gh-pages # The branch the action should deploy to.
32+
folder: tutorial/book # The folder the action should deploy.

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1+
.cargo/config.toml
12
.vscode
23
**/*.rs.bk
34
**/target
45
Cargo.lock
5-
.cargo/config.toml
6+
tutorial/book

CHANGELOG.md

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,20 +34,23 @@ run. Return `false` to abort running any later functions during the frame.
3434
- `Sprite::write_collider` has been added (see note below about changes to colliders)
3535
- `TextActor` has been renamed to `Text` to eliminate the confusing "actor" terminology.
3636
- `TextActor.text` is now `Text.value` for similar reasons.
37-
- `Sprite`s may now be created with either a `SpritePreset` or the path to an image file via both `Sprite::new` or `EngineState::add_sprite`
37+
- `Sprite`s may now be created with either a `SpritePreset` or the path to an image file via both `Sprite::new` or `EngineState::add_sprite`. The image file needs to be stored in `assets/sprite` or one of its subdirectories. When specifying the path to the file, the path relative to `assets/sprite` should be used. For example, if your image is `assets/sprite/circus/animal.png` then you would pass `circus/animal.png` to one of the methods to create the sprite.
3838
- `SpritePreset::build_from_name` and `SpritePreset::build` have been removed (see note above about the new, more flexible way to create sprites)
3939
- `SpritePreset::collider()` has been removed since colliders are no longer hard-coded features of presets (see note below about changes to colliders)
4040
- `SpritePreset::filename -> String` has been replaced by `SpritePreset::filepath -> PathBuf`, which powers the `impl From<SpritePreset> for PathBuf` implementation.
4141
- Colliders are now loaded from collider files. Collider files use the [Rusty Object Notation (RON)](https://github.com/ron-rs/ron) format. The easiest way to create a collider is to run the `collider_creator` example by cloning the `rusty_engine` repository and running `cargo run --release --example collider_creator relative/path/to/my/image.png`. The image needs to be somewhere inside the `assets/` directory. You could also create the collider programmatically, set it on the `Sprite` struct, and call the sprite's `write_collider()` method. Or you could copy an existing collider file, name it the same as your image file (but with the `.collider` extension) and change it to match your image. Collider coordinates need to define a convex polygon with points going in clockwise order. Coordinates are floating point values relative to the center of the image, with the center of the image being (0.0, 0.0).
4242
- All sprites' colliders in the asset pack have been recreated more cleanly using the new `collider_creator` example.
43+
- The `assets/fonts` directory in the asset pack has been renamed to `assets/font` for consistency with the other directories.
44+
- `KeyboardState` and `MouseState` now both have 6 similar methods for processing key- and button-presses:
45+
- `pressed` -> `pressed_any`
46+
- `just_pressed` -> `just_pressed_any`
47+
- `just_released` -> `just_released_any`
4348

4449
### Other Changes
4550

4651
- `AudioManager::music_playing()` will return whether or not music is currently playing (accessible
4752
through `EngineState:audio_manager`)
48-
- Custom fonts may now be set on a `Text` at creation time.
49-
`EngineState::add_text_with_font` was added for a convenience. The font specified should be
50-
a `.ttf` or `.otf` file stored in `assets/fonts`
53+
- A custom font may now be selected by placing it in `assets/font` and specifying the relative filepath on `Text.font`.
5154
- Custom sounds may now be played via `AudioManager::play_music` and `AudioManager::play_sfx` by
5255
specifying a path to a sound file relative to `assets/audio`.
5356
- `Collider` now implements `PartialEq`, `Serialize`, and `Deserialize`

README.md

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,30 +2,34 @@ If you like Rusty Engine, please sponsor me [on GitHub] or [on Patreon], or [tak
22

33
# Rusty Engine
44

5-
Rusty Engine is a simple, 2D game engine for those who are learning Rust. Create simple game prototypes using straightforward Rust code without any advanced game engine concepts! It works on macOS, Linux, and Windows. Rusty Engine is a simplification wrapper over [Bevy], which I encourage you to use directly for more serious game engine needs.
5+
Rusty Engine is a simple, 2D game engine for those who are learning Rust. Create simple game prototypes using straightforward Rust code without needing to learning difficult game engine concepts! It works on macOS, Linux, and Windows. Rusty Engine is a simplification wrapper over [Bevy], which I encourage you to use directly for more serious game engine needs.
66

77
[Questions], [bug reports], and contributions are most welcome!
88

99
https://user-images.githubusercontent.com/5838512/122880590-651bae00-d2f7-11eb-8e5c-4810b3777828.mp4
1010

1111
## Features
1212

13+
- Asset pack included (sprites, music, sound effects, and fonts)
1314
- Sprites (2D images)
14-
- Includes 2 built-in asset packs
15+
- Use sprites from the included asset pack, or bring your own
16+
- Collision detection with custom colliders
1517
- Audio (music & sound effects)
16-
- Includes 2 built-in asset packs
17-
- Collision detection
18+
- Looping music
19+
- Multi-channel sound effects
1820
- Text
19-
- Includes 2 built-in fonts
21+
- 2 fonts included, or bring your own
2022
- Input handling (keyboard, mouse)
2123
- Timers
24+
- Custom game state
25+
- Window customization
2226

2327
## Courses
2428

25-
I teach courses which use this game engine:
29+
The following courses use Rusty Engine in their curriculum:
2630

27-
- `Ultimate Rust 2: Intermediate Concepts` on Udemy, etc. Coming soon!
28-
- [Rust in 3 Weeks](https://agileperception.com) conducted live on O'Reilly Online.
31+
- [Ultimate Rust 2: Intermediate Concepts](https://www.udemy.com/course/ultimate-rust-2/?referralCode=8ED694EBE5637F954414) on Udemy (the sequel to [Ultimate Rust Crash Course](https://www.udemy.com/course/ultimate-rust-crash-course/?referralCode=AF30FAD8C6CCCC2C94F0))
32+
- [Rust in 3 Weeks](https://agileperception.com) conducted live on O'Reilly Online approximately once each quarter.
2933

3034
## Linux Dependencies (Including WSL 2)
3135

File renamed without changes.
File renamed without changes.

examples/collider_creator.rs

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,16 +21,21 @@ fn main() {
2121
let args = std::env::args().skip(1).collect::<Vec<_>>();
2222
if args.len() != 1 {
2323
println!(
24-
"Please pass in the path of an image inside the assets directory! For example:\n\
25-
cargo run --release --example collider_creator -- sprite/racing/car_green.png"
24+
"Please pass in the path of an image inside the `assets/sprite` directory! For example:\n\
25+
cargo run --release --example collider_creator -- racing/car_green.png"
2626
);
2727
std::process::exit(1);
2828
}
2929

30-
// Convert the string to an actual path, and make sure it exists
30+
// If the user passed in `assets/sprite/something...` then we need to strip `assets/` (the asset loader will prepend `assets/`)
3131
let mut path = PathBuf::from(args[0].clone());
32-
if path.starts_with("assets") {
33-
path = path.strip_prefix("assets").unwrap().to_path_buf();
32+
if path.starts_with("assets/sprite") {
33+
path = path
34+
.strip_prefix("assets")
35+
.unwrap()
36+
.strip_prefix("sprite")
37+
.unwrap()
38+
.to_path_buf();
3439
}
3540
if !(PathBuf::from("assets").join(&path)).exists() {
3641
println!("Couldn't find the file {}", path.to_string_lossy());

examples/collision.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,11 @@ fn main() {
4141

4242
fn logic(engine_state: &mut EngineState, _: &mut ()) -> bool {
4343
// If a collision event happened last frame, print it out and play a sound
44-
for event in engine_state.collision_events.drain(..) {
44+
for collision_event in engine_state.collision_events.drain(..) {
4545
let text = engine_state.texts.get_mut("collision text").unwrap();
46-
match event.state {
46+
match collision_event.state {
4747
CollisionState::Begin => {
48-
text.value = format!("{:?}", event.pair);
48+
text.value = format!("{:?}", collision_event.pair);
4949
engine_state.audio_manager.play_sfx(SfxPreset::Switch1, 1.0)
5050
}
5151
CollisionState::End => {

examples/music.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,12 @@ fn main() {
1313
);
1414
msg.translation.y = 50.0;
1515

16-
let msg2 = game.add_text_with_font(
16+
let msg2 = game.add_text(
1717
"msg2",
1818
"engine_state.audio_manager.play_music(MusicPreset::Classy8Bit, 1.0);",
19-
"FiraMono-Medium.ttf",
2019
);
2120
msg2.translation.y = -50.0;
21+
msg2.font = "FiraMono-Medium.ttf".to_string();
2222

2323
game.audio_manager.play_music(MusicPreset::Classy8Bit, 1.0);
2424

examples/sfx.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,12 @@ fn main() {
1313
);
1414
msg.translation.y = 50.0;
1515

16-
let msg2 = game.add_text_with_font(
16+
let msg2 = game.add_text(
1717
"msg2",
1818
"engine_state.audio_manager.play_sfx(SfxPreset::Jingle1, 1.0);",
19-
"FiraMono-Medium.ttf",
2019
);
2120
msg2.translation.y = -50.0;
21+
msg2.font = "FiraMono-Medium.ttf".to_string();
2222

2323
game.audio_manager.play_sfx(SfxPreset::Jingle1, 1.0);
2424

examples/sound.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,12 @@ fn main() {
1313
);
1414
msg.translation.y = 100.0;
1515

16-
let msg2 = game.add_text_with_font(
16+
let msg2 = game.add_text(
1717
"msg2",
1818
"engine_state.audio_manager.play_sfx(\"sfx/congratulations.ogg\", 1.0);",
19-
"FiraMono-Medium.ttf",
2019
);
2120
msg2.translation.y = -100.0;
21+
msg2.font = "FiraMono-Medium.ttf".to_string();
2222

2323
game.audio_manager.play_sfx("sfx/congratulations.ogg", 1.0);
2424

examples/text.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,9 @@ rusty_engine::init!(GameState);
88

99
fn main() {
1010
let mut game = Game::new();
11-
let fps = game.add_text_with_font("fps", "FPS: ", "FiraMono-Medium.ttf");
11+
let fps = game.add_text("fps", "FPS: ");
1212
fps.translation = Vec2::new(0.0, 250.0);
13+
fps.font = "FiraMono-Medium.ttf".to_string();
1314
fps.font_size = 60.0;
1415

1516
let zoom_msg = game.add_text(
@@ -19,12 +20,12 @@ fn main() {
1920
zoom_msg.font_size = 35.0;
2021
zoom_msg.translation = Vec2::new(0.0, 150.0);
2122

22-
let font_msg = game.add_text_with_font(
23+
let font_msg = game.add_text(
2324
"font_msg",
24-
"You can choose a font at creation time by providing the filename of a font stored in assets/fonts.\n\"FiraSans-Bold.ttf\" is the default. \"FiraMono-Medium.ttf\" is also included in the asset pack.",
25-
"FiraMono-Medium.ttf",
25+
"You can choose a font at creation time by providing the filename of a font stored in assets/font.\n\"FiraSans-Bold.ttf\" is the default. \"FiraMono-Medium.ttf\" is also included in the asset pack."
2626
);
2727
font_msg.font_size = 20.0;
28+
font_msg.font = "FiraMono-Medium.ttf".to_string();
2829
font_msg.translation.y = 0.0;
2930

3031
let msg = game.add_text("msg", "Changing the text's translation, rotation*, and scale* is fast,\n so feel free to do that a lot.");

release.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ tag-message = "{{tag_name}}"
44
tag-name = "{{prefix}}v{{version}}"
55
pre-release-replacements = [
66
{ file = "README.md", search = "rusty_engine = \".*\"", replace = "rusty_engine = \"{{version}}\"", exactly = 1 },
7+
{ file = "tutorial/src/05-config.md", search = "rusty_engine = \".*\"", replace = "rusty_engine = \"{{version}}\"", exactly = 1 },
78
{ file = "CHANGELOG.md", search = "Unreleased", replace = "{{version}}", min = 1 },
89
{ file = "CHANGELOG.md", search = "\\.\\.\\.HEAD", replace = "...{{tag_name}}", exactly = 1 },
910
{ file = "CHANGELOG.md", search = "ReleaseDate", replace = "{{date}}", min = 1 },

src/game.rs

Lines changed: 9 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,7 @@ pub struct EngineState {
3939
/// [`add_sprite`](EngineState::add_sprite) method. Modify & remove sprites as you like.
4040
pub sprites: HashMap<String, Sprite>,
4141
/// SYNCED - The state of all texts this frame. For convenience adding a text, use the
42-
/// [`add_text`](EngineState::add_text) or
43-
/// [`add_text_with_font`](EngineState::add_text_with_font) methods. Modify & remove
44-
/// text as you like.
42+
/// [`add_text`](EngineState::add_text) method. Modify & remove text as you like.
4543
pub texts: HashMap<String, Text>,
4644
/// SYNCED - If set to `true`, the game exits. Note: the current frame will run to completion first.
4745
pub should_exit: bool,
@@ -91,7 +89,7 @@ pub struct EngineState {
9189
pub time_since_startup_f64: f64,
9290
/// A struct with methods to play sound effects and music
9391
pub audio_manager: AudioManager,
94-
/// INFO - Window dimensions in pixels
92+
/// INFO - Window dimensions in logical pixels
9593
pub window_dimensions: Vec2,
9694
}
9795

@@ -132,31 +130,6 @@ impl EngineState {
132130
// Unwrap: Can't crash because we just inserted the text
133131
self.texts.get_mut(&label).unwrap()
134132
}
135-
136-
#[must_use]
137-
/// Add a [`Text`] with a specific font. Use the `&mut Text` that is returned to set the
138-
/// translation, rotation, etc. Use a unique label for each text. Attempting to add two texts
139-
/// with the same label will crash. The `font` parameter should be the filename of a font
140-
/// located in the assets/fonts directory. The default font is "FiraSans-Bold.ttf".
141-
pub fn add_text_with_font<L, T, F>(&mut self, label: L, text: T, font: F) -> &mut Text
142-
where
143-
L: Into<String>,
144-
T: Into<String>,
145-
F: Into<String>,
146-
{
147-
let label = label.into();
148-
let text = text.into();
149-
let font = font.into();
150-
let curr_text = Text {
151-
label: label.clone(),
152-
value: text,
153-
font,
154-
..Default::default()
155-
};
156-
self.texts.insert(label.clone(), curr_text);
157-
// Unwrap: Can't crash because we just inserted the sprite
158-
self.texts.get_mut(&label).unwrap()
159-
}
160133
}
161134

162135
// startup system - grab window settings, initialize all the starting sprites
@@ -181,7 +154,7 @@ pub fn add_sprites(
181154
) {
182155
for (_, sprite) in engine_state.sprites.drain() {
183156
let transform = sprite.bevy_transform();
184-
let texture_handle = asset_server.load(sprite.filepath.clone());
157+
let texture_handle = asset_server.load(PathBuf::from("sprite").join(&sprite.filepath));
185158
commands.spawn().insert(sprite).insert_bundle(SpriteBundle {
186159
material: materials.add(texture_handle.into()),
187160
transform,
@@ -202,7 +175,7 @@ pub fn add_texts(
202175
let transform = text.bevy_transform();
203176
let font_size = text.font_size;
204177
let text_string = text.value.clone();
205-
let font_path = format!("fonts/{}", text.font);
178+
let font_path = format!("font/{}", text.font);
206179
commands.spawn().insert(text).insert_bundle(Text2dBundle {
207180
text: BevyText::with_section(
208181
text_string,
@@ -527,6 +500,11 @@ fn game_logic_sync(
527500
if text.font_size != bevy_text_component.sections[0].style.font_size {
528501
bevy_text_component.sections[0].style.font_size = text.font_size;
529502
}
503+
let font_path = format!("font/{}", text.font);
504+
let font = asset_server.load(font_path.as_str());
505+
if bevy_text_component.sections[0].style.font != font {
506+
bevy_text_component.sections[0].style.font = font;
507+
}
530508
} else {
531509
commands.entity(entity).despawn();
532510
}

src/mouse.rs

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -87,17 +87,17 @@ impl MouseState {
8787
pub fn just_released(&self, mouse_button: MouseButton) -> bool {
8888
self.just_released.contains(&mouse_button)
8989
}
90-
/// Returns an iterator over all the mouse buttons were being pressed at the end of the last frame
91-
pub fn get_pressed(&self) -> impl ExactSizeIterator<Item = &MouseButton> {
92-
self.pressed.iter()
90+
/// Returns true if any of the indicated mouse buttons were pressed
91+
pub fn pressed_any(&self, mouse_buttons: &[MouseButton]) -> bool {
92+
mouse_buttons.iter().any(|k| self.pressed(*k))
9393
}
94-
/// Returns an iterator over all the mouse buttons that started being pressed at the end of the last frame
95-
pub fn get_just_pressed(&self) -> impl ExactSizeIterator<Item = &MouseButton> {
96-
self.just_pressed.iter()
94+
/// Returns true if any of the indicated mouse buttons were just pressed this frame
95+
pub fn just_pressed_any(&self, mouse_buttons: &[MouseButton]) -> bool {
96+
mouse_buttons.iter().any(|k| self.just_pressed(*k))
9797
}
98-
/// Returns an iterator over all the mouse buttons that started being released at the end of the last frame
99-
pub fn get_just_released(&self) -> impl ExactSizeIterator<Item = &MouseButton> {
100-
self.just_released.iter()
98+
/// Returns true if any of the indicated mouse buttons were just released this frame
99+
pub fn just_released_any(&self, mouse_buttons: &[MouseButton]) -> bool {
100+
mouse_buttons.iter().any(|k| self.just_released(*k))
101101
}
102102
}
103103

0 commit comments

Comments
 (0)