Open13

作業メモ:bevy 0.14 → 0.15

hyoihyoi

2024/10/23時点でv0.15.0-rc.1が公開されていたのでマイグレーションしてみる
2024/10/28時点でv0.15.0-rc.2が公開されていたのでマイグレーションしてみる
2024/11/06時点でv0.15.0-rc.3が公開されていたのでマイグレーションしてみる
2024/11/20からMaster branchでマイグレーションしてみる

  • MsaaがResourceからComponentへ変更された。
    そのため以前の様にapp.insert_resource( Msaa::Sample4 );と書けない。
    Cameraをspawnする時にMsaaをセットする。
0.14 の Msaa の設定
use bevy::prelude::*;
fn main()
{   App::new()
        .insert_resource( Msaa::Sample4 )
        .add_plugins( DefaultPlugins )
        .run();
}
0.15 の Msaa の設定
use bevy::prelude::*;
fn main()
{   App::new()
        .add_plugins( DefaultPlugins )
        .add_systems( Startup, spawn_camera )
        .run();
}
fn spawn_camera( mut cmds: Commands )
{   cmds.spawn( ( Camera3d::default(), Msaa::Sample4 ) );
}
  • Camera2dBundleが非推奨になった。代わりにCamera2dを使う。
    なおCamera2d::default()と書くとclippyにwarningをもらうのでCamera2dだけでいいらしい。
  • Camera3dBundleが非推奨になった。代わりにCamera3dを使う。
  • DirectionalLightBundleが非推奨になった。代わりにDirectionalLightを使う。

✕✕が非推奨になった。代わりに○○を使う。」が多いのはv0.15で導入された新機能Required Componentsによるもので、Bundleは廃止路線のようですね。

hyoihyoi

v0.15のText UI関係は、破壊的変更が多いらしい。

  • Text2dBundleが非推奨になった。代わりにText2dを使う。
  • (未確認だけど)TextBundleも非推奨だろきっと。代わりにTextだろ絶対。
  • 各設定の場所
    • v0.14:Text(justify,linebreak)、TextSection(string)、TextStyle(font,font_size,color)
    • v0.15:TextText2d(string)、TextFont(font,font_size)、TextColor(color)、TextLayout(justify,linebreak)
0.14 で Hello, world!
use bevy::{ prelude::*, color::palettes::* };
fn main()
{   App::new()
        .add_plugins( DefaultPlugins )
        .add_systems( Startup, spawn_helloworld )
        .run();
}
fn spawn_helloworld( mut cmds: Commands )
{   cmds.spawn( Camera2dBundle::default() );

    let style = TextStyle { font_size: 100.0, color: css::YELLOW.into(), ..default() };
    let text = Text::from_section( "Hello, world!", style );

    cmds.spawn( Text2dBundle { text, ..default() } );
}
0.15 で Hello, world!
use bevy::{ prelude::*, color::palettes::* };
fn main()
{   App::new()
        .add_plugins( DefaultPlugins )
        .add_systems( Startup, spawn_helloworld )
        .run();
}
fn spawn_helloworld( mut cmds: Commands )
{   cmds.spawn( Camera2d );

    cmds.spawn( (
        Text2d::new( "Hello, world!" ),
        TextFont { font_size: 100.0, ..default() },
        TextColor ( css::YELLOW.into() ),
    ) );
}
hyoihyoi

3Dメッシュ関係のメモ。

  • PbrBundleが非推奨になった。代わりにMesh3d(形状)とMeshMaterial3d(色/テクスチャ)を組み合わせる。
