Doodle Jump — 使用 Flutter&Flame 开发游戏真不错!
- 2024-04-11 浙江
本文字数:12197 字
阅读完需:约 40 分钟
前言
最近网上冲浪的时候,我偶然发现了一个国外的游戏网站,里面聚集了各种有趣的小游戏,类似于国内的 4399。在浏览时,我遇到了一款经典的小游戏:Doodle Jump。上一次玩还是在上小学的时候,那时候父母在厨房做饭,我就偷摸拿他们的手机玩...回到正题,我猜很多人小时候都有着做一款游戏的想法,那今天就让我用 flutter 带大家一起实现下这款童年游戏经典!
仓库地址:https://github.com/taxze6/flutter_game_collection/tree/main/flutter_dash_doodle_jump
游戏素材来源:https://i.imgur.com
游戏演示
演示的 GIF 两倍速加速了。
导入 Flame
这次和之前游戏编写的文章有所不同,使用上了 flame。很多游戏通用逻辑都不用在自己写了。
flame: 1.7.3
游戏主体框架
分为三块来进行游戏的开发:
游戏页面
开局菜单页面
游戏结束页面
Scaffold(
body: Center(
child: GameWidget(
game: game,
overlayBuilderMap: <String, Widget Function(BuildContext, Game)>{
"gameOverlay": (context, game) => GameOverlay(game: game),
"mainMenuOverlay": (context, game) =>
MainMenuOverlay(game: game),
"gameOverOverlay": (context, game) =>
GameOverOverlay(game: game),
},
),
),
);
游戏状态控制器
结合游戏主体框架,分为三个状态:1.菜单页面 2. 游戏中 3.游戏结束
enum GameState { menu, playing, gameOver }
再额外记录一个游戏分数。我们的游戏状态控制器就成型啦!
class GameManager extends Component with HasGameRef<FlutterDashDoodleJump> {
GameManager();
ValueNotifier<int> score = ValueNotifier(0);
GameState gameState = GameState.menu;
bool get isPlaying => gameState == GameState.playing;
bool get isGameOver => gameState == GameState.gameOver;
bool get isMenu => gameState == GameState.menu;
///重置、 初始化
void reset() {
score.value = 0;
gameState = GameState.menu;
}
///增加分数
void increaseScore() {
score.value++;
}
}
游戏难度控制器
大部分“跑酷类”游戏都会随着游戏进行的时间/获得的分数来增加难度,增加趣味,那么让我们也来实现一个游戏难度控制器。
class Difficulty {
final double minDistance;
final double maxDistance;
final double jumpSpeed;
final int score;
const Difficulty({
required this.minDistance,
required this.maxDistance,
required this.jumpSpeed,
required this.score,
});
}
class LevelManager extends Component with HasGameRef<FlutterDashDoodleJump> {
LevelManager({this.selectedLevel = 1, this.level = 1});
//玩家在一开始选择的难度
int selectedLevel;
//游戏中的难度
int level;
//不同难度的配置,当达到对应分数,则启用对应难度,玩家跳跃的高度也要相对应增加
final Map<int, Difficulty> levelsConfig = {
1: const Difficulty(
minDistance: 200, maxDistance: 300, jumpSpeed: 600, score: 0),
2: const Difficulty(
minDistance: 200, maxDistance: 400, jumpSpeed: 650, score: 20),
3: const Difficulty(
minDistance: 200, maxDistance: 500, jumpSpeed: 700, score: 40),
4: const Difficulty(
minDistance: 200, maxDistance: 600, jumpSpeed: 750, score: 80),
5: const Difficulty(
minDistance: 200, maxDistance: 700, jumpSpeed: 800, score: 100),
};
double get minDistance {
return levelsConfig[level]!.minDistance;
}
double get maxDistance {
return levelsConfig[level]!.maxDistance;
}
double get jumpSpeed {
return levelsConfig[level]!.jumpSpeed;
}
Difficulty get difficulty {
return levelsConfig[level]!;
}
///判断是否还能加大难度(最高5级)
bool shouldLevelUp(int score) {
int nextLevel = level + 1;
if (levelsConfig.containsKey(nextLevel)) {
return levelsConfig[nextLevel]!.score == score;
}
return false;
}
List<int> get levels {
return levelsConfig.keys.toList();
}
///难度增加
void increaseLevel() {
if (level < levelsConfig.keys.length) {
level++;
}
}
/// 开始游戏前,设置难度
void setLevel(int newLevel) {
if (levelsConfig.containsKey(newLevel)) {
level = newLevel;
}
}
void selectLevel(int selectLevel) {
if (levelsConfig.containsKey(selectLevel)) {
selectedLevel = selectLevel;
}
}
///重置难度为刚开始的难度
void reset() {
level = selectedLevel;
}
}
⚙玩家、平台、道具、敌人、游戏背景的定义
有了游戏状态和游戏难度的控制器,我们还需要对游戏中随机生成的平台、道具进行控制。那么先让我们来定义对应的信息。
定义平台
首先,定义平台通用的抽象类。
abstract class Platform<T> extends SpriteGroupComponent<T>
with HasGameRef<FlutterDashDoodleJump>, CollisionCallbacks {
//碰撞监测
final hitBox = RectangleHitbox();
//是否移动
bool isMoving = false;
Platform({
super.position,
}) : super(
size: Vector2.all(100),
// 确保平台始终在Dash的后面
priority: 2,
);
@override
Future<void> onLoad() async {
await super.onLoad();
// 添加碰撞监测
await add(hitBox);
// 这个平台是否会移动(大于80,代表20%的概率移动)
final int rand = Random().nextInt(100);
if (rand > 80) isMoving = true;
}
double direction = 1;
final Vector2 velocity = Vector2.zero();
//移动速度
double speed = 35;
void move(double deltaTime) {
if (!isMoving) return;
//获取游戏场景的宽度
final double gameWidth = gameRef.size.x;
// 根据平台的位置来确定移动的方向。
// 如果平台的 x 坐标小于等于 0,说明平台到达了左边界,将 direction 设置为 1,表示向右移动;
// 如果平台的 x 坐标大于等于游戏宽度减去平台宽度,说明平台到达了右边界,将 direction 设置为 -1,表示向左移动。
if (position.x <= 0) {
direction = 1;
} else if (position.x >= gameWidth - size.x) {
direction = -1;
}
velocity.x = direction * speed;
position += velocity * deltaTime;
}
//这个注解是 Dart 语言中的一个元数据注解(metadata annotation),用于标记方法,表示子类在覆盖这个方法时必须调用父类的同名方法。
//在这里,@mustCallSuper 注解告诉子类在覆盖 update 方法时必须调用父类的 update 方法。
@mustCallSuper
@override
void update(double dt) {
move(dt);
//确保调用父类的 update 方法
super.update(dt);
}
}
定义普通的平台方块
///默认的普通平台(方块)
enum NormalPlatformState { only }
class NormalPlatform extends Platform<NormalPlatformState> {
NormalPlatform({super.position});
final Map<String, Vector2> spriteOptions = {
'platform1': Vector2(106, 52),
'platform2': Vector2(106, 52),
'platform3': Vector2(106, 52),
'platform4': Vector2(106, 52),
};
@override
Future<void> onLoad() async {
var randSpriteIndex = Random().nextInt(spriteOptions.length);
String randSprite = spriteOptions.keys.elementAt(randSpriteIndex);
sprites = {
NormalPlatformState.only: await gameRef.loadSprite('game/$randSprite.png')
};
current = NormalPlatformState.only;
size = spriteOptions[randSprite]!;
await super.onLoad();
}
}
定义跳一下就会破碎的方块
enum BrokenPlatformState { cracked, broken }
class BrokenPlatform extends Platform<BrokenPlatformState> {
BrokenPlatform({super.position});
@override
Future<void> onLoad() async {
await super.onLoad();
sprites = <BrokenPlatformState, Sprite>{
BrokenPlatformState.cracked:
await gameRef.loadSprite('game/platform_cracked_monitor.png'),
BrokenPlatformState.broken:
await gameRef.loadSprite('game/platform_monitor_broken.png'),
};
current = BrokenPlatformState.cracked;
size = Vector2(113, 48);
}
void breakPlatform() {
current = BrokenPlatformState.broken;
}
}
定义带弹簧的方块
///弹簧床
enum SpringState { down, up }
class SpringBoard extends Platform<SpringState> {
SpringBoard({
super.position,
});
@override
Future<void> onLoad() async {
await super.onLoad();
sprites = <SpringState, Sprite>{
SpringState.down:
await gameRef.loadSprite('game/platform_trampoline_down.png'),
SpringState.up:
await gameRef.loadSprite('game/platform_trampoline_up.png'),
};
current = SpringState.up;
size = Vector2(100, 45);
}
@override
void onCollisionStart(
Set<Vector2> intersectionPoints, PositionComponent other) {
super.onCollisionStart(intersectionPoints, other);
//通过计算交点的垂直差值,判断是否发生垂直碰撞、
//如果垂直差值小于 5,则将当前状态 current 设置为 SpringState.down,表示向下弹簧状态
bool isCollidingVertically =
(intersectionPoints.first.y - intersectionPoints.last.y).abs() < 5;
if (isCollidingVertically) {
current = SpringState.down;
}
}
@override
void onCollisionEnd(PositionComponent other) {
//这个方法在碰撞结束时被调用。
//首先调用super.onCollisionEnd(other),以确保调用父类的碰撞结束方法。
//然后,将当前状态 current 设置为 SpringState.up,表示向上弹簧状态。
super.onCollisionEnd(other);
current = SpringState.up;
}
}
定义敌人
可以把敌人当作平台,因为大部分的特性和平台是一样的,只是碰撞事件不同,图片不同。
///敌人
enum EnemyPlatformState { only }
class EnemyPlatform extends Platform<EnemyPlatformState> {
EnemyPlatform({super.position});
@override
Future<void> onLoad() async {
var randBool = Random().nextBool();
var enemySprite = randBool ? 'enemy_heart' : 'enemy_e';
sprites = <EnemyPlatformState, Sprite>{
EnemyPlatformState.only:
await gameRef.loadSprite('game/$enemySprite.png'),
};
current = EnemyPlatformState.only;
if (enemySprite == "enemy_heart") {
size = Vector2(100, 45);
} else {
//雷电
size = Vector2(100, 32);
}
return super.onLoad();
}
}
定义道具
和平台一样,我们先定义通用的抽象类。
abstract class PowerUp extends SpriteComponent
with HasGameRef<FlutterDashDoodleJump>, CollisionCallbacks {
PowerUp({
super.position,
}) : super(
size: Vector2.all(50),
priority: 2,
);
final hitBox = RectangleHitbox();
double get jumpSpeedMultiplier;
@override
Future<void>? onLoad() async {
await super.onLoad();
// 添加碰撞检测逻辑
await add(hitBox);
}
@override
void onCollision(Set<Vector2> intersectionPoints, PositionComponent other) {
if (other is Player && !other.isInvincible && !other.isWearingHat) {
removeFromParent();
}
super.onCollision(intersectionPoints, other);
}
}
定义火箭道具(直接变身成为一团火,起飞+无敌,但是不能移动)
///火箭
class Rocket extends PowerUp {
@override
double get jumpSpeedMultiplier => 3.5;
Rocket({
super.position,
});
@override
Future<void>? onLoad() async {
await super.onLoad();
sprite = await gameRef.loadSprite('game/rocket.png');
size = Vector2(50, 70);
}
}
定义起飞魔法帽(直接起飞,可以左右移动,但是不无敌)
///起飞魔法帽
class Hat extends PowerUp {
@override
double get jumpSpeedMultiplier => 2.5;
Hat({
super.position,
});
final int activeLengthInMS = 5000;
@override
Future<void>? onLoad() async {
await super.onLoad();
sprite = await gameRef.loadSprite('game/hat.png');
size = Vector2(75, 50);
}
}
定义玩家
玩家相对于平台或者道具都更复杂一些,让我们一点点来实现。
定义玩家的状态
一共有 7 种状态,分别是,1.正常 2.向左移动 3.向右移动 4.吃了道具火箭 5.吃到了道具魔法帽然后向左移动 6.吃到了道具魔法帽正常状态 7.吃到了道具魔法帽向右移动。
enum PlayerState { left, right, center, rocket, hatCenter, hatLeft, hatRight }
定义玩家的尺寸
class Player extends SpriteGroupComponent<PlayerState>
with
HasGameRef<FlutterDashDoodleJump>,
KeyboardHandler,
CollisionCallbacks {
Player({super.position, this.jumpSpeed = 600})
: super(
size: Vector2(79, 109),
anchor: Anchor.center,
priority: 1,
);
Vector2 velocity = Vector2.zero();
}
标识玩家的移动方向、移动速度
计算用户是向左(-1)还是向右(1)移动,当向左移动时,x 轴速度乘以-1,得到一个负数。在 flutter 中,x 轴上的数字从左向右增加,因此负数向左移动。当向右移动时,结果将是一个正数,如果数字是 0,Dash 是垂直移动的,也就是正常模式。
int hAxisInput = 0;
final int movingLeftInput = -1;
final int movingRightInput = 1;
//用于计算水平移动速度
final double gravity = 9;
//垂直速度
double jumpSpeed;
标识用户当前的状态
//吃到了道具(火箭和起飞帽子)
bool get hasPowerUp =>
current == PlayerState.rocket ||
current == PlayerState.hatLeft ||
current == PlayerState.hatRight ||
current == PlayerState.hatCenter;
//处于无敌状态(在火箭里)
bool get isInvincible => current == PlayerState.rocket;
//是否戴着帽子(处于起飞状态)
bool get isWearingHat =>
current == PlayerState.hatLeft ||
current == PlayerState.hatRight ||
current == PlayerState.hatCenter;
跳跃
在 flutter 中,因为左上角是(0,0),所以向上是负的。
void jump({double? specialJumpSpeed}) {
velocity.y = specialJumpSpeed != null ? -specialJumpSpeed : -jumpSpeed;
}
碰撞检测
检测玩家和平台、道具、敌人的碰撞。
//玩家与游戏中另一个组件碰撞的回调
@override
void onCollision(Set<Vector2> intersectionPoints, PositionComponent other) {
super.onCollision(intersectionPoints, other);
//碰到敌人且不是无敌状态,直接嗝屁
if (other is EnemyPlatform && !isInvincible) {
gameRef.onLose();
return;
}
//计算碰撞点的垂直差值,是否小于5
bool isCollidingVertically =
(intersectionPoints.first.y - intersectionPoints.last.y).abs() < 5;
bool enablePowerUp = false;
//是否可以激活道具
if (!hasPowerUp && (other is Rocket || other is Hat)) {
enablePowerUp = true;
}
//如果玩家正在向下移动且发生了垂直碰撞,根据碰撞的对象类型,执行相应的操作。
if (isMovingDown && isCollidingVertically) {
current = PlayerState.center;
//普通平台
if (other is NormalPlatform) {
jump();
return;
}
//弹簧板
else if (other is SpringBoard) {
jump(specialJumpSpeed: jumpSpeed * 2);
return;
}
//起飞
else if (other is BrokenPlatform &&
other.current == BrokenPlatformState.cracked) {
jump();
other.breakPlatform();
return;
}
if (other is Rocket || other is Hat) {
enablePowerUp = true;
}
}
if (!enablePowerUp) return;
if (other is Rocket) {
current = PlayerState.rocket;
//基础跳跃速度 jumpSpeed 乘以火箭的跳跃速度倍数 other.jumpSpeedMultiplier
jump(specialJumpSpeed: jumpSpeed * other.jumpSpeedMultiplier);
return;
} else if (other is Hat) {
//根据Dash的当前位置,判断要显示的图标
if (current == PlayerState.center) current = PlayerState.hatCenter;
if (current == PlayerState.left) current = PlayerState.hatLeft;
if (current == PlayerState.right) current = PlayerState.hatRight;
removePowerUpAfterTime(other.activeLengthInMS);
jump(specialJumpSpeed: jumpSpeed * other.jumpSpeedMultiplier);
return;
}
}
键盘控制
当方向键被按下时,改变玩家的移动方向。
@override
bool onKeyEvent(RawKeyEvent event, Set<LogicalKeyboardKey> keysPressed) {
//默认情况下不向左或向右
hAxisInput = 0;
// 向左移动
if (keysPressed.contains(LogicalKeyboardKey.arrowLeft)) {
if (isWearingHat) {
current = PlayerState.hatLeft;
} else if (!hasPowerUp) {
current = PlayerState.left;
}
hAxisInput += movingLeftInput;
}
// 向右移动
if (keysPressed.contains(LogicalKeyboardKey.arrowRight)) {
if (isWearingHat) {
current = PlayerState.hatRight;
} else if (!hasPowerUp) {
current = PlayerState.right;
}
hAxisInput += movingRightInput;
}
// 外挂,一直跳,不过撞到敌人还是会嗝屁
//if (keysPressed.contains(LogicalKeyboardKey.arrowUp)) {
// jump();
//}
return true;
}
游戏进行中的刷新检测
@override
void update(double dt) {
//判断是否是游戏中状态
if (gameRef.gameManager.isMenu || gameRef.gameManager.isGameOver) return;
final double dashHorizontalCenter = size.x / 2;
//玩家的水平速度
velocity.x = hAxisInput * jumpSpeed;
//玩家的垂直速度
velocity.y += gravity;
// 如果玩家移动到不在屏幕上(位置从中心开始),则从另一侧出现
if (position.x < dashHorizontalCenter) {
position.x = gameRef.size.x - (dashHorizontalCenter);
}
if (position.x > gameRef.size.x - (dashHorizontalCenter)) {
position.x = dashHorizontalCenter;
}
//玩家的速度除以经过的时间
//计算当前位置
position += velocity * dt;
super.update(dt);
}
这样我们就完成了对玩家的定义。
游戏背景定义
如果是长图/多图背景,则可以通过baseVelocity
和velocityMultiplierDelta
启用视差效果,这里只有单一的一张,就用静态的效果啦。
class World extends ParallaxComponent<FlutterDashDoodleJump> {
@override
Future<void> onLoad() async {
parallax = await gameRef.loadParallax(
[
ParallaxImageData('game/background/background.png'),
],
fill: LayerFill.width,
repeat: ImageRepeat.repeat,
);
}
}
🎯平台/道具生成控制器
现在,让我来对游戏中随机生成的平台、道具进行控制吧!
定义生成平台之间的距离
class ObjectManager extends Component with HasGameRef<FlutterDashDoodleJump> {
ObjectManager({
this.minVerticalDistanceToNextPlatform = 200,
this.maxVerticalDistanceToNextPlatform = 300,
});
//到下一个平台的最小垂直距离
double minVerticalDistanceToNextPlatform;
//到下一个平台的最大垂直距离
double maxVerticalDistanceToNextPlatform;
}
定义存放平台、道具、敌人的列表
//存放Dash可以踩的平台
final List<Platform> platforms = [];
final List<PowerUp> powerUps = [];
final List<EnemyPlatform> enemies = [];
生成平台
// 返回随机类型的平台
// 各类平台出现的概率都是不同的
Platform _semiRandomPlatform(Vector2 position) {
if (specialPlatforms['spring'] == true &&
probGen.generateWithProbability(15)) {
// 15%的机会得到跳板
return SpringBoard(position: position);
}
if (specialPlatforms['broken'] == true &&
probGen.generateWithProbability(10)) {
// 10%的机会出现只能跳一次的平台
return BrokenPlatform(position: position);
}
// 默认为普通平台
return NormalPlatform(position: position);
}
生成道具
void _maybeAddPowerUp() {
//20%的概率出现起飞魔法帽
if (specialPlatforms['hat'] == true &&
probGen.generateWithProbability(20)) {
// 生成帽子道具
var hat = Hat(
position: Vector2(_generateNextX(75), _generateNextY()),
);
add(hat);
powerUps.add(hat);
return;
}
// 15%的概率出现火箭
if (specialPlatforms['rocket'] == true &&
probGen.generateWithProbability(15)) {
var rocket = Rocket(
position: Vector2(_generateNextX(50), _generateNextY()),
);
add(rocket);
powerUps.add(rocket);
}
}
生成怪物
void _maybeAddEnemy() {
// 判断有没有到能生成怪物的游戏难度
if (specialPlatforms['enemy'] != true) {
return;
}
if (probGen.generateWithProbability(20)) {
var enemy = EnemyPlatform(
position: Vector2(_generateNextX(100), _generateNextY()),
);
add(enemy);
enemies.add(enemy);
_cleanup();
}
}
手动删除道具/怪物
因为道具和敌人的生成依赖于概率,所以不存在将它们从游戏中移除的最佳时机。我们需要定期检查是否有可以删除的。
void _cleanup() {
final screenBottom = gameRef.player.position.y +
(gameRef.size.x / 2) +
gameRef.screenBufferSpace;
while (enemies.isNotEmpty && enemies.first.position.y > screenBottom) {
remove(enemies.first);
enemies.removeAt(0);
}
while (powerUps.isNotEmpty && powerUps.first.position.y > screenBottom) {
if (powerUps.first.parent != null) {
remove(powerUps.first);
}
powerUps.removeAt(0);
}
}
计算生成组件的 x 轴和 y 轴
double _generateNextX(int platformWidth) {
// 确保下一个平台不会重叠
final previousPlatformXRange = Range(
platforms.last.position.x,
platforms.last.position.x + platformWidth,
);
double nextPlatformAnchorX;
// 如果前一个平台和下一个平台重叠,尝试一个新的随机X
do {
nextPlatformAnchorX =
random.nextInt(gameRef.size.x.floor() - platformWidth).toDouble();
} while (previousPlatformXRange.overlaps(
Range(nextPlatformAnchorX, nextPlatformAnchorX + platformWidth)));
return nextPlatformAnchorX;
}
// 用于确定下一个平台应该放置的位置
// 它返回minVerticalDistanceToNextPlatform和maxVerticalDistanceToNextPlatform之间的随机距离
double _generateNextY() {
// 添加platformHeight(单个平台的高度)可以防止平台重叠。
final currentHighestPlatformY =
platforms.last.center.y + tallestPlatformHeight;
final distanceToNextY = minVerticalDistanceToNextPlatform.toInt() +
random
.nextInt((maxVerticalDistanceToNextPlatform -
minVerticalDistanceToNextPlatform)
.floor())
.toDouble();
return currentHighestPlatformY - distanceToNextY;
}
初始化游戏时生成组件
@override
void onMount() {
super.onMount();
var currentX = (gameRef.size.x.floor() / 2).toDouble() - 50;
//第一个平台总是在初始屏幕的底部三分之一处
var currentY =
gameRef.size.y - (random.nextInt(gameRef.size.y.floor()) / 3) - 50;
//生成10个随机x, y位置的平台,并添加到平台列表。
for (var i = 0; i < 9; i++) {
if (i != 0) {
currentX = _generateNextX(100);
currentY = _generateNextY();
}
platforms.add(
_semiRandomPlatform(
Vector2(
currentX,
currentY,
),
),
);
add(platforms[i]);
}
}
刷新游戏
游戏更新控制,分数计算。
@override
void update(double dt) {
//增加平台高度可以确保两个平台不会重叠。
final topOfLowestPlatform =
platforms.first.position.y + tallestPlatformHeight;
final screenBottom = gameRef.player.position.y +
(gameRef.size.x / 2) +
gameRef.screenBufferSpace;
//当平台往下移动离开屏幕时,可以将其移除并放置一个新平台
if (topOfLowestPlatform > screenBottom) {
// 生成一个新跳板
var newPlatformX = _generateNextX(100);
var newPlatformY = _generateNextY();
final nextPlatform =
_semiRandomPlatform(Vector2(newPlatformX, newPlatformY));
add(nextPlatform);
platforms.add(nextPlatform);
//移除屏幕外的平台
final lowestPlat = platforms.removeAt(0);
lowestPlat.removeFromParent();
//增加分数,移除一个屏幕加一分
gameRef.gameManager.increaseScore();
_maybeAddPowerUp();
_maybeAddEnemy();
}
super.update(dt);
}
🏓游戏控制器
完成了所有需要的组件内容,现在是最后一步,编写游戏的运行逻辑!
class FlutterDashDoodleJump extends FlameGame
with HasKeyboardHandlerComponents, HasCollisionDetection {
FlutterDashDoodleJump({super.children});
}
定义所有需要的控制器
late Player player;
final World _world = World();
GameManager gameManager = GameManager();
LevelManager levelManager = LevelManager();
ObjectManager objectManager = ObjectManager();
加载游戏
@override
Future<void> onLoad() async {
await add(_world);
// 添加游戏管理器
await add(gameManager);
// 添加暂停按钮和记分器的UI
overlays.add('gameOverlay');
// 添加关卡/难度管理器
await add(levelManager);
}
初始化游戏
void initializeGameStart() {
//重新计分
gameManager.reset();
if (children.contains(objectManager)) objectManager.removeFromParent();
levelManager.reset();
player.reset();
// 设置摄像机的世界边界将允许摄像机“向上移动”
// 但要保持水平固定,这样玩家就可以从屏幕的一边走出去,然后在另一边重新出现。
camera.worldBounds = Rect.fromLTRB(
0,
-_world.size.y,
camera.gameSize.x,
_world.size.y + screenBufferSpace, // 确保游戏的底部边界低于屏幕底部
);
camera.followComponent(player);
player.position = Vector2(
(_world.size.x - player.size.x) / 2,
(_world.size.y - player.size.y) / 2,
);
objectManager = ObjectManager(
minVerticalDistanceToNextPlatform: levelManager.minDistance,
maxVerticalDistanceToNextPlatform: levelManager.maxDistance);
add(objectManager);
objectManager.configure(levelManager.level, levelManager.difficulty);
}
开始/重新开始游戏
///开始游戏
void startGame() {
addPlayer();
initializeGameStart();
gameManager.gameState = GameState.playing;
overlays.remove('mainMenuOverlay');
}
void addPlayer() {
player = Player();
player.setJumpSpeed(levelManager.jumpSpeed);
add(player);
}
///再来一次
void resetGame() {
startGame();
overlays.remove('gameOverOverlay');
}
///回到主页面
void backMenu() {
overlays.remove('gameOverOverlay');
overlays.add('mainMenuOverlay');
}
游戏暂停
void togglePauseState() {
if (paused) {
resumeEngine();
} else {
pauseEngine();
}
}
玩家死亡
//嗝屁了
void onLose() {
gameManager.gameState = GameState.gameOver;
player.removeFromParent();
overlays.add('gameOverOverlay');
}
计分、暂停、开始菜单、难度选择这些简单的页面就不在文章里说明了,有兴趣的朋友可以自行查看 git 仓库~那么到这里我们就完成了整个游戏的编写!😘
关于我
Hello,我是 Taxze,如果您觉得文章对您有价值,希望您能给我的文章点个❤️,有问题需要联系我的话:我在这里 ,也可以通过掘金的新的私信功能联系到我。如果您觉得文章还差了那么点东西,也请通过关注督促我写出更好的文章~万一哪天我进步了呢?😝
版权声明: 本文为 InfoQ 作者【编程的平行世界】的原创文章。
原文链接:【http://xie.infoq.cn/article/c89be405e19357f45addeb61a】。
本文遵守【CC-BY 4.0】协议,转载请保留原文出处及本版权声明。
编程的平行世界
他日若遂凌云志 敢笑黄巢不丈夫 2020-10-15 加入
曾许少年凌云志,誓做人间第一流. 一起加入Flutter技术交流群532403442 有好多好多滴学习资料喔~ 小T目前主攻Android与Flutter, 通常会搞搞人工智能、SpringBoot 、Mybatiys等.
评论