异步执行

js是单线程的,分为同步任务和异步任务,同一个时刻只能去处理一个任务
有一个任务执行栈,同步任务都放到同步栈里面,异步任务执行有结果了,会放到异步栈
任务执行栈会从同步栈里取任务执行,当所有的同步栈任务执行结束,会从异步栈里取任务,
异步栈里也可以分的再细一点就是宏观异步栈微观异步栈在同一时刻,微观异步栈要先于宏观异步栈执行
宏观异步栈指的是:setTimeout(func,time);
微观异步栈指的是:(new Promise()).then(func)
浏览器刷新触发函数:window.requestAnimationFrame
上面三个触发器有一个最明显的差别,就是浏览器刷新函数最准确,其它两个异步触发器就算时间到了,也不一定触发,他有一个先后触发顺序,这一点细微的差别要格外注意

运行环境简要

微信小游戏运行在多种平台上:iOS(iPhone/iPad)微信客户端、Android 微信客户端、PC 微信客户端、Mac 微信客户端和用于调试的微信开发者工具。

各平台脚本执行环境是各不相同的:
在 iOS 上,小程序逻辑层的 javascript 代码运行在 JavaScriptCore 中;
在 Android 上,小程序逻辑层的 javascript 代码运行在 V8 中;
在 开发工具上,小程序逻辑层的 javascript 代码是运行在 NW.js
微信内部植入了一个叫runtime的浏览器内核,它和html5浏览器内核有一些区别,没有放入html解析引擎和css解析引擎,但最牛逼的js引擎(JavaScriptCore or v8)还是搞不来还是植入了,自己还做了一些业务封装,框架如下:
普通html5框架

微信runtime框架

代码分包

js分包比较简单,就是一个解析js代码,找出之前合并的所有文件(文件名+文件内容),然后以s[‘文件名’]=文件内容;这个为一个最小单位往子包中填充,满4兆则为一个子包,重新再生成一个子包文件,再继续填充,依次类推,关键代码如下,只包含了index.js,module.js,nameMap.js这三个js文件就可以轻松分解js代码,就是这么简单
其中index.js文件内容的末尾那个函数是拆包的入口函数
index.js

