前言
本例是基于cocos官方推荐raphael实现的。其中画板与看板的同步处理我采用的是在画板画完一条线之后看板再同步数据来显示画线过程。
一、实现效果如下:
二、画板核心代码
1、初始化画板参数
onLoad: function () {
// 初始化参数
this.lineWidth = 5;
this.strokeColor = cc.color(0, 0, 0);
this.isClearMode = false;
this.group = this.addComponent('R.group');
// 绑定触摸通知事件通知
cc.eventManager.addListener({
event: cc.EventListener.TOUCH_ONE_BY_ONE,
onTouchBegan: this.onTouchBegan.bind(this),
onTouchMoved: this.onTouchMoved.bind(this),
onTouchEnded: this.onTouchEnded.bind(this),
}, this.node);
},
需要注意的事项:
① 本例使用的是 raphael
库的 group
的概念来处理的画板中的线
2、触摸开始事件回调处理
onTouchBegan: function (touch, event) {
// 初始一条线的数据
this.dataDict = {};
this.dataDict.dataEvent = 'draw';
// 获取开始时间点
this.dataDict.startTime = new Date().getTime();
// 获取触摸点数据
var touchLoc = touch.getLocation();
touchLoc = this.node.parent.convertToNodeSpaceAR(touchLoc);
// 从group获取一条Path实例
var path = this.group.addPath();
path.fillColor = 'none';
// 判断是否是橡皮擦状态
if (this.isClearMode) {
path.lineWidth = 15;
path.strokeColor = cc.color(255, 255, 255);
} else {
path.lineWidth = this.lineWidth;
path.strokeColor = this.strokeColor;
}
this.dataDict.strokeColor = path.strokeColor.toHEX("#rrggbb");
this.dataDict.lineWidth = path.lineWidth;
// 初始化点数组,并赋值开始位置的点
this.points = [touchLoc];
return true;
},
需要注意的事项:
① 橡皮擦处理的方式是用与画板背景色相同的颜色画线,并加粗线的宽度来实现的。
② this.dataDict
记录的数据是为了通过 webSocket
传递到看板,来使看板同步画线。
3、触摸移动事件处理
onTouchMoved: function (touch, event) {
// 获取触摸点数据
var touchLoc = touch.getLocation();
touchLoc = this.node.parent.convertToNodeSpaceAR(touchLoc);
// 添加到点数组内
this.points.push(touchLoc);
// 获取当前画的path实例,并更新内部展现点数据
var path = this.group.children[this.group.children.length - 1];
path.points(this.points);
},
需要注意的事项:
① 每次获取的都是group中最新的一条path去画。
4、触摸事件结束处理
onTouchEnded: function(touch, event) {
// 获取触摸点数据
var path = this.group.children[this.group.children.length - 1];
path.points(this.points);
// 获取结束时间点
this.dataDict.endTime = new Date().getTime();
// 将点数组转化为相对于node宽高的映射位置
this.pointDicts = [];
for (var i = 0; i < this.points.length; i++) {
let point = this.points[i];
var pointDict = {};
pointDict.x = point.x / this.node.width;
pointDict.y = point.y / this.node.height;
this.pointDicts.push(pointDict);
}
this.dataDict.points = this.pointDicts;
let sendData = this.dataDict;
// 本地测试同步数据
// this.lookDraw.startDraw(sendData);
// 网络同步数据
if (window.room_user) {
var drawAction = gameAction.getDrawDataAction(window.room_user, sendData)
happySocket.sendData(drawAction)
}
},
需要注意的事项:
① 首先为了适应不同的屏幕,需要获取到实际画点的相对于宽高的映射点。
② 本地测试与网络同步数据可根据自己项目做相应的处理。
③ 也可在本地搭建webSocket去模拟实际服务器传输,可以看这里。
4、其他处理(比如清屏、橡皮擦)
// 清屏
clearAll: function() {
this.group.ctx.clear();
this.group.children = [];
this.isClearMode = false;
// 初始化清屏的数据
this.dataDict = {};
this.dataDict.dataEvent = 'clear';
let sendData = this.dataDict;
// 本地测试同步数据
// this.lookDraw.startDraw(sendData);
// 网络同步数据
if (window.room_user) {
var drawAction = gameAction.getDrawDataAction(window.room_user, sendData)
happySocket.sendData(drawAction)
}
},
// 橡皮擦
rubber: function() {
this.isClearMode = true;
},
需要注意的事项:
① 清屏的action会放入看板事件队列中,在画完最后一条线后执行。具体看看板代码。
三、看板核心代码
1、初始化参数(要与画板保持一致)
onLoad: function () {
// 初始化数据
this.lineWidth = 5;
this.strokeColor = cc.color(0, 0, 0);
this.isClearMode = false;
this.group = this.addComponent('R.group');
this.time = 3.0;
this.duration = 1.0;
this.pathArray = [];
// 监听画板数据
happyDispatch.addEventListener('happyAction', this.receiveData, this);
},
需要注意的事项:
① this.pathArray
为画线的事件队列。
2、开始画线
// 开始画线
startDraw: function (dataDict) {
// 判断是否是清屏
if (dataDict.dataEvent === 'clear') {
this.pathArray.push(dataDict);
return;
}
// 初始化线
let path = this.group.addPath();
path.strokeColor = cc.color(0, 0, 0).fromHEX(dataDict.strokeColor);
path.lineWidth = dataDict.lineWidth;
path.fillColor = 'none';
// 映射还原点
var pathPoints = dataDict.points;
var userPoints = [];
for (var i = 0; i < pathPoints.length; i++) {
let pointDict = pathPoints[i];
var point = cc.p(pointDict.x * this.node.width, pointDict.y * this.node.height);
userPoints.push(point);
}
// 画线并隐藏
path.points(userPoints.reverse())
var pathLength = path.getTotalLength();
path.dashOffset = pathLength;
path.dashArray = [pathLength];
// 设置path字典
var pathDict = {};
pathDict.path = path;
pathDict.duration = (dataDict.endTime - dataDict.startTime) / 1000.0;
// 将path字典存入数组
this.pathArray.push(pathDict);
},
需要注意的事项:
① 清屏数据结构与画线不同所以特殊处理。
② 线的动画显示,实质上是已经画了,不过被隐藏了,只不过是在一定时间里按比例显示出来。
3、处理画线队列(在update
中)
update: function (dt) {
// 时间递增
this.time += dt;
// 设置显示比例
let percent = this.time / this.duration;
// 显示比例超过1 更新到队列中的下一条线
if (percent > 1) {
// 假如队列有path,排除没画线时出错
if (this.pathArray.length > 0) {
// 假如是清屏命令 直接清屏退出此次update
if (this.pathArray[0].dataEvent === 'clear') {
this.clearAll();
this.pathArray.shift();
return;
}
// 比较是否是当前path,是的话移除
if (this.path === this.pathArray[0].path) {
this.pathArray.shift();
}
}
// 在移除之后(或者第一次) 判断队列还有没有path,有的话继续画
if (this.pathArray.length > 0) {
// 假如是清屏命令 直接清屏退出此次update
if (this.pathArray[0].dataEvent === 'clear') {
this.clearAll();
this.pathArray.shift();
return;
}
// 开始新一条线的显示(初始化)
this.path = this.pathArray[0].path;
this.pathLength = this.path.getTotalLength();
this.time = 0;
this.duration = this.pathArray[0].duration;
percent = 0;
}
return;
}
// 根据时间刷新画笔的显示
this.path.dashOffset = this.pathLength * (1 - percent);
this.path._dirty = true;
},
});
四、相关资源
需要注意的事项:
① 按照raphael_demo中的安装教程安装时,记得后两步即:git submodule update --init
及 npm install
需要在终端cd到项目所在的文件夹执行,不是全局的。
.