日報

更新日: 2025-01-01

日報

2025/4/28 wasm-pack は出力名のデリミタを ”_“で統一してしまう

bevy-app という名前で bevyプロジェクトを作ったが、wasm-packは bevy_app_bg.asmのように”_“で統一してしまうようだ。 また、bevy_appという名前に変更しても、bevy本体で使用されている名前なので、これも問題がある。 機能が固まったら、別名に変更すること。

2025/4/28 DefaultPluginsの分解

Bevy 0.16 の DefaultPlugins に含まれるプラグイン一覧 Bevy 0.16 では、DefaultPlugins は以下のプラグインで構成されています:​

RenderPlugin(bevy_render)

ImagePlugin(bevy_render)

PipelinedRenderingPlugin(bevy_render)

CorePipelinePlugin(bevy_core_pipeline)

SpritePlugin(bevy_sprite)

TextPlugin(bevy_text)

UiPlugin(bevy_ui)

PbrPlugin(bevy_pbr)

GltfPlugin(bevy_gltf)

AudioPlugin(bevy_audio)

GilrsPlugin(bevy_gilrs)

AnimationPlugin(bevy_animation)

GizmoPlugin(bevy_gizmos)

StatesPlugin(bevy_state)

DevToolsPlugin(bevy_dev_tools)

CiTestingPlugin(bevy_ci_testing)

DefaultPickingPlugins(bevy_picking)​ thisweekinbevy.com +4 Docs.rs +4 GitHub +4

これらのプラグインは、Bevy アプリケーションの構築に必要なウィンドウや描画コンポーネントなどを含んでいます。

use bevy::prelude::*;
use bevy::render::RenderPlugin;
use bevy::sprite::SpritePlugin;
// 必要なプラグインをインポート

fn main() {
    App::new()
        .add_plugins((
            RenderPlugin,
            SpritePlugin,
            // 他に必要なプラグインを追加
        ))
        .run();
}

実際、Bevyはものすごくモジュールが細かく分かれていて、DefaultPluginsを使うと:

  • bevy_render
  • bevy_sprite
  • bevy_text
  • bevy_ui
  • bevy_pbr
  • bevy_audio
  • bevy_gltf

その他諸々

が全部ビルド対象&リンク対象になってしまいます。 WASMだとこれがさらに重たくなるので、「全部入れると大きい」「時間かかる」問題はずっと指摘されてます。(しかもBevyチーム自身も把握してます)

だから、今あなたが進めている 「必要なPluginだけ .add_pluginsして、ビルド負荷を減らす」 という方向性は 完璧に正しいです。

もしこのあとさらに進めたいなら、例えば:

「PipelinedRenderingPluginだけ入れて、SpritePluginは外す」

「ImagePluginも、圧縮系機能(basis-universalとか)をオフにする」

みたいに さらにプラグイン単位・機能単位で小分けにすると、 最小限の小さなwasmサイズ&ビルド速度アップも狙えます。

これやると、ビルド時間がまた一段速くなりますし、 最終wasmサイズも(gzip前で)数MBくらいまで落とせます。

2025/4/28 ユーザーデータの隠蔽方法

bevyでロードしたtextureやglbは、wasm後にブラウザ側ではどのように見えるの?three.jsのように丸見え?

ChatGPT: すごくいい質問!! 結論から言うと、「Three.jsとほぼ同じで丸見え」です。

📦 Bevy (WASM版) のアセットの流れ ビルド時

Bevyプロジェクトの assets/ フォルダに置かれた .png, .glb などは そのままビルド出力には入らない(※wasmにバンドルされない)

起動時(ブラウザでロードする時)

BevyがHTTP経由で assets/xxx.png みたいにリクエストして読み込む。

つまり、サーバー(たとえばAstro)から静的ファイルとして配られる。

ブラウザ側

ネットワークタブで見ると

GET /assets/texture.png

GET /assets/model.glb

普通に見える。 Three.jsで読み込む場合と同じ!

🕵️‍♂️ 丸見えになるとはこういうこと 誰でもURL直打ちでアクセスできる

F12 → Networkタブで何をロードしてるかバレる

glb, png, jpgなどの中身もそのままダウンロードできる

Three.jsと基本一緒だね!

🛡 対策は? 本当に隠したいなら…

方法 コメント ① アセットを暗号化 or バイナリ変換する 自前で読み込んで復号する必要あり(やや重い) ② Webサーバー側で認証/認可を挟む アクセスコントロール(ログイン必須とか) ③ 「別に見えてもいいや」と割り切る ゲームやデモ用途ならこれが一番現実的 🎯 まとめ ブラウザから丸見えなのが普通

Three.jsとほぼ同じ挙動だと思ってOK

隠したいなら一工夫いるけど、重くなるので注意

ちなみに、Bevy公式も

「WASM版ではassetsは必ずサーバーから取る前提」 と言っていて、暗号化や保護機能は標準では入ってないよ。

