Repository: spritewidget/spaceblast Branch: master Commit: de3584cab5f2 Files: 33 Total size: 114.5 KB Directory structure: gitextract_xpul06_z/ ├── .flutter-plugins-dependencies ├── .gitignore ├── .metadata ├── LICENSE ├── README.md ├── analysis_options.yaml ├── assets/ │ ├── game_ui.json │ ├── sprites.json │ └── temp_music.aac ├── build_web ├── fonts/ │ └── OFL.txt ├── lib/ │ ├── coordinate_system.dart │ ├── custom_actions.dart │ ├── explosions.dart │ ├── flash.dart │ ├── game_demo.dart │ ├── game_demo_node.dart │ ├── game_object_factory.dart │ ├── game_objects.dart │ ├── generated_plugin_registrant.dart │ ├── main.dart │ ├── persistant_game_state.dart │ ├── player_state.dart │ ├── power_bar.dart │ ├── render_coordinate_system.dart │ ├── repeated_image.dart │ ├── sound_assets.dart │ ├── star_field.dart │ └── widgets.dart ├── pubspec.yaml └── web/ ├── index.html ├── manifest.json └── styles.css ================================================ FILE CONTENTS ================================================ ================================================ FILE: .flutter-plugins-dependencies ================================================ {"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"audio_session","path":"/Users/viktor/Projects/flutter/.pub-cache/hosted/pub.dartlang.org/audio_session-0.1.6+1/","dependencies":[]},{"name":"just_audio","path":"/Users/viktor/Projects/flutter/.pub-cache/hosted/pub.dartlang.org/just_audio-0.9.20/","dependencies":["audio_session"]},{"name":"path_provider_ios","path":"/Users/viktor/Projects/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider_ios-2.0.8/","dependencies":[]},{"name":"shared_preferences_ios","path":"/Users/viktor/Projects/flutter/.pub-cache/hosted/pub.dartlang.org/shared_preferences_ios-2.1.0/","dependencies":[]}],"android":[{"name":"audio_session","path":"/Users/viktor/Projects/flutter/.pub-cache/hosted/pub.dartlang.org/audio_session-0.1.6+1/","dependencies":[]},{"name":"just_audio","path":"/Users/viktor/Projects/flutter/.pub-cache/hosted/pub.dartlang.org/just_audio-0.9.20/","dependencies":["audio_session"]},{"name":"path_provider_android","path":"/Users/viktor/Projects/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider_android-2.0.12/","dependencies":[]},{"name":"shared_preferences_android","path":"/Users/viktor/Projects/flutter/.pub-cache/hosted/pub.dartlang.org/shared_preferences_android-2.0.11/","dependencies":[]}],"macos":[{"name":"audio_session","path":"/Users/viktor/Projects/flutter/.pub-cache/hosted/pub.dartlang.org/audio_session-0.1.6+1/","dependencies":[]},{"name":"just_audio","path":"/Users/viktor/Projects/flutter/.pub-cache/hosted/pub.dartlang.org/just_audio-0.9.20/","dependencies":["audio_session"]},{"name":"path_provider_macos","path":"/Users/viktor/Projects/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider_macos-2.0.5/","dependencies":[]},{"name":"shared_preferences_macos","path":"/Users/viktor/Projects/flutter/.pub-cache/hosted/pub.dartlang.org/shared_preferences_macos-2.0.3/","dependencies":[]}],"linux":[{"name":"path_provider_linux","path":"/Users/viktor/Projects/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider_linux-2.1.5/","dependencies":[]},{"name":"shared_preferences_linux","path":"/Users/viktor/Projects/flutter/.pub-cache/hosted/pub.dartlang.org/shared_preferences_linux-2.1.0/","dependencies":["path_provider_linux"]}],"windows":[{"name":"path_provider_windows","path":"/Users/viktor/Projects/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider_windows-2.0.5/","dependencies":[]},{"name":"shared_preferences_windows","path":"/Users/viktor/Projects/flutter/.pub-cache/hosted/pub.dartlang.org/shared_preferences_windows-2.1.0/","dependencies":["path_provider_windows"]}],"web":[{"name":"audio_session","path":"/Users/viktor/Projects/flutter/.pub-cache/hosted/pub.dartlang.org/audio_session-0.1.6+1/","dependencies":[]},{"name":"just_audio_web","path":"/Users/viktor/Projects/flutter/.pub-cache/hosted/pub.dartlang.org/just_audio_web-0.4.7/","dependencies":[]},{"name":"shared_preferences_web","path":"/Users/viktor/Projects/flutter/.pub-cache/hosted/pub.dartlang.org/shared_preferences_web-2.0.3/","dependencies":[]}]},"dependencyGraph":[{"name":"audio_session","dependencies":[]},{"name":"just_audio","dependencies":["just_audio_web","audio_session","path_provider"]},{"name":"just_audio_web","dependencies":[]},{"name":"path_provider","dependencies":["path_provider_android","path_provider_ios","path_provider_linux","path_provider_macos","path_provider_windows"]},{"name":"path_provider_android","dependencies":[]},{"name":"path_provider_ios","dependencies":[]},{"name":"path_provider_linux","dependencies":[]},{"name":"path_provider_macos","dependencies":[]},{"name":"path_provider_windows","dependencies":[]},{"name":"shared_preferences","dependencies":["shared_preferences_android","shared_preferences_ios","shared_preferences_linux","shared_preferences_macos","shared_preferences_web","shared_preferences_windows"]},{"name":"shared_preferences_android","dependencies":[]},{"name":"shared_preferences_ios","dependencies":[]},{"name":"shared_preferences_linux","dependencies":["path_provider_linux"]},{"name":"shared_preferences_macos","dependencies":[]},{"name":"shared_preferences_web","dependencies":[]},{"name":"shared_preferences_windows","dependencies":["path_provider_windows"]}],"date_created":"2022-04-24 14:11:37.796311","version":"2.10.4"} ================================================ FILE: .gitignore ================================================ # Miscellaneous *.class *.lock *.log *.pyc *.swp .DS_Store .atom/ .buildlog/ .history .svn/ # IntelliJ related *.iml *.ipr *.iws .idea/ # Visual Studio Code related .vscode/ # Flutter/Dart/Pub related **/doc/api/ .dart_tool/ .flutter-plugins .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/Generated.xcconfig **/ios/Flutter/app.flx **/ios/Flutter/app.zip **/ios/Flutter/flutter_assets/ **/ios/ServiceDefinitions.json **/ios/Runner/GeneratedPluginRegistrant.* # Exceptions to above rules. !**/ios/**/default.mode1v3 !**/ios/**/default.mode2v3 !**/ios/**/default.pbxuser !**/ios/**/default.perspectivev3 !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages ================================================ FILE: .metadata ================================================ # 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: 5391447fae6209bb21a89e6a5a6583cac1af9b4b channel: stable project_type: app ================================================ FILE: LICENSE ================================================ // Copyright 2022 The SpriteWidget Authors. All rights reserved. // Copyright 2014 The Chromium Authors. All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following disclaimer // in the documentation and/or other materials provided with the // distribution. // * Neither the name of Google Inc. nor the names of its // contributors may be used to endorse or promote products derived from // this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ================================================ FILE: README.md ================================================ # SpaceBlast A demo game for [SpriteWidget](https://spritewidget.com). Showcases most features of SpriteWidget. ## Getting Started Try it out online [here](https://spritewidget.com/spaceblast) or clone it and run it like you run any Flutter app: flutter create . flutter run ================================================ FILE: analysis_options.yaml ================================================ # This file configures the analyzer, which statically analyzes Dart code to # check for errors, warnings, and lints. # # The issues identified by the analyzer are surfaced in the UI of Dart-enabled # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be # invoked from the command line by running `flutter analyze`. # The following line activates a set of recommended lints for Flutter apps, # packages, and plugins designed to encourage good coding practices. include: package:flutter_lints/flutter.yaml linter: # The lint rules applied to this project can be customized in the # section below to disable rules from the `package:flutter_lints/flutter.yaml` # included above or to enable additional rules. A list of all available lints # and their documentation is published at # https://dart-lang.github.io/linter/lints/index.html. # # Instead of disabling a lint rule for the entire project in the # section below, it can also be suppressed for a single line of code # or a specific dart file by using the `// ignore: name_of_lint` and # `// ignore_for_file: name_of_lint` syntax on the line or in the file # producing the lint. rules: # avoid_print: false # Uncomment to disable the `avoid_print` rule # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule # Additional information about this file can be found at # https://dart.dev/guides/language/analysis-options ================================================ FILE: assets/game_ui.json ================================================ {"frames": [ { "filename": "badge.png", "frame": {"x":1948,"y":619,"w":62,"h":62}, "rotated": false, "trimmed": false, "spriteSourceSize": {"x":0,"y":0,"w":62,"h":62}, "sourceSize": {"w":62,"h":62}, "pivot": {"x":0.5,"y":0.5} }, { "filename": "bar_shield.png", "frame": {"x":769,"y":2,"w":412,"h":100}, "rotated": true, "trimmed": false, "spriteSourceSize": {"x":0,"y":0,"w":412,"h":100}, "sourceSize": {"w":412,"h":100}, "pivot": {"x":0.5,"y":0.5} }, { "filename": "bar_shield_fill.png", "frame": {"x":1965,"y":297,"w":320,"h":72}, "rotated": true, "trimmed": false, "spriteSourceSize": {"x":0,"y":0,"w":320,"h":72}, "sourceSize": {"w":320,"h":72}, "pivot": {"x":0.5,"y":0.5} }, { "filename": "btn_close.png", "frame": {"x":1788,"y":297,"w":175,"h":175}, "rotated": false, "trimmed": false, "spriteSourceSize": {"x":0,"y":0,"w":175,"h":175}, "sourceSize": {"w":175,"h":175}, "pivot": {"x":0.5,"y":0.5} }, { "filename": "btn_laser_upgrade.png", "frame": {"x":2,"y":2,"w":652,"h":290}, "rotated": true, "trimmed": false, "spriteSourceSize": {"x":0,"y":0,"w":652,"h":290}, "sourceSize": {"w":652,"h":290}, "pivot": {"x":0.5,"y":0.5} }, { "filename": "btn_level_down.png", "frame": {"x":1032,"y":150,"w":145,"h":145}, "rotated": false, "trimmed": false, "spriteSourceSize": {"x":0,"y":0,"w":145,"h":145}, "sourceSize": {"w":145,"h":145}, "pivot": {"x":0.5,"y":0.5} }, { "filename": "btn_level_up.png", "frame": {"x":1032,"y":2,"w":146,"h":145}, "rotated": true, "trimmed": false, "spriteSourceSize": {"x":0,"y":0,"w":146,"h":145}, "sourceSize": {"w":146,"h":145}, "pivot": {"x":0.5,"y":0.5} }, { "filename": "btn_play.png", "frame": {"x":1179,"y":2,"w":860,"h":293}, "rotated": false, "trimmed": false, "spriteSourceSize": {"x":0,"y":0,"w":860,"h":293}, "sourceSize": {"w":860,"h":293}, "pivot": {"x":0.5,"y":0.5} }, { "filename": "btn_powerup_0.png", "frame": {"x":360,"y":520,"w":259,"h":271}, "rotated": true, "trimmed": false, "spriteSourceSize": {"x":0,"y":0,"w":259,"h":271}, "sourceSize": {"w":259,"h":271}, "pivot": {"x":0.5,"y":0.5} }, { "filename": "btn_powerup_1.png", "frame": {"x":633,"y":520,"w":259,"h":271}, "rotated": true, "trimmed": false, "spriteSourceSize": {"x":0,"y":0,"w":259,"h":271}, "sourceSize": {"w":259,"h":271}, "pivot": {"x":0.5,"y":0.5} }, { "filename": "btn_powerup_2.png", "frame": {"x":906,"y":518,"w":259,"h":271}, "rotated": true, "trimmed": false, "spriteSourceSize": {"x":0,"y":0,"w":259,"h":271}, "sourceSize": {"w":259,"h":271}, "pivot": {"x":0.5,"y":0.5} }, { "filename": "btn_powerup_3.png", "frame": {"x":1179,"y":516,"w":259,"h":271}, "rotated": true, "trimmed": false, "spriteSourceSize": {"x":0,"y":0,"w":259,"h":271}, "sourceSize": {"w":259,"h":271}, "pivot": {"x":0.5,"y":0.5} }, { "filename": "coinboard.png", "frame": {"x":769,"y":416,"w":261,"h":100}, "rotated": false, "trimmed": false, "spriteSourceSize": {"x":0,"y":0,"w":261,"h":100}, "sourceSize": {"w":261,"h":100}, "pivot": {"x":0.5,"y":0.5} }, { "filename": "icn_crystal.png", "frame": {"x":1452,"y":516,"w":48,"h":81}, "rotated": false, "trimmed": false, "spriteSourceSize": {"x":0,"y":0,"w":48,"h":81}, "sourceSize": {"w":48,"h":81}, "pivot": {"x":0.5,"y":0.5} }, { "filename": "icn_medal.png", "frame": {"x":294,"y":2,"w":473,"h":516}, "rotated": false, "trimmed": false, "spriteSourceSize": {"x":0,"y":0,"w":473,"h":516}, "sourceSize": {"w":473,"h":516}, "pivot": {"x":0.5,"y":0.5} }, { "filename": "level_display.png", "frame": {"x":1505,"y":476,"w":294,"h":293}, "rotated": true, "trimmed": false, "spriteSourceSize": {"x":0,"y":0,"w":294,"h":293}, "sourceSize": {"w":294,"h":293}, "pivot": {"x":0.5,"y":0.5} }, { "filename": "level_display_0.png", "frame": {"x":2,"y":656,"w":107,"h":177}, "rotated": true, "trimmed": true, "spriteSourceSize": {"x":94,"y":60,"w":107,"h":177}, "sourceSize": {"w":294,"h":293}, "pivot": {"x":0.5,"y":0.5} }, { "filename": "level_display_1.png", "frame": {"x":1800,"y":619,"w":45,"h":160}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":156,"y":71,"w":45,"h":160}, "sourceSize": {"w":294,"h":293}, "pivot": {"x":0.5,"y":0.5} }, { "filename": "level_display_2.png", "frame": {"x":181,"y":656,"w":107,"h":177}, "rotated": true, "trimmed": true, "spriteSourceSize": {"x":94,"y":60,"w":107,"h":177}, "sourceSize": {"w":294,"h":293}, "pivot": {"x":0.5,"y":0.5} }, { "filename": "level_display_3.png", "frame": {"x":1687,"y":297,"w":99,"h":177}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":102,"y":60,"w":99,"h":177}, "sourceSize": {"w":294,"h":293}, "pivot": {"x":0.5,"y":0.5} }, { "filename": "level_display_4.png", "frame": {"x":1800,"y":474,"w":107,"h":161}, "rotated": true, "trimmed": true, "spriteSourceSize": {"x":94,"y":68,"w":107,"h":161}, "sourceSize": {"w":294,"h":293}, "pivot": {"x":0.5,"y":0.5} }, { "filename": "level_display_5.png", "frame": {"x":1251,"y":297,"w":107,"h":177}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":94,"y":60,"w":107,"h":177}, "sourceSize": {"w":294,"h":293}, "pivot": {"x":0.5,"y":0.5} }, { "filename": "level_display_6.png", "frame": {"x":1360,"y":297,"w":107,"h":177}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":94,"y":60,"w":107,"h":177}, "sourceSize": {"w":294,"h":293}, "pivot": {"x":0.5,"y":0.5} }, { "filename": "level_display_7.png", "frame": {"x":1847,"y":583,"w":99,"h":169}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":102,"y":60,"w":99,"h":169}, "sourceSize": {"w":294,"h":293}, "pivot": {"x":0.5,"y":0.5} }, { "filename": "level_display_8.png", "frame": {"x":1469,"y":297,"w":107,"h":177}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":94,"y":60,"w":107,"h":177}, "sourceSize": {"w":294,"h":293}, "pivot": {"x":0.5,"y":0.5} }, { "filename": "level_display_9.png", "frame": {"x":1578,"y":297,"w":107,"h":177}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":94,"y":60,"w":107,"h":177}, "sourceSize": {"w":294,"h":293}, "pivot": {"x":0.5,"y":0.5} }, { "filename": "number_0.png", "frame": {"x":294,"y":520,"w":38,"h":63}, "rotated": true, "trimmed": true, "spriteSourceSize": {"x":23,"y":22,"w":38,"h":63}, "sourceSize": {"w":84,"h":107}, "pivot": {"x":0.5,"y":0.5} }, { "filename": "number_1.png", "frame": {"x":1847,"y":754,"w":16,"h":57}, "rotated": true, "trimmed": true, "spriteSourceSize": {"x":45,"y":25,"w":16,"h":57}, "sourceSize": {"w":84,"h":107}, "pivot": {"x":0.5,"y":0.5} }, { "filename": "number_2.png", "frame": {"x":294,"y":560,"w":38,"h":63}, "rotated": true, "trimmed": true, "spriteSourceSize": {"x":23,"y":22,"w":38,"h":63}, "sourceSize": {"w":84,"h":107}, "pivot": {"x":0.5,"y":0.5} }, { "filename": "number_3.png", "frame": {"x":1452,"y":599,"w":35,"h":63}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":26,"y":22,"w":35,"h":63}, "sourceSize": {"w":84,"h":107}, "pivot": {"x":0.5,"y":0.5} }, { "filename": "number_4.png", "frame": {"x":1446,"y":476,"w":38,"h":57}, "rotated": true, "trimmed": true, "spriteSourceSize": {"x":23,"y":25,"w":38,"h":57}, "sourceSize": {"w":84,"h":107}, "pivot": {"x":0.5,"y":0.5} }, { "filename": "number_5.png", "frame": {"x":294,"y":600,"w":38,"h":63}, "rotated": true, "trimmed": true, "spriteSourceSize": {"x":23,"y":22,"w":38,"h":63}, "sourceSize": {"w":84,"h":107}, "pivot": {"x":0.5,"y":0.5} }, { "filename": "number_6.png", "frame": {"x":1251,"y":476,"w":38,"h":63}, "rotated": true, "trimmed": true, "spriteSourceSize": {"x":23,"y":22,"w":38,"h":63}, "sourceSize": {"w":84,"h":107}, "pivot": {"x":0.5,"y":0.5} }, { "filename": "number_7.png", "frame": {"x":1452,"y":664,"w":35,"h":60}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":26,"y":22,"w":35,"h":60}, "sourceSize": {"w":84,"h":107}, "pivot": {"x":0.5,"y":0.5} }, { "filename": "number_8.png", "frame": {"x":1316,"y":476,"w":38,"h":63}, "rotated": true, "trimmed": true, "spriteSourceSize": {"x":23,"y":22,"w":38,"h":63}, "sourceSize": {"w":84,"h":107}, "pivot": {"x":0.5,"y":0.5} }, { "filename": "number_9.png", "frame": {"x":1381,"y":476,"w":38,"h":63}, "rotated": true, "trimmed": true, "spriteSourceSize": {"x":23,"y":22,"w":38,"h":63}, "sourceSize": {"w":84,"h":107}, "pivot": {"x":0.5,"y":0.5} }, { "filename": "player_icon.png", "frame": {"x":1032,"y":297,"w":217,"h":217}, "rotated": false, "trimmed": false, "spriteSourceSize": {"x":0,"y":0,"w":217,"h":217}, "sourceSize": {"w":217,"h":217}, "pivot": {"x":0.5,"y":0.5} }, { "filename": "scoreboard.png", "frame": {"x":871,"y":2,"w":362,"h":98}, "rotated": true, "trimmed": false, "spriteSourceSize": {"x":0,"y":0,"w":362,"h":98}, "sourceSize": {"w":362,"h":98}, "pivot": {"x":0.5,"y":0.5} }], "meta": { "app": "http://www.codeandweb.com/texturepacker", "version": "1.0", "image": "game_ui.png", "format": "RGBA8888", "size": {"w":2041,"h":781}, "scale": "1", "smartupdate": "$TexturePacker:SmartUpdate:8cb2693ee295d1eeae391efd7de6f156:d356cc9506480f7ffb79175a765b156f:10ac111e32c27e51f4e8444dbb39eff6$" } } ================================================ FILE: assets/sprites.json ================================================ {"frames": [ { "filename": "asteroid_big_0.nrm.png", "frame": {"x":245,"y":799,"w":200,"h":188}, "rotated": false, "trimmed": false, "spriteSourceSize": {"x":0,"y":0,"w":200,"h":188}, "sourceSize": {"w":200,"h":188}, "pivot": {"x":0.5,"y":0.5} }, { "filename": "asteroid_big_0.png", "frame": {"x":706,"y":402,"w":200,"h":167}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":0,"y":21,"w":200,"h":167}, "sourceSize": {"w":200,"h":188}, "pivot": {"x":0.5,"y":0.5} }, { "filename": "asteroid_big_1.nrm.png", "frame": {"x":305,"y":631,"w":204,"h":166}, "rotated": false, "trimmed": false, "spriteSourceSize": {"x":0,"y":0,"w":204,"h":166}, "sourceSize": {"w":204,"h":166}, "pivot": {"x":0.5,"y":0.5} }, { "filename": "asteroid_big_1.png", "frame": {"x":706,"y":571,"w":204,"h":166}, "rotated": false, "trimmed": false, "spriteSourceSize": {"x":0,"y":0,"w":204,"h":166}, "sourceSize": {"w":204,"h":166}, "pivot": {"x":0.5,"y":0.5} }, { "filename": "asteroid_big_2.nrm.png", "frame": {"x":912,"y":571,"w":194,"h":165}, "rotated": false, "trimmed": false, "spriteSourceSize": {"x":0,"y":0,"w":194,"h":165}, "sourceSize": {"w":194,"h":165}, "pivot": {"x":0.5,"y":0.5} }, { "filename": "asteroid_big_2.png", "frame": {"x":912,"y":738,"w":194,"h":165}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":0,"y":2,"w":194,"h":165}, "sourceSize": {"w":194,"h":167}, "pivot": {"x":0.5,"y":0.5} }, { "filename": "asteroid_small_0.nrm.png", "frame": {"x":701,"y":881,"w":102,"h":84}, "rotated": false, "trimmed": false, "spriteSourceSize": {"x":0,"y":0,"w":102,"h":84}, "sourceSize": {"w":102,"h":84}, "pivot": {"x":0.5,"y":0.5} }, { "filename": "asteroid_small_0.png", "frame": {"x":805,"y":867,"w":102,"h":84}, "rotated": false, "trimmed": false, "spriteSourceSize": {"x":0,"y":0,"w":102,"h":84}, "sourceSize": {"w":102,"h":84}, "pivot": {"x":0.5,"y":0.5} }, { "filename": "asteroid_small_1.nrm.png", "frame": {"x":1108,"y":555,"w":96,"h":102}, "rotated": false, "trimmed": false, "spriteSourceSize": {"x":0,"y":0,"w":96,"h":102}, "sourceSize": {"w":96,"h":102}, "pivot": {"x":0.5,"y":0.5} }, { "filename": "asteroid_small_1.png", "frame": {"x":1108,"y":659,"w":96,"h":102}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":0,"y":0,"w":96,"h":102}, "sourceSize": {"w":96,"h":106}, "pivot": {"x":0.5,"y":0.5} }, { "filename": "asteroid_small_2.nrm.png", "frame": {"x":909,"y":905,"w":109,"h":84}, "rotated": false, "trimmed": false, "spriteSourceSize": {"x":0,"y":0,"w":109,"h":84}, "sourceSize": {"w":109,"h":84}, "pivot": {"x":0.5,"y":0.5} }, { "filename": "asteroid_small_2.png", "frame": {"x":1020,"y":905,"w":109,"h":84}, "rotated": false, "trimmed": false, "spriteSourceSize": {"x":0,"y":0,"w":109,"h":84}, "sourceSize": {"w":109,"h":84}, "pivot": {"x":0.5,"y":0.5} }, { "filename": "coin.png", "frame": {"x":805,"y":953,"w":42,"h":47}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":6,"y":4,"w":42,"h":47}, "sourceSize": {"w":54,"h":56}, "pivot": {"x":0.5,"y":0.5} }, { "filename": "crystal_0.png", "frame": {"x":886,"y":2,"w":229,"h":239}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":4,"y":3,"w":229,"h":239}, "sourceSize": {"w":256,"h":256}, "pivot": {"x":0.5,"y":0.5} }, { "filename": "crystal_1.png", "frame": {"x":2,"y":769,"w":241,"h":232}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":0,"y":0,"w":241,"h":232}, "sourceSize": {"w":256,"h":256}, "pivot": {"x":0.5,"y":0.5} }, { "filename": "crystal_2.png", "frame": {"x":305,"y":413,"w":208,"h":216}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":26,"y":8,"w":208,"h":216}, "sourceSize": {"w":256,"h":256}, "pivot": {"x":0.5,"y":0.5} }, { "filename": "enemy_boss_0.png", "frame": {"x":2,"y":2,"w":365,"h":389}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":45,"y":34,"w":365,"h":389}, "sourceSize": {"w":456,"h":457}, "pivot": {"x":0.5,"y":0.5} }, { "filename": "enemy_boss_1.png", "frame": {"x":2,"y":393,"w":301,"h":374}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":77,"y":42,"w":301,"h":374}, "sourceSize": {"w":456,"h":457}, "pivot": {"x":0.5,"y":0.5} }, { "filename": "enemy_boss_2.png", "frame": {"x":369,"y":2,"w":257,"h":409}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":125,"y":23,"w":257,"h":409}, "sourceSize": {"w":456,"h":457}, "pivot": {"x":0.5,"y":0.5} }, { "filename": "enemy_destroyer_0.png", "frame": {"x":855,"y":260,"w":167,"h":140}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":10,"y":31,"w":167,"h":140}, "sourceSize": {"w":188,"h":188}, "pivot": {"x":0.5,"y":0.5} }, { "filename": "enemy_destroyer_1.png", "frame": {"x":586,"y":739,"w":196,"h":140}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":24,"y":52,"w":196,"h":140}, "sourceSize": {"w":244,"h":244}, "pivot": {"x":0.5,"y":0.5} }, { "filename": "enemy_destroyer_2.png", "frame": {"x":628,"y":260,"w":225,"h":140}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":9,"y":52,"w":225,"h":140}, "sourceSize": {"w":244,"h":244}, "pivot": {"x":0.5,"y":0.5} }, { "filename": "enemy_scout_0.png", "frame": {"x":784,"y":739,"w":107,"h":126}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":11,"y":1,"w":107,"h":126}, "sourceSize": {"w":129,"h":129}, "pivot": {"x":0.5,"y":0.5} }, { "filename": "enemy_scout_1.png", "frame": {"x":1024,"y":243,"w":156,"h":154}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":9,"y":15,"w":156,"h":154}, "sourceSize": {"w":185,"h":185}, "pivot": {"x":0.5,"y":0.5} }, { "filename": "enemy_scout_2.png", "frame": {"x":1047,"y":399,"w":157,"h":154}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":5,"y":15,"w":157,"h":154}, "sourceSize": {"w":185,"h":185}, "pivot": {"x":0.5,"y":0.5} }, { "filename": "explosion_flare.png", "frame": {"x":1117,"y":2,"w":56,"h":209}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":4,"y":0,"w":56,"h":209}, "sourceSize": {"w":64,"h":256}, "pivot": {"x":0.5,"y":0.5} }, { "filename": "explosion_particle.png", "frame": {"x":511,"y":737,"w":36,"h":60}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":14,"y":1,"w":36,"h":60}, "sourceSize": {"w":64,"h":64}, "pivot": {"x":0.5,"y":0.5} }, { "filename": "explosion_ring.png", "frame": {"x":628,"y":2,"w":256,"h":256}, "rotated": false, "trimmed": false, "spriteSourceSize": {"x":0,"y":0,"w":256,"h":256}, "sourceSize": {"w":256,"h":256}, "pivot": {"x":0.5,"y":0.5} }, { "filename": "fire_particle.png", "frame": {"x":630,"y":668,"w":55,"h":55}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":4,"y":4,"w":55,"h":55}, "sourceSize": {"w":64,"h":64}, "pivot": {"x":0.5,"y":0.5} }, { "filename": "laser.png", "frame": {"x":1131,"y":901,"w":37,"h":76}, "rotated": false, "trimmed": false, "spriteSourceSize": {"x":0,"y":0,"w":37,"h":76}, "sourceSize": {"w":37,"h":76}, "pivot": {"x":0.5,"y":0.5} }, { "filename": "powerup.png", "frame": {"x":586,"y":881,"w":113,"h":113}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":71,"y":72,"w":113,"h":113}, "sourceSize": {"w":256,"h":256}, "pivot": {"x":0.5,"y":0.5} }, { "filename": "powerup_0.png", "frame": {"x":1108,"y":763,"w":64,"h":68}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":96,"y":95,"w":64,"h":68}, "sourceSize": {"w":256,"h":256}, "pivot": {"x":0.5,"y":0.5} }, { "filename": "powerup_1.png", "frame": {"x":575,"y":668,"w":53,"h":67}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":101,"y":93,"w":53,"h":67}, "sourceSize": {"w":256,"h":256}, "pivot": {"x":0.5,"y":0.5} }, { "filename": "powerup_2.png", "frame": {"x":1108,"y":833,"w":63,"h":66}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":96,"y":96,"w":63,"h":66}, "sourceSize": {"w":256,"h":256}, "pivot": {"x":0.5,"y":0.5} }, { "filename": "powerup_3.png", "frame": {"x":643,"y":604,"w":57,"h":62}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":100,"y":95,"w":57,"h":62}, "sourceSize": {"w":256,"h":256}, "pivot": {"x":0.5,"y":0.5} }, { "filename": "shield.png", "frame": {"x":515,"y":413,"w":189,"h":189}, "rotated": false, "trimmed": false, "spriteSourceSize": {"x":0,"y":0,"w":189,"h":189}, "sourceSize": {"w":189,"h":189}, "pivot": {"x":0.5,"y":0.5} }, { "filename": "ship.nrm.png", "frame": {"x":447,"y":799,"w":137,"h":167}, "rotated": false, "trimmed": false, "spriteSourceSize": {"x":0,"y":0,"w":137,"h":167}, "sourceSize": {"w":137,"h":167}, "pivot": {"x":0.5,"y":0.5} }, { "filename": "ship.png", "frame": {"x":908,"y":402,"w":137,"h":167}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":25,"y":10,"w":137,"h":167}, "sourceSize": {"w":188,"h":188}, "pivot": {"x":0.5,"y":0.5} }, { "filename": "star_0.png", "frame": {"x":515,"y":604,"w":62,"h":62}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":1,"y":1,"w":62,"h":62}, "sourceSize": {"w":64,"h":64}, "pivot": {"x":0.5,"y":0.5} }, { "filename": "star_1.png", "frame": {"x":579,"y":604,"w":62,"h":62}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":1,"y":1,"w":62,"h":62}, "sourceSize": {"w":64,"h":64}, "pivot": {"x":0.5,"y":0.5} }, { "filename": "star_2.png", "frame": {"x":511,"y":668,"w":62,"h":62}, "rotated": false, "trimmed": true, "spriteSourceSize": {"x":1,"y":1,"w":62,"h":62}, "sourceSize": {"w":64,"h":64}, "pivot": {"x":0.5,"y":0.5} }], "meta": { "app": "http://www.codeandweb.com/texturepacker", "version": "1.0", "image": "sprites.png", "format": "RGBA8888", "size": {"w":1206,"h":1003}, "scale": "1", "smartupdate": "$TexturePacker:SmartUpdate:b5fb5d5db8ad960a7927226c3156595b:fd6909c3527bc05911568aa5fd053d0d:1eabdf11f75e3a4fe3147baf7b5be24b$" } } ================================================ FILE: build_web ================================================ #!/bin/sh flutter build web --web-renderer canvaskit --base-href "/spaceblast/" cp -r build/web/ ../spritewidget_web/docs/spaceblast/ ================================================ FILE: fonts/OFL.txt ================================================ Copyright (c) 2009, Matt McInerney (matt@pixelspread.com), with Reserved Font Name Orbitron. This Font Software is licensed under the SIL Open Font License, Version 1.1. This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL ----------------------------------------------------------- SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 ----------------------------------------------------------- PREAMBLE The goals of the Open Font License (OFL) are to stimulate worldwide development of collaborative font projects, to support the font creation efforts of academic and linguistic communities, and to provide a free and open framework in which fonts may be shared and improved in partnership with others. The OFL allows the licensed fonts to be used, studied, modified and redistributed freely as long as they are not sold by themselves. The fonts, including any derivative works, can be bundled, embedded, redistributed and/or sold with any software provided that any reserved names are not used by derivative works. The fonts and derivatives, however, cannot be released under any other type of license. The requirement for fonts to remain under this license does not apply to any document created using the fonts or their derivatives. DEFINITIONS "Font Software" refers to the set of files released by the Copyright Holder(s) under this license and clearly marked as such. This may include source files, build scripts and documentation. "Reserved Font Name" refers to any names specified as such after the copyright statement(s). "Original Version" refers to the collection of Font Software components as distributed by the Copyright Holder(s). "Modified Version" refers to any derivative made by adding to, deleting, or substituting -- in part or in whole -- any of the components of the Original Version, by changing formats or by porting the Font Software to a new environment. "Author" refers to any designer, engineer, programmer, technical writer or other person who contributed to the Font Software. PERMISSION & CONDITIONS Permission is hereby granted, free of charge, to any person obtaining a copy of the Font Software, to use, study, copy, merge, embed, modify, redistribute, and sell modified and unmodified copies of the Font Software, subject to the following conditions: 1) Neither the Font Software nor any of its individual components, in Original or Modified Versions, may be sold by itself. 2) Original or Modified Versions of the Font Software may be bundled, redistributed and/or sold with any software, provided that each copy contains the above copyright notice and this license. These can be included either as stand-alone text files, human-readable headers or in the appropriate machine-readable metadata fields within text or binary files as long as those fields can be easily viewed by the user. 3) No Modified Version of the Font Software may use the Reserved Font Name(s) unless explicit written permission is granted by the corresponding Copyright Holder. This restriction only applies to the primary font name as presented to the users. 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font Software shall not be used to promote, endorse or advertise any Modified Version, except to acknowledge the contribution(s) of the Copyright Holder(s) and the Author(s) or with their explicit written permission. 5) The Font Software, modified or unmodified, in part or in whole, must be distributed entirely under this license, and must not be distributed under any other license. The requirement for fonts to remain under this license does not apply to any document created using the Font Software. TERMINATION This license becomes null and void if any of the above conditions are not met. DISCLAIMER THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE. ================================================ FILE: lib/coordinate_system.dart ================================================ part of game; class CoordinateSystem extends SingleChildRenderObjectWidget { const CoordinateSystem({ Key? key, required this.systemSize, this.systemType = CoordinateSystemType.fixedWidth, required Widget child, }) : super(key: key, child: child); final Size systemSize; final CoordinateSystemType systemType; @override RenderCoordinateSystem createRenderObject(BuildContext context) { return RenderCoordinateSystem( systemSize: systemSize, systemType: systemType, ); } @override void updateRenderObject( BuildContext context, RenderCoordinateSystem renderObject, ) { renderObject.systemSize = systemSize; renderObject.systemType = systemType; } } ================================================ FILE: lib/custom_actions.dart ================================================ part of game; typedef PointSetterCallback = void Function(Offset value); class ActionCircularMove extends MotionInterval { ActionCircularMove( this.setter, this.center, this.radius, this.startAngle, this.clockWise, double duration, ) : super(duration); final PointSetterCallback setter; final Offset center; final double radius; final double startAngle; final bool clockWise; @override void update(double t) { if (!clockWise) t = -t; double rad = radians(startAngle + t * 360.0); Offset offset = Offset(math.cos(rad) * radius, math.sin(rad) * radius); Offset pos = center + offset; setter(pos); } } class ActionOscillate extends MotionInterval { ActionOscillate(this.setter, this.center, this.radius, double duration) : super(duration); final PointSetterCallback setter; final Offset center; final double radius; @override void update(double t) { double rad = radians(t * 360.0); Offset offset = Offset(math.sin(rad) * radius, 0.0); setter(center + offset); } } ================================================ FILE: lib/explosions.dart ================================================ part of game; class Explosion extends Node { Explosion() { zPosition = 10.0; } } class ExplosionBig extends Explosion { ExplosionBig(SpriteSheet sheet) { // Add particles ParticleSystem particlesDebris = ParticleSystem( texture: sheet["explosion_particle.png"]!, rotateToMovement: true, startRotation: 90.0, startRotationVar: 0.0, endRotation: 90.0, startSize: 0.3, startSizeVar: 0.1, endSize: 0.3, endSizeVar: 0.1, numParticlesToEmit: 25, emissionRate: 1000.0, greenVar: 127, redVar: 127, life: 0.75, lifeVar: 0.5, ); particlesDebris.zPosition = 1010.0; addChild(particlesDebris); ParticleSystem particlesFire = ParticleSystem( texture: sheet["fire_particle.png"]!, colorSequence: ColorSequence( colors: [ const Color(0xffffff33), const Color(0xffff3333), const Color(0x00ff3333) ], stops: [ 0.0, 0.5, 1.0, ], ), numParticlesToEmit: 25, emissionRate: 1000.0, startSize: 0.5, startSizeVar: 0.1, endSize: 0.5, endSizeVar: 0.1, posVar: const Offset(10.0, 10.0), speed: 10.0, speedVar: 5.0, life: 0.75, lifeVar: 0.5, ); particlesFire.zPosition = 1011.0; addChild(particlesFire); // Add ring Sprite spriteRing = Sprite(texture: sheet["explosion_ring.png"]!); spriteRing.blendMode = ui.BlendMode.plus; addChild(spriteRing); Motion scale = MotionTween( setter: (a) => spriteRing.scale = a, start: 0.2, end: 1.0, duration: 0.75, ); Motion scaleAndRemove = MotionSequence( motions: [scale, MotionRemoveNode(node: spriteRing)], ); Motion fade = MotionTween( setter: (a) => spriteRing.opacity = a, start: 1.0, end: 0.0, duration: 0.75, ); motions.run(scaleAndRemove); motions.run(fade); // Add streaks for (int i = 0; i < 5; i++) { Sprite spriteFlare = Sprite(texture: sheet["explosion_flare.png"]!); spriteFlare.pivot = const Offset(0.3, 1.0); spriteFlare.scaleX = 0.3; spriteFlare.blendMode = ui.BlendMode.plus; spriteFlare.rotation = randomDouble() * 360.0; addChild(spriteFlare); double multiplier = randomDouble() * 0.3 + 1.0; Motion scale = MotionTween( setter: (a) => spriteFlare.scaleY = a, start: 0.3 * multiplier, end: 0.8, duration: 0.75 * multiplier, ); Motion scaleAndRemove = MotionSequence( motions: [ scale, MotionRemoveNode(node: spriteFlare), ], ); Motion fadeIn = MotionTween( setter: (a) => spriteFlare.opacity = a, start: 0.0, end: 1.0, duration: 0.25 * multiplier, ); Motion fadeOut = MotionTween( setter: (a) => spriteFlare.opacity = a, start: 1.0, end: 0.0, duration: 0.5 * multiplier, ); Motion fadeInOut = MotionSequence(motions: [fadeIn, fadeOut]); motions.run(scaleAndRemove); motions.run(fadeInOut); } } } class ExplosionMini extends Explosion { ExplosionMini(SpriteSheet sheet) { for (int i = 0; i < 2; i++) { Sprite star = Sprite(texture: sheet["star_0.png"]!); star.scale = 0.5; star.colorOverlay = const Color(0xff95f4fb); star.blendMode = ui.BlendMode.plus; addChild(star); double rotationStart = randomDouble() * 90.0; double rotationEnd = 180.0 + randomDouble() * 90.0; if (i == 0) { rotationStart = -rotationStart; rotationEnd = -rotationEnd; } MotionTween rotate = MotionTween( setter: (a) => star.rotation = a, start: rotationStart, end: rotationEnd, duration: 0.2, ); motions.run(rotate); MotionTween fade = MotionTween( setter: (a) => star.opacity = a, start: 1.0, end: 0.0, duration: 0.2, ); motions.run(fade); } MotionSequence seq = MotionSequence( motions: [ MotionDelay(delay: 0.2), MotionRemoveNode(node: this), ], ); motions.run(seq); } } ================================================ FILE: lib/flash.dart ================================================ part of game; class Flash extends NodeWithSize { Flash(Size size, this.duration) : super(size) { MotionTween fade = MotionTween( setter: (a) => _opacity = a, start: 1.0, end: 0.0, duration: duration, ); MotionSequence seq = MotionSequence( motions: [ fade, MotionRemoveNode(node: this), ], ); motions.run(seq); } double duration; double _opacity = 1.0; final Paint _cachedPaint = Paint(); @override void paint(Canvas canvas) { // Update the color _cachedPaint.color = Color.fromARGB((255.0 * _opacity).toInt(), 255, 255, 255); // Fill the area applyTransformForPivot(canvas); canvas.drawRect( Rect.fromLTRB(0.0, 0.0, size.width, size.height), _cachedPaint); } } ================================================ FILE: lib/game_demo.dart ================================================ library game; import 'dart:async'; import 'dart:convert'; import 'dart:math' as math; import 'dart:ui' as ui; import 'package:flutter/rendering.dart'; import 'package:flutter/widgets.dart'; import 'package:just_audio/just_audio.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:spritewidget/spritewidget.dart'; import 'package:vector_math/vector_math_64.dart'; part 'coordinate_system.dart'; part 'custom_actions.dart'; part 'explosions.dart'; part 'flash.dart'; part 'game_demo_node.dart'; part 'game_object_factory.dart'; part 'game_objects.dart'; part 'persistant_game_state.dart'; part 'player_state.dart'; part 'power_bar.dart'; part 'render_coordinate_system.dart'; part 'repeated_image.dart'; part 'sound_assets.dart'; part 'star_field.dart'; part 'widgets.dart'; ================================================ FILE: lib/game_demo_node.dart ================================================ part of game; var _gameSizeHeight = 320.0; const _chunkSpacing = 640.0; const int _chunksPerLevel = 9; const bool _drawDebug = false; typedef GameOverCallback = void Function( int score, int coins, int levelReached); class GameDemoNode extends NodeWithSize { GameDemoNode(this._images, this._spritesGame, this._spritesUI, this._sounds, this._gameState, this._gameOverCallback) : super(const Size(320.0, 320.0)) { // Add background _background = RepeatedImage(_images["assets/starfield.png"]!); addChild(_background); // Create starfield _starField = StarField(_spritesGame, 200); addChild(_starField); // Add nebula _nebula = RepeatedImage(_images["assets/nebula.png"]!, ui.BlendMode.plus); addChild(_nebula); // Setup game screen, it will always be anchored to the bottom of the screen _gameScreen = Node(); addChild(_gameScreen); // Setup the level and add it to the screen, the level is the node where // all our game objects live. It is moved to scroll the game _level = Level(); _gameScreen.addChild(_level); // Add heads up display _playerState = PlayerState(_spritesUI, _spritesGame, _gameState); _playerState.position = const Offset(0.0, 20.0); addChild(_playerState); _objectFactory = GameObjectFactory(_spritesGame, _sounds, _level, _playerState); _level.ship = Ship(_objectFactory); _level.ship.setupActions(); _level.addChild(_level.ship); // Add the joystick _joystick = VirtualJoystick(); _gameScreen.addChild(_joystick); // Add initial game objects addObjects(); } final PersistantGameState _gameState; // Resources final ImageMap _images; final SoundAssets _sounds; final SpriteSheet _spritesGame; final SpriteSheet _spritesUI; // Callback final GameOverCallback _gameOverCallback; // Game screen nodes late Node _gameScreen; late VirtualJoystick _joystick; late GameObjectFactory _objectFactory; late Level _level; int _topLevelReached = 0; late StarField _starField; late RepeatedImage _background; late RepeatedImage _nebula; late PlayerState _playerState; // Game properties double _scroll = 0.0; int _framesToFire = 0; final int _framesBetweenShots = 20; bool _gameOver = false; @override void spriteBoxPerformedLayout() { _gameSizeHeight = spriteBox!.visibleArea!.height; _gameScreen.position = Offset(0.0, _gameSizeHeight); } @override void update(double dt) { // Scroll the level _scroll = _level.scroll(_playerState.scrollSpeed); _starField.move(0.0, _playerState.scrollSpeed); _background.move(_playerState.scrollSpeed * 0.1); _nebula.move(_playerState.scrollSpeed); // Add objects addObjects(); // Move the ship if (!_gameOver) { _level.ship.applyThrust(_joystick.value, _scroll); } // Add shots if (_framesToFire == 0 && _joystick.isDown && !_gameOver) { fire(); _framesToFire = (_playerState.speedLaserActive) ? _framesBetweenShots ~/ 2 : _framesBetweenShots; } if (_framesToFire > 0) _framesToFire--; // Move game objects for (Node node in _level.children) { if (node is GameObject) { node.move(); } } // Remove offscreen game objects for (int i = _level.children.length - 1; i >= 0; i--) { Node node = _level.children[i]; if (node is GameObject) { node.removeIfOffscreen(_scroll); } } if (_gameOver) return; // Check for collisions between lasers and objects that can take damage List lasers = []; for (Node node in _level.children) { if (node is Laser) lasers.add(node); } List damageables = []; for (Node node in _level.children) { if (node is GameObject && node.canBeDamaged) damageables.add(node); } for (Laser laser in lasers) { for (GameObject damageable in damageables) { if (laser.collidingWith(damageable)) { // Hit something that can take damage damageable.addDamage(laser.impact); laser.destroy(); } } } // Check for collsions between ship and objects that can damage the ship List nodes = List.from(_level.children); for (Node node in nodes) { if (node is GameObject && node.canDamageShip) { if (node.collidingWith(_level.ship)) { if (_playerState.shieldActive) { // Hit, but saved by the shield! if (node is! EnemyBoss) node.destroy(); } else { // The ship was hit :( killShip(); } } } else if (node is GameObject && node.canBeCollected) { if (node.collidingWith(_level.ship)) { // The ship ran over something collectable node.collect(); } } } } int _chunk = 0; void addObjects() { while (_scroll + _chunkSpacing >= _chunk * _chunkSpacing) { addLevelChunk(_chunk, -_chunk * _chunkSpacing - _chunkSpacing); _chunk += 1; } } void addLevelChunk(int chunk, double yPos) { int level = chunk ~/ _chunksPerLevel + _gameState.currentStartingLevel; int part = chunk % _chunksPerLevel; if (part == 0) { LevelLabel lbl = LevelLabel(_objectFactory, level + 1); lbl.position = Offset(0.0, yPos + _chunkSpacing / 2.0 - 150.0); _topLevelReached = level; _level.addChild(lbl); } else if (part == 1) { _objectFactory.addAsteroids(level, yPos); } else if (part == 2) { _objectFactory.addEnemyScoutSwarm(level, yPos); } else if (part == 3) { _objectFactory.addAsteroids(level, yPos); } else if (part == 4) { _objectFactory.addEnemyDestroyerSwarm(level, yPos); } else if (part == 5) { _objectFactory.addAsteroids(level, yPos); } else if (part == 6) { _objectFactory.addEnemyScoutSwarm(level, yPos); } else if (part == 7) { _objectFactory.addAsteroids(level, yPos); } else if (part == 8) { _objectFactory.addBossFight(level, yPos); } } void fire() { int laserLevel = _objectFactory.playerState.laserLevel; Laser shot0 = Laser(_objectFactory, laserLevel, -90.0); shot0.position = _level.ship.position + const Offset(17.0, -10.0); _level.addChild(shot0); Laser shot1 = Laser(_objectFactory, laserLevel, -90.0); shot1.position = _level.ship.position + const Offset(-17.0, -10.0); _level.addChild(shot1); if (_playerState.sideLaserActive) { Laser shot2 = Laser(_objectFactory, laserLevel, -45.0); shot2.position = _level.ship.position + const Offset(17.0, -10.0); _level.addChild(shot2); Laser shot3 = Laser(_objectFactory, laserLevel, -135.0); shot3.position = _level.ship.position + const Offset(-17.0, -10.0); _level.addChild(shot3); } } void killShip() { // Hide ship _level.ship.visible = false; _sounds.playEffect("explosion_player"); // Add explosion ExplosionBig explo = ExplosionBig(_spritesGame); explo.scale = 1.5; explo.position = _level.ship.position; _level.addChild(explo); // Add flash Flash flash = Flash(size, 1.0); addChild(flash); // Set the state to game over _gameOver = true; // Return to main scene and report the score back in 2 seconds Timer(const Duration(seconds: 2), () { _gameOverCallback( _playerState.score, _playerState.coins, _topLevelReached); }); } } class Level extends Node { Level() { position = const Offset(160.0, 0.0); } late Ship ship; double scroll(double scrollSpeed) { position += Offset(0.0, scrollSpeed); return position.dy; } } ================================================ FILE: lib/game_object_factory.dart ================================================ part of game; const int _maxLevel = 9; class GameObjectFactory { GameObjectFactory(this.sheet, this.sounds, this.level, this.playerState); SpriteSheet sheet; SoundAssets sounds; Level level; PlayerState playerState; void addAsteroids(int level, double yPos) { int numAsteroids = 10 + level * 4; double distribution = (level * 0.2).clamp(0.0, 0.8); for (int i = 0; i < numAsteroids; i++) { GameObject obj; if (i == 0) { obj = AsteroidPowerUp(this); } else if (randomDouble() < distribution) { obj = AsteroidBig(this); } else { obj = AsteroidSmall(this); } Offset pos = Offset( randomSignedDouble() * 160.0, yPos + _chunkSpacing * randomDouble()); addGameObject(obj, pos); } } void addEnemyScoutSwarm(int level, double yPos) { int numEnemies = (3 + level * 3).clamp(0, 12); late List types; int swarmLevel = level % _maxLevel; if (swarmLevel == 0) { types = [0, 0, 0]; } else if (swarmLevel == 1) { types = [0, 1, 0]; } else if (swarmLevel == 2) { types = [1, 0, 1]; } else if (swarmLevel == 3) { types = [1, 1, 1]; } else if (swarmLevel == 4) { types = [0, 1, 2]; } else if (swarmLevel == 5) { types = [1, 2, 1]; } else if (swarmLevel == 6) { types = [2, 1, 2]; } else if (swarmLevel == 7) { types = [2, 1, 2]; } else if (swarmLevel == 8) { types = [2, 2, 2]; } for (int i = 0; i < numEnemies; i++) { int type = types[i % 3]; double spacing = math.max(_chunkSpacing / (numEnemies + 1.0), 80.0); double y = yPos + _chunkSpacing / 2.0 - (numEnemies - 1) * spacing / 2.0 + i * spacing; addGameObject(EnemyScout(this, type), Offset(0.0, y)); } } void addEnemyDestroyerSwarm(int level, double yPos) { int numEnemies = (2 + level).clamp(2, 10); late List types; int swarmLevel = level % _maxLevel; if (swarmLevel == 0) { types = [0, 0, 0]; } else if (swarmLevel == 1) { types = [0, 1, 0]; } else if (swarmLevel == 2) { types = [1, 0, 1]; } else if (swarmLevel == 3) { types = [1, 1, 1]; } else if (swarmLevel == 4) { types = [0, 1, 2]; } else if (swarmLevel == 5) { types = [1, 2, 1]; } else if (swarmLevel == 6) { types = [2, 1, 2]; } else if (swarmLevel == 7) { types = [2, 1, 2]; } else if (swarmLevel == 8) { types = [2, 2, 2]; } for (int i = 0; i < numEnemies; i++) { int type = types[i % 3]; addGameObject( EnemyDestroyer(this, type), Offset(randomSignedDouble() * 120.0, yPos + _chunkSpacing * randomDouble())); } } void addGameObject(GameObject obj, Offset pos) { obj.position = pos; obj.setupActions(); level.addChild(obj); } void addBossFight(int level, double yPos) { // Add boss EnemyBoss boss = EnemyBoss(this, level); Offset pos = Offset(0.0, yPos + _chunkSpacing / 2.0); addGameObject(boss, pos); playerState.boss = boss; int destroyerLevel = (level - 1 ~/ 3).clamp(0, 2); // Add boss's helpers if (level >= 1) { EnemyDestroyer destroyer0 = EnemyDestroyer(this, destroyerLevel); addGameObject( destroyer0, Offset(-80.0, yPos + _chunkSpacing / 2.0 + 70.0)); EnemyDestroyer destroyer1 = EnemyDestroyer(this, destroyerLevel); addGameObject( destroyer1, Offset(80.0, yPos + _chunkSpacing / 2.0 + 70.0)); if (level >= 2) { EnemyDestroyer destroyer0 = EnemyDestroyer(this, destroyerLevel); addGameObject( destroyer0, Offset(-80.0, yPos + _chunkSpacing / 2.0 - 70.0)); EnemyDestroyer destroyer1 = EnemyDestroyer(this, destroyerLevel); addGameObject( destroyer1, Offset(80.0, yPos + _chunkSpacing / 2.0 - 70.0)); } } } } const List laserColors = [ Color(0xff95f4fb), Color(0xff5bff35), Color(0xffff886c), Color(0xffffd012), Color(0xfffd7fff), ]; void addLaserSprites(Node node, int level, double r, SpriteSheet sheet) { int numLasers = level % 3 + 1; Color laserColor = laserColors[(level ~/ 3) % laserColors.length]; // Add sprites List sprites = []; for (int i = 0; i < numLasers; i++) { Sprite sprite = Sprite(texture: sheet["explosion_particle.png"]!); sprite.scale = 0.5; sprite.colorOverlay = laserColor; sprite.blendMode = ui.BlendMode.plus; node.addChild(sprite); sprites.add(sprite); } // Position the individual sprites if (numLasers == 2) { sprites[0].position = const Offset(-3.0, 0.0); sprites[1].position = const Offset(3.0, 0.0); } else if (numLasers == 3) { sprites[0].position = const Offset(-4.0, 0.0); sprites[1].position = const Offset(4.0, 0.0); sprites[2].position = const Offset(0.0, -2.0); } } ================================================ FILE: lib/game_objects.dart ================================================ part of game; abstract class GameObject extends Node { GameObject(this.f); double radius = 0.0; double removeLimit = 1280.0; bool canDamageShip = true; bool canBeDamaged = true; bool canBeCollected = false; double maxDamage = 3.0; double damage = 0.0; final GameObjectFactory f; final Paint _paintDebug = Paint() ..color = const Color(0xffff0000) ..strokeWidth = 1.0 ..style = ui.PaintingStyle.stroke; bool collidingWith(GameObject obj) { return (GameMath.distanceBetweenPoints(position, obj.position) < radius + obj.radius); } void move() {} void removeIfOffscreen(double scroll) { if (-position.dy > scroll + removeLimit || -position.dy < scroll - 50.0) { removeFromParent(); } } void destroy() { if (parent != null) { Explosion? explo = createExplosion(); if (explo != null) { explo.position = position; parent!.addChild(explo); } Collectable? powerUp = createPowerUp(); if (powerUp != null) { f.addGameObject(powerUp, position); } removeFromParent(); } } void collect() { removeFromParent(); } void addDamage(double d) { if (!canBeDamaged) return; damage += d; if (damage >= maxDamage) { destroy(); f.playerState.score += (maxDamage * 10).ceil(); } else { f.sounds.playEffect("hit"); } } Explosion? createExplosion() { return null; } Collectable? createPowerUp() { return null; } @override void paint(Canvas canvas) { if (_drawDebug) { canvas.drawCircle(Offset.zero, radius, _paintDebug); } super.paint(canvas); } void setupActions() {} } class LevelLabel extends GameObject { LevelLabel(GameObjectFactory f, int level) : super(f) { canDamageShip = false; canBeDamaged = false; Label lbl = Label("LEVEL $level", textAlign: TextAlign.center, textStyle: const TextStyle( fontFamily: "Orbitron", letterSpacing: 10.0, color: Color(0xffffffff), fontSize: 24.0, fontWeight: FontWeight.w600)); addChild(lbl); } } class Ship extends GameObject { Ship(GameObjectFactory f) : super(f) { // Add main ship sprite _sprite = Sprite(texture: f.sheet["ship.png"]!); _sprite.scale = 0.3; _sprite.rotation = -90.0; addChild(_sprite); _spriteShield = Sprite(texture: f.sheet["shield.png"]!); _spriteShield.scale = 0.35; _spriteShield.blendMode = ui.BlendMode.plus; addChild(_spriteShield); radius = 20.0; canBeDamaged = false; canDamageShip = false; // Set start position position = const Offset(0.0, 50.0); } late Sprite _sprite; late Sprite _spriteShield; void applyThrust(Offset joystickValue, double scroll) { Offset oldPos = position; Offset target = Offset( joystickValue.dx * 160.0, joystickValue.dy * 220.0 - 250.0 - scroll); double filterFactor = 0.2; position = Offset(GameMath.filter(oldPos.dx, target.dx, filterFactor), GameMath.filter(oldPos.dy, target.dy, filterFactor)); } @override void setupActions() { MotionTween rotate = MotionTween( setter: (a) => _spriteShield.rotation = a, start: 0.0, end: 360.0, duration: 1.0, ); _spriteShield.motions.run(MotionRepeatForever(motion: rotate)); } @override void update(double dt) { // Update shield if (f.playerState.shieldActive) { if (f.playerState.shieldDeactivating) { _spriteShield.visible = !_spriteShield.visible; } else { _spriteShield.visible = true; } } else { _spriteShield.visible = false; } } } class Laser extends GameObject { double impact = 0.0; Laser(GameObjectFactory f, int level, double r) : super(f) { // Game object properties radius = 10.0; removeLimit = _gameSizeHeight + radius; canDamageShip = false; canBeDamaged = false; impact = 1.0 + level * 0.5; // Offset for movement _offset = Offset(math.cos(radians(r)) * 8.0, math.sin(radians(r)) * 8.0 - f.playerState.scrollSpeed); // Drawing properties rotation = r + 90.0; addLaserSprites(this, level, r, f.sheet); } late Offset _offset; @override void move() { position += _offset; } @override Explosion createExplosion() { return ExplosionMini(f.sheet); } } Color colorForDamage(double damage, double maxDamage, [Color? toColor]) { int r, g, b; if (toColor == null) { r = 255; g = 3; b = 86; } else { r = toColor.red; g = toColor.green; b = toColor.blue; } int alpha = ((200.0 * damage) ~/ maxDamage).clamp(0, 200); return Color.fromARGB(alpha, r, g, b); } abstract class Obstacle extends GameObject { Obstacle(GameObjectFactory f) : super(f); double explosionScale = 1.0; @override Explosion createExplosion() { f.sounds.playEffect("explosion_${randomInt(3)}"); Explosion explo = ExplosionBig(f.sheet); explo.scale = explosionScale; return explo; } } abstract class Asteroid extends Obstacle { Asteroid(GameObjectFactory f) : super(f); late Sprite _sprite; @override void setupActions() { // Rotate obstacle int direction = 1; if (randomBool()) direction = -1; MotionTween rotate = MotionTween( setter: (a) => _sprite.rotation = a, start: 0.0, end: 360.0 * direction, duration: 5.0 + 5.0 * randomDouble(), ); _sprite.motions.run(MotionRepeatForever(motion: rotate)); } @override set damage(double d) { super.damage = d; _sprite.colorOverlay = colorForDamage(d, maxDamage); } @override Collectable createPowerUp() { return Coin(f); } } class AsteroidBig extends Asteroid { AsteroidBig(GameObjectFactory f) : super(f) { _sprite = Sprite(texture: f.sheet["asteroid_big_${randomInt(3)}.png"]!); _sprite.scale = 0.3; radius = 25.0; maxDamage = 5.0; addChild(_sprite); } } class AsteroidSmall extends Asteroid { AsteroidSmall(GameObjectFactory f) : super(f) { _sprite = Sprite(texture: f.sheet["asteroid_small_${randomInt(3)}.png"]!); _sprite.scale = 0.3; radius = 12.0; maxDamage = 3.0; addChild(_sprite); } } class AsteroidPowerUp extends AsteroidBig { late PowerUpType _powerUpType; AsteroidPowerUp(GameObjectFactory f) : super(f) { _powerUpType = nextPowerUpType(); removeAllChildren(); Sprite powerUpBg = Sprite( texture: f.sheet["powerup.png"]!, ); powerUpBg.scale = 0.3; addChild(powerUpBg); Sprite powerUpIcon = Sprite( texture: f.sheet["powerup_${_powerUpType.index}.png"]!, ); powerUpIcon.scale = 0.3; addChild(powerUpIcon); _sprite = Sprite( texture: f.sheet["crystal_${randomInt(2)}.png"]!, ); _sprite.scale = 0.3; addChild(_sprite); } @override void setupActions() {} @override Collectable createPowerUp() { return PowerUp(f, _powerUpType); } @override set damage(double d) { super.damage = d; _sprite.colorOverlay = colorForDamage(d, maxDamage, const Color.fromARGB(255, 200, 200, 255)); } } class EnemyScout extends Obstacle { EnemyScout(GameObjectFactory f, int level) : super(f) { _sprite = Sprite(texture: f.sheet["enemy_scout_$level.png"]!); _sprite.scale = 0.32; radius = 12.0 + level * 2.0; if (level == 0) { maxDamage = 1.0; } else if (level == 1) { maxDamage = 4.0; } else if (level == 2) { maxDamage = 8.0; } addChild(_sprite); constraints = [ConstraintRotationToMovement(dampening: 0.5)]; } final double _swirlSpacing = 80.0; _addRandomSquare(List offsets, double x, double y) { double xMove = (randomBool()) ? _swirlSpacing : -_swirlSpacing; double yMove = (randomBool()) ? _swirlSpacing : -_swirlSpacing; if (randomBool()) { offsets.addAll([ Offset(x, y), Offset(xMove + x, y), Offset(xMove + x, yMove + y), Offset(x, yMove + y), Offset(x, y) ]); } else { offsets.addAll([ Offset(x, y), Offset(x, y + yMove), Offset(xMove + x, yMove + y), Offset(xMove + x, y), Offset(x, y) ]); } } @override void setupActions() { List offsets = []; _addRandomSquare(offsets, -_swirlSpacing, 0.0); _addRandomSquare(offsets, _swirlSpacing, 0.0); offsets.add(Offset(-_swirlSpacing, 0.0)); List points = []; for (Offset offset in offsets) { points.add(position + offset); } MotionSpline spline = MotionSpline( setter: (Offset a) => position = a, points: points, duration: 6.0, ); spline.tension = 0.7; motions.run(MotionRepeatForever(motion: spline)); } @override Collectable createPowerUp() { return Coin(f); } @override set damage(double d) { super.damage = d; _sprite.colorOverlay = colorForDamage(d, maxDamage); } late Sprite _sprite; } class EnemyDestroyer extends Obstacle { EnemyDestroyer(GameObjectFactory f, int level) : super(f) { _sprite = Sprite(texture: f.sheet["enemy_destroyer_$level.png"]!); _sprite.scale = 0.32; radius = 24.0 + level * 2; if (level == 0) { maxDamage = 4.0; } else if (level == 1) { maxDamage = 8.0; } else if (level == 2) { maxDamage = 16.0; } addChild(_sprite); constraints = [ ConstraintRotationToNode(targetNode: f.level.ship, dampening: 0.05) ]; } int _countDown = randomInt(120) + 240; @override void setupActions() { ActionCircularMove circle = ActionCircularMove((Offset a) { position = a; }, position, 40.0, 360.0 * randomDouble(), randomBool(), 3.0); motions.run(MotionRepeatForever(motion: circle)); } @override Collectable createPowerUp() { return Coin(f); } @override void update(double dt) { _countDown -= 1; if (_countDown <= 0) { // Shoot at player f.sounds.playEffect("laser"); EnemyLaser laser = EnemyLaser(f, rotation, 5.0, const Color(0xffffe38e)); laser.position = position; f.level.addChild(laser); _countDown = 60 + randomInt(120); } } @override set damage(double d) { super.damage = d; _sprite.colorOverlay = colorForDamage(d, maxDamage); } late Sprite _sprite; } class EnemyLaser extends Obstacle { EnemyLaser(GameObjectFactory f, double rotation, double speed, Color color) : super(f) { _sprite = Sprite(texture: f.sheet["explosion_particle.png"]!); _sprite.scale = 0.5; _sprite.rotation = rotation + 90; _sprite.colorOverlay = color; addChild(_sprite); canDamageShip = true; canBeDamaged = false; double rad = radians(rotation); _movement = Offset(math.cos(rad) * speed, math.sin(rad) * speed); } late Sprite _sprite; late Offset _movement; @override void move() { position += _movement; } } class EnemyBoss extends Obstacle { EnemyBoss(GameObjectFactory f, int level) : super(f) { radius = 48.0; _sprite = Sprite(texture: f.sheet["enemy_boss_${level % 3}.png"]!); _sprite.scale = 0.32; addChild(_sprite); maxDamage = 40.0 + 20.0 * level; constraints = [ ConstraintRotationToNode(targetNode: f.level.ship, dampening: 0.05) ]; _powerBar = PowerBar(const Size(60.0, 10.0)); _powerBar.pivot = const Offset(0.5, 0.5); f.level.addChild(_powerBar); _powerBar.constraints = [ ConstraintPositionToNode( targetNode: this, dampening: 0.5, offset: const Offset(0.0, -70.0), ) ]; } late Sprite _sprite; late PowerBar _powerBar; int _countDown = randomInt(120) + 240; @override void update(double dt) { _countDown -= 1; if (_countDown <= 0) { // Shoot at player f.sounds.playEffect("laser"); fire(10.0); fire(0.0); fire(-10.0); _countDown = 60 + randomInt(120); } } void fire(double r) { r += rotation; EnemyLaser laser = EnemyLaser(f, r, 5.0, const Color(0xffffe38e)); double rad = radians(r); Offset startOffset = Offset(math.cos(rad) * 30.0, math.sin(rad) * 30.0); laser.position = position + startOffset; f.level.addChild(laser); } @override void setupActions() { ActionOscillate oscillate = ActionOscillate((Offset a) { position = a; }, position, 120.0, 3.0); motions.run(MotionRepeatForever(motion: oscillate)); } @override void destroy() { f.playerState.boss = null; if (_powerBar.parent != null) _powerBar.removeFromParent(); // Flash the screen NodeWithSize screen = f.playerState.parent as NodeWithSize; screen.addChild(Flash(screen.size, 1.0)); super.destroy(); // Add coins for (int i = 0; i < 20; i++) { Coin coin = Coin(f); Offset pos = Offset(randomSignedDouble() * 160, position.dy + randomSignedDouble() * 160.0); f.addGameObject(coin, pos); } } @override Explosion createExplosion() { f.sounds.playEffect("explosion_boss"); ExplosionBig explo = ExplosionBig(f.sheet); explo.scale = 1.5; return explo; } @override set damage(double d) { super.damage = d; _sprite.motions.stopAll(); _sprite.motions.run( MotionTween( setter: (a) => _sprite.colorOverlay = a, start: const Color.fromARGB(180, 255, 3, 86), end: const Color(0x00000000), duration: 0.3, ), ); _powerBar.power = (1.0 - (damage / maxDamage)).clamp(0.0, 1.0); } } class Collectable extends GameObject { Collectable(GameObjectFactory f) : super(f) { canDamageShip = false; canBeDamaged = false; canBeCollected = true; zPosition = 20.0; } } class Coin extends Collectable { Coin(GameObjectFactory f) : super(f) { _sprite = Sprite(texture: f.sheet["coin.png"]!); _sprite.scale = 0.7; addChild(_sprite); radius = 7.5; } @override void setupActions() { // Rotate MotionTween rotate = MotionTween( setter: (a) => _sprite.rotation = a, start: 0.0, end: 360.0, duration: 1.0, ); motions.run(MotionRepeatForever(motion: rotate)); // Fade in MotionTween fadeIn = MotionTween( setter: (a) => _sprite.opacity = a, start: 0.0, end: 1.0, duration: 0.6, ); motions.run(fadeIn); } late Sprite _sprite; @override void collect() { f.sounds.playEffect("pickup_0"); f.playerState.addCoin(this); super.collect(); } } enum PowerUpType { shield, speedLaser, sideLaser, speedBoost, } List _powerUpTypes = List.from(PowerUpType.values); int _lastPowerUp = _powerUpTypes.length; PowerUpType nextPowerUpType() { if (_lastPowerUp >= _powerUpTypes.length) { _powerUpTypes.shuffle(); _lastPowerUp = 0; } PowerUpType type = _powerUpTypes[_lastPowerUp]; _lastPowerUp++; return type; } class PowerUp extends Collectable { PowerUp(GameObjectFactory f, this.type) : super(f) { _sprite = Sprite(texture: f.sheet["powerup.png"]!); _sprite.scale = 0.3; addChild(_sprite); Sprite powerUpIcon = Sprite(texture: f.sheet["powerup_${type.index}.png"]!); powerUpIcon.scale = 0.3; addChild(powerUpIcon); radius = 10.0; } late Sprite _sprite; PowerUpType type; @override void setupActions() { MotionTween rotate = MotionTween( setter: (a) => _sprite.rotation = a, start: 0.0, end: 360.0, duration: 1.0, ); motions.run(MotionRepeatForever(motion: rotate)); // Fade in MotionTween fadeIn = MotionTween( setter: (a) => _sprite.opacity = a, start: 0.0, end: 1.0, duration: 0.6, ); motions.run(fadeIn); } @override void collect() { f.sounds.playEffect("buy_upgrade"); f.playerState.activatePowerUp(type); super.collect(); } } ================================================ FILE: lib/generated_plugin_registrant.dart ================================================ // // Generated file. Do not edit. // // ignore_for_file: directives_ordering // ignore_for_file: lines_longer_than_80_chars import 'package:audio_session/audio_session_web.dart'; import 'package:just_audio_web/just_audio_web.dart'; import 'package:shared_preferences_web/shared_preferences_web.dart'; import 'package:flutter_web_plugins/flutter_web_plugins.dart'; // ignore: public_member_api_docs void registerPlugins(Registrar registrar) { AudioSessionWeb.registerWith(registrar); JustAudioPlugin.registerWith(registrar); SharedPreferencesPlugin.registerWith(registrar); registrar.registerMessageHandler(); } ================================================ FILE: lib/main.dart ================================================ // Copyright 2015 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:spritewidget/spritewidget.dart'; import 'game_demo.dart'; late PersistantGameState _gameState; const Color _darkTextColor = Color(0xff3c3f4a); typedef SelectTabCallback = void Function(int index); typedef UpgradePowerUpCallback = void Function(PowerUpType type); late ImageMap _imageMap; late SpriteSheet _spriteSheet; late SpriteSheet _spriteSheetUI; late SoundAssets _sounds; main() async { // We need to call ensureInitialized if we are loading images before runApp // is called. // TODO: This should be refactored to use a loading screen WidgetsFlutterBinding.ensureInitialized(); // Hide all menu bars SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersive); // Load game state _gameState = PersistantGameState(); await _gameState.load(); // Load images _imageMap = ImageMap(); await _imageMap.load([ 'assets/nebula.png', 'assets/sprites.png', 'assets/starfield.png', 'assets/game_ui.png', 'assets/ui_bg_top.png', 'assets/ui_bg_bottom.png', 'assets/ui_popup.png', ]); _sounds = SoundAssets(rootBundle); final loads = []; loads.addAll([ _sounds.loadEffect('explosion_0'), _sounds.loadEffect('explosion_1'), _sounds.loadEffect('explosion_2'), _sounds.loadEffect('explosion_boss'), _sounds.loadEffect('explosion_player'), _sounds.loadEffect('laser'), _sounds.loadEffect('hit'), _sounds.loadEffect('levelup'), _sounds.loadEffect('pickup_0'), _sounds.loadEffect('pickup_1'), _sounds.loadEffect('pickup_2'), _sounds.loadEffect('pickup_powerup'), _sounds.loadEffect('click'), _sounds.loadEffect('buy_upgrade'), _sounds.loadMusic('music_intro'), _sounds.loadMusic('music_game'), ]); await Future.wait(loads); // Load sprite sheets String json = await rootBundle.loadString('assets/sprites.json'); _spriteSheet = SpriteSheet( image: _imageMap['assets/sprites.png']!, jsonDefinition: json, ); json = await rootBundle.loadString('assets/game_ui.json'); _spriteSheetUI = SpriteSheet( image: _imageMap['assets/game_ui.png']!, jsonDefinition: json, ); // All game assets are loaded - we are good to go! runApp(const GameDemo()); } class GameDemo extends StatefulWidget { const GameDemo({Key? key}) : super(key: key); @override GameDemoState createState() => GameDemoState(); } class GameDemoState extends State { final GlobalKey _navigatorKey = GlobalKey(); @override Widget build(BuildContext context) { return MaterialApp( home: Title( title: 'Space Blast', color: const Color(0xFF9900FF), child: AppFrame( child: Navigator( key: _navigatorKey, onGenerateRoute: (RouteSettings settings) { switch (settings.name) { case '/game': return _buildGameSceneRoute(); default: return _buildMainSceneRoute(); } }, ), ), ), ); } PageRoute _buildGameSceneRoute() { return MaterialPageRoute(builder: (BuildContext context) { return GameScene( onGameOver: (int lastScore, int coins, int levelReached) { setState(() { _gameState.lastScore = lastScore; _gameState.coins += coins; _gameState.reachedLevel(levelReached); }); }, gameState: _gameState); }); } PageRoute _buildMainSceneRoute() { return MaterialPageRoute(builder: (BuildContext context) { return MainScene( gameState: _gameState, onUpgradePowerUp: (PowerUpType type) { setState(() { if (_gameState.upgradePowerUp(type)) { _sounds.playEffect('buy_upgrade'); } else { _sounds.playEffect('click'); } }); }, onUpgradeLaser: () { setState(() { if (_gameState.upgradeLaser()) { _sounds.playEffect('buy_upgrade'); } else { _sounds.playEffect('click'); } }); }, onStartLevelUp: () { setState(() { _gameState.currentStartingLevel++; _sounds.playEffect('click'); }); }, onStartLevelDown: () { setState(() { _gameState.currentStartingLevel--; _sounds.playEffect('click'); }); }, ); }); } } class GameScene extends StatefulWidget { const GameScene({ this.onGameOver, this.gameState, Key? key, }) : super(key: key); final GameOverCallback? onGameOver; final PersistantGameState? gameState; @override State createState() => GameSceneState(); } class GameSceneState extends State { late NodeWithSize _game; @override void initState() { super.initState(); _game = GameDemoNode( _imageMap, _spriteSheet, _spriteSheetUI, _sounds, widget.gameState!, ( int score, int coins, int levelReached, ) { Navigator.pop(context); widget.onGameOver!(score, coins, levelReached); _sounds.playMusic('music_intro'); }, ); } @override Widget build(BuildContext context) { return SpriteWidget( _game, transformMode: SpriteBoxTransformMode.fixedWidth, ); } } class MainScene extends StatefulWidget { const MainScene({ Key? key, required this.gameState, required this.onUpgradePowerUp, required this.onUpgradeLaser, required this.onStartLevelUp, required this.onStartLevelDown, }) : super(key: key); final PersistantGameState gameState; final UpgradePowerUpCallback onUpgradePowerUp; final VoidCallback onUpgradeLaser; final VoidCallback onStartLevelUp; final VoidCallback onStartLevelDown; @override State createState() => MainSceneState(); } class MainSceneState extends State { @override void initState() { super.initState(); } @override Widget build(BuildContext context) { var notchOffset = MediaQuery.of(context).padding.top; return Column( crossAxisAlignment: CrossAxisAlignment.stretch, mainAxisSize: MainAxisSize.max, children: [ Container( height: notchOffset, ), Expanded( child: CoordinateSystem( systemSize: const Size(320.0, 320.0), child: DefaultTextStyle( style: const TextStyle( fontFamily: "Orbitron", fontSize: 20.0, color: Color(0xffffffff), ), child: Stack( children: [ const MainSceneBackground(), Column( children: [ SizedBox( width: 320.0, height: 98.0, child: TopBar( gameState: widget.gameState, ), ), Expanded( child: CenterArea( onUpgradeLaser: widget.onUpgradeLaser, onUpgradePowerUp: widget.onUpgradePowerUp, gameState: widget.gameState, ), ), SizedBox( width: 320.0, height: 93.0, child: BottomBar( onPlay: () { Navigator.pushNamed(context, '/game'); _sounds.playMusic('music_game'); }, onStartLevelUp: widget.onStartLevelUp, onStartLevelDown: widget.onStartLevelDown, gameState: widget.gameState, ), ), ], ), ], ), ), ), ), ], ); } } class TopBar extends StatelessWidget { const TopBar({ required this.gameState, Key? key, }) : super(key: key); final PersistantGameState gameState; @override Widget build(BuildContext context) { TextStyle scoreLabelStyle = const TextStyle( fontFamily: "Orbitron", fontSize: 20.0, fontWeight: FontWeight.w500, color: _darkTextColor); return Stack( children: [ Positioned( left: 18.0, top: 13.0, child: Text("Last Score", style: scoreLabelStyle), ), Positioned( left: 18.0, top: 39.0, child: Text("Weekly Best", style: scoreLabelStyle), ), Positioned( right: 18.0, top: 13.0, child: Text("${gameState.lastScore}", style: scoreLabelStyle), ), Positioned( right: 18.0, top: 39.0, child: Text("${gameState.weeklyBestScore}", style: scoreLabelStyle), ), Positioned( left: 18.0, top: 80.0, child: TextureImage( texture: _spriteSheetUI['icn_crystal.png']!, width: 12.0, height: 18.0), ), Positioned( left: 36.0, top: 82.5, child: Text( "${gameState.coins}", style: const TextStyle( fontSize: 16.0, fontWeight: FontWeight.w500, color: _darkTextColor, ), ), ), ], ); } } class CenterArea extends StatelessWidget { const CenterArea({ // required this.selection, required this.onUpgradeLaser, required this.gameState, required this.onUpgradePowerUp, Key? key, }) : super(key: key); // final int selection; final VoidCallback onUpgradeLaser; final UpgradePowerUpCallback onUpgradePowerUp; final PersistantGameState gameState; @override Widget build(BuildContext context) { return _buildCenterArea(); } Widget _buildCenterArea() { return _buildUpgradePanel(); } Widget _buildUpgradePanel() { return Column( children: [ const Text("Upgrade Laser"), _buildLaserUpgradeButton(), const Text("Upgrade Power-Ups"), Row(children: [ _buildPowerUpButton(PowerUpType.shield), _buildPowerUpButton(PowerUpType.sideLaser), _buildPowerUpButton(PowerUpType.speedBoost), _buildPowerUpButton(PowerUpType.speedLaser), ], mainAxisAlignment: MainAxisAlignment.center) ], mainAxisAlignment: MainAxisAlignment.center, key: const Key("upgradePanel"), ); } Widget _buildPowerUpButton(PowerUpType type) { return Padding( padding: const EdgeInsets.all(8.0), child: Column( children: [ TextureButton( texture: _spriteSheetUI['btn_powerup_${type.index}.png']!, width: 57.0, height: 57.0, label: "${gameState.powerUpUpgradePrice(type)}", labelOffset: const Offset(3.0, 20.5), textStyle: const TextStyle( fontFamily: "Orbitron", fontSize: 11.0, color: _darkTextColor), textAlign: TextAlign.center, onPressed: () => onUpgradePowerUp(type), ), Padding( padding: const EdgeInsets.all(5.0), child: Text("Lvl ${gameState.powerupLevel(type) + 1}", style: const TextStyle(fontSize: 12.0))) ], ), ); } Widget _buildLaserUpgradeButton() { return Padding( padding: const EdgeInsets.fromLTRB(0.0, 8.0, 0.0, 18.0), child: Stack( children: [ TextureButton( texture: _spriteSheetUI['btn_laser_upgrade.png']!, width: 137.0, height: 63.0, label: "${gameState.laserUpgradePrice()}", labelOffset: const Offset(2.0, 20.0), textStyle: const TextStyle( fontFamily: "Orbitron", fontSize: 12.0, color: _darkTextColor), textAlign: TextAlign.center, onPressed: onUpgradeLaser, ), Positioned( child: LaserDisplay(level: gameState.laserLevel), left: 19.5, top: 14.0, ), Positioned( child: LaserDisplay(level: gameState.laserLevel + 1), right: 19.5, top: 14.0, ) ], ), ); } } class BottomBar extends StatelessWidget { const BottomBar({ required this.onPlay, required this.gameState, required this.onStartLevelUp, required this.onStartLevelDown, Key? key, }) : super(key: key); final VoidCallback onPlay; final VoidCallback onStartLevelUp; final VoidCallback onStartLevelDown; final PersistantGameState gameState; @override Widget build(BuildContext context) { return Stack( children: [ Positioned( left: 18.0, top: 14.0, child: TextureImage( texture: _spriteSheetUI['level_display.png']!, width: 62.0, height: 62.0, ), ), Positioned( left: 18.0, top: 14.0, child: TextureImage( texture: _spriteSheetUI[ 'level_display_${gameState.currentStartingLevel + 1}.png']!, width: 62.0, height: 62.0, ), ), Positioned( left: 85.0, top: 14.0, child: TextureButton( texture: _spriteSheetUI['btn_level_up.png']!, width: 30.0, height: 30.0, onPressed: onStartLevelUp, ), ), Positioned( left: 85.0, top: 46.0, child: TextureButton( texture: _spriteSheetUI['btn_level_down.png']!, width: 30.0, height: 30.0, onPressed: onStartLevelDown, ), ), Positioned( left: 120.0, top: 14.0, child: TextureButton( onPressed: onPlay, texture: _spriteSheetUI['btn_play.png']!, label: "PLAY", textStyle: const TextStyle( fontFamily: "Orbitron", fontSize: 28.0, letterSpacing: 3.0, color: Color(0xffffffff), ), textAlign: TextAlign.center, width: 181.0, height: 62.0, ), ), ], ); } } class MainSceneBackground extends StatefulWidget { const MainSceneBackground({Key? key}) : super(key: key); @override MainSceneBackgroundState createState() => MainSceneBackgroundState(); } class MainSceneBackgroundState extends State { late MainSceneBackgroundNode _backgroundNode; @override void initState() { super.initState(); _backgroundNode = MainSceneBackgroundNode(); } @override Widget build(BuildContext context) { return SpriteWidget( _backgroundNode, transformMode: SpriteBoxTransformMode.fixedWidth, ); } } class MainSceneBackgroundNode extends NodeWithSize { late Sprite _bgTop; late Sprite _bgBottom; late RepeatedImage _background; late RepeatedImage _nebula; MainSceneBackgroundNode() : super(const Size(320.0, 320.0)) { // Add background _background = RepeatedImage(_imageMap["assets/starfield.png"]!); addChild(_background); StarField starField = StarField(_spriteSheet, 200, true); addChild(starField); // Add nebula _nebula = RepeatedImage(_imageMap["assets/nebula.png"]!, BlendMode.plus); addChild(_nebula); _bgTop = Sprite.fromImage(_imageMap["assets/ui_bg_top.png"]!); _bgTop.pivot = Offset.zero; _bgTop.size = const Size(320.0, 108.0); addChild(_bgTop); _bgBottom = Sprite.fromImage(_imageMap["assets/ui_bg_bottom.png"]!); _bgBottom.pivot = const Offset(0.0, 1.0); _bgBottom.size = const Size(320.0, 97.0); addChild(_bgBottom); } @override void paint(Canvas canvas) { canvas.drawRect(const Rect.fromLTWH(0.0, 0.0, 320.0, 320.0), Paint()..color = const Color(0xff000000)); super.paint(canvas); } @override void spriteBoxPerformedLayout() { _bgBottom.position = Offset(0.0, spriteBox!.visibleArea!.size.height); } @override void update(double dt) { _background.move(10.0 * dt); _nebula.move(100.0 * dt); } } class LaserDisplay extends StatelessWidget { const LaserDisplay({ required this.level, Key? key, }) : super(key: key); final int level; @override Widget build(BuildContext context) { return IgnorePointer( child: SizedBox( child: SpriteWidget(LaserDisplayNode(level)), width: 26.0, height: 26.0, ), ); } } class LaserDisplayNode extends NodeWithSize { LaserDisplayNode(int level) : super(const Size(16.0, 16.0)) { Node placementNode = Node(); placementNode.position = const Offset(8.0, 8.0); placementNode.scale = 0.7; addChild(placementNode); addLaserSprites(placementNode, level, 0.0, _spriteSheet); } } ================================================ FILE: lib/persistant_game_state.dart ================================================ part of game; class PersistantGameState { Future load() async { final prefs = await SharedPreferences.getInstance(); final json = prefs.getString('game_prefs'); if (json == null) return; JsonDecoder decoder = const JsonDecoder(); Map data = decoder.convert(json); coins = data['coins']; _powerupLevels = data['powerUpLevels'].cast(); _currentStartingLevel = data['currentStartingLevel']; maxStartingLevel = data['maxStartingLevel']; laserLevel = data['laserLevel']; _lastScore = data['lastScore']; weeklyBestScore = data['bestScore']; } Future store() async { final prefs = await SharedPreferences.getInstance(); Map data = { 'coins': coins, 'powerUpLevels': _powerupLevels, 'currentStartingLevel': _currentStartingLevel, 'maxStartingLevel': maxStartingLevel, 'laserLevel': laserLevel, 'lastScore': _lastScore, 'bestScore': weeklyBestScore }; JsonEncoder encoder = const JsonEncoder(); String json = encoder.convert(data); prefs.setString('game_prefs', json); } int coins = 0; List _powerupLevels = [0, 0, 0, 0]; int powerupLevel(PowerUpType type) { return _powerupLevels[type.index]; } int maxPowerUpLevel = 8; int _currentStartingLevel = 0; int get currentStartingLevel => _currentStartingLevel; set currentStartingLevel(int currentStartingLevel) { if (currentStartingLevel >= 0 && currentStartingLevel <= maxStartingLevel) { _currentStartingLevel = currentStartingLevel; } } int maxStartingLevel = 0; int laserLevel = 0; int maxLaserLevel = 11; int _lastScore = 0; int get lastScore => _lastScore; set lastScore(int lastScore) { _lastScore = lastScore; if (lastScore > weeklyBestScore) weeklyBestScore = lastScore; } int weeklyBestScore = 0; int powerUpUpgradePrice(PowerUpType type) { int level = powerupLevel(type) + 1; return level * 50 + 50; } int powerUpFrames(PowerUpType type) { int level = powerupLevel(type); if (type == PowerUpType.speedBoost) { return 150 + 25 * level; } else { return 300 + 50 * level; } } bool upgradePowerUp(PowerUpType type) { int price = powerUpUpgradePrice(type); if (coins >= price && _powerupLevels[type.index] < maxPowerUpLevel) { coins -= price; _powerupLevels[type.index] += 1; store(); return true; } else { return false; } } int laserUpgradePrice() { return laserLevel * 100 + 200; } bool upgradeLaser() { if (coins >= laserUpgradePrice() && laserLevel < maxLaserLevel) { coins -= laserUpgradePrice(); laserLevel++; store(); return true; } else { return false; } } void reachedLevel(int level) { if (level > maxStartingLevel && level < 9) { maxStartingLevel = level; _currentStartingLevel = level; } store(); } } ================================================ FILE: lib/player_state.dart ================================================ part of game; class PlayerState extends Node { PlayerState(this._sheetUI, this._sheetGame, this._gameState) { // Score display _spriteBackgroundScore = Sprite(texture: _sheetUI["scoreboard.png"]!); _spriteBackgroundScore.pivot = const Offset(1.0, 0.0); _spriteBackgroundScore.scale = 0.35; _spriteBackgroundScore.position = const Offset(240.0, 10.0); addChild(_spriteBackgroundScore); _scoreDisplay = ScoreDisplay(_sheetUI); _scoreDisplay.position = const Offset(349.0, 49.0); _spriteBackgroundScore.addChild(_scoreDisplay); // Coin display _spriteBackgroundCoins = Sprite(texture: _sheetUI["coinboard.png"]!); _spriteBackgroundCoins.pivot = const Offset(1.0, 0.0); _spriteBackgroundCoins.scale = 0.35; _spriteBackgroundCoins.position = const Offset(105.0, 10.0); addChild(_spriteBackgroundCoins); _coinDisplay = ScoreDisplay(_sheetUI); _coinDisplay.position = const Offset(252.0, 49.0); _spriteBackgroundCoins.addChild(_coinDisplay); laserLevel = _gameState.laserLevel; } final SpriteSheet _sheetUI; final SpriteSheet _sheetGame; final PersistantGameState _gameState; int laserLevel = 0; static const double normalScrollSpeed = 2.0; double scrollSpeed = normalScrollSpeed; double _scrollSpeedTarget = normalScrollSpeed; EnemyBoss? boss; late Sprite _spriteBackgroundScore; late ScoreDisplay _scoreDisplay; late Sprite _spriteBackgroundCoins; late ScoreDisplay _coinDisplay; int get score => _scoreDisplay.score; set score(int score) { _scoreDisplay.score = score; flashBackgroundSprite(_spriteBackgroundScore); } int get coins => _coinDisplay.score; void addCoin(Coin c) { // Animate coin to the top of the screen Offset startPos = convertPointFromNode(Offset.zero, c); Offset finalPos = const Offset(30.0, 30.0); Offset middlePos = Offset((startPos.dx + finalPos.dx) / 2.0 + 50.0, (startPos.dy + finalPos.dy) / 2.0); List path = [startPos, middlePos, finalPos]; Sprite sprite = Sprite(texture: _sheetGame["coin.png"]!); sprite.scale = 0.7; MotionSpline spline = MotionSpline( setter: (Offset a) => sprite.position = a, points: path, duration: 0.5, ); spline.tension = 0.25; MotionTween rotate = MotionTween( setter: (a) => sprite.rotation = a, start: 0.0, end: 360.0, duration: 0.5, ); MotionTween scale = MotionTween( setter: (a) => sprite.scale = a, start: 0.7, end: 1.2, duration: 0.5, ); MotionGroup group = MotionGroup(motions: [spline, rotate, scale]); sprite.motions.run( MotionSequence( motions: [ group, MotionRemoveNode(node: sprite), MotionCallFunction( callback: () { _coinDisplay.score += 1; flashBackgroundSprite(_spriteBackgroundCoins); }, ), ], ), ); addChild(sprite); } void activatePowerUp(PowerUpType type) { if (type == PowerUpType.shield) { _shieldFrames += _gameState.powerUpFrames(type); } else if (type == PowerUpType.sideLaser) { _sideLaserFrames += _gameState.powerUpFrames(type); } else if (type == PowerUpType.speedLaser) { _speedLaserFrames += _gameState.powerUpFrames(type); } else if (type == PowerUpType.speedBoost) { _speedBoostFrames += _gameState.powerUpFrames(type); _shieldFrames += _gameState.powerUpFrames(type) + 60; } } int _shieldFrames = 0; bool get shieldActive => _shieldFrames > 0 || _speedBoostFrames > 0; bool get shieldDeactivating => math.max(_shieldFrames, _speedBoostFrames) > 0 && math.max(_shieldFrames, _speedBoostFrames) < 60; int _sideLaserFrames = 0; bool get sideLaserActive => _sideLaserFrames > 0; int _speedLaserFrames = 0; bool get speedLaserActive => _speedLaserFrames > 0; int _speedBoostFrames = 0; bool get speedBoostActive => _speedBoostFrames > 0; void flashBackgroundSprite(Sprite sprite) { sprite.motions.stopAll(); MotionTween flash = MotionTween( setter: (a) => sprite.colorOverlay = a, start: const Color(0x66ccfff0), end: const Color(0x00ccfff0), duration: 0.3, ); sprite.motions.run(flash); } @override void update(double dt) { if (_shieldFrames > 0) { _shieldFrames--; } if (_sideLaserFrames > 0) { _sideLaserFrames--; } if (_speedLaserFrames > 0) { _speedLaserFrames--; } if (_speedBoostFrames > 0) { _speedBoostFrames--; } // Update speed if (boss != null) { Offset globalBossPos = boss!.convertPointToBoxSpace(Offset.zero); if (globalBossPos.dy > (_gameSizeHeight - 400.0)) { _scrollSpeedTarget = 0.0; } else { _scrollSpeedTarget = normalScrollSpeed; } } else { if (speedBoostActive) { _scrollSpeedTarget = normalScrollSpeed * 6.0; } else { _scrollSpeedTarget = normalScrollSpeed; } } scrollSpeed = GameMath.filter(scrollSpeed, _scrollSpeedTarget, 0.1); } } class ScoreDisplay extends Node { ScoreDisplay(this._sheetUI); int _score = 0; int get score => _score; set score(int score) { _score = score; _dirtyScore = true; } final SpriteSheet _sheetUI; bool _dirtyScore = true; @override void update(double dt) { if (_dirtyScore) { removeAllChildren(); String scoreStr = _score.toString(); double xPos = -37.0; for (int i = scoreStr.length - 1; i >= 0; i--) { String numStr = scoreStr.substring(i, i + 1); Sprite numSprite = Sprite(texture: _sheetUI["number_$numStr.png"]!); numSprite.position = Offset(xPos, 0.0); addChild(numSprite); xPos -= 37.0; } _dirtyScore = false; } } } ================================================ FILE: lib/power_bar.dart ================================================ part of game; class PowerBar extends NodeWithSize { PowerBar(Size size, [this.power = 1.0]) : super(size); double power; final Paint _paintFill = Paint()..color = const Color(0xffffffff); final Paint _paintOutline = Paint() ..color = const Color(0xffffffff) ..strokeWidth = 1.0 ..style = ui.PaintingStyle.stroke; @override void paint(Canvas canvas) { applyTransformForPivot(canvas); canvas.drawRect( Rect.fromLTRB(0.0, 0.0, size.width - 0.0, size.height - 0.0), _paintOutline, ); canvas.drawRect( Rect.fromLTRB(2.0, 2.0, (size.width - 2.0) * power, size.height - 2.0), _paintFill, ); } } ================================================ FILE: lib/render_coordinate_system.dart ================================================ part of game; enum CoordinateSystemType { fixedWidth, fixedHeight, stretch, } class RenderCoordinateSystem extends RenderProxyBox { RenderCoordinateSystem({ required Size systemSize, required CoordinateSystemType systemType, RenderBox? child, }) : super(child) { _systemSize = systemSize; _systemType = systemType; } Size get systemSize => _systemSize; late Size _systemSize; set systemSize(Size systemSize) { if (_systemSize == systemSize) return; _systemSize = systemSize; markNeedsPaint(); } CoordinateSystemType get systemType => _systemType; late CoordinateSystemType _systemType; set systemType(CoordinateSystemType systemType) { if (_systemType == systemType) return; _systemType = systemType; markNeedsPaint(); } Matrix4 get _effectiveTransform { double scaleX = 1.0; double scaleY = 1.0; switch (systemType) { case CoordinateSystemType.stretch: scaleX = size.width / systemSize.width; scaleY = size.height / systemSize.height; break; case CoordinateSystemType.fixedWidth: scaleX = size.width / systemSize.width; scaleY = scaleX; break; case CoordinateSystemType.fixedHeight: scaleY = size.height / systemSize.height; scaleX = scaleY; break; default: assert(false); } Matrix4 transformMatrix = Matrix4.identity(); transformMatrix.scale(scaleX, scaleY); return transformMatrix; } @override bool hitTest(HitTestResult result, {required Offset position}) { Matrix4 inverse = Matrix4.zero(); // TODO(abarth): Check the determinant for degeneracy. inverse.copyInverse(_effectiveTransform); Vector3 position3 = Vector3(position.dx, position.dy, 0.0); Vector3 transformed3 = inverse.transform3(position3); Offset transformed = Offset(transformed3.x, transformed3.y); return super.hitTest(result as BoxHitTestResult, position: transformed); } @override void paint(PaintingContext context, Offset offset) { if (child != null) { Matrix4 transform = _effectiveTransform; Offset? childOffset = MatrixUtils.getAsTranslation(transform); if (childOffset == null) { context.pushTransform(needsCompositing, offset, transform, super.paint); } else { super.paint(context, offset + childOffset); } } } @override void applyPaintTransform(RenderObject child, Matrix4 transform) { transform.multiply(_effectiveTransform); super.applyPaintTransform(child, transform); } // void debugDescribeChildren(List settings) { // super.debugDescribeChildren(settings); // settings.add('systemSize: $systemSize'); // settings.add('systemType: $systemType'); // } @override bool get sizedByParent => true; @override void performResize() { size = constraints.biggest; } // Perform layout @override void performLayout() { double xScale = _effectiveTransform[0]; double yScale = _effectiveTransform[5]; if (child != null) { child!.layout(BoxConstraints.tightFor( width: size.width / xScale, height: size.height / yScale)); } } } ================================================ FILE: lib/repeated_image.dart ================================================ part of game; class RepeatedImage extends Node { late Sprite _sprite0; late Sprite _sprite1; RepeatedImage(ui.Image image, [ui.BlendMode? mode]) { _sprite0 = Sprite.fromImage(image); _sprite0.size = const Size(1024.0, 1024.0); _sprite0.pivot = Offset.zero; _sprite1 = Sprite.fromImage(image); _sprite1.size = const Size(1024.0, 1024.0); _sprite1.pivot = Offset.zero; _sprite1.position = const Offset(0.0, -1024.0); if (mode != null) { _sprite0.blendMode = mode; _sprite1.blendMode = mode; } addChild(_sprite0); addChild(_sprite1); } void move(double dy) { double yPos = (position.dy + dy) % 1024.0; position = Offset(0.0, yPos); } } ================================================ FILE: lib/sound_assets.dart ================================================ part of game; class SoundAssets { final AudioPlayer _musicPlayer = AudioPlayer(); final Map _effectPlayers = {}; SoundAssets(this.bundle); final AssetBundle bundle; Future loadEffect(String name) async { final player = AudioPlayer(); player.setAsset(_effectPathForName(name)); await player.load(); _effectPlayers[name] = player; } Future loadMusic(String name) async { final player = AudioPlayer(); player.setAsset(_musicPathForName(name)); await player.load(); } void playEffect(String name) { _effectPlayers[name]?.setAsset(_effectPathForName(name)); _effectPlayers[name]?.play(); } void playMusic(String name) { _musicPlayer.setAsset(_musicPathForName(name)); _musicPlayer.setLoopMode(LoopMode.all); _musicPlayer.play(); } String _effectPathForName(String name) => 'assets/$name.wav'; String _musicPathForName(String name) => 'assets/$name.mp3'; } ================================================ FILE: lib/star_field.dart ================================================ part of game; class StarField extends NodeWithSize { late ui.Image _image; final SpriteSheet _spriteSheet; final int _numStars; final bool _autoScroll; List _starPositions = []; List _starScales = []; List _rects = []; List? _colors; final double _padding = 50.0; Size _paddedSize = Size.zero; final Paint _paint = Paint() ..filterQuality = ui.FilterQuality.low ..isAntiAlias = false ..blendMode = ui.BlendMode.plus; StarField(this._spriteSheet, this._numStars, [this._autoScroll = false]) : super(Size.zero) { _image = _spriteSheet.image; addStars(); } void addStars() { _starPositions = []; _starScales = []; _colors = []; _rects = []; if (spriteBox == null) { size = const Size(2048, 2048); } else { size = spriteBox!.visibleArea!.size; } _paddedSize = Size(size.width + _padding * 2.0, size.height + _padding * 2.0); for (int i = 0; i < _numStars; i++) { _starPositions.add( Offset( randomDouble() * _paddedSize.width, randomDouble() * _paddedSize.height, ), ); _starScales.add(randomDouble() * 0.4); _colors!.add( Color.fromARGB( (255.0 * (randomDouble() * 0.5 + 0.5)).toInt(), 255, 255, 255, ), ); _rects.add(_spriteSheet["star_${randomInt(2)}.png"]!.frame); } } @override void spriteBoxPerformedLayout() { addStars(); } @override void paint(Canvas canvas) { // Create a transform for each star List transforms = []; for (int i = 0; i < _numStars; i++) { ui.RSTransform transform = ui.RSTransform(_starScales[i], 0.0, _starPositions[i].dx - _padding, _starPositions[i].dy - _padding); transforms.add(transform); } // Draw the stars canvas.drawAtlas(_image, transforms, _rects, _colors, ui.BlendMode.modulate, null, _paint); } void move(double dx, double dy) { for (int i = 0; i < _numStars; i++) { double xPos = _starPositions[i].dx; double yPos = _starPositions[i].dy; double scale = _starScales[i]; xPos += dx * scale; yPos += dy * scale; if (xPos >= _paddedSize.width) xPos -= _paddedSize.width; if (xPos < 0) xPos += _paddedSize.width; if (yPos >= _paddedSize.height) yPos -= _paddedSize.height; if (yPos < 0) yPos += _paddedSize.height; _starPositions[i] = Offset(xPos, yPos); } } @override void update(double dt) { if (_autoScroll) { move(0.0, dt * 100.0); } } } ================================================ FILE: lib/widgets.dart ================================================ part of game; class TextureImage extends StatelessWidget { const TextureImage({ Key? key, required this.texture, this.width = 128.0, this.height = 128.0, }) : super(key: key); final SpriteTexture texture; final double width; final double height; @override Widget build(BuildContext context) { return SizedBox( width: width, height: height, child: CustomPaint( painter: TextureImagePainter(texture, width, height), ), ); } } class TextureImagePainter extends CustomPainter { TextureImagePainter(this.texture, this.width, this.height); final SpriteTexture texture; final double width; final double height; @override void paint(Canvas canvas, Size size) { canvas.save(); canvas.scale( size.width / texture.size.width, size.height / texture.size.height, ); texture.drawTexture(canvas, Offset.zero, Paint()); canvas.restore(); } @override bool shouldRepaint(TextureImagePainter oldDelegate) { return oldDelegate.texture != texture || oldDelegate.width != width || oldDelegate.height != height; } } class TextureButton extends StatefulWidget { const TextureButton( {Key? key, required this.onPressed, required this.texture, this.textureDown, this.width = 128.0, this.height = 128.0, this.label, this.textStyle, this.textAlign = TextAlign.center, this.labelOffset = Offset.zero}) : super(key: key); final VoidCallback onPressed; final SpriteTexture texture; final SpriteTexture? textureDown; final TextStyle? textStyle; final TextAlign textAlign; final String? label; final double width; final double height; final Offset labelOffset; @override TextureButtonState createState() => TextureButtonState(); } class TextureButtonState extends State { bool _highlight = false; @override Widget build(BuildContext context) { return GestureDetector( child: SizedBox( width: widget.width, height: widget.height, child: CustomPaint(painter: TextureButtonPainter(widget, _highlight))), onTapDown: (_) { setState(() { _highlight = true; }); }, onTap: () { setState(() { _highlight = false; }); widget.onPressed(); }, onTapCancel: () { setState(() { _highlight = false; }); }); } } class TextureButtonPainter extends CustomPainter { TextureButtonPainter(this.config, this.highlight); final TextureButton config; final bool highlight; @override void paint(Canvas canvas, Size size) { canvas.save(); if (highlight) { // Draw down state if (config.textureDown != null) { canvas.scale( size.width / config.textureDown!.size.width, size.height / config.textureDown!.size.height, ); config.textureDown!.drawTexture(canvas, Offset.zero, Paint()); } else { canvas.scale( size.width / config.texture.size.width, size.height / config.texture.size.height, ); config.texture.drawTexture( canvas, Offset.zero, Paint() ..colorFilter = const ColorFilter.mode( Color(0x66000000), BlendMode.srcATop, ), ); } } else { // Draw up state canvas.scale(size.width / config.texture.size.width, size.height / config.texture.size.height); config.texture.drawTexture(canvas, Offset.zero, Paint()); } canvas.restore(); if (config.label != null) { TextStyle style; if (config.textStyle == null) { style = const TextStyle(fontSize: 24.0, fontWeight: FontWeight.w700); } else { style = config.textStyle!; } TextSpan textSpan = TextSpan(style: style, text: config.label); TextPainter painter = TextPainter( text: textSpan, textAlign: config.textAlign, textDirection: TextDirection.ltr, ); painter.layout(minWidth: size.width, maxWidth: size.width); painter.paint( canvas, Offset(0.0, size.height / 2.0 - painter.height / 2.0) + config.labelOffset); } } @override bool shouldRepaint(TextureButtonPainter oldDelegate) { return oldDelegate.highlight != highlight || oldDelegate.config.texture != config.texture || oldDelegate.config.textureDown != config.textureDown || oldDelegate.config.textStyle != config.textStyle || oldDelegate.config.label != config.label || oldDelegate.config.width != config.width || oldDelegate.config.height != config.height; } } class AppFrame extends StatelessWidget { const AppFrame({Key? key, required this.child}) : super(key: key); final Widget child; static const _minRatio = 1.5; @override Widget build(BuildContext context) { return LayoutBuilder(builder: (context, constraints) { final ratio = constraints.maxHeight / constraints.maxWidth; if (ratio > _minRatio) { return child; } else { final width = constraints.maxHeight / _minRatio; final horizontalInset = (constraints.maxWidth - width) / 2; return Stack( children: [ Container( color: const Color(0xFF222244), ), Positioned( child: ClipRect( child: child, ), top: 0, bottom: 0, left: horizontalInset, right: horizontalInset, ), ], ); } }); } } ================================================ FILE: pubspec.yaml ================================================ name: spaceblast publish_to: 'none' description: A SpriteWidget demo. version: 0.9.0 environment: sdk: '>=2.12.0 <3.0.0' dependencies: flutter: sdk: flutter spritewidget: path: ../spritewidget cupertino_icons: ^1.0.4 shared_preferences: ^2.0.13 just_audio: ^0.9.20 dev_dependencies: flutter_test: sdk: flutter flutter_lints: 1.0.0 flutter: uses-material-design: true assets: - assets/nebula.png - assets/sprites.json - assets/sprites.png - assets/starfield.png - assets/game_ui.png - assets/game_ui.json - assets/ui_bg_top.png - assets/ui_bg_bottom.png - assets/ui_popup.png - assets/music_game.mp3 - assets/music_boss.mp3 - assets/music_intro.mp3 - assets/explosion_0.wav - assets/explosion_1.wav - assets/explosion_2.wav - assets/explosion_boss.wav - assets/explosion_player.wav - assets/laser.wav - assets/hit.wav - assets/levelup.wav - assets/pickup_0.wav - assets/pickup_1.wav - assets/pickup_2.wav - assets/pickup_powerup.wav - assets/click.wav - assets/buy_upgrade.wav - fonts/Orbitron-Medium.ttf fonts: - family: Orbitron fonts: - asset: fonts/Orbitron-Medium.ttf ================================================ FILE: web/index.html ================================================ SpaceBlast - SpriteWidget demo
================================================ FILE: web/manifest.json ================================================ { "name": "spaceblast", "short_name": "spaceblast", "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" }, { "src": "icons/Icon-maskable-192.png", "sizes": "192x192", "type": "image/png", "purpose": "maskable" }, { "src": "icons/Icon-maskable-512.png", "sizes": "512x512", "type": "image/png", "purpose": "maskable" } ] } ================================================ FILE: web/styles.css ================================================ html, body { background-color: #333333 } .center { margin: 0; position: absolute; top: 50%; left: 50%; margin-right: -50%; transform: translate(-50%, -50%) }