From c015af8bee853be30871dfccb3f88395d9a00918 Mon Sep 17 00:00:00 2001 From: Jochum van der Ploeg Date: Sun, 5 Sep 2021 23:44:29 +0200 Subject: [PATCH] Added flame_oxygen (#823) * Added flame_oxygen * Reworked structure * Added components and example * Added particle support * Updated code * Update example * Fixed mistake * Update * Updated documentation * Added system documentation * Fixed line length * Added most components docs * Docs done? * Update doc/oxygen.md Co-authored-by: Erick * Added GameRef * Fixed GameRef * Reworked library to not use part system * Added GameRef docs * Removed library line * Updated CHANGELOG * Added License * Fixed linting problem * Update packages/flame_oxygen/example/lib/main.dart Co-authored-by: Lukas Klingsbo * Update after review from spydon * Added flipping * Fixed CI/CD * Fix * Update * Update doc/oxygen.md Co-authored-by: Renan <6718144+renancaraujo@users.noreply.github.com> Co-authored-by: Erick Co-authored-by: Lukas Klingsbo Co-authored-by: Renan <6718144+renancaraujo@users.noreply.github.com> --- doc/oxygen.md | 389 ++++++++++++++++++ doc/summary.md | 1 + packages/flame_oxygen/.gitignore | 74 ++++ packages/flame_oxygen/.metadata | 10 + packages/flame_oxygen/CHANGELOG.md | 2 + packages/flame_oxygen/LICENSE | 22 + packages/flame_oxygen/README.md | 14 + packages/flame_oxygen/analysis_options.yaml | 148 +++++++ packages/flame_oxygen/example/.gitignore | 46 +++ packages/flame_oxygen/example/.metadata | 10 + packages/flame_oxygen/example/README.md | 3 + .../example/assets/images/chopper.png | Bin 0 -> 1701 bytes .../example/assets/images/pizza.png | Bin 0 -> 37161 bytes .../lib/component/timer_component.dart | 33 ++ .../lib/component/velocity_component.dart | 15 + packages/flame_oxygen/example/lib/main.dart | 51 +++ .../example/lib/system/debug_system.dart | 63 +++ .../example/lib/system/kawabunga_system.dart | 46 +++ .../example/lib/system/move_system.dart | 56 +++ .../example/lib/system/sprite_system.dart | 15 + packages/flame_oxygen/example/pubspec.yaml | 28 ++ packages/flame_oxygen/example/web/favicon.png | Bin 0 -> 917 bytes .../example/web/icons/Icon-192.png | Bin 0 -> 5292 bytes .../example/web/icons/Icon-512.png | Bin 0 -> 8252 bytes packages/flame_oxygen/example/web/index.html | 98 +++++ .../flame_oxygen/example/web/manifest.json | 23 ++ packages/flame_oxygen/lib/flame_oxygen.dart | 7 + packages/flame_oxygen/lib/src/component.dart | 8 + .../lib/src/component/anchor_component.dart | 14 + .../lib/src/component/angle_component.dart | 15 + .../lib/src/component/flip_component.dart | 30 ++ .../lib/src/component/particle_component.dart | 17 + .../lib/src/component/position_component.dart | 23 ++ .../lib/src/component/size_component.dart | 21 + .../lib/src/component/sprite_component.dart | 33 ++ .../lib/src/component/text_component.dart | 31 ++ .../lib/src/flame_system_manager.dart | 12 + .../flame_oxygen/lib/src/flame_world.dart | 33 ++ .../flame_oxygen/lib/src/oxygen_game.dart | 70 ++++ packages/flame_oxygen/lib/src/system.dart | 5 + .../lib/src/system/base_system.dart | 71 ++++ .../flame_oxygen/lib/src/system/game_ref.dart | 13 + .../lib/src/system/particle_system.dart | 36 ++ .../lib/src/system/render_system.dart | 19 + .../lib/src/system/update_system.dart | 19 + packages/flame_oxygen/pubspec.yaml | 22 + 46 files changed, 1646 insertions(+) create mode 100644 doc/oxygen.md create mode 100644 packages/flame_oxygen/.gitignore create mode 100644 packages/flame_oxygen/.metadata create mode 100644 packages/flame_oxygen/CHANGELOG.md create mode 100644 packages/flame_oxygen/LICENSE create mode 100644 packages/flame_oxygen/README.md create mode 100644 packages/flame_oxygen/analysis_options.yaml create mode 100644 packages/flame_oxygen/example/.gitignore create mode 100644 packages/flame_oxygen/example/.metadata create mode 100644 packages/flame_oxygen/example/README.md create mode 100644 packages/flame_oxygen/example/assets/images/chopper.png create mode 100644 packages/flame_oxygen/example/assets/images/pizza.png create mode 100644 packages/flame_oxygen/example/lib/component/timer_component.dart create mode 100644 packages/flame_oxygen/example/lib/component/velocity_component.dart create mode 100644 packages/flame_oxygen/example/lib/main.dart create mode 100644 packages/flame_oxygen/example/lib/system/debug_system.dart create mode 100644 packages/flame_oxygen/example/lib/system/kawabunga_system.dart create mode 100644 packages/flame_oxygen/example/lib/system/move_system.dart create mode 100644 packages/flame_oxygen/example/lib/system/sprite_system.dart create mode 100644 packages/flame_oxygen/example/pubspec.yaml create mode 100644 packages/flame_oxygen/example/web/favicon.png create mode 100644 packages/flame_oxygen/example/web/icons/Icon-192.png create mode 100644 packages/flame_oxygen/example/web/icons/Icon-512.png create mode 100644 packages/flame_oxygen/example/web/index.html create mode 100644 packages/flame_oxygen/example/web/manifest.json create mode 100644 packages/flame_oxygen/lib/flame_oxygen.dart create mode 100644 packages/flame_oxygen/lib/src/component.dart create mode 100644 packages/flame_oxygen/lib/src/component/anchor_component.dart create mode 100644 packages/flame_oxygen/lib/src/component/angle_component.dart create mode 100644 packages/flame_oxygen/lib/src/component/flip_component.dart create mode 100644 packages/flame_oxygen/lib/src/component/particle_component.dart create mode 100644 packages/flame_oxygen/lib/src/component/position_component.dart create mode 100644 packages/flame_oxygen/lib/src/component/size_component.dart create mode 100644 packages/flame_oxygen/lib/src/component/sprite_component.dart create mode 100644 packages/flame_oxygen/lib/src/component/text_component.dart create mode 100644 packages/flame_oxygen/lib/src/flame_system_manager.dart create mode 100644 packages/flame_oxygen/lib/src/flame_world.dart create mode 100644 packages/flame_oxygen/lib/src/oxygen_game.dart create mode 100644 packages/flame_oxygen/lib/src/system.dart create mode 100644 packages/flame_oxygen/lib/src/system/base_system.dart create mode 100644 packages/flame_oxygen/lib/src/system/game_ref.dart create mode 100644 packages/flame_oxygen/lib/src/system/particle_system.dart create mode 100644 packages/flame_oxygen/lib/src/system/render_system.dart create mode 100644 packages/flame_oxygen/lib/src/system/update_system.dart create mode 100644 packages/flame_oxygen/pubspec.yaml diff --git a/doc/oxygen.md b/doc/oxygen.md new file mode 100644 index 000000000..c1c7e6f57 --- /dev/null +++ b/doc/oxygen.md @@ -0,0 +1,389 @@ +# Oxygen + +We (the Flame organization) built an ECS(Entity Component System) named Oxygen. + +If you want to use Oxygen specifically for Flame as a replacement for the +FCS(Flame Component System) you should use our bridge library +[flame_oxygen](https://github.com/flame-engine/flame/tree/main/packages/flame_oxygen) and if you +just want to use it in a Dart project you can use the +[oxygen](https://github.com/flame-engine/oxygen) library directly. + +If you are not familiar with Oxygen yet we recommend you read up on its +[documentation](https://github.com/flame-engine/oxygen/tree/main/doc). + +To use it in your game you just need to add `flame_oxygen` to your pubspec.yaml, as can be seen +in the +[Oxygen example](https://github.com/flame-engine/flame/tree/main/packages/flame_oxygen/example) +and in the pub.dev [installation instructions](https://pub.dev/packages/flame_oxygen). + +## OxygenGame (Game extension) + +If you are going to use Oxygen in your project it can be a good idea to use the Oxygen specific +extension of the `Game` class. + +It is called `OxygenGame` and it will give you full access to the Oxygen framework while also +having full access to the Flame game loop. + +Instead of using `onLoad`, as you are used to with Flame, `OxygenGame` comes with the `init` +method. This method is called in the `onLoad` but before the world initialization, allowing you +to register components and systems and do anything else that you normally would do in `onLoad`. + +A simple `OxygenGame` implementation example can be seen in the +[example folder](https://github.com/flame-engine/flame/tree/main/packages/flame_oxygen/example). + +The `OxygenGame` also comes with it's own `createEntity` method that automically adds certain +default components on the entity. This is especially helpfull when you are using the +[BaseSystem](#BaseSystem) as your base. + +## Systems + +Systems define the logic of your game. In FCS you normally would add your logic inside a component +with Oxygen we use systems for that. Oxygen itself is completly platform agnostic, meaning it has +no render loop. It only knows `execute`, which is a method equal to the `update` method in Flame. + +On each `execute` Oxygen automatically calls all the systems that were registered in order. But in +Flame we can have different logic for different loops (render/update). So in `flame_oxygen` we +introduced the `RenderSystem` and `UpdateSystem` mixin. These mixins allow you to add the `render` +method and the `update` method respectivally to your custom system. For more information see the +[RenderSystem](#RenderSystem) and [UpdateSystem](#UpdateSystem) section. + +If you are coming from FCS you might expect certain default functionality that you normally got +from the `PositionComponent`. As mentioned before components do not contain any kind of logic, but +to give you the same default functionality we also created a class called `BaseSystem`. This system +acts almost identical to the prerender logic from the `PositionComponent` in FCS. You only have +to subclass it to your own system. For more information see the +[BaseSystem](#BaseSystem) section. + +Systems can be registered to the world using the `world.registerSystem` method on +[OxygenGame](#OxygenGame). + +### mixin GameRef + +The `GameRef` mixin allows a system to become aware of the `OxygenGame` instance its attached to. This +allows easy access to the methods on the game class. + +```dart +class YourSystem extends System with GameRef { + @override + void init() { + // Access to game using the .game propery + } + + // ... +} +``` + +### mixin RenderSystem + +The `RenderSystem` mixin allows a system to be registered for the render loop. +By adding a `render` method to the system you get full access to the canvas as +you normally would in Flame. + +```dart +class SimpleRenderSystem extends System with RenderSystem { + Query? _query; + + @override + void init() { + _query = createQuery([/* Your filters */]); + } + + void render(Canvas canvas) { + for (final entity in _query?.entities ?? []) { + // Render entity based on components + } + } +} +``` + +### mixin UpdateSystem + +The `MixinSystem` mixin allows a system to be registered for the update loop. +By adding a `update` method to the system you get full access to the delta time as you +normally would in Flame. + +```dart +class SimpleUpdateSystem extends System with UpdateSystem { + Query? _query; + + @override + void init() { + _query = createQuery([/* Your filters */]); + } + + void update(double dt) { + for (final entity in _query?.entities ?? []) { + // Update components values + } + } +} +``` + +### BaseSystem + +The `BaseSystem` is an abstract class whoms logic can be compared to the `PositionComponent` +from FCS. The `BaseSystem` automatically filters all entities that have the `PositionComponent` +and `SizeComponent` from `flame_oxygen`. On top of that you can add your own filters by defining +a getter called `filters`. These filters are then used to filter down the entities you are +interested in. + +The `BaseSystem` is also fully aware of the game instance. You can access the game instance by using +the `game` property. This also gives you access to the `createEntity` helper method on `OxygenGame`. + +On each render loop the `BaseSystem` will prepare your canvas the same way the `PositionComponent` +from FCS would (translating, rotating and setting the anchor. After that it will call the +`renderEntity` method so you can add your own render logic for that entity on a prepared canvas. + +The following components will be checked by `BaseSystem` for the prepartion of the +canvas: +- `PositionComponent` (required) +- `SizeComponent` (required) +- `AnchorComponent` (optional, defaults to `Anchor.topLeft`) +- `AngleComponent` (optional, defaults to `0`) + +```dart +class SimpleBaseSystem extends BaseSystem { + @override + List> get filters => []; + + @override + void renderEntity(Canvas canvas, Entity entity) { + // The canvas is translated, rotated and fully prepared for rendering. + } +} +``` + +### ParticleSystem + +The `ParticleSystem` is a simple system that brings the Particle API from Flame to Oxygen. This +allows you to use the [ParticleComponent](#ParticleComponent) without having to worry about how +it will render or when to update it. As most of that logic is already contained in the Particle +itself. + +Simply register the `ParticleSystem` and the `ParticleComponent` to your world like so: + +```dart +world.registerSystem(ParticleSystem()); + +world.registerComponent(() => ParticleComponent); +``` + +You can now create a new entity with a `ParticleComponent`. For more info about that see the +[ParticleComponent](#ParticleComponent) section. + +## Components + +Components in Oxygen are different than the ones from FCS mainly because instead of containing +logic they only contain data. This data is then used in systems which in turn define the logic. + +To accomdate people who are switching from FCS to Oxygen we implemented a few components to help +you get started. Some of these components are based on the multiple functionalities that the +`PositionComponent` from FCS has. Others are just easy wrappers around certain Flame API +functionality, they are often accompanied by predefined systems that you can use. + +Components can be registered to the world using the `world.registerComponent` method on +[OxygenGame](#OxygenGame). + +### PositionComponent + +The `PositionComponent` is as its name implies is a component that describe the position of an +entity. And it is registered to the world by default. + +Creating a positioned entity using OxygenGame: + +```dart +game.createEntity( + position: Vector2(100, 100), + size: // ... +); +``` + +Creating a positioned entity using the World: + +```dart +world.createEntity() + ..add(Vector2(100, 100)); +``` + +### SizeComponent + +The `SizeComponent` is as its name implies is a component that describe the size of an entity. +And it is registered to the world by default. + +Creating a sized entity using OxygenGame: + +```dart +game.createEntity( + position: // ... + size: Vector2(50, 50), +); +``` + +Creating a sized entity using the World: + +```dart +world.createEntity() + ..add(Vector2(50, 50)); +``` + +### AnchorComponent + +The `AnchorComponent` is as its name implies is a component that describe the anchor position of an +entity. And it is registered to the world by default. + +This component is especially useful when you are using the [BaseSystem](#BaseSystem). But can also +be used for your own anchoring logic. + +Creating an anchored entity using OxygenGame: + +```dart +game.createEntity( + position: // ... + size: // ... + anchor: Anchor.center, +); +``` + +Creating an anchored entity using the World: + +```dart +world.createEntity() + ..add(Anchor.center); +``` + +### AngleComponent + +The `AngleComponent` is as its name implies is a component that describe the angle of an entity. +And it is registered to the world by default. The angle is in radians. + +This component is especially useful when you are using the [BaseSystem](#BaseSystem). But can also +be used for your own angle logic. + +Creating an angled entity using OxygenGame: + +```dart +game.createEntity( + position: // ... + size: // ... + angle: 1.570796, +); +``` + +Creating an angled entity using the World: + +```dart +world.createEntity() + ..add(1.570796); +``` + +### FlipComponent + +The `FlipComponent` can be used to flip your rendering on either the X or Y axis. It is registered +to the world by default. + +This component is especially useful when you are using the [BaseSystem](#BaseSystem). But can also +be used for your own flipping logic. + +Creating an entity that is flipped on it's X axis using OxygenGame: + +```dart +game.createEntity( + position: // ... + size: // ... + flipX: true +); +``` + +Creating an entity that is flipped on it's X axis using the World: + +```dart +world.createEntity() + ..add(FlipInit(flipX: true)); +``` + +### SpriteComponent + +The `SpriteComponent` is as its name implies is a component that describe the sprite of an entity. +And it is registered to the world by default. + +This allows you to assigning a Sprite to an Entity. + +Creating an entity with a sprite using OxygenGame: + +```dart +game.createEntity( + position: // ... + size: // ... +)..add( + SpriteInit(await game.loadSprite('pizza.png')), +); +``` + +Creating an entity with a sprite using World: + +```dart +world.createEntity() + ..add( + SpriteInit(await game.loadSprite('pizza.png')), + ); +``` + +### TextComponent + +The `TextComponent` is as its name implies is a component that adds a text component to an entity. +And it is registered to the world by default. + +This allows you to add text to your entity, combined with the `PositionComponent` you can use it +as a text entity. + +Creating an entity with text using OxygenGame: + +```dart +game.createEntity( + position: // ... + size: // ... +)..add( + TextInit( + 'Your text', + config: const TextPaintConfig(), + ), +); +``` + +Creating an entity with text using World: + +```dart +world.createEntity() + ..add( + TextInit( + 'Your text', + config: const TextPaintConfig(), + ), + ); +``` + +### ParticleComponent + +The `ParticleComponent` wraps a `Particle` from Flame. Combined with the +[ParticleSystem](#ParticleSystem) you can easily add particles to your game without having to +worry about how to render a particle or when/how to update one. + +Creating an entity with a particle using OxygenGame: + +```dart +game.createEntity( + position: // ... + size: // ... +)..add( + // Your Particle. +); +``` + +Creating an entity with a particle using World: + +```dart +world.createEntity() + ..add( + // Your Particle. + ); +``` diff --git a/doc/summary.md b/doc/summary.md index 6256bbb12..77f0999e9 100644 --- a/doc/summary.md +++ b/doc/summary.md @@ -29,6 +29,7 @@ - [Util](util.md) - [Widgets](widgets.md) - [Forge2D](forge2d.md) + - [Oxygen](oxygen.md) - [Tiled](tiled.md) - [Debugging](debug.md) - [Splash screen](splash_screen.md) diff --git a/packages/flame_oxygen/.gitignore b/packages/flame_oxygen/.gitignore new file mode 100644 index 000000000..1985397a2 --- /dev/null +++ b/packages/flame_oxygen/.gitignore @@ -0,0 +1,74 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.packages +.pub-cache/ +.pub/ +build/ + +# Android related +**/android/**/gradle-wrapper.jar +**/android/.gradle +**/android/captures/ +**/android/gradlew +**/android/gradlew.bat +**/android/local.properties +**/android/**/GeneratedPluginRegistrant.java + +# iOS/XCode related +**/ios/**/*.mode1v3 +**/ios/**/*.mode2v3 +**/ios/**/*.moved-aside +**/ios/**/*.pbxuser +**/ios/**/*.perspectivev3 +**/ios/**/*sync/ +**/ios/**/.sconsign.dblite +**/ios/**/.tags* +**/ios/**/.vagrant/ +**/ios/**/DerivedData/ +**/ios/**/Icon? +**/ios/**/Pods/ +**/ios/**/.symlinks/ +**/ios/**/profile +**/ios/**/xcuserdata +**/ios/.generated/ +**/ios/Flutter/App.framework +**/ios/Flutter/Flutter.framework +**/ios/Flutter/Flutter.podspec +**/ios/Flutter/Generated.xcconfig +**/ios/Flutter/app.flx +**/ios/Flutter/app.zip +**/ios/Flutter/flutter_assets/ +**/ios/Flutter/flutter_export_environment.sh +**/ios/ServiceDefinitions.json +**/ios/Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!**/ios/**/default.mode1v3 +!**/ios/**/default.mode2v3 +!**/ios/**/default.pbxuser +!**/ios/**/default.perspectivev3 diff --git a/packages/flame_oxygen/.metadata b/packages/flame_oxygen/.metadata new file mode 100644 index 000000000..12f785186 --- /dev/null +++ b/packages/flame_oxygen/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: c5a4b4029c0798f37c4a39b479d7cb75daa7b05c + channel: stable + +project_type: package diff --git a/packages/flame_oxygen/CHANGELOG.md b/packages/flame_oxygen/CHANGELOG.md new file mode 100644 index 000000000..709ae2967 --- /dev/null +++ b/packages/flame_oxygen/CHANGELOG.md @@ -0,0 +1,2 @@ +## [Next] + - Initial release of `flame_oxygen` \ No newline at end of file diff --git a/packages/flame_oxygen/LICENSE b/packages/flame_oxygen/LICENSE new file mode 100644 index 000000000..3897c4d09 --- /dev/null +++ b/packages/flame_oxygen/LICENSE @@ -0,0 +1,22 @@ +MIT License + +Copyright (c) 2021 Blue Fire + +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. + diff --git a/packages/flame_oxygen/README.md b/packages/flame_oxygen/README.md new file mode 100644 index 000000000..f86d08609 --- /dev/null +++ b/packages/flame_oxygen/README.md @@ -0,0 +1,14 @@ +

+ Oxygen +

+

+ Flame Oxygen - The bridge between Flame and Oxygen +

+ +

+ + + +

+ +This library acts as a bridge between [Oxygen](https://github.com/flame-engine/oxygen) (an ECS written by the Flame Team) and the Flame game engine. diff --git a/packages/flame_oxygen/analysis_options.yaml b/packages/flame_oxygen/analysis_options.yaml new file mode 100644 index 000000000..bf5f8795a --- /dev/null +++ b/packages/flame_oxygen/analysis_options.yaml @@ -0,0 +1,148 @@ +# Source of linter options: +# http://dart-lang.github.io/linter/lints/options/options.html + +analyzer: + strong-mode: + implicit-casts: false + implicit-dynamic: false + + plugins: + - dart_code_metrics + +linter: + rules: + - always_declare_return_types + - always_put_control_body_on_new_line + - always_require_non_null_named_parameters + - annotate_overrides + - avoid_double_and_int_checks + - avoid_dynamic_calls + - avoid_empty_else + - avoid_equals_and_hash_code_on_mutable_classes + - avoid_escaping_inner_quotes + - avoid_field_initializers_in_const_classes + - avoid_init_to_null + - avoid_js_rounded_ints + - avoid_null_checks_in_equality_operators + - avoid_private_typedef_functions + - avoid_redundant_argument_values + - avoid_relative_lib_imports + - avoid_return_types_on_setters + - avoid_shadowing_type_parameters + - avoid_slow_async_io + - avoid_type_to_string + - avoid_types_as_parameter_names + - avoid_unused_constructor_parameters + - await_only_futures + - camel_case_extensions + - camel_case_types + - cancel_subscriptions + - cast_nullable_to_non_nullable + - close_sinks + - comment_references + - constant_identifier_names + - control_flow_in_finally + - curly_braces_in_flow_control_structures + - directives_ordering + - do_not_use_environment + - empty_catches + - empty_constructor_bodies + - empty_statements + - exhaustive_cases + - file_names + - hash_and_equals + - implementation_imports + - invariant_booleans + - iterable_contains_unrelated_type + - join_return_with_assignment + - library_names + - library_prefixes + - list_remove_unrelated_type + - literal_only_boolean_expressions + - missing_whitespace_between_adjacent_strings + - no_adjacent_strings_in_list + - no_duplicate_case_values + - no_runtimeType_toString + - omit_local_variable_types + - package_api_docs + - package_names + - package_prefixed_library_names + - parameter_assignments + - prefer_adjacent_string_concatenation + - prefer_asserts_in_initializer_lists + - prefer_collection_literals + - prefer_conditional_assignment + - prefer_const_constructors + - prefer_const_constructors_in_immutables + - prefer_const_declarations + - prefer_const_literals_to_create_immutables + - prefer_contains + - prefer_equal_for_default_values + - prefer_final_fields + - prefer_final_in_for_each + - prefer_final_locals + - prefer_for_elements_to_map_fromIterable + - prefer_foreach + - prefer_function_declarations_over_variables + - prefer_generic_function_type_aliases + - prefer_if_elements_to_conditional_expressions + - prefer_if_null_operators + - prefer_initializing_formals + - prefer_inlined_adds + - prefer_interpolation_to_compose_strings + - prefer_is_empty + - prefer_is_not_empty + - prefer_is_not_operator + - prefer_iterable_whereType + - prefer_mixin + - prefer_null_aware_operators + - prefer_single_quotes + - prefer_spread_collections + - prefer_relative_imports + - prefer_typing_uninitialized_variables + - prefer_void_to_null + - provide_deprecation_message + - recursive_getters + - slash_for_doc_comments + - sort_unnamed_constructors_first + - test_types_in_equals + - throw_in_finally + - type_annotate_public_apis + - type_init_formals + - unnecessary_await_in_return + - unnecessary_brace_in_string_interps + - unnecessary_const + - unnecessary_getters_setters + - unnecessary_lambdas + - unnecessary_new + - unnecessary_null_aware_assignments + - unnecessary_null_in_if_null_operators + - unnecessary_overrides + - unnecessary_parenthesis + - unnecessary_raw_strings + - unnecessary_statements + - unnecessary_string_escapes + - unnecessary_string_interpolations + - unnecessary_this + - use_full_hex_values_for_flutter_colors + - use_function_type_syntax_for_parameters + - use_is_even_rather_than_modulo + - use_rethrow_when_possible + - unrelated_type_equality_checks + - unsafe_html + - void_checks + +dart_code_metrics: + rules: + - prefer-trailing-comma + - prefer-trailing-comma-for-collection + - no-equal-then-else + - no-object-declaration + - potential-null-dereference + metrics-exclude: + - test/** + metrics: + number-of-parameters: 8 + number-of-methods: 32 + source-lines-of-code: 200 + cyclomatic-complexity: 36 diff --git a/packages/flame_oxygen/example/.gitignore b/packages/flame_oxygen/example/.gitignore new file mode 100644 index 000000000..0fa6b675c --- /dev/null +++ b/packages/flame_oxygen/example/.gitignore @@ -0,0 +1,46 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.packages +.pub-cache/ +.pub/ +/build/ + +# Web related +lib/generated_plugin_registrant.dart + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release diff --git a/packages/flame_oxygen/example/.metadata b/packages/flame_oxygen/example/.metadata new file mode 100644 index 000000000..54ec0a8dc --- /dev/null +++ b/packages/flame_oxygen/example/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: b22742018b3edf16c6cadd7b76d9db5e7f9064b5 + channel: stable + +project_type: app diff --git a/packages/flame_oxygen/example/README.md b/packages/flame_oxygen/example/README.md new file mode 100644 index 000000000..ceb4ac373 --- /dev/null +++ b/packages/flame_oxygen/example/README.md @@ -0,0 +1,3 @@ +# Oxygen Samples + +An example showcasing the bridge between [Flame](https://flame-engine.org) and [Oxygen](https://pub.dev/packages/oxygen). diff --git a/packages/flame_oxygen/example/assets/images/chopper.png b/packages/flame_oxygen/example/assets/images/chopper.png new file mode 100644 index 0000000000000000000000000000000000000000..7e7d3da5c06a1af3f74182a9551b350404ab2e27 GIT binary patch literal 1701 zcmV;W23q-vP)n1t``ZI~0X)YV?*N`-O(PCqGK6`uX?qH15+4CP#~SYdo?}fT4sia9&s*Jw@cQjy zQooM?o@33sCck|5^&4z$Z_-lAR z1WTBlnNIvWm&PF#Z0l(gwK%|HuO}CKz1$NorIc4MUvlO|aigKfng|ea?aJKH=WHRR zl)ZjGdY&gjUA=tCsrfHzKXvN2Rs-Cb{oSIlRR9s@uu5(pv0-TUj&W5W_t zA?+)J0ebzubv-s1ADgOVLq;5+$W073o#5rGr(&YhO?*EHmZHbx1gc(J`uTBr1mJ1V zsS_8X*InxbrOg}JPnQ1iu_?)F%u>|u0A=pranT8aUy+ ztRRFCD}w=61_My-LXNBMr+2{#)O3?aF(7U~Y5ISDvJ*8}s?2{aA(QgA-+p~RdTnoi zL+t`(i?I~K7kfSV{dZ4>wCajr;3zdxX-Ypo;X(+(=RXXWBj-)x+=m|k0PcPBt(E!K z8EmiLAF`h$lI*I?{MQx%xcEHKD#}IMTuDbp15hML>HsAn4l$)I4=aN~Wc+CbJOKRh z_dgA(8Ybx+=e3`-MYW9mSS^AH+r~q1CrIMiqsK!q6y`~GtlQYwik-0Dq z-O5izq)xw$C_v6ji$>bw!3*Fb=`?+BUQI@?1QY7XNlGtVFeKo}2~M9m3E<#lkP)e? zf0Me>A`PEkwFu_=U4X{H$HO~r1Zm!V{U$mWbz{T<)XCNvkBTeC4;<({edc5|@q7Cl z&KXe}VYnQdYZvqvNfWtFItQjwxg%qYq+FTQ;(bmQ2tG1sxs?kr*OxH4o!Vy5d46j>(sE50Yu$2*k_n?9J zit0in$RbUD7Ph)^-1v3+btw>Vq_=EZUb+a*d!V)%ze-v<)CNdDPZwNU{UjF?w~|13|`QqA0C{tSi@movjkOE5AD9PqH6%XRi9)Wi*wF z0DKU94+s6PhA_^@=f92~KmYM3>leQsKD;G>1MMHp$Mx&F3|#-#_GaZ9GO9xQ0SxI^ zQPT*OrqiE^PB&KAXXyv(x1YihKsQNrkt+z6qCo(_4_|d-f&t+7=U)~U*#xv*V*$%p zT>utVfz<_I{>Dz~YPq$&iHS~Enra;u;l)~f{{Q=BC-(l@?X$JC4y!;>KsA0KTR%|0 z{Zx(s0Adl~#ai+eUoM{Zq`SZ;pZ*&?CjF}mNAJ(y7(Njk+mS%09{}F_>@3!9AN?b2 z*8r;Vr|IXIu%FVamaYY=ZhD*ObhX_Q+c#vW(2eD^E0o5Nl`?Q_?q$;5kOU0h*iWnf zwJUS6?dX7;`jcvbI_;;{3t_tb+`J7+IommhevyD!84QMsG}rgkt57|lwx-FE59eRH zWc(aezq*~jcyJA2D&AAY_0K}s8(D+=_4m4{sl{Uk< zIC*8^zpPLMm**hshq7GO-V~L0gVHT+Zv)&Q1cN_M`g<2i+kr{> z*53WMsa8<4kFS5dk1k`(*=i82tSF5Hj}H$30FY#5Bvjx2n!UZ#VWHoC)3h^60RU9Y zKI+2)U|ZbRID@ZnVOvV4N?YKI_3IB?;P zclg2o<>mU}#Pg_57^Ug;L2_R@yl(Fx|MukI=x%bB1m^x%<9+S(k%&jYv&riX&edXO z)AN}~pQ5^(WTxZO9o93S@|bX+OdoRnLY-bF*NY#*U>6zd8eN^q6?6@|9AqT!ca+^W+nj_DzPDw0Br$sZc~JT6`eP7hM>w}5M|k2XBfl^Abg#a1ws9SbdMM%bHz_sjp~7Ue%HD*+QHzy=6;k)|KTvF2uBG(&WN15ISYt zT(mMZ<_C1HjHM}rU-`H%@Q!@#9ey2rHpY&&D$NkJvC!GA0=Ju+tdK&g(bToCx#7xc zS=MCkxn1xjG1zL8!gFt){*24DudVq}x5;6UVX2w(iXq4}+dtmgb)tpmXlFFWli9F- zVWR7_bM@TUZq>C;{8x_8%8?}@f@R*`a!e@3hJX97YxmjGREM6kTmIU0@5SOdL+_iG z#Z9l5WB0N~@0Fox4nO8d z%D3zn%+XppTXYtg*EZ7f=B2W-X*+&7D;0LRBguS}-w>;LHS+%2$BvWQ`5HYAReHW@ zc5ZZai=yKydKFFSho?$FSJ5yP(7mjeWNbZmCf zKI^BN$hfDlKDq?k999dhOO=Nlw0skO^b&H|y~*pJa%Hpbsg`eaTe3TtTP>B`DZm?> z({|5ksm@Rk>RZ9kq=)#~_k#cO5IU5Wvwqw=VW2;WMW%2t?Ut&p^R`>bz zJds=(jYqKlS|KJcH|QOM4d!S>r@jQ}<7a%~c~HU;3mbbeco_`T1VRcR9lu{#tHC(# zt_itYK2?s!o>pe%c1XHb14ls3dmN_rx^Y&-_gy~k_)KRCaFR8cEj3lL$ujXW&H_V( zL0af<&wxm4g-nl!Z4vi9hj!cKpWGh~+9<);iY=q5m5!t;z(owoZ&!87W~0af5zG(v zG_!Bg!lxZ#~dc{;*-2ffT$Yq8Vc3BJQWBj@z~9tZh~O0SDVvI_OOV z1PIX#TX;@0n7QR68`V4k`!R`C+$v5btPoCV@B3GxnPGbME>A+P&)W#Fo$p#6yXLmA zkibOoE+BDxm0@w7!Jt1SE*rV~;E#>KS$v!#i=6oBQZKlgDFh9ZSf&1=-vC~WRRrTWq9t5~*Q zd5KBnd<&XDNsmX@laJ^3XF@B?FPJ7}-CykrZuwIN(KzS`W3$=U6n&+y<$}{EVv}eX zdAbtUa8~Iik6sqO?-aQ-g2@METBGgnU7OZ!>EDUaE@7E<8fH}q-yd%v`U&30tQ5T; z60S+oeEYw)ZnKxi%Wdq`^*BtX z7=G_*DdU^$>Y0TlkgEJbHiAPD_Q|v;v&+qU*_jA7ifPPuy2IARRNo4Y#fAcxFqkeP zY`tkEr?ZT*o`k@`zdPw6ywp+(vPqZ(<>U%1@Fw zx8Rb*jZsu=+x$KS)`gp9n_YbO{i0^HvVNFrsVpMvO`K zEM2U~C=4Iixax=QQ97VG@_w>$2t}Yu zYRoGIefu`RpW|w~S4MeBE(WL!&*c-DvI6Q$JOiF|^kqgAWALC%HvyD|ATuIWgzvL_ z*C(HEjd3+rw_4w?lqgQ6s-ZJ&@TW?zm?@mn5UzcJz$-Pju13dRL+i2rc`PUF%D|C@Va0-P{lB5g(1&O9^5_hf?;>ozq?XKgg;oo6t zVX6{K>!B?RD?}c#dPQLw+R*5~5OYLT1k|I$b-+NiLf+6)bduWLU$jTZUOAu| zLg$g!w??E5sIr6rgeoiS=zStTR-ye=Z-UQ_+6j9N&UA!sXXl-h$QMF8ZS)zPil9~L zP-JeOKZ1`8Zw`%iBrceIX^LuD(URPcIH{YaOZqB0aRYNZNsQ2MA5zzvc29QwJH3>M zSI1CU@UHg8M=vtOAmU0DH$kc${PjqQz4V`=VstT4wCmVUKrd@{;7Gx2xFEKZE0jZ* z{X+ylL6lfS1(@hp4yH_vrdQSiMfm8dIUbBbC)I-B4lq0TV}zn?h-~P23m-wj3eW8h zkqo|zznsU#{|A|(@UcpaA|q7PHYN4Wdz zlVQIK1@+GzN7PZRO7aXh)SF38S(@#|dOS22uAQ=DMwH0-D-xJ3S={duPV9dC2wSuH z4kajaZa#4qN_drgM4z0At;e~T~=E$1<$C^0n{i^bQza+->N*cR5MhsBusSV*O& zS&HVSY#x%Dnpb>Z=Lh#5`6Nwor^1o3!CPll<;8%#Bhpg@1VMC=%`60u@*sj?G}j`_ zJwjD4wXI(JD28=37d%f8UUV5WQ6xzS69?-4;ETAM;>}OZ?S~b@YcL41vmx*fdUlSR z6UE(S`4KW!?BzP;f?M>il`=G5AG28oE`+acp$)nCWG0OODKjprXZCa`#}&mOF#}} zEX7fHJ^)Sz$LfKxKIN%X+ec2Fqun!eZDQn4>?_NTJeC6Xi)QeYPlu*=7C4dd>Qk2khq|ZhQG^ajSA1(pxMm7E!)esrq~Nt3K*adK6OWB^T#g11V2so$%Bbl}w1$mEx`~!g>NJoG z3zZ>|Y^!8@AbI!c(ToLeTWS0I0|@4U!o;8JuG1Efk5R8;l*6T^&?e15!(1rEl+!lH z2CK3Tkqqbx$xbI`g7RGe@tG~PyS*;(q!yUz$Qs&Nl0qJVq!nuONl}lX-#<(1pnz}F z`oT$qb#I5f}P4B*l^#WP!W`f12c(bzy2|?qt~)p*iSyY%sIcQy5eA6si}d4Xf-o@$&AQshRM`J#xT0_VHn78x$aKp`-_sfvHfLg*6gQt@{ zV1c+SKSCiW>&0hH0vHj9jY!tGG8Js&+M8U)7Z=;do9`0oWEn$Zd@F!@coAn5zV;j;b?;&1$= zC0@dYv|RI=5t7aKNCRt*Vd+UJu5-a2OB%4l)`d_TFp7cIIT28gi;`bs_&?=?!Qh%5 zFVAyLWLsCMMHT8%d@u$x-zpfK<-pyBA{VH_PsB#JT&ciU+KEhnBv+zQOfX(&Xk6cZvJDa$1*EF**5uV{>mQVV51*4uS12Xk<%~_?pMG@im*^u4YgZLW zaCNKg%UDb9-bt3&azyTiCUQxFm9+C5K=Y1`1x>Hq$#Udz?JMxwGo*XabUTQAqnJa1 za`Ao3O|o)hBbgWU>^U{=Pb~6I1vv&KYkYKQ)f8e_E4w%?k$|(989BTe;u6G&JM~mv z00rdpOiyVDhO~SQk)ztw8KG#@!;o*)BV3NwGbU{_N{Kf_!vP_UHlcCK%V4v9P2B_) zct+QnF+d6;oF6R(pQ%(26JZqpP)uMx!f0?0f{0@^Vx0!co+BBbN+Q)03yJGI{7V76 zXo-6BF#$gt44KI6j&)&`qk7-~tRL!8(45hnF1zlV2w~*N5JN7RKY(t*pTN`3wJfgi zdgCl*Zdc z1|~|>5~+FN>#$i7357D`Q?-=r*wn$FhR7`5#uu}qIjM!T(lm!Pt#}<(GjeV*Ia8ib zwWYHr5&8~rZa($8@M_1>G$;&JH2F^>L|jI`RRskdtmFgi^^^nVhtTh?B>0C zG4c?Lha=Fd>>Il2?6^3@X>b#?sz1#y045bqmV>UCh=)IZ41!zDEjOdDf67=r-(CKyjg~0Wu$BWbf|^cythR4N+^FbO9hfzBBgOb1o9MFkRpjP*tO612;cFU z8GQ{oRGfX%@3rJmA0DKm^pN2h;{`?XufFoBDj&B)n{{JSA08|Ku&l|>fA+5>eg*Vv zw5yrk*MMK>G97RawGIm4H};djAya}!X)AlC$k+GQx+P#cP7i)-)kN-ZYOIK8L(@18 zv?m=~ISV0&l#%o1Dx*TqR8vlehAN)B zL}ioDwC#IFTd8ZG0XoR(t{Z5^M7q8PX@?E-p-!RkE7@n0Zb@J^Jv=)&4L9$H$EoS~ zH9s}V|8fuok&2DRz)N?61*bsB999Yt+<554nZyS9X`X>*Cec(=^GqM82-F5 zQY#&WA8=sZoT=?A)wirj26WO0LpHKCwl_FQJ1a<^lKRB~yA`L9A8LC6Niy{qL2Bs4QRkp{FbOt|!)TMK%L&Nvl> z+Eg5cel92MqH2kfBGIZYRw#>~4dK0>w$_guzyh?j`bxyPQQ1lJx}|bl(>OUq{VHap zc1@2Yfq+URhsZWX1Er1*3<|3FC7RTzlH)S{0?v95Rj>FVpeZmoEYuEaPz-XGi8Y&l zZ%vdx#64RAfQ9E3?-NzEzE3C7s1sAZA)D@~DCLxJIbfcps>s!8-hDs|`>bDzP7(k% zZ5WN_15SJO zD@v)jb|mrP62Tq>M}`w46!CF_n`|oiWv}EKfP>4~5)NbZLD$fSZ^C8;?`N_dL?te+ z@f;o{;=v&;s{7{;EMNvy4dgcy^MIz3MyQ7z{{6BaF<$5&5BPtro<3AsZIsELQ7o#X zNjo#n6w>fADa4~W#qzYoM9hg`BPF#k0?Ke>oNQjN=eghrJE=xAPL4J$B7;{LSD5&| zuVsM;60k2KI6Ephv)pZZsHjUXOmLVH7{Yc>Y_h{UwL8W`g={fQ-?*qaT~04lS40yk z)3?1edBhbgmf=jPDzOv@!ee@Jbmdx4y_T39g270DArem3)4;~eF@Lbf|IF}>nnoW| zy>KDQ`Y1TXX`Ax4%TcgVT@ucHLO5;TWWO&Pp+@2i0$9oxpxy2znxD=%k zgEl#923$B)*DPe^9W1x8QT)^#Y-8LT$s#|nBQ5!qSU-5$=W0f@y1giF@0C)u63Ubk zj)~gBlc_`n1PwtTOCAGu>;8faj@3?JU3#KLC?dU0fbe{usfQj+ZzgbULY3 zJX#qB4(+ITG4qA$w2}%a&a=R1ZL-1()|G3!Gc6dj#WEOW8#hYK3I%zVIq|q9n<&g^ z9_=On^{bwWhtuRZC;VFk*~YpQyl^5a`}+w75JHSA+-TNN$LG_r_*PW@fvHwP3TBG| zN|fdj@P4soHdqOuDjAlh2r{t1*;pb0TXZnENhZ3ANJ`1|L#Y!4WRPEpk?hZiVu2CF z3T&(*w}je;t=NOYUP6S1D*Zt6e*YjQYuk=&j0-FL*h3wx%O7Dd90(gaGVmMxtZuU} zkm_GAS&D2$(VzL+dqgu5F}`7MIhe)H?S2y3DoBWko-FeCpdJhLERhcuA#N*NXj6QT z=d=aBA~#*s7lYrtT6rwgn|XobduIq5&MmiyA8W3Y;maE@TA~@Gttq+j`6z^=_>e$_ zwx=g`jm!y@KMB4A69f<5xKPJ05|OuBunc~5SI$YY=%xs?-4Km$(SK(Ul6V+SA$NVG zZHkU}5Ut@Z(mRF?V4@=c3l2`Dhsol-6d960xY)E{1z*gN%GlyM+k~=~>bjA*(yD>! zXq6T9%%DGBA|UE#+ZGu+oT zg5~UlhkZNt#|V}HX$~Z*A}I(x-rEU6YI7=F z=L6pQcN(YuZ=7zS?|az-lIFe(SXwPcqcTZ&UZ3F<$BT$H6A^(ed})%ZZ?lO2 zDf`vQafc?Zu^VD+WogaE|{ zK>yGn(AHP&F+eWkJeQfVut)=G&y=k+$iG{PJvJs<+xgM4B}AkcT1vMORZR&`Usn9u zY6jb%NmI!e7aWqBNaKQJ*PKMGT`PC8?pM3zx!(4dFX4UB015`Z>uU)&3a6;f-v z=$a-ie-IVbB8b`exsw=)^`s59bf67d;s*we7NrYaMsEv@Z(mcNLBdK>ss^7r@?7}E zSVL=>!5!L2(slN|D)>ZPnMLz^Vk`+VRc$ZQZ>-+soN$d0u|?9w>6B9~((GfVD>dCk z*eHgYb+Q;L^2wUIE}injoT3J^G)i4vn7GOQ{K*7Q=QKo(N~aVOVzcUsClt9hwn*-Q zN&2A`^tD)M+B0*SjP{gmMGD}0Uxj{H<63V!0gIZWdU+;I}<{^pfVkXvjt`v_@%eY)putY&O?3|N4BawPZaU%D6qz`}hpwCktvFf*$GG>;4v>HQ7wkxR;dIauAnLd&{1Z+L|g2lNqZWI*X*vl0?5aii5)m z14NrIy|K@}N#7XF=3yMw*HBZ{1QtKkFs}4>1%l>-h$v}f(mX@Mgs(99vGEU~c!zzx zKCYqVA=rlMh6jXF>AP5Ilq6Y&men+M>vh{tp_nvOWFJ$H7b^(#7j-5URiItAP9?et z)6wB|n&(xXksT`73rk@a(6_)trH!tfSc83CVG6EIV*lJF6jwDP1~9G=1A{9ZVsLlE zpV9D*>qE-Y?kQSsyk*r)4TM?h7h|lo2{avVj6kVB^=TxVD{jn?7=+z19u-R)Opr*) zgP?VKht^PR$#aOD((t?%YzxKPd+`b{Qm>-px(!|d4<5=_zR8Ox2Pg?*Bn_msuE6fA z_(lPHSQdGh=McfxC^ZKUpNoc73y>4XJnHbx5D{ z>u3Z`ewGb`!3115lT=V8nhX*g#DzxT6qgcYHc6930_=2A_%vQ7efDq%N#3z@#}BBI z`H)@Qx@dJ(NId#Iq>5NSR<+e?P)ZzBRW5Vt>r`+E$8VQk)+Uc~mBRv`$bNHZoE`&U z{xXTtgs=|QMeTrpTezj*+m_IZwmsoImhoxeo6R5|7C|DvkBO?D2R+ya%$!>l6~a*- zeEQ|tC@eLEMXQWNV#ou5$uZF(Ih0wbm^Wto~$2(ia5tejfg5JCCWXp0C z3sY$3fDSZNB1056mC_gpA~X*bEh`vCh1IwPkw$@K6&ivitvS(}SWy6}HSI+DRg6+D z_^-0L=;=0Ye4Z&xnFI^#a&*CRYNwlYHx51V=i_{XCOfgk9qP z!fqzmBL4Z#OF&bQcsYq(4Pg`F=!VM&1}zG8D&jhVBf((O!Un!LZK#aEb@ky@0*K#~ z_qrc4T_E2CEm~7HA;U_;{$!-9>s#JLBZUfhyW6%F7gv@Q7ypl=V{Zq>a{QA7Wd=nE zhmF)E88u+|qj=0Jc8o=zKB-hIhryR>TFnpa&M;feB$l-Ez}G^-2xC_xGfi~zwZn&O z!9=ALrxeAc-uIPEk&T3Y59sc?YxjkLZ9!N2*2*d}&p?hB^ZsbOYU$^DfX`Go+B@0t zxdZfQ9)Zt`f_{EIF+UhCTm$1BC~evLsnI9d*6X9VqsqA%bFYM)qp&E#?{mE73J}nR zT?{;a#kp>Pf-NIr)m`#ia2Eb2S5JM~32smvt?nmmrqn(|l+?bLxFmg*V? z*W7)eiS`gWIx1;#2$@_xYM}=Cutf50G4Tu7=X_wsyIL|_UTozE__3d%uLBnj-`0lO zvoT}A&zM`ESs~p{!EdVU14&jro*{n-Lul#F8r6ca=a*<@>)yj2u0u1`W@mN7=$`|g zl{(F2;&;nwcU}4)@%+`G54)hiZ5$kIf+6wRZOdai+KxU36=1gApPH$)Z0004DFDHo%5c)JoLvvPBD;$vp^^z>x%WM^`8v1DfD<>h5&VPj@vV|+s}x_UdffxH+UT*-e^ z{KX+*?rQ2{?c`?d=m7l92{LhXcM~Kddm9J-gZypORMP8B_zwfWum8}yx|uP{zFly< z&F2lk%*MjP!^pzM$i~b3cm20f1%-cVJGlNMif{2`_5wLEvof(T+uQ$(hO3*T$A8-U zFEw1%-;Pu=tD3tyy1ST~OL~|)xRL+esFR($>)&mYo91YkPAwH_-2(v2wAnadGi*@NjXnv9WP-|0hV(+{N{+$bWOPvM{l+ z{TVSc<&%121iclTwLQqvoY~32^3TBUwD7%6<}I+G-@Wn1{73#a7d~+pbC8>(i@Kwu zogmq72Y|mR|FjfP;2+Jx_s-GukK!M&x!Lc2`$xx#fh^y?Ed71O{NI89HzqYJM^A_U zZ#@4A{U?j4i<_sTi>;E2l8KGEsoQ_g^Iw7g$)x(W*15X5c+37DCiVZo3H(*AGH<$$ zF5Z8$uV(K2*VSJOlAZORPyvB|76Cqx>0jY@1$mg8{Yk-FcGl)D{~DP8D3t#f+~4&7QI!8D^}oaZ(iV4g@_uVtD>o%ihyT|6 ze*yf9;hnXqxr3|Yf9Lw&A%BJCZ~M-h&wt6@_OrL`k@=td<3Gyccjx?H{P{<@{a>{3 zM*VLi|D*W+J6!(`*Z(L2|0D3ftLxw4`X5E$e+2$_b^ZSqF8Kd^!Zvq!yV3J}dwy2; zvUYfT_J%f*mzDs${(cqol&8E&;GAT1Tmg_NTfg66QQ%09Z$cP1Sp`X$U3g?TT6}}- zfp!1@2#}Q!Rrgvs?eR=CUpCl$Tz(wNHr<=k$n7dGx9p_0NtMEn`$RK&4a5rz3Xfw2 zdIc1~a54X=XEhuN+J=K569@%l7W^=#h?|L(vT`{+BCP6kS?_drN)7G61 zkxW(5*J88oelFbN>3@Fr_;}_&AqZm#^-!7_a)qGw%d-!EpUem;#R}y{Z!nRh7kbyc zNI4oK8r}1?`lsIueqx5Ua3*s*{Lk~$(%kOBt~xO9Xe{}kT#W99@CqAgcLorb=<*1P zGH|t|H2Y_5LOXZQ)7^@~xKW(~ zf5@u`>`cu-i~c9m)?_W%FJ!v3=nI|pXQWJsE8d;22Ud$*xE=<&h9M*|k3b(uaPMKM zbj+Qw(bQ-MZ|*_vXY#or2R?!jhe=@K{%Rnr#<(HGCDF{0Z=4%b^#(6~?tr#$lG87; z0L((y_k|HY`RYY`f{SD){8(D1X`UPDcqOLOW@k--Ln|FjDy^|yEODMb5K7`7dBL7O z+amu$rtzLt%1`tROuarO=2}6EF-W<5eQ3&2z#B?q_<}0)+3S(nT#9Wm$S>RdHnYyM zJNo1T&a*F=rYk0uTY{hJ#8PS63hC%3aw(jHy=^0_ciUHK%@t|1P4DPamqc0ed$c8l z#gsZ8856~qeQG$(>@YE71X&Lk#B%NS3;aaKCXO7L(M2oGh z6n2*+2&!dDFTAgF9hLf$_bt1Vp?zOEB3+$vUmQ-6#?5|lPz%5K!X9mnlTd9`Cc#7K zMi(4BF3BXHDO7hE1iq-Pu3Fae?yj;fS^Z#&uwRVz@21C@wqlZ&T>D;kuvKUf%a?8KQo5*Cp1xw2Tp|)P9c^`SvFO; z?B#9JPn5o;68q$GQz$XP{HMLF9h$%^?kcnXP`|NXNzhuSgr{$|%%}7P3D{JCTL||L zwlqDI-tTV{Zuk_J=82+5xKA=9Qs1eT-K^ZAm+>E)$3KlYSPl<(nd)7jlqQCbevppdcBt zZ^P^0^WBBN-O5Zs1cbQ#=8wq#AYD5xT-@?ZHW#ZY&v@T&(`&~ z|4Vf+jl?USoar3y&1|rvH1CVM!V&iWM!NZ4bYkN=uR+i7ghw%!+?37LmxfRvqT>L! zms4!&bjH?wG(MqTO-~#~s4ZY}UHwj>2u4tK8Iio}KuJI5qvO1Z)1<*2^&S-8vk<>1 z37R)gDVs;Fi1unhTeqSxChVp-GScnFFGKW96ZeL%ZXIE_{!jBao-YpOD?Qjcfl9eg zDS8AW{Eq#^zDWSR?`GG_8s6jB<^%iAa~<`uRpWMI{kj-FG@llk2V054sN6T2 z_XC3oNoTiXWsEc7h!*CMmKIs3wLc$gB^NfiYi$VbjC`)GkNv|<%dPxhCJUE`kIxek zA7BSXHAX53(7RGaGebb-N;L}<97Ja5$t`3O)r$2!H?@6P`EGAzLbBfVFn5uf{4K__ zrmopJYWe=2*>sT!qpl7#1SeakFn6Ll#i*FDci(iLm`2Xc+P#g-pNs536-A(p z5L82tbEw}tUfY4k77k7q4C)d6eu>2u1D)V7Z@93&@U^qzxhyzM&#nKgLBc-MeshrT-hXC-=3U_f=-cUi4Sp4MQ&9t2B2_{DhGS*Vgd<<~C<(cD}~ z_W99Zgv9j99)yU(BA$FvKiNV{rSq~x8Y!)V*2n@s|(U(1i*jDz&(ipnf#(hW`z(F0URlq`;fA8D#5g+5ny zlF1>)k89z1_A_P4h^t+cTFpJ;ic08c>wB)&RMbk|omk>}e>=PQ^~Bh=?D!g*5V;ui z#BH@#*!S`Hd?c3+w?L|F%pefa^`}bgJkpGc$U#!wjIQI=l9a@HBH?8Jz!9OQr3fnD zb*CU322)vaR53J4;CIILC0-r1 zX+<_^ zKHsUb5F$4XqHOpJW==ax*cCpA%b?Gt&5rk6@OV^;tl51}t;YOH`_3`wmi)b(37L~T zR422c*9D0Y_f6w_#-A?u@19~{Ms`GMv_}s6kgDFN9 zE?2T^g51UFTG2KNPY=+QdXD`@eBv|2V!O(H-E#g7lV4nrCSAO1*(q#4ZTNS|B0cEe zg>K&6W(z+|_BqvjePH(I+a2j?CB_|7p6);lfNiC$rz|PvH1o!`Tq5hahOF$4R_laI z%a@`MXIKng_J+=A`|+}mHnhet%F!c#Ftq?X-+-8X_QASxY>7oxZk7eBT{E$Ek}}a&)H0_UOCR%F30rqH#X-h+l{uTDbxLy(TCTu*NP8` zaJJE;C0da!k_sG@b(Ho|Ftda!<`{6g z<;qgZ9athnwUWW1h|e{~rd*!Jr4}B-%`JVNb1cr2Q3Nm}0GYGbHx|FF)tNQ@Tr0qy z-hliJ#eI_O&cP=b?R(9oT1(++oWAk}>yxI8`Gv%0uM>!*NE z^$UY(Lc@YORlX2x2v9STw*auB51;PjMi&;=q!W&ep3~GSY&jv9?tk4W-kLqcpJ}A* zm2#-SV!&Y2VA&y(saHpk7rNb}I#7HVwft!kj2tnPi7^44x$wYa>!+Wr=~;qgnZ@b~GJ3Q{L!T~JqWtiR1F?(s#8-_qA5+=! zl0~sdvE&eC9xQ>}F_#`wWD;?NB3QMbh5Q6Fw|4#eGit%W!rq@N`S`opgdMSxWabai zWgI%9{4m9FO8|L7JVv~oBrsLKBjk%7i`6blajTEIU1AC-f0l=;9-2MZ$vT#B-m>(~3A=T?tc>9pi-w@=_SQ zXfurM7PMTVW*jt0ROzht1#%tT5yCl;DUsT#nOphIXy1Le?nAAIjMKVsJ2Q4I?Byh? z&?PFuXKJg(6-a_?bwW|nlkE$V@0pflw~Tq8MoXN}1bVii_rk@^#=|BWrI+u~Yq(#) z?WyHG8otd*KpoNG>W6J(H8(5IW15sJbVD1GQyr$UbTu_I(b`pyjK*P58~>u;oR{oz zJJPzyuoU_xF>hYRCiVCQI-bu;^XVE~0>b0$ z4-qZ#7$PKk8 z7YMKJEBCeTGS_p=6H|UaTQi;FCf*LnRm$?QNuE^VG_YY05sgoimk?oiHV)mp`&J4Q zH~Mra2scNeOWN!M_b;)0q;NF!id^nBeX6(Y^2wH-6+fC0s3D@g(7N$S?L+>|qPm3I zx56ebr#VF))ttpuQ!9=hB-rYx3-FZEToPo3bv1GF4&A4eWnqm(PX>8LCCQ;$1Q9ck zCvhOKcmLp?Wa(BGe}vmR*`K1vk=0Z}#}{l3g|zKb*vc`*)j>?>u84O4%|L2ExDv8K zj3)`5kFe1}ksTC%lbkCY)E*?%g$?6(-js*irHo@(*X<0fyyqi6iEZe`+MtgW(}{3LxeFB@ih-%4mXY1j&+@V%0Zo+$Uh~KocXizNXYjrbg)$gE zLCaG!V+cK;RKy8Wzk`=Z68~5}ErtI$1%9LoP0kIK9yDTp$gM(9eF(aLIS+k(qKv7@ zI7Nxf8-6<`sb8tG0XP^X<@rVN6G!Olbz$iB zVB(qPJNxQk3-Kx9Z?1!{m+S&Rj_T?IZV78r4rqpvR6FC$DLxd1&zQnyihdLdevbfI z%^3yLKJ47grcD<}a{1({UHn~VFJAEaYrfav7f%f{@00Ur^;Yt=8zuI=gaUT*doAp- z-siXyY-Z&pgW)p=41^5Umf@>hQi2=Io=Lw@`UapBOM_*wsB-bkC|TF08g(1X$Z(`5 z3q$Dq5b%&TeZozLhp&LGM}c?K(u?>ZI+9VUK^>aEqHrA1bsJ}@jZj(!!S(71ZfpCZ zYP9il`MKpKP1Sn&)+0}=q|-j)HV%o&#HewNqbVcd&h;3l(97h@d&eA_Hk5+N^Q<%Dg>(Ic^?-t560x>2rsWV{>W)%Z? z*Km1(@dRHnC|&^7$>AmP$HRSXV5kLZO#jx|>?>`L=8~iI5wp6ch8M ztD@?=!sGp7128zPNZrTxrQ)!){ zRWqX2F*2Ep`ql6#G$bNUjwXShZm3Ole2a@1aUUE_!qZ+%`A$A4jdIK&Ac-rYmqWLn`*9~1(`T~QU*oVSL;CmjVvKsOp5}G(NpNKUOH7?{pq{mq6=<&eaFlgr;4s=J=IN zj0`8nI|F!ylo_vz5_dzuMOVkrCgfDb1$*?TnQpscA z!;i7azN+A-!<+C;*z<=DxVrhGn^A;O8CGpuRyP+O$G#Nt*Ii5P&6Og)e9)=8=hV47 zug8;5RT`q&qh-6>5#gCGJfY=LA#)K7Q5dM+e;ZskL@cp! zru*dw2aKw}I_m5lV>i&uW}%?jmT;D>o=^URt-FG?2q#O+HbW>O}n4Gky-*+hkmw!$X`WNcld z7$#?)R>a*1R$)tN%w|SC2uuTl$FdI&Kh_j{Rr{DPJowbLk#LN>w6+37$N$VR_5qLW z8&#-(9#`^9r4Cr=Y8g)!_g zq7&?`E6m)u4>ZaDD)S{EN<-|@YiedJl@U-JgsIAZ(MX>3DaBoXJk z9crGY6G?J8g9Gy=ECrd2fy}tH*F3ylieX3Mzj&IQ;b7J-_kL%k`-N8}kVAI*HwQ|L z_nIC%D;xALU7$Bq7>2=>xFops4f40$MdY1l{>^tX zdFNYbJn<~a)&{*ma{6z+O8eXrj+PWERVwp)sekPegsCtDG=;zsP>=?@iUw07Dcnp6 z;YT#DG-$5JbbBW8K%s&NEAEkWdo%CP<$vuHjsB}^Vfb_T@_rwV4uQTFC@o03KGms7 zQnNF(>Q69`g0$8oB>0IYs&5ku7_&4P4Fy|Jy8b9>QD)fPqP%dF!c-Y+YlnE{9F>VN z`kPz4>9;?D(cGc+&}S$rO{6tLBzYhuz>yK9iCu_nniz{t(_{Ve4(+~)Fk^C3QC^rs z*$J81;#BA9Gq(eezN&zPk`rwWAh6MxK%zCZ?K*OF5~&4RXhIZPnn<1CcY9>AS@8QP z4W1GV|KQiKGG(?~4T3-Z1|>sKJbakWg|lR(L3X4{7@Gwdb=B!ulQzExpsr$(-1lSv7@cALz|45~avxj21--~Y4k2$1jCI>ehz?)Oc| z0Iq^5%@?3Lq#74?WVByT!(aWxWN!T3@MJ3@MCbNX0XPl*!-(c(Kvm2 z#y@-RwpUG2|DW&qz>nQ=?+?9kcJ~~O%`+6QJ48{`nZ5fTFca;d&OVD!Ce27wwk3(y zG`R{RCnZJGKoLO9FVq%oF zL66nm01Z?sBjhR-f@YgSCWCG@Fv?@l>(lomnw;np==W^wg(6NqW1%$XfU)H>L|fa`tSpVqCNqa`gT@N!JMP9Y1JvS0yqyNN zC5U6erq-AU3O5|bHUy&YVM{?FWs)-$WBc}?8`}&+$uJRQrzTl{{At?#2s2lp-)^(r z>!AgV85U{NLNBj!;O;j-yMvA;>A3|=9U{=kY>w3E2(6WMl3oX|*WuZ7OLUWj%$8rs zEBEt%y;EO*MFV+L9Q=6Z>{D+jCPT`1-;28ZBB!6-A>V9aRmRE9?!uayBPmZ{q;1S+ zzKTq_)V+j(hKWN**m>b8W^Oo+ZtRe=KzKt+d-ow4brNZ_+Ks3gDUw2k)tx3@ETGqC z(ClHSQ=}aSFA6dJfV2Q{d6kUiVh=h5t1ASJCc{pb+Sx_CexHdfkwrv{@ZSMCmk+WkaUOk$rn{ zx?M!d#wnKZIvpaFfYywp9L8s+Nw2TtXoYiZp7uzIj$_kauHkt;}+y%eLhF?YGgdcgd)byc;vQx4`&^{vpA* zP4k6Mpq~FE_QlVD5h9E{QK(TG`U(OKz3m2WwMzHtZ{X&0$Wj&MI`oDit_Ei~MC|Nv zd~5`#xeWq_%|1rx#tjoXOP6sbcM)x@U}W;F zZMJa`j2=A5K1-8M8^oDB`F(q6Tv=s$-vZhjvK@w04at$)Zl>x=^7FeeycYR8-%51( zGS(Aku^bB#CnSD=;k7YFCy>KFR$p`^Cq(asMt-`6}ss$1rDhlSl&*D5?`T6Gaj6sE5eSQLK#drhSjnf8cqZ+=RgM z5HiK3%V#koSi5wELI{+kNaTC8gh>=A21q)wCRx4&(qi=BJ_elz-IWyvFD%^$eDoFP zkf$I0!qL^sFaEqKd?t-1Z#XiKb>Oww2W~>{I!2NkM|o|+wPzqbf|<^dAW5W&Hch0r z%2-%qxZ}}`B#|G|YqjY5J|i;=l=mE@ckwKOgt41$Akq1zQZUcg<27$rEk0C{_v>*&>x)b6A}kTiZJ{ zx;1jjF1>qx?t#x-xVZ6(0&;MF;UoX|pM3mvtLLBM`h`*Ky>G?Lk06FuFf0?1E`pRu zzlFj;X38iHN^5Wm=+ro}y@*&`r`5ISX;5fl!0q)Jv>MoHmpF=QhTMrQW`gne~;#Lx+e0uR$LNRouaND;&ig|v&eu}yanG7w-0iPkY2fltf#S$O?h zu}UR&z2|ODKK2+RnF7f~ft5mr@f(jZwR<=9bLTNNl$BzvoX2o87?~8plrZd}Er)dd z4D8;I0OHy<_GpFP(i&DJPq4a1X-2?CmhvUd6~%!LTq(6UdqU?4GX@HCKt7 z1A;_fb&9yU=cR$kvAxtDdxp`mX$-r}`csc{_UvW4!#-D@Jxwl~A`Sy=rAgJd7(Dw7 zf#o8!P59)agi9ANM2I$Qa>0a6#gM$Sz@#jQVX2jB89_k3H{AySGH(hvwK(E^a->MtOqMA{aH zY2xS}X1s%p>R8bRhW4@ZWlYmXZaxf+uaXY7uu}!Bv3aIm_d0HW?^{?H$up{<1VmbK zroPF@Ew3j#F@`lVhEAJUKt@2&>yjTGM;a!96qvW(O7;gojPHl|^#NjP9HUaiA83?P zbe?<+duN5}{1m&#V)oy26H^sQY4Iue`d`o)#OOQTNhTvtydom&(&pB21V5{_2Ca!B zAEgou!$L{}R6-mLkbs*ipu;Za@Ek-PLS4Zexsll0!QJ`-cH;#&`z*cnA?agp!K{o? zN?MG_0b$p}?)&tC*47$>L5Hdu5;bNqVvfSd1no`(e=uNAxxmEU-J~l8 z%zT=3e+OgOz(_Q4kl?gVV>BD&l);GAAaBLEP7f#N&|BTcfAfdPx{yv8<+tv1{-00w zgRPgfLxdFf0|)`__A(EC={MM{T|jF{l9(ikh@(EzG{MTC?F!0WAW9#`c2k%-!c`qi zVzT)oq|3V*IkFE^dAKT~^V~O>e&buX>Gtb5P>2~xG`7|>pM8wwlb@-OtJs(sg_DagjRA6UhV8tIKX(hQm6M2( zDU1*P9H~kQv9(27^@%-?X8&9N0Wx)P&0iBuuc zY8_h%hFY;vs}Tvv*ap+Dy%Q;VtZr>1`(2!N7f%az&yBP8w~rIElp5wc^7ga7zkAAQ*Z(OTb+{!a&2FKAFoA<&=|!nk@iFpdMXRwRic5f(C4MY?6; z^epkrn=vhL4MqRaFOigwlC!#4>klDTzD$xoOf7OrydF|3CijdpHJ|3@(SSK4rbNtE z7?W9;r_=0W3+PDLF$B9a66N)gCiG0jjx9+Xi^50_J8cpNJ<@4|^n?EbzVHjsUjf5K zz3>QP&_{jZPq6;|?~_q|RDF%G?J?+tCtjA3b!BT?fcRYv#sB=SLdOwO8YrdED#0{N zbey2#2nED(gq9Z4OrwR3rQ5i@uOoehh|NK9S$GXP zRV3;(F&vk{PK$0V=?H z9V0d9(Civm2DCd0LxlvWP!JcgMr;AZofl_|Ebw5VOEQ~Aix(?5TjZlg<(E@0KqOQ6~85lUkD=*?!W-$#gh!BZ~qgN0@6P-DUoSq{r?IkpFXf44? zVYxZ5ED&H?7Q(QQv5y^g=?~WEB|C(r!%QB14KMuZUqGuzR|_1VxO*2F+rpvGP-^-C z8uFJb4hw4Ogr-1k6AY9c0c=+M|>t1BOS z*#P-hAN}#$m6`pg!pfkLS8p6BzWE})c_1ZPX|z&TSMNfgqY&-)i0jLwT@$A~g9uyL zaUE0liL6;H?kHRe~GbC zCodtTA#Xu@@e0=71&SZ}De8I)H;!=xUDVfpZf|?G78f-&~{2JK6!?L7tnVl0|9L%=t@Cvr$LH{ z)Yvpur$-8bWtpg9M8-78&F;c1<-aBE@8289OW3jy8ywuwAV34DPO*A@PYjYs8&RRBH{{K7+{35zpUE;f7sg zT#aR!2x5e|+Q})UP%1`85o*{cYHs3$9_>NQcH3t#Na*ziDv7X!!e3p)iyVd?H0mCg zo;%0NN{x#buVAGlT4>T)o0)_2#0X5lu?=L}fvr_CX#>|E;=~En@f;3{tQnKC&4bVU z%ugMD$!o~>?iv5-?Ck8V=GXreceX;5DxmEwT3Tqq-*sC_lKkgmG=i(0m}S}6+QU)} z3_P^oh9JNQ`iP64!!Q)eE}+J5rSr^}adH^~G)fB$H**z8925IP)aGUC5Bv$;C;yhl z$`*l=%ubhBm~%)Uyn{?}gu&BarCsxgoh<9Eh>lilYVd{wY}>%LO zez%JA$gfjg{uSKLNput{3 zKKVtg$3KQT^&qj#kiX@f$gRh5O_Rv)A+!di&{2r$)JZNrMRNH?cCIYa@WDk>tyCBc zBt~_daN;1%9gAiUd~IP`l5pUmYju1DH9yAl0<1V7Y<9>OG6=^ah%_V~M$RHVULaF8 z$y6OGd&Y@-9n8pw`UYZs?ZitOSxeX7en)5R%4B1`M)al+u=7Mhyaj%eLPb7N6rojw zN@CPEVQ8%pLjGNi5r##iB#}`?9)aG>3{2aj0o0CdF!wLfEGJ*MExVNm@gjrL8{ASVNeQ)`AQI zr<5l*UZr}&jo3MtRLWssNMay~HBxGHt8r8zbmqx2SI5--N@Y!mex#9?@Kn5y3C zB=$JM%pz5T#LlAJDoRAeleeKVVX8c9u-i|d zLxC`J#EWMz=5Jv*`36*aln5Jr`6?!`6hQN=E69etv+2tFf}npb=W3TC?dK&GGRcV6kej~*Bhj@z_BFN z@gh3tvmR@la*pwBmZG(?caaLfxJ%2*QaM^f842#5nfp$ zh=&kpRx=sQ)-nfgJjR>e{(kIC1tAR#^Xg+dgkhmo!sdk+c;?T3lZm+!t_*RK29^lu z)FTG#=P1Tal3)n)uOrOPqqF10!#>T=eUk3UCz*czn<&hbQGO4F$cphx_ET~tdb@>4r|9oANK42| zfwmxJ!qCpLooF^&J}4W%F~lEwgi(x>$rfBeIx+Rk?r$Pvr_D4}<= zUOkWOY$9j(Gx5F;lC91&`sqLBt~c$%ojZU?m9X>>!_HhS<_M$^_}eey#AmTAmrQ9M zC)mUi8gt|@sayqFdxCKCwdhQRq_>6IUPqmOnBnpZtQOlj?pkkTY{CXj}SG)$a)nRf3* z-0T!iZVp2TjATe8@(eoz{PDLin0y^x^(M^NN1gi;sl&HWo*zSuPN1?AC}|QVlDNK( zYSr;iJx}}M6?S?C10^vH=tl~f&C%%xbde-d)0Kj}1SFj{t&JM784(*1 zwh2-QEK`xrK_=_q4@2r(Eu^+dB53vpoD!NQ(2qlmSYate5R2+xY`1^=I|k&QjP*u8 z=Eo8Zi-!oY`HFPUH7G{4EEZt{h)j?G3D7h}7AY!~%W{=v(^J|52=e)led_PpJ8O@i4ZBy~CuldxFCWG#Rls&JX~t-8gNesKj5D~)AUH!}&yWpT zB=!v2@{u|v2#ZKNKnj73n&5cg7m+~;BP2>0uxw;y9+96wAsIHdA)G>2mN7vIl^K%Z zklhzw;2Re=FbYy57zjfqBvT*%6i0sYC%Am^4SGspBM?$h1VSriw6{xtJY<{=aorSc zWrPJl#wa7mGD&BwiPDrPGYBo&AE%6U3MygI)|@YE7Hc+P=r9sRHn&?GuNL-xXM5+x z@46s=4=nfeqhIOXyPeB4WC$$lz=fNCKtd0$XSdeOnl%_od zeczHdMleVWLuE*fV3bNy<8V7kXc$e-hC?|xG-F{q23IOj8aW;iszLStX$=3(ciru+ zE!CcydthbqYnQ9I-40c)iH+ppFa8y>>NKslUZei}H}I_#@!oSJX&v>z(}?~K=H5G8 z`T9E?{p?>wD~YWRAORE);hy^v3IUb_>L3tdvh-jk@A{xpWEvr}j8UtBTbyLv?y&dl z@6deiS#+F{WER>DNTY;eK43H$u@fg;{I#zzZCk`~%)IAdY=_n`#+jL7xU<8C5?mWu z*=MHXZ~dE)^~MR-@gnC z=TL?DT6HHT8(5EjcjzjVFv(sxaepFKKlyOOVr$K$YT`(*tt6DFbR2NdU*=&rBh zm#4V<&F4A##0M$dewW^{M^Me1Sj9Qi(uauaef0hs;h9Hab_FaCl_|182fcHf=;pg5 z%{{`bkNVT!K>9UY2eQGK)=STm?9_3_9g;g&>0Y}@Z)cz3NYfjP@uz2S-GJ4-ed-8q z2R^oAb9rZ%JC=>(1uPss$jvG($g%L#sqduI&3m-r4&+^Nmr5c3pm0j3m2-tdVTfZeAl+D_0)R$@G}q9 z?!EI4*&DBt_iRF2;t50AYZK%Yo(-NSs6Fv1vT={}n}1CHkU>lwMc1nM$Ip_ul2P#x zrZ>b&4s40;#g}RP?ypg?GIkqdW=|}TpPFX#jkhV+ii9RZj7B7d2_{z_X7BBbwAw>P zr8z>~!;D7ks+4h~!F3DzQt;G?2k6ZnAxk3YHeElyYlDZnyR8OBLyZ$!q{VvI1$ky$Bw)#+> zo?-T>Pmr5EN)ktO*Vd`}9-$QM7*PE-vbB%bZL_?zz(n9MH@QG-{Wh}GVs7OGL9s-r z3~7CX=_AL;xjEk5T0OWH->*Dm_bt)bfF}SW&<1Q}ktj)=SQJBWbe>X>;yH$ig#|3B zamuC2KmT{n|KfLCkfp%;_z3eiRW3)X-DZ#_T=?OiWc|&zIPut{)L(y@jjK1QKJg5p zG=v40;wQf!_l4iabtG)w!0g{aSq|OEqC8QdP%RRr3ac`S<9m#5T_fyoGDv-_)(-K` z4x@M9BC9vq-EUF)^k>;G&eOlMNt7xkmgey6Jky6y zTlWVzfjmGK_{`0MpBUi6)D{EEVad>q{bl(3|`)u`}=?O`+xnP{Pwrn-%&x% z>J0HXGl1jq$`H8(;gkY+k%ccW1zG5Ha^7A15w^ zococ#Mfs5nh-QbfV`#nj0w#3GPfXzI5qHS%H zqXzBkH(30h&l66Upw(d9+9xQ4^c#Cr@&(@BUZZFrCk(z6$os2g+SM3fWCQ~v2rxvM z#8Wmo*G9w&(`!&!n8)`bf(wr#mQHQ|<6rpd%il3UK0i@*5@Wt%3lZdPiO5o9ni6JX zhP^(!N)!7z9&&8v0|9HRNEy($xlZoF%A{~v0H&z+E z_7eSzS7>gG*uT0;yDup&EYNQc>F)PP$^n7xbNy@IBsX@L|Ijmxn|1P)JdUtw-@8ec z_qliV7O51tfQLaDL%-LH9Yy^Jl+DYy`^OpSJ@gVaN$XB@a`R1tw;5gykW*k3NH*JAtX4sQk>&{;Oa5 zj@`1PmfsUNR^`yt47YbS85_Z*5VSHdC?;kWs9J_n7!ZE=X+}40Fp`2ZKl4vZME~MvkkTi;xys#}n^-nD8dksh z8rSSMn92KC|J`{ksqkBM{3B;TXp|SQ{@k-f0z5y58xJYkF1ty>>B$0>6Q{^milqG^ z{*e<@Yh9Yx-lQmPr0wIDr%2oTsPTxT-=S8TqCV_nBv?|DN1!snP)QI58-pt#Cp4Y~ z6Sah+=W7%uHN^)$fHl8DGIJg~EMckkBjdF{Ivgy0^3Q#6?Aw|ce#|vLbRkWco?W1< z1&0BxV9y8=Aviiwpt`w<>9pATrGL%#YJ;@dAnUe>{XE&$2J!YD?KiIyd4lxNB6hxj z=?lzQ;#mTAw^4&0&f+qb7Nld1D+PfNTj7bb( zI-uK2Q5tFsb2!>y;+QER2!aB^-YU&ISFsd`QkCuJ{)n_YV(qtn8IdIn>g#m3S5dtI zq0*Em=IJOwdVi~OZ9~md6dg^`(G+aWq?<7nWIT8(;o(oNQ2yXY2p@Td;R4U5pIBt#Z$wE66{nRNSyq_SEe;nBKnP=$G0-l zD>sqaFj2`Nfzm@~u_e?bsJ$MJB}rOcvi<-)s^c9!gRIujN>ES&apfkHCyp^tg2H5t zUb{uNHzd0C9w{ldE3rJy;-@}GFgr)TRmav2^AA2w;q-%)=gSnF3=DXd!L<&;u(qY~ zZH;Fc7QK`yOEFtbm|6_*4=rI&PDAY&t{9N-y@91#NTJZeMq9xbzav|gwJ`sQb?KHz zW<+PWha(_}`nbgc#<3B*bt(@%h`MtV-EJ}QcYYe=FT(Se*r@j@mdc1&(@VzK!Z03b z;zZCe5=VgF&oDbJm~J69@6mm49a~7ILxZ0s*c$ZP?@%s;*b=f>5noxSes_yJDLKcc zas74rjXKLlqY~Jbg54XdOjmN8_}mv*|M!20s?Bik)mNxK^%;isCZ!V((%au=|Lz@% zPL`=yt#|Bf@xuBR~l#`x!+qqnFr3kpvBar^yRNF@)k|g|O%ns8x(9W2^~u zeg!AmCUHv$+d-Q6v1sktC4cENYyVk?y2n61RnN)5wQkZDOl8bS#(KlmB!>1mRyH|gvo zNFi|r_$LnICnMbZS57sEFyj=<1{G^8tuc45qSxNVZr@|*<~fvGWns|}s+4RbNgWsN z-C%oXpU%!M(Kuney@ofl%-ZWO;VmD>#=<*#gsGL&T>t8?P&xfDmNb+f{xEh}V7PIW zvZpZjue!a&B4-;4wxZ-{78V87W3z3_UZ?o! zPm=9ylWeRpzIYkef=of&O(_Qkx0q6$PMANTQMEam*27$U_I1t`5-xnmCcGYVr6IZ4 zjtHe>EDWA93~t?|{(Ha5;V=9MjqMHU@4v%bu83J45-c6Y%~uFcKg?>Q#W2&1v>;Z3 zP=X~4GeL@P3}r84u4E_|e8QZMuhtRvDV$^-k?Z7FPF{mLr0TaxB3HaxKX+eZC zpR7Qs7X*x>5p4k(h8xWmvqB;)P)f75y-7nF`WQxmpkUdYdGaZuciy3YZ-=@i@G)p5 z$e{?mj9f9reC#LjHlO3#-4Vu1@sH0_UU`t**4v~n{$E(-8HUY$jHSsJQi|macfwFP z2EoY%%*^B16(6;`P3@aESeZ%)Txhfu1=nTi!iSJbFk7?fzW)-d9fL3dssi6HXiW)@BKat7aqZvEo^t0#(Qs5IlRK17bS-{=tN=!o`Fzk0s)g#l8Lzr!O~eQ z2ZSDAjE_hLI7(pFUPR45ii*3)cn@p%++)#i{@9}b!@s`%mlEWmkA3({ciwrA^>&-S zePAJk2oeGPgA6;N=aI$=q4zHjb3z5_@B_HF@;D#37 zXFfpiJAX{MqzQV0wb@5-FMOE8*??lrXOtNZ2UD0k*Rd-h*Pnk5k&N-90nwn#^od2L zPaNmY8*dZ$-e>B>X|`K!<{V9$TBy`wDopXEqULIHKIAJl*3tv$@)?Y7;EDv%dXLnb zhh77-y^kJdNJk+1n+)&lh=lns0zdl~K0|)~>8JgvAphmc(vmm4vr0LiW00jdLSO+A zh7`d-Yi!3QG5R2Li=d1k%`yfkEGd|*mT8Y7B1@9lhFmU(lmgy;neDf)p*w8~i?di+ zih1DUY%i=}hm*)}{VIp^L#8L&Ys^W;k&#X@1gt^ z#%n$L+bN^8RQ%sBt$*b&EXYs&=wJI@r;z(`>6KCAE{-3v)oUUx3(r8+%OO?9&=`6~ z;aLt51|z@|0wEw1koP=JKK>-TcW=_P1Qr5UI-muOUP^1EKv-yP<6DxbzC(~N;H_;k zdFz`@S9`cQfl48(?~(N-p}LFLzlCVtL|PIp@_0MH#m#pT2CHLk-wjEnMoi6+ZLU+l z{66iCUBa?MnrOVdMXBJidhI%$cP>#n_b7)Rf0EtRb*?=BET!e+1f?<;f9n@1nlVN| z#miW#C=MT=WcKta@;MJb3=w_-EAAs2s|+_gbocV8(SX)__vj9*4B{N!n|q80ia{qP z86*ooIvM=(uWWRCe<49Wed@t~SUqw6Y&=My-=s1#h1(yHw*#CwA&Dcv@Wc;%fKIc+ z<}e}#90Z{O%NRf~QW;UJ$vBP}8b|~rDkX4iL~W9OZ%C>H?RF187vgz7&iXC%+WYhd zHbi6e7}BAnx29Nscbl+!9Mj)GwZ^2KUDW0^+;NlHsS(X_mCnK$hV>3pKl$I#d+uen zdMV7d5geUcrrqyyvQVTEB~)i-ATx}zn4SO|K{wMR!x2IoQVkgvdpli5^)`iC zl{6aQA9D*4~u4&S)qB5NzKKWB@y!r~^ z$+K)??-@QY4^c2faf0|oYuCRakB1yYNxf-HI9d`OL?>+km z%-KVZR1=mT{S>w?;$v{!3YG{kg?W^}z;IuYP-dV*y4UaEmZs=6N00@K2AX@j1ANO6 z4`P(I5w=fHhsEFA-u>loTaaT>{C&A%^~Z|y%WPkKg}F25Fh*nQ4JIyph|0_~osBi@ zaZJb2tX|zCk6>CzGGmyq9ZIgl%*+hijV7&rhkh1gNy*3<^5Vdh8ySr)B#~v|*fuKD z#EC|_5|xY@3=^Dkg~XKTGzTOiMwBLLZ?}nhhVIa!*GMq_BFP{l9=f!5#+ZR-dS#hA zzx{vH9UOei#1cfEUA#e`l7O(Wk2QS=LYKzQ4pE%)>Yu*M+iN?tuU+C;Zpd;?P@X7} zqshWU=W&N!JU@r59-_0|p>u1KEK7)cLvjy&nCQk;OcFD_v`BTP0&zmx?XgpD64s^} z#un))#x(X9zIbTi%fDT3_P;Gb&W`zCCMK(A+Se~K8jtaEE^cX>q_cw)7D)EiaibB9 zMwe`>gPUp!VL;6fDJB_Vm?Mw^73OKT>g0r^1M~n#T9o`8`zj#_3)Hie$%EFprb;Kzo#C zjv$VF5HFab@y6SvBFA{ALE7&VcLogleYDmLc6W#^k8ZEejmvk*ADY9?+pON}p{8aK zqcPTSgv1i=@}c#w-dlV9&lTirf9rn=a`Uw>?_a*`O3!Dj+oZnVpfoj!=eQ(ULg~mU zhVNfOwTDz5eh9zYWjG$Ak_5EDjK??_g7KJ96d^20q&0mZa2^5Us;|$O9 zDNoH)SUiqz3o4ZY1xqpG^_h73`(LM4Y*d>Mf*O#4FVZ3nkpGy99+YV!wrzflmghLTibM9JJO%y@XEP(C^uFyDq~< z!XS2tj6+nc61BRFMhWBOz*gBG#JH0aOr1H3I_VRhdywt5J+u-`S4)Jpg+P)&G>hXo zjPLF({I}0M`Q=}~d%OStD@be5{$GjG2VA9TcUufQZF1v?kN(qtNI&fp@9r?Zb`6s$ zrXPKZco^ZNL%e(u*AEa;gsImtY0Bi;hnX&xY2LX_CJ&lOc_AqogRK>=>yZ-{smd@2 z#sYR5bu0miFtlxp!Ens#Ygd>J%JkYz++3cpQl?ZW5k(QZv0@}aTY}Jb$a|7zr0DE- zY2Djpu-~RXGPr)8alL^OF1_6r{`?#%4ue!tT{%Gyo3-|s5dl3D(rFnQsm(}u7$*l) z)7;suqy0Sftu2~KhVXpcTpp*GM_UfLBg-VcKEatu-hb;AD#sRZI-03Uh0fL<3r}3Y zuT3DLKIwj2j0e57-|ctb_)iM**PnVaYzXViT_GL%UD7zE6c|?KJcRGyUAayrEFX+C zJ9Vg*A@uO_RpQn@g;Qs6a{(fbFt$zg$O`qVm#~##97n`LkTaTDBe4zS@_G9G0sei` z!1$o#hJ_&(&`VP4d;6^SyI64qI%7Q^)9ek<(xN*WF%*)}wopQl^F8`;#(t);9EU8Q zV?Wh2vWOTBUVyD7ST^l?8*_M(;IkLFzQ2JSDf*+B(DPWFnk5o~PO->-GNha@5@|zN znLs%Xog`-I*b2t7@I4!;44LOpD3?iujWCLogy`NDhYy`XBoU)V8{KZ>b$2KPn(`sV z%;_}$OIHTJ@}IO${`>D;_}=xsb749(?0oI_aE@v|ywXSK4U%W_7SailCW{l#l z$tYE)_{c-&*}n7^VjPhxPoPFaX0=5%A28V2WA6M}L~p>bwMXaf4R-oNuDlvCQ_OLq zT4T6#6_NBgcFJY)c!9a|kHFn;psfP#)XInI$6lVD{70>w|IZcjPyX6RetN=hot`>= zn3c2e;8GW%?;~9&YZMk|m^fXgaAqDi7r|Zw=kR&_T8*SRB$%1QnV3Zg54$o;JQ~vO z?$g+7kfs@jPM%@u^nHe3z)%#7$EkJa>+At3_UE zY$=Hi#P?T{4`=}{aC&MQCzs=HG{!GiNc&w#Qf3{G(Dyl6n_)7|u;T21(ObyT>Gas% zZDU2S_|U`j-nzzowTd1`7{{hosbD(=#(Nt~9a&+zR%Y+*OVH_ZIP#P%h(^B0O{i>AR@X3VKvW4EMSnuE&O!%sbhJsdH)ewS{$&k;Y5 z$QN1P-(#kl$6zP}D$LQ36GmyqWVMLeuCxE#8|;7m705E~49BcgDp;8U4e#B1pTngx z^6ph~(^EWjHpj8E$0^hTqQ(Y=X5~5C2z~xQCHGuH|F3 zwn(*y6fwk-Wd1>>(l+@MA7K;~8C||YFu6pOn?PqVmFhJ6*Waa5E)jJ)#743H_K;S+ z#mZA3WqNrYv%Nv=d4zG7iTN2^E5WMHVA^fe#s;~S)0FLl^J0{Y$eDu~cY?qbnq@a* z^QEtI{&PRU^wcz0{_x)u7R!hu4{&eLWvS}ZymOC<6Q`Luc7na_eWHGwfk+4@tAvFd zNfc9G+u+m`99hmW-rmHGn>=@~i#4vbhnbp0H!mTA zkZIpV6iOuIu^k^d+6MP5a(o*h+nCBptjQ%Zr$AyAAe{Ng-uqvhnLhK`-T&|mDV4tz zt(;-MH^e{p!^nIMM}Rxp#@~M#k`0s-BCBO&e~m0Wf)$3Caf&;21gA7Z!P#f`t!r3& z0W6**S$&tPQS=s;X>PubH$B76Yxh`QoF*F%XuWfZ($W!f6Z3T6e~*dDY5Kcc3^uo! zI(dS<7hWOmwK#hI5ytPnNtA+}9Qb2BqxfiLOu67OQ6ABI_5UFM=#Mk=fpd(T4c>n7 zk0G#lB9ERXr4cBq7k|B1W z3FagGa2kP!O!gTSA4ldVG2s!c;Vpy{kj;J(OJ!s_A}O84mI^z%g^WBu8B>fJqTfuUDa?(TQrZ83> zIl73zC-Z7px`Q?ms3un0fuTZ_eda#*Jq)j2rE~F5$S<9yzqd!GG765U@>QJ>`#is@;n zO;wQAn5iRGjFlpU!mA#|E|gJ|7r-_zIKu0_hi!unmeA1-va|rI195^75}CCLCpFlU$mLED@4(dv!8vOc1CQ)=EK^KN6$9zj@u6zHx%WG*T{Nx5^K@rh|FyBVf1OWx}u;%zKD zk6#Os(+`u*J%*|tMg+zC?sbd^r%2QY9SzXdI$A|!?j+bDarpws1Dtwhx!6t-p)#}+ zFwC81AYE)}NSp~|Iz*1IqMba(_OYZys0g9eN4D?1J3V*!p*dtxzH<$Bwvcwp%&{VsLYJw93KMfx617kM@I%7Z_(6-{ zp&D_kfuHlSRRNJJku7|b{^BPfs2p&-EC*@XC@FEIM5P0S8e?>frG{ubK;WW;L`aD? z2Yj$BOK@zLOvRYm*#mgA&M+ziVKMX`!}jt>DZwaAHpU3s62kcc@C)|M_bwI1xcMoQ z_EAz|y8)y_qzo`BL7=hF2$P_!fYJ0Ph=MX%ZUMXkna;3Pf*9Pylx7h^AqoZ5);3WL z?qnXjG>P^sO0+Q5l!+hzVV3utaEd5@f@uC}go<&8H?UiqXab0P zxXCV7*+)-&lJWEhi2W(FSH!j*5CUo0i2LCVVHCo0$gmMA2CcD8hR%jaoq}&4&?pimfLeF^vlvA(taqv-6{g-$W!0Xi8V zkVrKIlO6C$vcW+D&#RIlFkTU3J6M(r8038xl@JzEScv-jh*y6Doh8WXG?tTM^7F{< z7FIe!6=yN=5F!JEO@y#faTnc>u=E<4T|{`n0mZbmfT%5z=8s{^A;J&HN-HFVqh#iw z?2|SuCfv`QWJ!CS%WHFX7q{BV*aGyaYP-X(k%c#^~ z+v!2w+AaJU*x5J%ndOibrVn_H*$B%H5h#oiShj;efXNW|L3qYk;1rO>9GOyBc5u)Z z&ISk7Zrj7$w}6aF5SEL{5==bC2yws*vjS|HBh3yXua*;_Er)nCBFi$%WWyh@mEnR) zQnYQOq(Mp>yI8{n1++?$0z^9CG99SayVPN?7HS2sK3ZHXt4$yo0SqhtuHY(772@P{m-Obb9bA zkCb2xQW&HV7%32L36Tsi(nCrI)xLwa1JY4|t}Y|OGE!Pt0*th<-5goeM_3kEA(-qy zY`8Cm0&P$xMp`c5U~s`QSeA>{3N0)U7DBoR;h+y_xMb-;Y{(K0S_+aRBaOybo`sG& pXr)nFp_2rCf6A&=210)E{{>WLGIh;Qw`~9b002ovPDHLkV1is1ktF~C literal 0 HcmV?d00001 diff --git a/packages/flame_oxygen/example/lib/component/timer_component.dart b/packages/flame_oxygen/example/lib/component/timer_component.dart new file mode 100644 index 000000000..f6160ecfa --- /dev/null +++ b/packages/flame_oxygen/example/lib/component/timer_component.dart @@ -0,0 +1,33 @@ +import 'package:flame_oxygen/flame_oxygen.dart'; + +class TimerComponent extends Component { + late double _maxTime; + + /// Max time in seconds. + double get maxTime => _maxTime; + + late double _timePassed; + + /// Passed time in seconds. + double get timePassed => _timePassed; + + set timePassed(double time) { + _timePassed = time.clamp(0, maxTime); + } + + bool get done => _timePassed >= _maxTime; + + double get percentage => _timePassed / _maxTime; + + @override + void init([double? maxTime]) { + _maxTime = maxTime ?? 0; + _timePassed = 0; + } + + @override + void reset() { + _maxTime = 0; + _timePassed = 0; + } +} diff --git a/packages/flame_oxygen/example/lib/component/velocity_component.dart b/packages/flame_oxygen/example/lib/component/velocity_component.dart new file mode 100644 index 000000000..533b65d11 --- /dev/null +++ b/packages/flame_oxygen/example/lib/component/velocity_component.dart @@ -0,0 +1,15 @@ +import 'package:flame/extensions.dart'; +import 'package:flame_oxygen/flame_oxygen.dart'; + +class VelocityComponent extends Component { + late Vector2 _velocity; + + Vector2 get velocity => _velocity; + set velocity(Vector2 position) => _velocity.setFrom(position); + + @override + void init([Vector2? velocity]) => _velocity = velocity ?? Vector2.zero(); + + @override + void reset() => _velocity.setZero(); +} diff --git a/packages/flame_oxygen/example/lib/main.dart b/packages/flame_oxygen/example/lib/main.dart new file mode 100644 index 000000000..34574d13f --- /dev/null +++ b/packages/flame_oxygen/example/lib/main.dart @@ -0,0 +1,51 @@ +import 'dart:math'; + +import 'package:flame/game.dart'; +import 'package:flame_oxygen/flame_oxygen.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; + +import 'component/timer_component.dart'; +import 'component/velocity_component.dart'; +import 'system/debug_system.dart'; +import 'system/kawabunga_system.dart'; +import 'system/move_system.dart'; +import 'system/sprite_system.dart'; + +void main() { + runApp(GameWidget(game: ExampleGame())); +} + +class ExampleGame extends OxygenGame with FPSCounter { + @override + Future init() async { + if (kDebugMode) { + world.registerSystem(DebugSystem()); + } + world.registerSystem(MoveSystem()); + world.registerSystem(SpriteSystem()); + world.registerSystem(KawabungaSystem()); + + world.registerComponent(() => TimerComponent()); + world.registerComponent( + () => VelocityComponent(), + ); + + final random = Random(); + for (var i = 0; i < 10; i++) { + createEntity( + name: 'Entity $i', + position: size / 2, + size: Vector2.all(64), + angle: 0, + ) + ..add( + SpriteInit(await loadSprite('pizza.png')), + ) + ..add(Vector2( + random.nextDouble() * 100 * (random.nextBool() ? 1 : -1), + random.nextDouble() * 100 * (random.nextBool() ? 1 : -1), + )); + } + } +} diff --git a/packages/flame_oxygen/example/lib/system/debug_system.dart b/packages/flame_oxygen/example/lib/system/debug_system.dart new file mode 100644 index 000000000..8f3b1d190 --- /dev/null +++ b/packages/flame_oxygen/example/lib/system/debug_system.dart @@ -0,0 +1,63 @@ +import 'package:flame/game.dart'; +import 'package:flame_oxygen/flame_oxygen.dart'; +import 'package:flutter/material.dart'; + +class DebugSystem extends BaseSystem { + final debugPaint = Paint() + ..color = Colors.green + ..style = PaintingStyle.stroke; + + final textPainter = TextPaint( + config: const TextPaintConfig( + color: Colors.green, + fontSize: 10, + ), + ); + + final statusPainter = TextPaint( + config: const TextPaintConfig( + color: Colors.green, + fontSize: 16, + ), + ); + + @override + List> get filters => []; + + @override + void render(Canvas canvas) { + super.render(canvas); + statusPainter.render( + canvas, + [ + 'FPS: ${(world!.game as FPSCounter).fps()}', + 'Entities: ${world!.entities.length}', + ].join('\n'), + Vector2.zero(), + ); + } + + @override + void renderEntity(Canvas canvas, Entity entity) { + final size = entity.get()!.size; + + canvas.drawRect(Vector2.zero() & size, debugPaint); + + textPainter.render( + canvas, + [ + 'position: ${entity.get()!.position}', + 'size: $size', + 'angle: ${entity.get()?.radians ?? 0}', + 'anchor: ${entity.get()?.anchor ?? Anchor.topLeft}', + ].join('\n'), + Vector2(size.x + 2, 0), + ); + textPainter.render( + canvas, + entity.name ?? '', + Vector2(size.x / 2, size.y + 2), + anchor: Anchor.topCenter, + ); + } +} diff --git a/packages/flame_oxygen/example/lib/system/kawabunga_system.dart b/packages/flame_oxygen/example/lib/system/kawabunga_system.dart new file mode 100644 index 000000000..937d900b8 --- /dev/null +++ b/packages/flame_oxygen/example/lib/system/kawabunga_system.dart @@ -0,0 +1,46 @@ +import 'package:flame/game.dart'; +import 'package:flame_oxygen/flame_oxygen.dart'; +import 'package:flutter/material.dart'; + +import '../component/timer_component.dart'; + +class KawabungaSystem extends BaseSystem with UpdateSystem { + @override + List> get filters => [ + Has(), + Has(), + ]; + + @override + void renderEntity(Canvas canvas, Entity entity) { + final timer = entity.get()!; + final textComponent = entity.get()!; + final textRenderer = TextPaint( + config: textComponent.config.withColor( + textComponent.config.color.withOpacity(1 - timer.percentage), + ), + ); + + textRenderer.render( + canvas, + textComponent.text, + Vector2.zero(), + ); + } + + @override + void update(double delta) { + for (final entity in entities) { + final textComponent = entity.get()!; + final size = entity.get()!.size; + final textRenderer = TextPaint(config: textComponent.config); + size.setFrom(textRenderer.measureText(textComponent.text)); + + final timer = entity.get()!; + timer.timePassed = timer.timePassed + delta; + if (timer.done) { + entity.dispose(); + } + } + } +} diff --git a/packages/flame_oxygen/example/lib/system/move_system.dart b/packages/flame_oxygen/example/lib/system/move_system.dart new file mode 100644 index 000000000..20753515b --- /dev/null +++ b/packages/flame_oxygen/example/lib/system/move_system.dart @@ -0,0 +1,56 @@ +import 'package:flame/extensions.dart'; +import 'package:flame/game.dart'; +import 'package:flame_oxygen/flame_oxygen.dart'; +import 'package:flutter/material.dart'; + +import '../component/timer_component.dart'; +import '../component/velocity_component.dart'; +import '../main.dart'; + +class MoveSystem extends System with UpdateSystem, GameRef { + Query? _query; + + @override + void init() { + _query = createQuery([ + Has(), + Has(), + ]); + } + + @override + void dispose() { + _query = null; + super.dispose(); + } + + @override + void update(double delta) { + for (final entity in _query?.entities ?? []) { + final velocity = entity.get()!.velocity; + final size = entity.get()!.size; + final position = entity.get()!.position + ..add(velocity * delta); + + final screenSize = Vector2.zero() & game!.size; + if (!screenSize.containsPoint(position) || + !screenSize.containsPoint(position + size)) { + velocity.setFrom(-velocity); + + game!.createEntity( + name: '${entity.name} says', + position: position + size / 2, + size: Vector2.zero(), + anchor: Anchor.topCenter, + ) + ..add( + TextInit( + 'Kawabunga', + config: const TextPaintConfig(color: Colors.blue, fontSize: 12), + ), + ) + ..add(3); + } + } + } +} diff --git a/packages/flame_oxygen/example/lib/system/sprite_system.dart b/packages/flame_oxygen/example/lib/system/sprite_system.dart new file mode 100644 index 000000000..d4684261f --- /dev/null +++ b/packages/flame_oxygen/example/lib/system/sprite_system.dart @@ -0,0 +1,15 @@ +import 'package:flame_oxygen/flame_oxygen.dart'; +import 'package:flutter/material.dart'; + +class SpriteSystem extends BaseSystem { + @override + List> get filters => [Has()]; + + @override + void renderEntity(Canvas canvas, Entity entity) { + final size = entity.get()!.size; + final sprite = entity.get()?.sprite; + + sprite?.render(canvas, size: size); + } +} diff --git a/packages/flame_oxygen/example/pubspec.yaml b/packages/flame_oxygen/example/pubspec.yaml new file mode 100644 index 000000000..6a1deed95 --- /dev/null +++ b/packages/flame_oxygen/example/pubspec.yaml @@ -0,0 +1,28 @@ +name: example +description: Flame Oxygen example + +publish_to: 'none' + +version: 1.0.0+1 + +environment: + sdk: ">=2.12.0 <3.0.0" + +dependencies: + flutter: + sdk: flutter + flame: + path: ../../flame + flame_oxygen: + path: ../ + +dev_dependencies: + flutter_test: + sdk: flutter + dart_code_metrics: ^3.2.2 + +flutter: + uses-material-design: true + assets: + - assets/images/pizza.png + - assets/images/chopper.png \ No newline at end of file diff --git a/packages/flame_oxygen/example/web/favicon.png b/packages/flame_oxygen/example/web/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..8aaa46ac1ae21512746f852a42ba87e4165dfdd1 GIT binary patch literal 917 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|I14-?iy0X7 zltGxWVyS%@P(fs7NJL45ua8x7ey(0(N`6wRUPW#JP&EUCO@$SZnVVXYs8ErclUHn2 zVXFjIVFhG^g!Ppaz)DK8ZIvQ?0~DO|i&7O#^-S~(l1AfjnEK zjFOT9D}DX)@^Za$W4-*MbbUihOG|wNBYh(yU7!lx;>x^|#0uTKVr7USFmqf|i<65o z3raHc^AtelCMM;Vme?vOfh>Xph&xL%(-1c06+^uR^q@XSM&D4+Kp$>4P^%3{)XKjo zGZknv$b36P8?Z_gF{nK@`XI}Z90TzwSQO}0J1!f2c(B=V`5aP@1P1a|PZ!4!3&Gl8 zTYqUsf!gYFyJnXpu0!n&N*SYAX-%d(5gVjrHJWqXQshj@!Zm{!01WsQrH~9=kTxW#6SvuapgMqt>$=j#%eyGrQzr zP{L-3gsMA^$I1&gsBAEL+vxi1*Igl=8#8`5?A-T5=z-sk46WA1IUT)AIZHx1rdUrf zVJrJn<74DDw`j)Ki#gt}mIT-Q`XRa2-jQXQoI%w`nb|XblvzK${ZzlV)m-XcwC(od z71_OEC5Bt9GEXosOXaPTYOia#R4ID2TiU~`zVMl08TV_C%DnU4^+HE>9(CE4D6?Fz oujB08i7adh9xk7*FX66dWH6F5TM;?E2b5PlUHx3vIVCg!0Dx9vYXATM literal 0 HcmV?d00001 diff --git a/packages/flame_oxygen/example/web/icons/Icon-192.png b/packages/flame_oxygen/example/web/icons/Icon-192.png new file mode 100644 index 0000000000000000000000000000000000000000..b749bfef07473333cf1dd31e9eed89862a5d52aa GIT binary patch literal 5292 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 literal 0 HcmV?d00001 diff --git a/packages/flame_oxygen/example/web/icons/Icon-512.png b/packages/flame_oxygen/example/web/icons/Icon-512.png new file mode 100644 index 0000000000000000000000000000000000000000..88cfd48dff1169879ba46840804b412fe02fefd6 GIT binary patch literal 8252 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 literal 0 HcmV?d00001 diff --git a/packages/flame_oxygen/example/web/index.html b/packages/flame_oxygen/example/web/index.html new file mode 100644 index 000000000..0081e1894 --- /dev/null +++ b/packages/flame_oxygen/example/web/index.html @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + example + + + + + + + diff --git a/packages/flame_oxygen/example/web/manifest.json b/packages/flame_oxygen/example/web/manifest.json new file mode 100644 index 000000000..8c012917d --- /dev/null +++ b/packages/flame_oxygen/example/web/manifest.json @@ -0,0 +1,23 @@ +{ + "name": "example", + "short_name": "example", + "start_url": ".", + "display": "standalone", + "background_color": "#0175C2", + "theme_color": "#0175C2", + "description": "A new Flutter project.", + "orientation": "portrait-primary", + "prefer_related_applications": false, + "icons": [ + { + "src": "icons/Icon-192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "icons/Icon-512.png", + "sizes": "512x512", + "type": "image/png" + } + ] +} diff --git a/packages/flame_oxygen/lib/flame_oxygen.dart b/packages/flame_oxygen/lib/flame_oxygen.dart new file mode 100644 index 000000000..d8ecfc0eb --- /dev/null +++ b/packages/flame_oxygen/lib/flame_oxygen.dart @@ -0,0 +1,7 @@ +export 'package:oxygen/oxygen.dart'; + +export 'src/component.dart'; +export 'src/flame_system_manager.dart'; +export 'src/flame_world.dart'; +export 'src/oxygen_game.dart'; +export 'src/system.dart'; diff --git a/packages/flame_oxygen/lib/src/component.dart b/packages/flame_oxygen/lib/src/component.dart new file mode 100644 index 000000000..22d9e39fa --- /dev/null +++ b/packages/flame_oxygen/lib/src/component.dart @@ -0,0 +1,8 @@ +export 'component/anchor_component.dart'; +export 'component/angle_component.dart'; +export 'component/flip_component.dart'; +export 'component/particle_component.dart'; +export 'component/position_component.dart'; +export 'component/size_component.dart'; +export 'component/sprite_component.dart'; +export 'component/text_component.dart'; diff --git a/packages/flame_oxygen/lib/src/component/anchor_component.dart b/packages/flame_oxygen/lib/src/component/anchor_component.dart new file mode 100644 index 000000000..304ec984a --- /dev/null +++ b/packages/flame_oxygen/lib/src/component/anchor_component.dart @@ -0,0 +1,14 @@ +import 'package:flame/particles.dart'; +import 'package:oxygen/oxygen.dart'; + +export 'package:flame/particles.dart' show Anchor; + +class AnchorComponent extends Component { + late Anchor anchor; + + @override + void init([Anchor? anchor]) => this.anchor = anchor ?? Anchor.topLeft; + + @override + void reset() => anchor = Anchor.topLeft; +} diff --git a/packages/flame_oxygen/lib/src/component/angle_component.dart b/packages/flame_oxygen/lib/src/component/angle_component.dart new file mode 100644 index 000000000..9ecbdac89 --- /dev/null +++ b/packages/flame_oxygen/lib/src/component/angle_component.dart @@ -0,0 +1,15 @@ +import 'package:flame/extensions.dart'; +import 'package:oxygen/oxygen.dart'; + +class AngleComponent extends Component { + late double radians; + + double get degrees => radians * radians2Degrees; + set degrees(double degrees) => radians = degrees * degrees2Radians; + + @override + void init([double? radians]) => this.radians = radians ?? 0; + + @override + void reset() => radians = 0; +} diff --git a/packages/flame_oxygen/lib/src/component/flip_component.dart b/packages/flame_oxygen/lib/src/component/flip_component.dart new file mode 100644 index 000000000..72d9942ef --- /dev/null +++ b/packages/flame_oxygen/lib/src/component/flip_component.dart @@ -0,0 +1,30 @@ +import 'package:oxygen/oxygen.dart'; + +class FlipInit { + final bool flipX; + + final bool flipY; + + FlipInit({ + this.flipX = false, + this.flipY = false, + }); +} + +class FlipComponent extends Component { + late bool flipX; + + late bool flipY; + + @override + void init([FlipInit? initValue]) { + flipX = initValue?.flipX ?? false; + flipY = initValue?.flipY ?? false; + } + + @override + void reset() { + flipX = false; + flipY = false; + } +} diff --git a/packages/flame_oxygen/lib/src/component/particle_component.dart b/packages/flame_oxygen/lib/src/component/particle_component.dart new file mode 100644 index 000000000..a35cd4986 --- /dev/null +++ b/packages/flame_oxygen/lib/src/component/particle_component.dart @@ -0,0 +1,17 @@ +import 'package:flame/particles.dart'; +import 'package:oxygen/oxygen.dart'; + +export 'package:flame/particles.dart' show Particle; + +class ParticleComponent extends Component { + Particle? particle; + + /// Returns progress of the [particle]. + double? get progress => particle?.progress; + + @override + void init([Particle? data]) => particle = data; + + @override + void reset() => particle = null; +} diff --git a/packages/flame_oxygen/lib/src/component/position_component.dart b/packages/flame_oxygen/lib/src/component/position_component.dart new file mode 100644 index 000000000..cd679f158 --- /dev/null +++ b/packages/flame_oxygen/lib/src/component/position_component.dart @@ -0,0 +1,23 @@ +import 'package:flame/extensions.dart'; +import 'package:oxygen/oxygen.dart'; + +class PositionComponent extends Component { + late Vector2 _position; + + Vector2 get position => _position; + set position(Vector2 position) => _position.setFrom(position); + + double get x => _position.x; + set x(double x) => _position.x = x; + + double get y => _position.y; + set y(double y) => _position.y = y; + + @override + void init([Vector2? position]) { + _position = position?.clone() ?? Vector2.zero(); + } + + @override + void reset() => _position.setZero(); +} diff --git a/packages/flame_oxygen/lib/src/component/size_component.dart b/packages/flame_oxygen/lib/src/component/size_component.dart new file mode 100644 index 000000000..041966c7d --- /dev/null +++ b/packages/flame_oxygen/lib/src/component/size_component.dart @@ -0,0 +1,21 @@ +import 'package:flame/extensions.dart'; +import 'package:oxygen/oxygen.dart'; + +class SizeComponent extends Component { + late Vector2 _size; + + Vector2 get size => _size; + set size(Vector2 position) => _size.setFrom(position); + + double get width => _size.x; + set width(double x) => _size.x = width; + + double get height => _size.y; + set height(double height) => _size.y = height; + + @override + void init([Vector2? size]) => _size = size?.clone() ?? Vector2.zero(); + + @override + void reset() => _size.setZero(); +} diff --git a/packages/flame_oxygen/lib/src/component/sprite_component.dart b/packages/flame_oxygen/lib/src/component/sprite_component.dart new file mode 100644 index 000000000..b001e81de --- /dev/null +++ b/packages/flame_oxygen/lib/src/component/sprite_component.dart @@ -0,0 +1,33 @@ +import 'package:flame/extensions.dart'; +import 'package:flame/sprite.dart'; +import 'package:oxygen/oxygen.dart'; + +export 'package:flame/sprite.dart'; + +class SpriteInit { + final Sprite sprite; + + const SpriteInit(this.sprite); + + factory SpriteInit.fromImage( + Image image, { + Vector2? srcPosition, + Vector2? srcSize, + }) { + return SpriteInit(Sprite( + image, + srcPosition: srcPosition, + srcSize: srcSize, + )); + } +} + +class SpriteComponent extends Component { + Sprite? sprite; + + @override + void init([SpriteInit? initValue]) => sprite = initValue?.sprite; + + @override + void reset() => sprite = null; +} diff --git a/packages/flame_oxygen/lib/src/component/text_component.dart b/packages/flame_oxygen/lib/src/component/text_component.dart new file mode 100644 index 000000000..4c6aac4a5 --- /dev/null +++ b/packages/flame_oxygen/lib/src/component/text_component.dart @@ -0,0 +1,31 @@ +import 'package:flame/game.dart'; +import 'package:oxygen/oxygen.dart'; + +class TextInit { + final String text; + + TextPaintConfig? config; + + TextInit( + this.text, { + this.config, + }); +} + +class TextComponent extends Component { + late String text; + + late TextPaintConfig config; + + @override + void init([TextInit? initValue]) { + config = initValue?.config ?? const TextPaintConfig(); + text = initValue?.text ?? ''; + } + + @override + void reset() { + config = const TextPaintConfig(); + text = ''; + } +} diff --git a/packages/flame_oxygen/lib/src/flame_system_manager.dart b/packages/flame_oxygen/lib/src/flame_system_manager.dart new file mode 100644 index 000000000..a9dc13926 --- /dev/null +++ b/packages/flame_oxygen/lib/src/flame_system_manager.dart @@ -0,0 +1,12 @@ +import 'package:oxygen/oxygen.dart'; + +import 'system.dart'; + +/// Extension class for adding Flame specific system filters. +extension FlameSystemManager on SystemManager { + /// List of all systems that can render. + Iterable get renderSystems => systems.whereType(); + + /// List of all systems that can update. + Iterable get updateSystems => systems.whereType(); +} diff --git a/packages/flame_oxygen/lib/src/flame_world.dart b/packages/flame_oxygen/lib/src/flame_world.dart new file mode 100644 index 000000000..790a7c222 --- /dev/null +++ b/packages/flame_oxygen/lib/src/flame_world.dart @@ -0,0 +1,33 @@ +import 'package:flame/extensions.dart'; +import 'package:oxygen/oxygen.dart'; + +import 'flame_system_manager.dart'; +import 'oxygen_game.dart'; +import 'system.dart'; + +class FlameWorld extends World { + /// The game this world belongs to. + final OxygenGame game; + + FlameWorld(this.game) : super(); + + /// Render all the [RenderSystem]s. + void render(Canvas canvas) { + for (final system in systemManager.renderSystems) { + system.render(canvas); + } + } + + /// Render all the [UpdateSystem]s. + void update(double delta) { + for (final system in systemManager.updateSystems) { + system.update(delta); + } + entityManager.processRemovedEntities(); + } + + @override + void execute(double delta) => throw Exception( + 'FlameWorld.execute is not supported in flame_oxygen', + ); +} diff --git a/packages/flame_oxygen/lib/src/oxygen_game.dart b/packages/flame_oxygen/lib/src/oxygen_game.dart new file mode 100644 index 000000000..53bfb9714 --- /dev/null +++ b/packages/flame_oxygen/lib/src/oxygen_game.dart @@ -0,0 +1,70 @@ +import 'package:flame/extensions.dart'; +import 'package:flame/game.dart'; +import 'package:flutter/material.dart'; +import 'package:oxygen/oxygen.dart'; + +import 'component.dart'; +import 'flame_world.dart'; + +/// This is an Oxygen based implementation of [Game]. +/// +/// [OxygenGame] should be extended to add your own game logic. +/// +/// It is based on the Oxygen package. +abstract class OxygenGame extends Game { + late final FlameWorld world; + + OxygenGame() { + world = FlameWorld(this); + } + + /// Create a new [Entity]. + Entity createEntity({ + String? name, + required Vector2 position, + required Vector2 size, + double? angle, + Anchor? anchor, + bool flipX = false, + bool flipY = false, + }) { + final entity = world.entityManager.createEntity(name) + ..add(position) + ..add(size) + ..add(anchor) + ..add(angle) + ..add(FlipInit(flipX: flipX, flipY: flipY)); + return entity; + } + + @override + @mustCallSuper + Future onLoad() async { + // Registering default components. + world.registerComponent(() => SizeComponent()); + world.registerComponent( + () => PositionComponent(), + ); + world.registerComponent(() => AngleComponent()); + world.registerComponent(() => AnchorComponent()); + world.registerComponent( + () => SpriteComponent(), + ); + world.registerComponent(() => TextComponent()); + world.registerComponent(() => FlipComponent()); + + await init(); + world.init(); + } + + /// Initialize the game and world. + Future init(); + + @override + @mustCallSuper + void render(Canvas canvas) => world.render(canvas); + + @override + @mustCallSuper + void update(double delta) => world.update(delta); +} diff --git a/packages/flame_oxygen/lib/src/system.dart b/packages/flame_oxygen/lib/src/system.dart new file mode 100644 index 000000000..e17b71c29 --- /dev/null +++ b/packages/flame_oxygen/lib/src/system.dart @@ -0,0 +1,5 @@ +export 'system/base_system.dart'; +export 'system/game_ref.dart'; +export 'system/particle_system.dart'; +export 'system/render_system.dart'; +export 'system/update_system.dart'; diff --git a/packages/flame_oxygen/lib/src/system/base_system.dart b/packages/flame_oxygen/lib/src/system/base_system.dart new file mode 100644 index 000000000..f201afb82 --- /dev/null +++ b/packages/flame_oxygen/lib/src/system/base_system.dart @@ -0,0 +1,71 @@ +import 'package:flutter/material.dart'; +import 'package:oxygen/oxygen.dart'; + +import '../component.dart'; +import '../system.dart'; + +/// System that provides base rendering for default components. +/// +/// Based on the PositionComponent logic from Flame. +abstract class BaseSystem extends System with RenderSystem { + Query? _query; + + /// List of all the entities found using the [filters]. + List get entities => _query?.entities ?? []; + + /// Filters used for querying entities. + /// + /// The [PositionComponent] and [SizeComponent] will be added automatically. + List> get filters; + + @override + @mustCallSuper + void init() { + _query = createQuery([ + Has(), + Has(), + ...filters, + ]); + } + + @override + void dispose() { + _query = null; + super.dispose(); + } + + @override + void render(Canvas canvas) { + for (final entity in entities) { + final position = entity.get()!; + final size = entity.get()!.size; + final anchor = entity.get()?.anchor ?? Anchor.topLeft; + final angle = entity.get()?.radians ?? 0; + final flip = entity.get(); + + canvas + ..save() + ..translate(position.x, position.y) + ..rotate(angle); + + final delta = -anchor.toVector2() + ..multiply(size); + canvas.translate(delta.x, delta.y); + + // Handle inverted rendering by moving center and flipping. + if (flip != null && (flip.flipX || flip.flipY)) { + canvas.translate(size.x / 2, size.y / 2); + canvas.scale(flip.flipX ? -1.0 : 1.0, flip.flipY ? -1.0 : 1.0); + canvas.translate(-size.x / 2, -size.y / 2); + } + renderEntity(canvas, entity); + + canvas.restore(); + } + } + + /// Render given entity. + /// + /// The canvas is already prepared for this entity. + void renderEntity(Canvas canvas, Entity entity); +} diff --git a/packages/flame_oxygen/lib/src/system/game_ref.dart b/packages/flame_oxygen/lib/src/system/game_ref.dart new file mode 100644 index 000000000..4db092be8 --- /dev/null +++ b/packages/flame_oxygen/lib/src/system/game_ref.dart @@ -0,0 +1,13 @@ +import 'package:oxygen/oxygen.dart'; + +import '../flame_world.dart'; +import '../oxygen_game.dart'; + +mixin GameRef on System { + /// The world this system belongs to. + @override + FlameWorld? get world => super.world as FlameWorld?; + + /// The [T] this system belongs to. + T? get game => world?.game as T?; +} diff --git a/packages/flame_oxygen/lib/src/system/particle_system.dart b/packages/flame_oxygen/lib/src/system/particle_system.dart new file mode 100644 index 000000000..151ee9285 --- /dev/null +++ b/packages/flame_oxygen/lib/src/system/particle_system.dart @@ -0,0 +1,36 @@ +import 'package:flame/extensions.dart'; +import 'package:oxygen/oxygen.dart'; + +import '../component.dart'; +import 'render_system.dart'; +import 'update_system.dart'; + +/// Allows Particles from Flame to be rendered. +class ParticleSystem extends System with RenderSystem, UpdateSystem { + Query? _query; + + @override + void init() => _query = createQuery([Has()]); + + @override + void dispose() { + _query = null; + super.dispose(); + } + + @override + void render(Canvas canvas) { + for (final entity in _query?.entities ?? []) { + final particle = entity.get()!.particle; + particle?.render(canvas); + } + } + + @override + void update(double delta) { + for (final entity in _query?.entities ?? []) { + final particle = entity.get()!.particle; + particle?.update(delta); + } + } +} diff --git a/packages/flame_oxygen/lib/src/system/render_system.dart b/packages/flame_oxygen/lib/src/system/render_system.dart new file mode 100644 index 000000000..4494b1317 --- /dev/null +++ b/packages/flame_oxygen/lib/src/system/render_system.dart @@ -0,0 +1,19 @@ +import 'package:flame/extensions.dart'; +import 'package:oxygen/oxygen.dart'; + +import '../flame_world.dart'; + +/// Allow a [System] to be part of the render loop from Flame. +mixin RenderSystem on System { + /// The world this system belongs to. + @override + FlameWorld? get world => super.world as FlameWorld?; + + /// Implement this method to render the current game state in the [canvas]. + void render(Canvas canvas); + + @override + void execute(double delta) { + throw Exception('RenderSystem.execute is not supported in flame_oxygen'); + } +} diff --git a/packages/flame_oxygen/lib/src/system/update_system.dart b/packages/flame_oxygen/lib/src/system/update_system.dart new file mode 100644 index 000000000..397b40398 --- /dev/null +++ b/packages/flame_oxygen/lib/src/system/update_system.dart @@ -0,0 +1,19 @@ +import 'package:oxygen/oxygen.dart'; + +import '../flame_world.dart'; + +/// Allow a [System] to be part of the update loop from Flame. +mixin UpdateSystem on System { + /// The world this system belongs to. + @override + FlameWorld? get world => super.world as FlameWorld?; + + /// Implement this method to update the game state, given the time [delta] + /// that has passed since the last update. + void update(double delta); + + @override + void execute(double delta) { + throw Exception('UpdateSystem.execute is not supported in flame_oxygen'); + } +} diff --git a/packages/flame_oxygen/pubspec.yaml b/packages/flame_oxygen/pubspec.yaml new file mode 100644 index 000000000..78b30923b --- /dev/null +++ b/packages/flame_oxygen/pubspec.yaml @@ -0,0 +1,22 @@ +name: flame_oxygen +description: Integrate the Oxygen ECS with the Flame Engine. +version: 0.1.0 +homepage: https://github.com/flame-engine/flame/tree/main/packages/flame_oxygen +environment: + sdk: ">=2.12.0 <3.0.0" + flutter: ">=1.17.0" +publish_to: 'none' + +dependencies: + flutter: + sdk: flutter + flame: + path: ../flame + oxygen: ^0.2.0 + +dev_dependencies: + dart_code_metrics: ^3.2.2 + dartdoc: ^0.42.0 + flutter_test: + sdk: flutter + test: ^1.9.4