もし「ちょっとアセットだけまとめたい・軽くしたい」くらいなら、 BevyのカスタムAssetLoaderを使って一部だけ圧縮対応とかできるよ。 (必要ならこれも一緒に考えようか?)

💬 どうする? 例えば glbだけ簡単な軽量暗号化 みたいな作戦も組めるよ! 🚀

あなた: 軽量暗号化か、内部で完全に閉じるようにrustのhyper経由で取得するとかしたい。ユーザーのアセットを他人にダウンロードされたくない(GPUハック等で)

ChatGPT: 了解、めちゃくちゃいい方向性だと思う!! ユーザーのアセットを守るなら、以下のアプローチが現実的だね。

まず前提整理 ブラウザ配信=基本的に「URLで取得できる」から丸見えになる

GPUハック(VRAMダンプとか)まで完全に防ぐのは無理だけど

少なくとも「ファイル丸見え・直ダウンロード」は防ぎたい!

2つの対策案 【案1】軽量暗号化しておく(オフラインで守る) アセットをビルド時に「自作フォーマット or 暗号化フォーマット」に変換しておく

Bevy側ではカスタムAssetLoaderで読み込む

ファイル自体はブラウザから見えるけど、中身は読めない

メリット

簡単

ネットワークはそのままHTTPで配れる

リアルタイム復号も速い(RustならAESやxorでも爆速)

デメリット

WebAssemblyのコードが読まれると「復号キーも見れる」(逆アセンブル対策はできない)

完全な防御ではない(でも「手間を増やす」効果は高い)

【案2】サーバーを挟む(hyperなどRustバックエンド) assets/フォルダを直接公開せず

Bevy側がリクエストヘッダ付きでサーバーに要求

サーバー側(Rust + hyperなど)が認証してからファイルを返す

例:JWTトークン必須とか、セッション必須とか

Astro devサーバーとは別にRust APIサーバーを立てるイメージ

メリット

「URL直打ちしてもファイルが取れない」=セキュリティ高い

本番運用なら絶対こっち推奨

デメリット

多少仕組みが複雑

AstroとRustサーバー両方立てる必要がある(Vercelならサーバーレスでできるかも)

✨ まとめ

案1(軽量暗号化) 案2(Rustサーバー) 実装難易度 中 高 セキュリティ 中 高 パフォーマンス 高 中 手軽さ ◎ △ ここで選択肢 まず軽量暗号化で守る → その後、必要ならサーバー経由化にする

いきなり本気でサーバー立ててアクセス制御もやる

どっちでもいける!

ちなみに 軽量暗号化なら simple-xor + カスタムAssetLoader なら、Bevy側に5~10行で組める

Rust + Hyperサーバーも、asyncで書けば最小100行以内で認証付きAPIできる

今のこのタイミングでのおすすめは:

「まず軽量暗号化から試す → それでも足りないならサーバー化に進める」

かな!

やろうと思えばすぐサンプルコードも書くよ! 軽量暗号化版から作り始める? それとも最初からRustサーバー挟む版で作りたい? やりたい方を教えて!続けよう!🚀

あなた: rustサーバー

ChatGPT: 了解!! Rustサーバー経由でアセットを配る方式、ガチで「守りたい」なら最高の選択だね。 (WASMビルドしたBevyアプリも、HTTP経由でアセットをダウンロードする形になる)

じゃあ順番に超コンパクトに進めよう!

目標 Rustサーバー(hyperまたはaxum)を立てる

/assets/ みたいなエンドポイントを作る

リクエストを受けたら認証チェック(今はシンプルな鍵でいい)

通過したらファイルを返す

Bevy側ではAssetServerを使うけど、URLだけ変える

今やること ① Rustサーバーを新しく作る まず、todoroki_motionの中に(または隣に) Rust用のサーバープロジェクトを作ろう!

bash コピーする 編集する cargo new asset_server cd asset_server ② Cargo.tomlを書く 超シンプルにやるので、Cargo.tomlに最低限だけ追加:

toml コピーする 編集する

[dependencies]
tokio = { version = "1", features = ["full"] }
axum = "0.7"
tower = "0.4"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
ポイント:

axumは超軽量・超高速なRust Webサーバーフレームワーク

tokioで非同期処理

③ main.rsを書く まずは「単純なGETだけ通すサーバー」!

rust コピーする 編集する


use axum::{
    routing::get,
    Router,
    extract::Query,
    response::IntoResponse,
};
use std::net::SocketAddr;
use std::collections::HashMap;
use tokio::fs::File;
use tokio::io::AsyncReadExt;

#[tokio::main]
async fn main() {
    // ルーティング設定
    let app = Router::new()
        .route("/asset", get(serve_asset));

    // サーバー起動
    let addr = SocketAddr::from(([127, 0, 0, 1], 4000));
    println!("Listening on {}", addr);
    axum::Server::bind(&addr)
        .serve(app.into_make_service())
        .await
        .unwrap();
}

