Every piece of all creatures in this freezing world, are racing for freedom.
有jslint, javascriptlint(jsl), 还有eslint. 貌似eslint是最受欢迎的. 安装eslintnpm i -g eslint 运行:eslint *.js 在js目录下放一个.eslintrc.json文件, 可以对需要lint的规则做设置.下面是我复制cocos creator的配置,并做了一点修改, 忽略了末尾分号检测.{ "extends": "eslint:recommended", "rules": { "comma-dangle": 0, "no-console": 0, "no-constant-condition": 0, "semi": 0, "no-unused-vars": 1 }, "env": { "browser": true, "node": true, "es6": true, "mocha": true }, "plugins": [ ], "globals": { "cc": false, "CC_EDITOR": false, "CC_DEV": false, "CC_JSB": false, "_ccsg": false } }
在REPL中比较容易调试和发现问题, 所以这儿介绍REPL的玩法.node.js服务端需要安装模块. npm install ws 进入node, 开启服务,等待客户端连接. 为了便于以后向客户端发送消息, 把连接后的ws对象赋值给全局变量thiswsnode > var thisws var Wss = require('./ws').Server var wss = new Wss({port: 3888}) wss.on('connection', function(ws){ thisws = ws console.log('connected.') ws.on('message', function(message){ console.log("msg: ",message) }) }) chrome 打开console,进行连接和监听服务器消息s = new WebSocket('ws://localhost:3888') s.onmessage = function(e){console.log(e.data)} 好啦!, 在chrome中send消息可以在node那边收到, 在node中send消息也可以在chrome中收到了 先从chrome中sends.send('我是chrome') // 在node中将看到这条消息. 再从node中sendthisws.send('我是node服务器') //在chrome中将看到这条消息. 当然,除了chrome自带的WebSocket客户端, 在node中ws也提供客户端, 另外,socket.io也是另一种websocket客户端和服务器的提供者. 总结 浏览器客户端监听和发送 onclose = function(e){} onmessage = function(e){} onopen = function(){} onerror = function(){} send(string) close() node ws服务端监听和发送wss.on('connection',function(ws){}) wss.on('error', function(err){}) wss.on('header',function(array){}) wss.on('listening',function(){}) ws.on('message',function(message_string,flags){}) ws.on('close',function(code, reason){}) ws.on('error',function(err){}) ws.on('open',function(){}) ws.onclose = function(e){} ws.onerror = function(e){} ws.onmessage = function(e){} ws.onopen = function(e){} ws.readyState ws.send(message_string, function(error){}) ws.close(code, reason) // 所有客户端列表 wss.clients wss.close(function(){}) 参考文章: WebSocket and Socket.IO node ws API
开始以前,准备好visual studio 2015, 现在社区版是免费的.这玩意儿很大, 需要装个半天的.先安装由微软推出的Electron打包为uwp工具 (真的是微软推出啊! 微软现在竟然这么拥抱开源...)npm install -g electron-windows-store 然后进入管理员权限的powershell运行:Set-ExecutionPolicy -ExecutionPolicy RemoteSigned OK, 接下来就打包了, 举我自己的app的例子:electron-windows-store --input-directory C:\r\vueproj\nhproj\nhpe --output-directory C:\r\vueproj\nhproj\nhpe_uwp --flatten true --package-version 1.0.0.0 --package-name nhpe 哦, 真的好长, 幸好里面的参数还可以简写:electron-windows-store -i C:\r\vueproj\nhproj\nhpe -o C:\r\vueproj\nhproj\nhpe_uwp -f true -p 1.0.0.0 -n nhpe 需要注意的是, --package-name一项, 名称必须是app运行文件的名称. 比如, 我已经把electron.exe修改为nhpe.exe了,这儿就一定要填写nphe 然后回车后要提示:? Did you download and install the Desktop App Converter? It is *not* required to run this tool. No 这一步不是必须的, 回答no即可. 如果yes的话, 你需要去这儿安装Desktop App Converter,不过还是懒得麻烦了. 回车后提示:? You need to install a development certificate in order to run your app. Would you like us to create one? Yes 这一步必须yes, 否则虽然可以打包成功,然而打好的包由于没有签名而不能运行. 继续回车:? Please enter your publisher identity: CN=idarc ? Please enter the location of your Windows Kit's bin folder: C:\Program Files (x86)\Windows Kits\10\bin\x64 publisher identity这一步必须填写为 CN=xxxx 的格式,否则后面会报错的... Windows Kit这一步, 如果visual studio 装的OK的话, 直接回车就可以了. 接下来会弹窗让你输入密码. 不用输入密码, 点击"none"即可. 等待一会儿, 如果没有错误的话, 就会在指定的目录下看到nhpe.appx, 双击运行安装即可. 安装完了再运行, 哦, 这货和直接运行electron.exe改名的nhpe.exe为毛没有任何外观上的区别? 微软说区别是可以获取在在其他xbox等上运行的能力,然而我并没有xbox...
js如何判断一个对象没有任何自己的属性(为空)?
参考原文 假如有两个空对象, 判断他们是否相等, 结果一定是false:var a = {} var b = {} a == b // false 因为跟c语言类似, 这儿判断的是地址而不是内容. 那么怎么知道a是空呢? 有三个方法. 这儿先假设有一个对象objvar obj = {}方法1Object.keys(obj).length === 0方法2Object.getOwnPropertyNames(obj).length === 0方法3, 自己判断// Speed up calls to hasOwnProperty var hasOwnProperty = Object.prototype.hasOwnProperty; function isEmpty(obj) { // null and undefined are "empty" if (obj == null) return true; // Assume if it has a length property with a non-zero value // that that property is correct. if (obj.length > 0) return false; if (obj.length === 0) return true; // If it isn't an object at this point // it is empty, but it can't be anything *but* empty // Is it empty? Depends on your application. if (typeof obj !== "object") return true; // Otherwise, does it have any properties of its own? // Note that this doesn't handle // toString and valueOf enumeration bugs in IE < 9 for (var key in obj) { if (hasOwnProperty.call(obj, key)) return false; } return true; } // test it isEmpty(""), // true isEmpty(33), // true (arguably could be a TypeError) isEmpty([]), // true isEmpty({}), // true isEmpty({length: 0, custom_property: []}), // true isEmpty("Hello"), // false isEmpty([1,2,3]), // false isEmpty({test: 1}), // false isEmpty({length: 3, custom_property: [1,2,3]}) // false
在chrome中你只能这么做了:(function() { 'use strict'; var foo = 123;//works fine bar = 345;//ReferenceError: bar is not defined }()); 上面这种模式被称之为IIFE(Immediately Invoked Function Expression)即刻执行的函数表达式. 在node中, 你当然也可以用上这种方式, 不过还有一种方式, 在启动node的时候使用--use-strict参数node --use_strict
java8及其以前是不支持交互式命令环境REPL的. Java9 将引入一个JShell来支持. 但目前Java9还没有推出, 怎么办呢? 有一个github的项目, 支持java的REPL 使用前, 需要安装JDK(不是JRE), 以及gradle. 下面是命令行中的安装方法git clone https://github.com/albertlatacz/java-repl.git cd java-repl gradle shadowJar cd build/libs java -jar javarepl-dev.jar 需要注意的是,gradle这一步需要执行数分钟, 我的电脑上是8分多钟. java运行这一步, 需要是类似于jdk1.8.0_111/bin/java这样的目录
由于当前cocos creator音频部分的文档还没有, 这儿简单介绍一下音频部分. 普通的音频的组件是cc.AudioSource, 可以附加在一个node节点上, 同时只能播放一个音频, 添加组件后, 需要将音频拖放到Clip一栏里面(属性为audio-clip). 组件可以进行的操作包括播放/暂停/停止/继续audioSource.play() audioSource.pause() audioSource.stop() audioSource.resume() 可以获取播放进度和总长度audioSource.getCurrentTime() //单位为秒的浮点数 audioSource.getDuration() //同样是单位为秒的浮点数 从属性浏览器中可以发现还有如下属性: clip volume mute loop playOnLoad preload 使用音频引擎 引擎是一个cc下的对象cc.audioEngine来操作的.引擎并不需要去操作一个node对象, 而是直接操作一个资源管理器里面的源文件.所以在定义cc.Class的时候与audioSource不同. 以url的方式定义, 音乐文件(很可能)需要放在resources目录中.// 在操作audioSource的AudioSourceControl.js文件中, 定义属性为cc.AudioSource类型. 然后, 从资源管理器中拖动一个音乐文件到audiosource节点的clip上. 再从层级管理器中拖动audiosource节点到另一个节点的AudioSourceControl.js属性的audioSource上 cc.Class({ properties: { audioSource: { type: cc.AudioSource, default: null }, // 省略其他代码 }) // 在操作audioEngine的AudioEngineControl.js文件中, 定义**url**为cc.AudioClip类型. 然后, 从资源管理器中拖动一个音乐文件到使用此AudioEngineControl.js的节点的audio属性上. // 从示例代码中来看, 这个文件是放在resources目录中的,也就是说, 需要动态加载的. cc.Class({ properties: { audio: { url: cc.AudioClip, default: null }, // 省略其他代码 }) 使用引擎播放, 每次会产生一个audio id, 记住这个id, 用来在后面对该播放中的音乐进行操作.// 播放,暂停,继续, 停止 var id = cc.audioEngine.play(this.audio, false, 1); //参数分别为filepath, loop, volume. filepath示例为res/raw-assets/resources/audio/music_logo.mp3, 如果是拖进来的就不用管这个了. cc.audioEngine.pause(id) cc.audioEngine.resume(id) cc.audioEngine.stop(id) 对所有的播放中音乐统一操作: cc.audioEngine.stopAll(); cc.audioEngine.pauseAll(); cc.audioEngine.resumeAll(); 获取各种状态cc.audioEngine.getCurrentTime(id) // 秒数, 有对应set方法 cc.audioEngine.getDuration(id) // 秒数 cc.audioEngine.getStatus(id) // 返回值-1: 停止状态, 1: 播放状态, 2: 暂停状态. cc.audioEngine.getVolume(id) // 有对应set方法 下面放出cocos creator的示例代码, 先是AudioSourceControl cc.Class({ extends: cc.Component, properties: { audioSource: { type: cc.AudioSource, default: null }, label: { type: cc.Label, default: null } }, // use this for initialization onLoad: function () { // cc.audioEngine.setMaxWebAudioSize(1024*10); }, update: function () { if (!this.label) { return; } var audio = this.audioSource; this.label.string = audio.getCurrentTime().toFixed(1) + ' s / ' + audio.getDuration().toFixed(1) + ' s'; }, play: function () { this.audioSource.play(); }, pause: function () { this.audioSource.pause(); }, stop: function () { this.audioSource.stop(); }, resume: function () { this.audioSource.resume(); } }); 再是audioEngineControl cc.Class({ extends: cc.Component, properties: { audio: { url: cc.AudioClip, default: null }, label: { type: cc.Label, default: null } }, onLoad: function () { this.maxNum = cc.audioEngine.getMaxAudioInstance(); this.audioPool = []; // check deprecated ['playMusic', 'playEffect'].forEach(function (name) { if (!cc.audioEngine[name]) { cc.warn('.' + name + ' is not found!'); } }); }, update: function () { if (!this.label) return; for (var i=0; i<this.audioPool.length; i++) { var id = this.audioPool[i]; var state = cc.audioEngine.getState(id); if (state < 0) { this.audioPool.splice(i, 1); i--; } } this.label.string = 'Instance: ' + this.audioPool.length + ' / ' + this.maxNum; }, play: function () { if (!this.audio) return; var id = cc.audioEngine.play(this.audio, false, 1); this.audioPool.push(id); }, stopAll: function () { if (!this.audio) return; cc.audioEngine.stopAll(); }, pauseAll: function () { if (!this.audio) return; cc.audioEngine.pauseAll(); }, resumeAll: function () { if (!this.audio) return; cc.audioEngine.resumeAll(); }, });
微软目前的开源项目: 代码编辑器Visual Studio Code, 基于github的开源项目electron Edge浏览器的JS引擎Chakra, 甚至被允许用于node.js, 以替换V8. ANGLE, OpenGL ES到DirectX的翻译器 HoloJS, 微软增强现实眼镜Hololens的开发库 WinJS, 是的, 你可以用WinJS+HTML+JavaScript开发win10的UWP软件, 哦, 不过这和electron有什么区别?
箭头函数里使用的this不是它自己的this, 是上层环境的this. 默认情况下, 是window.
cocos creator集electron, node.js, vue.js, 浏览器对象, canvas, webGL于一身, 哦看起来还是很强大的.结构/assets 根目录下面主要编辑的文件都在这儿 /library, /temp 可以认为都是临时文件, 貌似为了加速编辑器某些处理产生的, 和assets里的资源是匹配关系, 如果匹配出了问题, 这些都删掉, creator会自动再次生成. /local 编辑器布局, 一般情况下并没有什么用, 貌似删了也没有关系 /settings 构建发布等相关配置, 蛮重要的,要保留. package.json 也规定了一些重要的信息, 要保留.运行后还会生成很多.meta文件, 是一个资源文件对应一个.meta, 里面主要是资源文件的uuid和其他一些信息.assets资源所以, 主要的编辑工作都是在assets里面. 再说说assets, 文件可以包括: .fire 场景文件, 规定了场景中各个内容, 也就是creator中层级管理器里面显示的那些东东. 其实是一个json文件. .js 脚本文件, 当然了, 是JavaScript, 这个后面专门说一下资源文件: 图片 包括.png, jpg等, 这个不消说, 是在场景中添加到精灵里面的; 粒子 以.plist结尾. plist和json文件作用相同, 只不过是xml形式的. 粒子文件的粒子图片可以是独立图片, 也可以是内嵌在plist中的base64数据.独立图片的话, 需要和plist放在一个目录中. cocos支持的粒子文件在windows上可以用Particle Editor编辑. 字体 可以使用ttf字体或者是位图字体.fnt, windows下使用BMFont制作, MAC下使用glyph designer制作. windows下的制作方法查看这儿动画 动画目前就我所知, 有三种形式: 通过creator编辑的动画, 以.anim结尾, 实际上也是json文件. 这个应该和项目是紧密耦合的, 因为在文件里面规定了frame帧引用的资源是uuid形式的, 而uuid是在项目中生成的. spine骨骼动画 是使用spine软件制作的骨骼动画, 收费才能使用, 恩, 因此我也不知道这个文件是什么样子的. DragonBones骨骼动画 是使用DragonBones软件制作的骨骼动画, 常被称为龙骨动画, 免费开源, 但似乎以前cocos不支持, 现在貌似也支持的吧太好, 我下载了一个动画加入, 发现骨头缺了一块...不厚道地揣测, 也许是因为龙骨软件的开发商是Egret, 也就是另一个游戏引擎白鹭的开发方, 白鹭引擎貌似现在发展的也不错, 在知乎上还能看到两个引擎的拥护者对喷, 是由于这种竞争关系导致的支持不好吗? 龙骨动画包括三个文件, xxx_ske.json, xxx_tex.json, _tex.png. 在creator中, 将xxx_ske.json拖进层级管理器, 再把_tex.png拖到Dragon Atlas Asset中即可. 音乐等其他文件, 目前暂未涉及. js脚本与nodejs和浏览器js异同js脚本很大程度上结合了node.js和浏览器环境, 然而又有所不同.目前发现与nodejs一致的有: 可以使用node.js的require加载自定义在assets中的任何js模块. js中定义的所有变量var ,function 都只能在本js文件中引用. 要让require的方式暴露本模块中的变量, 同nodejs一样,需要通过module.exports的方式. 可以使用ES2015语法, 如let, const, 箭头函数等 不一致的有: 不能使用node.js自带的模块, 如fs, path等. 与浏览器环境一致的有: 包含一个全局变量window, 向window.xxx赋值,可以在任一js中访问. window其实本来是BOM(浏览器对象模型)的一部分, 然而可以在这儿使用. 据说还可以使用npm install安装第三方纯js库使用, 尚未试过. 另外根据帮助文档, cocos creator的项目中也有一个类似于electron的index.html文件, 目前暂未开放出来提前修改.js脚本运行方式 事实上, 在assets目录中的js脚本, 会在游戏一开始加载的时候全部运行一遍, 运行的顺序似乎并没有什么规律. 如果要控制运行顺序参考这儿 所以, 在js正文中所书写的内容都会被运行. 通过require('js文件名')可以直接引用任意目录下的js 文件, 所以js文件就算在不同的目录下也不能同名. js脚本与其他资源文件的关联 所有的资源是以节点树的方式组织, 有点类似于DOM树, 节点树的在'层级管理器'中显示. 在js文件中定义一个cc.Class, 就可以与其他资源节点关联, 每个js文件最多只能有一个cc.Class 这时把js拖到资源节点的属性面板中, 在cc.Class的properties中定义的内容可以显示在面板上, 如果将内容定义为内置的类型, 则可以将节点拖放到属性面板的该类型上, 在程序中可以对此节点进行引用. 如 cc.Class({ extends: cc.Component, properties: { label: cc.Label, } }, accessLabel: function(){ // 访问 this.label } } 通过几个父子方法, 从本节点可以到达任意其他节点. 如果本节点不是一个node,要获取node后才能使用. this.label.node.parent //父节点 this.label.node.children // 子节点的array this.label.node.parent.children // 父节点的子节点array. 由于父节点是node,所以可以直接使用node下的元素children this.label.node.getParent() === this.label.node.parent // true. 目前暂不清楚get方法与直接访问元素是否可能有什么区别. this.label.node.getChildern() === this.label.node.children //true this.label.node.getChildByName('childname') // 按名字查找 通过修改某个节点的父节点来改变这个节点的位置. this.label.node.parent = another.node js脚本对任一资源引用通过cc.find()方法, 可以从根节点到达任意子节点cc.find('Canvas/Label') //返回Canvas下的Label节点. js对某个节点的及其组件component的操作 获取到某个节点后, 你就可以对节点下面的属性直接操作. this.label.node.x = 123 // 其他在属性检查器中看得到的都可以这样操作 要操作节点上的某个组件, 使用getComponent和addComponent this.label.getComponent(cc.Widget).top = 123 // getComponent可以传入一个cc类型 this.label.getComponent('js_file_name') //可以传入字符串, 获取节点上附加的脚本组件 this.label.addComponent(cc.Layout) //也可以通过addComponent添加组件 使用new cc.Node()创建新的节点, 还可以通过cc.instantiate克隆一个节点. var newNode = new cc.Node('newNodeName') var clonedNode = cc.instantiate(anExistedNode) 使用destory()删除一个节点 somenode.destory() 节点生命周期 附加在节点上的js组件,可以获得节点的生命周期变更通知, 并通过回调函数进行操作.生命周期的顺序是: onLoad onEnable start update, lastUpdate 依次循环 onDisable onDestroy 对节点的active赋值true/false, 将触发onEnable/onDisable事件 somenode.active = false // onDisable somenode.active = true // onEnable 对节点调用onDestroy, 触发onDisable, onDestory事件 somenode.destroy() // onDisable, onDestory 节点destory以后, 可以用isValid判断是否被destory了. 恩, 我认为这个名称按照上面的active命名方式, 应该叫valid..., 要么需要把active改为isActive以表明其boolean身份. somenode.isValid //true somenode.destroy() somenode.isValid //false 需要注意的是, 节点生命随着scene的load开始, scene关闭结束. 节点(node)的生命周期和js本身的只执行一次不同, 请注意区分. js与被附属节点的关联 通过this可以获取到当前的js脚本组件, 通过this.node可以获取js脚本所附属的节点. 由于properties和自定义function都是通过this来引用的, 所以, 在cc.Class中定义properties和function的时候要注意了, 这两者都不要起一些creator保留的名字, 列了一下, 这样的保留字还是很多的: __cid__ __classname__ __eventTargets __instanceId __onNodeActivated __preload __scriptAsset __scriptUuid _deserialize _destroyImmediate _destruct _enabled _getLocalBounds _id _instantiate _isOnLoadCalled _name _objFlags _onPreDestroy addComponent constructor destroy enabled enabledInHierarchy getComponent getComponentInChildren getComponents getComponentsInChildren isRunning lateUpdate name node onEnable onFocusInEditor onLoad onLostFocusInEditor onRestore playJump playRun resetInEditor schedule scheduleOnce start unschedule unscheduleAllCallbacks update uuid 场景切换与固定部分节点 加载另一个场景cc.director.loadScene('anotherScene', callSomeFunctionWhenLoaded) 预加载另一个场景cc.director.preLoadScene('anotherScene', callSomeFunctionWhenPreloaded) 保留一个场景中的节点到下一个场景.cc.game.addPersistRootNode('nodeName') 取消保留节点cc.game.removePersistRootNode('nodeName') 排列节点树中的节点 前面说过了, 把一个节点下挂到其他节点下,只需要继续改这个节点的parent属性就行了 somenode.parent = someNewParentNode 然而, 与兄弟节点间的排序, 该怎么做呢? 可以用父节点的addChild()和removeChild(), 每次addChild()以后, 被增加的节点都会自动加到父节点树的最后面, 也就是场景的最顶端. var pa = somenode.parent pa.removeChild(somenode) pa.addChild(somenode) // 将somenode移动到了父节点树的最后面. 值得注意的是, 虽然可以用pa.children直接访问到children的array, 然而此array做pop/push/shift/unshift等操作, 似乎并不会对节点树有任何影响. 因此还是乖乖用addChild和removeChild吧! * 还可以在本级使用setSiblingIndex(index), 可以调整自己在本级中的位置为indexsomenode.getSiblingIndex() // 例如, 0, 是在最低端 somenode.setSiblingIndex(2) // 例如, 2, 上移两层. 发射和监听事件 只能从节点上发送和监听事件 通过在本节点上使用emit发射的事件, 只能被本节点接收到. 通过在本节点上dispatchEvent发射的事件, 通过冒泡的方式逐层被上级节点接收到.