v0.15 3Dの球体
use bevy::{ prelude::*, color::palettes::* };
fn main()
{   App::new()
        .add_plugins( DefaultPlugins )
        .add_systems( Startup, spawn_sphere )
        .run();
}
fn spawn_sphere
(   mut cmds: Commands,
    mut meshes: ResMut<Assets<Mesh>>,
    mut materials: ResMut<Assets<StandardMaterial>>,
)
{   //3Dカメラと光源
    let position = Vec3::new( -2.0, 2.0, 2.0 );
    cmds.spawn
    ( ( Camera3d::default(),
        Transform::from_translation( position ).looking_at( Vec3::ZERO, Vec3::Y ),
    ) );
    let position = Vec3::new( 1.0, 3.0, 2.0 );
    cmds.spawn
    ( ( DirectionalLight { illuminance: 3000.0, shadows_enabled: true, ..default() },
        Transform::from_translation( position ).looking_at( Vec3::ZERO, Vec3::Y ),
    ) );

    //3D球体
    cmds.spawn
    ( ( Mesh3d ( meshes.add( Sphere::default() ) ),
        MeshMaterial3d ( materials.add( Color::Srgba ( css::YELLOW ) ) ),
    ) );
}
hyoihyoi
  • NodeBundleが非推奨になった。代わりにNodeを使う。
  • v0.14までNodeBundleのメンバーだったStyleは、v0.15で廃止された。その中にあったWEB CSS 似のパラメータ38つはそのまま丸ごとNodeに移されている。
v0.15 Node
pub struct Node {
    pub display: Display,
    pub position_type: PositionType,
    pub overflow: Overflow,
    pub overflow_clip_margin: OverflowClipMargin,
    pub left: Val,
    pub right: Val,
    pub top: Val,
    pub bottom: Val,
    pub width: Val,
    pub height: Val,
    pub min_width: Val,
    pub min_height: Val,
    pub max_width: Val,
    pub max_height: Val,
    pub aspect_ratio: Option<f32>,
    pub align_items: AlignItems,
    pub justify_items: JustifyItems,
    pub align_self: AlignSelf,
    pub justify_self: JustifySelf,
    pub align_content: AlignContent,
    pub justify_content: JustifyContent,
    pub margin: UiRect,
    pub padding: UiRect,
    pub border: UiRect,
    pub flex_direction: FlexDirection,
    pub flex_wrap: FlexWrap,
    pub flex_grow: f32,
    pub flex_shrink: f32,
    pub flex_basis: Val,
    pub row_gap: Val,
    pub column_gap: Val,
    pub grid_auto_flow: GridAutoFlow,
    pub grid_template_rows: Vec<RepeatedGridTrack>,
    pub grid_template_columns: Vec<RepeatedGridTrack>,
    pub grid_auto_rows: Vec<GridTrack>,
    pub grid_auto_columns: Vec<GridTrack>,
    pub grid_row: GridPlacement,
    pub grid_column: GridPlacement,
}
hyoihyoi
  • TextBundleはv0.15で非推奨になり今後はTextを使うようになる。
    v0.14までTextBundleの中にあったStyleがv0.15で廃止され、その中身がNodeに移されたことにより、v0.15からはTextにCSSレイアウトを反映したい場合はNodeも明示的に設定しないといけない
v0.15 CSSレイアウトの実験
use bevy::{ prelude::*, color::palettes::* };
fn main()
{   App::new()
        .add_plugins( DefaultPlugins )
        .add_systems( Startup, spawn_css_layout )
        .run();
}
fn spawn_css_layout( mut cmds: Commands )
{   cmds.spawn( Camera2d ); //2Dカメラ

    //クロージャ
    let node_text =
        | text: &str, align_self: AlignSelf, justify_self: JustifySelf |
        {   (   Node { align_self, justify_self, ..default() }, //CSSレイアウトの反映にNodeが必要
                Text::new( text.to_string() ),
                TextFont { font_size: 30.0, ..default() },
                TextColor ( css::YELLOW.into() ),
                BackgroundColor ( css::RED.into() ),
            )
        };

    cmds.spawn
    (   Node //レイアウト用の親ノード
        {   width : Val::Percent ( 100.0 ),
            height: Val::Percent ( 100.0 ),
            display: Display::Grid, //CSSグリッドレイアウト
            grid_template_columns: RepeatedGridTrack::fr( 3, 1.0 ), //3列
            ..default()
        }
    )
    .with_children //テキスト表示用の子ノード
    (   |cmds|
        {   cmds.spawn( node_text( "TOP/LEFT"    , AlignSelf::Start , JustifySelf::Start  ) );
            cmds.spawn( node_text( "TOP"         , AlignSelf::Start , JustifySelf::Center ) );
            cmds.spawn( node_text( "TOP/RIGHT"   , AlignSelf::Start , JustifySelf::End    ) );

            cmds.spawn( node_text( "LEFT"        , AlignSelf::Center, JustifySelf::Start  ) );
            cmds.spawn( node_text( "CENTER"      , AlignSelf::Center, JustifySelf::Center ) );
            cmds.spawn( node_text( "RIGHT"       , AlignSelf::Center, JustifySelf::End    ) );

            cmds.spawn( node_text( "BOTTOM/LEFT" , AlignSelf::End   , JustifySelf::Start  ) );
            cmds.spawn( node_text( "BOTTOM"      , AlignSelf::End   , JustifySelf::Center ) );
            cmds.spawn( node_text( "BOTTOM/RIGHT", AlignSelf::End   , JustifySelf::End    ) );
        }
    );
}
hyoihyoi

