Repository: idanarye/bevy-yoleck Branch: main Commit: b832597b4775 Files: 58 Total size: 411.6 KB Directory structure: gitextract_d08yw1cr/ ├── .cargo/ │ └── config.toml ├── .github/ │ └── workflows/ │ └── ci.yml ├── .gitignore ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── MIGRATION-GUIDES.md ├── README.md ├── assets/ │ ├── levels2d/ │ │ ├── .gitkeep │ │ └── example.yol │ ├── levels3d/ │ │ ├── .gitkeep │ │ └── example.yol │ ├── levels_doors/ │ │ ├── entry.yol │ │ ├── index.yoli │ │ ├── room1.yol │ │ ├── room2.yol │ │ └── room3.yol │ ├── models/ │ │ ├── planet.blend │ │ ├── planet.glb │ │ ├── spaceship.blend │ │ └── spaceship.glb │ ├── sprites/ │ │ ├── doorway.pxo │ │ ├── fruits.pxo │ │ └── player.pxo │ └── these-assets-are-for-the-examples ├── examples/ │ ├── custom_camera3d.rs │ ├── doors_to_other_levels.rs │ ├── example2d.rs │ └── example3d.rs ├── macros/ │ ├── Cargo.toml │ └── src/ │ └── lib.rs ├── run-retrospective-crate-version-tagging.sh ├── src/ │ ├── auto_edit.rs │ ├── console.rs │ ├── editing.rs │ ├── editor.rs │ ├── editor_panels.rs │ ├── editor_window.rs │ ├── entity_management.rs │ ├── entity_ref.rs │ ├── entity_upgrading.rs │ ├── entity_uuid.rs │ ├── errors.rs │ ├── exclusive_systems.rs │ ├── knobs.rs │ ├── level_files_manager.rs │ ├── level_files_upgrading.rs │ ├── level_index.rs │ ├── lib.rs │ ├── picking_helpers.rs │ ├── populating.rs │ ├── specs_registration.rs │ ├── util.rs │ ├── vpeol.rs │ ├── vpeol_2d.rs │ └── vpeol_3d.rs └── tests/ └── upgrade_level_file.rs ================================================ FILE CONTENTS ================================================ ================================================ FILE: .cargo/config.toml ================================================ [alias] example2d = "run --example example2d --features _example2d_full" example3d = "run --example example3d --features _example3d_full" custom_camera3d = "run --example custom_camera3d --features _example3d_full" doors_to_other_levels = "run --example doors_to_other_levels --features _doors_to_other_levels_full" ================================================ FILE: .github/workflows/ci.yml ================================================ name: CI on: pull_request: push: branches: [main] # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages permissions: contents: read pages: write id-token: write checks: write jobs: ci: name: CI needs: [test, clippy, docs] runs-on: ubuntu-latest steps: - name: Done run: exit 0 test: name: Tests strategy: fail-fast: false matrix: os: [ubuntu-latest] rust: [1.92.0, nightly] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v3 - name: Install rust uses: dtolnay/rust-toolchain@master with: toolchain: ${{ matrix.rust }} - name: Ready cache if: matrix.os == 'ubuntu-latest' run: sudo chown -R $(whoami):$(id -ng) ~/.cargo/ - name: Install dependencies run: sudo apt-get update; sudo apt-get install --no-install-recommends libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev libudev-dev - name: Cache cargo uses: actions/cache@v4 id: cache with: path: ~/.cargo key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} - name: Test run: cargo test --verbose --all-features --features bevy/bevy_gltf -- --nocapture fmt: name: Rustfmt runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: dtolnay/rust-toolchain@master with: toolchain: 1.92.0 components: rustfmt - name: Run fmt --all -- --check run: cargo fmt --all -- --check clippy: name: Clippy runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: dtolnay/rust-toolchain@master with: toolchain: 1.92.0 components: clippy - name: Install dependencies run: sudo apt-get update; sudo apt-get install --no-install-recommends libudev-dev - name: Cache cargo uses: actions/cache@v4 id: cache with: path: ~/.cargo key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} - name: Run clippy --all-targets -- uses: actions-rs/clippy-check@v1 with: token: ${{ secrets.GITHUB_TOKEN }} args: --all-targets -- docs: name: Docs runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: dtolnay/rust-toolchain@master with: toolchain: 1.92.0 - name: Install dependencies run: sudo apt-get update; sudo apt-get install --no-install-recommends libudev-dev - name: Cache cargo uses: actions/cache@v4 id: cache with: path: ~/.cargo key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} - name: Run doc tests run: cargo test --doc --all-features --features bevy/bevy_gltf - name: Check docs run: cargo doc --no-deps --all-features --features bevy/x11 docs-and-demos-ghpages: name: Update Docs and Demos in GitHub Pages runs-on: ubuntu-latest if: github.ref == 'refs/heads/main' steps: - uses: actions/checkout@v3 - uses: jetli/wasm-bindgen-action@v0.2.0 with: version: 'latest' - uses: dtolnay/rust-toolchain@master with: targets: wasm32-unknown-unknown toolchain: 1.92.0 - name: Build docs env: GITHUB_REPO: ${{ github.repository }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: |- cargo doc --no-deps --verbose --all-features --features bevy/webgl2,bevy/x11 && echo "" > target/doc/index.html required_features=$( ( cargo metadata --no-deps --format-version 1 \ | jq '.packages[].targets[] | select(.kind == ["example"]) | .["required-features"][]' -r echo bevy/webgl2 echo bevy/x11 ) | tr '\n' ' ' ) RUSTFLAGS='--cfg getrandom_backend="wasm_js"' cargo build --examples --release --features "$required_features" --target wasm32-unknown-unknown for demowasm in $(cd target/wasm32-unknown-unknown/release/examples; ls *.wasm | grep -v -); do wasm-bindgen target/wasm32-unknown-unknown/release/examples/$demowasm --out-dir target/doc/demos/ --target web cat > target/doc/demos/${demowasm%.*}.html < EOF done cp -R assets/ target/doc/demos/ - name: Add read permissions run: |- chmod --recursive +r target/doc - name: Upload artifact uses: actions/upload-pages-artifact@v3 with: path: target/doc deploy-ghpages: environment: name: github-pages url: ${{ steps.deployment.outputs.page_url }} runs-on: ubuntu-latest needs: docs-and-demos-ghpages if: github.ref == 'refs/heads/main' steps: - name: Deploy to GitHub Pages id: deployment uses: actions/deploy-pages@v4 ================================================ FILE: .gitignore ================================================ /target Cargo.lock assets/levels2d/*.yol assets/levels2d/*.yoli !assets/levels2d/example.yol assets/levels3d/*.yol assets/levels3d/*.yoli !assets/levels3d/example.yol #IDES /.vscode # No ignored level files in levels_doors ================================================ FILE: CHANGELOG.md ================================================ # Changelog All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). ## [Unreleased] ## 0.31.0 - 2026-01-15 ### Changed - Upgrade Bevy to 0.18 ## 0.30.0 - 2026-01-15 ### Added - Automatic UI generation for components using reflection and attributes. - Supported numeric, boolean, string, vector, color, enum, option, list, asset, and entity fields. - `YoleckEntityRef` type with automatic UI, filtering, and runtime UUID resolution. - Drag and drop support for entity references: entities with UUID can now be dragged from the entity list and dropped onto `YoleckEntityRef` fields in the properties panel. - Entity type filtering is automatically applied when dropping entities onto entity reference fields with type constraints. - `console_layer_factory` for routing Bevy logs into the Yoleck console. - `YoleckConsoleLogHistory` for storing up to 1000 recent log entries. - `YoleckConsoleState` for managing console UI state. - `YoleckEditorBottomPanelSections` for extensible bottom-panel tabs. - `Vpeol3dCameraMode` enum with variants: `Fps`, `Sidescroller`, `Topdown`, `Custom(u32)`. - `YoleckCameraChoices` resource for customizing available camera modes in 3D editor. - Support for custom camera modes with user-defined movement logic. - Camera mode selector dropdown in editor top panel for switching between camera modes. - `Vpeol3dCameraControl::fps()` preset for FPS-style camera with full rotation freedom. - Scene gizmo for camera orientation. - Translation gizmo for `vpeol_3d`. - For all 3 world axes (X, Y, Z) by default. - World/Local mode toggle for the translation gizmo in 3D editor top panel. - Keyboard shortcut to delete selected entities: Press `Delete` key to remove selected entity from the level. - Copy/paste support for entities: Use `Ctrl+C` to copy and `Ctrl+V` to paste entities with all their components and values. - Cross-editor entity copying: Entities can be copied between different level editors through system clipboard (opt in via the `arboard` feature) - If `arboard` is not enabled the copy/paste will not be cross-editor - Entity deletion via keyboard in addition to the existing UI button. - Auto-selection of pasted entities: Newly pasted entities are automatically selected for immediate editing. - UI editing support for `Vpeol3dRotation` using Euler angles (X, Y, Z in degrees) - UI editing support for `Vpeol3dScale` with separate X, Y, Z drag values. - UI editing support for `Vpeol2dRotatation` using degrees. - UI editing support for `Vpeol2dScale` with separate X, Y drag values. - `Vpeol3dSnapToPlane` to force an entity on a specific plane. ### Changed - Improved editor ergonomics with better organized workspace instead of single cluttered panel. - Move camera with keyboard (WASD, with Q and E too for FPS camera) insted of mouse dragging. - Use registered systems instead of `YoleckEditorSection`. - `YoleckRawEntry::data` is now a `Map` instead of a `Value`. - Upgrade Rust edition to 2024. - Update bevy_egui version to 0.38. ### Removed - [**BREAKING**] Removed `Vpeol3dThirdAxisWithKnob` component (no longer needed as all axes have knobs by default) - Removed `plane_origin` field from `Vpeol3dCameraControl` (it was used for movement by mouse-drag, which was dropped) - Removed `YoleckEditorSection`. ## 0.29.0 - 2025-10-03 ### Changed - Upgrade Bevy to 0.17 - Rename: - `YoleckSystemSet` -> `YoleckSystems` - `VpeolSystemSet` -> `VpeolSystems` ## 0.28.0 - 2025-08-05 ### Changed - Update bevy_egui version to 0.36. ## 0.27.0 - 2025-07-03 ### Changed - Update bevy_egui version to 0.35. ## 0.26.1 - 2025-06-04 ### Fixed - Don't fail when adding `VpeolRouteClickTo` to a non-existing child. ## 0.26.0 - 2025-04-26 ### Changed - Upgrade Bevy to 0.16 - [**BREAKING**] Rename `YoleckEdit`'s method `get_single` and `get_single_mut` to `single` and `single_mut` (to mirror a similar change in Bevy itself) - Replace anyhow usage with `BevyError`. ## 0.25.0 - 2025-02-19 ### Changed - Update bevy_egui version to 0.33. ## 0.24.0 - 2025-01-09 ### Changed - Update bevy_egui version to 0.32. ## 0.23.0 - 2024-12-02 ### Changed - Update Bevy version to 0.15 and bevy_egui version to 0.31. ## 0.22.0 - 2024-07-06 ### Changed - Upgrade Bevy to 0.14 (and bevy_egui to 0.28) ## 0.21.0 - 2024-05-08 ### Changed - Update bevy_egui version to 0.27. ## 0.20.1 - 2024-04-01 ### Fixed - Enable bevy_egui's `render` feature, so that users won't need to load it explicitly and can use the one reexported from Yoleck (fixes https://github.com/idanarye/bevy-yoleck/issues/39) ## 0.20.0 - 2024-03-19 ### Changed - Update bevy_egui version to 0.26. ## 0.19.0 - 2024-02-27 ### Changed - Update Bevy version to 0.13 and bevy_egui version to 0.25. - [**BREAKING**] Changed some API types to use Bevy's new math types. See the [migration guide](MIGRATION-GUIDES.md#migrating-to-yoleck-019). ## 0.18.0 - 2024-02-18 ### Changed - Upgrade bevy_egui to 0.24. ### Fixed - [**BREAKING**] Typo - `Rotatation` -> `Rotation` in Vpeol. ## 0.17.1 - 2024-01-14 ### Fixed - Use a proper `OR` syntax for the dual license. ## 0.17.0 - 2023-11-25 ### Removed - [**BREAKING**] The `YoleckLoadingCommand` resource is removed, in favor of a `YoleckLoadLevel` component. See the [migration guide](MIGRATION-GUIDES.md#migrating-to-yoleck-017). - Note that unlike `YoleckLoadingCommand` that could load the level from either an asset or a value, `YoleckLoadLevel` can only load from an asset. If it is necessary to load a level from memory, add it to `ResMut>` first and pass the handle to `YoleckLoadLevel`. - [**BREAKING**] The `yoleck_populate_schedule_mut` method (which Yoleck was adding as an extension on Bevy's `App`) is removed in favor of just using `YoleckSchedule::Populate` directly. ### Added - `YoleckEditableLevels` resource (accessible only from edit systems) that provides the list of level file names. - Entity reference with `YoleckEntityUuid` and `YoleckUuidRegistry`. - Some picking helpers for handling entity references in the editor: `vpeol_read_click_on_entity`, `yoleck_map_entity_to_uuid` and `yoleck_exclusive_system_cancellable`. - Load multiple levels with `YoleckLoadLevel` (which is a component, that can be placed on multiple entities) - Unload levels by removing the `YoleckKeepLevel` component from the entity that was used to load the level - or by despawning that entity entirely. - `YoleckSchedule::LevelLoaded` schedule for interfering with levels before populating their entities. - `VpeolRepositionLevel` component. ### Change - `YoleckBelongsToLevel` now points to a level entity. - `YoleckDirective::spawn_entity` needs to know which level entity to create the component on. ## 0.16.0 - 2023-09-06 ### Changed - Upgrade Bevy to 0.12 (and bevy_egui to 0.23) ## 0.15.0 - 2023-10-15 ### Changed - Upgrade bevy_egui to 0.22 - [**BREAKING**] `YoleckUi` is a regular resource again. See the [migration guide](MIGRATION-GUIDES.md#migrating-to-yoleck-013). ## 0.14.1 - 2023-07-30 ### Fixed - `Vpeol2dCameraControl` reversing the Y axis when panning and zooming. Note that the implementation of this fix requires that the camera entity will have the `VpeolCameraState` component - without it `Vpeol2dCameraControl` will not work at all. I do not consider it a breaking change though, because: 1. The documentation do imply that you need `VpeolCameraState`. 2. `Vpeol3dCameraControl` was already requiring `VpeolCameraState`, and they are supposed to be equivalent. 3. `vpeol_2d` is useless without `VpeolCameraState`, so I'm not expecting anyone to not be using it just so that they can use the camera controls. So having `Vpeol2dCameraControl` work without `VpeolCameraState` was an undocumented feature, and I'm going to release this as a bugfix version, not a minor version. ## 0.14.0 - 2023-07-18 ### Changed ### Added - `#[derive(Reflect)]` to several components. Requires the `bevy_reflect` flag. - [**BREAKING**] Rename `YoleckRouteClickTo` to `VpeolRouteClickTo`. ## 0.13.0 - 2023-07-11 ### Changed - Upgrade Bevy to 0.11 (and bevy_egui to 0.21) - [**BREAKING**] `YoleckUi` is now a non-`Send` resource. See the [migration guide](MIGRATION-GUIDES.md#migrating-to-yoleck-013). ## 0.12.0 - 2023-06-20 ### Added - An exclusive systems mechanism for edit systems that operate alone and can thus assume control over the input (e.g. mouse motion and clicks) - Multiple selection with the Shift key, and `YoleckEdit` methods for editing multiple entities. ### Changed - When creating a new entity that uses `Vpeol*dPosition`, an exclusive system will kick in to allow placing the entity with the mouse (instead of just placing it in the origin and letting the user drag it from there) ## 0.11.0 - 2023-04-06 ### Added - `YoleckBelongsToLevel` for deciding which entities to despawn when the level unloads/restarts. This is added automatically by Yoleck, but should also be added to entities created by the game. ## 0.10.0 - 2023-03-28 ### Changed - Model detection now raycasts against the meshes in addition to the AABB. ### Added - Supported for 2D meshes in vpeol_2d. ## 0.9.0 - 2023-03-27 ### Changed - [**BREAKING**] This entire release is a huge breaking change. See the [migration guide](MIGRATION-GUIDES.md#migrating-to-yoleck-09). - [**BREAKING**] Move to a new model, where each Yoleck entity can be composed of multiple `YoleckComponent`s. - [**BREAKING**] The syntax of edit systems and populate systems has drastically changed. ### Added - A mechanism for upgrading entity's data when their layout changes. See `YoleckEntityUpgradingPlugin`. This can be used to upgrade old games to use the new semantics introduced in this version. - `vpeol_3d` is back in, without the dependencies and with better dragging. - `yoleck::prelude` - `yoleck::vpeol::prelude` ### Removed - `vpeol_position_edit_adapter` and `VpeolTransform2dProjection`. Use `Vpeol2dPosition` instead. ## 0.8.0 - 2023-03-14 ### Changed - Add scroll area to editor window. ### Fixed - Panic that happens sometimes when dragging an entity with children. ### Added - `YoleckDirective::spawn_entity` for spawning entities from user code (e.g. for creating entity duplication buttons) ## 0.7.0 - 2023-03-09 ### Changed - Upgrade Bevy to 0.10 (and bevy_egui to 0.20) - [**BREAKING**] `VpeolSystemLabel` becomes `VpeolSystems`, and uses Bevy's new system set semantics instead of the removed system label semantics. All sets of that system are configured to run during the `EditorActive` state. ### Added - `Anchor` is taken into account when vpeol_2d checks clicks on text (previous to Bevy 0.10 it did not have an `Anchor` component, and just used top-left) ## 0.6.0 - 2023-03-06 ### Changed - [**BREAKING**] Vpeol names no longer container the "yoleck" prefix - so `YoleckVpeolXYZ` becomes `VpeolXYZ` and `yoleck_vpeol_xyz` becomes `vpeol_xyz`. Vpeol is enough to avoid conflicts. - [**BREAKING**] `vpeol_2d` sends drag coordinates as `Vec3`, not `Vec2`. - [**BREAKING**] `YoleckWillContainClickableChildren` is renamed to `VpeolWillContainClickableChildren` and is no longer reexported by `vpeol_2d`. ### Added - [**BREAKING**] `VpeolCameraState` - must be placed on a camera in order for vpoel to work. - [**BREAKING**] `Vpeol2dCameraControl` - must be placed on a camera in order for vpoel_2d to apply camera panning and scrolling. ## 0.5.0 - 2023-02-22 ### Changed - Update bevy-egui version to 0.19. ## 0.4.0 - 2022-11-14 ### Changed - Update Bevy version to 0.9 and bevy-egui version to 0.17. ### Added - Ability to revert levels to their initial state: - `Wipe Level` button for ne` levels. - `REVERT` button for existing levels - This is important because otherwise the only ways to select a different level are to save the changes or restart the editor. ### Fixed - Knobs remaining during playtest. ## 0.3.0 - 2022-08-18 ### Changed - Update Bevy version to 0.8 and bevy-egui version to 0.15. ### Removed - **REGRESSION**: Removed `vpeol_3d` and `example3d`. They were depending on crates that were slow to migrate to Bevy 0.8 (one of then has still not released its Bevy 0.8 version when this changelog entry was written). Since `vpeol_3d` was barely usable to begin with (the gizmo is not a good way to move objects around - we need proper dragging! - and `bevy_mod_pickling` required lots of hacks to play nice with Yoleck) it has been removed for now and will be re-added in the future with less dependencies and better interface. ### Fixed - Use the correct transform when dragging child entities (#11) ### Added - Knobs! ## 0.2.0 - 2022-06-09 ### Added - `YoleckVpeolSelectionCuePlugin` for adding a pulse effect to show the selected entity in the viewport. ## 0.1.1 - 2022-06-02 ### Fixed - `vpeol_3d`: Entities sometimes getting deselected when cursor leaves egui area. - `vpeol_3d`: Freshly created entities getting selected in Yoleck but Gizmo is not shown. ## 0.1.0 - 2022-06-01 ### Added - Building `YoleckTypeHandler`s to define the entity types. - Editing entity structs with egui. - Populating entities with components based on entity structs. - Editor file manager. - Level loading from files. - Level index loading. - `vpeol_2d` and `vpeol_3d`. ================================================ FILE: Cargo.toml ================================================ [workspace] members = ["macros"] [workspace.package] edition = "2024" authors = ["IdanArye ", "dexsper "] license = "MIT OR Apache-2.0" repository = "https://github.com/idanarye/bevy-yoleck" [package] name = "bevy-yoleck" description = "Your Own Level Editor Creation Kit" version = "0.31.0" edition.workspace = true authors.workspace = true license.workspace = true repository.workspace = true documentation = "https://docs.rs/bevy-yoleck" readme = "README.md" categories = ["game-development"] keywords = ["bevy", "gamedev", "level-editor"] exclude = ["assets"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] bevy-yoleck-macros = { version = "0.10.0", path = "macros" } bevy = { version = "^0.18", default-features = false, features = [ "bevy_state", "bevy_window", "bevy_asset", "bevy_log", "bevy_camera", ] } bevy_egui = { version = "^0.39", default-features = false, features = [ "default_fonts", "render", ] } serde = "^1" serde_json = "^1" thiserror = "^2" uuid = "1.9.1" arboard = {version = "3.4", optional = true} [features] bevy_reflect = [] vpeol = [] vpeol_2d = ["vpeol", "bevy/bevy_text", "bevy/bevy_sprite", "bevy/png"] vpeol_3d = ["vpeol", "bevy/bevy_pbr"] # Support clipboard with the Arboard crate. Otherwise the clipboard will be internal. arboard = ["dep:arboard"] # Enable Wayland support in Arboard. arboard_wayland = ["arboard", "arboard/wayland-data-control"] _example2d_full = ["vpeol_2d", "bevy/bevy_gizmos", "bevy/bevy_sprite_render"] _example3d_full = [ "vpeol_3d", "bevy/bevy_scene", "bevy/bevy_gltf", "bevy/bevy_animation", "bevy/ktx2", "bevy/zstd_rust", "bevy/tonemapping_luts", "bevy/bevy_gizmos", "bevy/reflect_auto_register", "bevy/bevy_sprite_render", ] _doors_to_other_levels_full = ["vpeol_2d", "bevy/bevy_sprite_render"] [dev-dependencies] bevy = { version = "^0.18", default-features = false, features = [ "bevy_sprite", "x11", "bevy_window", "bevy_text", "bevy_render", ] } [[example]] name = "example2d" required-features = ["_example2d_full"] [[example]] name = "example3d" required-features = ["_example3d_full"] [[example]] name = "custom_camera3d" required-features = ["_example3d_full"] [[example]] name = "doors_to_other_levels" required-features = ["_doors_to_other_levels_full"] [package.metadata.docs.rs] all-features = true features = [ "bevy/x11", # required for bevy_egui "bevy/bevy_gltf", # required for SceneRoot in vpeol_3d's doctests ] ================================================ FILE: LICENSE-APACHE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: LICENSE-MIT ================================================ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: MIGRATION-GUIDES.md ================================================ # Migrating to Yoleck 0.19 ## Using the new Bevy types Bevy 0.13 introduces types for defining directions and planes, which can be used instead of vectors. Yoleck (mainly Vpeol) uses them now in its API. The conversion should be straightforward. Specifically: - `VpeolDragPlane` now uses `Plane3d`. - `Vpeol3dPluginForEditor` also uses `Plane3d`. - `Vpeol3dCameraControl` uses `Plane3d` for the camera drag plane, and `Direction3d` for configuring the UP direction to maintain while rotating the camera. ## `vpeol_read_click_on_entity` Bevy 0.13 [split `WorldQuery` to `QueryData` and `FilterData`](https://bevyengine.org/learn/migration-guides/0-12-to-0-13/#split-worldquery-into-querydata-and-queryfilter) (though there is still a `WorldQuery` trait with some of that functionality). When you use `vpeol_read_click_on_entity`, the data passed to it is `QueryFilter`, not `QueryData` - which measn that if it's a component (which should usually be the case) you need `vpeol_read_click_on_entity::>` and not `vpeol_read_click_on_entity::<&MyComponent>` (which would have worked before) # Migrating to Yoleck 0.17 ## Loading levels Instead of a `YoleckLoadingCommand` resource, level loading is now done via entities. This means that instead of loading a level like this: ```rust fn load_level( mut yoleck_loading_command: ResMut, asset_server: Res, ) { *yoleck_loading_command = YoleckLoadingCommand::FromAsset(asset_server.load("levels/my-level.yol")); } ``` You should do it like this: ```rust fn load_level( mut commands: Commands, asset_server: Res, ) { commands.spawn(YoleckLoadLevel(asset_server.load("levels/my-level.yol"))); } ``` Note that `YoleckLoadLevel` does not provide an equivalent for `YoleckLoadingCommand::FromData`. If you need to load a level from a value, put that value in `Assets` first. ## Clearing levels Instead of despawning all the entities marked with `YoleckBelongsToLevel`: ```rust fn unload_level( query: Query>, mut commands: Commands, ) { for entity in query.iter() { commands.entity(entity).despawn_recursive(); } } ``` You should despawn the entities that represent the levels - the ones marked with `YoleckKeepLevel`: ```rust fn unload_old_levels( query: Query>, mut commands: Commands, ) { for entity in query.iter() { commands.entity(entity).despawn_recursive(); } } ``` Yoleck will automatically despawn (with `despawn_recursive`) all the entities that belong to these levels. Note that it is also possible, if needed, to just remove the `YoleckKeepLevel` component from these entities to despawn their entities without despawning the level entities themselves. ## Changes in `YoleckBelongsToLevel` `YoleckBelongsToLevel` now has a `pub level: Entity` field that specifies which level the entity belongs to. When unloading a level (by despawning a `YoleckKeepLevel` entity, or removing the `YoleckKeepLevel` component from it), the entities that will be despawned are the ones who's `YoleckBelongsToLevel` points at that level. As before, if you create a component from a system and want it to be despawned when switching a level or restarting/finishing a playtest in the editor, it still needs the `YoleckBelongsToLevel` component. Except now you have to provide a level entity for it. Where should the level entity come from? Two options: * It can be attached to an existing level, so that its lifetime will be bound to it. This is useful for entities that need to exist in the level's space - when despawning the level, we don't want these entities to remain. The easiest way to achieve this is to use the `YoleckBelongsToLevel` of another component in that level. For example - say you have a treasure chest, and when the player shoots at it it opens up and a powerup pops from it for the player to pick up. Since the chest should already have a `YoleckBelongsToLevel` component, and since the system that spawns the powerup should already need to use some components of the chest entity, it should be easy to just clone the chest's `YoleckBelongsToLevel` and add it to the powerup spawning command. * You can create a faux level and attach the entities to it. This is useful, for example, for a player character entity that can travel between levels. Just create a new entity with a `YoleckKeepLevel` component and add its `Entity` to the roaming entity inside a `YoleckBelongsToLevel` component. Note that you can freely set an existing `YoleckBelongsToLevel` to point to different levels. So it might make more sense to switch the player character entity to different level as it travels between them than to associate it to some faux level. Both options are available. ## Adding populate systems `yoleck_populate_schedule_mut` is removed - this no longer works: ```rust app.yoleck_populate_schedule_mut().add_systems(my_populate_system); ``` Instead, just add the system on the `YoleckSchedule::Populate` schedule: ```rust app.add_systems(YoleckSchedule::Populate, my_populate_system); ``` `yoleck_populate_schedule_mut` made ergonomic sense in Bevy 0.10, but since starting Bevy 0.11 one has to always specify the schedule, it is no longer that ergonomic to have this helper method. # Migrating to Yoleck 0.15 ## Accessing the YoleckUi Now that https://github.com/emilk/egui/pull/3233 got in to egui 0.23, and bevy_egui 0.22 was released with that new version of egui, `YoleckUi` can be made a regular resource again. `YoleckUi` can no longer be accessed with `NonSend`/`NonSendMut`, and must be accessed with the regular `Res`/`ResMut`. # Migrating to Yoleck 0.13 ## Accessing the YoleckUi `YoleckUi` is now a non-`Send` resource, which means it can no longer be accessed as a regular `Res`/`ResMut`. It must now be accessed as `NonSend`/`NonSendMut`. Hopefully once https://github.com/emilk/egui/issues/3148 is fixed (and gets in to bevy_egui) this can be changed back. # Migrating to Yoleck 0.9 ## Importing Most of the commonly used stuff can be imported from the new prelude module: ```rust use bevy_yoleck::prelude::*; ``` ## Entity type definition and registration Previously entity types were declared as struct: ```rust #[derive(Clone, PartialEq, Serialize, Deserialize)] struct Foo { #[serde(default)] bar: Bar, #[serde(default)] baz: Baz, } ``` And registered with: ```rust app.add_yoleck_handler({ YoleckTypeHandler::::new("Foo") .populate_with(populate_foo) .edit_with(edit_foo) }); ``` Starting from 0.9, entities can be broken to multiple components: ```rust #[derive(Default, Clone, PartialEq, Serialize, Deserialize, Component, YoleckComponent)] struct Bar { // ... } #[derive(Default, Clone, PartialEq, Serialize, Deserialize, Component, YoleckComponent)] struct Baz { // ... } ``` You can still create one big component per entity type, but if there are data fields that are shared between different entity types it's better to split them out so that they can be edited with the same edit systems. Instead of registering type handlers, register entity types: ```rust app.add_yoleck_entity_type({ YoleckEntityType::new("Foo") .with::() .with::() }); ``` Unlike `YoleckTypeHandler`, that specifies the one data structure used by the entity and all the edit and populate systems it'll have, `YoleckEntityType` can specify multiple components and no systems. Systems are registered separately, and are not bound to a single entity type: ```rust app.add_yoleck_edit_system(edit_bar); app.add_yoleck_edit_system(edit_baz); app.yoleck_populate_schedule_mut().add_systems(( populate_bar, populate_baz, )); ``` ## Edit systems In addition to the different method of registreation specified in the previous section, the semantics of edit systems has also changed. Previously, edit systems would use a closure: ```rust fn edit_foo(mut edit: YoleckEdit) { edit.edit(|ctx, data, ui| { // ... }); } ``` Now they use something that acts like a query: ```rust fn edit_foo(mut ui: ResMut, mut edit: YoleckEdit<&mut Foo>) { let Ok(mut foo) = edit.get_single_mut() else { return }; // ... } ``` The differences: * Instead of a closure, we use `get_single_mut` to get the single entity. If no entity is being edited, or if the edited entity does not match, we use `return` to skip the rest of the edit system. * In the future, when Yoleck will have multi-entity editing, `YoleckEdit` will have `iter` and `iter_mut` for edit systems that can edit multiple entities. * Instead of getting the entity type directly as a generic parameter (`Foo`), `YoleckEdit` gets it like Bevy `Query`s would (`&mut Foo`). In fact, `YoleckEdit` can accept anything a Bevy query would accept, including filters as a second parameter. * Instead of getting the UI handle via a closure argument, we get it as a resource in a separate `SystemParam` argument for the edit system function. ## Populate systems In addition to the different method of registreation specified in an earlier section, the semantics of populate systems has also changed. Previously, populate systems would look like this: ```rust fn populate_foo(mut populate: YoleckPopulate) { populate.populate(|ctx, data, &mut cmd| { // ... }); } ``` Populate systems still use closures, but they look different: ```rust fn populate_foo(mut populate: YoleckPopulate<&Foo>) { populate.populate(|ctx, &mut cmd, foo| { // ... }); } ``` The differences: * Like `YoleckEdit`, `YoleckPopulate` also accepts query-like generic parameters. * The command and data arguments to the closure switch places. Now the command is the second argument and the data is the third. * The data argument is actually what a Bevy query with the same generic parameters as what the `YoleckPopulate` got would have yielded. ## Child entities Previously, a populate system could freely use `cmd.despawn_descendants();`. Now that there are multiple edit systems and their order is determined by a scheduler, this should not be used, so instead populate systems should mark child entities they create so that they can despawn them later (usually when they replace them with freshly spawned ones): ```rust fn populate_system(mut populate: YoleckPopulate<&MyComponent>, marking: YoleckMarking) { populate.populate(|_ctx, mut cmd, my_component| { marking.despawn_marked(&mut cmd); cmd.with_children(|commands| { let mut child = commands.spawn(marking.marker()); child.insert(( // relevant Bevy components )); }); }); } ``` ## Passed data Previously, passed data would be accessed from the context argument of an edit system's closure: ## Knobs Previously, knobs would be accessed from the context argument of an edit system's closure: ```rust fn edit_foo(mut edit: YoleckEdit, mut commands: Commands) { edit.edit(|ctx, data, ui| { let mut knob = ctx.knob(&mut commands, "knob-ident"); }); } ``` Starting from 0.9 knobs are accessed with a new `SystemParam` named `YoleckKnobs`: ```rust fn edit_foo(mut edit: YoleckEdit<&mut Foo>, mut knobs: YoleckKnobs) { let Ok(mut foo) = edit.get_single_mut() else { return }; let mut knob = knobs.knob("knob-ident"); } ``` The actual usage of the knob handle is unchained. Note that knobs are not associated to a specific edited entity (although they do reset when the selection changes). This was also true before 0.9, but is more visible now that they are not accessed from the edit closure's `ctx`. ## Position manipulation with vpeol_2d * Instead of `vpeol_position_edit_adapter`, use `Vpeol2dPosition` as a Yoleck component. * Don't set the translation by yourself - let vpeol_2d do it. * If you need to also set rotation and scale, use `Vpeol2dRotatation` and `Vpeol2dScale`. vpeol_2d does not currently offer edit systems for them (it only takes them into account in the populate system), so you'll still have to write them yourself. * `Vpeol2dPlugin` is split into two - `Vpeol2dPluginForEditor` and `Vpeol2dPluginForGame`. Use the appropriate one based on how the process started, just like you'd use the appropriate `YoleckPluginForEditor`/`YoleckPluginForGame`. ================================================ FILE: README.md ================================================ [![Build Status](https://github.com/idanarye/bevy-yoleck/workflows/CI/badge.svg)](https://github.com/idanarye/bevy-yoleck/actions) [![Latest Version](https://img.shields.io/crates/v/bevy-yoleck.svg)](https://crates.io/crates/bevy-yoleck) [![Rust Documentation](https://img.shields.io/badge/nightly-rustdoc-blue.svg)](https://idanarye.github.io/bevy-yoleck/) [![Rust Documentation](https://img.shields.io/badge/stable-rustdoc-purple.svg)](https://docs.rs/bevy-yoleck/) # Bevy YOLECK - Your Own Level Editor Creation Kit Yoleck is a crate for having a game built with the Bevy game engine act as its own level editor. ## Features * Same executable can launch in either game mode or editor mode, depending on the plugins added to the app. * Write systems that create entities based on serializable structs - use same systems for both loading the levels and visualizing them in the editor. * Entity editing is done with egui widgets that edit these structs. | * Automatic UI generation for components with support for numeric, boolean, string, vector, color, enum, option, list, asset, and entity fields. | * Entity linking system with automatic UI, filtering, and runtime UUID resolution. Supports drag-and-drop. * Visual scene gizmo for camera orientation and control. * Support for external plugins that offer more visual editing. * One simple such plugin - Vpeol is included in the crate. It provides basic entity selection, positioning with mouse dragging, and basic camera control. It has two variants behind feature flags - `vpeol_2d` and `vpeol_3d`. * A knobs mechanism for more visual editing. * Playtest the levels inside the editor. * Multiple entity selection in the editor with the Shift key. * Optional console system for displaying logs in the UI | ## Examples: ```bash git clone https://github.com/dexsper/bevy-yoleck-fork cd bevy-yoleck ``` Then you can run the examples: * 2D example: ```bash cargo example2d ``` Or check out the WASM version: https://idanarye.github.io/bevy-yoleck/demos/example2d https://user-images.githubusercontent.com/1149255/228007948-31a37b3f-7bd3-4a36-a3bc-4617d359c7c2.mp4 * 3D example: ```bash cargo example3d ``` Or check out the WASM version: https://idanarye.github.io/bevy-yoleck/demos/example3d https://user-images.githubusercontent.com/1149255/228008014-825ef02e-2edc-49f5-a15c-1fa6044f84de.mp4 * Multi-level example: ```bash cargo doors_to_other_levels ``` Or check out the WASM version (gameplay only): https://idanarye.github.io/bevy-yoleck/demos/doors_to_other_levels https://github.com/idanarye/bevy-yoleck/assets/1149255/590beba4-2ca5-4218-af52-143321bb5946 ## File Format Yoleck saves the levels in JSON files that have the `.yol` extension. A `.yol` file's top level is a tuple (actually JSON array) of three values: * File metadata - e.g. Yoleck version. * Level data (placeholder - currently an empty object) * List of entities. Each entity is a tuple of two values: * Entity metadata - e.g. its type. * Entity componments - that's the user defined structs. The reason tuples are used instead of objects is to ensure ordering - to guarantee the metadata can be read before the data. This is important because the metadata is needed to parse the data. Yoleck generates another JSON file in the same directory as the `.yol` files called `index.yoli`. The purpose of this file is to let the game know what level are available to it (in WASM, for example, the asset server cannot look at a directory's contents). The index file contains a tuple of two values: * Index metadata - e.g. Yoleck version. * List of objects, each contain a path to a level file relative to the index file. ## Versions | bevy | bevy-yoleck | bevy_egui | |------|-------------|-----------| | 0.18 | 0.31 | 0.39 | | 0.17 | 0.30 | 0.38 | | 0.17 | 0.29 | 0.37 | | 0.16 | 0.28 | 0.36 | | 0.16 | 0.27 | 0.35 | | 0.16 | 0.26 | 0.34 | | 0.15 | 0.25 | 0.33 | | 0.15 | 0.24 | 0.32 | | 0.15 | 0.23 | 0.31 | | 0.14 | 0.22 | 0.28 | | 0.13 | 0.21 | 0.27 | | 0.13 | 0.20 | 0.26 | | 0.13 | 0.19 | 0.25 | | 0.12 | 0.18 | 0.24 | | 0.12 | 0.16, 0.17 | 0.23 | | 0.11 | 0.15 | 0.22 | | 0.11 | 0.13 - 0.14 | 0.21 | | 0.10 | 0.7 - 0.12 | 0.20 | | 0.9 | 0.5, 0.6 | 0.19 | | 0.9 | 0.4 | 0.17 | | 0.8 | 0.3 | 0.15 | | 0.7 | 0.1, 0.2 | 0.14 | ## License Licensed under either of * Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) at your option. ### Contribution Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. ================================================ FILE: assets/levels2d/.gitkeep ================================================ ================================================ FILE: assets/levels2d/example.yol ================================================ [{"format_version":2,"app_format_version":1},{},[[{"type":"Player","name":""},{"Vpeol2dPosition":[-309.3874206542969,81.44441223144531],"Vpeol2dRotatation":0.0}],[{"type":"Triangle","name":""},{"TriangleVertices":{"vertices":[[-12.283805847167969,-141.28375244140625],[211.94580078125,-151.37274169921875],[199.81985473632812,16.863494873046875]]},"Vpeol2dPosition":[-459.87017822265625,-29.312294006347656]}],[{"type":"FloatingText","name":"","uuid":"8acaa597-8c7d-4b82-8fd5-4eda44e24b5f"},{"TextContent":{"text":"Collect the fruit"},"TextLaserPointer":{"target":{}},"Vpeol2dPosition":[118.0838623046875,306.9156494140625],"Vpeol2dScale":[1.0,1.0]}],[{"type":"FloatingText","name":"","uuid":"14195c3b-e420-4fa4-bc59-7149a7c7f5d4"},{"TextContent":{"text":"Orange"},"TextLaserPointer":{"target":{"uuid":"e8f8d516-ca5f-41d2-99a6-1be029fdd0ee"}},"Vpeol2dPosition":[74.51683044433594,-226.31687927246094],"Vpeol2dScale":[0.75,0.75]}],[{"type":"Fruit","name":"","uuid":"e8f8d516-ca5f-41d2-99a6-1be029fdd0ee"},{"FruitType":{"index":1},"Vpeol2dPosition":[-3.697650909423828,-60.341732025146484]}],[{"type":"Fruit","name":"","uuid":"eb26f293-7548-4bcc-a268-871c80726fd3"},{"FruitType":{"index":2},"Vpeol2dPosition":[-265.73712158203125,-226.1751708984375]}],[{"type":"FloatingText","name":"","uuid":"e9f7c89f-f975-41e0-9ab0-30991e715877"},{"TextContent":{"text":"Grapes"},"TextLaserPointer":{"target":{"uuid":"eb26f293-7548-4bcc-a268-871c80726fd3"}},"Vpeol2dPosition":[-440.10369873046875,-230.0904998779297],"Vpeol2dScale":[0.75,0.75]}]]] ================================================ FILE: assets/levels3d/.gitkeep ================================================ ================================================ FILE: assets/levels3d/example.yol ================================================ [{"format_version":2,"app_format_version":0},{},[[{"type":"Planet","name":"","uuid":"c8aa8fdd-5431-473c-8774-b069c95f794f"},{"Vpeol3dPosition":[-7.047080993652344,6.785710334777832,0.0],"Vpeol3dRotation":[0.0,0.0,0.0,1.0],"Vpeol3dScale":[1.0,1.0,1.0]}],[{"type":"Planet","name":"","uuid":"2d676a47-146f-49f0-bba7-cf58a8833b1f"},{"Vpeol3dPosition":[16.688783645629883,-3.0687923431396484,0.0],"Vpeol3dRotation":[0.0,0.0,0.0,1.0],"Vpeol3dScale":[1.0,1.0,1.0]}],[{"type":"Spaceship","name":""},{"SpaceshipSettings":{"enabled":true,"rotation_speed":2.0,"speed":2.0},"Vpeol3dPosition":[1.2451118230819702,-2.86102294921875e-6,21.062850952148438]}],[{"type":"PlanetPointer","name":""},{"LaserPointer":{"target":{"uuid":"c8aa8fdd-5431-473c-8774-b069c95f794f"}},"Vpeol3dPosition":[3.227558135986328,2.0,-29.523635864257812]}]]] ================================================ FILE: assets/levels_doors/entry.yol ================================================ [{"format_version":2,"app_format_version":0},{},[[{"type":"FloatingText","name":""},{"TextContent":{"text":"Start Room"},"Vpeol2dPosition":[28.116905212402344,132.0689239501953],"Vpeol2dScale":[1.0,1.0]}],[{"type":"Doorway","name":""},{"Doorway":{"marker":"s-1","target_level":"room1.yol"},"Vpeol2dPosition":[326.6886291503906,-15.409356117248535],"Vpeol2dRotatation":0.0}],[{"type":"Player","name":""},{"Vpeol2dPosition":[0.0,0.0]}]]] ================================================ FILE: assets/levels_doors/index.yoli ================================================ [{"format_version":1},[{"filename":"entry.yol"},{"filename":"room1.yol"},{"filename":"room2.yol"},{"filename":"room3.yol"}]] ================================================ FILE: assets/levels_doors/room1.yol ================================================ [{"format_version":2,"app_format_version":0},{},[[{"type":"Doorway","name":""},{"Doorway":{"marker":"s-1","target_level":"entry.yol"},"Vpeol2dPosition":[-341.4138488769531,-77.32525634765625],"Vpeol2dRotatation":-3.1367220878601074}],[{"type":"Doorway","name":""},{"Doorway":{"marker":"1-2","target_level":"room2.yol"},"Vpeol2dPosition":[13.468215942382812,371.9578857421875],"Vpeol2dRotatation":1.5801841020584106}],[{"type":"Doorway","name":""},{"Doorway":{"marker":"1-3","target_level":"room3.yol"},"Vpeol2dPosition":[23.59503173828125,-422.729248046875],"Vpeol2dRotatation":-1.5713714361190796}],[{"type":"Player","name":""},{"Vpeol2dPosition":[0.0,0.0]}],[{"type":"FloatingText","name":""},{"TextContent":{"text":"Room 1"},"Vpeol2dPosition":[3.049217939376831,77.6406021118164],"Vpeol2dScale":[1.0,1.0]}]]] ================================================ FILE: assets/levels_doors/room2.yol ================================================ [{"format_version":2,"app_format_version":0},{},[[{"type":"Doorway","name":""},{"Doorway":{"marker":"1-2","target_level":"room1.yol"},"Vpeol2dPosition":[-5.04287052154541,-384.5793762207031],"Vpeol2dRotatation":-1.5634950399398804}],[{"type":"Player","name":""},{"Vpeol2dPosition":[0.0,0.0]}],[{"type":"FloatingText","name":""},{"TextContent":{"text":"Room 2"},"Vpeol2dPosition":[-3.6070098876953125,-112.29154968261719],"Vpeol2dScale":[1.0,1.0]}]]] ================================================ FILE: assets/levels_doors/room3.yol ================================================ [{"format_version":2,"app_format_version":0},{},[[{"type":"Doorway","name":""},{"Doorway":{"marker":"1-3","target_level":"room1.yol"},"Vpeol2dPosition":[-4.219451904296875,364.6886291503906],"Vpeol2dRotatation":1.5755369663238525}],[{"type":"Player","name":""},{"Vpeol2dPosition":[0.0,0.0]}],[{"type":"FloatingText","name":""},{"TextContent":{"text":"Room 3"},"Vpeol2dPosition":[1.2645721435546875,150.7604217529297],"Vpeol2dScale":[1.0,1.0]}]]] ================================================ FILE: assets/these-assets-are-for-the-examples ================================================ ================================================ FILE: examples/custom_camera3d.rs ================================================ use std::path::Path; use bevy::color::palettes::css; use bevy::prelude::*; use bevy_egui::{EguiContexts, EguiPlugin}; use bevy_yoleck::YoleckEditMarker; use bevy_yoleck::prelude::*; use bevy_yoleck::vpeol::prelude::*; /// Custom camera mode for isometric view with diagonal movement. const CAMERA_MODE_ISOMETRIC: Vpeol3dCameraMode = Vpeol3dCameraMode::Custom(0); /// Custom camera mode for orbital rotation around selected entities. const CAMERA_MODE_ORBITAL: Vpeol3dCameraMode = Vpeol3dCameraMode::Custom(1); fn main() { let mut app = App::new(); app.add_plugins(DefaultPlugins); let level = std::env::args().nth(1); if let Some(level) = level { app.add_plugins(EguiPlugin::default()); app.add_plugins(YoleckPluginForGame); app.add_plugins(Vpeol3dPluginForGame); app.add_systems( Startup, move |asset_server: Res, mut commands: Commands| { commands.spawn(YoleckLoadLevel( asset_server.load(Path::new("levels3d").join(&level)), )); }, ); } else { app.add_plugins(EguiPlugin::default()); app.add_plugins(YoleckPluginForEditor); app.add_plugins(Vpeol3dPluginForEditor::topdown()); app.add_plugins(VpeolSelectionCuePlugin::default()); app.insert_resource(bevy_yoleck::YoleckEditorLevelsDirectoryPath( Path::new(".").join("assets").join("levels3d"), )); #[cfg(target_arch = "wasm32")] app.add_systems( Startup, |asset_server: Res, mut commands: Commands| { commands.spawn(YoleckLoadLevel(asset_server.load("levels3d/example.yol"))); }, ); app.insert_resource( YoleckCameraChoices::default() .choice_with_transform( "Isometric", { let mut control = Vpeol3dCameraControl::fps(); control.mode = CAMERA_MODE_ISOMETRIC; control.allow_rotation_while_maintaining_up = None; control.wasd_movement_speed = 15.0; control }, Vec3::new(10.0, 10.0, 10.0), Vec3::ZERO, Vec3::Y, ) .choice_with_transform( "Orbital", { let mut control = Vpeol3dCameraControl::fps(); control.mode = CAMERA_MODE_ORBITAL; control.allow_rotation_while_maintaining_up = None; control.wasd_movement_speed = 0.0; control.mouse_sensitivity = 0.005; control }, Vec3::new(0.0, 5.0, 15.0), Vec3::ZERO, Vec3::Y, ), ); app.add_systems( PostUpdate, isometric_camera_movement.run_if(in_state(YoleckEditorState::EditorActive)), ); app.add_systems( PostUpdate, orbital_camera_movement.run_if(in_state(YoleckEditorState::EditorActive)), ); } app.add_systems(Startup, (setup_camera, setup_scene)); app.add_yoleck_entity_type({ YoleckEntityType::new("Cube") .with::() .with::() .insert_on_init(|| IsCube) }); app.add_systems(YoleckSchedule::Populate, populate_cube); app.add_yoleck_entity_type({ YoleckEntityType::new("Sphere") .with::() .insert_on_init(|| IsSphere) }); app.add_systems(YoleckSchedule::Populate, populate_sphere); app.run(); } fn setup_camera(mut commands: Commands) { commands.spawn(( Camera3d::default(), Transform::from_xyz(10.0, 10.0, 10.0).looking_at(Vec3::ZERO, Vec3::Y), VpeolCameraState::default(), Vpeol3dCameraControl::topdown(), )); commands.spawn(( DirectionalLight { color: Color::WHITE, illuminance: 10_000.0, shadows_enabled: true, ..Default::default() }, Transform::from_xyz(5.0, 10.0, 5.0).looking_at(Vec3::ZERO, Vec3::Y), )); } fn setup_scene( mut commands: Commands, mut mesh_assets: ResMut>, mut material_assets: ResMut>, ) { let mesh = mesh_assets.add(Mesh::from( Plane3d { normal: Dir3::Y, half_size: Vec2::new(50.0, 50.0), } .mesh(), )); let material = material_assets.add(Color::from(css::DARK_GRAY)); commands.spawn(( Mesh3d(mesh), MeshMaterial3d(material), Transform::from_xyz(0.0, -1.0, 0.0), )); } fn isometric_camera_movement( mut egui_context: EguiContexts, keyboard_input: Res>, time: Res