<< [System編](https://qiita.com/9laceef/items/b1e9496717e0a60e9b7e) [#4 エンティティの作成・削除](https://qiita.com/9laceef/items/04e6221ff62fa3fe1b25) >>
ご注意!
この記事の内容は2019/06/17現在の最新版amethystクレート(v0.11.0)では仕様が変わっており動きません!随時更新していきますので、しばしお待ちください。
もし更新が待てなかったり直接尋ねたいことなどありましたらコメントにてお願いいたします。
はじめに
今回は、マウスからの入力とTransform
コンポーネントを扱ってみます。クリックした位置に画像を移動させるプログラムを作ってみます。
#1でウィンドウの設定ファイルについて紹介しました。今回は画像用の設定ファイルについても扱っていきます。
サンプルコードはこちら。今回はexamples/03_move_to_mouse
になります。
spritesheet.ron
画像ファイルの1枚ごとに設定ファイルを用意します。
(
spritesheet_width: 50,
spritesheet_height: 50,
sprites: [
(
x: 0,
y: 0,
width: 50,
height: 50,
),
],
)
spritesheet_width
, spritesheet_height
に画像ファイルのサイズを指定します。sprites
には画像サイズ(左上のx座標、y座標、横幅、高さ)の配列を与えます。
main.rs
#2では最後にコード全体を紹介していましたが、見やすさのためにこれからは最初に載せようと思います。ただし、インポートやinitialise_camera
関数など使いまわすものに関しては省略させていただきます。前回までの章、もしくはサンプルコードを見てください。
基本的に長いので最初は流し読みで大丈夫です。
struct Icon;
impl Component for Icon {
type Storage = DenseVecStorage<Self>;
}
struct ExampleState;
impl SimpleState for ExampleState {
fn on_start(&mut self, data: StateData<'_, GameData<'_, '_>>) {
let world = data.world;
initialise_camera(world);
world.register::<Icon>();
initialise_icon(world);
}
fn handle_event(
&mut self,
_: StateData<'_, GameData<'_, '_>>,
event: StateEvent,
) -> SimpleTrans {
/* body omitted */
}
}
struct MoveSystem;
impl<'s> System<'s> for MoveSystem {
type SystemData = (
ReadStorage<'s, Icon>,
WriteStorage<'s, Transform>,
Read<'s, InputHandler<String, String>>,
);
fn run(&mut self, (icons, mut transforms, input): Self::SystemData) {
if input.button_is_down(Button::Mouse(MouseButton::Left)) {
if let Some((x, y)) = input.mouse_position() {
for (_icon, transform) in (&icons, &mut transforms).join() {
transform.set_xyz(x as f32, 500.0 - y as f32, 0.0);
}
}
}
}
}
fn main() -> amethyst::Result<()> {
amethyst::start_logger(Default::default());
let pipe = Pipeline::build().with_stage(
Stage::with_backbuffer()
.clear_target([1.0; 4], 1.0)
.with_pass(DrawFlat2D::new().with_transparency(
ColorMask::all(),
ALPHA,
Some(DepthMode::LessEqualWrite)
))
);
let config = DisplayConfig::load("./examples/03_move_to_mouse/config.ron");
let render_bundle = RenderBundle::new(pipe, Some(config));
let input_bundle = InputBundle::<String, String>::new();
let transform_bundle = TransformBundle::new();
let game_data = GameDataBuilder::new()
.with_bundle(render_bundle.with_sprite_sheet_processor())?
.with_bundle(input_bundle)?
.with_bundle(transform_bundle)?
.with(MoveSystem, "move-system", &[]);
Application::new(
"./examples/03_move_to_mouse/",
ExampleState,
game_data
)?.run();
Ok(())
}
fn initialise_camera(world: &mut World) {
/* body omitted */
}
fn initialise_icon(world: &mut World) {
let sprite_sheet_handle = {
let loader = world.read_resource::<Loader>();
let texture_handle = {
let texture_storage = world.read_resource::<AssetStorage<Texture>>();
loader.load(
"icon.png",
PngFormat,
TextureMetadata::srgb_scale(),
(),
&texture_storage,
)
};
let sprite_sheet_store = world.read_resource::<AssetStorage<SpriteSheet>>();
loader.load(
"spritesheet.ron",
SpriteSheetFormat,
texture_handle,
(),
&sprite_sheet_store,
)
};
let sprite_render = SpriteRender {
sprite_sheet: sprite_sheet_handle,
sprite_number: 0,
};
let mut transform = Transform::default();
transform.set_xyz(250.0, 250.0, 0.0);
world.create_entity()
.with(Icon)
.with(sprite_render)
.with(transform)
.build();
}
struct Icon
今回はIcon
構造体を移動させるオブジェクトとします。
struct Icon;
impl Component for Icon {
type Storage = DenseVecStorage<Self>;
}
System編で解説した通り、Component
トレイトを実装させます。これによりエンティティに付加させることができるようになります。
initialise_icon
fn initialise_icon(world: &mut World) {
let sprite_sheet_handle = {
let loader = world.read_resource::<Loader>();
let texture_handle = {
let texture_storage = world.read_resource::<AssetStorage<Texture>>();
loader.load(
"icon.png",
PngFormat,
TextureMetadata::srgb_scale(),
(),
&texture_storage,
)
};
let sprite_sheet_store = world.read_resource::<AssetStorage<SpriteSheet>>();
loader.load(
"spritesheet.ron",
SpriteSheetFormat,
texture_handle,
(),
&sprite_sheet_store,
)
};
let sprite_render = SpriteRender {
sprite_sheet: sprite_sheet_handle,
sprite_number: 0,
};
let mut transform = Transform::default();
transform.set_xyz(250.0, 250.0, 0.0);
world.create_entity()
.with(Icon)
.with(sprite_render)
.with(transform)
.build();
}
Loader
に関しては以前触れましたね。いろいろなアセットを呼び出すことができるこのload
メソッドですが、今回はスプライトシートを呼び出しています。
余裕が出てくるまではコピペで良いと思います。"icon.png"
が画像のパス、"spritesheet.ron"
がスプライトシートの設定ファイルになり、sprite_number
がspritesheet.ronで設定した描画位置配列のインデックスになります。また今回はアニメーションはせずに1枚全体を使います(スプライトシートを使ったアニメーションをしてみたいと思った方は、examples/11_animation
(名前変わるかもしれません)などを参考に頑張ってみてください。いつか記事にするとは思いますが)。
余談ですが、スプライトというパラパラ漫画のようにコマ送りでアニメーションを表現する技法があり、それを1枚にまとめて位置だけをずらして描画する方法をとるため、そのまとめた画像(シート)をスプライトシートというそうです。
MoveSystem
このシステムの中で、マウスボタンが押されている間の座標の取得、Icon
の座標(Transform
)の変更を行っています。
Read
System編ではRead
とWrite
の説明は省略していました。これは型ごとに一つだけリソースを追加することができます。ストレージにエネミーオブジェクトを複数追加するような使い方ではなく、「時間」や「入力」といったリソースに対して使われます。~~Storage
と同じように、Read
は不変参照を、Write
は可変参照を呼び出します。
このプログラムではInputHandler
という、マウスやキーボードからの入力を読むためのリソースを呼び出しています。これは後ほど出てくるInputBundle
を必要とします。
struct MoveSystem;
impl<'s> System<'s> for MoveSystem {
type SystemData = (
ReadStorage<'s, Icon>,
WriteStorage<'s, Transform>,
Read<'s, InputHandler<String, String>>,
);
fn run(&mut self, (icons, mut transforms, input): Self::SystemData) {
if input.button_is_down(Button::Mouse(MouseButton::Left)) {
if let Some((x, y)) = input.mouse_position() {
for (_icon, transform) in (&icons, &mut transforms).join() {
transform.set_xyz(x as f32, 500.0 - y as f32, 0.0);
}
}
}
}
}
SystemData
は、Icon
コンポーネントへの不変参照、Transform
コンポーネントへの可変参照、そしてマウス入力を読み込むためのInputHandler
への不変参照の3つを割り当てます。
run
メソッド内にて、Icon
コンポーネントとTransform
コンポーネントをもつエンティティにのみ作用させたいので、join()
で選択します。今回は一つしか作っていませんのでfor文は1回でおわりますね。
input.mouse_position()
とtransform.set_xyz()
についてなのですが、y座標のみおかしなことになっています。というのも、Tranform
コンポーネントにとってウィンドウの下がy=0になりますが、マウス座標にとってはウィンドウ上がy=0なので、ウィンドウの縦サイズから引いて逆転させないと画面真ん中で鏡のような挙動になってしまいます。
main()
ここはこれといった変化はありません。InputHandler
を扱うためにInputBundle
を用いていますね。
fn main() -> amethyst::Result<()> {
amethyst::start_logger(Default::default());
let pipe = Pipeline::build().with_stage(
Stage::with_backbuffer()
.clear_target([1.0; 4], 1.0)
.with_pass(DrawFlat2D::new().with_transparency(
ColorMask::all(),
ALPHA,
Some(DepthMode::LessEqualWrite)
))
);
let config = DisplayConfig::load("./examples/03_move_to_mouse/config.ron");
let render_bundle = RenderBundle::new(pipe, Some(config));
let input_bundle = InputBundle::<String, String>::new();
let transform_bundle = TransformBundle::new();
let game_data = GameDataBuilder::new()
.with_bundle(render_bundle.with_sprite_sheet_processor())?
.with_bundle(input_bundle)?
.with_bundle(transform_bundle)?
.with(MoveSystem, "move-system", &[]);
Application::new(
"./examples/03_move_to_mouse/",
ExampleState,
game_data
)?.run();
Ok(())
}
上から7行目あたりで行っているpng画像の透過処理は忘れないようにしましょう。
お疲れさまでした!cargo run --example 03
で実行されたら画面の適当なところをクリックしてみたり押しながら動かしたりしてみましょう。また別の画像を用意してspritesheet.ronの使い方に慣れてみてもいいですね。
おわりに
以前デバッグのためにウィンドウタイトルを動的に変更できないかと試行錯誤していて結局できなかったのですが、ウィンドウサイズもおそらく取得できないかもしれません。そうなるとマウス座標から作用させるのにハードコーディングさせる必要が出てくるというのが厄介です...。けれどRead
でいろいろリソースを取得できるので、これで何か得られるかもしれませんね?
次回は、何らかのアクションにより後からエンティティを追加してみようと思います。
ではでは。