diff --git a/assets/audio/Infinite_Spankage_M.mp3 b/assets/audio/Infinite_Spankage_M.mp3 new file mode 100644 index 0000000..7bf2b7a Binary files /dev/null and b/assets/audio/Infinite_Spankage_M.mp3 differ diff --git a/assets/images/coin-frames.png b/assets/images/coin-frames.png new file mode 100644 index 0000000..217c705 Binary files /dev/null and b/assets/images/coin-frames.png differ diff --git a/assets/images/crawl-frames.png b/assets/images/crawl-frames.png new file mode 100644 index 0000000..b6868d5 Binary files /dev/null and b/assets/images/crawl-frames.png differ diff --git a/assets/images/death-normal-frames.png b/assets/images/death-normal-frames.png new file mode 100644 index 0000000..350d3ca Binary files /dev/null and b/assets/images/death-normal-frames.png differ diff --git a/assets/images/electrecuted-frames.png b/assets/images/electrecuted-frames.png new file mode 100644 index 0000000..3329b01 Binary files /dev/null and b/assets/images/electrecuted-frames.png differ diff --git a/assets/images/jump-frames.png b/assets/images/jump-frames.png new file mode 100644 index 0000000..a02d755 Binary files /dev/null and b/assets/images/jump-frames.png differ diff --git a/assets/images/kick-frames.png b/assets/images/kick-frames.png new file mode 100644 index 0000000..6589fc9 Binary files /dev/null and b/assets/images/kick-frames.png differ diff --git a/assets/images/run-frames.png b/assets/images/run-frames.png index e049169..79df4a8 100644 Binary files a/assets/images/run-frames.png and b/assets/images/run-frames.png differ diff --git a/lib/Runner.dart b/lib/Runner.dart new file mode 100644 index 0000000..9641164 --- /dev/null +++ b/lib/Runner.dart @@ -0,0 +1,277 @@ +import 'package:firo_runner/main.dart'; +import 'package:flame/effects.dart'; +import 'package:flutter/material.dart'; + +import 'package:flame/components.dart'; + +enum RunnerState { + run, + jump, + duck, + kick, + float, + fall, + die, + electro, +} + +class Runner extends Component with HasGameRef { + late SpriteAnimationGroupComponent sprite; + + void setPosition(Vector2 position) { + sprite.position = position; + } + + void setSize(Vector2 size, double ySize) { + // runnerSize = size; + sprite.size = size; + } + + Sprite getSprite() { + return sprite.animation!.getSprite(); + } + + @override + void render(Canvas c) { + super.render(c); + getSprite().render(c, position: sprite.position, size: sprite.size); + } + + int level = 7; + + void updateLevel() { + level = (sprite.position.y / gameRef.blockSize).round(); + } + + String runnerState = "run"; + + void event(String event) { + print(event); + switch (event) { + case "jump": + runnerState = event; + sprite.current = RunnerState.jump; + sprite.addEffect(MoveEffect( + path: [ + // sprite.position, + Vector2(sprite.x, (level - 1) * gameRef.blockSize), + ], + speed: 50, + curve: Curves.bounceIn, + onComplete: () { + updateLevel(); + runnerState = "run"; + }, + )); + break; + case "doublejump": + runnerState = event; + sprite.current = RunnerState.jump; + sprite.addEffect(MoveEffect( + path: [ + sprite.position, + Vector2(sprite.x, (level - 2) * gameRef.blockSize), + ], + speed: 50, + curve: Curves.bounceIn, + onComplete: () { + updateLevel(); + runnerState = "run"; + }, + )); + break; + case "fall": + runnerState = event; + sprite.current = RunnerState.fall; + // TODO calculate distance to next platform. + sprite.addEffect(MoveEffect( + path: [ + // sprite.position, + Vector2(sprite.x, (level + 1) * gameRef.blockSize), + ], + speed: 50, + curve: Curves.ease, + onComplete: updateLevel, + )); + break; + case "kick": + runnerState = event; + sprite.current = RunnerState.kick; + break; + case "run": + runnerState = event; + sprite.current = RunnerState.run; + break; + case "float": + runnerState = event; + sprite.current = RunnerState.float; + sprite.addEffect(MoveEffect( + path: [ + sprite.position, + Vector2(sprite.x, (level - 1) * gameRef.blockSize), + ], + speed: 50, + curve: Curves.ease, + onComplete: () { + updateLevel(); + runnerState = event; + sprite.current = RunnerState.float; + }, + )); + break; + case "duck": + runnerState = event; + sprite.current = RunnerState.duck; + break; + case "die": + runnerState = event; + sprite.current = RunnerState.die; + break; + case "electro": + runnerState = event; + sprite.current = RunnerState.electro; + break; + default: + break; + } + } + + void control(String input) { + print(runnerState + " " + input); + print(sprite.position); + switch (input) { + case "up": + if (runnerState == "run") { + event("jump"); + } else if (runnerState == "jump") { + event("doublejump"); + } else if (runnerState == "duck") { + event("run"); + } + break; + case "down": + if (runnerState == "run") { + event("duck"); + } else if (runnerState == "float") { + event("fall"); + } + break; + case "right": + if (runnerState == "run") { + event("kick"); + } + break; + case "center": + if (runnerState == "jump") { + event("float"); + } + break; + } + } + + @override + void update(double dt) { + super.update(dt); + // If the animation is finished + if (sprite.animation?.done() ?? false) { + sprite.animation!.reset(); + if (runnerState == "kick") { + event("run"); + } + sprite.current = RunnerState.run; + } + + sprite.update(dt); + } + + Future load(loadSpriteAnimation) async { + SpriteAnimation running = await loadSpriteAnimation( + 'run-frames.png', + SpriteAnimationData.sequenced( + amount: 7, + stepTime: 0.1, + textureSize: Vector2(512, 512), + ), + ); + + SpriteAnimation jumping = await loadSpriteAnimation( + 'jump-frames.png', + SpriteAnimationData.sequenced( + amount: 5, + stepTime: 0.1, + textureSize: Vector2(512, 512), + loop: false, + ), + ); + + SpriteAnimation ducking = await loadSpriteAnimation( + 'crawl-frames.png', + SpriteAnimationData.sequenced( + amount: 3, + stepTime: 0.1, + textureSize: Vector2(512, 512), + ), + ); + + SpriteAnimation kicking = await loadSpriteAnimation( + 'kick-frames.png', + SpriteAnimationData.sequenced( + amount: 13, + stepTime: 0.03, + textureSize: Vector2(512, 512), + loop: false, + ), + ); + + SpriteAnimation floating = await loadSpriteAnimation( + 'run-frames.png', + SpriteAnimationData.sequenced( + amount: 1, + stepTime: 0.1, + textureSize: Vector2(512, 512), + ), + ); + + SpriteAnimation falling = await loadSpriteAnimation( + 'run-frames.png', + SpriteAnimationData.sequenced( + amount: 1, + stepTime: 0.1, + textureSize: Vector2(512, 512), + ), + ); + + SpriteAnimation dieing = await loadSpriteAnimation( + 'death-normal-frames.png', + SpriteAnimationData.sequenced( + amount: 20, + stepTime: 0.1, + textureSize: Vector2(512, 512), + loop: false, + ), + ); + + SpriteAnimation dieingElectorcuted = await loadSpriteAnimation( + 'electrecuted-frames.png', + SpriteAnimationData.sequenced( + amount: 2, + stepTime: 0.1, + textureSize: Vector2(512, 512), + ), + ); + + sprite = SpriteAnimationGroupComponent( + animations: { + RunnerState.run: running, + RunnerState.jump: jumping, + RunnerState.duck: ducking, + RunnerState.kick: kicking, + RunnerState.float: floating, + RunnerState.fall: falling, + RunnerState.die: dieing, + RunnerState.electro: dieingElectorcuted, + }, + current: RunnerState.run, + ); + } +} diff --git a/lib/main.dart b/lib/main.dart index b9bfc38..8c7649a 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,113 +1,199 @@ +import 'package:flame/components.dart'; +import 'package:flame/extensions.dart'; +import 'package:flame/flame.dart'; +import 'package:flame/game.dart'; +import 'package:flame/gestures.dart'; +import 'package:flame/keyboard.dart'; +import 'package:flame/palette.dart'; +import 'package:flame_audio/flame_audio.dart'; import 'package:flutter/material.dart'; +import 'package:flame_audio/bgm.dart'; +import 'package:flutter/services.dart'; +import 'Runner.dart'; -void main() { - runApp(MyApp()); +const COLOR = const Color(0xFFDDC0A3); +const SIZE = 52.0; +const GRAVITY = 400.0; +const BOOST = -380.0; + +void main() async { + WidgetsFlutterBinding.ensureInitialized(); + await Flame.device.fullScreen(); + await Flame.device.setLandscape(); + final myGame = MyGame(); + runApp(GameWidget(game: myGame)); } -class MyApp extends StatelessWidget { - // This widget is the root of your application. +class MyGame extends BaseGame with PanDetector, TapDetector, KeyboardEvents { + TextPaint textPaint = TextPaint( + config: TextPaintConfig(fontSize: 48.0), + ); + + late Sprite background1; + late Sprite background2; + late Runner runner; + late var background; + late var platform1; + late var platform2; + late var platform3; + late var wire; + late var bug; + late var coin; + + var runnerPosition = Vector2(0, 0); + var runnerSize; + var backgroundSize; + var background1Position; + var background2Position; + late double blockSize; + @override - Widget build(BuildContext context) { - return MaterialApp( - title: 'Flutter Demo', - theme: ThemeData( - // This is the theme of your application. - // - // Try running your application with "flutter run". You'll see the - // application has a blue toolbar. Then, without quitting the app, try - // changing the primarySwatch below to Colors.green and then invoke - // "hot reload" (press "r" in the console where you ran "flutter run", - // or simply save your changes to "hot reload" in a Flutter IDE). - // Notice that the counter didn't reset back to zero; the application - // is not restarted. - primarySwatch: Colors.blue, - ), - home: MyHomePage(title: 'Flutter Demo Home Page'), + Future onLoad() async { + print("load"); + FlameAudio.bgm.initialize(); + background = await Flame.images.load('bg.png'); + background1 = Sprite(background); + background2 = Sprite(background); + platform1 = await Flame.images.load('platform1.png'); + platform2 = await Flame.images.load('platform2.png'); + platform3 = await Flame.images.load('platform3.png'); + wire = await Flame.images.load('wire.png'); + bug = await Flame.images.load('bug.png'); + coin = await Flame.images.load('coin.png'); + + runner = Runner(); + await runner.load(loadSpriteAnimation); + runner.setSize(runnerSize, blockSize); + runnerPosition = Vector2(blockSize, blockSize * 7); + runner.setPosition(runnerPosition); + add(runner); + + FlameAudio.bgm.play('Infinite_Spankage_M.mp3'); + } + + @override + void render(Canvas canvas) { + background1.render( + canvas, + position: Vector2(0, 0), + size: backgroundSize, ); - } -} - -class MyHomePage extends StatefulWidget { - MyHomePage({Key? key, required this.title}) : super(key: key); - - // This widget is the home page of your application. It is stateful, meaning - // that it has a State object (defined below) that contains fields that affect - // how it looks. - - // This class is the configuration for the state. It holds the values (in this - // case the title) provided by the parent (in this case the App widget) and - // used by the build method of the State. Fields in a Widget subclass are - // always marked "final". - - final String title; - - @override - _MyHomePageState createState() => _MyHomePageState(); -} - -class _MyHomePageState extends State { - int _counter = 0; - - void _incrementCounter() { - setState(() { - // This call to setState tells the Flutter framework that something has - // changed in this State, which causes it to rerun the build method below - // so that the display can reflect the updated values. If we changed - // _counter without calling setState(), then the build method would not be - // called again, and so nothing would appear to happen. - _counter++; - }); + super.render(canvas); + final fpsCount = fps(1); + // textPaint.render( + // canvas, + // fpsCount.toString(), + // Vector2(0, 0), + // ); } @override - Widget build(BuildContext context) { - // This method is rerun every time setState is called, for instance as done - // by the _incrementCounter method above. - // - // The Flutter framework has been optimized to make rerunning build methods - // fast, so that you can just rebuild anything that needs updating rather - // than having to individually change instances of widgets. - return Scaffold( - appBar: AppBar( - // Here we take the value from the MyHomePage object that was created by - // the App.build method, and use it to set our appbar title. - title: Text(widget.title), - ), - body: Center( - // Center is a layout widget. It takes a single child and positions it - // in the middle of the parent. - child: Column( - // Column is also a layout widget. It takes a list of children and - // arranges them vertically. By default, it sizes itself to fit its - // children horizontally, and tries to be as tall as its parent. - // - // Invoke "debug painting" (press "p" in the console, choose the - // "Toggle Debug Paint" action from the Flutter Inspector in Android - // Studio, or the "Toggle Debug Paint" command in Visual Studio Code) - // to see the wireframe for each widget. - // - // Column has various properties to control how it sizes itself and - // how it positions its children. Here we use mainAxisAlignment to - // center the children vertically; the main axis here is the vertical - // axis because Columns are vertical (the cross axis would be - // horizontal). - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - 'You have pushed the button this many times:', - ), - Text( - '$_counter', - style: Theme.of(context).textTheme.headline4, - ), - ], - ), - ), - floatingActionButton: FloatingActionButton( - onPressed: _incrementCounter, - tooltip: 'Increment', - child: Icon(Icons.add), - ), // This trailing comma makes auto-formatting nicer for build methods. + void update(double dt) { + super.update(dt); + } + + @override + void onResize(Vector2 size) { + super.onResize(size); + blockSize = size.y / 9; + print(blockSize); + runnerSize = Vector2( + size.y / 9, + size.y / 9, ); + + backgroundSize = Vector2(size.x * 2, size.y); + } + + // Mobile controls + late List xdeltas; + late List ydeltas; + @override + void onPanStart(DragStartInfo info) { + xdeltas = List.empty(growable: true); + ydeltas = List.empty(growable: true); + } + + @override + void onPanUpdate(DragUpdateInfo info) { + xdeltas.add(info.delta.game.x); + ydeltas.add(info.delta.game.y); + } + + @override + void onPanEnd(DragEndInfo info) { + double xdelta = xdeltas.isEmpty + ? 0 + : xdeltas.reduce((value, element) => value + element); + double ydelta = ydeltas.isEmpty + ? 0 + : ydeltas.reduce((value, element) => value + element); + if (xdelta.abs() > ydelta.abs()) { + if (xdelta > 0) { + runner.control("right"); + } else { + runner.control("left"); + } + } else if (xdelta.abs() < ydelta.abs()) { + if (ydelta > 0) { + runner.control("down"); + } else { + runner.control("up"); + } + } + } + + @override + void onTap() { + runner.control("center"); + } + + // Keyboard controls. + var keyboardKey; + @override + void onKeyEvent(RawKeyEvent event) { + if (event is RawKeyUpEvent) { + keyboardKey = null; + switch (event.data.keyLabel) { + case "w": + runner.control("up"); + break; + case "a": + runner.control("left"); + break; + case "s": + runner.control("down"); + break; + case "d": + runner.control("right"); + break; + default: + if (event.data.logicalKey.keyId == 32) { + runner.control("down"); + } + break; + } + } + if (event is RawKeyDownEvent && event.data.logicalKey.keyId == 32) { + if (keyboardKey == null) { + runner.control("center"); + } + keyboardKey = "spacebar"; + } } } + +class Background extends Component { + static final Paint _paint = Paint()..color = COLOR; + final size; + + Background(this.size); + + @override + void render(Canvas c) { + c.drawRect(Rect.fromLTWH(0.0, 0.0, size.x, size.y), _paint); + } + + @override + void update(double t); +} diff --git a/pubspec.lock b/pubspec.lock index 1757435..d72f0a9 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -14,7 +14,7 @@ packages: name: audioplayers url: "https://pub.dartlang.org" source: hosted - version: "0.18.3" + version: "0.19.1" boolean_selector: dependency: transitive description: @@ -22,13 +22,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.1.0" - box2d_flame: - dependency: transitive - description: - name: box2d_flame - url: "https://pub.dartlang.org" - source: hosted - version: "0.4.6" characters: dependency: transitive description: @@ -57,13 +50,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.15.0" - convert: - dependency: transitive - description: - name: convert - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.1" crypto: dependency: transitive description: @@ -105,21 +91,14 @@ packages: name: flame url: "https://pub.dartlang.org" source: hosted - version: "0.29.4" - flare_dart: - dependency: transitive + version: "1.0.0-releasecandidate.13" + flame_audio: + dependency: "direct main" description: - name: flare_dart + name: flame_audio url: "https://pub.dartlang.org" source: hosted - version: "2.3.4" - flare_flutter: - dependency: transitive - description: - name: flare_flutter - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.6" + version: "1.0.0-rc.1" flutter: dependency: "direct main" description: flutter @@ -135,6 +114,20 @@ packages: description: flutter source: sdk version: "0.0.0" + http: + dependency: transitive + description: + name: http + url: "https://pub.dartlang.org" + source: hosted + version: "0.13.3" + http_parser: + dependency: transitive + description: + name: http_parser + url: "https://pub.dartlang.org" + source: hosted + version: "4.0.0" js: dependency: transitive description: @@ -162,7 +155,7 @@ packages: name: ordered_set url: "https://pub.dartlang.org" source: hosted - version: "2.0.2" + version: "3.2.0" path: dependency: transitive description: @@ -176,7 +169,7 @@ packages: name: path_provider url: "https://pub.dartlang.org" source: hosted - version: "2.0.2" + version: "2.0.3" path_provider_linux: dependency: transitive description: @@ -205,6 +198,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.0.3" + pedantic: + dependency: transitive + description: + name: pedantic + url: "https://pub.dartlang.org" + source: hosted + version: "1.11.1" platform: dependency: transitive description: @@ -265,7 +265,7 @@ packages: name: synchronized url: "https://pub.dartlang.org" source: hosted - version: "2.2.0+2" + version: "3.0.0" term_glyph: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 6ce7a4a..a3acb6c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -23,7 +23,8 @@ environment: dependencies: flutter: sdk: flutter - flame: ^0.29.4 + flame: "^1.0.0-releasecandidate.11" + flame_audio: "^1.0.0-rc.1" # The following adds the Cupertino Icons font to your application.