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<double>(
setter: (a) => spriteRing.scale = a,
start: 0.2,
end: 1.0,
duration: 0.75,
);
Motion scaleAndRemove = MotionSequence(
motions: <Motion>[scale, MotionRemoveNode(node: spriteRing)],
);
Motion fade = MotionTween<double>(
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<double>(
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<double>(
setter: (a) => spriteFlare.opacity = a,
start: 0.0,
end: 1.0,
duration: 0.25 * multiplier,
);
Motion fadeOut = MotionTween<double>(
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<double>(
setter: (a) => star.rotation = a,
start: rotationStart,
end: rotationEnd,
duration: 0.2,
);
motions.run(rotate);
MotionTween fade = MotionTween<double>(
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<double>(
setter: (a) => _opacity = a,
start: 1.0,
end: 0.0,
duration: duration,
);
MotionSequence seq = MotionSequence(
motions: <Motion>[
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<Laser> lasers = <Laser>[];
for (Node node in _level.children) {
if (node is Laser) lasers.add(node);
}
List<GameObject> damageables = <GameObject>[];
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<Node> nodes = List<Node>.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<int> 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<int> 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<Color> 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<Sprite> sprites = <Sprite>[];
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<double>(
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<double>(
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 = <Constraint>[ConstraintRotationToMovement(dampening: 0.5)];
}
final double _swirlSpacing = 80.0;
_addRandomSquare(List<Offset> offsets, double x, double y) {
double xMove = (randomBool()) ? _swirlSpacing : -_swirlSpacing;
double yMove = (randomBool()) ? _swirlSpacing : -_swirlSpacing;
if (randomBool()) {
offsets.addAll(<Offset>[
Offset(x, y),
Offset(xMove + x, y),
Offset(xMove + x, yMove + y),
Offset(x, yMove + y),
Offset(x, y)
]);
} else {
offsets.addAll(<Offset>[
Offset(x, y),
Offset(x, y + yMove),
Offset(xMove + x, yMove + y),
Offset(xMove + x, y),
Offset(x, y)
]);
}
}
@override
void setupActions() {
List<Offset> offsets = <Offset>[];
_addRandomSquare(offsets, -_swirlSpacing, 0.0);
_addRandomSquare(offsets, _swirlSpacing, 0.0);
offsets.add(Offset(-_swirlSpacing, 0.0));
List<Offset> points = <Offset>[];
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 = <Constraint>[
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 = <Constraint>[
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 = <Constraint>[
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<Color>(
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<double>(
setter: (a) => _sprite.rotation = a,
start: 0.0,
end: 360.0,
duration: 1.0,
);
motions.run(MotionRepeatForever(motion: rotate));
// Fade in
MotionTween fadeIn = MotionTween<double>(
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<PowerUpType> _powerUpTypes = List<PowerUpType>.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<double>(
setter: (a) => _sprite.rotation = a,
start: 0.0,
end: 360.0,
duration: 1.0,
);
motions.run(MotionRepeatForever(motion: rotate));
// Fade in
MotionTween fadeIn = MotionTween<double>(
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(<String>[
'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 = <Future>[];
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<GameDemo> {
final GlobalKey<NavigatorState> _navigatorKey = GlobalKey<NavigatorState>();
@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<GameScene> createState() => GameSceneState();
}
class GameSceneState extends State<GameScene> {
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<MainScene> createState() => MainSceneState();
}
class MainSceneState extends State<MainScene> {
@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: <Widget>[
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: <Widget>[
const MainSceneBackground(),
Column(
children: <Widget>[
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: <Widget>[
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: <Widget>[
const Text("Upgrade Laser"),
_buildLaserUpgradeButton(),
const Text("Upgrade Power-Ups"),
Row(children: <Widget>[
_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: <Widget>[
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: <Widget>[
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: <Widget>[
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<MainSceneBackground> {
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<int>();
_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<int> _powerupLevels = <int>[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<Offset> path = <Offset>[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<double>(
setter: (a) => sprite.rotation = a,
start: 0.0,
end: 360.0,
duration: 0.5,
);
MotionTween scale = MotionTween<double>(
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<Color>(
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<String> 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<String, AudioPlayer> _effectPlayers = {};
SoundAssets(this.bundle);
final AssetBundle bundle;
Future<void> loadEffect(String name) async {
final player = AudioPlayer();
player.setAsset(_effectPathForName(name));
await player.load();
_effectPlayers[name] = player;
}
Future<void> 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<Offset> _starPositions = [];
List<double> _starScales = [];
List<Rect> _rects = [];
List<Color>? _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<ui.RSTransform> transforms = <ui.RSTransform>[];
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<TextureButton> {
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
================================================
<!DOCTYPE html>
<html>
<head>
<!--
If you are serving your web app in a path other than the root, change the
href value below to reflect the base path you are serving from.
The path provided below has to start and end with a slash "/" in order for
it to work correctly.
For more details:
* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base
This is a placeholder for base href that will be replaced by the value of
the `--base-href` argument provided to `flutter build`.
-->
<base href="$FLUTTER_BASE_HREF">
<meta charset="UTF-8">
<meta content="IE=Edge" http-equiv="X-UA-Compatible">
<meta name="description" content="A new Flutter project.">
<!-- iOS meta tags & icons -->
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="apple-mobile-web-app-title" content="spaceblast">
<link rel="apple-touch-icon" href="icons/Icon-192.png">
<!-- Favicon -->
<link rel="icon" type="image/png" href="favicon.png"/>
<title>SpaceBlast - SpriteWidget demo</title>
<link rel="manifest" href="manifest.json">
<link rel="stylesheet" type="text/css" href="styles.css">
</head>
<body>
<!-- This script installs service_worker.js to provide PWA functionality to
application. For more information, see:
https://developers.google.com/web/fundamentals/primers/service-workers -->
<script>
var serviceWorkerVersion = null;
var scriptLoaded = false;
function loadMainDartJs() {
if (scriptLoaded) {
return;
}
scriptLoaded = true;
var scriptTag = document.createElement('script');
scriptTag.src = 'main.dart.js';
scriptTag.type = 'application/javascript';
document.body.append(scriptTag);
}
if ('serviceWorker' in navigator) {
// Service workers are supported. Use them.
window.addEventListener('load', function () {
// Wait for registration to finish before dropping the <script> tag.
// Otherwise, the browser will load the script multiple times,
// potentially different versions.
var serviceWorkerUrl = 'flutter_service_worker.js?v=' + serviceWorkerVersion;
navigator.serviceWorker.register(serviceWorkerUrl)
.then((reg) => {
function waitForActivation(serviceWorker) {
serviceWorker.addEventListener('statechange', () => {
if (serviceWorker.state == 'activated') {
console.log('Installed new service worker.');
loadMainDartJs();
}
});
}
if (!reg.active && (reg.installing || reg.waiting)) {
// No active web worker and we have installed or are installing
// one for the first time. Simply wait for it to activate.
waitForActivation(reg.installing || reg.waiting);
} else if (!reg.active.scriptURL.endsWith(serviceWorkerVersion)) {
// When the app updates the serviceWorkerVersion changes, so we
// need to ask the service worker to update.
console.log('New service worker available.');
reg.update();
waitForActivation(reg.installing);
} else {
// Existing service worker is still good.
console.log('Loading app from service worker.');
loadMainDartJs();
}
});
// If service worker doesn't succeed in a reasonable amount of time,
// fallback to plaint <script> tag.
setTimeout(() => {
if (!scriptLoaded) {
console.warn(
'Failed to load app from service worker. Falling back to plain <script> tag.',
);
loadMainDartJs();
}
}, 4000);
});
} else {
// Service workers not supported. Just drop the <script> tag.
loadMainDartJs();
}
</script>
<div class="center">
<img src="img/spinner.gif" width="64" height="64" />
</div>
</body>
</html>
================================================
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%)
}
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
SYMBOL INDEX (181 symbols across 17 files)
FILE: lib/coordinate_system.dart
class CoordinateSystem (line 3) | class CoordinateSystem extends SingleChildRenderObjectWidget {
method createRenderObject (line 15) | RenderCoordinateSystem createRenderObject(BuildContext context)
method updateRenderObject (line 23) | void updateRenderObject(
FILE: lib/custom_actions.dart
type PointSetterCallback (line 3) | typedef PointSetterCallback = void Function(Offset value);
class ActionCircularMove (line 5) | class ActionCircularMove extends MotionInterval {
method update (line 22) | void update(double t)
class ActionOscillate (line 31) | class ActionOscillate extends MotionInterval {
method update (line 40) | void update(double t)
FILE: lib/explosions.dart
class Explosion (line 3) | class Explosion extends Node {
class ExplosionBig (line 9) | class ExplosionBig extends Explosion {
class ExplosionMini (line 126) | class ExplosionMini extends Explosion {
FILE: lib/flash.dart
class Flash (line 3) | class Flash extends NodeWithSize {
method paint (line 25) | void paint(Canvas canvas)
FILE: lib/game_demo_node.dart
type GameOverCallback (line 10) | typedef GameOverCallback = void Function(
class GameDemoNode (line 13) | class GameDemoNode extends NodeWithSize {
method spriteBoxPerformedLayout (line 90) | void spriteBoxPerformedLayout()
method update (line 96) | void update(double dt)
method addObjects (line 183) | void addObjects()
method addLevelChunk (line 191) | void addLevelChunk(int chunk, double yPos)
method fire (line 220) | void fire()
method killShip (line 242) | void killShip()
class Level (line 269) | class Level extends Node {
method scroll (line 276) | double scroll(double scrollSpeed)
FILE: lib/game_object_factory.dart
class GameObjectFactory (line 5) | class GameObjectFactory {
method addAsteroids (line 13) | void addAsteroids(int level, double yPos)
method addEnemyScoutSwarm (line 33) | void addEnemyScoutSwarm(int level, double yPos)
method addEnemyDestroyerSwarm (line 69) | void addEnemyDestroyerSwarm(int level, double yPos)
method addGameObject (line 103) | void addGameObject(GameObject obj, Offset pos)
method addBossFight (line 110) | void addBossFight(int level, double yPos)
function addLaserSprites (line 152) | void addLaserSprites(Node node, int level, double r, SpriteSheet sheet)
FILE: lib/game_objects.dart
class GameObject (line 3) | abstract class GameObject extends Node {
method collidingWith (line 21) | bool collidingWith(GameObject obj)
method move (line 26) | void move()
method removeIfOffscreen (line 28) | void removeIfOffscreen(double scroll)
method destroy (line 34) | void destroy()
method collect (line 51) | void collect()
method addDamage (line 55) | void addDamage(double d)
method createExplosion (line 67) | Explosion? createExplosion()
method createPowerUp (line 71) | Collectable? createPowerUp()
method paint (line 76) | void paint(Canvas canvas)
method setupActions (line 83) | void setupActions()
class LevelLabel (line 86) | class LevelLabel extends GameObject {
class Ship (line 103) | class Ship extends GameObject {
method applyThrust (line 127) | void applyThrust(Offset joystickValue, double scroll)
method setupActions (line 138) | void setupActions()
method update (line 149) | void update(double dt)
class Laser (line 163) | class Laser extends GameObject {
method move (line 187) | void move()
method createExplosion (line 192) | Explosion createExplosion()
function colorForDamage (line 197) | Color colorForDamage(double damage, double maxDamage, [Color? toColor])
class Obstacle (line 213) | abstract class Obstacle extends GameObject {
method createExplosion (line 219) | Explosion createExplosion()
class Asteroid (line 227) | abstract class Asteroid extends Obstacle {
method setupActions (line 233) | void setupActions()
method createPowerUp (line 253) | Collectable createPowerUp()
class AsteroidBig (line 258) | class AsteroidBig extends Asteroid {
class AsteroidSmall (line 268) | class AsteroidSmall extends Asteroid {
class AsteroidPowerUp (line 278) | class AsteroidPowerUp extends AsteroidBig {
method setupActions (line 306) | void setupActions()
method createPowerUp (line 309) | Collectable createPowerUp()
class EnemyScout (line 321) | class EnemyScout extends Obstacle {
method setupActions (line 367) | void setupActions()
method createPowerUp (line 388) | Collectable createPowerUp()
class EnemyDestroyer (line 401) | class EnemyDestroyer extends Obstacle {
method setupActions (line 426) | void setupActions()
method createPowerUp (line 434) | Collectable createPowerUp()
method update (line 439) | void update(double dt)
class EnemyLaser (line 462) | class EnemyLaser extends Obstacle {
method move (line 482) | void move()
class EnemyBoss (line 487) | class EnemyBoss extends Obstacle {
method update (line 517) | void update(double dt)
method fire (line 531) | void fire(double r)
method setupActions (line 543) | void setupActions()
method destroy (line 551) | void destroy()
method createExplosion (line 570) | Explosion createExplosion()
class Collectable (line 594) | class Collectable extends GameObject {
class Coin (line 604) | class Coin extends Collectable {
method setupActions (line 614) | void setupActions()
method collect (line 637) | void collect()
type PowerUpType (line 644) | enum PowerUpType {
function nextPowerUpType (line 654) | PowerUpType nextPowerUpType()
class PowerUp (line 666) | class PowerUp extends Collectable {
method setupActions (line 683) | void setupActions()
method collect (line 703) | void collect()
FILE: lib/generated_plugin_registrant.dart
function registerPlugins (line 15) | void registerPlugins(Registrar registrar)
FILE: lib/main.dart
type SelectTabCallback (line 15) | typedef SelectTabCallback = void Function(int index);
type UpgradePowerUpCallback (line 16) | typedef UpgradePowerUpCallback = void Function(PowerUpType type);
function main (line 24) | main()
class GameDemo (line 90) | class GameDemo extends StatefulWidget {
method createState (line 94) | GameDemoState createState()
class GameDemoState (line 97) | class GameDemoState extends State<GameDemo> {
method build (line 101) | Widget build(BuildContext context)
method _buildGameSceneRoute (line 123) | PageRoute _buildGameSceneRoute()
method _buildMainSceneRoute (line 137) | PageRoute _buildMainSceneRoute()
class GameScene (line 176) | class GameScene extends StatefulWidget {
method createState (line 187) | State<GameScene> createState()
class GameSceneState (line 190) | class GameSceneState extends State<GameScene> {
method initState (line 194) | void initState()
method build (line 216) | Widget build(BuildContext context)
class MainScene (line 224) | class MainScene extends StatefulWidget {
method createState (line 241) | State<MainScene> createState()
class MainSceneState (line 244) | class MainSceneState extends State<MainScene> {
method initState (line 246) | void initState()
method build (line 251) | Widget build(BuildContext context)
class TopBar (line 314) | class TopBar extends StatelessWidget {
method build (line 323) | Widget build(BuildContext context)
class CenterArea (line 377) | class CenterArea extends StatelessWidget {
method build (line 392) | Widget build(BuildContext context)
method _buildCenterArea (line 396) | Widget _buildCenterArea()
method _buildUpgradePanel (line 400) | Widget _buildUpgradePanel()
method _buildPowerUpButton (line 418) | Widget _buildPowerUpButton(PowerUpType type)
method _buildLaserUpgradeButton (line 443) | Widget _buildLaserUpgradeButton()
class BottomBar (line 475) | class BottomBar extends StatelessWidget {
method build (line 490) | Widget build(BuildContext context)
class MainSceneBackground (line 555) | class MainSceneBackground extends StatefulWidget {
method createState (line 559) | MainSceneBackgroundState createState()
class MainSceneBackgroundState (line 562) | class MainSceneBackgroundState extends State<MainSceneBackground> {
method initState (line 566) | void initState()
method build (line 572) | Widget build(BuildContext context)
class MainSceneBackgroundNode (line 580) | class MainSceneBackgroundNode extends NodeWithSize {
method paint (line 610) | void paint(Canvas canvas)
method spriteBoxPerformedLayout (line 617) | void spriteBoxPerformedLayout()
method update (line 622) | void update(double dt)
class LaserDisplay (line 628) | class LaserDisplay extends StatelessWidget {
method build (line 637) | Widget build(BuildContext context)
class LaserDisplayNode (line 648) | class LaserDisplayNode extends NodeWithSize {
FILE: lib/persistant_game_state.dart
class PersistantGameState (line 3) | class PersistantGameState {
method load (line 4) | Future load()
method store (line 21) | Future store()
method powerupLevel (line 42) | int powerupLevel(PowerUpType type)
method powerUpUpgradePrice (line 75) | int powerUpUpgradePrice(PowerUpType type)
method powerUpFrames (line 80) | int powerUpFrames(PowerUpType type)
method upgradePowerUp (line 90) | bool upgradePowerUp(PowerUpType type)
method laserUpgradePrice (line 103) | int laserUpgradePrice()
method upgradeLaser (line 107) | bool upgradeLaser()
method reachedLevel (line 118) | void reachedLevel(int level)
FILE: lib/player_state.dart
class PlayerState (line 3) | class PlayerState extends Node {
method addCoin (line 58) | void addCoin(Coin c)
method activatePowerUp (line 107) | void activatePowerUp(PowerUpType type)
method flashBackgroundSprite (line 135) | void flashBackgroundSprite(Sprite sprite)
method update (line 147) | void update(double dt)
class ScoreDisplay (line 181) | class ScoreDisplay extends Node {
method update (line 198) | void update(double dt)
FILE: lib/power_bar.dart
class PowerBar (line 3) | class PowerBar extends NodeWithSize {
method paint (line 15) | void paint(Canvas canvas)
FILE: lib/render_coordinate_system.dart
type CoordinateSystemType (line 3) | enum CoordinateSystemType {
class RenderCoordinateSystem (line 9) | class RenderCoordinateSystem extends RenderProxyBox {
method hitTest (line 63) | bool hitTest(HitTestResult result, {required Offset position})
method paint (line 75) | void paint(PaintingContext context, Offset offset)
method applyPaintTransform (line 88) | void applyPaintTransform(RenderObject child, Matrix4 transform)
method performResize (line 103) | void performResize()
method performLayout (line 109) | void performLayout()
FILE: lib/repeated_image.dart
class RepeatedImage (line 3) | class RepeatedImage extends Node {
method move (line 25) | void move(double dy)
FILE: lib/sound_assets.dart
class SoundAssets (line 3) | class SoundAssets {
method loadEffect (line 10) | Future<void> loadEffect(String name)
method loadMusic (line 17) | Future<void> loadMusic(String name)
method playEffect (line 23) | void playEffect(String name)
method playMusic (line 28) | void playMusic(String name)
method _effectPathForName (line 34) | String _effectPathForName(String name)
method _musicPathForName (line 36) | String _musicPathForName(String name)
FILE: lib/star_field.dart
class StarField (line 3) | class StarField extends NodeWithSize {
method addStars (line 28) | void addStars()
method spriteBoxPerformedLayout (line 63) | void spriteBoxPerformedLayout()
method paint (line 68) | void paint(Canvas canvas)
method move (line 83) | void move(double dx, double dy)
method update (line 102) | void update(double dt)
FILE: lib/widgets.dart
class TextureImage (line 3) | class TextureImage extends StatelessWidget {
method build (line 16) | Widget build(BuildContext context)
class TextureImagePainter (line 27) | class TextureImagePainter extends CustomPainter {
method paint (line 35) | void paint(Canvas canvas, Size size)
method shouldRepaint (line 46) | bool shouldRepaint(TextureImagePainter oldDelegate)
class TextureButton (line 53) | class TextureButton extends StatefulWidget {
method createState (line 78) | TextureButtonState createState()
class TextureButtonState (line 81) | class TextureButtonState extends State<TextureButton> {
method build (line 85) | Widget build(BuildContext context)
class TextureButtonPainter (line 111) | class TextureButtonPainter extends CustomPainter {
method paint (line 118) | void paint(Canvas canvas, Size size)
method shouldRepaint (line 175) | bool shouldRepaint(TextureButtonPainter oldDelegate)
class AppFrame (line 186) | class AppFrame extends StatelessWidget {
method build (line 194) | Widget build(BuildContext context)
Condensed preview — 33 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (128K chars).
[
{
"path": ".flutter-plugins-dependencies",
"chars": 4310,
"preview": "{\"info\":\"This is a generated file; do not edit or check into version control.\",\"plugins\":{\"ios\":[{\"name\":\"audio_session\""
},
{
"path": ".gitignore",
"chars": 1294,
"preview": "# Miscellaneous\n*.class\n*.lock\n*.log\n*.pyc\n*.swp\n.DS_Store\n.atom/\n.buildlog/\n.history\n.svn/\n\n# IntelliJ related\n*.iml\n*."
},
{
"path": ".metadata",
"chars": 305,
"preview": "# This file tracks properties of this Flutter project.\n# Used by Flutter tool to assess capabilities and perform upgrade"
},
{
"path": "LICENSE",
"chars": 1624,
"preview": "// Copyright 2022 The SpriteWidget Authors. All rights reserved.\n// Copyright 2014 The Chromium Authors. All rights rese"
},
{
"path": "README.md",
"chars": 287,
"preview": "# SpaceBlast\n\nA demo game for [SpriteWidget](https://spritewidget.com). Showcases most features of SpriteWidget.\n\n## Get"
},
{
"path": "analysis_options.yaml",
"chars": 1453,
"preview": "# This file configures the analyzer, which statically analyzes Dart code to\n# check for errors, warnings, and lints.\n#\n#"
},
{
"path": "assets/game_ui.json",
"chars": 9182,
"preview": "{\"frames\": [\n\n{\n\t\"filename\": \"badge.png\",\n\t\"frame\": {\"x\":1948,\"y\":619,\"w\":62,\"h\":62},\n\t\"rotated\": false,\n\t\"trimmed\": fal"
},
{
"path": "assets/sprites.json",
"chars": 9903,
"preview": "{\"frames\": [\n\n{\n\t\"filename\": \"asteroid_big_0.nrm.png\",\n\t\"frame\": {\"x\":245,\"y\":799,\"w\":200,\"h\":188},\n\t\"rotated\": false,\n\t"
},
{
"path": "build_web",
"chars": 135,
"preview": "#!/bin/sh\n\nflutter build web --web-renderer canvaskit --base-href \"/spaceblast/\"\ncp -r build/web/ ../spritewidget_web/do"
},
{
"path": "fonts/OFL.txt",
"chars": 4488,
"preview": "Copyright (c) 2009, Matt McInerney (matt@pixelspread.com),\r\nwith Reserved Font Name Orbitron.\r\nThis Font Software is lic"
},
{
"path": "lib/coordinate_system.dart",
"chars": 726,
"preview": "part of game;\n\nclass CoordinateSystem extends SingleChildRenderObjectWidget {\n const CoordinateSystem({\n Key? key,\n "
},
{
"path": "lib/custom_actions.dart",
"chars": 1065,
"preview": "part of game;\n\ntypedef PointSetterCallback = void Function(Offset value);\n\nclass ActionCircularMove extends MotionInterv"
},
{
"path": "lib/explosions.dart",
"chars": 4343,
"preview": "part of game;\n\nclass Explosion extends Node {\n Explosion() {\n zPosition = 10.0;\n }\n}\n\nclass ExplosionBig extends Ex"
},
{
"path": "lib/flash.dart",
"chars": 805,
"preview": "part of game;\n\nclass Flash extends NodeWithSize {\n Flash(Size size, this.duration) : super(size) {\n MotionTween fade"
},
{
"path": "lib/game_demo.dart",
"chars": 808,
"preview": "library game;\n\nimport 'dart:async';\nimport 'dart:convert';\nimport 'dart:math' as math;\nimport 'dart:ui' as ui;\n\nimport '"
},
{
"path": "lib/game_demo_node.dart",
"chars": 7781,
"preview": "part of game;\n\nvar _gameSizeHeight = 320.0;\n\nconst _chunkSpacing = 640.0;\nconst int _chunksPerLevel = 9;\n\nconst bool _dr"
},
{
"path": "lib/game_object_factory.dart",
"chars": 4961,
"preview": "part of game;\n\nconst int _maxLevel = 9;\n\nclass GameObjectFactory {\n GameObjectFactory(this.sheet, this.sounds, this.lev"
},
{
"path": "lib/game_objects.dart",
"chars": 16153,
"preview": "part of game;\n\nabstract class GameObject extends Node {\n GameObject(this.f);\n\n double radius = 0.0;\n double removeLim"
},
{
"path": "lib/generated_plugin_registrant.dart",
"chars": 624,
"preview": "//\n// Generated file. Do not edit.\n//\n\n// ignore_for_file: directives_ordering\n// ignore_for_file: lines_longer_than_80_"
},
{
"path": "lib/main.dart",
"chars": 17710,
"preview": "// Copyright 2015 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style licen"
},
{
"path": "lib/persistant_game_state.dart",
"chars": 2957,
"preview": "part of game;\n\nclass PersistantGameState {\n Future load() async {\n final prefs = await SharedPreferences.getInstance"
},
{
"path": "lib/player_state.dart",
"chars": 5921,
"preview": "part of game;\n\nclass PlayerState extends Node {\n PlayerState(this._sheetUI, this._sheetGame, this._gameState) {\n // "
},
{
"path": "lib/power_bar.dart",
"chars": 663,
"preview": "part of game;\n\nclass PowerBar extends NodeWithSize {\n PowerBar(Size size, [this.power = 1.0]) : super(size);\n\n double "
},
{
"path": "lib/render_coordinate_system.dart",
"chars": 3204,
"preview": "part of game;\n\nenum CoordinateSystemType {\n fixedWidth,\n fixedHeight,\n stretch,\n}\n\nclass RenderCoordinateSystem exten"
},
{
"path": "lib/repeated_image.dart",
"chars": 714,
"preview": "part of game;\n\nclass RepeatedImage extends Node {\n late Sprite _sprite0;\n late Sprite _sprite1;\n\n RepeatedImage(ui.Im"
},
{
"path": "lib/sound_assets.dart",
"chars": 968,
"preview": "part of game;\n\nclass SoundAssets {\n final AudioPlayer _musicPlayer = AudioPlayer();\n final Map<String, AudioPlayer> _e"
},
{
"path": "lib/star_field.dart",
"chars": 2659,
"preview": "part of game;\n\nclass StarField extends NodeWithSize {\n late ui.Image _image;\n final SpriteSheet _spriteSheet;\n final "
},
{
"path": "lib/widgets.dart",
"chars": 5805,
"preview": "part of game;\n\nclass TextureImage extends StatelessWidget {\n const TextureImage({\n Key? key,\n required this.textu"
},
{
"path": "pubspec.yaml",
"chars": 1237,
"preview": "name: spaceblast\npublish_to: 'none'\ndescription: A SpriteWidget demo.\nversion: 0.9.0\n\nenvironment:\n sdk: '>=2.12.0 <3.0"
},
{
"path": "web/index.html",
"chars": 4101,
"preview": "<!DOCTYPE html>\n<html>\n<head>\n <!--\n If you are serving your web app in a path other than the root, change the\n h"
},
{
"path": "web/manifest.json",
"chars": 916,
"preview": "{\n \"name\": \"spaceblast\",\n \"short_name\": \"spaceblast\",\n \"start_url\": \".\",\n \"display\": \"standalone\",\n \"back"
},
{
"path": "web/styles.css",
"chars": 186,
"preview": "html,\nbody {\n background-color: #333333\n}\n\n.center {\n margin: 0;\n position: absolute;\n top: 50%;\n left: 5"
}
]
// ... and 1 more files (download for full content)
About this extraction
This page contains the full source code of the spritewidget/spaceblast GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 33 files (114.5 KB), approximately 34.4k tokens, and a symbol index with 181 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.