使用Flutter编写的一个2048小游戏
最近在学习flutter,随手写了一个2048的小游戏,目前只实现了基本的功能,还有一些功能没有加上去,主要是因为在调用setState()方法更新UI的时候出现了一些异常,暂时没有找到解决的方法。
下面是程序执行效果:
主要widget就是两个,一个是游戏页面,一个是游戏页面中每一个方块对应的GameBoxWidget,其中游戏页面主要负责GameBox的创建,维护和移动等操作,GameBoxWidget主要负责游戏方块自身的数字更新,颜色更新等。下面是代码:
//滑动方向,默认垂直方向
bool _moveVertical = true;
//滑动距离,配合滑动方向确定方块的移动信息
double _moveDistance = 0;
bool _needBuildNextBox = false;
//创建游戏方块对象
List<GameBoxWidget> _gameBoxList = List();
int _firstPosition;
int _secondPosition;
@override
void initState() {
super.initState();
//依次创建创建16个方块,避免重复创建对象
_initGameBox();
}
//初始化方块信息
void _initGameBox() {
_gameBoxList.clear();
for (int i = 0; i < 16; i++) {
GameBoxWidget gameBoxWidget = GameBoxWidget(
position: i,
);
_gameBoxList.add(gameBoxWidget);
}
_firstPosition = generalPoaition();
_secondPosition = generalPoaition();
}
@override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.only(top: MediaQuery.of(context).padding.top),
color: Color.fromARGB(255, 227, 204, 169),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
//上面是显示游戏名称和得分
Expanded(
flex: 2,
child: Stack(
alignment: Alignment.centerLeft,
children: <Widget>[
Positioned(
left: 10.0,
//左边显示游戏名称
child: Text(
"2048",
style: TextStyle(
color: Color.fromARGB(200, 105, 75, 35),
fontSize: 20.0,
fontWeight: FontWeight.bold,
),
textScaleFactor: 2.0,
),
),
),
Expanded(
flex: 5,
child: GestureDetector(
child: Center(
child: Container(
padding: EdgeInsets.all(15.0),
constraints: BoxConstraints.tightFor(),
child: Container(
padding: EdgeInsets.only(bottom: 10.0, right: 10.0),
constraints: BoxConstraints.tightFor(),
decoration: BoxDecoration(
color: Color.fromARGB(255, 105, 75, 35),
borderRadius: BorderRadius.circular(5.0)),
child: Stack(
children: _gameBoxList.map((item) {
if (item.position == _firstPosition ||
item.position == _secondPosition) {
item.value = generatorRandom();
}
return item;
}).toList(),
),
),
),
),
onVerticalDragUpdate: (updateEvent) {
_moveVertical = true;
_moveDistance += updateEvent.delta.dy;
},
onVerticalDragEnd: (dragUpDetails) {
checkPostionMoveInfo(_moveVertical, _moveDistance);
_moveDistance = 0;
},
onHorizontalDragUpdate: (updateEvent) {
_moveVertical = false;
_moveDistance += updateEvent.delta.dx;
},
onHorizontalDragEnd: (details) {
checkPostionMoveInfo(_moveVertical, _moveDistance);
_moveDistance = 0;
},
),
),
Spacer(
flex: 1,
),
],
),
);
}
//遍历查看需要滑动的position和滑动的距离
void checkPostionMoveInfo(bool isVertical, double path) {
//如果是垂直方向滑动
if (isVertical) {
List<GameBoxWidget> column0Box = List();
List<GameBoxWidget> column1Box = List();
List<GameBoxWidget> column2Box = List();
List<GameBoxWidget> column3Box = List();
for (int i = 0; i < 16; i++) {
switch (i % 4) {
case 0:
//将第一列的数据加入到List中
column0Box.add(_gameBoxList[i]);
break;
case 1:
column1Box.add(_gameBoxList[i]);
break;
case 2:
column2Box.add(_gameBoxList[i]);
break;
case 3:
column3Box.add(_gameBoxList[i]);
break;
default:
}
}
if (path < 0) {
print("向上滑动");
//垂直方向向上移动
moveVerticalUp(column0Box, true);
moveVerticalUp(column1Box, true);
moveVerticalUp(column2Box, true);
moveVerticalUp(column3Box, true);
} else {
moveVerticalDown(column0Box, true);
moveVerticalDown(column1Box, true);
moveVerticalDown(column2Box, true);
moveVerticalDown(column3Box, true);
}
} else {
List<GameBoxWidget> row0Box = List();
List<GameBoxWidget> row1Box = List();
List<GameBoxWidget> row2Box = List();
List<GameBoxWidget> row3Box = List();
for (int i = 0; i < 16; i++) {
switch (i ~/ 4) {
case 0:
//将第一列的数据加入到List中
row0Box.add(_gameBoxList[i]);
break;
case 1:
row1Box.add(_gameBoxList[i]);
break;
case 2:
row2Box.add(_gameBoxList[i]);
break;
case 3:
row3Box.add(_gameBoxList[i]);
break;
default:
}
}
if (path < 0) {
//水平方向向左移动
moveVerticalUp(row0Box, false);
moveVerticalUp(row1Box, false);
moveVerticalUp(row2Box, false);
moveVerticalUp(row3Box, false);
print("循环完成:是否需要生成下一个游戏方块$_needBuildNextBox");
} else {
moveVerticalDown(row0Box, false);
moveVerticalDown(row1Box, false);
moveVerticalDown(row2Box, false);
moveVerticalDown(row3Box, false);
}
}
//在此处判断是否需要生成洗一个方块
if (_needBuildNextBox) {
_generalNextRandonBox();
} else {
SnackBar(
content: Text("请向其它方向移动"),
duration: Duration(seconds: 1),
);
}
_needBuildNextBox = false;
}
/**
*具体的移动方块的代码
*/
//查看当前方块是否存在
bool checkGameBoxExit(GameBoxWidget widget) {
if (widget.value != null && widget.value > 0) {
return true;
}
return false;
}
//查看两个方块是否可以合并
bool checkGameBoxMerge(GameBoxWidget widget1, GameBoxWidget widget2) {
if (widget1.value == widget2.value) {
return true;
}
return false;
}
//在0~15之间生成一个随机数
int generalPoaition() {
//首先循环遍历查看是否所有的方块都被填充
bool allIn = true;
for (var i = 0; i < _gameBoxList.length; i++) {
if (_gameBoxList[i].value == null || _gameBoxList[i].value < 2) {
allIn = false;
break;
}
}
if (allIn) {
return -1;
}
int generalPosition;
do {
generalPosition = Random().nextInt(16);
} while (_gameBoxList[generalPosition].value != null &&
_gameBoxList[generalPosition].value > 0);
return generalPosition;
}
//生成一个2或者4的随机数
int generatorRandom() {
int value;
do {
value = Random().nextInt(5);
} while (value != 2 && value != 4);
return value;
}
}
下面是游戏方块GameBoxWidget的代码
class GameBoxWidget extends StatefulWidget {
//方块所在的位置
final int position;
//方块显示的数值
int value;
_GameBoxState _gameBoxState;
GameBoxWidget({Key key, this.position = 0}) : super(key: key);
//更新value值
void updateValue(int newValue){
_gameBoxState.setState((){
this.value =newValue;
});
}
//设置动画执行
void startAnimation(int movePosition, bool vertical) {
_gameBoxState.startPaddingAnimation(movePosition, vertical);
}
@override
_GameBoxState createState() {
_gameBoxState = _GameBoxState();
return _gameBoxState;
}
}
class _GameBoxState extends State<GameBoxWidget>
with SingleTickerProviderStateMixin {
//方块的宽度
double _boxWidth;
//方块初始时的颜色,根据方块中的数值来显示
Color _baseColor = Colors.orangeAccent;
int _baseColorR = 20;
int _baseColorG = 30;
int _baseColorB = 30;
//距离顶部的padding
double _topPadding = 0;
//距离左边的padding
double _leftPadding = 0;
//动画控制器
AnimationController _controller;
//距离顶部的动画
Animation<double> _topAnimation;
Tween<double> _topTween;
//距离左边的动画
Animation<double> _leftAnimation;
Tween<double> _leftTween;
@override
void initState() {
// TODO: implement initState
super.initState();
//获取颜色值
//_getBoxColor();
//设置动画控制器
_controller = new AnimationController(
duration: Duration(milliseconds: 200),
vsync: this,
);
//初始化动画参数
_topTween = Tween<double>();
_topAnimation = _topTween.animate(_controller);
_leftTween = Tween<double>();
_leftAnimation = _leftTween.animate(_controller);
}
//根据不同的数字设置不同的背景颜色
Color _getBoxColor() {
int currentValue = widget.value;
if (widget.value != null && widget.value > 0) {
do {
if (_baseColorR + 30 < 255) {
_baseColorR += 22;
} else {
_baseColorR = 70;
}
if (_baseColorG + 20 < 255) {
_baseColorG += 22;
} else {
_baseColorG = 80;
}
if (_baseColorB + 33 < 255) {
_baseColorB += 22;
} else {
_baseColorB = 90;
}
currentValue = (currentValue ~/ 2).toInt();
} while (currentValue / 2 != 1);
}
print("获取到的颜色值:$_baseColorR,$_baseColorG,$_baseColorB");
_baseColor = Color.fromARGB(255, _baseColorR, _baseColorG, _baseColorB);
return _baseColor;
}
//根据不同的value值设置不同的背景颜色
Color _getBackColor() {
switch (widget.value ?? 0) {
case 0:
return Colors.orangeAccent;
case 2:
return Colors.pinkAccent;
case 4:
return Colors.purpleAccent;
case 8:
return Colors.redAccent;
case 16:
return Colors.tealAccent;
case 32:
return Colors.deepOrange;
case 64:
return Colors.amberAccent;
case 128:
return Colors.blueAccent;
case 256:
return Colors.brown;
case 512:
return Colors.cyanAccent;
case 1024:
return Colors.deepOrangeAccent;
case 2048:
return Colors.deepPurpleAccent;
case 4096:
return Colors.indigoAccent;
case 8192:
return Colors.limeAccent;
case 16384:
return Colors.orangeAccent;
default:
}
}
//启动动画
/**
* value: 移动距离
* vertical:方向信息
*/
void startPaddingAnimation(int movePosition, bool isVertical) async{
print("开始执行动画:$isVertical");
if (widget.position < 16) {
if (isVertical) {
_topTween.begin = 10.0 +
(widget.position ~/ 4) * _boxWidth +
(widget.position ~/ 4) * 10;
_topTween.end =
10.0 + (movePosition ~/ 4) * _boxWidth + (movePosition ~/ 4) * 10;
} else {
_leftTween.begin = 10.0 +
(widget.position % 4) * _boxWidth +
(widget.position % 4) * 10;
_leftTween.end =
10.0 + (movePosition % 4) * _boxWidth + (movePosition % 4) * 10;
}
_controller.addStatusListener((listener) {
switch (listener) {
case AnimationStatus.completed:
//动画执行完成,重置topPadding和leftPadding
_topPadding = 10.0 +
(widget.position ~/ 4) * _boxWidth +
(widget.position ~/ 4) * 10;
_leftPadding = 10.0 +
(widget.position % 4) * _boxWidth +
(widget.position % 4) * 10;
//_getBoxColor();
break;
default:
}
});
_controller.addListener(() {
if (isVertical) {
_topPadding = _topAnimation.value;
} else {
_leftPadding = _leftAnimation.value;
}
});
await _controller.forward().orCancel;
}
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _controller,
builder: (context, child) {
return Positioned(
left: _leftPadding,
top: _topPadding,
child: Container(
width: _boxWidth,
height: _boxWidth,
alignment: Alignment.center,
decoration: BoxDecoration(
color: _getBackColor(),
borderRadius: BorderRadius.circular(4.0),
),
child: Text(
widget.value == null ? "" : widget.value.toString(),
style: TextStyle(
color: Colors.white,
fontSize: 14.0,
),
textScaleFactor: 1.3,
),
),
);
});
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
_boxWidth = (MediaQuery.of(context).size.width - 30 - 50) / 4;
//设置每一个gameBox的位置
_topPadding =
10.0 + (widget.position ~/ 4) * _boxWidth + (widget.position ~/ 4) * 10;
_leftPadding =
10.0 + (widget.position % 4) * _boxWidth + (widget.position % 4) * 10;
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
}
上面的代码没有写完,主要是复制粘贴太麻烦,完整的代码在github上:https://github.com/yiwenfanjuan/project2048。