Flutter 的出现和成长利用了跨平台游戏设计的发展;Flutter 游戏只需几行代码即可创建设计和逻辑,同时保持出色的 UI/UX。
Flutter 能够以高达 60FPS 的速度渲染。你可以利用该功能构建简单的 2D 甚至 3D 游戏。请记住,在 Flutter 中开发更复杂的游戏并不是一个好主意,因为大多数开发人员会倾向于为复杂的应用程序进行原生开发。
如何使用Flutter构建2D游戏?在本教程中,我们将重新创建有史以来最早的电脑游戏之一:Pong,Pong是一个简单的游戏,所以它是一个很好的起点。本文分为两个主要部分:游戏逻辑和用户界面,通过分别关注重要部分,使构建更加清晰。
在我们进入构建之前,让我们回顾一下先决条件和设置。
Flutter 2D游戏构建教程预先准备
要理解本课程并编写代码,你将需要以下内容:
- Flutter安装在你的机器上
- Dart 和 Flutter 的工作知识
- 一个文本编辑器
Flutter构建2D游戏实例入门
在这篇文章中,我们将用作屏幕 X 轴和 Y 轴位置的表示,这将有助于开发游戏的物理特性。我们还将为我们的一些变量创建无状态小部件,并在文件中声明它们,以使代码体积更小且易于理解。Alignment(x,y)
Vector(x,y)
homepage.dart
首先,创建一个 Flutter 项目。清除文件中的默认代码,并导入用于在应用程序中包含Material 小部件的包。main.dart
material.dart
接下来,创建一个名为并返回的类,然后创建一个并将其传递给如下所示的参数:MyApp()
MaterialApp()
statefulWidget
HomePage()
home
MaterialApp()
import 'package:flutter/material.dart';
import 'package:pong/homePage.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner:false,
home: HomePage(),
);
}
}
游戏逻辑
如何使用Flutter构建2D游戏?在HomePage()
中,我们需要编写一些函数和方法来处理数学和物理相关的操作。这些包括处理碰撞、加速或减速以及游戏中的导航。
但首先,我们需要声明一些参数,这些参数将代表球、球员的位置对齐以及双方球员的初始得分。参数的代码应该放在 下_HomePageState
,我们将在后面的帖子中引用:
//player variations
double playerX = -0.2;
double brickWidth = 0.4;
int playerScore = 0;
// enemy variable
double enemyX = -0.2;
int enemyScore = 0;
//ball
double ballx = 0;
double bally = 0;
var ballYDirection = direction.DOWN;
var ballXDirection = direction.RIGHT;
bool gameStarted = false;
...
然后,我们提供球和砖块运动方向的枚举:
enum direction { UP, DOWN, LEFT, RIGHT }
...
为了让这个游戏工作,我们需要创建人工重力,这样当球碰到顶部砖块 (0.9) 或底部砖块 (-0.9) 时,它会朝着相反的方向前进。否则,如果它没有击中任何一块砖块,而是进入了比赛场地的顶部 (1) 或底部 (-1),则将其记录为玩家的损失。
当球撞到左边 (1) 或右边 (-1) 的墙壁时,它会朝着相反的方向前进:
void startGame() {
gameStarted = true;
Timer.periodic(Duration(milliseconds: 1), (timer) {
updatedDirection();
moveBall();
moveEnemy();
if (isPlayerDead()) {
enemyScore++;
timer.cancel();
_showDialog(false);
// resetGame();
}
if (isEnemyDead()) {
playerScore++;
timer.cancel();
_showDialog(true);
// resetGame();
}
});
}
...
在上面的代码,我们开始与函数从而改变了布尔来,之后我们称之为以一秒的持续时间。startGame()
gameStarted
true
Timer()
在计时器内,函数如、和与检查任一玩家是否失败的语句一起传递。如果是,则累积分数,取消计时器,并显示一个对话框。updatedDirection()
moveBall()
moveEnemy()
if
Flutter构建2D游戏实例:以下函数确保球不会超出0.9
对齐,并且球在与砖块接触时只会向相反的方向移动:
void updatedDirection() {
setState(() {
//update vertical dirction
if (bally >= 0.9 && playerX + brickWidth>= ballx && playerX <= ballx) {
ballYDirection = direction.UP;
} else if (bally <= -0.9) {
ballYDirection = direction.DOWN;
}
// update horizontal directions
if (ballx >= 1) {
ballXDirection = direction.LEFT;
} else if (ballx <= -1) {
ballXDirection = direction.RIGHT;
}
});
}
void moveBall() {
//vertical movement
setState(() {
if (ballYDirection == direction.DOWN) {
bally += 0.01;
} else if (ballYDirection == direction.UP) {
bally -= 0.01;
}
});
//horizontal movement
setState(() {
if (ballXDirection == direction.LEFT) {
ballx -= 0.01;
} else if (ballXDirection == direction.RIGHT) {
ballx += 0.01;
}
});
}
...
此外,如果球击中场地的左侧或右侧,则它会朝着相反的方向前进:
void moveLeft() {
setState(() {
if (!(playerX - 0.1 <= -1)) {
playerX -= 0.1;
}
});
}
void moveRight() {
if (!(playerX + brickWidth >= 1)) {
playerX += 0.1;
}
}
...
该和功能有助于控制我们的砖块运动使用键盘上的箭头从左向右。这些与声明一起使用,以确保积木不会超出场地两个轴的宽度。moveLeft()
moveRight()
if
该函数将球员和球返回到他们的默认位置:resetGame()
void resetGame() {
Navigator.pop(context);
setState(() {
gameStarted = false;
ballx = 0;
bally = 0;
playerX = -0.2;
enemyX =- 0.2;
});
}
...
接下来,我们创建两个函数和,它们返回一个布尔值。他们检查是否有任何一个球员输了(如果球已经击中了砖块后面的垂直部分):isEnemyDead()
isPlayerDead()
bool isEnemyDead(){
if (bally <= -1) {
return true;
}
return false;
}
bool isPlayerDead() {
if (bally >= 1) {
return true;
}
return false;
}
...
Flutter 2D游戏构建教程:最后,_showDialog
当任一玩家获胜时,该函数会显示一个对话框。它传递一个布尔值 ,enemyDied
来区分玩家何时输球。然后,它宣布非失败玩家赢得了这一轮,并使用获胜玩家的颜色显示文本“再玩一次:”
void _showDialog(bool enemyDied) {
showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
// return object of type Dialog
return AlertDialog(
elevation: 0.0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10.0)),
backgroundColor: Colors.purple,
title: Center(
child: Text(
enemyDied?"Pink Wins": "Purple Wins",
style: TextStyle(color: Colors.white),
),
),
actions: [
GestureDetector(
onTap: resetGame,
child: ClipRRect(
borderRadius: BorderRadius.circular(5),
child: Container(
padding: EdgeInsets.all(7),
color: Colors.purple[100],
child: Text(
"Play Again",
style: TextStyle(color:enemyDied?Colors.pink[300]: Colors.purple[000]),
)),
),
)
],
);
});
}
用户界面
如何使用Flutter构建2D游戏?现在,我们将开始开发用户界面。
在文件的小部件build
内,添加以下代码:homePage.dart
return RawKeyboardListener(
focusNode: FocusNode(),
autofocus: false,
onKey: (event) {
if (event.isKeyPressed(LogicalKeyboardKey.arrowLeft)) {
moveLeft();
} else if (event.isKeyPressed(LogicalKeyboardKey.arrowRight)) {
moveRight();
}
},
child: GestureDetector(
onTap: startGame,
child: Scaffold(
backgroundColor: Colors.grey[900],
body: Center(
child: Stack(
children: [
Welcome(gameStarted),
//top brick
Brick(enemyX, -0.9, brickWidth, true),
//scoreboard
Score(gameStarted,enemyScore,playerScore),
// ball
Ball(ballx, bally),
// //bottom brick
Brick(enemyX, 0.9, brickWidth, false)
],
))),
),
);
在代码中,我们返回,它将在我们在网络上构建时提供从左到右的移动。这也可以为触摸屏设备复制。RawKeyboardListener()
小部件提供用于调用上面在逻辑中编写的函数的功能。还编写了一个子项来指定应用程序的背景颜色和正文。GestureDetector()
onTap
startGame
Scaffold()
接下来,创建一个名为的类Welcome
并传入一个布尔值来检查游戏是否已经开始。如果游戏还没有开始,“tap to play”的文字会变成可见的:
class Welcome extends StatelessWidget {
final bool gameStarted;
Welcome(this.gameStarted);
@override
Widget build(BuildContext context) {
return Container(
alignment: Alignment(0, -0.2),
child: Text(
gameStarted ? "": "T A P T O P L A Y",
style: TextStyle(color: Colors.white),
));
}
}
现在我们可以创建另一个类 ,使用Ball
来处理球设计及其在场地中每个点的位置。我们通过构造函数传递这些参数以实现移动性,如下所示:Alignment(x,y)
class Ball extends StatelessWidget {
final x;
final y;
Ball(this.x, this.y);
@override
Widget build(BuildContext context) {
return Container(
alignment: Alignment(x, y),
child: Container(
decoration: BoxDecoration(shape: BoxShape.circle, color: Colors.white),
width: 20,
height: 20,
),
);
}
}
现在让我们设计Brick
类来处理砖块设计、颜色、位置和玩家类型。
Flutter构建2D游戏实例:在这里,我们使用数学方程 ( ) 来传递 x 和 y 轴的位置:Alignment((2* x +brickWidth)/(2-brickWidth), y)
class Brick extends StatelessWidget {
final x;
final y;
final brickWidth;
final isEnemy;
Brick( this.x, this.y, this.brickWidth, this.isEnemy);
@override
Widget build(BuildContext context) {
return Container(
alignment: Alignment((2* x +brickWidth)/(2-brickWidth), y),
child: ClipRRect(
borderRadius: BorderRadius.circular(10),
child: Container(
alignment: Alignment(0, 0),
color: isEnemy?Colors.purple[500]: Colors.pink[300],
height: 20,
width:MediaQuery.of(context).size.width * brickWidth/ 2,
),
));
}
}
最后,Score
该类应直接放置build
在文件中的小部件下方;它显示每个玩家的分数。homepage.dart
为变量创建一个构造函数enemyScore
并playerScore
处理每个玩家的分数,并gameStarted
检查游戏是否已经开始。这将显示, 或空的内容:Stack()
Container()
class Score extends StatelessWidget {
final gameStarted;
final enemyScore;
final playerScore;
Score(this.gameStarted, this.enemyScore,this.playerScore, );
@override
Widget build(BuildContext context) {
return gameStarted? Stack(children: [
Container(
alignment: Alignment(0, 0),
child: Container(
height: 1,
width: MediaQuery.of(context).size.width / 3,
color: Colors.grey[800],
)),
Container(
alignment: Alignment(0, -0.3),
child: Text(
enemyScore.toString(),
style: TextStyle(color: Colors.grey[800], fontSize: 100),
)),
Container(
alignment: Alignment(0, 0.3),
child: Text(
playerScore.toString(),
style: TextStyle(color: Colors.grey[800], fontSize: 100),
)),
]): Container();
}
}
下面的 gif 显示了游戏的测试:
Flutter 2D游戏构建教程结论
如何使用Flutter构建2D游戏?在这篇文章中,我们介绍了alignment
、RawKeyboardListener
、小部件、布尔值、容器的 ClipRect 以及我们代码中的数学函数,所有这些都用于重新创建 Pong 游戏。游戏还可以通过增加球数或减少砖块长度来改进,使其更加复杂。
我希望这篇文章像构建和记录它一样有用和有趣。随意使用文章中的原理重新创建其他经典游戏,或发明一个新游戏。你可以在 GitHub 上找到这篇文章中的代码链接。