diff --git a/.github/workflows/release.yaml.template b/.github/workflows/release.yaml.template new file mode 100644 index 00000000..5325523e --- /dev/null +++ b/.github/workflows/release.yaml.template @@ -0,0 +1,316 @@ +name: Release + +on: + # Trigger this workflow when a tag is pushed in the format `v1.2.3`. + push: + tags: + # Pattern syntax: https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#filter-pattern-cheat-sheet + - "v[0-9]+.[0-9]+.[0-9]+*" + # Trigger this workflow manually via workflow dispatch. + workflow_dispatch: + inputs: + version: + description: 'Version number in the format `v1.2.3`' + required: true + type: string + +# Configure the release workflow by editing these values. +env: + # The base filename of the binary produced by `cargo build`. + cargo_build_binary_name: {{project-name}} + + # The path to the assets directory. + assets_path: assets + + # Whether to upload the packages produced by this workflow to a GitHub release. + upload_to_github: true + + # The itch.io project to upload to in the format `user-name/project-name`. + # There will be no upload to itch.io if this is commented out. + {%- if itch_username != "" %} + {%- if itch_project != "" %} + upload_to_itch: {{itch_username}}/{{itch_project}} + {%- else %} + upload_to_itch: {{itch_username}}/{{project-name}} + {%- endif %} + {%- else %} + #upload_to_itch: your-itch-username/{{project-name}} + {%- endif %} + + ############ + # ADVANCED # + ############ + + # The ID of the app produced by this workflow. + # Applies to macOS releases. + # Must contain only A-Z, a-z, 0-9, hyphen, and period: https://developer.apple.com/documentation/bundleresources/information_property_list/cfbundleidentifier + app_id: {{itch_username | kebab_case}}.{{project-name | kebab_case}} + + # The base filename of the binary in the package produced by this workflow. + # Applies to Windows, macOS, and Linux releases. + # Defaults to `cargo_build_binary_name` if commented out. + #app_binary_name: {{project-name}} + + # The name of the `.zip` or `.dmg` file produced by this workflow. + # Defaults to `app_binary_name` if commented out. + #app_package_name: {{project-name | kebab_case}} + + # The display name of the app produced by this workflow. + # Applies to macOS releases. + # Defaults to `app_package_name` if commented out. + #app_display_name: {{project-name | title_case}} + + # The short display name of the app produced by this workflow. + # Applies to macOS releases. + # Must be 15 or fewer characters: https://developer.apple.com/documentation/bundleresources/information_property_list/cfbundlename + # Defaults to `app_display_name` if commented out. + #app_short_name: {{project-name | title_case | truncate: 15, "…"}} + + # Before enabling LFS, please take a look at GitHub's documentation for costs and quota limits: + # https://docs.github.com/en/repositories/working-with-files/managing-large-files/about-storage-and-bandwidth-usage + git_lfs: false + +{%- raw %} +jobs: + # Determine the version number for this workflow. + get-version: + runs-on: ubuntu-latest + steps: + - name: Get version number from tag + id: tag + run: echo "tag=${GITHUB_REF#refs/tags/}" >> "${GITHUB_OUTPUT}" + outputs: + # Use the input from workflow dispatch, or fall back to the git tag. + version: ${{ inputs.version || steps.tag.outputs.tag }} + + # Build and package a release for each platform. + build: + needs: + - get-version + env: + version: ${{ needs.get-version.outputs.version }} + strategy: + matrix: + include: + - platform: web + targets: wasm32-unknown-unknown + profile: release + binary_ext: .wasm + package_ext: .zip + runner: ubuntu-latest + + - platform: linux + targets: x86_64-unknown-linux-gnu + profile: release-native + features: bevy/wayland + package_ext: .zip + runner: ubuntu-latest + + - platform: windows + targets: x86_64-pc-windows-msvc + profile: release-native + binary_ext: .exe + package_ext: .zip + runner: windows-latest + + - platform: macos + targets: x86_64-apple-darwin aarch64-apple-darwin + profile: release-native + app_suffix: .app/Contents/MacOS + package_ext: .dmg + runner: macos-latest + runs-on: ${{ matrix.runner }} + permissions: + # Required to create a GitHub release: https://docs.github.com/en/rest/releases/releases#create-a-release + contents: write + defaults: + run: + shell: bash + + steps: + - name: Set up environment + run: | + # Default values: + echo "app_binary_name=${app_binary_name:=${{ env.cargo_build_binary_name }}}" >> "${GITHUB_ENV}" + echo "app_package_name=${app_package_name:=${app_binary_name}}" >> "${GITHUB_ENV}" + echo "app_display_name=${app_display_name:=${app_package_name}}" >> "${GITHUB_ENV}" + echo "app_short_name=${app_short_name:=${app_display_name}}" >> "${GITHUB_ENV}" + + # File paths: + echo "app=tmp/app/${app_package_name}"'${{ matrix.app_suffix }}' >> "${GITHUB_ENV}" + echo "package=${app_package_name}-"'${{ matrix.platform }}${{ matrix.package_ext }}' >> "${GITHUB_ENV}" + + # macOS environment: + if [ '${{ matrix.platform }}' == 'macos' ]; then + echo 'MACOSX_DEPLOYMENT_TARGET=11.0' >> "${GITHUB_ENV}" # macOS 11.0 Big Sur is the first version to support universal binaries. + echo "SDKROOT=$(xcrun --sdk macosx --show-sdk-path)" >> "${GITHUB_ENV}" + fi + + - name: Checkout repository + uses: actions/checkout@v4 + with: + lfs: ${{ env.git_lfs }} + + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@stable + with: + targets: ${{ matrix.targets }} + + - name: Populate cargo cache + uses: Leafwing-Studios/cargo-cache@v2 + with: + sweep-cache: true + + - name: Install dependencies (Linux) + if: ${{ matrix.platform == 'linux' }} + run: sudo apt-get update; sudo apt-get install --no-install-recommends libasound2-dev libudev-dev libwayland-dev libxkbcommon-dev + + - name: Prepare output directories + run: rm -rf tmp; mkdir -p tmp/binary '${{ env.app }}' + + - name: Install cargo-binstall (Web) + if: ${{ matrix.platform == 'web' }} + uses: cargo-bins/cargo-binstall@v1.9.0 + + - name: Install and run trunk (Web) + if: ${{ matrix.platform == 'web' }} + run: | + cargo binstall --no-confirm trunk wasm-bindgen-cli wasm-opt + trunk build --locked --release --dist '${{ env.app }}' + + - name: Build binaries (non-Web) + if: ${{ matrix.platform != 'web' }} + run: | + for target in ${{ matrix.targets }}; do + cargo build --locked --profile='${{ matrix.profile }}' --target="${target}" --no-default-features --features='${{ matrix.features }}' + mv target/"${target}"/'${{ matrix.profile }}/${{ env.cargo_build_binary_name }}${{ matrix.binary_ext }}' tmp/binary/"${target}"'${{ matrix.binary_ext }}' + done + + - name: Add binaries to app (non-Web) + if: ${{ matrix.platform != 'web' }} + run: | + if [ '${{ matrix.platform }}' == 'macos' ]; then + lipo tmp/binary/*'${{ matrix.binary_ext }}' -create -output '${{ env.app }}/${{ env.app_binary_name }}${{ matrix.binary_ext }}' + else + mv tmp/binary/*'${{ matrix.binary_ext }}' '${{ env.app }}/${{ env.app_binary_name }}${{ matrix.binary_ext }}' + fi + + - name: Add assets to app (non-Web) + if: ${{ matrix.platform != 'web' }} + run: cp -r ./'${{ env.assets_path }}' '${{ env.app }}' || true # Ignore error if assets folder does not exist + + - name: Add metadata to app (macOS) + if: ${{ matrix.platform == 'macos' }} + run: | + cat >'${{ env.app }}/../Info.plist' < + + + + CFBundleDevelopmentRegion + en + CFBundleDisplayName + ${{ env.app_display_name }} + CFBundleExecutable + ${{ env.app_binary_name }} + CFBundleIdentifier + ${{ env.app_id }} + CFBundleName + ${{ env.app_short_name }} + CFBundleShortVersionString + ${{ env.version }} + CFBundleVersion + ${{ env.version }} + CFBundleInfoDictionaryVersion + 6.0 + CFBundlePackageType + APPL + CFBundleSupportedPlatforms + + MacOSX + + + + EOF + + - name: Package app (non-Windows) + if: ${{ matrix.platform != 'windows' }} + working-directory: tmp/app + run: | + if [ '${{ matrix.platform }}' == 'macos' ]; then + ln -s /Applications . + hdiutil create -fs HFS+ -volname '${{ env.app_package_name }}' -srcfolder . '${{ env.package }}' + else + zip --recurse-paths '${{ env.package }}' '${{ env.app_package_name }}' + fi + + - name: Package app (Windows) + if: ${{ matrix.platform == 'windows' }} + working-directory: tmp/app + shell: pwsh + run: Compress-Archive -Path '${{ env.app_package_name }}' -DestinationPath '${{ env.package }}' + + - name: Upload package to workflow artifacts + uses: actions/upload-artifact@v4 + with: + path: tmp/app/${{ env.package }} + name: package-${{ matrix.platform }} + retention-days: 1 + + - name: Upload package to GitHub release + if: ${{ env.upload_to_github == 'true' }} + uses: svenstaro/upload-release-action@v2 + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + file: tmp/app/${{ env.package }} + asset_name: ${{ env.package }} + release_name: ${{ env.version }} + tag: ${{ env.version }} + overwrite: true + + # Check if upload to itch.io is enabled. + # This is needed because the `env` context can't be used in the `if:` condition of a job: + # https://docs.github.com/en/actions/learn-github-actions/contexts#context-availability + check-upload-to-itch: + runs-on: ubuntu-latest + steps: + - name: Do nothing + run: 'true' + outputs: + target: ${{ env.upload_to_itch }} + + # Upload all packages to itch.io. + upload-to-itch: + runs-on: ubuntu-latest + needs: + - get-version + - check-upload-to-itch + - build + if: ${{ needs.check-upload-to-itch.outputs.target != '' }} + + steps: + - name: Download all packages + uses: actions/download-artifact@v4 + with: + pattern: package-* + path: tmp + + - name: Install butler + run: | + curl -L -o butler.zip 'https://broth.itch.zone/butler/linux-amd64/LATEST/archive/default' + unzip butler.zip + chmod +x butler + ./butler -V + + - name: Upload all packages to itch.io + env: + BUTLER_API_KEY: ${{ secrets.BUTLER_CREDENTIALS }} + run: | + for channel in $(ls tmp); do + ./butler push \ + --fix-permissions \ + --userversion='${{ needs.get-version.outputs.version }}' \ + tmp/"${channel}"/* \ + '${{ env.upload_to_itch }}':"${channel#package-}" + done +{% endraw -%} diff --git a/Cargo.toml.template b/Cargo.toml.template new file mode 100644 index 00000000..41c3d00f --- /dev/null +++ b/Cargo.toml.template @@ -0,0 +1,88 @@ +[package] +name = "{{project-name}}" +authors = ["{{authors}}"] +version = "0.1.0" +edition = "2021" + +[dependencies] +bevy = { version = "0.14", features = ["wayland"] } +rand = "0.8" +# Compile low-severity logs out of native builds for performance. +log = { version = "0.4", features = [ + "max_level_debug", + "release_max_level_warn", +] } +# Compile low-severity logs out of web builds for performance. +tracing = { version = "0.1", features = [ + "max_level_debug", + "release_max_level_warn", +] } + +[features] +default = [ + # Default to a native dev build. + "dev_native", +] +dev = [ + # Improve compile times for dev builds by linking Bevy as a dynamic library. + "bevy/dynamic_linking", + "bevy/bevy_dev_tools", +] +dev_native = [ + "dev", + # Enable asset hot reloading for native dev builds. + "bevy/file_watcher", + # Enable embedded asset hot reloading for native dev builds. + "bevy/embedded_watcher", +] + + +# Idiomatic Bevy code often triggers these lints, and the CI workflow treats them as errors. +# In some cases they may still signal poor code quality however, so consider commenting out these lines. +[lints.clippy] +# Bevy supplies arguments to systems via dependency injection, so it's natural for systems to +# request more than 7 arguments -- which triggers this lint. +too_many_arguments = "allow" +# Queries that access many components may trigger this lint. +type_complexity = "allow" + + +# Compile with Performance Optimizations: +# https://bevyengine.org/learn/quick-start/getting-started/setup/#compile-with-performance-optimizations + +# Enable a small amount of optimization in the dev profile. +[profile.dev] +opt-level = 1 + +# Enable a large amount of optimization in the dev profile for dependencies. +[profile.dev.package."*"] +opt-level = 3 + +# Remove expensive debug assertions due to +[profile.dev.package.wgpu-types] +debug-assertions = false + +# The default profile is optimized for Wasm builds because +# that's what [Trunk reads](https://github.com/trunk-rs/trunk/issues/605). +# Optimize for size in the wasm-release profile to reduce load times and bandwidth usage on web. +[profile.release] +# Compile the entire crate as one unit. +# Slows compile times, marginal improvements. +codegen-units = 1 +# Do a second optimization pass over the entire program, including dependencies. +# Slows compile times, marginal improvements. +lto = "thin" +# Optimize with size in mind (also try "z", sometimes it is better). +# Slightly slows compile times, great improvements to file size and runtime performance. +opt-level = "s" +# Strip all debugging information from the binary to slightly reduce file size. +strip = "debuginfo" + +# Override some settings for native builds. +[profile.release-native] +# Default to release profile values. +inherits = "release" +# Optimize with performance in mind. +opt-level = 3 +# Keep debug information in the binary. +strip = "none" diff --git a/cargo-generate.toml b/cargo-generate.toml new file mode 100644 index 00000000..fc3807e3 --- /dev/null +++ b/cargo-generate.toml @@ -0,0 +1,14 @@ +[template] +ignore = ["target", "Cargo.lock"] +include = ["*.template"] + +[hooks] +post = ["post-generate.rhai"] + +[placeholders.itch_username] +prompt = "Enter your itch.io username. Leave blank to disable itch.io upload." +type = "string" +default = "" + +[conditional.'itch_username != ""'.placeholders] +itch_project = { type = "string", prompt = "Enter the project name used in the itch.io URL. Leave blank to use this crate's `project-name`.", default = "" } diff --git a/post-generate.rhai b/post-generate.rhai new file mode 100644 index 00000000..fa194309 --- /dev/null +++ b/post-generate.rhai @@ -0,0 +1,10 @@ +// Rename template files. +file::rename(".github/workflows/release.yaml.template", ".github/workflows/release.yaml"); +file::rename("Cargo.toml.template", "Cargo.toml"); +file::rename("web/index.html.template", "web/index.html"); +file::rename("src/main.rs.template", "src/main.rs"); +file::rename("src/lib.rs.template", "src/lib.rs"); +file::rename("src/audio.rs.template", "src/audio.rs"); + +// Generate `Cargo.lock`. +system::command("cargo", ["update", "--package", variable::get("project-name")]); diff --git a/src/audio.rs.template b/src/audio.rs.template new file mode 100644 index 00000000..52129274 --- /dev/null +++ b/src/audio.rs.template @@ -0,0 +1,37 @@ +use bevy::prelude::*; + +/// An organizational marker component that should be added to a spawned [`AudioBundle`] if it is in the +/// general "music" category (ex: global background music, soundtrack, etc). +/// +/// This can then be used to query for and operate on sounds in that category. For example: +/// +/// ``` +/// use bevy::prelude::*; +/// use {{crate_name}}::audio::Music; +/// +/// fn set_music_volume(sink_query: Query<&AudioSink, With>) { +/// for sink in &sink_query { +/// sink.set_volume(0.5); +/// } +/// } +/// ``` +#[derive(Component, Default)] +pub struct Music; + +/// An organizational marker component that should be added to a spawned [`AudioBundle`] if it is in the +/// general "sound effect" category (ex: footsteps, the sound of a magic spell, a door opening). +/// +/// This can then be used to query for and operate on sounds in that category. For example: +/// +/// ``` +/// use bevy::prelude::*; +/// use {{crate_name}}::audio::SoundEffect; +/// +/// fn set_sound_effect_volume(sink_query: Query<&AudioSink, With>) { +/// for sink in &sink_query { +/// sink.set_volume(0.5); +/// } +/// } +/// ``` +#[derive(Component, Default)] +pub struct SoundEffect; diff --git a/src/lib.rs b/src/lib.rs index 354edb68..05c1b78c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -37,7 +37,7 @@ impl Plugin for AppPlugin { }) .set(WindowPlugin { primary_window: Window { - title: "bevy_quickstart".to_string(), + title: "Bevy Quickstart".to_string(), canvas: Some("#bevy".to_string()), fit_canvas_to_parent: true, prevent_default_event_handling: true, diff --git a/src/lib.rs.template b/src/lib.rs.template new file mode 100644 index 00000000..21ab1435 --- /dev/null +++ b/src/lib.rs.template @@ -0,0 +1,96 @@ +mod asset_tracking; +pub mod audio; +mod demo; +#[cfg(feature = "dev")] +mod dev_tools; +mod screens; +mod theme; + +use bevy::{ + asset::AssetMetaCheck, + audio::{AudioPlugin, Volume}, + prelude::*, +}; + +pub struct AppPlugin; + +impl Plugin for AppPlugin { + fn build(&self, app: &mut App) { + // Order new `AppStep` variants by adding them here: + app.configure_sets( + Update, + (AppSet::TickTimers, AppSet::RecordInput, AppSet::Update).chain(), + ); + + // Spawn the main camera. + app.add_systems(Startup, spawn_camera); + + // Add Bevy plugins. + app.add_plugins( + DefaultPlugins + .set(AssetPlugin { + // Wasm builds will check for meta files (that don't exist) if this isn't set. + // This causes errors and even panics on web build on itch. + // See https://github.com/bevyengine/bevy_github_ci_template/issues/48. + meta_check: AssetMetaCheck::Never, + ..default() + }) + .set(WindowPlugin { + primary_window: Window { + title: "{{project-name | title_case}}".to_string(), + canvas: Some("#bevy".to_string()), + fit_canvas_to_parent: true, + prevent_default_event_handling: true, + ..default() + } + .into(), + ..default() + }) + .set(AudioPlugin { + global_volume: GlobalVolume { + volume: Volume::new(0.3), + }, + ..default() + }), + ); + + // Add other plugins. + app.add_plugins(( + asset_tracking::plugin, + demo::plugin, + screens::plugin, + theme::plugin, + )); + + // Enable dev tools for dev builds. + #[cfg(feature = "dev")] + app.add_plugins(dev_tools::plugin); + } +} + +/// High-level groupings of systems for the app in the `Update` schedule. +/// When adding a new variant, make sure to order it in the `configure_sets` +/// call above. +#[derive(SystemSet, Debug, Clone, Copy, Eq, PartialEq, Hash, PartialOrd, Ord)] +enum AppSet { + /// Tick timers. + TickTimers, + /// Record player input. + RecordInput, + /// Do everything else (consider splitting this into further variants). + Update, +} + +fn spawn_camera(mut commands: Commands) { + commands.spawn(( + Name::new("Camera"), + Camera2dBundle::default(), + // Render all UI to this camera. + // Not strictly necessary since we only use one camera, + // but if we don't use this component, our UI will disappear as soon + // as we add another camera. This includes indirect ways of adding cameras like using + // [ui node outlines](https://bevyengine.org/news/bevy-0-14/#ui-node-outline-gizmos) + // for debugging. So it's good to have this here for future-proofing. + IsDefaultUiCamera, + )); +} diff --git a/src/main.rs.template b/src/main.rs.template new file mode 100644 index 00000000..b9e1c782 --- /dev/null +++ b/src/main.rs.template @@ -0,0 +1,9 @@ +// Disable console on Windows for non-dev builds. +#![cfg_attr(not(feature = "dev"), windows_subsystem = "windows")] + +use bevy::prelude::*; +use {{crate_name}}::AppPlugin; + +fn main() -> AppExit { + App::new().add_plugins(AppPlugin).run() +} diff --git a/web/index.html b/web/index.html index fc779da8..cc4593ef 100644 --- a/web/index.html +++ b/web/index.html @@ -3,7 +3,7 @@ - bevy_quickstart + Bevy Quickstart diff --git a/web/index.html.template b/web/index.html.template new file mode 100644 index 00000000..ec52b4c3 --- /dev/null +++ b/web/index.html.template @@ -0,0 +1,41 @@ + + + + + + {{project-name | title_case}} + + + + + + + + +
+
+ +
+ + + Javascript and canvas support is required + +
+ + {%- raw %} + + {%- endraw %} + + +