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
|
||||||
|
|
||||||
```
|
- Clone the repository
|
||||||
cargo run --features "vulkan"
|
|
||||||
|
```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
|
||||||
|
|
||||||
```
|
```bash
|
||||||
cargo run --features "metal"
|
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
|
#### For Linux Users
|
||||||
the `empty` feature.
|
|
||||||
|
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