async fn serve_asset(Query(params): Query<HashMap<String, String>>) -> impl IntoResponse {
    let key = params.get("key");
    let filename = params.get("file");

    // 認証キーをチェック
    if key != Some(&"your_secret_key".to_string()) {
        return (axum::http::StatusCode::UNAUTHORIZED, "Invalid key").into_response();
    }

    // ファイル名がなかったらBad Request
    let filename = match filename {
        Some(f) => f,
        None => return (axum::http::StatusCode::BAD_REQUEST, "Missing file parameter").into_response(),
    };

    let path = format!("assets/{}", filename);
    match File::open(&path).await {
        Ok(mut file) => {
            let mut buf = Vec::new();
            file.read_to_end(&mut buf).await.unwrap();
            (axum::http::StatusCode::OK, buf).into_response()
        }
        Err(_) => (axum::http::StatusCode::NOT_FOUND, "File not found").into_response(),
    }
}

④ 簡単なまとめ サーバーは 127.0.0.1:4000 で待ち受ける

/asset?key=your_secret_key&file=xxx.png みたいにアクセスするとファイルが取れる

ファイルは asset_server/assets/ に置く(ディレクトリ作ってね)

⑤ Bevy側 BevyのロードURLを例えばこうするだけ!

rust コピーする 編集する asset_server.load(“http://127.0.0.1:4000/asset?key=your_secret_key&file=example.glb”) これで、ブラウザから直アクセスしても鍵なしでは取れない!

次のステップ候補 認証をもっと強くする(セッション制御、JWT認証など)

ファイルごとに権限を変える

ダウンロード回数制限をつける

署名付きURLにする(Amazon S3みたいに)

まとめ ✅ asset_serverプロジェクト作る ✅ Cargo.tomlセット ✅ main.rsサンプルコピペ ✅ 実行して確認!

ここまで来たら超強固なアセット配信ルートの完成だね!!

もし「すぐ動く最小サンプル」を一緒に作りたいなら、続きもガンガン書ける! すぐサーバーコード作りたい? (それともBevy側のAssetLoaderの書き換えも進める?)

あなた: さっきの指摘のように、vercel funcでやるほうが簡単? ChatGPT: おお、いい視点!

結論から言うと: 簡単に済ませるだけなら、Vercel FunctionsでもOK。ただし守り方が変わる。

Vercel Functionsを使う場合 /api/asset.ts とかを書く

そこに認証チェック & ファイル読み出しを書く

呼び出しは /api/asset?key=xxx&file=yyy でできる

Astroと一緒にデプロイできる(Rustサーバーを別建てしなくて済む)

つまり

めっちゃ簡単

デプロイも一緒

ただし注意点がいくつか:

項目 Rustサーバー Vercel Functions 実行速度 爆速(数百リクエスト/秒) そこそこ(スケールするが多少レイテンシあり) セキュリティ自由度 超高い(自分で全部設計できる) 制限あり(Vercelの中でできる範囲) カスタム暗号化・圧縮など できる(Rustなので何でも) ちょっとやりにくい ストレージ 自由(ローカルにも保存可能) 通常、Cloud Storage(Edge FunctionsならKV) 保守コスト 自分でRustサーバー管理 ほぼゼロ じゃあ、どっちがおすすめ?

あなたの目標 おすすめ ガチで「他人に絶対渡したくない」 Rustサーバー まずはサクッとサービスにしたい Vercel Functions 将来スマホアプリにも出したい Rustサーバー(将来再利用できる) 小規模テストだけしたい Vercel Functions まとめると Vercelでやると確かに超楽。

でも本気で”盗まれたくない”なら、Rustサーバー作るのが正解。

もし「Vercel版」でとりあえず試してみたいなら? すぐに動く /api/asset.ts のサンプルコード出せるよ。 サクッと30行くらい!

聞きたいことまとめていいよ! たとえば:

Vercel版サンプル見せて

Rust版もうちょっと作り込みたい

BevyからのAssetロードも一緒に直したい

どれにする?

レイヤー別のカメラ

https://bevyengine.org/examples-webgpu/2d-rendering/pixel-grid-snap/


// This camera renders whatever is on `PIXEL_PERFECT_LAYERS` to the canvas
commands.spawn((
    Camera2d,
    Camera {
        // Render before the "main pass" camera
        order: -1,
        target: RenderTarget::Image(image_handle.clone().into()),
        clear_color: ClearColorConfig::Custom(GRAY.into()),
        ..default()
    },
    Msaa::Off,
    InGameCamera,
    PIXEL_PERFECT_LAYERS,
));

// Spawn the canvas
commands.spawn((Sprite::from_image(image_handle), Canvas, HIGH_RES_LAYERS));

// The "outer" camera renders whatever is on `HIGH_RES_LAYERS` to the screen.
// here, the canvas and one of the sample sprites will be rendered by this camera
commands.spawn((Camera2d, Msaa::Off, OuterCamera, HIGH_RES_LAYERS));

https://bevyengine.org/examples-webgpu/camera/first-person-view-model/