Skip to content

Write the tutorial #25

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Dec 31, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions .github/workflows/tutorial.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
name: Publish Tutorial
on:
push:
branches:
- main
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2

- name: install stable Rust toolchain
id: install_toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: stable
profile: minimal
# Don't use a 'components:' entry--we don't need them with beta/nightly, plus nightly often doesn't have them
override: true

- name: install mdbook
run: cargo install mdbook

- name: build tutorial 🔧
run: mdbook build tutorial

- name: Deploy to GitHub Pages 🚀
uses: JamesIves/github-pages-deploy-action@4
with:
branch: gh-pages # The branch the action should deploy to.
folder: tutorial/book # The folder the action should deploy.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
.cargo/config.toml
.vscode
**/*.rs.bk
**/target
Cargo.lock
.cargo/config.toml
tutorial/book
11 changes: 7 additions & 4 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,20 +34,23 @@ run. Return `false` to abort running any later functions during the frame.
- `Sprite::write_collider` has been added (see note below about changes to colliders)
- `TextActor` has been renamed to `Text` to eliminate the confusing "actor" terminology.
- `TextActor.text` is now `Text.value` for similar reasons.
- `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`
- `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.
- `SpritePreset::build_from_name` and `SpritePreset::build` have been removed (see note above about the new, more flexible way to create sprites)
- `SpritePreset::collider()` has been removed since colliders are no longer hard-coded features of presets (see note below about changes to colliders)
- `SpritePreset::filename -> String` has been replaced by `SpritePreset::filepath -> PathBuf`, which powers the `impl From<SpritePreset> for PathBuf` implementation.
- 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).
- All sprites' colliders in the asset pack have been recreated more cleanly using the new `collider_creator` example.
- The `assets/fonts` directory in the asset pack has been renamed to `assets/font` for consistency with the other directories.
- `KeyboardState` and `MouseState` now both have 6 similar methods for processing key- and button-presses:
- `pressed` -> `pressed_any`
- `just_pressed` -> `just_pressed_any`
- `just_released` -> `just_released_any`

### Other Changes

- `AudioManager::music_playing()` will return whether or not music is currently playing (accessible
through `EngineState:audio_manager`)
- Custom fonts may now be set on a `Text` at creation time.
`EngineState::add_text_with_font` was added for a convenience. The font specified should be
a `.ttf` or `.otf` file stored in `assets/fonts`
- A custom font may now be selected by placing it in `assets/font` and specifying the relative filepath on `Text.font`.
- Custom sounds may now be played via `AudioManager::play_music` and `AudioManager::play_sfx` by
specifying a path to a sound file relative to `assets/audio`.
- `Collider` now implements `PartialEq`, `Serialize`, and `Deserialize`
Expand Down
20 changes: 12 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,34 @@ If you like Rusty Engine, please sponsor me [on GitHub] or [on Patreon], or [tak

# Rusty Engine

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.
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.

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

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

## Features

- Asset pack included (sprites, music, sound effects, and fonts)
- Sprites (2D images)
- Includes 2 built-in asset packs
- Use sprites from the included asset pack, or bring your own
- Collision detection with custom colliders
- Audio (music & sound effects)
- Includes 2 built-in asset packs
- Collision detection
- Looping music
- Multi-channel sound effects
- Text
- Includes 2 built-in fonts
- 2 fonts included, or bring your own
- Input handling (keyboard, mouse)
- Timers
- Custom game state
- Window customization

## Courses

I teach courses which use this game engine:
The following courses use Rusty Engine in their curriculum:

- `Ultimate Rust 2: Intermediate Concepts` on Udemy, etc. Coming soon!
- [Rust in 3 Weeks](https://agileperception.com) conducted live on O'Reilly Online.
- [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))
- [Rust in 3 Weeks](https://agileperception.com) conducted live on O'Reilly Online approximately once each quarter.

## Linux Dependencies (Including WSL 2)

Expand Down
File renamed without changes.
File renamed without changes.
15 changes: 10 additions & 5 deletions examples/collider_creator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,21 @@ fn main() {
let args = std::env::args().skip(1).collect::<Vec<_>>();
if args.len() != 1 {
println!(
"Please pass in the path of an image inside the assets directory! For example:\n\
cargo run --release --example collider_creator -- sprite/racing/car_green.png"
"Please pass in the path of an image inside the `assets/sprite` directory! For example:\n\
cargo run --release --example collider_creator -- racing/car_green.png"
);
std::process::exit(1);
}

// Convert the string to an actual path, and make sure it exists
// If the user passed in `assets/sprite/something...` then we need to strip `assets/` (the asset loader will prepend `assets/`)
let mut path = PathBuf::from(args[0].clone());
if path.starts_with("assets") {
path = path.strip_prefix("assets").unwrap().to_path_buf();
if path.starts_with("assets/sprite") {
path = path
.strip_prefix("assets")
.unwrap()
.strip_prefix("sprite")
.unwrap()
.to_path_buf();
}
if !(PathBuf::from("assets").join(&path)).exists() {
println!("Couldn't find the file {}", path.to_string_lossy());
Expand Down
6 changes: 3 additions & 3 deletions examples/collision.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,11 @@ fn main() {

fn logic(engine_state: &mut EngineState, _: &mut ()) -> bool {
// If a collision event happened last frame, print it out and play a sound
for event in engine_state.collision_events.drain(..) {
for collision_event in engine_state.collision_events.drain(..) {
let text = engine_state.texts.get_mut("collision text").unwrap();
match event.state {
match collision_event.state {
CollisionState::Begin => {
text.value = format!("{:?}", event.pair);
text.value = format!("{:?}", collision_event.pair);
engine_state.audio_manager.play_sfx(SfxPreset::Switch1, 1.0)
}
CollisionState::End => {
Expand Down
4 changes: 2 additions & 2 deletions examples/music.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@ fn main() {
);
msg.translation.y = 50.0;

let msg2 = game.add_text_with_font(
let msg2 = game.add_text(
"msg2",
"engine_state.audio_manager.play_music(MusicPreset::Classy8Bit, 1.0);",
"FiraMono-Medium.ttf",
);
msg2.translation.y = -50.0;
msg2.font = "FiraMono-Medium.ttf".to_string();

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

Expand Down
4 changes: 2 additions & 2 deletions examples/sfx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@ fn main() {
);
msg.translation.y = 50.0;

let msg2 = game.add_text_with_font(
let msg2 = game.add_text(
"msg2",
"engine_state.audio_manager.play_sfx(SfxPreset::Jingle1, 1.0);",
"FiraMono-Medium.ttf",
);
msg2.translation.y = -50.0;
msg2.font = "FiraMono-Medium.ttf".to_string();

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

Expand Down
4 changes: 2 additions & 2 deletions examples/sound.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@ fn main() {
);
msg.translation.y = 100.0;

let msg2 = game.add_text_with_font(
let msg2 = game.add_text(
"msg2",
"engine_state.audio_manager.play_sfx(\"sfx/congratulations.ogg\", 1.0);",
"FiraMono-Medium.ttf",
);
msg2.translation.y = -100.0;
msg2.font = "FiraMono-Medium.ttf".to_string();

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

Expand Down
9 changes: 5 additions & 4 deletions examples/text.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ rusty_engine::init!(GameState);

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

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

let font_msg = game.add_text_with_font(
let font_msg = game.add_text(
"font_msg",
"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.",
"FiraMono-Medium.ttf",
"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."
);
font_msg.font_size = 20.0;
font_msg.font = "FiraMono-Medium.ttf".to_string();
font_msg.translation.y = 0.0;

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.");
Expand Down
1 change: 1 addition & 0 deletions release.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ tag-message = "{{tag_name}}"
tag-name = "{{prefix}}v{{version}}"
pre-release-replacements = [
{ file = "README.md", search = "rusty_engine = \".*\"", replace = "rusty_engine = \"{{version}}\"", exactly = 1 },
{ file = "tutorial/src/05-config.md", search = "rusty_engine = \".*\"", replace = "rusty_engine = \"{{version}}\"", exactly = 1 },
{ file = "CHANGELOG.md", search = "Unreleased", replace = "{{version}}", min = 1 },
{ file = "CHANGELOG.md", search = "\\.\\.\\.HEAD", replace = "...{{tag_name}}", exactly = 1 },
{ file = "CHANGELOG.md", search = "ReleaseDate", replace = "{{date}}", min = 1 },
Expand Down
40 changes: 9 additions & 31 deletions src/game.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,7 @@ pub struct EngineState {
/// [`add_sprite`](EngineState::add_sprite) method. Modify & remove sprites as you like.
pub sprites: HashMap<String, Sprite>,
/// SYNCED - The state of all texts this frame. For convenience adding a text, use the
/// [`add_text`](EngineState::add_text) or
/// [`add_text_with_font`](EngineState::add_text_with_font) methods. Modify & remove
/// text as you like.
/// [`add_text`](EngineState::add_text) method. Modify & remove text as you like.
pub texts: HashMap<String, Text>,
/// SYNCED - If set to `true`, the game exits. Note: the current frame will run to completion first.
pub should_exit: bool,
Expand Down Expand Up @@ -91,7 +89,7 @@ pub struct EngineState {
pub time_since_startup_f64: f64,
/// A struct with methods to play sound effects and music
pub audio_manager: AudioManager,
/// INFO - Window dimensions in pixels
/// INFO - Window dimensions in logical pixels
pub window_dimensions: Vec2,
}

Expand Down Expand Up @@ -132,31 +130,6 @@ impl EngineState {
// Unwrap: Can't crash because we just inserted the text
self.texts.get_mut(&label).unwrap()
}

#[must_use]
/// Add a [`Text`] with a specific font. Use the `&mut Text` that is returned to set the
/// translation, rotation, etc. Use a unique label for each text. Attempting to add two texts
/// with the same label will crash. The `font` parameter should be the filename of a font
/// located in the assets/fonts directory. The default font is "FiraSans-Bold.ttf".
pub fn add_text_with_font<L, T, F>(&mut self, label: L, text: T, font: F) -> &mut Text
where
L: Into<String>,
T: Into<String>,
F: Into<String>,
{
let label = label.into();
let text = text.into();
let font = font.into();
let curr_text = Text {
label: label.clone(),
value: text,
font,
..Default::default()
};
self.texts.insert(label.clone(), curr_text);
// Unwrap: Can't crash because we just inserted the sprite
self.texts.get_mut(&label).unwrap()
}
}

// startup system - grab window settings, initialize all the starting sprites
Expand All @@ -181,7 +154,7 @@ pub fn add_sprites(
) {
for (_, sprite) in engine_state.sprites.drain() {
let transform = sprite.bevy_transform();
let texture_handle = asset_server.load(sprite.filepath.clone());
let texture_handle = asset_server.load(PathBuf::from("sprite").join(&sprite.filepath));
commands.spawn().insert(sprite).insert_bundle(SpriteBundle {
material: materials.add(texture_handle.into()),
transform,
Expand All @@ -202,7 +175,7 @@ pub fn add_texts(
let transform = text.bevy_transform();
let font_size = text.font_size;
let text_string = text.value.clone();
let font_path = format!("fonts/{}", text.font);
let font_path = format!("font/{}", text.font);
commands.spawn().insert(text).insert_bundle(Text2dBundle {
text: BevyText::with_section(
text_string,
Expand Down Expand Up @@ -527,6 +500,11 @@ fn game_logic_sync(
if text.font_size != bevy_text_component.sections[0].style.font_size {
bevy_text_component.sections[0].style.font_size = text.font_size;
}
let font_path = format!("font/{}", text.font);
let font = asset_server.load(font_path.as_str());
if bevy_text_component.sections[0].style.font != font {
bevy_text_component.sections[0].style.font = font;
}
} else {
commands.entity(entity).despawn();
}
Expand Down
18 changes: 9 additions & 9 deletions src/mouse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,17 +87,17 @@ impl MouseState {
pub fn just_released(&self, mouse_button: MouseButton) -> bool {
self.just_released.contains(&mouse_button)
}
/// Returns an iterator over all the mouse buttons were being pressed at the end of the last frame
pub fn get_pressed(&self) -> impl ExactSizeIterator<Item = &MouseButton> {
self.pressed.iter()
/// Returns true if any of the indicated mouse buttons were pressed
pub fn pressed_any(&self, mouse_buttons: &[MouseButton]) -> bool {
mouse_buttons.iter().any(|k| self.pressed(*k))
}
/// Returns an iterator over all the mouse buttons that started being pressed at the end of the last frame
pub fn get_just_pressed(&self) -> impl ExactSizeIterator<Item = &MouseButton> {
self.just_pressed.iter()
/// Returns true if any of the indicated mouse buttons were just pressed this frame
pub fn just_pressed_any(&self, mouse_buttons: &[MouseButton]) -> bool {
mouse_buttons.iter().any(|k| self.just_pressed(*k))
}
/// Returns an iterator over all the mouse buttons that started being released at the end of the last frame
pub fn get_just_released(&self) -> impl ExactSizeIterator<Item = &MouseButton> {
self.just_released.iter()
/// Returns true if any of the indicated mouse buttons were just released this frame
pub fn just_released_any(&self, mouse_buttons: &[MouseButton]) -> bool {
mouse_buttons.iter().any(|k| self.just_released(*k))
}
}

Expand Down
Loading