const fs = require('fs');
const path = require('path');const fileUtils = require('../utils/file');const acorn = require('acorn');
const escodegen = require('escodegen');
const estraverse = require('estraverse');const {scanScripts} = require('./nameMap');const scriptsToken = '__scripts';const CODE_NAME = "index.js"const codegenOpt = {format: {compact: true}
}let settingsName;function getChild(node, child, type) {let c = node[child]if (!c || c.type != type) {throw new Error('parsed error')}return c;
}function isConsoleExpression(node) {if (node.type == "LogicalExpression"&& node.right.type == 'CallExpression'&& node.right.callee.type == 'MemberExpression'&& node.right.callee.object.type == 'Identifier'&& node.right.callee.object.name == 'console') {if (node.right.callee.property.type == 'Identifier' && node.right.callee.property.name == 'log') {return false;}return true;}if (node.type == 'CallExpression'&& node.callee.type == 'MemberExpression'&& node.callee.object.type == 'Identifier'&& node.callee.object.name == 'console') {if (node.callee.property.type == 'Identifier' && node.callee.property.name == 'log') {return false;}return true;}return false;}function removeConsole(ast) {estraverse.replace(ast, {enter: function (node) {if (node.type == 'ExpressionStatement' && isConsoleExpression(node.expression)) {return this.remove();}if (isConsoleExpression(node)) {return this.remove();}}})estraverse.traverse(ast, {enter: function (node, parent) {if (node.type == 'IfStatement' && !node.consequent) {node.consequent = {type: 'BlockStatement',body: []}}if (node.type == 'ConditionalExpression') {if (!node.consequent) {node.consequent = {type: 'BlockStatement',body: []}} else if (!node.alternate) {node.alternate = {type: 'BlockStatement',body: []}}}if (node.type == 'UnaryExpression' && node.operator == 'void' && !node.argument) {node.argument = {type: 'Literal',value: 0}}if (node.type == 'CallExpression'&& node.callee.type == 'MemberExpression'&& node.callee.object.type == 'Identifier'&& node.callee.object.name == 'console') {// console.log(node.type, parent.type)}}})
}function slice(ast) {let body = ast.body[0];let expression = getChild(body, 'expression', 'AssignmentExpression');let callExp = getChild(expression, 'right', 'CallExpression')let args = callExp.arguments;let modules = args[0]['properties'];args[0] = getScriptsToken();//扫描脚本scanScripts(modules);let scripts = [];let script = '';let size = 0;let maxSize = 4 * 1000 * 1000; //4M;for (let i = 0; i < modules.length; i++) {let module = modules[i];//获取文件名let keyStr = escodegen.generate(module.key, codegenOpt);//获取文件内容let valueStr = escodegen.generate(module.value, codegenOpt);//此处会多7个字节 因为s['']=;这个结构正好是7个字节//我们每个文件都会以这样的形式存放 s['文件名']=文件内容;let moduleSize= keyStr.length + valueStr.length + 7;//s['xxx']=yyy;if (size + moduleSize > maxSize) {//满4兆了 OK 保存起来scripts.push(script);script = '';size = 0;}script += `s['${keyStr}']=${valueStr};`size += moduleSize;}//每一个子包内容都保存在这个scripts数组中scripts.push(script);scripts = scripts.map((s) => {return `(function(s){${s}})(window.__scripts||(window.__scripts={}))`});scripts.push(escodegen.generate(ast, codegenOpt))return scripts
}function getScriptsToken() {return {type: 'MemberExpression',object: {type: 'Identifier',name: 'window'},property: {type: 'Identifier',name: scriptsToken}}
}/*** js源码路径* @param {*} src */
module.exports =  function(src) {let code = fs.readFileSync(src, 'utf8');let ast = acorn.parse(code);removeConsole(ast);return slice(ast);
}

nameMap.js

let map = {};function scanScripts(properties) {for (let i = 0; i < properties.length; i++) {let property = properties[i];let key = property.key;let nameif (key.type == "Literal") {name = key.valueproperty.key = {type: 'Identifier',name: name}} else if (key.type == "Identifier") {name = key.name;}if (!name || map[name]) {throw new Error('script name is duplicated:' + key);}map[name] = next();}
}function getShortName(key) {return map[key];
}let letters = 'abcdefghijklmnopqrstuvwxyz'
let numbers = '0123456789'
let lettersLen = letters.length;
let numbersLen = numbers.length;let nameIndex = 0;
function next() {let index = nameIndex;let name = letters.charAt(index % lettersLen);index = Math.floor(index / lettersLen);while (index > 0) {name += getCharAt(index % (lettersLen + numbersLen));index = Math.floor(index / (lettersLen + numbersLen))}nameIndex++;return name;
}function getCharAt(i) {if (i < lettersLen) {return letters.charAt(i)} else {return numbers.charAt(i - lettersLen)}
}module.exports = {getShortName: getShortName,scanScripts: scanScripts
}

module.js

const {getShortName} = require('./nameMap')function Module(ast) {this.name = ast.key;let value=  ast.value;this.function = value[0];this.map = value[1];
}let proto = Module.prototype;proto.convertName = function() {this.shortName = getShortName(this.name)this.convertedFunc = {type: this.function.type}
}proto.toString = function() {}module.exports = Module

防破解

对于微信小游戏而言想要获取它的代码和资源太简单了,现成的脚本工具,几乎用不了几分钟就可以拿到,可是要想连到小游戏的服务端这个问题就复杂了,微信后台做了保护,大致如下:

//发起登录
public login() {let wx: any = window['wx'];let fail = function () {UIPopupHelper.showOfflineDialog(Lang.get("login_network_timeout"), null, this.login.bind(this));G_WaitingMask.showWaiting(false);}.bind(this);let this1 = this;G_WaitingMask.showWaiting(true);wx.login({success(res) {//微信登录成功后 会返回一个临时codethis1._loginServer(res.code, (ret, data) => {G_WaitingMask.showWaiting(false);this1._onGetToken(ret, data);}, fail);},fail: fail})}//拿到这个微信返回的code 去访问我们自己的服务器//我们的服务器会拿着这个code和appid还有appsecret这三个值去再一次访问微信来判断有效性//如果有效 则合法 允许登录//否则 登陆不合法private _loginServer(code: string, success?: Function, fail?: Function) {// console.log("NativeAgentWeChat loginServer", code);let url = config.LOGIN_URL_TEMPLATE;url = url.replace("#domain#", config.LOGIN_URL);let requestData = {appID: this.getGameId(),channelID: this.getChannelId(),extension: "",sdkVersionCode: "1.0",deviceID: "",userType: "",sign: ""}requestData.extension = JSON.stringify({ code: code });let sign = "appID=" + requestData.appID + "channelID=" + requestData.channelID + "extension=" + requestData.extension + this._appkey;// console.log("sign:", sign);requestData.sign = window['md5'](sign);let srcs = JSON.stringify(requestData);// console.log("requestData", srcs);let aesRequestData = CryptoJS.AES.encrypt(srcs, this._aesKey, { mode: CryptoJS.mode.ECB, padding: CryptoJS.pad.Pkcs7 });// console.log(aesRequestData.toString());url = url.replace("#data#", encodeURIComponent(aesRequestData));// console.log(url);let http = new HttpRequest();http.get(url, (response) => {// console.log(response);let ret = JSON.parse(response);if (ret.state != 1 || ret.data == null) {fail && fail();return;}let decrypt = CryptoJS.AES.decrypt(ret.data, this._aesKey, { mode: CryptoJS.mode.ECB, padding: CryptoJS.pad.Pkcs7 });let data = decrypt.toString(CryptoJS.enc.Utf8);console.log("decrypt", data);success && success(ret.state, data);}, fail);}private _onGetToken(ret, response?: string) {if (ret != 1) {this._dispatch({ event: NativeConst.SDKLoginResult, ret: NativeConst.STATUS_FAILED, param: "" });return;}let responseData = JSON.parse(response);console.log("_onGetToken:", ret, responseData);this._openid = responseData.extension.openid;this._sessionKey = responseData.extension.session_key;// if (!ALDStatistics.instance.isFirstLoginGame() && !ALDStatistics.instance.hasMarkAB) {//     this.abCode = this._openid.charCodeAt(this._openid.length - 1);// } else {//     var code = window['md5'](this._openid);//     this.abCode = code.charCodeAt(code.length - 1);// }// wx.aldUserAB(this.getversionAB());// console.log('AB_code: ', this.getversionAB());this._topUserID = responseData.topUserID.toString();let topUserName = responseData.topUserName.toString();this._topUserName = topUserName;let data: any = {};data.topUserID = topUserName;data.topUserName = topUserName;data.platformID = responseData.platformID;data.sdkUserName = "";data.sdkUserID = this._openid;data.channelID = this.getPlatformId(); // responseData.channelID;data.token = "这是一个自定义token";data.timestamp = (new Date().getTime() * 1000).toString();data.extension = "gptxxxxxxx|1|1";let sign = topUserName + topUserName + data.sdkUserID + data.token + config.TOKEN_KEY;data.sign = window['md5'](sign);console.log("_onGetToken:", data);this._data = data;if (G_StorageManager.load('server')) {this._dispatch({ event: NativeConst.SDKLoginResult, ret: NativeConst.STATUS_SUCCESS, param: data });}else {G_RoleListManager.checkUpdateList();}}

由代码和图可以知道,如果想要破解微信小游戏和服务器连接,你得知道人家的appid,微信验证登录的时候是结合code+appid+appsecret这三个值来判断的,我们的小游戏在打包成功以后,她如果调用wx.login,那么除了code是临时的,appid和appsecret都是确定的,所以我们还要把我们的小游戏发布到原版的微信后台,什么这不是自投罗网吗?

resources目录下的资源与library目录下的关系

resources目录下的每个资源都会带有一个meta文件,这个meta文件会存放一个唯一的uuid来标记该文件
资源之图片

resources目录下的.meta文件内容如下,红框中为图片的uuid

在library库中找到对应的存放位置:

在library库下还有个配置文件uuid-to-mtime.json,这个是用来建立resources目录下的资源和library/imports目录下资源的桥梁,那么当前这个图片在配置文件的记录如下:

打包后这个资源的位置如下:
由于构建发布后,资源会进行自动合并到图集,所以这个资源的存储位置可能已经镶嵌到一张大图中了,但是路径是不会变的,我们可以拿着下面的路径去找

"relativePath": "resources\\icon\\achievement\\bg_signpics.png"

构建发布后,主包的资源会放在这个文件夹下

打开这张图里的config,输入路径,会看到他被放到了一个叫11953下标的位置存放,继续搜索这个下标

会看到11953这个下标又被一个07d5dfd4a这个下标的位置存放,继续搜索

会看到这个07d5dfd4a被存到下面这个数组里,ok这个算是到顶层了,打开import这个文件夹,搜索07d5dfd4a

07d5dfd4a.md5.json

打开这个07d5dfd4a.md5.json

注意到texture也存了一个MD5值,拿这个值在native文件夹下搜索就可以找到打包后的资源了啊

资源之预制体:未完待续

resources文件下对应文件的.meta文件的记录

library文件夹的uuid-to-mtime.json中的记录

library文件夹的imports文件下

构建成微信小游戏包后的资源存放目录:查看一下这个config.MD5.json文件
输入资源的名字

prefab/achievement/DailyActivityHint




在import文件夹下输入:04bfb49bb

资源之声音:未完待续
总结:对于原生资源,比如一张图片或者一个声音文件,一般情况即没有被引用的情况下,会在import文件夹生成一个json,我们通过json可以获取到每个资源的native的路径MD5值,拿这个值在native文件下找到对应的文件,我们在外围访问这个资源的时候是通过和import文件夹同目录的config文件里找到访问路径的
资源的相对路径===========》输入到config.md5.json配置文件中========》获取到import文件下的json文件MD5名========》打开import文件夹下对应的json文件,即可以找到对应native文件夹下资源的MD5名=====》在native文件夹输入对应的MD5名即可以获取到资源
再次化简:
资源的相对路径==》config==》import==》native==>资源

其实这个过程就是config负责建立外围和import的联系,而import是为了建立和native原生资源的联系
发现:图片会被自动的合并起来,如果预制体里使用了图片,图片的json信息也会和预制体的json信息合并成一个json存放在import文件夹下

AB包

ab包通过一个文件夹生成,这个文件夹里包含了所有的图片资源,声音,脚本文件等,那么最后生成一个AB包的时候,最终的产物一个import文件夹,一个naitive文件夹,一个config.md5.json
如果包含脚本的话,会单独生成一个index.js文件,将所有脚本文件合并
注意:
1:Creator 有 4 个 内置AB包,包括 resources、internal、main、start-scene,在设置 Bundle 名称 时请不要使用这四个名称
2:小游戏分包只能放在本地,不能配置为远程包,所以当 压缩类型 设置为 小游戏分包 时,配置为远程包 项不可勾选
3:Zip 压缩类型主要是为了降低网络请求数量,如果放在本地,不用网络请求,则没什么必要,所以要求与 配置为远程包 搭配使用
进一步说一下:import naitive config.md5.json
import:你可以理解为creator将一个显示页面导出一个配置文件,creator加载这个配置文件还是可以还原显示页面的
native:这个是资源,是实实在在的资源
config.md5.json:每一个AB包生成以后最外层都会有一个config文件,看他的名字很特别,加了一个MD5值,你可以理解这个MD5值就是当前这个AB包的名字,因为最后creator会根据这个值来找这个AB包

过程

下面这张图是位于src/setting.js的文件内容,他是一份配置
bundleVers:这个字段记录了当前包使用的各个AB包的版本,用一个MD5值去记录,内部会自动根据这个MD5值来找到对应的配置
关于AB包说一下:creator自己内置四个AB包
1:interval这个内置包主要是creator自己使用的资源(shader,图片等)
2:start-scene这个内置包主要是我们如果在构建工程的时候,如果勾选了初始场景分包,那么就会为我们生成这个子包,这里面主要的内容就是我们的启动场景所用到的所有资源和脚本,
3:remote这个内置包是我们构建的时候,勾选了将主包设置为远程资源包,那么就会生成这么一个内置包,
4:resources这个内置包是游戏主资源包,如果我们勾选了主包设置为远程资源包,那么该文件夹下的所有资源就会跑到同级remote文件夹下,这个remote下的所有资源可以考虑打成zip包,上传到资源服,然后再资源服将其解压
特别注意1:我们一般将主要资源都放在resources这个文件夹中,资源主要包括预制体声音图片动画脚本等文件,其中脚本文件会单独的放到根目录的src文件夹下,假设我们之前用的是js文件,那么此处src中将会放置相对路径的js文件,如果之前都是ts脚本文件,那么将统一合并到相对路径的index.js文件中,这里的相对路径指的是之前是基于resources为根目录,现在是基于src为根目录
特别注意2:小游戏如果代码包超过了限制,那么就要分包,这里分包的代码其实就是分的主包的代码,也是上面提到的index.js,我们一般都是使用ts开发的,这些放在resouces文件夹下的ts,最终都会合并到src/scripts/resources/index.js中,我们只要读取这个文件进行代码拆分即可
特别注意3:start-scene中也会包含脚本文件,这个是不会合并到src目录下的index.js中的,它属于启动场景,不是主包,所以在自己的文件夹进行合并生成index.js,

ccRequire.js:这里面包含要加载的脚本文件,一部分脚本文件是我们在工程中自己使用的,还有一部分是生成assetboudle过程中产生,assetboudle对应的资源也有可能包含脚本代码,那这个代码就会自动合并到index.js文件夹中

游戏启动
调用了下面这句话来加载各个AB包,如果当前这个AB包是启动场景,那么游戏就会依据启动场景的逻辑代码开始加载游戏资源,从而进入游戏
cc.assetManager.loadBundle(bundleRoot[i], cb);
main.js

"use strict";window.boot = function () {var settings = window._CCSettings;window._CCSettings = undefined;var onStart = function onStart() {cc.view.enableRetina(true);cc.view.resizeWithBrowserSize(true);var launchScene = settings.launchScene; // load scenecc.director.loadScene(launchScene, null, function () {console.log('Success to load scene: ' + launchScene);});};var isSubContext = cc.sys.platform === cc.sys.WECHAT_GAME_SUB;var option = {id: 'GameCanvas',debugMode: settings.debug ? cc.debug.DebugMode.INFO : cc.debug.DebugMode.ERROR,showFPS: !isSubContext && settings.debug,frameRate: 60,groupList: settings.groupList,collisionMatrix: settings.collisionMatrix};cc.assetManager.init({bundleVers: settings.bundleVers,subpackages: settings.subpackages,remoteBundles: settings.remoteBundles,server: settings.server,subContextRoot: settings.subContextRoot}); var _cc$AssetManager$Buil = cc.AssetManager.BuiltinBundleName,RESOURCES = _cc$AssetManager$Buil.RESOURCES,INTERNAL = _cc$AssetManager$Buil.INTERNAL,START_SCENE = _cc$AssetManager$Buil.START_SCENE;var bundleRoot = [INTERNAL];settings.hasStartSceneBundle && bundleRoot.push(START_SCENE);settings.hasResourcesBundle && bundleRoot.push(RESOURCES);var count = 0;function cb(err) {if (err) return console.error(err.message, err.stack);count++;if (count === bundleRoot.length + 1) {cc.game.run(option, onStart);}} // load plugins//加载脚本cc.assetManager.loadScript(settings.jsList.map(function (x) {return 'src/' + x;}), cb); // load bundles//加载所有bundle里生成的index.js脚本//这里包含了内置的start-scene这个AB包所包含的启动脚本for (var i = 0; i < bundleRoot.length; i++) {cc.assetManager.loadBundle(bundleRoot[i], cb);}
};

拓展

1:微信小程序开发者模式右上角的RT-FPS和Min-FPS和EX-FPS分别含义

rt-fps  : runtime fps  实时 帧率
ex-fps :是极限帧率,可以理解为在不受驱动帧率的限制下(大部分手机微 60fps),仅仅计算 js 运行耗时,可以达到的极限帧率。这个数字可以用于评估在满帧的前提下,运行性能是否有变化
min-fps: 最小帧率

那creator中又是如何设置帧率的呢
window.requestAnimationFrame这个是浏览器的刷新界面函数,每秒调用60次,这个是死的,如果我们想一秒钟调用30次,下面的实现的逻辑就是奇偶帧错开渲染,这不就是30帧了吗,目前只支持这两种帧率,如果低于这个帧率,那么就会显示不正常,但是根据这个规律,其实可以自定义各种帧率,只是没有意义而已
如果想该改变window.requestAnimationFrame这个函数的刷新时间,可以通过wx.setPreferredFramesPerSecond(fps)这个函数

//  @Game play control/*** !#en Set frame rate of game.* !#zh 设置游戏帧率。* @method setFrameRate* @param {Number} frameRate*/setFrameRate: function (frameRate) {var config = this.config;config.frameRate = frameRate;if (this._intervalId)window.cancelAnimFrame(this._intervalId);this._intervalId = 0;this._paused = true;this._setAnimFrame();this._runMainLoop();},
//  @Time ticker section_setAnimFrame: function () {this._lastTime = performance.now();var frameRate = game.config.frameRate;this._frameTime = 1000 / frameRate;cc.director._maxParticleDeltaTime = this._frameTime / 1000 * 2;if (CC_JSB || CC_RUNTIME) {jsb.setPreferredFramesPerSecond(frameRate);window.requestAnimFrame = window.requestAnimationFrame;window.cancelAnimFrame = window.cancelAnimationFrame;}else {if (frameRate !== 60 && frameRate !== 30) {window.requestAnimFrame = this._stTime;window.cancelAnimFrame = this._ctTime;}else {window.requestAnimFrame = window.requestAnimationFrame ||window.webkitRequestAnimationFrame ||window.mozRequestAnimationFrame ||window.oRequestAnimationFrame ||window.msRequestAnimationFrame ||this._stTime;window.cancelAnimFrame = window.cancelAnimationFrame ||window.cancelRequestAnimationFrame ||window.msCancelRequestAnimationFrame ||window.mozCancelRequestAnimationFrame ||window.oCancelRequestAnimationFrame ||window.webkitCancelRequestAnimationFrame ||window.msCancelAnimationFrame ||window.mozCancelAnimationFrame ||window.webkitCancelAnimationFrame ||window.oCancelAnimationFrame ||this._ctTime;}}},_stTime: function(callback){//获取页面加载到现在的时间 单位(毫秒)var currTime = performance.now();var timeToCall = Math.max(0, game._frameTime - (currTime - game._lastTime));var id = window.setTimeout(function() { callback(); },timeToCall);game._lastTime = currTime + timeToCall;return id;},_ctTime: function(id){window.clearTimeout(id);},
//Run game._runMainLoop: function () {if (CC_EDITOR) {return;}if (!this._prepared) return;var self = this, callback, config = self.config,director = cc.director,skip = true, frameRate = config.frameRate;debug.setDisplayStats(config.showFPS);callback = function (now) {if (!self._paused) {self._intervalId = window.requestAnimFrame(callback);if (!CC_JSB && !CC_RUNTIME && frameRate === 30) {if (skip = !skip) {return;}}director.mainLoop(now);}};self._intervalId = window.requestAnimFrame(callback);self._paused = false;},

creator打包微信小游戏笔记相关推荐

  1. Cocos Creator Erro 4916,Failed to load scene的问题找到了!---cocos creator打包微信小游戏的坑

    首先我们来回顾一下问题 当我在用cocos微信小游戏开发的时候,发现你加上开放数据域居然不能超过8M ,wtf? 于是我把自己打包后的res文件夹放在服务器上并且删了本地的res文件.结果问题来了 这 ...

  2. Cocos Creator发布微信小游戏包内体积过大问题

    1.初识 设置微信开发工具和js编辑器 3.5.2 :Cocos Creator perferences 2.Cocos Creator发布微信小游戏包内体积过大问题 2.1 已不可取:搭建本机服务器 ...

  3. Cocos Creator之微信小游戏的游戏圈

    Cocos Creator之微信小游戏的游戏圈 1.官方文档游戏圈使用指南 · 小游戏: 2.需要将游戏圈放到指定地方(比如下图,坐标为x:160,y:850,因为游戏圈图标的锚点在左上角,所在层的锚 ...

  4. Cocos creator导出微信小游戏, 转发给朋友,分享到朋友圈 灰色不能点击

    Cocos creator导出微信小游戏, 转发给朋友,分享到朋友圈 灰色不能点击 解决方法如下: onLoad(){ wx.showShareMenu({withShareTicket: true, ...

  5. unity打包微信小游戏,配置CDN全流程

    1.获取CDN服务器[七牛云为例] 我们需要购买cdn产品,不同厂家的价格都不同,按照自己的需求购买,新用户的话基本都有优惠,测试用或者用户量小的情况一般都够用. 2.打包微信小游戏 小游戏 unit ...

  6. cocos creator开发微信小游戏(五)贪吃蛇大作战

    目录 小游戏介绍 小游戏cocos creator场景图 小游戏部分JS代码 开发中碰到的问题 工程及说明 小游戏介绍 贪吃蛇小游戏:贪吃蛇试玩(首次加载比较慢),类似贪吃蛇大作战的小游戏.当玩家的蛇 ...

  7. CocosCreator 打包微信小游戏

    1. 下载微信开发者工具并安装,申请小游戏ID,在ccc的文件-->设置-->原生环境中,指定路径 2.发布平台选择微信小游戏,填入appid,这里要注意,小程序的id 和小游戏的id不同 ...

  8. scratch跳一跳游戏脚本_cocos creator制作微信小游戏「跳一跳」

    一.游戏的分析(之前没有接触过小游戏,制作的思维还停留在大型ARPG游戏大家共同协作的想法里,但是小游戏讲究小而全,大部分时间是一个人独立开发,所以需要迫使自己养成看到小游戏先拆分细化的思想) 二.一 ...

  9. 微信小游戏开新手攻略

    Creator星球「脱贫实验室」有不少伙伴开始实践微信小游戏,遇到一些问题,在这里简单总结并回复大家. 1. 开发微信小游戏需要版号吗? 开发微信小游戏不需要版号,但需要<计算机软件著作权登记证 ...

最新文章

  1. EBS-使用 fnd_user_pkg API 创建用户,添加职责,修改用户
  2. java注解机制_Java 注解机制
  3. Codeforces757E.Bash Plays With Functions(积性函数 DP)
  4. [认证授权] 1.OAuth2授权
  5. 《C#3.0 in a Nutshell,3rd Edition》之C#3.0和.net3.5基本介绍篇
  6. android pcm调节音量,调整PCM语音数据的音量
  7. php 获取cookieid,Redis实现Session共享详解
  8. java自定义一个方法,用于返回两个整数的和
  9. 简约响应式导航主题VIK_WordPress模板
  10. C++内存分配方式-堆、栈、自由存储区、全局/静态存储区和常量存储区
  11. 新四则运算 合作完成
  12. java 任务栏程序_如何为Java Swing程序动态启用或禁用任务栏图标
  13. ASP.NET Core json返回忽略某些字段,实体不与数据库映射字段
  14. 小米9008授权账号_小米AI音箱APP的秘密
  15. MATLAB 如何使用pascal函数创建Pascal(帕斯卡)矩阵
  16. RFID EPC Class1 Gen2电子标签笔记
  17. 上网账号口令怎么获取_如何获取自己路由器的上网账号和上网口令
  18. OpenCV官方教程节选
  19. Python好酷|allpairspy一款高效的正交实验法生成用例工具
  20. 音频功放的失真的原因分析及测量

热门文章

  1. 如何快速的进行CAD创建图层并给进行样式更改?
  2. redis mysql排行榜实现_使用Redis实现实时排行榜
  3. 开源软件中的商标问题
  4. vivo手机使用应用沙盒一键修改运营商信息
  5. BI技巧丨计算组柱形图
  6. 关于火牛板3.2TFT显示图片Image2lcd的搞法
  7. 有关游戏中的探索测试体会
  8. MATLAB求解函数极值及函数图像
  9. 【前端js】实现剑指offer|leetcode(二)——数组题目集合
  10. 【实战】物联网安防监控项目【3】———CGI的移植及与学习html制作网页