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 000000000..7e7d3da5c Binary files /dev/null and b/packages/flame_oxygen/example/assets/images/chopper.png differ diff --git a/packages/flame_oxygen/example/assets/images/pizza.png b/packages/flame_oxygen/example/assets/images/pizza.png new file mode 100644 index 000000000..1e6a7aa54 Binary files /dev/null and b/packages/flame_oxygen/example/assets/images/pizza.png differ 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 000000000..8aaa46ac1 Binary files /dev/null and b/packages/flame_oxygen/example/web/favicon.png differ 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 000000000..b749bfef0 Binary files /dev/null and b/packages/flame_oxygen/example/web/icons/Icon-192.png differ 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 000000000..88cfd48df Binary files /dev/null and b/packages/flame_oxygen/example/web/icons/Icon-512.png differ 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