ゲームパッド関係は、破壊的変更が入ってスッキリした。

  • ゲームパッドの入力を処理する関数は、v0.14では4つくらい引数を取った(全部Resource)。v0.15では1つQueryを書くだけでいいのでスッキリ。
  • 対戦ゲーム等で複数のゲームパッドを接続する場合、v0.14ではGamepadのフィールドidで識別した。v0.15ではQuery<(Entity, &Gamepad)>としてEntityで識別する。
v0.14
fn input_gamepad
(   gamepads    : Res<Gamepads>, //Queryに変わった
    input_button: Res<ButtonInput<GamepadButton>>, //要らなくなった
    axis_stick  : Res<Axis<GamepadAxis>>, //要らなくなった
    axis_button : Res<Axis<GamepadButton>>, //要らなくなった
)
{   for gamepad in gamepads.iter()
    {   if gamepad.id == xxx  //xxxはusize型で、接続時にアサインされたIDとする
        {   ....
        }
    }
}
v0.15
fn input_gamepad
(   qry_gamepads: Query<(Entity, &Gamepad)>,
)
{   for ( entity, gamepad ) in qry_gamepads
    {   if entity == xxx  //xxxはEntity型で、以下同文
        {   ....
        }
    }
}
  • GamepadButtonTypeGamepadButtonにリネームされてスッキリ。
  • gamepad.get_pressed()gamepad.pressed( .... )でボタンの状態を取得できる。
v0.15
    for ( _, gamepad ) in &qry_gamepads
    {   //十字ボタン&トリガー
        gamepad.get_pressed().for_each
        (   | &button |
            match button
            {   GamepadButton::DPadUp       => { .... },
                GamepadButton::DPadDown     => { .... },
                GamepadButton::DPadRight    => { .... },
                GamepadButton::DPadLeft     => { .... },
                GamepadButton::RightTrigger => { .... },
                GamepadButton::LeftTrigger  => { .... },
                _ => (),
            }
        );
    }
  • GamepadAxisTypeGamepadAxisにリネームされてスッキリ。
  • gamepad.get( .... )がOptionでラップされたアナログ入力の変化量を返してくれる。
v0.15
    for ( _, gamepad ) in &qry_gamepads
    {   //スティック&トリガー(アナログ入力)
        if let Some( value ) = gamepad.get( GamepadAxis::LeftStickY      ) { .... }
        if let Some( value ) = gamepad.get( GamepadAxis::LeftStickX      ) { .... }
        if let Some( value ) = gamepad.get( GamepadButton::RightTrigger2 ) { .... }
        if let Some( value ) = gamepad.get( GamepadButton::LeftTrigger2  ) { .... }
    }

hyoihyoi

WindowModeの機能が拡張されていた。

  • ウィンドウ⇔フルスクリーンを切り替えるだけだったv0.14のWindowModeに対して、v0.15では複数ディスプレイ接続中にどのモニタを使うかMonitorSelectionで選択できるようになった。
  • 使い方に悩んだら取り敢えずMonitorSelection::Primaryで良いんじゃなかろうか。
    ダライアスみたいなマルチモニターゲーム用の機能かな?
v0.14
pub enum WindowMode {
    Windowed,
    BorderlessFullscreen,
    SizedFullscreen,
    Fullscreen,
}
v0.15
pub enum WindowMode {
    Windowed,
    BorderlessFullscreen(MonitorSelection),
    SizedFullscreen(MonitorSelection),
    Fullscreen(MonitorSelection),
}
pub enum MonitorSelection {
    Current,
    Primary,
    Index(usize),
    Entity(Entity),
}
hyoihyoi

