画布 - Canvas
# 画布
canvas提供了使用画布进行2D画图的支持,可用于简单的小游戏开发或者图片编辑。使用canvas可以轻松地在一张图片或一个界面上绘制各种线与图形。
> **注意!** Canvas模块本质上是将 [Android Canvas](https://developer.android.google.cn/reference/android/graphics/Canvas) 进行包装后的结果。本模块的部分用法和文档暂时缺失,但可以在 Android 文档中找到。请参阅 [Android Canvas](https://developer.android.google.cn/reference/android/graphics/Canvas)、[Android Paint](https://developer.android.google.cn/reference/android/graphics/Paint) 与 [Android Path](https://developer.android.google.cn/reference/android/graphics/Path) 了解更多细节。
canvas的坐标系为平面直角坐标系,以控件左上角为原点,控件上边沿为x轴正方向,控件左边沿为y轴正方向。例如分辨率为1920*1080的屏幕上,canvas控件覆盖全屏,画一条从屏幕左上角到屏幕右下角的线段为:
```javascript
canvas.drawLine(0, 0, 1080, 1920, paint);
```
canvas的绘制依赖于画笔Paint, 通过设置画笔的粗细、颜色、填充等可以改变绘制出来的图形。例如绘制一个红色实心正方形为:
```javascript
let paint = new Paint();
// 设置画笔为填充,则绘制出来的图形都是实心的
paint.setStyle(Paint.STYLE.FILL);
// 设置画笔颜色为红色
paint.setColor(colors.RED);
// 绘制一个从坐标(0, 0)到坐标(100, 100)的正方形
canvas.drawRect(0, 0, 100, 100, paint);
```
如果要绘制正方形的边框,则通过设置画笔的Style来实现:
```javascript
let paint = new Paint();
// 设置画笔为描边,则绘制出来的图形都是轮廓
paint.setStyle(Paint.STYLE.STROKE);
// 设置画笔颜色为红色
paint.setColor(colors.RED);
// 绘制一个从坐标(0, 0)到坐标(100, 100)的正方形
canvas.drawRect(0, 0, 100, 100, paint);
```
结合画笔,canvas可以绘制基本图形、图片等。
## 常用方法索引
绘制颜色:
- 根据R、G、B分量绘制颜色:[canvas.drawRGB(r, g, b)](#canvasdrawrgbr-g-b)
- 根据A、R、G、B分量绘制颜色:[canvas.drawARGB(r, g, b)](#canvasdrawargba-r-g-b)
- 根据颜色值绘制颜色:[canvas.drawColor(color)](#canvasdrawcolorcolor)
- 根据颜色值与混合模式绘制颜色:[canvas.drawColor(color, mode)](#canvasdrawcolorcolor-mode)
绘制画笔:
- [canvas.drawPaint(paint)](#canvasdrawpaintpaint)
绘制几何图形:
- 绘制一个点:[canvas.drawPoint(x, y, paint)](#canvasdrawpointx-y-paint)
- 绘制多个点:[canvas.drawPoints(pts, paint)](#canvasdrawpointspts-paint)
- 绘制一条线:[canvas.drawLine(startX, startY, stopX, stopY, paint)](#canvasdrawlinestartx-starty-stopx-stopy-paint)
- 绘制多条线:[canvas.drawLines(pts, paint)](#canvasdrawlinespts-paint)
- 绘制矩形:[canvas.drawRect(r, paint)](#canvasdrawrectr-paint)
- 绘制椭圆:[canvas.drawOval(oval, paint)](#canvasdrawovaloval-paint)
- 绘制圆:[canvas.drawCircle(cx, cy, radius, paint)](#canvasdrawcirclecx-cy-radius-paint)
- 绘制弧:[canvas.drawArc(oval, startAngle, sweepAngle, useCenter, paint)](#canvasdrawarcoval-startangle-sweepangle-usecenter-paint)
- 绘制圆角矩形:[canvas.drawRoundRect(rect, rx, ry, paint)](#canvasdrawroundrectrect-rx-ry-paint)
- 绘制路径:[canvas.drawPath(path, paint)](#canvasdrawpathpath-paint)
绘制文字:
- 沿直线绘制文字:[canvas.drawText(text, x, y, paint)](#canvasdrawtexttext-x-y-paint)
- 沿路径绘制文字:[canvas.drawTextOnPath(text, path, hOffset, vOffset, paint)](#canvasdrawtextonpathtext-path-hoffset-voffset-paint)
绘制图片:
- 绘制位图:[canvas.drawBitmap(bitmap, left, top, paint)](#canvasdrawbitmapbitmap-left-top-paint)
- 绘制图章:[canvas.drawPicture(picture)](#canvasdrawpicturepicture)
获取画布大小:
- 获取宽度:[canvas.getWidth()](#canvasgetwidth)
- 获取高度:[canvas.getHeight()](#canvasgetheight)
矩阵变换:
- 平移:[canvas.translate(dx, dy)](#canvastranslatedx-dy)
- 缩放:[canvas.scale(sx, sy[, px, py])](#canvasscalesx-sy-px-py)
- 旋转:[canvas.rotate(degrees[, px, py])](#canvasrotatedegrees-px-py)
- 切变变换:[canvas.skew(sx, sy)](#canvasskewsx-sy)
## canvas.getWidth()
* 返回 {number}
返回画布当前图层的宽度。
## canvas.getHeight()
* 返回 {number}
返回画布当前图层的高度。
## canvas.drawRGB(r, g, b)
* `r` {number} 红色通道值
* `g` {number} 绿色通道值
* `b` {number} 蓝色通道值
将整个可绘制区域填充为r、g、b指定的颜色。相当于 `canvas.drawColor(colors.rgb(r, g, b))`。
## canvas.drawARGB(a, r, g, b)
* `a` {number} 透明通道值
* `r` {number} 红色通道值
* `g` {number} 绿色通道值
* `b` {number} 蓝色通道值
将整个可绘制区域填充为a、r、g、b指定的颜色。相当于 `canvas.drawColor(colors.argb(a, r, g, b))`。
## canvas.drawColor(color)
* `color` {number} 颜色值
将整个可绘制区域填充为color指定的颜色。
## canvas.drawColor(color, mode)
* `color` {number} 颜色值
* `mode` {PorterDuff.Mode} 混合模式
将整个可绘制区域按mode指定的混合模式填充为color指定的颜色。
## canvas.drawPaint(paint)
* `paint` {Paint} 画笔
将整个可绘制区域用paint指定的画笔填充。相当于绘制一个无限大的矩形,但是更快。通过该方法可以绘制一个指定的着色器的图案。
## canvas.drawPoint(x, y, paint)
* `x` {number} x坐标
* `y` {number} y坐标
* `paint` {Paint} 画笔
在可绘制区域绘制由坐标(x, y)指定的点。
点的形状由画笔的线帽决定(参见paint.setStrokeCap(cap))。
点的大小由画笔的宽度决定(参见paint.setStrokeWidth(width))。
> 如果画笔宽度为0,则也会绘制1个像素(若抗锯齿启用则绘制至多4个像素)。
相当于 `canvas.drawPoints([x, y], paint)`。
## canvas.drawPoints(pts, paint)
* `pts` {Array<number>} 点坐标数组 [x0, y0, x1, y1, x2, y2, ...]
* `paint` {Paint} 画笔
在可绘制区域绘制由坐标数组指定的多个点。
## canvas.drawLine(startX, startY, stopX, stopY, paint)
* `startX` {number} 起点x坐标
* `startY` {number} 起点y坐标
* `endX` {number} 终点x坐标
* `endY` {number} 终点y坐标
* `paint` {Paint} 画笔
在可绘制区域绘制由起点坐标(startX, startY)和终点坐标(endX, endY)指定的线。
绘制时会忽略画笔的样式(Style)。也就是说,即使样式设为“仅填充(FILL)”也会绘制。
退化为点的线(长度为0)不会被绘制。
## canvas.drawLines(pts, paint)
* `pts` {Array<number>} 点坐标数组 [x0, y0, x1, y1, x2, y2, ...]
* `paint` {Paint} 画笔
在可绘制区域绘制由坐标数组指定的点两两连成的一系列线。
每条线需要点坐标数组中的四个连续的值,因此要绘制一条线,数组必须至少包含四个值。
相当于 `canvas.drawLine(pts[0], pts[1], pts[2], pts[3], paint)` 然后 `canvas.drawLine(pts[4], pts[5], pts[6], pts[7], paint)`,之后以此类推。
绘制时会忽略画笔的样式(Style)。也就是说,即使样式设为“仅填充(FILL)”也会绘制。
## canvas.drawRect(r, paint)
* `r` {Rect|RectF} 矩形边界
* `paint` {Paint} 画笔
在可绘制区域绘制由矩形边界r指定的矩形。
绘制时画笔的样式(Style)决定了是否绘制矩形界线和填充矩形。
相当于 `canvas.drawRect(r.left, r.top, r.right, r.bottom, paint)`。
## canvas.drawRect(left, top, right, bottom, paint)
* `left` {number} 矩形左边界x坐标
* `top` {number} 矩形上边界y坐标
* `right` {number} 矩形右边界x坐标
* `bottom` {number} 矩形下边界y坐标
* `paint` {Paint} 画笔
在可绘制区域绘制由矩形边界(left, top) - (right, bottom)指定的矩形。
绘制时画笔的样式(Style)决定了是否绘制矩形界线和填充矩形。
## canvas.drawOval(oval, paint)
* `oval` {RectF} 椭圆的外接矩形的边界
* `paint` {Paint} 画笔
在可绘制区域绘制由矩形边界oval指定的椭圆。
绘制时画笔的样式(Style)决定了是否绘制椭圆界线和填充椭圆。
相当于 `canvas.drawOval(oval.left, oval.top, oval.right, oval.bottom, paint)`。
## canvas.drawOval(left, top, right, bottom, paint)
* `left` {number} 椭圆外接矩形的左边界x坐标
* `top` {number} 椭圆外接矩形的上边界y坐标
* `right` {number} 椭圆外接矩形的右边界x坐标
* `bottom` {number} 椭圆外接矩形的下边界y坐标
* `paint` {Paint} 画笔
在可绘制区域绘制由矩形边界(left, top) - (right, bottom)指定的椭圆。
绘制时画笔的样式(Style)决定了是否绘制椭圆界线和填充椭圆。
## canvas.drawCircle(cx, cy, radius, paint)
* `cx` {number} 圆心的x坐标
* `cy` {number} 圆心的y坐标
* `radius` {number} 圆的半径
* `paint` {Paint} 画笔
在可绘制区域绘制由圆心(cx, cy)与半径radius指定的圆。如果半径小于等于0则不会绘制。
绘制时画笔的样式(Style)决定了是否绘制圆的界线和填充圆。
## canvas.drawArc(oval, startAngle, sweepAngle, useCenter, paint)
* `oval` {RectF} 圆弧对应的椭圆外接矩形的边界
* `startAngle` {number} 圆弧的起始角(以角度计算)
* `sweepAngle` {number} 圆弧所对的圆心角(顺时针方向,以角度计算)
* `useCenter` {boolean} 绘制路径时是否连接圆弧对应的椭圆圆心
* `paint` {Paint} 画笔
在可绘制区域绘制指定的圆弧。
这一圆弧是由矩形边界oval指定的椭圆的一部分,起始于startAngle指定的角度,并以顺时针方向扫过sweepAngle指定的角度。
如果起始角startAngle为负数或大于等于360,则绘制时起始角将对360取模。
圆弧是沿顺时针方向绘制的。起始角0°相当于从直角坐标系的0°开始绘制(即从3点钟方向沿顺时针方向绘制)。
如果圆心角sweepAngle大于等于360,则此函数将绘制一个完整的椭圆。
注意,这与path.arcTo方法不同,path.arcTo会将圆心角对360取模。
如果圆心角为负数则会将圆心角对360取模。
如果指定useCenter为true,在描边和填充时将会连接圆弧对应的椭圆圆心,最终将绘制出一个扇形。
相当于 `canvas.drawArc(oval.left, oval.top, oval.right, oval.bottom, startAngle, sweepAngle, useCenter, paint)`。
## canvas.drawArc(left, top, right, bottom, startAngle, sweepAngle, useCenter, paint)
* `left` {number} 椭圆外接矩形的左边界x坐标
* `top` {number} 椭圆外接矩形的上边界y坐标
* `right` {number} 椭圆外接矩形的右边界x坐标
* `bottom` {number} 椭圆外接矩形的下边界y坐标
* `startAngle` {number} 圆弧的起始角(以角度计算)
* `sweepAngle` {number} 圆弧所对的圆心角(顺时针方向,以角度计算)
* `useCenter` {boolean} 绘制路径时是否连接圆弧对应的椭圆圆心
* `paint` {Paint} 画笔
在可绘制区域绘制指定的圆弧。这一圆弧是由矩形边界(left, top) - (right, bottom)指定的椭圆的一部分,其余参数请参见 [canvas.drawArc(oval, startAngle, sweepAngle, useCenter, paint)](#canvasdrawarcoval-startangle-sweepangle-usecenter-paint)。
## canvas.drawRoundRect(rect, rx, ry, paint)
* `rect` {RectF} 矩形边界
* `rx` {number} 圆角在x轴上的半径
* `ry` {number} 圆角在y轴上的半径
* `paint` {Paint} 画笔
在可绘制区域绘制由矩形边界rect、圆角半径rx、ry指定的圆角矩形。
绘制时画笔的样式(Style)决定了是否绘制圆角矩形界线和填充圆角矩形。
相当于 `canvas.drawRoundRect(rect.left, rect.top, rect.right, rect.bottom, rx, ry, paint)`。
## canvas.drawRoundRect(left, top, right, bottom, rx, ry, paint)
* `left` {number} 圆角矩形左边界x坐标
* `top` {number} 圆角矩形上边界y坐标
* `right` {number} 圆角矩形右边界x坐标
* `bottom` {number} 圆角矩形下边界y坐标
* `rx` {number} 圆角在x轴上的半径
* `ry` {number} 圆角在y轴上的半径
* `paint` {Paint} 画笔
在可绘制区域绘制由矩形边界(left, top) - (right - bottom)、圆角半径rx、ry指定的圆角矩形。
绘制时画笔的样式(Style)决定了是否绘制圆角矩形界线和填充圆角矩形。
## canvas.drawPath(path, paint)
* `path` {Path} 路径
* `paint` {Paint} 画笔
在可绘制区域绘制指定的路径。
绘制时画笔的样式(Style)决定了是否描边路径和填充路径。
## canvas.drawBitmap(bitmap, left, top, paint)
* `bitmap` {Bitmap} 位图
* `left` {number} x坐标
* `top` {number} y坐标
* `paint` {Paint} 画笔
在可绘制区域绘制指定的位图,使它的左上角位于(left, top)指定的坐标,并应用画布的变换矩阵。
> **注意!** 如果画笔指定了遮罩过滤器并且该过滤器范围比位图大(超出位图的长/宽)(如 BlurMaskFilter),位图将会像被应用了CLAMP铺展模式的着色器一样绘制。因此超出位图原始范围的颜色将会重复使用边缘的颜色。
如果位图bitmap与画布canvas的密度不同,在绘制时位图将会被缩放至与画布相同密度再绘制。
## canvas.drawPicture(picture)
* `picture` {Picture} 图章
保存画布的变换矩阵与可绘制区域范围,在可绘制区域绘制指定的图章,随后恢复画布的变换矩阵与可绘制区域范围。
该过程与 `picture.draw(canvas)` 不同,因为后者不会进行保存与恢复的操作。
> **注意!**绘制图章将会强制图章退出录制模式(即调用 `picture.endRecording()`)以准备之后的绘制。
## canvas.drawText(text, x, y, paint)
* `text` {string} 文字
* `x` {number} x坐标
* `y` {number} y坐标
* `paint` {Paint} 画笔
在可绘制区域绘制指定的文字,使文字的原点位于(x, y)。文字的起始位置取决于paint的对齐选项。
文字的样式取决于paint的相关设置。
## canvas.drawTextOnPath(text, path, hOffset, vOffset, paint)
* `text` {string} 文字
* `path` {Path} 路径
* `hOffset` {number} 在与路径平行方向的偏移,取正为沿路径方向平移
* `vOffset` {number} 在与路径垂直方向的偏移,取正为向文字的下方平移
* `paint` {Paint} 画笔
在可绘制区域沿着指定的路径绘制指定的文字。文字的起始位置取决于paint的对齐选项。
文字的样式取决于paint的相关设置。
## canvas.translate(dx, dy)
* `dx` {number} 向x轴正方向平移的距离,负数表示反方向平移
* `dy` {number} 向y轴正方向平移的距离,负数表示反方向平移
将当前的变换矩阵右乘指定的平移变换矩阵。相当于将坐标系平移指定距离。
## canvas.scale(sx, sy[, px, py])
* `sx` {number} 在x轴上缩放的倍数,负数表示沿x轴翻转
* `sy` {number} 在y轴上缩放的倍数,负数表示沿y轴翻转
* `px` {number} 缩放中心的x坐标,默认为0
* `py` {number} 缩放中心的y坐标,默认为0
将当前的变换矩阵右乘指定的缩放变换矩阵。相当于以(px, py)为中心将坐标系缩放指定的倍数。
倍数大于 1 表示放大,小于 1 表示缩小。
## canvas.rotate(degrees[, px, py])
* `degrees` {number} 旋转的角度(以角度计算)
* `px` {number} 旋转中心的x坐标,默认为0
* `py` {number} 旋转中心的y坐标,默认为0
将当前的变换矩阵右乘指定的旋转变换矩阵。相当于以(px, py)为中心将坐标系旋转指定的角度。
## canvas.skew(sx, sy)
* `sx` {number} x轴方向的切变系数
* `sy` {number} y轴方向的切变系数
将当前的变换矩阵右乘指定的切变变换矩阵。
# 画笔
# 变换矩阵
# 路径
# Porter-Duff模式与混合模式
# 图章
# 着色器
# 遮罩过滤器
# 颜色过滤器
# 路径特效
# 区域
# 字体
# 位图与图像解码器
# .9图
# 示例
## 函数图像高级版
```js
"ui";
//ui布局为一块画布和一些函数调整控件
ui.layout(
<vertical>
<linear>
<input id="fx" textSize="16sp" text="x*x+3*x-4" layout_weight="1"/>
<button id="ok" w="50dp"/>
</linear>
<linear>
<button id="left" text="←" layout_weight="1"/>
<button id="right" text="→" layout_weight="1"/>
<button id="up" text="↓" layout_weight="1"/>
<button id="down" text="↑" layout_weight="1"/>
<button id="zoom_in" text="+" layout_weight="1"/>
<button id="zoom_out" text="-" layout_weight="1"/>
</linear>
<canvas id="board" w="*" h="*"/>
</vertical>
);
//函数表达式
var f = "x*x+3*x-4";
//绘制区间
var minX = -5;
var maxX = 5;
var minY;
var h = 1;
var w = 1;
//画笔
var paint = new Paint();
paint.setStrokeWidth(2);
ui.board.on("draw", function(canvas){
w = canvas.getWidth();
h = canvas.getHeight();
if(minY == undefined){
minY = -(maxX - minX) * h / w / 2;
}
//计算y轴区间上限
var maxY = minY + (maxX - minX) * h / w;
//设置画笔颜色为黑色
paint.setColor(colors.parseColor("#000000"));
//绘制两个坐标轴
var x0 = parseInt(- minX / (maxX - minX) * w);
canvas.drawLine(x0, 0, x0, h, paint);
var y0 = parseInt(h + minY / (maxY - minY) * h);
canvas.drawLine(0, y0, w, y0, paint);
//设置画笔颜色为红色
paint.setColor(colors.parseColor("#ff0000"));
//绘制图像
for(var i = 0; i < w; i++){
var x = minX + i / w * (maxX - minX);
var y = eval(f);
var j = h - (y - minY) / (maxY - minY) * h;
canvas.drawPoint(i, j, paint);
}
});
ui.ok.click(()=>{
f = String(ui.fx.text());
});
ui.left.click(()=>{
var d = maxX - minX;
maxX -= d / 10;
minX -= d / 10;
});
ui.right.click(()=>{
var d = maxX - minX;
maxX += d / 10;
minX += d / 10;
});
ui.up.click(()=>{
var d = maxX - minX;
minY += d / 8;
});
ui.down.click(()=>{
var d = maxX - minX;
minY -= d / 8;
});
ui.zoom_in.click(()=>{
var d = maxX - minX;
var a = (maxX + minX) / 2;
maxX = a + d;
minX = a - d;
minY *= (maxX - minY) / d * h / w;
});
ui.zoom_out.click(()=>{
var d = maxX - minX;
maxX -= d / 2;
minX += d / 2;
});
```
## 函数图像简单版
```js
"ui";
//ui布局为一块画布
ui.layout(
<frame>
<canvas id="board" w="*" h="*"/>
</frame>
);
//要绘制的函数,这里是一个一元二次函数
var f = function(x){
return x * x + 3 * x - 4;
}
//绘制区间
var minX = -5;
var maxX = 5;
var minY = -10;
//画笔
var paint = new Paint();
ui.board.on("draw", function(canvas){
var w = canvas.getWidth();
var h = canvas.getHeight();
//计算y轴区间上限
var maxY = minY + (maxX - minX) * h / w;
//设置画笔颜色为黑色
paint.setColor(colors.parseColor("#000000"));
//绘制两个坐标轴
canvas.drawLine(w / 2, 0, w / 2, h, paint);
canvas.drawLine(0, h / 2, w, h / 2, paint);
//设置画笔颜色为红色
paint.setColor(colors.parseColor("#ff0000"));
//绘制图像
for(var i = 0; i < w; i++){
var x = minX + i / w * (maxX - minX);
var y = f(x);
var j = h - (y - minY) / (maxY - minY) * h;
canvas.drawPoint(i, j, paint);
}
});
```
## 贪吃蛇
```js
"ui";
ui.layout(
<vertical>
<canvas id="board" layout_weight="1"/>
<relative h="120">
<button id="up" text="↑" w="60" h="60" layout_alignParentTop="true" layout_centerHorizontal="true"/>
<button id="left" text="←" w="60" h="60" layout_alignParentBottom="true" layout_toLeftOf="@id/down"/>
<button id="down" text="↓" w="60" h="60" layout_alignParentBottom="true" layout_centerHorizontal="true"/>
<button id="right" text="→" w="60" h="60" layout_alignParentBottom="true" layout_toRightOf="@id/down"/>
</relative>
</vertical>
);
//蛇的颜色
const SNAKE_COLOR = colors.parseColor("#4caf50");
//背景色
const BG_COLOR = colors.parseColor("#ffffff");
//苹果颜色
const APPLE_COLOR = colors.parseColor("#f44336");
//墙的颜色
const WALL_COLOR = colors.parseColor("#607d8b");
//文本颜色
const TEXT_COLOR = colors.parseColor("#03a9f4");
//蛇自动移动的时间间隔,调小可以增加难度
const MOVE_INTERVAL = 500;
//方块宽度
const BLOCK_WIDTH = 40;
//游戏区域宽高
const GAME_BOARD_HEIGHT = 20;
const GAME_BOARD_WIDTH = 15;
//蛇的四个移动方向
const DIRECTION_LEFT = {x: -1, y: 0};
const DIRECTION_RIGHT = {x: 1, y: 0};
const DIRECTION_UP = {x: 0, y: -1};
const DIRECTION_DOWN = {x: 0, y: 1};
//蛇,是一个蛇身的坐标的数组
var snake = [{x: 4, y: 2}, {x: 3, y: 2}, {x: 2, y: 2}];
//苹果的坐标
var apple = generateApple();
//当前蛇的移动方向
var direction = DIRECTION_RIGHT;
//标记游戏是否结束
var isGameOver = false;
//分数
var score = 0;
var paint = new Paint();
ui.board.on("draw", function(canvas){
//绘制背景色
canvas.drawColor(BG_COLOR);
//绘制分数
paint.setColor(TEXT_COLOR);
paint.setTextSize(50);
canvas.drawText("分数: " + score, 30, 70, paint);
//如果游戏结束则绘制游戏结束字样
if(isGameOver){
canvas.drawText("游戏结束!", canvas.getWidth() - 280, 70, paint);
}
//计算坐标偏移,是的游戏区域绘制在画面的水平居中位置
var offset = {
x: (canvas.getWidth() - (GAME_BOARD_WIDTH + 2) * BLOCK_WIDTH) / 2,
y: 100
};
//偏移坐标
canvas.translate(offset.x, offset.y);
//绘制围墙
paint.setColor(WALL_COLOR);
for(var i = 0; i <= GAME_BOARD_WIDTH + 1; i++){
//上围墙
drawBlock(canvas, paint, i, 0);
//下围墙
drawBlock(canvas, paint, i, GAME_BOARD_HEIGHT + 1);
}
for(var i = 0; i <= GAME_BOARD_HEIGHT + 1; i++){
//左围墙
drawBlock(canvas, paint, 0, i);
//右围墙
drawBlock(canvas, paint, GAME_BOARD_WIDTH + 1, i);
}
//绘制蛇身
paint.setColor(SNAKE_COLOR);
for(var i = 0; i < snake.length; i++){
drawBlock(canvas, paint, snake[i].x, snake[i].y);
}
//绘制苹果
paint.setColor(APPLE_COLOR);
drawBlock(canvas, paint, apple.x, apple.y);
});
//启动游戏线程
var gameThread = threads.start(game);
//按键点击时改变蛇的移动方向
ui.left.on("click", ()=> direction = DIRECTION_LEFT);
ui.right.on("click", ()=> direction = DIRECTION_RIGHT);
ui.up.on("click", ()=> direction = DIRECTION_UP);
ui.down.on("click", ()=> direction = DIRECTION_DOWN);
function game(){
//每隔一段时间让蛇自动前进
setInterval(()=>{
move(direction.x, direction.y);
}, MOVE_INTERVAL);
}
function move(dx, dy){
log("move: %d, %d", dx, dy);
direction.x = dx;
direction.y = dy;
//蛇前进时把一个新的方块添加到蛇头前面
var head = snake[0];
snake.splice(0, 0, {
x: head.x + dx,
y: head.y + dy
});
//如果蛇头吃到了苹果
if(snakeEatsApple()){
//添加分数和重新生成苹果
score += 5;
apple = generateApple();
}else{
//没有吃到苹果的情况下把蛇尾去掉保持蛇身长度不变
snake.pop();
}
//碰撞检测
collisionTest();
}
function snakeEatsApple(){
return snake[0].x == apple.x && snake[0].y == apple.y;
}
function generateApple(){
//循环生成苹果直至苹果不会生成在蛇身上
var x, y;
do{
x = random(1, GAME_BOARD_WIDTH);
y = random(1, GAME_BOARD_HEIGHT);
}while(!isAppleValid(x, y));
return {x: x, y: y};
}
function isAppleValid(x, y){
for (var i = 0; i < snake.length; i++) {
if (snake[i].x == x && snake[i].y == y) {
return false;
}
}
return true;
}
function collisionTest(){
//检测蛇有没有撞到墙上
var head = snake[0];
if(head.x < 1 || head.x > GAME_BOARD_WIDTH
|| head.y < 1 || head.y > GAME_BOARD_HEIGHT){
gameOver();
return;
}
//检测蛇有没有撞到自己
for(var i = 1; i < snake.length; i++){
if(snake[i].x == head && snake[i].y == head){
gameOver();
return;
}
}
}
function gameOver(){
gameThread.interrupt();
isGameOver = true;
}
function drawBlock(canvas, paint, x, y){
x *= BLOCK_WIDTH;
y *= BLOCK_WIDTH;
canvas.drawRect(x, y, x + BLOCK_WIDTH, y + BLOCK_WIDTH, paint);
}
```