Skip to content

Commit

Permalink
Port all pong examples and book to RenderingBundle
Browse files Browse the repository at this point in the history
  • Loading branch information
Frizi committed Jul 12, 2019
1 parent 6d6f143 commit 24c4c86
Show file tree
Hide file tree
Showing 11 changed files with 166 additions and 935 deletions.
8 changes: 2 additions & 6 deletions amethyst_rendy/src/plugins.rs
Original file line number Diff line number Diff line change
Expand Up @@ -153,11 +153,7 @@ impl<D: Base3DPassDef> RenderBase3D<D> {

impl<B: Backend, D: Base3DPassDef> RenderPlugin<B> for RenderBase3D<D> {
fn build<'a, 'b>(&mut self, builder: &mut DispatcherBuilder<'a, 'b>) -> Result<(), Error> {
builder.add(
VisibilitySortingSystem::new(),
"visibility_system",
&["transform_system"],
);
builder.add(VisibilitySortingSystem::new(), "visibility_system", &[]);

// TODO: We should ideally register `VertexSkinningBundle` here,
// but that would make renderer dependant on animation crate.
Expand Down Expand Up @@ -211,7 +207,7 @@ impl<B: Backend> RenderPlugin<B> for RenderFlat2D {
builder.add(
SpriteVisibilitySortingSystem::new(),
"sprite_visibility_system",
&["transform_system"],
&[],
);
Ok(())
}
Expand Down
206 changes: 50 additions & 156 deletions book/src/pong-tutorial/pong-tutorial-01.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,13 @@ You can delete everything in that file, then add these imports:
use amethyst::{
assets::Processor,
ecs::{ReadExpect, Resources, SystemData},
prelude::*,
renderer::{
pass::DrawFlat2DDesc, types::DefaultBackend, Factory, Format, GraphBuilder, GraphCreator,
Kind, RenderGroupDesc, RenderingSystem, SpriteSheet, SubpassBuilder,
plugins::{RenderFlat2D, RenderToWindow},
types::DefaultBackend,
RenderingBundle, SpriteSheet,
},
utils::application_root_dir,
window::{ScreenDimensions, Window, WindowBundle},
};
```

Expand Down Expand Up @@ -167,22 +166,17 @@ let display_config_path = app_root.join("resources").join("display_config.ron");
# }
```

## Opening a window
## Creating an application

After preparing the display config, it's time to actually use it. To do that,
we have to create an amethyst application scaffolding and tell it to open a window for us.

In `main()` in `main.rs` we are going to add the basic application setup:

```rust,edition2018,no_run,noplaypen
# extern crate amethyst;
# use amethyst::{prelude::*, window::*};
# use amethyst::{prelude::*};
# fn main() -> Result<(), amethyst::Error>{
# let display_config_path = "";
# struct Pong; impl SimpleState for Pong {}
let game_data = GameDataBuilder::default()
// The WindowBundle provides all the scaffolding for opening a window
.with_bundle(WindowBundle::from_config_path(display_config_path))?;
let game_data = GameDataBuilder::default();
# let app_root = std::path::PathBuf::from(".");
let assets_dir = app_root.join("assets");
Expand All @@ -192,13 +186,9 @@ game.run();
# }
```

Here we're creating a new `WindowBundle` that uses the config we prepared above.
That bundle is being used as a part of `GameDataBuilder`, a central repository
of all the game logic that runs periodically during the game runtime.

> **Note:** We will cover systems and bundles in more details later, for now, think of the
> bundle as a group of functionality that together provides a certain feature to the engine.
> You will surely be writing your own bundles for your own game's features soon.
Here we're creating a new instance of `GameDataBuilder`, a central repository
of all the game logic that runs periodically during the game runtime. Right now it's empty,
but soon we will start adding there all sorts of systems and bundles that run our game code.

That builder is then combined with the game state struct (`Pong`), creating the overarching
Amethyst's root object: [Application][ap]. It binds the OS event loop, state machines,
Expand All @@ -208,159 +198,63 @@ Then we call `.run()` on `game` which starts the game loop. The game will
continue to run until our `SimpleState` returns `Trans::Quit`, or when all states
have been popped off the state machine's stack.

Try compiling the code now. You should be able to see the window already.
The content of that window right now is undefined and up to the operating system.
It's time to start drawing on it.
Try compiling the code now. You should be able to see that application starts but nothing
happens and it hangs your terminal until you kill the process. That means the core game loop
is running and awaiting for tasks to run. Let's give it something to do by adding a renderer.

## Setting up basic rendering

Now, let's define some rendering code so we can keep moving. This part is not strictly
necessary to show a window, but we need the renderer to display anything inside it.

We'll cover rendering in more depth later in this tutorial, but for now place the
following code _below_ the `main()` function:
After preparing the display config and application scaffolding, it's time to actually use it.
Last time we left our `GameDataBuilder` instance empty, now we can add some systems to it.

```rust,edition2018,no_run,noplaypen
# extern crate amethyst;
# use amethyst::{
# assets::Processor,
# ecs::{ReadExpect, Resources, SystemData},
# prelude::*,
# renderer::{
# pass::DrawFlat2DDesc, types::DefaultBackend, Factory, Format, GraphBuilder, GraphCreator,
# Kind, RenderGroupDesc, RenderingSystem, SpriteSheet, SubpassBuilder,
# },
# utils::application_root_dir,
# window::{ScreenDimensions, Window, WindowBundle},
# };
// This graph structure is used for creating a proper `RenderGraph` for rendering.
// A renderGraph can be thought of as the stages during a render pass. In our case,
// we are only executing one subpass (DrawFlat2D, or the sprite pass). This graph
// also needs to be rebuilt whenever the window is resized, so the boilerplate code
// for that operation is also here.
#[derive(Default)]
struct ExampleGraph {
dimensions: Option<ScreenDimensions>,
dirty: bool,
}
impl GraphCreator<DefaultBackend> for ExampleGraph {
// This trait method reports to the renderer if the graph must be rebuilt, usually because
// the window has been resized. This implementation checks the screen size and returns true
// if it has changed.
fn rebuild(&mut self, res: &Resources) -> bool {
// Rebuild when dimensions change, but wait until at least two frames have the same.
let new_dimensions = res.try_fetch::<ScreenDimensions>();
use std::ops::Deref;
if self.dimensions.as_ref() != new_dimensions.as_ref().map(|d| d.deref()) {
self.dirty = true;
self.dimensions = new_dimensions.map(|d| d.clone());
return false;
}
return self.dirty;
}
// This is the core of a RenderGraph, which is building the actual graph with subpasses and target
// images.
fn builder(
&mut self,
factory: &mut Factory<DefaultBackend>,
res: &Resources,
) -> GraphBuilder<DefaultBackend, Resources> {
use amethyst::renderer::rendy::{
graph::present::PresentNode,
hal::command::{ClearDepthStencil, ClearValue},
};
self.dirty = false;
// Retrieve a reference to the target window, which is created by the WindowBundle
let window = <ReadExpect<'_, Window>>::fetch(res);
let dimensions = self.dimensions.as_ref().unwrap();
let window_kind = Kind::D2(dimensions.width() as u32, dimensions.height() as u32, 1, 1);
// Create a new drawing surface in our window
let surface = factory.create_surface(&window);
let surface_format = factory.get_surface_format(&surface);
// Begin building our RenderGraph
let mut graph_builder = GraphBuilder::new();
let color = graph_builder.create_image(
window_kind,
1,
surface_format,
// clear screen to black
Some(ClearValue::Color([0.0, 0.0, 0.0, 1.0].into())),
);
let depth = graph_builder.create_image(
window_kind,
1,
Format::D32Sfloat,
Some(ClearValue::DepthStencil(ClearDepthStencil(1.0, 0))),
);
// Create our single `Subpass`, which is the DrawFlat2D pass.
// We pass the subpass builder a description of our pass for construction
let pass = graph_builder.add_node(
SubpassBuilder::new()
.with_group(DrawFlat2DDesc::new().builder())
.with_color(color)
.with_depth_stencil(depth)
.into_pass(),
);
// Finally, add the pass to the graph
let _present = graph_builder
.add_node(PresentNode::builder(factory, surface, color).with_dependency(pass));
graph_builder
}
}
```

Here we are creating an `ExampleGraph` struct and implementing a `GraphCreator` trait for it.
This trait is responsible for setting up all the details of our rendering pipeline.

> **Note:** This setup code is directly using `rendy` crate to define the rendering.
> You can read about its concepts in the [rendy graph docs][graph].
The important thing to note is that this renders a black background.
It is also ready to draw 2D sprites for us, which we will use in the next chapter.

If you want to use a different background color, you can tweak the RGBA
values inside `ClearValue::Color`. Values range from `0.0` to `1.0`,
so to get that cool green color you can try `[0.00196, 0.23726, 0.21765, 1.0]`.

Now let's pack everything up and run it back in the `main()` function. We have to
expand the existing `GameDataBuilder` with `RenderingSystem` that uses our graph:

```rust,ignore
# use amethyst::{prelude::*};
# fn main() -> Result<(), amethyst::Error>{
let game_data = GameDataBuilder::default()
// The WindowBundle provides all the scaffolding for opening a window
.with_bundle(WindowBundle::from_config_path(display_config_path))?
// A Processor system is added to handle loading spritesheets.
.with(
Processor::<SpriteSheet>::new(),
"sprite_sheet_processor",
&[],
)
// The renderer must be executed on the same thread consecutively, so we initialize it as thread_local
// which will always execute on the main thread.
.with_thread_local(RenderingSystem::<DefaultBackend, _>::new(
ExampleGraph::default(),
));
.with_bundle(
RenderingBundle::<DefaultBackend>::new()
// The RenderToWindow plugin provides all the scaffolding for opening a window and drawing on it
.with_plugin(
RenderToWindow::from_config_path(display_config_path)
.with_clear([0.0, 0.0, 0.0, 1.0]),
)
// RenderFlat2D plugin is used to render entities with `SpriteRender` component.
.with_plugin(RenderFlat2D::default()),
)?;
# Ok(()) }
```

let assets_dir = app_root.join("assets/");
Here we are adding a system `Processor::<SpriteSheet>`. This system is responsible for making
sure that any `SpriteSheet` assets that we will load later are actually being loaded and ready to use.

let mut game = Application::new(assets_dir, Pong, game_data)?;
game.run();
```
After that, we are adding a `RenderingBundle`. Bundles are essentially sets of systems
preconfigured to work together, so you don't have to write them all down one by one.

> **Note:** We will cover systems and bundles in more details later, for now, think of the
> bundle as a group of functionality that together provides a certain feature to the engine.
> You will surely be writing your own bundles for your own game's features soon.
The `RenderingBundle` is actually a bit special, as it doesn't really do anything on its own,
but relies on it's own plugin system to define what should be rendered and how. We have to use
`with_plugin` method to tell it that we want `RenderToWindow` and `RenderFlat2D` plugins.
Those plugins will equip our renderer with ability to open a window and draw 2D sprites on it.

In this configuration, our window will have a black background.
If you want to use a different color, you can tweak the RGBA
values inside `with_clear`. Values range from `0.0` to `1.0`, to get that cool green color
you can try `[0.00196, 0.23726, 0.21765, 1.0]`.

Here we're creating a new `RenderingSystem`, adding the `ExampleGraph` we
created. Additionally we are adding a `Processor::<SpriteSheet>` system,
which will make sure that all `SpriteSheet` assets are being properly loaded.
We will learn more about those in the next chapter.
> **Note:** This setup code is using amethyst's `RenderPlugin` trait based system that
> uses `rendy` crate to define the rendering. If you plan to go beyond the rendering
> building blocks that amethyst provides out of the box, you can read about
> render graph in the [rendy graph docs][graph].
Success! Now we can compile and run this code with `cargo run` and
get a window. It should look something like this:
Expand Down
10 changes: 2 additions & 8 deletions book/src/pong-tutorial/pong-tutorial-02.md
Original file line number Diff line number Diff line change
Expand Up @@ -392,15 +392,9 @@ registering another one will look similar. You have to first import
use amethyst::core::transform::TransformBundle;
#
# use amethyst::{
# assets::Processor,
# ecs::{ReadExpect, Resources, SystemData},
# core::TransformBundle,
# prelude::*,
# renderer::{
# pass::DrawFlat2DDesc, types::DefaultBackend, Factory, Format, GraphBuilder, GraphCreator,
# Kind, RenderGroupDesc, RenderingSystem, SpriteSheet, SubpassBuilder,
# },
# utils::application_root_dir,
# window::{ScreenDimensions, Window, WindowBundle},
# };
#
# struct Pong;
Expand Down Expand Up @@ -597,7 +591,7 @@ the right one is flipped horizontally.
```rust,edition2018,no_run,noplaypen
# extern crate amethyst;
# use amethyst::ecs::World;
# use amethyst::{assets::Handle, renderer::sprite::{SpriteRender, SpriteSheet}};
# use amethyst::{assets::Handle, renderer::{SpriteRender, SpriteSheet}};
# fn initialise_paddles(world: &mut World, sprite_sheet: Handle<SpriteSheet>) {
// Assign the sprites for the paddles
let sprite_render = SpriteRender {
Expand Down
Loading

0 comments on commit 24c4c86

Please sign in to comment.