UNPKG

ycc.js

Version:

Mini and powerful canvas engine for creating App or Game.

2,591 lines (2,176 loc) 223 kB
/** * @file Ycc.class.js * @author xiaohei * @date 2017/9/30 * @description Ycc.class文件 * */ /** * 应用启动入口类,每个实例都与一个canvas绑定。 * 该canvas元素会被添加至HTML结构中,作为应用的显示舞台。 * @param config {Object} 整个ycc的配置项 * @param config.debugDrawContainer {Boolean} 是否显示所有UI的容纳区域 * 若开启,所有UI都会创建一个属于自己的离屏canvas,大小与舞台一致 * @constructor */ var Ycc = function Ycc(config){ /** * 绘图环境 * @type {CanvasRenderingContext2D} */ this.ctx = null; /** * 与ycc绑定的canvas元素 * @type {null} */ this.canvasDom = null; /** * Layer对象数组。包含所有的图层 * @type {Array} */ this.layerList = []; /** * 实例的快照管理模块 * @type {Ycc.PhotoManager} */ this.photoManager = null; /** * ycc的图层管理器 * @type {null} */ this.layerManager = null; /** * 系统心跳管理器 */ this.ticker = null; /** * 调试模块 * @type {null} */ this.debugger = null; /** * 资源加载器 * @type {Ycc.Loader} */ this.loader = new Ycc.Loader(); /** * 异步请求的封装 * @type {Ycc.Ajax} */ this.ajax = new Ycc.Ajax(); /** * 基础绘图UI。这些绘图操作会直接作用于舞台。 * @type {Ycc.UI} */ this.baseUI = null; /** * 系统的手势模块 * @type {null} */ this.gesture = null; /** * 整个ycc的配置项 * @type {{debugDrawContainer:boolean}} */ this.config = Ycc.utils.extend({ debugDrawContainer:false },config||{}); /** * 是否移动端 * @type {boolean} */ this.isMobile = Ycc.utils.isMobile(); this.stageW = 0; this.stageH = 0; /** * dpi * @type {number} */ this.dpi = this.getSystemInfo().devicePixelRatio; }; /** * 获取舞台的宽 */ Ycc.prototype.getStageWidth = function () { return this.canvasDom.width; }; /** * 获取舞台的高 */ Ycc.prototype.getStageHeight = function () { return this.canvasDom.height; }; /** * 绑定canvas元素,一个canvas绑定一个ycc实例 * @param canvas * @return {Ycc} */ Ycc.prototype.bindCanvas = function (canvas) { this.canvasDom = canvas; this.ctx = canvas.getContext('2d'); this.layerList = []; this.photoManager = new Ycc.PhotoManager(this); this.layerManager = new Ycc.LayerManager(this); this.ticker = new Ycc.Ticker(this); this.debugger = new Ycc.Debugger(this); this.baseUI = new Ycc.UI(this); this.init(); return this; }; /** * 类初始化 */ Ycc.prototype.init = function () { //初始化手势库 this._initStageGestureEvent(); // 心跳模块初始化 ticker默认启动,10帧每刷新 this.ticker.start(6); }; /** * * 需求实现:初始化舞台的手势事件监听器 * 1、事件传递给所有图层 * 2、事件传递给最上层UI * 3、pc端和移动端统一,pc端视为一个触摸点,而移动端可以存在多个触摸点 * 4、dragging、dragend事件,的最上层UI为dragstart时所指的UI * 5、所有鼠标事件均由舞台转发,转发的坐标均为绝对坐标。 * `layer`和`ui`可以调用各自的`transformToLocal`方法,将绝对坐标转换为自己的相对坐标。 * * @private */ Ycc.prototype._initStageGestureEvent = function () { var self = this; // 鼠标/触摸点开始拖拽时,所指向的UI对象,只用于单个触摸点的情况 //var dragstartUI = null; // 鼠标/触摸点开始拖拽时,所指向的UI对象map,用于多个触摸点的情况 var dragstartUIMap = {}; // 拖拽时的上一个坐标,用于判断是否下发dragging事件 var draggingLastXY = ''; var gesture = new Ycc.Gesture({target:this.ctx.canvas}); this.gesture = gesture; gesture.addListener('tap',gestureListener); gesture.addListener('longtap',gestureListener); gesture.addListener('doubletap',gestureListener); gesture.addListener('swipe',gestureListener); gesture.addListener('swipeleft',gestureListener); gesture.addListener('swiperight',gestureListener); gesture.addListener('swipeup',gestureListener); gesture.addListener('swipedown',gestureListener); gesture.addListener('dragstart',dragstartListener); gesture.addListener('dragging',draggingListener); gesture.addListener('dragend',dragendListener); // PC端专属事件 gesture.addListener('mousemove',gestureListener); function dragstartListener(e) { // 在canvas中的绝对位置 var x = parseInt(e.clientX - self.ctx.canvas.getBoundingClientRect().left), y = parseInt(e.clientY - self.ctx.canvas.getBoundingClientRect().top); // 重置位置 draggingLastXY = ''; var dragstartUI = self.getUIFromPointer(new Ycc.Math.Dot(x,y)); if(dragstartUI){ dragstartUIMap[e.identifier]=dragstartUI; dragstartUI.triggerUIEventBubbleUp(e.type,x,y); } // dragstartUI&&dragstartUI.belongTo.enableEventManager&&triggerUIEvent(e.type,x,y,dragstartUI); triggerLayerEvent(e.type,x,y); } function draggingListener(e) { // 在canvas中的绝对位置 var x = parseInt(e.clientX - self.ctx.canvas.getBoundingClientRect().left), y = parseInt(e.clientY - self.ctx.canvas.getBoundingClientRect().top); // 位置在一个像素内,不下发dragging事件 if(draggingLastXY===x+'_'+y) return; draggingLastXY = x+'_'+y; var dragstartUI = dragstartUIMap[e.identifier]; if(dragstartUI){ dragstartUIMap[e.identifier]=dragstartUI; dragstartUI.triggerUIEventBubbleUp(e.type,x,y); } triggerLayerEvent(e.type,x,y); } function dragendListener(e) { // 在canvas中的绝对位置 var x = parseInt(e.clientX - self.ctx.canvas.getBoundingClientRect().left), y = parseInt(e.clientY - self.ctx.canvas.getBoundingClientRect().top); var dragstartUI = dragstartUIMap[e.identifier]; if(dragstartUI){ dragstartUIMap[e.identifier]=dragstartUI; dragstartUI.triggerUIEventBubbleUp(e.type,x,y); dragstartUI = null; dragstartUIMap[e.identifier]=null; } triggerLayerEvent(e.type,x,y); } // 通用监听 function gestureListener(e) { // console.log('通用监听',e); // 在canvas中的绝对位置 var x = parseInt(e.clientX - self.ctx.canvas.getBoundingClientRect().left), y = parseInt(e.clientY - self.ctx.canvas.getBoundingClientRect().top); var ui = self.getUIFromPointer(new Ycc.Math.Dot(x,y)); // triggerLayerEvent(e.type,x,y); // ui&&ui.belongTo.enableEventManager&&triggerUIEvent(e.type,x,y,ui); if(ui){ ui.triggerUIEventBubbleUp(e.type,x,y,e); } triggerLayerEvent(e.type,x,y); } function triggerLayerEvent(type,x,y){ for(var i=self.layerList.length-1;i>=0;i--){ var layer = self.layerList[i]; if(!layer.enableEventManager) continue; layer.enableEventManager&&layer.triggerListener(type,new Ycc.Event({ type:type, x:x, y:y })); } } function triggerUIEvent(type,x,y,ui){ ui.triggerListener(type,new Ycc.Event({ x:x, y:y, type:type, target:ui })); } /** * 冒泡触发UI的事件 * @param type * @param x * @param y * @param ui * @return {null} */ // function triggerUIEventBubbleUp(type,x,y,ui) { // if(ui && ui.belongTo && ui.belongTo.enableEventManager){ // // 触发ui的事件 // ui.triggerListener(type,new Ycc.Event({x:x,y:y,type:type,target:ui})); // // // 如果ui阻止了事件冒泡,则不触发其父级的事件 // if(ui.stopEventBubbleUp) return; // // // 触发父级ui的事件 // ui.getParentList().reverse().forEach(function (fa) { // fa.triggerListener(type,new Ycc.Event({x:x,y:y,type:type,target:fa})); // }); // } // } }; /** * 清除 */ Ycc.prototype.clearStage = function () { this.ctx.clearRect(0,0,this.getStageWidth(),this.getStageHeight()); }; /** * 根据id查找图层 * @param id 图层id * @return {Ycc.Layer} */ Ycc.prototype.findLayerById = function (id) { for(var i =0;i<this.layerList.length;i++){ var layer = this.layerList[i]; if(layer.id===id) return layer; } return null; }; /** * 根据id查找UI * @param id UI的id * @return {Ycc.UI} */ Ycc.prototype.findUiById = function (id) { for(var i =0;i<this.layerList.length;i++){ var layer = this.layerList[i]; for(var j=0;j<layer.uiList.length;j++){ var ui = layer.uiList[j]; if(ui.id===id) return ui; } } return null; }; /** * 获取舞台中某个点所对应的最上层UI。 * @param dot {Ycc.Math.Dot} 点坐标,为舞台的绝对坐标 * @param uiIsShow {Boolean} 是否只获取显示在舞台上的UI,默认为true * @return {UI} */ Ycc.prototype.getUIFromPointer = function (dot,uiIsShow) { var self = this; uiIsShow = Ycc.utils.isBoolean(uiIsShow)?uiIsShow:true; // 从最末一个图层开始寻找 for(var j=self.layerList.length-1;j>=0;j--){ var layer = self.layerList[j]; // 幽灵图层,直接跳过 if(layer.ghost) continue; if(uiIsShow&&!layer.show) continue; var ui = layer.getUIFromPointer(dot,uiIsShow); if(ui) return ui; } return null; }; /** * 获取舞台中某个点所对应的最上层UI,不遍历不可见图层 * 默认取可见且不是幽灵的UI * @param dot {Ycc.Math.Dot} 点坐标,为舞台的绝对坐标 * @param options {object} 点坐标,为舞台的绝对坐标 * @param options.uiIsShow {boolean} UI是否可见 * @param options.uiIsGhost {boolean} UI是否未幽灵 * @return {Ycc.UI[]} 返回找到的UI列表,图层越靠后图层内的UI越靠前 */ Ycc.prototype.getUIListFromPointer = function (dot,options) { var self = this; options = options || { uiIsShow:true, uiIsGhost:false }; var uiList = []; // 从最末一个图层开始寻找 for(var j=self.layerList.length-1;j>=0;j--){ var layer = self.layerList[j]; // if(!layer.show) continue; var list = layer.getUIListFromPointer(dot); uiList = uiList.concat(list); } uiList = uiList.filter(function(item){return (item.show===options.uiIsShow)&&(item.ghost===options.uiIsGhost);}); return uiList; }; /** * 创建canvas,只针对H5端。微信小游戏的canvas为全局变量,直接使用即可 * @example * var ycc = new Ycc(); * var stage = canvas || ycc.createCanvas(); * ycc.bindCanvas(stage); * * @param options * @param options.width * @param options.height * @param options.dpiAdaptation 是否根据dpi适配canvas大小 * @return {*} 已创建的canvas元素 */ Ycc.prototype.createCanvas = function (options) { options = options||{}; var option = Ycc.utils.mergeObject({ width:window.innerWidth, height:window.innerHeight, dpiAdaptation:false },options); var canvas = document.createElement("canvas"); var dpi = this.getSystemInfo().devicePixelRatio; if(option.dpiAdaptation){ canvas.width = option.width*dpi; canvas.height = option.height*dpi; canvas.style.width=option.width+'px'; }else{ canvas.width = option.width; canvas.height = option.height; } // 去除5px inline-block偏差 canvas.style.display='block'; return canvas; }; /** * 获取系统信息 * @return {{model: string, pixelRatio: number, windowWidth: number, windowHeight: number, system: string, language: string, version: string, screenWidth: number, screenHeight: number, SDKVersion: string, brand: string, fontSizeSetting: number, benchmarkLevel: number, batteryLevel: number, statusBarHeight: number, safeArea: {right: number, bottom: number, left: number, top: number, width: number, height: number}, platform: string, devicePixelRatio: number}} */ Ycc.prototype.getSystemInfo = function () { return { "model":"iPhone 5", "pixelRatio":window.devicePixelRatio||1, "windowWidth":window.innerWidth, "windowHeight":window.innerHeight, "screenWidth":320, "screenHeight":568, "devicePixelRatio":window.devicePixelRatio||1 }; }; /** * 创建一个离屏的绘图空间,默认大小与舞台等同 * @param options * @param options.width * @param options.height */ Ycc.prototype.createCacheCtx = function (options) { options = options || { width:this.getStageWidth(), height:this.getStageHeight() }; var ctxCache = this.createCanvas({ width:options.width, height:options.height }).getContext('2d'); // debug // console.log('create cache ctx',options); document.body.appendChild(ctxCache.canvas); return ctxCache; };;/** * @file Ycc.utils.js * @author xiaohei * @desc * 整个程序公用的基础工具模块 * * @requires Ycc.init */ (function(Ycc){ Ycc.utils = {}; /** * 合并两个对象。只会保留targetObj中存在的字段。 * @param targetObj 目标对象 * @param obj2 待合入的对象 * @param isDeepClone 是否进行深拷贝 * @return {{}} targetObj对象 */ Ycc.utils.extend = function(targetObj, obj2,isDeepClone) { if(isDeepClone) obj2 = Ycc.utils.deepClone(obj2); for (var i in targetObj) { if(!targetObj.hasOwnProperty(i)) continue; if (obj2 && typeof obj2[i] !=="undefined") { targetObj[i] = obj2[i]; } } return targetObj; }; /** * 合并对象 * 将src所有的字段全部合并至target,若存在公有字段,则src会覆盖target对象的字段。 * 这个操作是浅拷贝。prototype内的属性不会被覆盖。 * @param target {object} 待覆盖的目标对象 * @param src {object} 源对象 * @return 返回target对象 */ Ycc.utils.mergeObject = function(target,src){ src = src || {}; for(var key in src){ if(!src.hasOwnProperty(key)) continue; if(typeof src[key]!=="undefined"){ target[key] = src[key]; } } return target; }; /** * 判断字符串 * @param str * @return {boolean} */ Ycc.utils.isString = function(str) { return typeof(str) === "string"; }; /** * 判断数字 * @param str * @return {boolean} */ Ycc.utils.isNum = function(str) { return typeof(str) === "number"; }; /** * 判断boolean * @param str * @return {boolean} */ Ycc.utils.isBoolean = function(str) { return typeof(str) === "boolean"; }; /** * 判断对象 * @param str * @return {boolean} */ Ycc.utils.isObj = function(str) { return typeof(str) === "object"; }; /** * 判断函数 * @param str * @return {boolean} */ Ycc.utils.isFn = function(str) { return typeof(str) === "function"; }; /** * 判断数组 * @param str * @return {boolean} */ Ycc.utils.isArray = function(str) { return Object.prototype.toString.call(str) === '[object Array]'; }; /** * 检测是否是移动端 * @return {boolean} */ Ycc.utils.isMobile = function () { var userAgentInfo = navigator.userAgent; var Agents = ["Android", "iPhone", "SymbianOS", "Windows Phone", "iPad", "iPod"]; var flag = false; for (var v = 0; v < Agents.length; v++) { if (userAgentInfo.indexOf(Agents[v]) > 0) { flag = true; break; } } return flag; }; /** * 深拷贝某个对象或者数组 * @param arrOrObj * @return {*} */ Ycc.utils.deepClone = function(arrOrObj){ return (Ycc.utils.isArray(arrOrObj))? deepCopy(arrOrObj):deepExtend(arrOrObj); function deepExtend(obj){ var tempObj = {}; for(var i in obj){ if(!obj.hasOwnProperty(i)) continue; tempObj[i] = obj[i]; if(Ycc.utils.isArray(obj[i])){ tempObj[i] = deepCopy(obj[i]); }else if(Ycc.utils.isObj(obj[i])){ tempObj[i] = deepExtend(obj[i]); }else{ tempObj[i] = obj[i]; } } return tempObj; } function deepCopy(arr){ var newArr = []; var v = null; for(var i=0;i<arr.length;i++){ v = arr[i]; if(Ycc.utils.isArray(v)) newArr.push(deepCopy(v)); else if(Ycc.utils.isObj(v)) newArr.push(deepExtend(v)); else{ newArr.push(v); } } return newArr; } }; /** * 迷你模板,替换__field__,其中的`field`为`renderObj`中的字段 * 返回替换后的模板文本 * @param tpl 模板字符串 * @param renderObj 渲染的对象 * @return {string} */ Ycc.utils.renderTpl=function (tpl,renderObj) { return tpl.replace(/__.+?__/g,function (txt) { console.log('匹配到的文本-->',txt); var key = txt.slice(2).slice(0,-2).trim(); if(renderObj[key]!==undefined) return renderObj[key]; else return txt; }); }; /** * 释放obj内存 只清空字段 * @param obj */ Ycc.utils.releaseObject = function (obj) { for(var key in obj){ if(!obj.hasOwnProperty(key)) continue; delete obj[key]; } }; /** * 释放arr内存 只清空元素 * @param arr */ Ycc.utils.releaseArray = function (arr) { arr.length = 0; }; /** * 检测是否微信环境 * @return {boolean} */ Ycc.utils.isWx = function () { return "undefined"!== typeof wx; } })(Ycc); ;/** * @file Ycc.Math.js * @author xiaohei * @date 2017/11/2 * @description Ycc.Math文件 */ (function (Ycc) { var extend = Ycc.utils.extend; /** * 数学表达式模块 * @constructor */ Ycc.Math = function () {}; /** * 点 * @param x {number} x坐标 * @param y {number} y坐标 * @constructor *//** * 点 * @param [dot] {object} * @param dot.x {number} x坐标 * @param dot.y {number} y坐标 * @constructor */ Ycc.Math.Dot = function (dot) { /** * x坐标 * @type {number} */ this.x = 0; /** * y坐标 * @type {number} */ this.y = 0; var len = arguments.length; if(len===1){ this.x = dot.x; this.y = dot.y; }else if(len===2){ this.x = arguments[0]; this.y = arguments[1]; } }; /** * 点是否在某个区域内 * @param rect {Ycc.Math.Rect} 区域 */ Ycc.Math.Dot.prototype.isInRect = function (rect) { return this.x>=rect.x&&this.x<=rect.x+rect.width && this.y>=rect.y && this.y<=rect.y+rect.height; }; /** * 判读两点位置是否相同 * @param dot * @return {boolean} */ Ycc.Math.Dot.prototype.isEqual = function (dot) { return this.x===dot.x && this.y===dot.y; }; /** * 点的加法/点的偏移量 * @param dot {Ycc.Math.Dot} 加的点 * @return {Ycc.Math.Dot} 返回一个新的点 */ Ycc.Math.Dot.prototype.plus = function (dot) { return new Ycc.Math.Dot(this.x+dot.x,this.y+dot.y); }; /** * 将当前点绕另外一个点旋转一定度数 * @param rotation 旋转角度 * @param anchorDot 锚点坐标 * @return 旋转后的点 */ Ycc.Math.Dot.prototype.rotate = function (rotation,anchorDot) { anchorDot=anchorDot||new Ycc.Math.Dot(0,0); var dotX = this.x,dotY=this.y,anchorX=anchorDot.x,anchorY=anchorDot.y; var dx = (dotX - anchorX)*Math.cos(rotation/180*Math.PI) - (dotY - anchorY)*Math.sin(rotation/180*Math.PI)+anchorX; var dy = (dotY - anchorY)*Math.cos(rotation/180*Math.PI) + (dotX - anchorX)*Math.sin(rotation/180*Math.PI)+anchorY; return new Ycc.Math.Dot(dx,dy); }; /** * 判断三点是否共线 * @param dot1 * @param dot2 * @param dot3 */ Ycc.Math.Dot.threeDotIsOnLine = function (dot1,dot2,dot3) { // 存在位置相同点肯定共线 if(dot1.isEqual(dot2) || dot1.isEqual(dot3) || dot2.isEqual(dot3)) return true; // 三个点x一样 if(dot1.x===dot2.x&&dot2.x===dot3.x) return true; var k1 = Math.abs(dot1.y-dot2.y)/Math.abs(dot1.x-dot2.x); var k2 = Math.abs(dot1.y-dot3.y)/Math.abs(dot1.x-dot3.x); return k1===k2; }; /** * 区域 * @param startDot {Dot} * @param width * @param height * @constructor *//** * 区域 * @param x * @param y * @param width * @param height * @constructor *//** * 区域 * @param rect * @param rect.x * @param rect.y * @param rect.width * @param rect.height * @constructor */ Ycc.Math.Rect = function (rect) { /** * 构造器的引用 * @type {function} */ this.yccClass = Ycc.Math.Rect; /** * 左上角x坐标 * @type {number} */ this.x = 0; /** * 左上角y坐标 * @type {number} */ this.y = 0; /** * 区域宽 * @type {number} */ this.width = 0; /** * 区域高 * @type {number} */ this.height = 0; var len = arguments.length; if(len===1){ this.x = rect.x; this.y = rect.y; this.width = rect.width; this.height = rect.height; }else if(len===3){ this.x = arguments[0].x; this.y = arguments[0].y; this.width = arguments[1]; this.height = arguments[2]; }else if(len === 4){ this.x = arguments[0]; this.y = arguments[1]; this.width = arguments[2]; this.height = arguments[3]; } this.toPositive(); }; /** * 将矩形的长和宽转换为正数 */ Ycc.Math.Rect.prototype.toPositive = function () { var x0 = this.x, y0 = this.y, x1 = this.x + this.width, y1 = this.y + this.height; this.x = x0<x1?x0:x1; this.y = y0<y1?y0:y1; this.width = Math.abs(this.width); this.height = Math.abs(this.height); }; /** * 获取区域的顶点列表 * @return {Ycc.Math.Dot[]} */ Ycc.Math.Rect.prototype.getVertices = function () { return [ new Ycc.Math.Dot(this.x,this.y), new Ycc.Math.Dot(this.x+this.width,this.y), new Ycc.Math.Dot(this.x+this.width,this.y+this.height), new Ycc.Math.Dot(this.x,this.y+this.height), new Ycc.Math.Dot(this.x,this.y) ]; }; /** * 根据顶点更新数值 * @param vertices * @return {*} */ Ycc.Math.Rect.prototype.updateByVertices = function (vertices) { if(!Ycc.utils.isArray(vertices)) return console.error('参数必须是数组!'); this.x = vertices[0].x; this.y = vertices[0].y; this.width = vertices[1].x-this.x; this.height = vertices[2].y-this.y; }; /** * 向量构造函数 * @constructor */ Ycc.Math.Vector = function () { this.x = 0; this.y = 0; this.z = 0; if(arguments.length===3 || arguments.length===2){ this.x=arguments[0]||0; this.y=arguments[1]||0; this.z=arguments[2]||0; } if(arguments.length===1){ if(!Ycc.utils.isObj(arguments[0])) console.error('constructor need a objec as param!'); this.x=arguments[0].x||0; this.y=arguments[0].y||0; this.z=arguments[0].z||0; } }; /** * 向量的点乘法 * @param v2 {Ycc.Math.Vector} 点乘向量 * @return {number} */ Ycc.Math.Vector.prototype.dot = function (v2) { return this.x*v2.x+this.y*v2.y+this.z*v2.z; }; /** * 向量的叉乘法 * @param v2 {Ycc.Math.Vector} 叉乘向量 * @return {number} */ Ycc.Math.Vector.prototype.cross = function (v2) { var res = new Ycc.Math.Vector(); res.x = this.y*v2.z-v2.y*this.z; res.y = v2.x*this.z-this.x*v2.z; res.z = this.x*v2.y-v2.x*this.y; return res; }; /** * 获取向量的模长 * @return {number} */ Ycc.Math.Vector.prototype.getLength = function () { return Math.sqrt(this.x*this.x+this.y*this.y+this.z*this.z,2); }; /** * 矩阵的构造方法。 * @param data {array} 矩阵所有行拼接的数组 * @param m {number} 行数 * @param n {number} 列数 * @constructor */ Ycc.Math.Matrix = function (data,m,n) { this.data = data; this.m = m; this.n = n; }; /** * 矩阵点乘法 * @param M {Ycc.Math.Matrix} 另一个矩阵 */ Ycc.Math.Matrix.prototype.dot = function (M) { if(M.m!==this.n || M.n!==this.m) return console.error('两个矩阵的行数和列数不对应,不能相乘!'); var N = new Ycc.Math.Matrix([],this.m,this.m); // 循环行 for(var i=1;i<=this.m;i++){ // 循环矩阵赋值 for(var k=1;k<=this.m;k++){ var temp =0; // 循环列 for(var j=1;j<=this.n;j++){ temp += this.get(i,j)*M.get(j,k); } N.set(i,k,temp); } } return N; }; /** * 获取矩阵i行j列的元素。 * 注:i,i下标从1开始 * @param i * @param j * @return {number} */ Ycc.Math.Matrix.prototype.get = function (i, j) { return this.data[(i-1)*this.n+j-1]; }; /** * 设置矩阵i行j列的元素为val * 注:i,i下标从1开始 * @param i * @param j * @param val */ Ycc.Math.Matrix.prototype.set = function (i, j, val) { this.data[(i-1)*this.n+j-1] = val; }; })(Ycc);;/** * @file Ycc.Tree.class.js * @author xiaohei * @date 2018/8/6 * @description Ycc.Tree.class文件 */ (function (Ycc) { // 节点的自增id var nodeID = 1; /** * 存储所有树节点的引用,不允许重载 * 使用prototype好处: * 1、所有Tree对象公用 * 2、JSON.stringify不会将此序列化,避免抛循环引用的错误 * key为$id val为Tree对象 * @type {{}} */ var nodeMap = {}; /** * 树的构造函数 * 若参数为空,默认创建只有一个根节点的树 * @constructor */ Ycc.Tree = function() { /** * 节点的自增ID,不允许修改,且每个对象必须唯一 * @type {number} */ this.$id = nodeID++; /** * 节点的父节点ID,不允许修改 * @type {null|Ycc.Tree} */ this.$parentID = null; /** * 节点的子节点列表 * @type {Array} */ this.children = []; /** * 节点所携带的数据 * @type {any} */ this.data = null; // 存入map中,方便通过id寻找 nodeMap[this.$id] = this; }; /** * 释放当前节点的内存,非递归 * @param treeNode */ Ycc.Tree.release = function (treeNode) { // 删除父级children的引用 var pa = treeNode.getParent(); if(pa){ var children = pa.children; var index = children.indexOf(treeNode); if(index!==-1) children[index]=null; } // 删除nodeMap引用 delete nodeMap[treeNode.$id]; /** * 节点的子节点列表 * @type {Array} */ treeNode.children.length = 0; /** * 节点所携带的数据 * @type {any} */ treeNode.data = null; }; /** * 获取nodeMap表 * @return {{}} */ Ycc.Tree.getNodeMap = function () { return nodeMap; }; /** * 获取nodeMap表 * @return {{}} */ Ycc.Tree.prototype.getNodeMap = function () { return nodeMap; }; /** * 获取父级 * @return {Ycc.Tree} */ Ycc.Tree.prototype.getParent = function () { if(!this.$parentID) return null; return nodeMap[this.$parentID]; }; /** * 添加一颗子树 * @param tree */ Ycc.Tree.prototype.addChildTree = function (tree) { if(tree.$parentID) return console.error("sub tree's parent has exist! can't add!",tree); tree.$parentID = this.$id; this.children.push(tree); return this; }; /** * 删除一颗子树,只能删除直接子节点 * @param tree * @return {*} */ Ycc.Tree.prototype.removeChildTree = function (tree) { var index = this.children.indexOf(tree); if(index===-1) return this; this.children.splice(index,1); return this; }; /** * 获取树的深度 * @return {number} */ Ycc.Tree.prototype.getDepth = function () { var self = this; var dep = 1; if(self.children.length>0){ for(var i=0;i<self.children.length;i++){ var subDep = self.children[i].getDepth(); dep = subDep+1>dep?subDep+1:dep; } } return dep; }; /** * 树的迭代器,返回集中常用的迭代方法 * @param option 遍历时的配置项 * @param option.reverse 遍历子节点时,是否将子节点反向,默认false,目前仅支持depthDown遍历 * @return {{each: each, leftChildFirst: leftChildFirst, rightChildFirst: rightChildFirst, depthDown: depthDown}} */ Ycc.Tree.prototype.itor = function (option) { var self = this; option = option || { reverse:false }; /** * 父代优先遍历 * 先遍历父代,再依次遍历子代 * 若cb返回true,则停止遍历 * @param cb * @return {boolean} */ function each(cb) { if(cb.call(self,self)) return true; if(self.children.length>0){ for(var i=0;i<self.children.length;i++){ if(self.children[i].itor().each(cb)) return true; } } return false; } /** * 左树优先遍历 * 只要最左边的树不为空就继续遍历其子树,最后遍历根节点 * 若cb返回true,则停止遍历 * * @param cb * @return {boolean} */ function leftChildFirst(cb) { if(self.children.length>0){ for(var i=0;i<self.children.length;i++){ if(self.children[i].itor().leftChildFirst(cb)) return true; } } if(cb.call(self,self)) return true; } /** * 右树优先遍历 * 只要最右边的树不为空就继续遍历其子树,最后遍历根节点 * 若cb返回true,则停止遍历 * * @param cb * @return {boolean} */ function rightChildFirst(cb) { if(self.children.length>0){ for(var i=self.children.length-1;i>=0;i--){ if(self.children[i].itor().rightChildFirst(cb)) return true; } } if(cb.call(self,self)) return true; } /** * 根据当前节点,按层级依次向下遍历 * 这只是depthDownByNodes的特殊情况 * 若cb返回true,则停止遍历 * * @param cb(node) * @return {boolean} */ function depthDown(cb) { depthDownByNodes([self],cb); } /** * 根据所给的节点列表,按层级依次向下遍历 * 若cb返回true,则停止遍历 * * @param nodes {Ycc.Tree[]} * @param cb(node,layer) * @param [layer] {number} 当前nodes列表所在的层级,可选参数 * @return {boolean|number} */ function depthDownByNodes(nodes,cb,layer){ if(nodes.length===0) return true; layer=layer||0; layer++; // 待遍历的节点判断反向 var itorNodes = option.reverse?reverse(nodes):nodes; // 下一层需要遍历的所有节点 var nextNodes = []; // 是否停止遍历下一层的标志位 var breakFlag = false; for(var i=0;i<itorNodes.length;i++) { var rvl = cb.call(self, itorNodes[i], layer); // 如果返回为true,则表示停止遍历下一层 // 如果返回为-1,则表示当前节点的所有子孙节点不再遍历 if (rvl===true) { breakFlag = true; break; }else if(rvl===-1) continue; nextNodes = nextNodes.concat(option.reverse?reverse(itorNodes[i].children):itorNodes[i].children); } if(breakFlag){ nextNodes = []; } // 是否反向 if(option.reverse) nextNodes.reverse(); return depthDownByNodes(nextNodes,cb,layer); } /** * 将list反向 * @param list * @return {Array} */ function reverse(list){ var ls = []; for(var i=list.length-1;i>=0;i--){ ls.push(list[i]); } return ls; } return { each:each, leftChildFirst:leftChildFirst, rightChildFirst:rightChildFirst, depthDown:depthDown }; }; /** * 转化为节点列表 * @return {Ycc.Tree[]} */ Ycc.Tree.prototype.toNodeList = function () { var list = []; this.itor().depthDown(function (node) { list.push(node); }); return list; }; /** * 获取节点列表按照层级的分类 * key为层级,val为Ycc.Tree列表 * @return {{}} */ Ycc.Tree.prototype.getNodeListGroupByLayer = function () { var list = {}; this.itor().depthDown(function (node,layer) { if(!list[layer]) list[layer] = []; list[layer].push(node); }); return list; }; /** * 获取节点的所有父级,靠近节点的父级排序在后 * @return {Ycc.Tree[]} */ Ycc.Tree.prototype.getParentList = function () { var node = this; var list = []; while(node.$parentID){ var parent = nodeMap[node.$parentID]; list.unshift(parent); node = parent; } return list; }; /** * 获取节点的所有兄弟节点,包括自身 * @return {Ycc.Tree[]} */ Ycc.Tree.prototype.getBrotherList = function () { var list = []; if(!this.$parentID) list = [this]; else list = nodeMap[this.$parentID].children; return list; }; /////////////////////////// static Methods /** * 根据传入的json构造一棵树 * * 若节点有数据必须包含data字段 * 若节点有子节点必须包含children字段,且为数组 * 只关注data和children字段,其他字段将忽略 * * @param json {object} json对象,示例:{data,children} * @return {Ycc.Tree} */ Ycc.Tree.createByJSON = function (json) { var root = new Ycc.Tree(); root.data = json.data; if(Ycc.utils.isArray(json.children) && json.children.length>0){ for(var i=0;i<json.children.length;i++){ root.addChildTree(Ycc.Tree.createByJSON(json.children[i])); } } return root; }; /** * 根据传入的节点列表构造一棵树 * * 只关注字段id,parentID * * 构造成功后将生成新的$id,$parentID,且所有字段都将放入data中,包括id和parentID * * @param nodes {Array[]} json对象数组 * @return {Ycc.Tree} */ Ycc.Tree.createByNodes = function (nodes) { if(!Ycc.utils.isArray(nodes) || nodes.length===0) return console.error('need an Array as param!'); var root = null; var treeNodes = []; nodes.forEach(function (node) { var treeNode = new Ycc.Tree(); treeNode.data = node; treeNodes.push(treeNode); if(!Ycc.utils.isNum(node.parentID) && !node.parentID) root = treeNode; }); treeNodes.forEach(function (treeNode) { treeNodes.forEach(function (subNode) { if(subNode.data.parentID === treeNode.data.id){ treeNode.addChildTree(subNode); } }); }); return root; }; })(Ycc);;/** * @file Ycc.Graph.class.js * @author xiaohei * @date 2018/8/14 * @description Ycc.Graph.class文件 * * 图的结构类,有向图、无向图等 */ (function (Ycc) { // 节点唯一id var vid = 1; // 边的唯一id var eid = 1; // 图的唯一id var gid = 1; // 节点map key为$id val为有向顶点/无向顶点 var vMap = {}; // 边map key为$id val为有向边/无向边 var eMap = {}; /** * 图的结构类 * @constructor */ Ycc.Graph = function (type) { /** * 图的分类 1--有向图 2--无向图 * @type {number} */ this.type = type||1; /** * 图的id * @type {number} */ this.$id = gid++; /** * 图包含的顶点 * @type {Ycc.Graph.DirectedV[] | Ycc.Graph.UnDirectedV[]} */ this.vList = []; /** * 图包含的边 * @type {Ycc.Graph.E[]} */ this.eList = []; }; /** * 获取顶点的map * @return {{}} */ Ycc.Graph.prototype.getMapV = function () { return vMap; }; /** * 获取边的map * @return {{}} */ Ycc.Graph.prototype.getMapE = function () { return eMap; }; /** * 广度优先搜索 * @param vArrId {array} 顶点$id数组,代表从哪些顶点开始遍历 * @param cb {function} 回调函数 * 若回调函数返回true,则遍历结束 * @param [vSearchedId] 已遍历的$id数组 * * @return {boolean} */ Ycc.Graph.prototype.bfs = function (vArrId,cb,vSearchedId) { vArrId = vArrId||[]; vSearchedId = vSearchedId || []; // 递归结束条件 if(vSearchedId.length===this.vList.length) return true; // 若未结束,但长度为0,说明图中存在孤立部分,任取一个孤立部分的顶点,继续遍历 if(vArrId.length===0){ var tempID = null; for(var j=0;j<this.vList.length;j++){ if(vSearchedId.indexOf(this.vList[j].$id)===-1){ tempID=this.vList[j].$id; break; } } return this.bfs([tempID],cb,vSearchedId); } // 修改已遍历的顶点 var v = null; for(var i=0;i<vArrId.length;i++){ v = vMap[vArrId[i]]; if(cb.call(this,v)) return true; vSearchedId.push(v.$id); } // 下一层需要遍历的节点 var next = []; for(var k=0;k<vArrId.length;k++){ v = vMap[vArrId[k]]; var temp = v.getAccessibleIds().filter(function (id) { return vSearchedId.indexOf(id)===-1; }); next = next.concat(temp); } return this.bfs(next,cb,vSearchedId); }; /** * 图的深度优先遍历 * @param vStartID 从哪个顶点开始遍历 * @param cb * @param vSearchedId 已遍历的$id数组 */ Ycc.Graph.prototype.dfs = function (vStartID,cb,vSearchedId) { vSearchedId = vSearchedId || []; // 修改已遍历的顶点 var v = vMap[vStartID]; vSearchedId.push(v.$id); if(cb.call(this,v)) return true; // 遍历可达的节点 var accessibleIds = v.getAccessibleIds(); for(var k=0;k<accessibleIds.length;k++){ var next = accessibleIds[k]; if(vSearchedId.indexOf(next)===-1){ if(this.dfs(next,cb,vSearchedId)) return true; } } // 递归结束条件 if(vSearchedId.length===this.vList.length) return true; // 若递归未结束,说明图中存在孤立部分,任取一个孤立部分的顶点,继续遍历 var tempID = null; for(var j=0;j<this.vList.length;j++){ if(vSearchedId.indexOf(this.vList[j].$id)===-1){ tempID=this.vList[j].$id; break; } } return this.dfs(tempID,cb,vSearchedId); }; /** * 创建一个有向图 * @static * @param vArr {Array} 顶点列表,示例:[{id,data,...}] * @param eArr {Array} 边列表,示例:[{fromId,toId,data,...}] */ Ycc.Graph.createDirectedGraph = function (vArr,eArr) { var graph = new Ycc.Graph(1); var vList = graph.vList; var eList = graph.eList; vArr.forEach(function (v) { vList.push(new Ycc.Graph.DirectedV(v)); }); eArr.forEach(function (e) { var from = null,to=null; var edge = new Ycc.Graph.DirectedE(); for(var i =0;i<vList.length;i++){ // 两个都找到了就跳出去 if(from && to) break; var v = vList[i]; if(v.data.id === e.fromId){ from = v; v.outIDs.push(edge.$id); } if(v.data.id === e.toId){ to = v; v.inIDs.push(edge.$id); } } edge.init(from.$id,to.$id,e); eList.push(edge); }); return graph; }; /** * 有向图中的顶点类 * @param data 节点的数据 * @constructor */ Ycc.Graph.DirectedV = function (data) { /** * 节点id * @type {number} */ this.$id = vid++; /** * 节点所携带的数据 * @type {any} */ this.data = data; /** * 节点的入边$id列表 * @type {number[]} */ this.inIDs = []; /** * 节点的出边$id列表 * @type {number[]} */ this.outIDs = []; // 放入map,方便查找 vMap[this.$id] = this; }; /** * 获取有向图中,某个节点指向的节点ID列表 * @return {Array} */ Ycc.Graph.DirectedV.prototype.getAccessibleIds = function () { var ids = []; this.outIDs.forEach(function (id) { ids.push(eMap[id].toID); }); return ids; }; /** * 有向图中的边类 * @constructor */ Ycc.Graph.DirectedE = function () { /** * 边的id * @type {number} */ this.$id = eid++; /** * 边所携带的数据,比如权重 * @type {any} */ this.data = null; /** * 边的起点$id * @type {number} */ this.fromID = null; /** * 边的终点$id * @type {number} */ this.toID = null; // 放入map,方便查找 eMap[this.$id] = this; }; /** * 有向图边的初始化 * @param fromID * @param toID * @param data */ Ycc.Graph.DirectedE.prototype.init = function (fromID, toID, data) { this.fromID = fromID; this.toID = toID; this.data = data; }; /** * 创建一个无向图 * @static * @param vArr {Array} 顶点列表,示例:[{id,data,...}] * @param eArr {Array} 边列表,ids为边关联的两个顶点id列表,长度为2,示例:[{ids,data,...}] */ Ycc.Graph.createUnDirectedGraph = function (vArr,eArr) { var graph = new Ycc.Graph(2); var vList = graph.vList; var eList = graph.eList; vArr.forEach(function (v) { vList.push(new Ycc.Graph.UnDirectedV(v)); }); eArr.forEach(function (e) { // 边的节点id列表 var ids = []; var edge = new Ycc.Graph.UnDirectedE(); for(var i =0;i<vList.length;i++){ // 遍历的顶点 var v = vList[i]; // 两个都找到了就跳出去 if(ids.length===2) break; if(v.data.id === e.ids[0] || v.data.id === e.ids[1]){ ids.push(v.$id); v.eIDs.push(edge.$id); } } edge.init(ids,e); eList.push(edge); }); return graph; }; /** * 无向图中的顶点类 * @param data {any} 顶点携带的数据 * @constructor */ Ycc.Graph.UnDirectedV = function (data) { /** * 节点id * @type {number} */ this.$id = vid++; /** * 节点所携带的数据 * @type {any} */ this.data = data; /** * 节点的边$id列表 * @type {number[]} */ this.eIDs = []; // 放入map,方便查找 vMap[this.$id] = this; }; /** * 获取节点的可达节点id列表 * @return {number[]} */ Ycc.Graph.UnDirectedV.prototype.getAccessibleIds = function () { var accessibleIds=[]; for(var i=0;i<this.eIDs.length;i++){ // 边 var edge = eMap[this.eIDs[i]]; if(edge.vIDs[0]===this.$id){ accessibleIds.push(edge.vIDs[1]); } if(edge.vIDs[1]===this.$id){ accessibleIds.push(edge.vIDs[0]); } } return accessibleIds; }; /** * 无向图中的边类 * @constructor */ Ycc.Graph.UnDirectedE = function () { /** * 边的id * @type {number} */ this.$id = eid++; /** * 边所携带的数据,比如权重 * @type {any} */ this.data = null; /** * 关联的两个顶点$id数组 长度为2 * @type {number[]} */ this.vIDs = []; // 放入map,方便查找 eMap[this.$id] = this; }; /** * 无向图中的边类初始化 * @param ids * @param data */ Ycc.Graph.UnDirectedE.prototype.init = function (ids, data) { this.vIDs = ids; this.data = data; }; })(Ycc);;/** * @file Ycc.Ticker.class.js * @author xiaohei * @date 2017/10/26 * @description Ycc.Ticker.class文件 */ (function (Ycc) { /** * 系统心跳管理类。 * 管理系统的心跳;自定义帧事件的广播;帧更新图层的更新等。 * * 注: * 心跳间隔时间为1e3/60; * 无论帧率为多少,心跳间隔时间不变; * 总帧数<=总心跳次数; * 只有当总帧数*每帧的理论时间小于总心跳时间,帧的监听函数才会触发,以此来控制帧率; * * @param yccInstance * @constructor */ Ycc.Ticker = function (yccInstance) { this.yccClass = Ycc.Ticker; /** * ycc实例的引用 * @type {Ycc} */ this.yccInstance = yccInstance; /** * 当前帧 * @type {Frame} */ this.currentFrame = null; /** * 启动时间戳 * @type {number} */ this.startTime = performance.now(); /** * 上一帧刷新的时间戳 * @type {number} */ this.lastFrameTime = this.startTime; /** * 上一帧刷新时的心跳数 * @type {number} */ this.lastFrameTickerCount = 0; /** * 当前帧与上一帧的刷新的时间差 * @type {number} */ this.deltaTime = 0; /** * 当前帧与上一帧时间差的期望值(根据帧率计算而来的) * @type {number} */ this.deltaTimeExpect = 0; /** * 实际帧间隔与期望帧间隔的时间比 * @type {number} */ this.deltaTimeRatio = 1; /** * 所有自定义的帧监听函数列表 * @type {function[]} */ this.frameListenerList = []; /** * 默认帧率 * @type {number} */ this.defaultFrameRate = 60; /** * 默认帧间隔 * @type {number} */ this.defaultDeltaTime = 1e3/this.defaultFrameRate; /** * 每帧之间间隔的心跳数 * @type {number} */ this.tickerSpace = 1; /** * 总帧数 * @type {number} */ this.frameAllCount = 0; /** * 总心跳次数 * @type {number} */ this.timerTickCount = 0; /** * 定时器ID。用于停止心跳。 * @type {number} * @private */ this._timerId = 0; /** * 心跳是否已经启动 * @type {boolean} * @private */ this._isRunning = false; }; /** * 定时器开始 * @param [frameRate] 心跳频率,即帧率 * 可取值有[60,30,20,15] */ Ycc.Ticker.prototype.start = function (frameRate) { var timer = requestAnimationFrame || webkitRequestAnimationFrame || mozRequestAnimationFrame || oRequestAnimationFrame || msRequestAnimationFrame; var self = this; //重置状态 self.currentFrame = null; self.timerTickCount = 0; self.lastFrameTickerCount = 0; // 正常设置的帧率 frameRate = frameRate?frameRate:self.defaultFrameRate; // 每帧之间的心跳间隔,默认为1 self.tickerSpace = parseInt(60/frameRate)||1; // 每帧理论的间隔时间 self.deltaTimeExpect = 1000/frameRate; // 初始帧数量设为0 self.frameAllCount = 0; // 启动时间 self.startTime = performance.now(); // 正在进行中 不再启动心跳 if(self._isRunning) return; // timer兼容 timer || (timer = function(callback) { return setTimeout(function () { callback(Date.now()); }, 1e3 / 60); } ); // 启动心跳 // self._timerId = timer.call(window, cb); self._timerId = timer(cb); self._isRunning = true; // 心跳回调函数。约60fps function cb(curTime) { // 总的心跳数加1 self.timerTickCount++; if(self.timerTickCount - self.lastFrameTickerCount === self.tickerSpace){ // 设置 总帧数加1 self.frameAllCount++; // 设置 两帧的时间差 self.deltaTime = curTime-self.lastFrameTime; // 设置 帧间隔缩放比 self.deltaTimeRatio = self.deltaTime/self.deltaTimeExpect; // 设置 上一帧刷新时间 self.lastFrameTime += self.deltaTime; // 设置 上一帧刷新时的心跳数 self.lastFrameTickerCount = self.timerTickCount; // 构造一帧 self.currentFrame = new Frame(self); // 执行所有自定义的帧监听函数 self.broadcastFrameEvent(self.currentFrame); // 执行所有图层的帧监听函数 self.broadcastToLayer(self.currentFrame); } // 递归调用心跳函数 // self._timerId = timer.call(window,cb); self._timerId = timer(cb); } }; /** * 停止心跳 */ Ycc.Ticker.prototype.stop = function () { var stop = cancelAnimationFrame || webkitCancelAnimationFrame || mozCancelAnimationFrame || oCancelAnimationFrame; stop || (stop = function (id) { return clearTimeout(id); }); stop(this._timerId); this._isRunning = false; this.currentFrame = null; }; /** * 给每帧添加自定义的监听函数 * @param listener */ Ycc.Ticker.prototype.addFrameListener = function (listener) { this.frameListenerList.push(listener); }; /** * 移除某个监听函数 * @param listener */ Ycc.Ticker.prototype.removeFrameListener = function (listener) { var index = this.frameListenerList.indexOf(listener); if(index!==-1) this.frameListenerList.splice(index,1); }; /** * 执行所有自定义的帧监听函数 */ Ycc.Ticker.prototype.broadcastFrameEvent = function (frame) { for(var i =0;i<this.frameListenerList.length;i++){ var listener = this.frameListenerList[i]; Ycc.utils.isFn(listener) && listener(frame); } }; /** * 执行所有图层的监听函数 */ Ycc.Ticker.prototype.broadcastToLayer = function (frame) { for(var i = 0;i<this.yccInstance.layerList.length;i++){ var layer = this.yccInstance.layerList[i]; layer.show && layer.enableFrameEvent && layer.onFrameComing(frame); } }; /** * 帧 私有类 * @constructor * @param ticker {Ycc.Ticker} */ function Frame(ticker){ /** * 帧创建时间 * @type {number} */ this.createTime = Date.now(); /** * 与上一帧的时间差 * @type {number} */ this.deltaTime = ticker.deltaTime; /** * 实时帧率 * @type {number} */ this.fps = parseInt(1000/this.deltaTime); /** * 帧下标,表示第几帧 * @type {number} */ this.frameCount = ticker.frameAllCount; /** * 当前帧是否已全部绘制,ticker回调函数可根据此字段判断 * 以此避免一帧内的重复绘制 * @type {boolean} */ this.isRendered = false; } })(Ycc);;/** * @file Ycc.Debugger.class.js * @author xiaohei * @date 2018/10/24 * @description Ycc.Debugger.class文件 */ (function (Ycc) { /** * ycc的调试模块 * @constructor */ Ycc.Debugger = function (yccInstance) { this.yccClass = Ycc.Debugger; /** * ycc的实例 */ this.yccInstance = yccInstance; /** * 信息面板显示的UI 帧间隔 * @type {Ycc.UI} */ this.deltaTime = null; /** * 信息面板显示的UI 帧间隔期望值 * @type {Ycc.UI} */ this.deltaTimeExpect = null; /** * 信息面板显示的UI 总帧数 * @type {Ycc.UI} */ this.frameAllCount = null; /** * 信息面板显示的UI 帧间隔平均值 * @type {Ycc.UI} */ this.deltaTimeAverage = null; /** * 当前帧渲染耗时 * @type {Ycc.UI} */ this.renderTime = null; /** * 当前帧渲染的所有UI个数 * @type {Ycc.UI} */ this.renderUiCount = null; this.totalJSHeapSize = null; this.usedJSHeapSize = null; this.jsHeapSizeLimit = null; /** * 调试面板所显示的字段,示例:[{name,cb,ui}] * @type {Array} */ this.fields = []; /** * 调试面板的容纳区 * @type {Ycc.UI.Rect} */ this.rect = null; /** * 调试面板的图层 */ this.layer = null; }; Ycc.Debugger.prototype.init = function () { this.rect = new Ycc.UI.Rect({ name:'debuggerRect', rect:new Ycc.Math.Rect(10,10,200,140), color:'rgba(255,255,0,0.5)' }); this.layer = yccInstance.layerManager.newLayer({ name:"debug图层" }); var self = this; this.yccInstance.ticker.addFrameListener(function () { self.updateInfo(); }); }; /** * 显示调试面板 */ Ycc.Debugger.prototype.showDebugPanel = function () { this.init(); var layer = this.layer; if(layer.uiList.indexOf(this.rect)===-1) layer.addUI(this.rect); }; /** * 更新面板的调试信息 */ Ycc.Debugger.prototype.updateInfo = function () { // 强制使debug面板置顶 var layerList = this.yccInstance.layerList; var index = layerList.indexOf(this.layer); // bug 调试layer必须存在 if(index===-1) return; if(index+1!==layerList.length){ layerList.splice(index,1); layerList.push(this.layer); } this.rect.rect.height = this.fields.length*20; this.fields.forEach(function (field) { field.ui.content = field.name+' '+field.cb(); }); }; /** * 添加一个信息项 * @param name * @param cb() {function} * cb必须返回一个值,这个值将直接填入 * */ Ycc.Debugger.prototype.addField = function (name, cb) { var index = this.fields.length; var ui = new Ycc.UI.SingleLineText({ content:"usedJSHeapSize "+cb(), fontSize:'12px', rect:new Ycc.Math.Rect(0,20*index,100,20), color:'green' }); this.fields.push({name:name,cb:cb,ui:ui}); this.rect.addChild(ui); }; /** * 将调试面板添加至某个图层 * @param layer {Ycc.Layer} */ Ycc.Debugger.prototype.addToLayer = function (layer) { if(layer.uiList.indexOf(this.rect)===-1) layer.addUI(this.rect); }; /** * 更新某个调试字段的回调函数 * @param name * @param cb */ Ycc.Debugger.prototype.updateField = function (name,cb) { for(var i=0;i<this.fields.length;i++){ if(this.fields[i].name===name){ this.fields[i].cb=null; this.fields[i].cb=cb; return; } } }; /** * 调试日志信息类 * @param message * @constructor */ Ycc.Debugger.Log = function (message) { this.message = '[Ycc logger]=> '+message; }; /** * 调试错误信息类 * @param message * @constructor */ Ycc.Debugger.Error = function (message) { this.message = '[Ycc error]=> '+message; }; })(Ycc);;/** * @file Ycc.Loader.class.js * @author xiaohei * @date 2017/10/9 * @description Ycc.Loader.class文件 */ (function (Ycc) { /** * ycc实例的资源加载类 * @constructor */ Ycc.Loader = function () { this.yccClass = Ycc.Loader; /** * 异步模块 * @type {Ycc.Ajax} */ this.ajax = new Ycc.Ajax(); /** * 基础地址,末尾必须有斜线'/' * @type {string} */ this.basePath = ''; }; /** * 并发加载资源 * @param resArr * @param [resArr.name] 资源名称,方便查找 * @param resArr.url 资源的url * @param [resArr.type] 资源类型 image,audio,默认为image * @param [resArr.res] 资源加载完成后,附加给该字段 * @param [resArr.crossOrigin] 资源跨域配置项 * @param endCb 资源加载结束的回调 * @param [progressCb] 资源加载进度的回调 * @param [endResArr] 用于存储加载已结束的音频,一般不用传值 * @param [endResMap] 用于存储加载已结束的音频map,一般不用传值。注:map的key是根据name字段生成的 */ Ycc.Loader.prototype.loadResParallel = function (resArr, endCb, progressCb,endResArr,endResMap) { endResArr = endResArr || []; endResMap = endResMap || {}; for(var i=0;i<resArr.length;i++){ var curRes = resArr[i]; var successEvent = "load"; var errorEvent = "error"; curRes.type = curRes.type || 'image'; if(curRes.type==='image'){ curRes.res = new Image(); curRes.res.src = curRes.url; curRes.res.crossOrigin = curRes.crossOrigin||''; } if(curRes.type==='audio'){ successEvent = 'loadedmetadata'; curRes.res = new Audio(); curRes.res.src = curRes.url; curRes.res.preload = "load"; curRes.res.crossOrigin = curRes.crossOrigin||''; } curRes.res.addEventListener(successEvent,listener(curRes,i,true)); curRes.res.addEventListener(errorEvent,listener(curRes,i,false)); function listener(curRes,index,error) { return function () { endResArr.push(curRes); if(typeof curRes.name!=='undefined') endResMap[curRes.name] = curRes.res; Ycc.utils.isFn(progressCb) && progressCb(curRes,error,index); if(resArr.length===endResArr.length){ endCb(endResArr,endResMap); } }; } } }; /** * 依次加载资源 * @param resArr * @param [resArr.name] 资源名称,方便查找 * @param resArr.url 资源的url * @param [resArr.type] 资源类型 image,audio * @param [resArr.res] 资源加载完成后,附加给该字段 * @param endCb 资源加载结束的回调 * @param [progressCb] 资源加载进度的回调 * @param [endResArr] 用于存储加载已结束的音频,一般不用传值 * @param [endResMap] 用于存储加载已结束的音频map,一般不用传值。注:map的key是根据name字段生成的 */ Ycc.Loader.prototype.loadResOneByOne = function (resArr, endCb, progressCb,endResArr,endResMap) { endResArr = endResArr || []; endResMap = endResMap || {}; if(resArr.length===endResArr.length){ endCb(endResArr,endResMap); return; } var self = this; // 当前加载的下标 var index = endResArr.length; var curRes = resArr[index]; var successEvent = "load"; var errorEvent = "error"; curRes.type = curRes.type || 'image'; var timerId = 0; polyfillWx(self.basePath + curRes.url,function (fullPath) { if(curRes.type==='image'){ curRes.res = new Image(); curRes.res.src = fullPath; curRes.res.addEventListener(successEvent,onSuccess); curRes.res.addEventListener(errorEvent,onError); // 超时提示只针对图片 timerId = setTimeout(function () { curRes.res.removeEventListener(successEvent,onSuccess); curRes.res.removeEventListener(errorEvent,onSuccess); onError({message:"获取资源超时!"}); },curRes.timeout||10000); }else if(curRes.type==='audio'){ // 兼容wx端 if("undefined"!==typeof wx){ curRes.res = new Audio(); curRes.res.src = fullPath; successEvent = 'loadedmetadata'; errorEvent = 'error'; curRes.res.addEventListener(successEvent,onSuccess); curRes.res.addEventListener(errorEvent,onError); return; } curRes.res = new AudioPolyfill(); if(!curRes.res.context){ onError({message:"浏览器不支持AudioContext!"}); return; } console.log(fullPath); self.ajax.get(fullPath,(function (curRes) { return function (arrayBuffer) { curRes.res.context.decodeAudioData(arrayBuffer, function(buf) { curRes.res.buf=buf; onSuccess(); }, onError); } })(curRes),onError,'arraybuffer'); } }); function onSuccess() { // console.log('loader:',curRes.name,'success'); clearTimeout(timerId); if(curRes.type==='image' || ("undefined"!==typeof wx && curRes.type==='audio' )){ curRes.res.removeEventListener(successEvent,onSuccess); curRes.res.removeEventListener(errorEvent,onErr