Gizmoのメモ。

  • .grid()の引数が変更された。v0.14の第一・第二引数(positionとrotation)を、v0.15ではまとめてIsometry3dで渡す。その時、positionの軸が変わるようだ。
v0.14
    gizmos.grid
    (   Vec3::Z * 0.5,
        Quat::from_rotation_x( PI * 0.5 ),
        UVec2::new( 5, 5 ),
        Vec2::splat( 1.0 ),
        css::GREEN,
    )
    .outer_edges();

v0.15
    let posi_rote = Isometry3d::new( Vec3::NEG_Y * 0.5, Quat::from_rotation_x( PI * 0.5 ) );
    gizmos.grid
    (   posi_rote,
        UVec2::new( 5, 5 ),
        Vec2::splat( 1.0 ),
        css::GREEN,
    )
    .outer_edges();
  • grid_2d()も同様。ただし2Dなので、v0.15ではIsometry2dを使ってpositionとrotationを渡す。rotationはf32ではなくRot2::radians( f32 )になる。
v0.15
    let posi_rote = Isometry2d::new( Vec2::ZERO, Rot2::radians( PI * 0.5 ) );
hyoihyoi
  • SpriteBundleが非推奨になった。代わりにSpriteを使う。
    スプライト画像のHandle<Image>が、v0.14では独立したComponetだったのに対し、v0.15ではSpriteのフィールドになっている。
v0.14
pub struct Sprite {
    pub color: Color,
    pub flip_x: bool,
    pub flip_y: bool,
    pub custom_size: Option<Vec2>,
    pub rect: Option<Rect>,
    pub anchor: Anchor,
}
pub struct SpriteBundle {
    pub sprite: Sprite,
    pub transform: Transform,
    pub global_transform: GlobalTransform,
    pub texture: Handle<Image>, //スプライト画像
    pub visibility: Visibility,
    pub inherited_visibility: InheritedVisibility,
    pub view_visibility: ViewVisibility,
}
v0.15
pub struct Sprite {
    pub image: Handle<Image>, //スプライト画像
    pub texture_atlas: Option<TextureAtlas>,
    pub color: Color,
    pub flip_x: bool,
    pub flip_y: bool,
    pub custom_size: Option<Vec2>,
    pub rect: Option<Rect>,
    pub anchor: Anchor,
    pub image_mode: SpriteImageMode,
}
hyoihyoi

Textのセクションの考え方が個人的にちょっと改悪に見える‥‥。
というのは、セクションの先頭だけ特別扱いする理由が自分には分からないからなんだけど。

  • v0.14では、TextのセクションはVecだった。要素は先頭から末尾まですべて同じ扱い。
  • v0.15では先頭要素だけTextでspawnし、残りの要素はTextSpanでpawnする。
    先頭が親要素、残りが子要素の位置づけ。
    親はそのText UIノードの代表で、CSSレイアウトのNode等を持てる。

v0.14
use bevy::{ prelude::*, color::palettes::* };
fn main()
{   App::new()
        .add_plugins( DefaultPlugins )
        .add_systems( Startup, spawn_text_sections )
        .run();
}
fn spawn_text_sections( mut cmds: Commands )
{   cmds.spawn( Camera2dBundle::default() ); //2Dカメラ

    let font_size = 60.0;
    let sections = vec!
    [   TextSection
        {   value: "first, ".to_string(),
            style: TextStyle { font_size, color: css::RED.into(), ..default() }
        },
        TextSection
        {   value: "second, ".to_string(),
            style: TextStyle { font_size, color: css::GREEN.into(), ..default() }
        },
        TextSection
        {   value: "third\n".to_string(),
            style: TextStyle { font_size, color: css::BLUE.into(), ..default() }
        },
        TextSection
        {   value: "end.".to_string(),
            style: TextStyle { font_size, color: css::BLACK.into(), ..default() }
        },
    ];
    cmds.spawn( TextBundle::from_sections( sections ) );
}
v0.15
use bevy::{ prelude::*, color::palettes::* };
fn main()
{   App::new()
        .add_plugins( DefaultPlugins )
        .add_systems( Startup, spawn_text_sections )
        .run();
}
fn spawn_text_sections( mut cmds: Commands )
{   cmds.spawn( Camera2d ); //2Dカメラ

    let font_size = 60.0;
    cmds.spawn
    ( ( Text::new( "first, " ),
        TextFont { font_size, ..default() },
        TextColor ( css::RED.into() ),
    ) )
    .with_children
    (   | cmds |
        {   cmds.spawn
            ( ( TextSpan::new( "second, " ),
                TextFont { font_size, ..default() },
                TextColor ( css::GREEN.into() ),
            ) );
            cmds.spawn
            ( ( TextSpan::new( "third\n" ),
                TextFont { font_size, ..default() },
                TextColor ( css::BLUE.into() ),
            ) );
            cmds.spawn
            ( ( TextSpan::new( "end." ),
                TextFont { font_size, ..default() },
                TextColor ( css::BLACK.into() ),
            ) );
        }
    );
}
hyoihyoi

