parent
da1cbadfea
commit
988a8f9b57
@ -1,26 +1,66 @@
|
||||
# amethyst-cli-starter-2d
|
||||
<p align="center">
|
||||
<a href="https://amethyst.rs">
|
||||
<img
|
||||
alt="Amethyst"
|
||||
src="https://amethyst.rs/brand/logo-standard.svg"
|
||||
width="60"
|
||||
/>
|
||||
</a>
|
||||
</p>
|
||||
<h1 align="center">
|
||||
Amethyst 2D Starter
|
||||
</h1>
|
||||
|
||||
## How to run
|
||||
This project template will get you from 0 to drawing something on the screen in no time. If you're looking for a more in-depth introduction to the engine, please have a look at [our book](https://book.amethyst.rs/stable/)!
|
||||
|
||||
To run the game, use
|
||||
## Quickstart
|
||||
|
||||
```
|
||||
cargo run --features "vulkan"
|
||||
- Clone the repository
|
||||
|
||||
```bash
|
||||
git clone https://github.com/amethyst/amethyst-starter-2d.git
|
||||
cd amethyst-starter-2d
|
||||
```
|
||||
|
||||
on Windows and Linux, and
|
||||
- Build and run the project
|
||||
|
||||
```
|
||||
cargo run --features "metal"
|
||||
```bash
|
||||
cargo run
|
||||
```
|
||||
|
||||
on macOS.
|
||||
#### For Mac Users
|
||||
|
||||
For building without any graphics backend, you can use
|
||||
This starter uses vulkan as a renderer by default. You'll want to change the backend to use `metal`, which can be done by opening the `Cargo.toml` file and changing
|
||||
|
||||
```toml
|
||||
[features]
|
||||
default = ["vulkan"]
|
||||
```
|
||||
cargo run --features "empty"
|
||||
|
||||
to
|
||||
|
||||
```toml
|
||||
[features]
|
||||
default = ["metal"]
|
||||
```
|
||||
|
||||
but be aware that as soon as you need any rendering you won't be able to run your game when using
|
||||
the `empty` feature.
|
||||
#### For Linux Users
|
||||
|
||||
You might need to install some dependencies. Please refer to [this section](https://github.com/amethyst/amethyst#dependencies) of the README for more details.
|
||||
|
||||
## Features
|
||||
|
||||
This project contains the minimum amount of code needed to draw sprites to the screen. Here's a small summary of what you'll find in the source files:
|
||||
|
||||
- `resources/display_config.ron`
|
||||
Contains the window configuration (size, title).
|
||||
|
||||
- `src/main.rs`
|
||||
Creates the render graph, adds the required bundles, builds the game data with our own state and finally, starts the game's main event loop.
|
||||
|
||||
- `src/render.rs`
|
||||
Configures the RenderGraph, which defines draw passes, color formats and so on.
|
||||
|
||||
- `src/state.rs`
|
||||
Implements the main game state. In the `on_start` hook, the camera is initialized, and the sprites that will be drawn are loaded and their entities created.
|
||||
In the `handle_event` hook, we print any keys that were pressed and close the window if the user presses escape or the OS requests that we quit.
|
||||
|
@ -0,0 +1,90 @@
|
||||
use amethyst::{
|
||||
ecs::prelude::{ReadExpect, Resources, SystemData},
|
||||
renderer::{
|
||||
pass::DrawFlat2DDesc,
|
||||
rendy::{
|
||||
factory::Factory,
|
||||
graph::{
|
||||
render::{RenderGroupDesc, SubpassBuilder},
|
||||
GraphBuilder,
|
||||
},
|
||||
hal::format::Format,
|
||||
},
|
||||
types::DefaultBackend,
|
||||
GraphCreator, Kind,
|
||||
},
|
||||
window::{ScreenDimensions, Window},
|
||||
};
|
||||
|
||||
// Window background color
|
||||
static CLEAR_COLOR: [f32; 4] = [0.34, 0.36, 0.52, 1.0];
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct RenderGraph {
|
||||
dimensions: Option<ScreenDimensions>,
|
||||
dirty: bool,
|
||||
}
|
||||
|
||||
impl GraphCreator<DefaultBackend> for RenderGraph {
|
||||
fn rebuild(&mut self, res: &Resources) -> bool {
|
||||
use std::ops::Deref;
|
||||
|
||||
// Only rebuild when dimensions have changed
|
||||
let new_dimensions = res.try_fetch::<ScreenDimensions>();
|
||||
let new_dimensions = new_dimensions.as_ref().map(|d| d.deref());
|
||||
|
||||
if self.dimensions.as_ref() != new_dimensions {
|
||||
self.dirty = true;
|
||||
self.dimensions = new_dimensions.map(|d| d.clone());
|
||||
return false;
|
||||
}
|
||||
|
||||
self.dirty
|
||||
}
|
||||
|
||||
fn builder(
|
||||
&mut self,
|
||||
factory: &mut Factory<DefaultBackend>,
|
||||
res: &Resources,
|
||||
) -> GraphBuilder<DefaultBackend, Resources> {
|
||||
use amethyst::renderer::rendy::{
|
||||
graph::present::PresentNode,
|
||||
hal::command::{ClearDepthStencil, ClearValue},
|
||||
};
|
||||
|
||||
// Since we're freshly building the graph, it will never
|
||||
// be dirty after this function is called.
|
||||
self.dirty = false;
|
||||
|
||||
let window = <ReadExpect<'_, Window>>::fetch(res);
|
||||
|
||||
let surface = factory.create_surface(&window);
|
||||
let surface_format = factory.get_surface_format(&surface);
|
||||
|
||||
let dimensions = self.dimensions.as_ref().unwrap();
|
||||
let window_kind = Kind::D2(dimensions.width() as u32, dimensions.height() as u32, 1, 1);
|
||||
|
||||
let clear_color = ClearValue::Color(CLEAR_COLOR.into());
|
||||
let clear_depth = ClearValue::DepthStencil(ClearDepthStencil(1.0, 0));
|
||||
|
||||
// Build the RenderGraph
|
||||
let mut builder = GraphBuilder::new();
|
||||
let color = builder.create_image(window_kind, 1, surface_format, Some(clear_color));
|
||||
let depth = builder.create_image(window_kind, 1, Format::D32Sfloat, Some(clear_depth));
|
||||
|
||||
// Add additional draw groups here for things like UI
|
||||
let pass = builder.add_node(
|
||||
SubpassBuilder::new()
|
||||
.with_group(DrawFlat2DDesc::new().builder()) // Draw sprites
|
||||
.with_color(color)
|
||||
.with_depth_stencil(depth)
|
||||
.into_pass(),
|
||||
);
|
||||
|
||||
// Render the result to the surface
|
||||
let present = PresentNode::builder(factory, surface, color).with_dependency(pass);
|
||||
builder.add_node(present);
|
||||
|
||||
builder
|
||||
}
|
||||
}
|
@ -0,0 +1,130 @@
|
||||
use amethyst::{
|
||||
assets::{AssetStorage, Loader},
|
||||
core::transform::Transform,
|
||||
input::{get_key, is_close_requested, is_key_down, VirtualKeyCode},
|
||||
prelude::*,
|
||||
renderer::{Camera, ImageFormat, SpriteRender, SpriteSheet, SpriteSheetFormat, Texture},
|
||||
window::ScreenDimensions,
|
||||
};
|
||||
|
||||
use log::info;
|
||||
|
||||
pub struct MyState;
|
||||
|
||||
impl SimpleState for MyState {
|
||||
// On start will run when this state is initialized. For more
|
||||
// state lifecycle hooks, see:
|
||||
// https://book.amethyst.rs/stable/concepts/state.html#life-cycle
|
||||
fn on_start(&mut self, data: StateData<'_, GameData<'_, '_>>) {
|
||||
let world = data.world;
|
||||
|
||||
// Get the screen dimensions so we can initialize the camera and
|
||||
// place our sprites correctly later. We'll clone this since we'll
|
||||
// pass the world mutably to the following functions.
|
||||
let dimensions = world.read_resource::<ScreenDimensions>().clone();
|
||||
|
||||
// Place the camera
|
||||
init_camera(world, &dimensions);
|
||||
|
||||
// Load our sprites and display them
|
||||
let sprites = load_sprites(world);
|
||||
init_sprites(world, &sprites, &dimensions);
|
||||
}
|
||||
|
||||
fn handle_event(
|
||||
&mut self,
|
||||
mut _data: StateData<'_, GameData<'_, '_>>,
|
||||
event: StateEvent,
|
||||
) -> SimpleTrans {
|
||||
if let StateEvent::Window(event) = &event {
|
||||
// Check if the window should be closed
|
||||
if is_close_requested(&event) || is_key_down(&event, VirtualKeyCode::Escape) {
|
||||
return Trans::Quit;
|
||||
}
|
||||
|
||||
// Listen to any key events
|
||||
if let Some(event) = get_key(&event) {
|
||||
info!("handling key event: {:?}", event);
|
||||
}
|
||||
|
||||
// If you're looking for a more sophisticated event handling solution,
|
||||
// including key bindings and gamepad support, please have a look at
|
||||
// https://book.amethyst.rs/stable/pong-tutorial/pong-tutorial-03.html#capturing-user-input
|
||||
}
|
||||
|
||||
// Keep going
|
||||
Trans::None
|
||||
}
|
||||
}
|
||||
|
||||
fn init_camera(world: &mut World, dimensions: &ScreenDimensions) {
|
||||
// Center the camera in the middle of the screen, and let it cover
|
||||
// the entire screen
|
||||
let mut transform = Transform::default();
|
||||
transform.set_translation_xyz(dimensions.width() * 0.5, dimensions.height() * 0.5, 1.);
|
||||
|
||||
world
|
||||
.create_entity()
|
||||
.with(Camera::standard_2d(dimensions.width(), dimensions.height()))
|
||||
.with(transform)
|
||||
.build();
|
||||
}
|
||||
|
||||
fn load_sprites(world: &mut World) -> Vec<SpriteRender> {
|
||||
// Load the texture for our sprites. We'll later need to
|
||||
// add a handle to this texture to our `SpriteRender`s, so
|
||||
// we need to keep a reference to it.
|
||||
let texture_handle = {
|
||||
let loader = world.read_resource::<Loader>();
|
||||
let texture_storage = world.read_resource::<AssetStorage<Texture>>();
|
||||
loader.load(
|
||||
"sprites/logo.png",
|
||||
ImageFormat::default(),
|
||||
(),
|
||||
&texture_storage,
|
||||
)
|
||||
};
|
||||
|
||||
// Load the spritesheet definition file, which contains metadata on our
|
||||
// spritesheet texture.
|
||||
let sheet_handle = {
|
||||
let loader = world.read_resource::<Loader>();
|
||||
let sheet_storage = world.read_resource::<AssetStorage<SpriteSheet>>();
|
||||
loader.load(
|
||||
"sprites/logo.ron",
|
||||
SpriteSheetFormat(texture_handle),
|
||||
(),
|
||||
&sheet_storage,
|
||||
)
|
||||
};
|
||||
|
||||
// Create our sprite renders. Each will have a handle to the texture
|
||||
// that it renders from. The handle is safe to clone, since it just
|
||||
// references the asset.
|
||||
(0..3)
|
||||
.map(|i| SpriteRender {
|
||||
sprite_sheet: sheet_handle.clone(),
|
||||
sprite_number: i,
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn init_sprites(world: &mut World, sprites: &[SpriteRender], dimensions: &ScreenDimensions) {
|
||||
for (i, sprite) in sprites.iter().enumerate() {
|
||||
// Center our sprites around the center of the window
|
||||
let x = (i as f32 - 1.) * 100. + dimensions.width() * 0.5;
|
||||
let y = (i as f32 - 1.) * 100. + dimensions.height() * 0.5;
|
||||
let mut transform = Transform::default();
|
||||
transform.set_translation_xyz(x, y, 0.);
|
||||
|
||||
// Create an entity for each sprite and attach the `SpriteRender` as
|
||||
// well as the transform. If you want to add behaviour to your sprites,
|
||||
// you'll want to add a custom `Component` that will identify them, and a
|
||||
// `System` that will iterate over them. See https://book.amethyst.rs/stable/concepts/system.html
|
||||
world
|
||||
.create_entity()
|
||||
.with(sprite.clone())
|
||||
.with(transform)
|
||||
.build();
|
||||
}
|
||||
}
|
Loading…
Reference in new issue