前语
今日咱们持续探索制作与手势的组合的实践,不知道的朋友有没有喜爱下围棋的,其实下棋也是制作和手势结合的一个典型的实际使用,毕竟棋盘肯定是需求咱们自定义制作出来的,那么今日咱们就用Flutter来持续制作一个规范的围棋棋盘吧。
制作
制作之前,咱们先了解下,围棋棋盘和棋子以及规矩,在咱们下棋时,每落下一颗棋子,棋盘是固定不变的,改动的仅仅棋盘上的棋子,那么在下棋改写画布时,咱们只需改写棋盘上的棋子即可,棋子落子生根,落下不行随意走动,那么在手势中处理中咱们只需处理点击事情即可,没有移动以及其他事情处理,Ok,有了这个思路,接下来咱们开端制作。
棋盘
第一步,画棋盘,围棋中,现在主流的游戏规矩有9、13、19
路三种棋盘,9
路适合新手入门,而19
路则是围棋发展至今的规范的围棋棋盘,也是各种竞赛默许的围棋棋盘。
题外话:尽管仅仅19 * 19路的棋盘,可是就在这小小的围棋棋盘上可呈现的改动绝对是一个无法想象的天文数字,以至于围棋发展了2000多年,古今棋局至今没有彻底相同的一局棋,正所谓千古无同局说的就是围棋,这也是人类对围棋为之入神的原因,正是这样的原因,人类相较于电脑一直控制着围棋游戏水平的天花板,直至2016年Google
研发的Alphago
面世之后,初次与当时人类顶尖棋手李世石的对局中,以4-1战绩横扫人类,人类控制围棋的神话也随之崩塌了,而赢的那一局也成为了现在为止人类打败Alphago
的唯一的一局棋,之后Alphago
横扫围棋棋坛,对战人类再无败绩,也旁边面说明了人类关于围棋当中无量奥妙的把握初次被人工智能逾越,之后人类不服,约Alphago
在17年与当时世界冠军柯洁进行的三番局对弈,成果Alphago
以3-0完胜柯洁,随之Google宣告,Alphago
将永久退出围棋届一切竞赛,并将源代码开源,再之后,国内围棋人工智能如漫山遍野般的涌出,至此,人工智能控制围棋的年代到来。
哈哈,扯远了,接下来咱们开端画棋盘了, 首要创立棋盘和棋子,棋盘为布景,棋子为前景,接下来首要制作棋盘。
CustomPaint(
size: Size(
// MediaQuery.of(context).size.width, MediaQuery.of(context).size.width),
size,
size),
painter: _QpPainter(),// 棋盘
foregroundPainter: _QzPainter(),// 棋子
),
咱们知道围棋棋盘由相同线条组成的是一个正方矩形。首要制作布景以及竖线:
//eSide 为线之间的间隔
for (int i = 0; i < qpSize; i++) {
canvas.save();
canvas.translate(eSide * i, 0);
if (i == 0 || i == qpSize - 1) {
paint..strokeWidth = 2;
} else {
paint..strokeWidth = 1;
}
canvas.drawLine(Offset(0,0),
Offset(0, size.height), paint..color = Colors.black);
canvas.restore();
}
制作横线:
是不是很简略,咱们将外部周围的线粗1个像素单位,也就是在制作横竖线第一条和终究一条时画笔加粗即可。
星位及天元
接下来咱们制作星位和天元,19路
围棋棋盘为例,一共有8
个星位以及1
个天元,别离是4
个角星位,4
个边星位,加中心1
个天元,,用大黑点符号,便利定位,
代码:
double eSide = size.width / 18; //格子边长
/// 星位 坐标数据
List<Offset> offsetXList = [];
// 19 路
// 左星位
offsetXList.add(Offset(eSide * 3, eSide * 3));
offsetXList.add(Offset(eSide * 3, eSide * 9));
offsetXList.add(Offset(eSide * 3, eSide * 15));
// 中心
offsetXList.add(Offset(eSide * 9, eSide * 3));
offsetXList.add(Offset(eSide * 9, eSide * 9)); // 天元
offsetXList.add(Offset(eSide * 9, eSide * 15));
// 右星位
offsetXList.add(Offset(eSide * 15, eSide * 3));
offsetXList.add(Offset(eSide * 15, eSide * 9));
offsetXList.add(Offset(eSide * 15, eSide * 15));
for (var i = 0; i < offsetXList.length; i++) {
canvas.drawCircle(offsetXList[i], 3, paint..style = PaintingStyle.fill);
}
坐标
接下来制作棋盘坐标,上面仅仅棋盘的中心区域,其实除了下棋区域,围棋棋盘上还会标有坐标,能够对某一个点进行精准的描绘,不同棋盘坐标方位不同,这儿咱们将他定位到左面和上边,那么咱们上面的棋盘的下棋区域就不能占有悉数方位了,需求腾出点空间留给坐标,比如咱们将左面和上边别离留出40
个像素的值设为margin1
,下方和右方留出20
个像素值设为margin2
,变成这样, 格子边长核算公式就变为:
double eSide = (size.width - margin1 - margin2) / (qpSize - 1); //格子边长
棋盘款式就变为这样:
接下来咱们就能够在左面和上边制作坐标了,咱们先制作左面坐标,坐标为1-19
数字对齐网格线。
首要咱们再来回想下制作文字的办法,
var textPainter = TextPainter(
text: TextSpan(
text: "888",
style: TextStyle(
fontSize: 40,
foreground: Paint()
..style = PaintingStyle.fill
..strokeWidth = 1,
)),
textAlign: TextAlign.left,
maxLines: 1,
ellipsis: "...",
textDirection: TextDirection.ltr);
textPainter.layout();
textPainter.paint(canvas, Offset(0,0));
上方代码制作888文字,默许制作区域为文字区域的左上角和原点重合,
这儿咱们期望让888关于原点居中,能够通过下方代码获取文本Size
巨细,将文本向左上平移,即可让文字和原点居中。
Size textSize = textPainter.size;
textPainter.paint(canvas, Offset(-textSize.width / 2, -textSize.height / 2));
canvas.drawRect(
Rect.fromLTRB(0, 0, textSize.width, textSize.height)
.translate(-textSize.width / 2, -textSize.height / 2),
_paint
..color = Colors.blue.withAlpha(88)
..style = PaintingStyle.fill);
ok,有了以上了解,接下来咱们就能够开端制作坐标了,依照以上方法,先制作个1,由于咱们的棋盘画布原点是没有通过平移的,所以在原始的左上角,是如下作用,
接着咱们平移画布,将文本对齐最下面的网格线,向下移动区域长度为 margin1
+18
个格子边长,
canvas.save();
canvas.translate(
margin1 - 20, (size.height - margin2) - eSide * 18);
canvas.drawLine(Offset(margin1, margin1),
Offset(margin1, size.height - margin2), paint..color = Colors.black);
canvas.restore();
1
就跑到咱们想要的方位了。
接下来的作业就变的简略了,循环改动平移即可。
复制粘贴循环今后终究得到了以下作用:
棋盘的根本制作到这儿就结束了。
棋子
接下来开端制作棋子, 棋子其实仍是比较简略的,是非两个圆即可,
可是这么看着太平面了,接下来这儿咱们能够给棋子添加一点点细节,让棋子看起来愈加的丰满立体些,首要棋子咱们不要设置纯黑和纯白色,纯黑和纯白在设备上显现上有时不太友爱,第二咱们从棋子的左上角像右下角调整突变色进行线性突变,让棋子有一些阴影过渡的感觉,这样看起来就有些立体感了。
代码:
canvas.save();
canvas.rotate(pi * 2 - pi / 4);
canvas.drawPath(
path,
_paint
..shader = ui.Gradient.linear(Offset(0, -40), Offset(10, 10),
[Color(0xFFa5a5a5), Color(0xFF333333)])
..style = PaintingStyle.fill);
// 白字
path.addOval(Rect.fromCenter(center: Offset.zero, width: 40, height: 40));
path.close();
canvas.translate(60, 0);
canvas.rotate(pi * 2 - pi / 4);
canvas.drawPath(
path,
_paint
..shader = ui.Gradient.linear(Offset(0, -40), Offset(10, 10),
[Color(0xFFa5a5a5), Color(0xFFF3F3F3)])
..style = PaintingStyle.fill);
canvas.restore();
调整结束的作用:
看起来是不是比刚开端纯是非稍稍舒服了一点。
手数
下一步制作棋子上的手数,咱们都知道围棋棋子上是能够显现手数的,这儿咱们只需求在棋子坐标中心添加文字即可, 也十分的简略。
作用:
插曲: 这儿遇到了一个小插曲,当咱们的文本字号设置比较小的时分,例如fontSize = 7.2时,文本没有在文本区域中居中, 以8数字为例,由于8是左右对称数字,可是在字号较小时它呈现了比较显着的不对称的作用,呈现这样的作用会导致,当棋子过小,手数的数字不会呈现在棋子中央,会呈现一些比较显着的偏移,大一点是看不出来的。
字号7.2:
当我调整为7的时分,形似左右对称,可是上下间距和7.2时距离又不同,
字号7:显着下边距变大了
当我将字号调大一点时,例如14时的作用:
字号14:
好像确实没有彻底对称,仅仅字号大一些没有那么显着,不知道文本的区域是怎样核算的,有知道的小伙伴能够告诉我下。感谢感谢~~
当时符号
接下来持续制作当时符号,围棋下棋时当落子时,需求对刚落下的子进行一个符号,好让对手一眼看到你下到了哪个地方,这儿咱们就简略的在棋子的下方制作一个倒三角,三角形上底边宽度为字号的巨细,也是十分的简略。
到这儿,棋子也画完了,接下来咱们就需求结合手势将这些棋子下到棋盘上。
手势
手势这儿比较的简略,咱们只需求处理点击事情即可。首要咱们先来确定手势接触区域的落子的规模。
手势点击触发落子规模
由于整个棋盘是咱们的手势接触区域,由于坐标原因,真实的手势触发区域应该是真实的棋盘,见下图外围红框,能够看到可是当手指在外围红框内时,才以为是落子规模,由于棋子是要下在横竖线条的交叉点上的,所以这儿的接触规模咱们设置为以交叉点为中心,边长 = 棋盘格子边长,见下图蓝色区域的规模,关于边角,咱们需求向外扩展格子边长的一半,见下方小红框,这时咱们就以为当时用户预备落到区域中心的这个方位,其他点同理。
点击事情处理代码:
onPanDown: (e) {
double dx = e.localPosition.dx;
double dy = e.localPosition.dy;
double eSide =
(widget.size - (margin1 + margin2)) / (widget.qpSize - 1); // 格子边长
if (dx < margin1 - eSide / 2 ||
dy < margin1 - eSide / 2 ||
dx - (margin1 + ((widget.qpSize - 1) * eSide) + eSide / 2) > 0 ||
dy - (margin1 + ((widget.qpSize - 1) * eSide) + eSide / 2) > 0) {
return;
}
print("边长:$eSide ");
// 将原点设置为上方红框左上角
dx = dx - (margin1 - eSide / 2);
dy = dy - (margin1 - eSide / 2);
print("点击坐标:dx= $dx dy= $dy");
dx = dx - dx % eSide;
dy = dy - dy % eSide;
print("点击核算后棋盘上的坐标:dx= $dx dy= $dy");
for (var i = 0; i < qzList.length; i++) {
double x = goList.value[i].dx;
double y = goList.value[i].dy;
if (dx == x && dy == y) {
//说明这个方位已经有棋子 return
return;
}
}
qzList.add(Offset(dx, dy));
List<Offset> qList = [];
qList.addAll(qzList);
// 更新棋子
goList.value = qList;
print("添加坐标:dx= $dx dy= $dy");
},
获取到终究落子坐标,接下来的作业就比较简略了,只需更新数据,改写画布将棋子、手数以及符号制作到对应坐标即可。
看下作用:
试下
假如在手机上操作,由于屏幕小的原因,免不了会有误触的时分,这时分初次点击棋盘时就需求有一个提示是否下在此处的功用,那么咱们就需求再添加一个试下的坐标点数据,
// 试下点数据
ValueNotifier<Offset?> tryOffset = ValueNotifier(null);
试下时,这儿将棋子本来颜色设置为了0.6的的透明度,
if (qzType == QzType.black) {
if (isTry) {
qzColors = [
Color(0xFFa5a5a5).withOpacity(0.6),
Color(0xFF333333).withOpacity(0.6)
];
} else {
qzColors = [Color(0xFFa5a5a5), Color(0xFF333333)];
}
} else {
if (isTry) {
qzColors = [
Color(0xFFa5a5a5).withOpacity(0.6),
Color(0xFFF3F3F3).withOpacity(0.6)
];
} else {
qzColors = [Color(0xFFa5a5a5), Color(0xFFF3F3F3)];
}
}
围棋考究落子生根,所以这儿没有加悔棋功用,悔棋功用加的话也很简略,只需将棋子数组的终究一条数据删除改写画布即可。
之后咱们将棋盘巨细、棋盘路数、是否显现手数等字段对外露出,就是一个支持9、13、19路的围棋棋盘了,现在离真实的下棋就差规矩算法了,这个今后有时间再加。
终究演示:
围棋棋盘的制作以及手势交互到这儿就根本完成了, 由于棋盘用的Flutter的纯UI制作,所以天然的支持跨端展现,之后有时间加上围棋的游戏规矩,连个网,就能够对战了。
总结
本篇文章主要介绍了围棋棋盘的制作以及落子的手势交互,并不能直接下棋哈,由于我还没加游戏算法规矩,主要围棋的规矩尽管简略,可是算起来仍是有点复杂的,这个有时间再研讨下,有机会能够做一个内网联机围棋对战渠道,嘿嘿,那本篇文章到这儿就结束了,期望对你有所帮助~
我正在参加技术社区创作者签约计划招募活动,点击链接报名投稿。