Timeのメモ。

  • Timeのメソッドtime.elapsed_seconds_wrapped()が、v0.15ではtime.elapsed_secs_wrapped()にリネームされた。(…seconds…→…secs… 省略形にしたのか (^_^;)
hyoihyoi

Textの書き換えに、いくつか方法がある。
A) セクションが分かれていないケースTextだけのケース)
 1. Query<&mut Text, With<XXXX>>する
 2. let Ok ( mut text ) = query.get_single_mut() else { .... };とかでラップを剥く
 3. text.0 = "ZZZZ".to_string()で書き換える。
B) セクションが複数に分かれているケースText+TextSpan×n個のケース)
 1. Text部分は A) と同じ
 2. TextSpan部分もほぼ A) と同じだが、Query<&mut TextSpan, With<XXXX>>になる。
  Queryのiter()ではセクションの順序が保証されない点は要注意。順序が重要なら C) を使う。
C) インデックスを使ってセクションを指定する方法(TextTextSpanを区別しなくて済む)

v0.15 TextとTextSpanの区別なくインデックスでアクセス
use bevy::{ prelude::*, color::palettes::* };
fn main()
{   App::new()
        .add_plugins( DefaultPlugins )
        .add_systems( Startup, spawn_text_sections )
        .add_systems( Update, uapdate_text.run_if( run_once ) ) //一度だけ実行
        .run();
}
fn spawn_text_sections( mut cmds: Commands )
{   cmds.spawn( Camera2d ); //2Dカメラ

    cmds.spawn
    ( ( Text::new( "" ), //placeholder
        TextFont { font_size: 50.0, ..default() },
        TextColor ( css::RED.into() ),
    ) )
    .with_children
    (   | cmds |
        {   cmds.spawn
            ( ( TextSpan::new( "" ), //placeholder
                TextFont { font_size: 50.0, ..default() },
                TextColor ( css::GREEN.into() ),
            ) );
            cmds.spawn
            ( ( TextSpan::new( "" ), //placeholder
                TextFont { font_size: 50.0, ..default() },
                TextColor ( css::BLUE.into() ),
            ) );
        }
    );
}
fn uapdate_text
(   query: Query<Entity, With<Text>>, //Text(親)のEntityをQueryする
    mut writer: TextUiWriter,
)
{   let Ok ( entity ) = query.get_single() else { return };

    //TextとTextSpanの区別なくインデックス(0~2)でアクセスできる
    // for ( index, s ) in ( 0.. ).zip( [ "One, ", "Two, ", "Three" ] )
    // {   if let Some ( mut text ) = writer.get_text( entity, index ) { *text = s.into() }
    // }
    let mut s = [ "One, ", "Two, ", "Three" ].iter();
    writer.for_each_text( entity, | mut text | { *text = s.next().unwrap().to_string() } );
}

Textセクションの操作ちょっと複雑だなぁ。なんかまた将来 破壊的変更 入りそう ( ̄~ ̄;) 。

hyoihyoi

気付いたことメモ。

  • 子を先にspawnして親を後からspawnする場合に使うことがあるpush_children()が、v0.15ではadd_children()にリネームされているようだ。
v0.14
cmds.spawn().push_children( &[] );
v0.15
cmds.spawn().add_children( &[] );