Beginning game that has a moving runner charcter.

This commit is contained in:
Marco Salazar 2021-09-02 16:30:18 -06:00
parent fbbfa59d6d
commit 05b3891cbe
12 changed files with 496 additions and 132 deletions

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 539 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 176 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 295 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 323 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 844 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 161 KiB

After

Width:  |  Height:  |  Size: 516 KiB

277
lib/Runner.dart Normal file
View File

@ -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<MyGame> {
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,
);
}
}

View File

@ -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.
@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'),
class MyGame extends BaseGame with PanDetector, TapDetector, KeyboardEvents {
TextPaint textPaint = TextPaint(
config: TextPaintConfig(fontSize: 48.0),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key? key, required this.title}) : super(key: key);
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;
// 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;
var runnerPosition = Vector2(0, 0);
var runnerSize;
var backgroundSize;
var background1Position;
var background2Position;
late double blockSize;
@override
_MyHomePageState createState() => _MyHomePageState();
}
Future<void> 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');
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
runner = Runner();
await runner.load(loadSpriteAnimation);
runner.setSize(runnerSize, blockSize);
runnerPosition = Vector2(blockSize, blockSize * 7);
runner.setPosition(runnerPosition);
add(runner);
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++;
});
FlameAudio.bgm.play('Infinite_Spankage_M.mp3');
}
@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: <Widget>[
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 render(Canvas canvas) {
background1.render(
canvas,
position: Vector2(0, 0),
size: backgroundSize,
);
super.render(canvas);
final fpsCount = fps(1);
// textPaint.render(
// canvas,
// fpsCount.toString(),
// Vector2(0, 0),
// );
}
@override
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<double> xdeltas;
late List<double> 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);
}

View File

@ -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:

View File

@ -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.