├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .helix └── languages.toml ├── .ignore ├── CHANGELOG.md ├── Cargo.lock ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── assets ├── bevy.png ├── bevy_tween.png ├── big_triangle.png ├── big_x.png ├── circle.png ├── circle_filled.png ├── dot.png ├── pink_fire_ball.png ├── square.png ├── square_filled.png ├── triangle.png ├── triangle_filled.png ├── tween.png └── x.png ├── build.rs ├── examples ├── animation │ ├── banner_bounce.rs │ ├── banner_triangle.rs │ └── thumbnail_triangle.rs ├── bevy_lookup_curve.rs ├── demo │ ├── click.rs │ ├── entity_event.rs │ ├── event.rs │ ├── follow.rs │ ├── hold.rs │ ├── sprite_sheet.rs │ └── utils │ │ └── mod.rs ├── entity_structure.rs └── interpolator.rs ├── flake.lock ├── flake.nix ├── images └── easefunction │ ├── BackIn.svg │ ├── BackInOut.svg │ ├── BackOut.svg │ ├── BothSteps.svg │ ├── BounceIn.svg │ ├── BounceInOut.svg │ ├── BounceOut.svg │ ├── CircularIn.svg │ ├── CircularInOut.svg │ ├── CircularOut.svg │ ├── CubicIn.svg │ ├── CubicInOut.svg │ ├── CubicOut.svg │ ├── Elastic.svg │ ├── ElasticIn.svg │ ├── ElasticInOut.svg │ ├── ElasticOut.svg │ ├── EndSteps.svg │ ├── ExponentialIn.svg │ ├── ExponentialInOut.svg │ ├── ExponentialOut.svg │ ├── Linear.svg │ ├── NoneSteps.svg │ ├── QuadraticIn.svg │ ├── QuadraticInOut.svg │ ├── QuadraticOut.svg │ ├── QuarticIn.svg │ ├── QuarticInOut.svg │ ├── QuarticOut.svg │ ├── QuinticIn.svg │ ├── QuinticInOut.svg │ ├── QuinticOut.svg │ ├── README.md │ ├── SineIn.svg │ ├── SineInOut.svg │ ├── SineOut.svg │ ├── SmoothStep.svg │ ├── SmoothStepIn.svg │ ├── SmoothStepOut.svg │ ├── SmootherStep.svg │ ├── SmootherStepIn.svg │ ├── SmootherStepOut.svg │ └── StartSteps.svg ├── rustfmt.toml ├── src ├── combinator.rs ├── combinator │ ├── animation_combinators.rs │ └── state.rs ├── interpolate.rs ├── interpolate │ ├── blanket_impl.rs │ ├── sprite.rs │ ├── transform.rs │ └── ui.rs ├── interpolation.rs ├── interpolation │ └── bevy_lookup_curve.rs ├── lib.rs ├── tween.rs ├── tween │ └── systems.rs ├── tween_event.rs └── utils.rs └── wsl_run /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | merge_group: 9 | 10 | env: 11 | CARGO_TERM_COLOR: always 12 | 13 | jobs: 14 | check_build_test: 15 | strategy: 16 | matrix: 17 | os: [windows-latest, ubuntu-latest, macos-latest] 18 | runs-on: ${{ matrix.os }} 19 | env: 20 | RUSTDOCFLAGS: -D warnings 21 | 22 | steps: 23 | - uses: actions/checkout@v4 24 | 25 | - uses: actions/cache@v4 26 | with: 27 | path: | 28 | ~/.cargo/bin/ 29 | ~/.cargo/registry/index/ 30 | ~/.cargo/registry/cache/ 31 | ~/.cargo/git/db/ 32 | ~/.cargo/.crates.toml 33 | ~/.cargo/.crates2.json 34 | target/ 35 | key: ${{ runner.os }}-cargo-build-${{ hashFiles('**/Cargo.toml') }} 36 | 37 | - uses: dtolnay/rust-toolchain@stable 38 | - uses: dtolnay/rust-toolchain@nightly 39 | 40 | - name: Install dependencies 41 | if: runner.os == 'linux' 42 | run: sudo apt-get update; sudo apt-get install --no-install-recommends libasound2-dev libudev-dev 43 | 44 | - name: Install cargo commands 45 | run: cargo install cargo-all-features 46 | 47 | - name: Check all features 48 | run: cargo check-all-features 49 | 50 | - name: Check docs 51 | run: cargo +nightly doc --all-features --no-deps 52 | 53 | - name: Check all examples 54 | run: cargo check --examples -F bevy_lookup_curve 55 | 56 | - name: Run tests 57 | run: cargo test -F bevy/x11 58 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /.envrc 3 | /.direnv 4 | 5 | -------------------------------------------------------------------------------- /.helix/languages.toml: -------------------------------------------------------------------------------- 1 | [language-server.rust-analyzer.config] 2 | checkOnSave = { command = "clippy" } 3 | cargo = { allFeatures = true } 4 | rustfmt = { extraArgs = [ "+nightly" ] } 5 | -------------------------------------------------------------------------------- /.ignore: -------------------------------------------------------------------------------- 1 | assets/ 2 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## v0.8.0 - 2025-05-09 4 | 5 | ### Changes 6 | 7 | - Migrate to bevy 0.16 8 | - Update all examples 9 | - Change to Rust edition 2024 10 | - Add Nix flake files 11 | - Update EaseKind documentation to EaseFunction 12 | 13 | ## v0.7.0 - 2024-12-09 14 | 15 | ### Changes 16 | 17 | - Migrate to bevy 0.15 18 | - `LookupCurveHandle` as a replacement for `Handle` 19 | - Update all examples 20 | - Update README.md 21 | - Add public `new()` constructor for `AnimationBuilder` 22 | - Update Cargo.toml dependnecies 23 | - Replace bevy_eventlistener with observer ([#44](https://github.com/Multirious/bevy_tween/pull/44)) 24 | - Remove `tween_event_taking_system`, `TweenEventTakingPlugin`, and inner option inside `TweenEvent` ([#44](https://github.com/Multirious/bevy_tween/pull/44)) 25 | - `entity_event` is now a example for tween event and observers 26 | - Change EaseFunction to EaseKind which is a direct copy from `bevy_math::curve::EaseFunction` and will be deprecated in favour of `bevy_math::curve::EaseFunction` 27 | 28 | ## v0.6.0 - 2024-7-07 29 | 30 | ### Changes 31 | 32 | - Update `bevy` to `0.14` 33 | - Add interpolators for some UI components when using the `bevy_ui` feature. ([#33](https://github.com/Multirious/bevy_tween/pull/33)) 34 | - `BackgroundColor` 35 | - `BorderColor` 36 | - Add optional feature for `serde`. ([#31](https://github.com/Multirious/bevy_tween/pull/31)) 37 | - Derive `Serialize` and `Deserialize` for `EaseFunction` 38 | - Clean up `TweenAppResource` after the app runs. ([#28](https://github.com/Multirious/bevy_tween/pull/28)) 39 | - Update animation builder ([#36](https://github.com/Multirious/bevy_tween/pull/36)) 40 | - Add `entity_commands()` getter 41 | - Add `time_runner()` getter 42 | - Add `time_runner_mut()` getter 43 | - Add `skipped()` method 44 | - Add `disabled()` method 45 | - Add `time_scale()` method 46 | - Add `direction()` method 47 | - Optimize crate size (11 MB to 0.488 MB) 48 | - Remove one 9.8 MB gif file 49 | 50 | ## v0.5.0 - 2024-06-09 51 | 52 | ### Breaking changes 53 | 54 | - Move span_tweener and tween_timer types to `bevy_time_runner` 55 | - Remove `tween_timer` module and all types in it. Some types can be found in `bevy_time_runner` 56 | - Remove `span_tween` module and all types in it 57 | - Remove `"span_tween"` feature flag 58 | - Remove `TweenSystemSet::TickTween ` 59 | - Remove `TweenSystemSet::Tweener` 60 | - Replace `TargetComponent::Tweener*` with `TargetComponent::Marker` and `AnimationTarget`. Update default accordingly 61 | - Update library to use types from `bevy_time_runner` 62 | - Remove all types, method, and function related to tweener. Most is renamed and move to `bevy_time_runner` 63 | 64 | All timing types is moved to `bevy_time_runner` including some changes. 65 | 66 | - `Repeat` 67 | - `RepeatStyle` 68 | - `SpanTweener` is replaced with `TimeRunner` 69 | - `AnimationDirection` is replaced with `TimeDirection` 70 | - `SpanTweenerEnded` is replaced with `TimeRunnerEnded` 71 | - `TweenTimeSpan` is replaced with `TimeSpan` 72 | - ...And some more 73 | 74 | ### Fixes 75 | 76 | - Fix tween systems error will flood the console 77 | 78 | ### Changes 79 | 80 | - Supports `bevy_eventlistener` #16 by musjj 81 | - Interpolation implementation for bevy_lookup_curve 82 | - Update readme 83 | - Update docs 84 | - Improve lib docs 85 | - Fix getting started code example. You're suppose to use `bevy_tween::prelude::*` not `bevy_tween::*`! 86 | - Add curve text art to EaseFunction 87 | - Implements combinator 88 | - Implements state 89 | - New animation builder and traits 90 | - Add function constructor for interpolators 91 | - Add `IntoTarget` trait 92 | - pub use bevy_time_runner 93 | - `TweenCorePlugin` adds `TimeRunnerPlugin` automatically if not exists 94 | - Remove deprecated systems and types 95 | - Add build.rs file to actually make CHANNEL_NIGHTLY cfg flag works 96 | - Update all examples to account for new changes 97 | - Add rustc_version to build dependencies 98 | - Remove span_tween example 99 | - Turn off format_code_in_doc_comments rust fmt config 100 | 101 | ## v0.4.0 - 2024-04-08 102 | 103 | ### Changes 104 | 105 | - Add `SpanTweensBuilder::add` trait 106 | - Add `SpanTweenPreset` trait 107 | - Update examples to use the preset APIs. 108 | - Documentations 109 | - Add "Features" section to README.md 110 | - Add "Contributions" section to README.md 111 | - Add "Your contributions" section to README.md 112 | 113 | ## v0.3.1 - 2024-04-04 114 | 115 | - Fix README.md 116 | 117 | ## v0.3.0 - 2024-04-03 118 | 119 | ### Breaking Changes 120 | 121 | - Remove unnecessary generics from `TargetComponent` and `TargetResource` 122 | - Add `app_resource: TweenAppResource` field to `TweenCorePlugin` 123 | - All plugins and APIs that uses `PostUpdate` schedule is changed to use schedule from 124 | `TweenAppResource` 125 | - Delegate `span_tweener_system()`'s ticking responsibility to `tick_span_tweener_system()` 126 | - Remove `Eq` and `Hash` derives from `SpanTweener`, `Elasped`, and `TweenTimer` 127 | - Remove `new()` from `Elasped` 128 | - Remove `state: TweenState` field from SpanTweenBundle 129 | - Remove `TweenState` 130 | - Remove `TweenTarget` impl from `TargetComponent`, `TargetResource` and, `TargetAsset` 131 | - Change `component_tween_system_full`, `resource_tween_system_full`, and `asset_tween_system_full` 132 | function signature to account for `SkipTween` component 133 | - Remove `TickResult` 134 | - Change `Elasped` struct definition 135 | - Combine `repeat` and `repeat_style` in `TweenTimer` to just `repeat` then 136 | change corresponding methods. 137 | - Change `TweenTimer::tick()` to accepts `f32` instead of `Duration` 138 | - Change `TweenTimer::tick()` behavior to not update `previous` field in `Elasped`. 139 | `collaspe_elasped` will update the `previous` field instead. 140 | - Change `Repeat` to use `i32` instead of `usize` and update their corresponding methods. 141 | 142 | ### Changes 143 | 144 | - Add `TweenAppResource` 145 | - Add `DefaultTweenEventsPlugin` 146 | - Add `TweenEventData` 147 | - Add `TweenEvent` 148 | - Add `TweenTimer::set_tick` 149 | - Add `TweenTimer::collaspe_elasped` 150 | - Add `Repeat::advance_counter_by` 151 | - Add `apply_component_tween_system`, `apply_resource_tween_system`, 152 | and `apply_asset_tween_system` 153 | - Add `SkipTween` 154 | - Add `SkipTweener` 155 | - Add `TweenProgress` to replace `TweenState` 156 | - Add `SpanTweensBuilderExt` 157 | - Add `SpanTweensBuilder` 158 | - Add `tick_span_tweener_system()` 159 | - Add `SpanTweenerBundle::tween_here()` 160 | - Add `SpanTweenHereBundle` 161 | - Add `DefaultTweenEventsPlugin` to `DefaultTweenPlugins` 162 | - Add `BoxedInterpolator` alias for `Box` 163 | - Add impl `Interpolator` for `Arc` and `dyn Fn` 164 | - Register `EaseFunction` in `EaseFunctionPlugin` 165 | - Register `TweenProgress` in `TweenCorePlugin` 166 | - Add unit tests for `TweenTimer` 167 | - Lots of documentations and code examples 168 | - Remove `TweenTarget` and `Interpolator` trait requirement from `Tween` 169 | - Remove many `TweenTarget` requirement from `Tween` implementations 170 | - Improves `TweenTimer::tick()` code to account to new `Elasped` 171 | - Improves `span_tweener_system` code to account to new `TweenTimer::tick()` behavior 172 | 173 | ### Fixes 174 | 175 | - Fixed missing `AngleZ` tween system in `DefaultInterpolatorsPlugin` 176 | 177 | ### Deprecates 178 | 179 | - Deprecate `QuickSpanTweenBundle` 180 | - Deprecate `span_tween::span_tween()` 181 | - Deprecate `ChildSpanTweenBuilder` 182 | - Deprecate `ChildSpanTweenBuilderExt` 183 | - Deprecate `WorldChildSpanTweenBuilder` 184 | - Deprecate `WorldChildSpanTweenBuilderExt` 185 | - Deprecate `TweenTarget` 186 | - Deprecate `component_tween_system_full` 187 | - Deprecate `resource_tween_system_full` 188 | - Deprecate `asset_tween_system_full` 189 | - Deprecate `Repeat::try_advance_counter` 190 | 191 | ## v0.2.0 - 2024-03-14 192 | 193 | _First release!_ 194 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "bevy_tween" 3 | description = "Flexible tweening plugin library for Bevy" 4 | version = "0.8.0" 5 | edition = "2024" 6 | authors = ["Multirious"] 7 | license = "MIT OR Apache-2.0" 8 | repository = "https://github.com/Multirious/bevy_tween" 9 | homepage = "https://github.com/Multirious/bevy_tween" 10 | documentation = "https://docs.rs/bevy_tween" 11 | readme = "README.md" 12 | keywords = ["bevy", "tween", "tweening", "easing", "animation"] 13 | categories = ["game-development"] 14 | 15 | [workspace] 16 | resolver = "2" 17 | 18 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 19 | 20 | [dependencies.bevy] 21 | version = "0.16.0" 22 | default-features = false 23 | features = ["std"] 24 | 25 | [dependencies.bevy_math] 26 | version = "0.16.0" 27 | default-features = false 28 | features = ["curve"] 29 | 30 | [dependencies.bevy_time_runner] 31 | version = "0.4.0" 32 | 33 | [dependencies.serde] 34 | version = "1" 35 | optional = true 36 | features = ["derive"] 37 | 38 | [dependencies.tracing] 39 | version = "0.1.41" 40 | features = ["std"] 41 | 42 | [dependencies.bevy_lookup_curve ] 43 | version = "0.9.0" 44 | optional = true 45 | 46 | [dev-dependencies] 47 | bevy-inspector-egui = "0.31.0" 48 | rand = "0.9.0" 49 | 50 | [dev-dependencies.bevy] 51 | version = "0.16.0" 52 | default-features = false 53 | features = [ 54 | "bevy_window", 55 | "bevy_winit", 56 | "bevy_core_pipeline", 57 | "bevy_sprite", 58 | "hdr", 59 | "tonemapping_luts", 60 | "png", 61 | ] 62 | 63 | [build-dependencies] 64 | rustc_version = "0.4.1" 65 | 66 | [features] 67 | default = [ 68 | "bevy_asset", 69 | "bevy_render", 70 | "bevy_sprite", 71 | "bevy_ui", 72 | ] 73 | 74 | # Adds tweening systems for asset 75 | bevy_asset = ["bevy/bevy_asset"] 76 | # Adds nothing just yet but required by the "bevy_sprite" feature. 77 | bevy_render = ["bevy/bevy_render"] 78 | # Add some built-in interpolators related to sprite 79 | bevy_sprite = ["bevy/bevy_sprite"] 80 | # Adds some built-in interpolators related to ui 81 | bevy_ui = ["bevy/bevy_ui"] 82 | # Supports for `bevy_lookup_curve` (https://github.com/villor/bevy_lookup_curve) 83 | bevy_lookup_curve = ["dep:bevy_lookup_curve", "bevy_asset"] 84 | # Derive Serialize and Deserialize for some types 85 | serde = ["dep:serde", "bevy_math/serialize"] 86 | 87 | [package.metadata.docs.rs] 88 | all-features = true 89 | 90 | [[example]] 91 | name = "banner_bounce" 92 | path = "examples/animation/banner_bounce.rs" 93 | required-features = [ 94 | "bevy_sprite", 95 | "bevy_asset", 96 | ] 97 | 98 | [[example]] 99 | name = "banner_triangle" 100 | path = "examples/animation/banner_triangle.rs" 101 | required-features = [ 102 | "bevy_sprite", 103 | "bevy_asset", 104 | ] 105 | 106 | [[example]] 107 | name = "thumbnail_triangle" 108 | path = "examples/animation/thumbnail_triangle.rs" 109 | required-features = [ 110 | "bevy_sprite", 111 | ] 112 | 113 | [[example]] 114 | name = "follow" 115 | path = "examples/demo/follow.rs" 116 | required-features = [ 117 | "bevy_sprite", 118 | "bevy_asset", 119 | ] 120 | 121 | [[example]] 122 | name = "click" 123 | path = "examples/demo/click.rs" 124 | required-features = [ 125 | "bevy_sprite", 126 | "bevy_asset", 127 | ] 128 | 129 | [[example]] 130 | name = "hold" 131 | path = "examples/demo/hold.rs" 132 | required-features = [ 133 | "bevy_sprite", 134 | "bevy_asset", 135 | ] 136 | 137 | [[example]] 138 | name = "event" 139 | path = "examples/demo/event.rs" 140 | required-features = [ 141 | "bevy_sprite", 142 | "bevy_asset", 143 | ] 144 | 145 | [[example]] 146 | name = "entity_event" 147 | path = "examples/demo/entity_event.rs" 148 | 149 | [[example]] 150 | name = "sprite_sheet" 151 | path = "examples/demo/sprite_sheet.rs" 152 | required-features = [ 153 | "bevy_sprite", 154 | ] 155 | 156 | [[example]] 157 | name = "entity_structure" 158 | path = "examples/entity_structure.rs" 159 | 160 | [[example]] 161 | name = "bevy_lookup_curve" 162 | path = "examples/bevy_lookup_curve.rs" 163 | required-features = [ 164 | "bevy_sprite", 165 | "bevy_lookup_curve", 166 | ] 167 | 168 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Multirious 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [![Crates.io Version](https://img.shields.io/crates/v/bevy_tween?style=for-the-badge)](https://crates.io/crates/bevy_tween) 4 | [![Crates.io License](https://img.shields.io/crates/l/bevy_tween?style=for-the-badge)](https://github.com/Multirious/bevy_tween/blob/main/README.md#license) 5 | [![Docs.rs](https://img.shields.io/docsrs/bevy_tween?style=for-the-badge)](https://docs.rs/bevy_tween) 6 | 7 | # `bevy_tween` 8 | 9 | [Bevy](https://github.com/bevyengine/bevy) procedural and keyframe animation library. 10 | 11 | This is a young plugin and APIs are to be fleshed out. 12 | Breaking changes are to be expected! 13 | 14 | See changelog [here](CHANGELOG.md). 15 | 16 | ## Features 17 | - **Ergonomic and user-friendly API**: This crate provides functional animation 18 | framework, inspired by [nom](https://crates.io/crates/nom) and other functional 19 | languages, which allows for declarative animation. 20 |
21 | Example 22 | 23 | ```rust 24 | let sprite_id = commands.spawn(SpriteBundle { ... }).id(); 25 | let sprite = sprite_id.into_target(); 26 | commands.animation() 27 | .insert(tween( 28 | Duration::from_secs(1), 29 | EaseKind::Linear, 30 | sprite.with(translation(pos0, pos1)) 31 | )); 32 | ``` 33 | 34 | Many functional techniques are available such as decoupling and composition. 35 | ```rust 36 | fn my_animation( 37 | target: TargetComponent, 38 | duration: Duration 39 | ) -> impl FnOnce(&mut AnimationCommands, &mut Duration) { 40 | parallel(( 41 | tween(duration, EaseKind::QuadraticOut, target.with(translation(...))), 42 | tween(duration, EaseKind::QuadraticOut, target.with(rotation(...))), 43 | )) 44 | } 45 | 46 | let sprite_id = commands.spawn(Sprite { ... }).id(); 47 | let sprite = sprite_id.into_target(); 48 | commands.animation().insert(my_animation(sprite, Duration::from_secs(1))) 49 | ``` 50 | 51 |
52 | - **Flexible and Extensible**: This crate is built on top of [`bevy_time_runner`](https://github.com/Multirious/bevy_time_runner) 53 | which mean we can extend this crate by adding *any* components and systems. 54 | - Tween anything from anywhere, built-in or custom system. 55 | - Interpolate with any curve, built-in or custom system. 56 | - Anything else. 57 | 58 | - **Parallelism**: Tweens are typed and can be query over by their typed system 59 | which increase chances for system parallelism. 60 | - **Rich timer control**: 61 | - Looping 62 | - Time scaling 63 | - Skip backward or forward 64 | - Jumping to anywhen 65 | 66 | See [demos](#Demos) 67 | 68 | Goals: 69 | - [x] Flexible 🎉 70 | - integration with other crates (?) 71 | - [ ] `bevy_animation` 72 | - [x] `bevy_lookup_curve` 73 | - [ ] Editor. While the original goal for this crate is tweening from code, 74 | this crate absolutely has the capability to work on any complex animations. 75 | The editor will aid in such jobs. 76 | - Real-time display at any point in time in the animation. 77 | - Editing path from point A to point B with arbitary curve. 78 | 79 | ## Differences and how it works 80 | This crate chose a different approach to storing tween data/input to solve flexibility issues present in 81 | [`bevy_tweening`](https://github.com/djeedai/bevy_tweening) or [`bevy_easings`](https://github.com/vleue/bevy_easings), 82 | like difficulty tweening in parallel or sequence of multiple types. 83 | 84 | This approach offers: 85 | - As mentioned above, capability to tween in parallel, sequence, or overlapping of any types. 86 | - Extendability. And not just for tweening. For example, this crate adds a custom event 87 | plugin that can emit arbitrary events anytime in the animation by just building off the same system. 88 | 89 | How it works: 90 | 1. By building on top of [`bevy_time_runner`](https://github.com/Multirious/bevy_time_runner), an animator is a tree of entities. 91 | 2. The parent is [`TimeRunner`](https://docs.rs/bevy_time_runner/latest/bevy_time_runner/struct.TimeRunner.html). 92 | This entity handles all the timing and progress of its children. 93 | 3. The children can be any combination of any components, such as tweens by this crate. 94 | 4. Systems are then run in a pipeline, communicating through various dependency-injected components. 95 | 96 | ## Feature gates 97 | - Defaults 98 | - `bevy_asset`
99 | Add tweening systems for asset. 100 | - `bevy_render`
101 | Currently add nothing but required by the `bevy_sprite` feature. 102 | - `bevy_sprite`
103 | Add some built-in interpolators related to sprite. 104 | - `bevy_ui`
105 | Add some built-in interpolators related to ui. 106 | - Optional 107 | - `bevy_lookup_curve`.
108 | Adds interpolation implementation using [`bevy_lookup_curve`](https://github.com/villor/bevy_lookup_curve). 109 | 110 | ## Bevy Version Support 111 | 112 | | `bevy` | `bevy_tween` | 113 | |--------|--------------| 114 | | 0.16 | 0.8 | 115 | | 0.15 | 0.7 | 116 | | 0.14 | 0.6 | 117 | | 0.13 | 0.2–0.5 | 118 | 119 | ## Credits 120 | - [`bevy_tweening`](https://github.com/djeedai/bevy_tweening) 121 | 122 | The first crate that I've learned and improve this crate upon. 123 | 124 | - [`godot`](https://github.com/godotengine/godot) 125 | 126 | There are one common pattern in Godot to uses Node as composition interface 127 | which inspired how [`bevy_time_runner`](https://github.com/Multirious/bevy_time_runner) works. 128 | 129 | - [`nom`](https://crates.io/crates/nom) 130 | 131 | `nom` provides function parser combinator framework that inspried this crate's 132 | animation framework. 133 | 134 | ## Contributions 135 | 136 | Contributions are welcome! 137 | 138 | ## License 139 | 140 | Licensed under either of 141 | 142 | * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 143 | * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 144 | 145 | at your option. 146 | 147 | ### Your contributions 148 | Unless you explicitly state otherwise, any contribution intentionally submitted for 149 | inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual 150 | licensed as above, without any additional terms or conditions. 151 | 152 | 153 | 154 | ## Demos 155 | 156 | `cargo run --example follow -F bevy/bevy_winit`
157 | A square will follow your circle with configurable animation.
158 | 159 | https://github.com/Multirious/bevy_tween/assets/77918086/d582c2de-0f54-4b22-be03-e3bff3348deb 160 | 161 | --- 162 | 163 | `cargo run --example click -F bevy/bevy_winit`
164 | Click left to spawn a circle. Hold right click to repetitively spawn a circle every frame.
165 | 166 | https://github.com/Multirious/bevy_tween/assets/77918086/fd0fe9d3-13a2-4261-880c-cc2609b875ba 167 | 168 | --- 169 | 170 | `cargo run --example hold -F bevy/bevy_winit`
171 | Hold left click to increase the effect intensitiy.
172 | 173 | https://github.com/Multirious/bevy_tween/assets/77918086/33a297a6-19f2-4146-a906-1a88ff037ab3 174 | 175 | --- 176 | 177 | `cargo run --example event -F bevy/bevy_winit`
178 | Showcasing the tween event feature.
179 | 180 | https://github.com/Multirious/bevy_tween/assets/77918086/9507c467-6428-4aed-bd00-511f05e6e951 181 | 182 | --- 183 | 184 | `cargo run --example sprite_sheet -F bevy/bevy_winit`
185 | Sprite Sheet animation.
186 | 187 | https://github.com/Multirious/bevy_tween/assets/77918086/e3997b06-38e6-4add-85f5-a885b69c6687 188 | -------------------------------------------------------------------------------- /assets/bevy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Multirious/bevy_tween/ff0c61a6adbe7364abf26b296ee28850b3edd33b/assets/bevy.png -------------------------------------------------------------------------------- /assets/bevy_tween.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Multirious/bevy_tween/ff0c61a6adbe7364abf26b296ee28850b3edd33b/assets/bevy_tween.png -------------------------------------------------------------------------------- /assets/big_triangle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Multirious/bevy_tween/ff0c61a6adbe7364abf26b296ee28850b3edd33b/assets/big_triangle.png -------------------------------------------------------------------------------- /assets/big_x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Multirious/bevy_tween/ff0c61a6adbe7364abf26b296ee28850b3edd33b/assets/big_x.png -------------------------------------------------------------------------------- /assets/circle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Multirious/bevy_tween/ff0c61a6adbe7364abf26b296ee28850b3edd33b/assets/circle.png -------------------------------------------------------------------------------- /assets/circle_filled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Multirious/bevy_tween/ff0c61a6adbe7364abf26b296ee28850b3edd33b/assets/circle_filled.png -------------------------------------------------------------------------------- /assets/dot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Multirious/bevy_tween/ff0c61a6adbe7364abf26b296ee28850b3edd33b/assets/dot.png -------------------------------------------------------------------------------- /assets/pink_fire_ball.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Multirious/bevy_tween/ff0c61a6adbe7364abf26b296ee28850b3edd33b/assets/pink_fire_ball.png -------------------------------------------------------------------------------- /assets/square.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Multirious/bevy_tween/ff0c61a6adbe7364abf26b296ee28850b3edd33b/assets/square.png -------------------------------------------------------------------------------- /assets/square_filled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Multirious/bevy_tween/ff0c61a6adbe7364abf26b296ee28850b3edd33b/assets/square_filled.png -------------------------------------------------------------------------------- /assets/triangle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Multirious/bevy_tween/ff0c61a6adbe7364abf26b296ee28850b3edd33b/assets/triangle.png -------------------------------------------------------------------------------- /assets/triangle_filled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Multirious/bevy_tween/ff0c61a6adbe7364abf26b296ee28850b3edd33b/assets/triangle_filled.png -------------------------------------------------------------------------------- /assets/tween.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Multirious/bevy_tween/ff0c61a6adbe7364abf26b296ee28850b3edd33b/assets/tween.png -------------------------------------------------------------------------------- /assets/x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Multirious/bevy_tween/ff0c61a6adbe7364abf26b296ee28850b3edd33b/assets/x.png -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | use rustc_version::{version_meta, Channel}; 2 | 3 | fn main() { 4 | println!("cargo::rustc-check-cfg=cfg(CHANNEL_NIGHTLY)"); 5 | if let Channel::Nightly = version_meta().unwrap().channel { 6 | println!("cargo::rustc-cfg=CHANNEL_NIGHTLY") 7 | }; 8 | } 9 | -------------------------------------------------------------------------------- /examples/animation/banner_triangle.rs: -------------------------------------------------------------------------------- 1 | use std::f32::consts::TAU; 2 | use bevy::prelude::*; 3 | use bevy_tween::{ 4 | combinator::{parallel, tween_exact, AnimationCommands}, 5 | interpolate::{angle_z, translation}, 6 | prelude::*, 7 | tween::{AnimationTarget, TargetComponent}, 8 | }; 9 | 10 | fn main() { 11 | App::new() 12 | .add_plugins(( 13 | DefaultPlugins.set(WindowPlugin { 14 | primary_window: Some(Window { 15 | title: "Banner triangle".to_string(), 16 | resizable: false, 17 | resolution: Vec2::new(1100., 250.).into(), 18 | ..Default::default() 19 | }), 20 | ..Default::default() 21 | }), 22 | DefaultTweenPlugins, 23 | )) 24 | .add_systems(Startup, setup) 25 | .run(); 26 | } 27 | 28 | fn setup(mut commands: Commands, asset_server: Res) { 29 | commands.spawn(Camera2d); 30 | 31 | let triangle_image = asset_server.load("big_triangle.png"); 32 | // colors by https://color-hex.org/color-palettes/189 33 | let colors = [ 34 | Color::srgb_u8(0, 128, 191), 35 | Color::srgb_u8(0, 172, 223), 36 | Color::srgb_u8(85, 208, 255), 37 | Color::srgb_u8(124, 232, 255), 38 | Color::srgb_u8(204, 249, 255), 39 | ]; 40 | 41 | let mut spawn_triangle = |color, z| { 42 | commands 43 | .spawn(( 44 | Sprite { 45 | image: triangle_image.clone(), 46 | color, 47 | ..Default::default() 48 | }, 49 | Transform::from_xyz(0., 0., z), 50 | )) 51 | .id() 52 | }; 53 | let triangles = colors 54 | .iter() 55 | .enumerate() 56 | .map(|(i, color)| spawn_triangle(*color, (i + 2) as f32)) 57 | .map(|t| t.into_target()) 58 | .collect::>(); 59 | 60 | let secs = 12.; 61 | let ease = EaseKind::ExponentialInOut; 62 | 63 | commands 64 | .animation() 65 | .repeat(Repeat::Infinitely) 66 | .insert(parallel(( 67 | snap_rotate(triangles[4].clone(), secs, 7, 4., ease), 68 | snap_rotate(triangles[3].clone(), secs, 7, 6., ease), 69 | snap_rotate(triangles[2].clone(), secs, 7, 8., ease), 70 | snap_rotate(triangles[1].clone(), secs, 7, 10., ease), 71 | snap_rotate(triangles[0].clone(), secs, 7, 12., ease), 72 | ))); 73 | 74 | let dotted_line_target = AnimationTarget.into_target(); 75 | commands 76 | .spawn((Transform::IDENTITY, Visibility::Visible, AnimationTarget)) 77 | .with_children(dotted_line) 78 | .animation() 79 | .repeat(Repeat::Infinitely) 80 | .insert_tween_here( 81 | Duration::from_secs_f32(12. / 7.), 82 | EaseKind::ExponentialInOut, 83 | dotted_line_target 84 | .with(translation(Vec3::ZERO, Vec3::new(30. * 10., 0., 0.))), 85 | ); 86 | 87 | commands.spawn(( 88 | Sprite { 89 | custom_size: Some(Vec2::new(250., 250.)), 90 | color: Color::srgb_u8(43, 44, 47), 91 | ..Default::default() 92 | }, 93 | Transform::from_xyz(0., 0., 1.), 94 | )); 95 | } 96 | 97 | fn secs(secs: f32) -> Duration { 98 | Duration::from_secs_f32(secs) 99 | } 100 | 101 | fn snap_rotate( 102 | target: TargetComponent, 103 | dur: f32, 104 | max: usize, 105 | rev: f32, 106 | ease: EaseKind, 107 | ) -> impl FnOnce(&mut AnimationCommands, &mut Duration) { 108 | move |a, pos| { 109 | for i in 0..max { 110 | let max = max as f32; 111 | let i = i as f32; 112 | tween_exact( 113 | secs(i / max * dur)..secs((i + 1.) / max * dur), 114 | ease, 115 | target.with(angle_z( 116 | rev * TAU * (max - i) / max, 117 | rev * TAU * (max - i - 1.) / max, 118 | )), 119 | )(a, pos); 120 | } 121 | *pos += secs(dur) 122 | } 123 | } 124 | 125 | fn dotted_line(c: &mut ChildSpawnerCommands) { 126 | let color = Color::WHITE; 127 | let count = 70; 128 | let height = 5.; 129 | let width = 20.; 130 | let spacing = 30.; 131 | let x_offset = 132 | -(width * count as f32 + (spacing - width) * count as f32) / 2.; 133 | for i in 0..count { 134 | let i = i as f32; 135 | c.spawn(( 136 | Sprite { 137 | color, 138 | custom_size: Some(Vec2::new(width, height)), 139 | ..Default::default() 140 | }, 141 | Transform::from_xyz(i * spacing + x_offset, 0., 0.), 142 | )); 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /examples/animation/thumbnail_triangle.rs: -------------------------------------------------------------------------------- 1 | use std::f32::consts::TAU; 2 | 3 | use bevy::prelude::*; 4 | use bevy_tween::{ 5 | combinator::{parallel, tween_exact, AnimationCommands}, 6 | interpolate::angle_z, 7 | prelude::*, 8 | tween::TargetComponent, 9 | }; 10 | 11 | fn main() { 12 | App::new() 13 | .add_plugins((DefaultPlugins, DefaultTweenPlugins)) 14 | .add_systems(Startup, setup) 15 | .run(); 16 | } 17 | 18 | fn setup(mut commands: Commands, asset_server: Res) { 19 | commands.spawn(Camera2d); 20 | 21 | let bevy_text = asset_server.load("bevy.png"); 22 | let tween_text = asset_server.load("tween.png"); 23 | let triangle_image = asset_server.load("big_triangle.png"); 24 | let ease = EaseKind::ExponentialInOut; 25 | 26 | commands.spawn(( 27 | Sprite { 28 | image: bevy_text, 29 | ..default() 30 | }, 31 | Transform::from_xyz(-300., 0., 0.), 32 | )); 33 | 34 | commands.spawn(( 35 | Sprite { 36 | image: tween_text, 37 | ..default() 38 | }, 39 | Transform::from_xyz(340., 10., 0.), 40 | )); 41 | 42 | // colors by https://color-hex.org/color-palettes/189 43 | let colors = [ 44 | Color::srgb_u8(0, 128, 191), 45 | Color::srgb_u8(0, 172, 223), 46 | Color::srgb_u8(85, 208, 255), 47 | Color::srgb_u8(124, 232, 255), 48 | Color::srgb_u8(204, 249, 255), 49 | ]; 50 | let mut spawn_triangle = |color, z| { 51 | commands 52 | .spawn(( 53 | Sprite { 54 | image: triangle_image.clone(), 55 | color, 56 | ..default() 57 | }, 58 | Transform::from_xyz(0., 0., z), 59 | )) 60 | .id() 61 | }; 62 | let triangles = colors 63 | .iter() 64 | .enumerate() 65 | .map(|(i, color)| spawn_triangle(*color, (i + 2) as f32)) 66 | .map(|t| t.into_target()) 67 | .collect::>(); 68 | 69 | let secs = 12.; 70 | 71 | commands 72 | .animation() 73 | .repeat(Repeat::Infinitely) 74 | .insert(parallel(( 75 | snap_rotate(triangles[4].clone(), secs, 7, 4., ease), 76 | snap_rotate(triangles[3].clone(), secs, 7, 6., ease), 77 | snap_rotate(triangles[2].clone(), secs, 7, 8., ease), 78 | snap_rotate(triangles[1].clone(), secs, 7, 10., ease), 79 | snap_rotate(triangles[0].clone(), secs, 7, 12., ease), 80 | ))); 81 | } 82 | 83 | fn secs(secs: f32) -> Duration { 84 | Duration::from_secs_f32(secs) 85 | } 86 | 87 | fn snap_rotate( 88 | target: TargetComponent, 89 | dur: f32, 90 | max: usize, 91 | rev: f32, 92 | ease: EaseKind, 93 | ) -> impl FnOnce(&mut AnimationCommands, &mut Duration) { 94 | move |a, pos| { 95 | for i in 0..max { 96 | let max = max as f32; 97 | let i = i as f32; 98 | tween_exact( 99 | secs(i / max * dur)..secs((i + 1.) / max * dur), 100 | ease, 101 | target.with(angle_z( 102 | rev * TAU * (max - i) / max, 103 | rev * TAU * (max - i - 1.) / max, 104 | )), 105 | )(a, pos); 106 | } 107 | *pos += secs(dur) 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /examples/bevy_lookup_curve.rs: -------------------------------------------------------------------------------- 1 | use bevy::prelude::*; 2 | use bevy_inspector_egui::bevy_egui::EguiPlugin; 3 | use bevy_tween::{ 4 | bevy_lookup_curve::{ 5 | editor::LookupCurveEditor, Knot, KnotInterpolation, LookupCurve, 6 | LookupCurvePlugin, 7 | }, 8 | combinator::tween, 9 | interpolate::translation, 10 | interpolation::bevy_lookup_curve::LookupCurveHandle, 11 | prelude::*, 12 | tween::{AnimationTarget, TargetComponent}, 13 | }; 14 | 15 | fn main() { 16 | App::new() 17 | .add_plugins(( 18 | DefaultPlugins, 19 | DefaultTweenPlugins, 20 | EguiPlugin { 21 | enable_multipass_for_primary_context: false, 22 | }, 23 | LookupCurvePlugin, 24 | )) 25 | .add_systems(Startup, setup) 26 | .run(); 27 | } 28 | 29 | fn default_curve() -> LookupCurve { 30 | LookupCurve::new(vec![ 31 | Knot { 32 | position: Vec2::ZERO, 33 | interpolation: KnotInterpolation::Cubic, 34 | ..Default::default() 35 | }, 36 | Knot { 37 | position: Vec2::ONE, 38 | interpolation: KnotInterpolation::Linear, 39 | ..Default::default() 40 | }, 41 | ]) 42 | } 43 | 44 | fn setup(mut commands: Commands, mut curves: ResMut>) { 45 | commands.spawn(Camera2d); 46 | let curve = curves.add(default_curve()); 47 | let sprite = TargetComponent::marker(); 48 | commands 49 | .spawn(( 50 | Sprite { 51 | custom_size: Some(Vec2::new(100., 100.)), 52 | ..default() 53 | }, 54 | AnimationTarget, 55 | )) 56 | .animation() 57 | .repeat(Repeat::Infinitely) 58 | .repeat_style(RepeatStyle::WrapAround) 59 | .insert(tween( 60 | Duration::from_secs(5), 61 | ( 62 | LookupCurveHandle(curve.clone()), 63 | LookupCurveEditor::new(curve), 64 | ), 65 | sprite.with(translation( 66 | Vec3::new(-300., -300., 0.), 67 | Vec3::new(300., -300., 0.), 68 | )), 69 | )); 70 | } 71 | -------------------------------------------------------------------------------- /examples/demo/click.rs: -------------------------------------------------------------------------------- 1 | use bevy::{ 2 | color::palettes::css::{DEEP_PINK, WHITE}, 3 | prelude::*, 4 | }; 5 | use bevy_tween::{ 6 | bevy_time_runner::TimeRunnerEnded, combinator::*, prelude::*, 7 | tween::AnimationTarget, 8 | }; 9 | mod utils; 10 | 11 | fn secs(secs: f32) -> Duration { 12 | Duration::from_secs_f32(secs) 13 | } 14 | 15 | fn main() { 16 | App::new() 17 | .add_plugins((DefaultPlugins, DefaultTweenPlugins)) 18 | .add_systems(Startup, setup) 19 | .add_systems( 20 | Update, 21 | ( 22 | utils::main_cursor_world_coord_system, 23 | click_spawn_circle, 24 | despawn_finished_circle, 25 | ), 26 | ) 27 | .init_resource::() 28 | .run(); 29 | } 30 | 31 | fn setup(mut commands: Commands) { 32 | commands.spawn((Camera2d, utils::MainCamera)); 33 | } 34 | 35 | fn click_spawn_circle( 36 | mut commands: Commands, 37 | coord: Res, 38 | key: Res>, 39 | asset_server: Res, 40 | ) { 41 | use interpolate::sprite_color; 42 | let circle_filled_image = asset_server.load("circle_filled.png"); 43 | if let Some(coord) = coord.0 { 44 | if key.just_pressed(MouseButton::Left) 45 | || key.pressed(MouseButton::Right) 46 | { 47 | let start = Vec3::new(coord.x, coord.y, 1.); 48 | let end = Vec3::new(0., 0., 0.); 49 | let transform = Transform::from_translation(start); 50 | let circle = AnimationTarget.into_target(); 51 | let mut circle_transform = circle.transform_state(transform); 52 | commands 53 | .spawn(( 54 | Sprite { 55 | image: circle_filled_image, 56 | ..default() 57 | }, 58 | transform, 59 | AnimationTarget, 60 | )) 61 | .animation() 62 | .insert(parallel(( 63 | tween( 64 | secs(2.), 65 | EaseKind::ExponentialOut, 66 | circle_transform.translation_to(end), 67 | ), 68 | tween( 69 | secs(1.), 70 | EaseKind::BackIn, 71 | circle_transform.scale_to(Vec3::ZERO), 72 | ), 73 | tween( 74 | secs(1.), 75 | EaseKind::Linear, 76 | circle.with(sprite_color( 77 | into_color(WHITE), 78 | into_color(DEEP_PINK), 79 | )), 80 | ), 81 | ))); 82 | } 83 | } 84 | } 85 | 86 | fn despawn_finished_circle( 87 | mut commands: Commands, 88 | mut time_runner_ended_reader: EventReader, 89 | ) { 90 | for t in time_runner_ended_reader.read() { 91 | commands.entity(t.time_runner).despawn(); 92 | } 93 | } 94 | 95 | fn into_color>(color: T) -> Color { 96 | Color::Srgba(color.into()) 97 | } 98 | -------------------------------------------------------------------------------- /examples/demo/entity_event.rs: -------------------------------------------------------------------------------- 1 | use bevy::prelude::*; 2 | use bevy_tween::{ 3 | bevy_time_runner::TimeRunnerEnded, 4 | combinator::{event, forward, sequence}, 5 | prelude::*, 6 | }; 7 | 8 | fn main() { 9 | App::new() 10 | .add_plugins((MinimalPlugins, DefaultTweenPlugins)) 11 | .add_systems(Startup, setup) 12 | .add_observer(|trigger: Trigger>| { 13 | println!("TweenEvent: {}", trigger.data) 14 | }) 15 | .add_observer(|trigger: Trigger| { 16 | if trigger.is_completed() { 17 | println!("done!"); 18 | } else { 19 | println!("repeating"); 20 | } 21 | }) 22 | .run(); 23 | } 24 | 25 | fn setup(mut commands: Commands) { 26 | commands 27 | .animation() 28 | .repeat(Repeat::times(5)) 29 | .insert(forward(Duration::from_secs_f32(0.5))); 30 | 31 | commands.animation().insert(sequence(( 32 | forward(Duration::from_secs_f32(3.)), 33 | event("tween"), 34 | forward(Duration::from_secs_f32(0.5)), 35 | event("event"), 36 | ))); 37 | } 38 | -------------------------------------------------------------------------------- /examples/demo/event.rs: -------------------------------------------------------------------------------- 1 | use bevy::{ 2 | color::palettes::css::{DEEP_PINK, WHITE}, 3 | prelude::*, 4 | }; 5 | use bevy_tween::{ 6 | bevy_time_runner::TimeRunnerEnded, combinator::*, prelude::*, 7 | tween::AnimationTarget, 8 | }; 9 | 10 | fn main() { 11 | App::new() 12 | .add_plugins((DefaultPlugins, DefaultTweenPlugins)) 13 | .add_systems(Startup, setup) 14 | .add_systems(Update, (effect_system, despawn_effect_system)) 15 | .run(); 16 | } 17 | 18 | fn secs(secs: f32) -> Duration { 19 | Duration::from_secs_f32(secs) 20 | } 21 | 22 | fn into_color>(color: T) -> Color { 23 | Color::Srgba(color.into()) 24 | } 25 | 26 | #[derive(Resource)] 27 | struct EffectPos { 28 | trail: Vec3, 29 | boom: Vec3, 30 | } 31 | 32 | #[derive(Component)] 33 | struct Triangle; 34 | 35 | fn setup(mut commands: Commands, asset_server: Res) { 36 | use interpolate::{angle_z_to, translation_to}; 37 | commands.spawn(Camera2d); 38 | 39 | let x_left = -300.; 40 | let x_right = 300.; 41 | 42 | commands.insert_resource(EffectPos { 43 | trail: Vec3::new(x_left - 40., 0., 0.), 44 | boom: Vec3::new(x_right, 0., 0.), 45 | }); 46 | 47 | let start_angle = -90.0_f32.to_radians(); 48 | let mid_angle = start_angle + 540.0_f32.to_radians(); 49 | let end_angle = mid_angle + 180.0_f32.to_radians(); 50 | 51 | let triangle = AnimationTarget.into_target(); 52 | let mut triangle_translation = triangle.state(Vec3::new(x_left, 0., 0.)); 53 | 54 | let mut triangle_angle_z = triangle.state(start_angle); 55 | 56 | commands 57 | .spawn(( 58 | Triangle, 59 | Sprite { 60 | image: asset_server.load("triangle_filled.png"), 61 | ..default() 62 | }, 63 | AnimationTarget, 64 | )) 65 | .animation() 66 | .repeat(Repeat::Infinitely) 67 | .insert(sequence(( 68 | event("bump"), 69 | tween( 70 | secs(1.), 71 | EaseKind::ExponentialIn, 72 | ( 73 | triangle_translation 74 | .with(translation_to(Vec3::new(x_right, 0., 0.))), 75 | triangle_angle_z.with(angle_z_to(mid_angle)), 76 | ), 77 | ), 78 | backward(secs(0.2)), 79 | event_for(secs(0.2), "small_boom"), 80 | event("boom"), 81 | tween( 82 | secs(1.), 83 | EaseKind::CircularOut, 84 | ( 85 | triangle_translation 86 | .with(translation_to(Vec3::new(x_left, 0., 0.))), 87 | triangle_angle_z.with(angle_z_to(end_angle)), 88 | ), 89 | ), 90 | ))); 91 | } 92 | 93 | #[derive(Component)] 94 | struct Effect; 95 | 96 | fn effect_system( 97 | mut commands: Commands, 98 | asset_server: Res, 99 | effect_pos: Res, 100 | q_triangle: Query<&Transform, With>, 101 | mut event: EventReader>, 102 | ) { 103 | use interpolate::{scale, sprite_color, translation}; 104 | event.read().for_each(|event| match event.data { 105 | "bump" => { 106 | let entity = AnimationTarget.into_target(); 107 | commands 108 | .spawn(( 109 | Effect, 110 | Sprite { 111 | custom_size: Some(Vec2::new(20., 100.)), 112 | ..Default::default() 113 | }, 114 | Transform::from_translation(effect_pos.trail), 115 | AnimationTarget, 116 | )) 117 | .animation() 118 | .insert_tween_here( 119 | secs(1.), 120 | EaseKind::QuinticOut, 121 | ( 122 | entity.with(translation( 123 | effect_pos.trail, 124 | effect_pos.trail - Vec3::new(100., 0., 0.), 125 | )), 126 | entity.with(sprite_color( 127 | into_color(WHITE), 128 | into_color(DEEP_PINK.with_alpha(0.)), 129 | )), 130 | ), 131 | ); 132 | } 133 | "small_boom" => { 134 | let entity = AnimationTarget.into_target(); 135 | commands 136 | .spawn(( 137 | Effect, 138 | Sprite { 139 | image: asset_server.load("circle.png"), 140 | ..default() 141 | }, 142 | Transform::from_translation( 143 | q_triangle.single().unwrap().translation, 144 | ), 145 | AnimationTarget, 146 | )) 147 | .animation() 148 | .insert_tween_here( 149 | secs(0.1), 150 | EaseKind::Linear, 151 | ( 152 | entity.with(scale( 153 | Vec3::new(0.5, 0.5, 0.), 154 | Vec3::new(3., 3., 0.), 155 | )), 156 | entity.with(sprite_color( 157 | into_color(WHITE.with_alpha(0.5)), 158 | into_color(DEEP_PINK.with_alpha(0.)), 159 | )), 160 | ), 161 | ); 162 | } 163 | "boom" => { 164 | let entity = AnimationTarget.into_target(); 165 | commands 166 | .spawn(( 167 | Effect, 168 | Sprite { 169 | image: asset_server.load("circle.png"), 170 | ..default() 171 | }, 172 | Transform::from_translation(effect_pos.boom), 173 | AnimationTarget, 174 | )) 175 | .animation() 176 | .insert_tween_here( 177 | secs(0.5), 178 | EaseKind::QuadraticOut, 179 | ( 180 | entity.with(scale( 181 | Vec3::new(1., 1., 0.), 182 | Vec3::new(15., 15., 0.), 183 | )), 184 | entity.with(sprite_color( 185 | into_color(WHITE.with_alpha(1.)), 186 | into_color(DEEP_PINK.with_alpha(0.)), 187 | )), 188 | ), 189 | ); 190 | } 191 | _ => {} 192 | }); 193 | } 194 | 195 | fn despawn_effect_system( 196 | mut commands: Commands, 197 | q_effect: Query<(), With>, 198 | mut ended: EventReader, 199 | ) { 200 | ended.read().for_each(|ended| { 201 | if ended.is_completed() && q_effect.contains(ended.time_runner) { 202 | commands.entity(ended.time_runner).despawn(); 203 | } 204 | }); 205 | } 206 | -------------------------------------------------------------------------------- /examples/demo/follow.rs: -------------------------------------------------------------------------------- 1 | mod utils; 2 | 3 | use std::f32::consts::TAU; 4 | 5 | use bevy::{ 6 | color::palettes::css::{DEEP_PINK, WHITE}, 7 | prelude::*, 8 | }; 9 | use bevy_inspector_egui::{ 10 | bevy_egui::EguiPlugin, quick::ResourceInspectorPlugin, 11 | }; 12 | use bevy_tween::{ 13 | bevy_time_runner::TimeRunner, 14 | interpolate::{scale, sprite_color, translation}, 15 | prelude::*, 16 | tween::AnimationTarget, 17 | }; 18 | 19 | fn main() { 20 | App::new() 21 | .add_plugins(( 22 | DefaultPlugins, 23 | DefaultTweenPlugins, 24 | EguiPlugin { 25 | enable_multipass_for_primary_context: false, 26 | }, 27 | ResourceInspectorPlugin::::new(), 28 | )) 29 | .add_systems(Startup, setup) 30 | .add_systems( 31 | Update, 32 | (utils::main_cursor_world_coord_system, jeb_follows_cursor), 33 | ) 34 | .init_resource::() 35 | .init_resource::() 36 | .register_type::() 37 | .run(); 38 | } 39 | 40 | #[derive(Reflect)] 41 | enum UpdateKind { 42 | CursorMoved, 43 | CusorStopped, 44 | AnimatorCompleted, 45 | } 46 | 47 | // Let us change the tween ease and duration at runtime 48 | #[derive(Resource, Reflect)] 49 | struct Config { 50 | tween_duration: Duration, 51 | tween_ease: EaseKind, 52 | update_kind: UpdateKind, 53 | } 54 | impl Default for Config { 55 | fn default() -> Self { 56 | Config { 57 | update_kind: UpdateKind::CursorMoved, 58 | tween_duration: Duration::from_millis(500), 59 | tween_ease: EaseKind::ExponentialOut, 60 | } 61 | } 62 | } 63 | 64 | /// Marker component for the square that will be following the cursor 65 | #[derive(Component)] 66 | struct Jeb; 67 | 68 | /// Marker component for the tween entity we will be modifying to make the follow 69 | /// effect 70 | #[derive(Component)] 71 | struct JebTranslationAnimator; 72 | 73 | fn setup(mut commands: Commands, asset_server: Res) { 74 | commands.spawn((Camera2d, utils::MainCamera)); 75 | 76 | // Spawning the square 77 | commands 78 | .spawn(( 79 | Sprite { 80 | image: asset_server.load("square_filled.png"), 81 | ..default() 82 | }, 83 | Jeb, 84 | AnimationTarget, 85 | )) 86 | .with_children(|c| { 87 | // Spawning the marker for an animator that will be responsible 88 | // for the follow effect 89 | c.spawn(JebTranslationAnimator); 90 | 91 | let jeb = AnimationTarget.into_target(); 92 | // Spawning an animator that's responsible for a rotating effect 93 | c.animation() 94 | .repeat(Repeat::Infinitely) 95 | .repeat_style(RepeatStyle::PingPong) 96 | .insert_tween_here( 97 | Duration::from_secs(2), 98 | EaseKind::CubicInOut, 99 | jeb.with_closure(|transform: &mut Transform, value| { 100 | let start = 0.; 101 | let end = TAU; 102 | transform.rotation = 103 | Quat::from_rotation_z(start.lerp(end, value)); 104 | }), 105 | ); 106 | 107 | // Spawning a Tweener that's responsible for scaling effect 108 | // when you launch up the demo. 109 | c.animation().insert_tween_here( 110 | Duration::from_secs(1), 111 | EaseKind::QuinticIn, 112 | jeb.with(scale(Vec3::ZERO, Vec3::ONE)), 113 | ); 114 | }); 115 | } 116 | 117 | fn jeb_follows_cursor( 118 | mut commands: Commands, 119 | coord: Res, 120 | config: Res, 121 | q_jeb: Query<&Transform, With>, 122 | q_jeb_translation_animator: Query< 123 | (Entity, Option<&TimeRunner>), 124 | With, 125 | >, 126 | mut cursor_moved: EventReader, 127 | ) { 128 | let Some(coord) = coord.0 else { 129 | return; 130 | }; 131 | if let (Ok(jeb_transform), Ok((jeb_animator_entity, jeb_time_runner))) = 132 | (q_jeb.single(), q_jeb_translation_animator.single()) 133 | { 134 | let update = match config.update_kind { 135 | UpdateKind::CursorMoved => cursor_moved.read().next().is_some(), 136 | UpdateKind::CusorStopped => { 137 | let dx = (coord.x - jeb_transform.translation.x).abs(); 138 | let dy = (coord.x - jeb_transform.translation.x).abs(); 139 | let is_near_coord = dx < 0.05 && dy < 0.05; 140 | cursor_moved.read().next().is_none() && !is_near_coord 141 | } 142 | UpdateKind::AnimatorCompleted => match jeb_time_runner { 143 | Some(jeb_time_runner) => { 144 | jeb_time_runner.is_completed() 145 | && coord != jeb_transform.translation.xy() 146 | } 147 | None => true, 148 | }, 149 | }; 150 | if update { 151 | let jeb = AnimationTarget.into_target(); 152 | commands 153 | .entity(jeb_animator_entity) 154 | .animation() 155 | .insert_tween_here( 156 | config.tween_duration, 157 | config.tween_ease, 158 | ( 159 | jeb.with(translation( 160 | jeb_transform.translation, 161 | Vec3::new(coord.x, coord.y, 0.), 162 | )), 163 | jeb.with(sprite_color( 164 | into_color(WHITE), 165 | into_color(DEEP_PINK), 166 | )), 167 | ), 168 | ); 169 | } 170 | } 171 | } 172 | 173 | fn into_color>(color: T) -> Color { 174 | Color::Srgba(color.into()) 175 | } 176 | -------------------------------------------------------------------------------- /examples/demo/hold.rs: -------------------------------------------------------------------------------- 1 | use std::f32::consts::PI; 2 | 3 | use bevy::{ 4 | color::palettes::css::{DEEP_PINK, WHITE}, 5 | prelude::*, 6 | window::{PrimaryWindow, SystemCursorIcon}, 7 | winit::cursor::CursorIcon, 8 | }; 9 | use bevy_tween::{bevy_time_runner::TimeRunner, prelude::*}; 10 | use rand::prelude::*; 11 | 12 | mod interpolate { 13 | use bevy::prelude::*; 14 | use bevy_tween::{prelude::*, resource_tween_system}; 15 | 16 | pub use bevy_tween::interpolate::*; 17 | 18 | pub fn custom_interpolators_plugin(app: &mut App) { 19 | app.add_tween_systems(resource_tween_system::()); 20 | } 21 | 22 | pub struct EffectIntensity { 23 | pub start: f32, 24 | pub end: f32, 25 | } 26 | 27 | impl Interpolator for EffectIntensity { 28 | type Item = super::EffectIntensitiy; 29 | 30 | fn interpolate(&self, item: &mut Self::Item, value: f32) { 31 | item.0 = self.start.lerp(self.end, value) 32 | } 33 | } 34 | 35 | pub fn effect_intensity( 36 | start: f32, 37 | end: f32, 38 | ) -> ResourceTween { 39 | ResourceTween::new(EffectIntensity { start, end }) 40 | } 41 | } 42 | 43 | fn secs(secs: f32) -> Duration { 44 | Duration::from_secs_f32(secs) 45 | } 46 | 47 | fn into_color>(color: T) -> Color { 48 | Color::Srgba(color.into()) 49 | } 50 | 51 | fn main() { 52 | App::new() 53 | .add_plugins(( 54 | DefaultPlugins, 55 | DefaultTweenPlugins, 56 | interpolate::custom_interpolators_plugin, 57 | )) 58 | .add_systems(Startup, setup) 59 | .add_systems(Update, (big_x_do_effect, mouse_hold)) 60 | .init_resource::() 61 | .run(); 62 | } 63 | 64 | #[derive(Component)] 65 | pub struct BigX; 66 | 67 | #[derive(Component)] 68 | pub struct EffectAnimator; 69 | 70 | #[derive(Component)] 71 | pub struct RotatationAnimator; 72 | 73 | #[derive(Default, Resource)] 74 | pub struct EffectIntensitiy(f32); 75 | 76 | fn setup( 77 | mut commands: Commands, 78 | asset_server: Res, 79 | window: Query>, 80 | ) { 81 | use interpolate::{effect_intensity, sprite_color}; 82 | commands 83 | .entity(window.single().unwrap()) 84 | .insert(CursorIcon::System(SystemCursorIcon::Pointer)); 85 | commands.spawn(Camera2d); 86 | let big_x = commands 87 | .spawn(( 88 | Sprite { 89 | image: asset_server.load("big_x.png"), 90 | color: into_color(DEEP_PINK), 91 | ..default() 92 | }, 93 | BigX, 94 | )) 95 | .id(); 96 | let big_x = big_x.into_target(); 97 | commands 98 | .spawn(EffectAnimator) 99 | .animation() 100 | .insert_tween_here( 101 | secs(1.), 102 | EaseKind::QuarticIn, 103 | ( 104 | effect_intensity(0., 1.), 105 | big_x.with(sprite_color( 106 | into_color(DEEP_PINK), 107 | into_color(WHITE), 108 | )), 109 | ), 110 | ); 111 | commands 112 | .spawn(RotatationAnimator) 113 | .animation() 114 | .repeat(Repeat::Infinitely) 115 | .insert_tween_here( 116 | secs(1.), 117 | EaseKind::Linear, 118 | big_x.with(interpolate::angle_z(0., PI * 0.5)), 119 | ); 120 | } 121 | 122 | fn mouse_hold( 123 | mut q_effect_animator: Query<&mut TimeRunner, With>, 124 | mouse_button: Res>, 125 | ) { 126 | let mouse_down = mouse_button.pressed(MouseButton::Left); 127 | q_effect_animator 128 | .single_mut() 129 | .unwrap() 130 | .set_direction(if mouse_down { 131 | TimeDirection::Forward 132 | } else { 133 | TimeDirection::Backward 134 | }); 135 | } 136 | 137 | fn big_x_do_effect( 138 | effect_intensity: Res, 139 | mut q_big_x: Query<&mut Transform, With>, 140 | mut q_rotation_animator: Query<&mut TimeRunner, With>, 141 | ) { 142 | let mut rng = rand::rng(); 143 | let dx: f32 = rng.random(); 144 | let dy: f32 = rng.random(); 145 | q_big_x.single_mut().unwrap().translation = 146 | Vec3::new(dx - 0.5, dy - 0.5, 0.) * 100. * effect_intensity.0; 147 | 148 | q_rotation_animator 149 | .single_mut() 150 | .unwrap() 151 | .set_time_scale(effect_intensity.0); 152 | } 153 | -------------------------------------------------------------------------------- /examples/demo/sprite_sheet.rs: -------------------------------------------------------------------------------- 1 | use bevy::prelude::*; 2 | use bevy_tween::{prelude::*, tween::AnimationTarget}; 3 | 4 | mod interpolate { 5 | use bevy::prelude::*; 6 | use bevy_tween::prelude::*; 7 | 8 | pub use bevy_tween::interpolate::*; 9 | 10 | pub fn custom_interpolators_plugin(app: &mut App) { 11 | app.add_tween_systems( 12 | bevy_tween::component_tween_system::(), 13 | ); 14 | } 15 | 16 | pub fn atlas_index(start: usize, end: usize) -> AtlasIndex { 17 | AtlasIndex { start, end } 18 | } 19 | 20 | pub struct AtlasIndex { 21 | pub start: usize, 22 | pub end: usize, 23 | } 24 | 25 | impl Interpolator for AtlasIndex { 26 | type Item = Sprite; 27 | 28 | fn interpolate(&self, item: &mut Self::Item, value: f32) { 29 | let Some(texture_atlas) = &mut item.texture_atlas else { 30 | return; 31 | }; 32 | let start = self.start as f32; 33 | let end = self.end as f32; 34 | texture_atlas.index = start.lerp(end, value).floor() as usize; 35 | } 36 | } 37 | } 38 | 39 | fn main() { 40 | App::new() 41 | .add_plugins(( 42 | DefaultPlugins.set(ImagePlugin::default_nearest()), 43 | DefaultTweenPlugins, 44 | interpolate::custom_interpolators_plugin, 45 | )) 46 | .add_systems(Startup, setup) 47 | .run(); 48 | } 49 | 50 | fn setup( 51 | mut commands: Commands, 52 | asset_server: Res, 53 | mut texture_atlas_layouts: ResMut>, 54 | ) { 55 | use interpolate::atlas_index; 56 | let texture = asset_server.load("pink_fire_ball.png"); 57 | let layout = 58 | TextureAtlasLayout::from_grid(UVec2::new(32, 32), 16, 1, None, None); 59 | let len = layout.len(); 60 | let atlas_layout = texture_atlas_layouts.add(layout); 61 | 62 | let sprite = AnimationTarget.into_target(); 63 | commands 64 | .spawn(( 65 | Sprite { 66 | image: texture, 67 | texture_atlas: Some(TextureAtlas::from(atlas_layout)), 68 | ..default() 69 | }, 70 | Transform::IDENTITY.with_scale(Vec3::splat(15.)), 71 | AnimationTarget, 72 | )) 73 | .animation() 74 | .repeat(Repeat::Infinitely) 75 | .insert_tween_here( 76 | Duration::from_secs(1), 77 | EaseKind::Linear, 78 | sprite.with(atlas_index(0, len)), 79 | ); 80 | 81 | commands.spawn(Camera2d); 82 | } 83 | -------------------------------------------------------------------------------- /examples/demo/utils/mod.rs: -------------------------------------------------------------------------------- 1 | use bevy::prelude::*; 2 | 3 | #[derive(Component)] 4 | pub struct MainCamera; 5 | 6 | #[derive(Default, Resource)] 7 | pub struct MainCursorWorldCoord(pub Option); 8 | 9 | pub fn main_cursor_world_coord_system( 10 | mut coord: ResMut, 11 | q_primary_window: Query<&Window, With>, 12 | q_camera: Query<(&Camera, &GlobalTransform), With>, 13 | ) { 14 | let (camera, camera_transform) = q_camera.single().unwrap(); 15 | let window = q_primary_window.single().unwrap(); 16 | 17 | if let Some(world_position) = window 18 | .cursor_position() 19 | .and_then(|cursor| { 20 | camera.viewport_to_world(camera_transform, cursor).ok() 21 | }) 22 | .map(|ray| ray.origin.truncate()) 23 | { 24 | coord.0 = Some(world_position); 25 | } else { 26 | coord.0 = None; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /examples/entity_structure.rs: -------------------------------------------------------------------------------- 1 | use bevy::prelude::*; 2 | use bevy_tween::interpolate::{AngleZ, Translation}; 3 | use bevy_tween::prelude::*; 4 | use bevy_tween::{ 5 | bevy_time_runner::{TimeRunner, TimeSpan}, 6 | tween::{AnimationTarget, TargetComponent}, 7 | }; 8 | 9 | fn main() { 10 | App::new() 11 | .add_plugins((DefaultPlugins, DefaultTweenPlugins)) 12 | .add_systems(Startup, setup) 13 | .run(); 14 | } 15 | 16 | fn sprite(start_x: f32, start_y: f32) -> (Sprite, Transform) { 17 | ( 18 | Sprite { 19 | custom_size: Some(Vec2::new(50., 50.)), 20 | color: Color::WHITE, 21 | ..default() 22 | }, 23 | Transform::from_xyz(start_x, start_y, 0.), 24 | ) 25 | } 26 | 27 | /// This show all the possible structure you can use. 28 | /// All of these result in exactly the same animation! 29 | /// Just use what fit for your use case. 30 | /// 31 | /// These will be presented in its most rawest form. 32 | /// See other examples for better APIs. 33 | fn setup(mut commands: Commands) { 34 | commands.spawn(Camera2d); 35 | 36 | let angle_start = 0.; 37 | let angle_end = std::f32::consts::PI * 2.; 38 | 39 | let start_x = -300.; 40 | let end_x = 300.; 41 | 42 | let spacing_y = 100.; 43 | let offset_y = -(spacing_y * 3.) / 2.; 44 | 45 | // Everything in the same entity 46 | let y = 0. * spacing_y + offset_y; 47 | commands.spawn(( 48 | sprite(start_x, y), 49 | AnimationTarget, 50 | TimeRunner::new(Duration::from_secs(5)), 51 | TimeSpan::try_from(..Duration::from_secs(5)).unwrap(), 52 | EaseKind::QuadraticInOut, 53 | ComponentTween::new_target( 54 | TargetComponent::marker(), 55 | Translation { 56 | start: Vec3::new(start_x, y, 0.), 57 | end: Vec3::new(end_x, y, 0.), 58 | }, 59 | ), 60 | ComponentTween::new_target( 61 | TargetComponent::marker(), 62 | AngleZ { 63 | start: angle_start, 64 | end: angle_end, 65 | }, 66 | ), 67 | )); 68 | // equivalent to 69 | // 70 | // let target = TargetComponent::marker(); 71 | // commands.spawn((sprite(...), AnimationTarget)) 72 | // .animation() 73 | // .insert_tween_here( 74 | // Duration::from_secs(5), 75 | // EaseKind::QuadraticOut, 76 | // ( 77 | // target.with(translation(...)), 78 | // target.with(angle_z(...)), 79 | // ), 80 | // ); 81 | 82 | // Sprite and time runner as parent, tweens as children. 83 | let y = 1. * spacing_y + offset_y; 84 | commands 85 | .spawn(( 86 | sprite(start_x, y), 87 | AnimationTarget, 88 | TimeRunner::new(Duration::from_secs(5)), 89 | )) 90 | .with_children(|c| { 91 | c.spawn(( 92 | TimeSpan::try_from(..Duration::from_secs(5)).unwrap(), 93 | EaseKind::QuadraticInOut, 94 | ComponentTween::new_target( 95 | TargetComponent::marker(), 96 | Translation { 97 | start: Vec3::new(start_x, y, 0.), 98 | end: Vec3::new(end_x, y, 0.), 99 | }, 100 | ), 101 | ComponentTween::new_target( 102 | TargetComponent::marker(), 103 | AngleZ { 104 | start: angle_start, 105 | end: angle_end, 106 | }, 107 | ), 108 | )); 109 | }); 110 | // equivalent to 111 | // 112 | // let target = TargetComponent::marker(); 113 | // commands.spawn((sprite(...), AnimationTarget)) 114 | // .animation() 115 | // .insert(tween( 116 | // Duration::from_secs(5), 117 | // EaseKind::QuadraticOut, 118 | // ( 119 | // target.with(translation(...)), 120 | // target.with(angle_z(...)), 121 | // ), 122 | // )); 123 | 124 | // Only Sprite as parent, time runner and tweens as children. 125 | let y = 2. * spacing_y + offset_y; 126 | commands 127 | .spawn((sprite(start_x, y), AnimationTarget)) 128 | .with_children(|c| { 129 | c.spawn(( 130 | TimeRunner::new(Duration::from_secs(5)), 131 | TimeSpan::try_from(..Duration::from_secs(5)).unwrap(), 132 | EaseKind::QuadraticInOut, 133 | ComponentTween::new_target( 134 | TargetComponent::marker(), 135 | Translation { 136 | start: Vec3::new(start_x, y, 0.), 137 | end: Vec3::new(end_x, y, 0.), 138 | }, 139 | ), 140 | ComponentTween::new_target( 141 | TargetComponent::marker(), 142 | AngleZ { 143 | start: angle_start, 144 | end: angle_end, 145 | }, 146 | ), 147 | )); 148 | }); 149 | // equivalent to 150 | // 151 | // let target = TargetComponent::marker(); 152 | // commands.spawn((sprite(...), AnimationTarget)) 153 | // .with_children(|c| { 154 | // c.animation().insert_tween_here( 155 | // Duration::from_secs(5), 156 | // EaseKind::QuadraticOut, 157 | // ( 158 | // target.with(translation(...)), 159 | // target.with(angle_z(...)) 160 | // ), 161 | // ); 162 | // }); 163 | 164 | // Only Sprite as parent, tweens as children of a time runner. 165 | let y = 3. * spacing_y + offset_y; 166 | commands 167 | .spawn((sprite(start_x, y), AnimationTarget)) 168 | .with_children(|c| { 169 | c.spawn(TimeRunner::new(Duration::from_secs(5))) 170 | .with_children(|c| { 171 | c.spawn(( 172 | TimeSpan::try_from(..Duration::from_secs(5)).unwrap(), 173 | EaseKind::QuadraticInOut, 174 | ComponentTween::new_target( 175 | TargetComponent::marker(), 176 | Translation { 177 | start: Vec3::new(start_x, y, 0.), 178 | end: Vec3::new(end_x, y, 0.), 179 | }, 180 | ), 181 | ComponentTween::new_target( 182 | TargetComponent::marker(), 183 | AngleZ { 184 | start: angle_start, 185 | end: angle_end, 186 | }, 187 | ), 188 | )); 189 | }); 190 | }); 191 | // equivalent to 192 | // 193 | // let target = TargetComponent::marker(); 194 | // commands.spawn((sprite(...), AnimationTarget)) 195 | // .with_children(|c| { 196 | // c.animation().insert(tween( 197 | // Duration::from_secs(5), 198 | // EaseKind::QuadraticOut, 199 | // ( 200 | // target.with(translation(...)), 201 | // target.with(angle_z(...)) 202 | // ), 203 | // )); 204 | // }); 205 | 206 | // or with this completely detached 207 | let y = 4. * spacing_y + offset_y; 208 | 209 | let sprite = commands.spawn(sprite(start_x, y)).id(); 210 | 211 | commands 212 | .spawn(TimeRunner::new(Duration::from_secs(5))) 213 | .with_children(|c| { 214 | c.spawn(( 215 | TimeSpan::try_from(..Duration::from_secs(5)).unwrap(), 216 | EaseKind::QuadraticInOut, 217 | ComponentTween::new_target( 218 | sprite, 219 | Translation { 220 | start: Vec3::new(start_x, y, 0.), 221 | end: Vec3::new(end_x, y, 0.), 222 | }, 223 | ), 224 | ComponentTween::new_target( 225 | sprite, 226 | AngleZ { 227 | start: angle_start, 228 | end: angle_end, 229 | }, 230 | ), 231 | )); 232 | }); 233 | // equivalent to 234 | // 235 | // let target = TargetComponent::entity(sprite); 236 | // commands.animate().insert(tween( 237 | // Duration::from_secs(5), 238 | // EaseKind::QuadraticOut, 239 | // ( 240 | // target.with(translation(...)), 241 | // target.with(angle_z(...)) 242 | // ), 243 | // )); 244 | } 245 | -------------------------------------------------------------------------------- /examples/interpolator.rs: -------------------------------------------------------------------------------- 1 | use bevy::{prelude::*, time::Stopwatch}; 2 | use bevy_tween::{ 3 | combinator::{parallel, tween}, 4 | prelude::*, 5 | }; 6 | 7 | #[derive(Component)] 8 | pub struct Circle { 9 | radius: f32, 10 | hue: f32, 11 | spikiness: f32, 12 | } 13 | 14 | mod interpolate { 15 | use super::Circle; 16 | use bevy::prelude::*; 17 | use bevy_tween::{ 18 | component_dyn_tween_system, component_tween_system, prelude::*, 19 | }; 20 | 21 | pub fn interpolators_plugin(app: &mut App) { 22 | app.add_tween_systems(( 23 | component_dyn_tween_system::(), 24 | component_tween_system::(), 25 | component_tween_system::(), 26 | )); 27 | } 28 | 29 | pub struct CircleRadius { 30 | start: f32, 31 | end: f32, 32 | } 33 | 34 | impl Interpolator for CircleRadius { 35 | type Item = Circle; 36 | 37 | fn interpolate(&self, item: &mut Self::Item, value: f32) { 38 | item.radius = self.start.lerp(self.end, value); 39 | } 40 | } 41 | 42 | pub fn circle_radius(start: f32, end: f32) -> CircleRadius { 43 | CircleRadius { start, end } 44 | } 45 | 46 | pub struct CircleHue { 47 | start: f32, 48 | end: f32, 49 | } 50 | 51 | impl Interpolator for CircleHue { 52 | type Item = Circle; 53 | 54 | fn interpolate(&self, item: &mut Self::Item, value: f32) { 55 | item.hue = self.start.lerp(self.end, value); 56 | } 57 | } 58 | 59 | pub fn circle_hue(start: f32, end: f32) -> CircleHue { 60 | CircleHue { start, end } 61 | } 62 | } 63 | use interpolate::{circle_hue, circle_radius}; 64 | 65 | fn main() { 66 | App::new() 67 | .add_plugins(( 68 | MinimalPlugins, 69 | DefaultTweenPlugins, 70 | interpolate::interpolators_plugin, 71 | )) 72 | .add_systems(Startup, setup) 73 | .add_systems(Update, what_happen) 74 | .run(); 75 | } 76 | 77 | fn setup(mut commands: Commands) { 78 | let circle_id = commands 79 | .spawn(Circle { 80 | radius: 1., 81 | hue: 0., 82 | spikiness: 2., 83 | }) 84 | .id(); 85 | 86 | let circle = circle_id.into_target(); 87 | commands.animation().insert(parallel(( 88 | tween( 89 | Duration::from_secs(2), 90 | EaseKind::Linear, 91 | circle.with(circle_hue(0., 10.)), 92 | ), 93 | tween( 94 | Duration::from_secs(2), 95 | EaseKind::Linear, 96 | circle.with(circle_radius(1., 50.)), 97 | ), 98 | tween( 99 | Duration::from_secs(2), 100 | EaseKind::Linear, 101 | // Requires [`component_dyn_tween_system`] 102 | circle.with_closure(|circle: &mut Circle, value| { 103 | circle.spikiness = (2.).lerp(4., value); 104 | }), 105 | ), 106 | ))); 107 | } 108 | 109 | fn what_happen( 110 | time: Res