Cocos Creator 资源加载流程剖析【二】——Download部分

Download流程的处理由Downloader这个pipe负责(downloader.js),Downloader提供了各种资源的“下载”方式——即如何获取文件内容,有从网络获取、从磁盘获取,不同类型的资源在不同的平台下有不同的获取方式。

  • 比如脚本在原生平台使用require方法获取,而在H5平台则使用动态添加的HTML标签,指定src进行加载。
  • 又比如json在原生平台使用jsb.fileutils进行加载,而在H5平台则使用XMLHttpRequest从网络下载。

  • Downloader处理

Downloader的handle接收一个item和callback,根据item的type在this.extMap中获取对应的downloadFunc,交由downloadFunc下载,根据下载结果调用callback。同时有一个并发限制,默认最多同时下载64个资源,超过的会进入队列,等待前面的资源加载完成后再依次进行加载。如果item的ignoreMaxConcurrency为true则无视该并发限制。downloadFunc接受一个item和一个callback,如果是同步下载,需要返回downloadFunc的返回值,而异步下载则返回undefined或不返回。

Downloader.prototype.handle = function (item,callback) {
    var self = this;
    var downloadFunc = this.extMap[item.type] || this.extMap['default'];
    var syncRet = undefined;
    if (this._curConcurrent < cc.macro.DOWNLOAD_MAX_CONCURRENT) {
        this._curConcurrent++;
        syncRet = downloadFunc.call(this,item,function (err,result) {
            self._curConcurrent = Math.max(0,self._curConcurrent - 1);
            self._handleLoadQueue();
            callback && callback(err,result);
        });
        // 当downloadFunc是同步执行的,会返回非undefined的syncRet
        if (syncRet !== undefined) {
            this._curConcurrent = Math.max(0,this._curConcurrent - 1);
            this._handleLoadQueue();
            return syncRet;
        }
    }
    else if (item.ignoreMaxConcurrency) {
        syncRet = downloadFunc.call(this,callback);
        if (syncRet !== undefined) {
            return syncRet;
        }
    }
    else {
        this._loadQueue.push({
            item: item,callback: callback
        });
    }
};

Downloader的this.extMap记录了各种资源类型的下载方式,所有的类型最终都对应这6个下载方法,downloadScript、downloadImage(downloadWebp)、downloadAudio、downloadText、downloadFont、downloadUuid,它们对应实现了各种类型资源的下载,通过Downloader.addHandlers可以添加或修改任意资源的下载方式。

  • downloadScript

如果是微信或者原生平台,只是对脚本进行require(CommonJS模块化规范),这里主要是web平台的处理,原生平台的处理在后面统一介绍,web平台是通过创建一个script的HTML标签,指定标签的src,添加事件监听,通过这种HTML的方式下载脚本,使其生效。

function downloadScript (item,callback,isAsync) {
    if (sys.platform === sys.WECHAT_GAME) {
        require(item.url);
        callback(null,item.url);
        return;
    }

    // 创建一个script标签元素,并指定其src为我们的源码路径
    var url = item.url,d = document,s = document.createElement('script');
    s.async = isAsync;
    s.src = urlAppendTimestamp(url);
    
    function loadHandler () {
        s.parentNode.removeChild(s);
        s.removeEventListener('load',loadHandler,false);
        s.removeEventListener('error',errorHandler,false);
        callback(null,url);
    }
    function errorHandler() {
        s.parentNode.removeChild(s);
        s.removeEventListener('load',false);
        callback(new Error('Load ' + url + ' failed!'),url);
    }
    // 添加加载完成和错误回调
    s.addEventListener('load',false);
    s.addEventListener('error',false);
    d.body.appendChild(s);
}

当cc.game.config[‘noCache‘]为true时,urlAppendTimestamp会在url的尾部添加当前的时间戳,这会导致每次加载资源时由于url不同,不会直接使用浏览器的缓存,而是重新获取最新的资源,接下来的各种下载函数中也有urlAppendTimestamp。

  • downloadImage

downloadWebp和downloadImage都是用于下载图片资源,downloadWebp只是判断了cc.sys.capabilities.webp是否为true,如果为false表示当前的环境不支持webp,如果支持则直接调用downloadImage进行下载。downloadImage中引入了2个概念,imagePool和crossOrigin,imagePool是一个JS.Pool,它的get方法会返回一个Image对象。如果是非https下的跨域请求,下载失败时会使用不跨域的方式再请求一次。

由于浏览器同源策略,凡是发送请求url的协议、域名、端口三者之间任意一与当前页面地址不同即为跨域,以下为跨域的详细描述表格。在web端,使用webgl模式无法直接使用跨域图片,需要服务器配合设置Access-Control-Allow-Origin(Canvas模式允许使用跨域图片)。

FJ3OeI.png

当我们访问跨域资源的时候,能否正确加载图片取决于图片服务器是否开启了跨域支持(Access-Control-Allow-Origin: *),比如 http://tools.itharbors.com/res/logo.png 这个资源的服务器开启了跨域支持,所以可以正确加载,不需要调整客户端加载的代码。

那么downloadImage为什么要在设置crossOrigin加载失败之后,将crossOrigin设置为null再加载一次呢?因为关闭crossOrigin之后虽然可以加载,但无法准确地捕获错误。在测试中,如果服务器没有开启跨域支持,通过将crossOrigin设置为null确实可以下载到图片,然而在webgl初始化该图片时会报错。

function downloadImage (item,isCrossOrigin,img) {
    if (isCrossOrigin === undefined) {
        isCrossOrigin = true;
    }

    var url = urlAppendTimestamp(item.url);
    img = img || misc.imagePool.get();
    if (isCrossOrigin && window.location.protocol !== 'file:') {
        img.crossOrigin = 'anonymous';
    } else {
        img.crossOrigin = null;
    }

    if (img.complete && img.naturalWidth > 0 && img.src === url) {
        return img;
    } else {
        function loadCallback () {
            img.removeEventListener('load',loadCallback);
            img.removeEventListener('error',errorCallback);
            callback(null,img);
        }
        function errorCallback () {
            img.removeEventListener('load',errorCallback);

            // Retry without crossOrigin mark if crossOrigin loading fails
            // 如果加载失败,重试的时候img.crossOrigin被置为null
            // Do not retry if protocol is https,even if the image is loaded,cross origin image isn't renderable.
            // 如果是https就不重试了,因为就算加载了到了图片也无法渲染
            if (window.location.protocol !== 'https:' && img.crossOrigin && img.crossOrigin.toLowerCase() === 'anonymous') {
                downloadImage(item,false,img);
            } else {
                callback(new Error('Load image (' + url + ') failed'));
            }
        }
        // 设置src开始加载图片
        img.addEventListener('load',loadCallback);
        img.addEventListener('error',errorCallback);
        img.src = url;
    }
}
  • downloadFont

downloadFont的本质也是通过添加HTML标签,通过div、style标签来实现字体的加载。通过item的name、srcs或name、url、type进行加载。

function _loadFont (name,srcs,type){
    // 创建一个类型为text/css的style标签
    var doc = document,fontStyle = document.createElement('style');
    fontStyle.type = 'text/css';
    doc.body.appendChild(fontStyle);

    // 构建并设置fontStyle的textContent属性
    var fontStr = '';
    if (isNaN(name - 0)) {
        fontStr += '@font-face { font-family:' + name + '; src:';
    }
    else {
        fontStr += '@font-face { font-family:\'' + name + '\'; src:';
    }
    if (srcs instanceof Array) {
        for (var i = 0,li = srcs.length; i < li; i++) {
            var src = srcs[i];
            type = Path.extname(src).toLowerCase();
            fontStr += 'url(\'' + srcs[i] + '\') format(\'' + FONT_TYPE[type] + '\')';
            fontStr += (i === li - 1) ? ';' : ',';
        }
    } else {
        type = type.toLowerCase();
        fontStr += 'url(\'' + srcs + '\') format(\'' + FONT_TYPE[type] + '\');';
    }
    fontStyle.textContent += fontStr + '}';

    // 添加一个试用该字体的div
    //<div style="font-family: PressStart;">.</div>
    var preloadDiv = document.createElement('div');
    var _divStyle =  preloadDiv.style;
    _divStyle.fontFamily = name;
    preloadDiv.innerHTML = '.';
    _divStyle.position = 'absolute';
    _divStyle.left = '-100px';
    _divStyle.top = '-100px';
    doc.body.appendChild(preloadDiv);
}

function downloadFont (item,callback) {
    var url = item.url,type = item.type,name = item.name,srcs = item.srcs;
    if (name && srcs) {
        if (srcs.indexOf(url) === -1) {
            srcs.push(url);
        }
        _loadFont(name,srcs);
    } else {
        type = Path.extname(url);
        name = Path.basename(url,type);
        _loadFont(name,url,type);
    }
    if (document.fonts) {
        document.fonts.load('1em ' + name).then(function () {
            callback(null,null);
        },function(err){
            callback(err);
        });
    } else {
        return null;
    }
}
  • downloadAudio

downloadAudio位于audio-downloader.js中,它会根据item的useDom选项决定使用哪种声音下载方式:

function downloadAudio (item,callback) {
    // 浏览器不支持音效
    if (formatSupport.length === 0) {
        return new Error('Audio Downloader: audio not supported on this browser!');
    }
    item.content = item.url;

    // 如果指定了useDom或者不支持WebAudio,会自动帮我们切换成DomAudio
    if (!__audioSupport.WEB_AUDIO || (item.urlParam && item.urlParam['useDom'])) {
        loadDomAudio(item,callback);
    } else {
        loadWebAudio(item,callback);
    }
}

loadWebAudio会使用cc.loader.getXMLHttpRequest下载资源,在onLoad回调中使用sys.__audioSupport.context["decodeAudioData"]()进行解码。
而loadDomAudio则是通过aduio这个HTML标签进行加载和监听。

  • downloadText

文本的下载分2中方式,如果是原生平台,会使用jsb.fileUtils.getStringFromFile从磁盘中直接获取,如果是其他普通,会使用cc.loader.getXMLHttpRequest下载。

在Creator2.x之后,这段判断被移到了engine目录的jsb目录下,Creator直接在构建时使用合适的代码,而不是在函数执行中去判断当前是哪种平台。

if (CC_JSB) {
    module.exports = function (item,callback) {
        var url = item.url;

        var result = jsb.fileUtils.getStringFromFile(url);
        if (typeof result === 'string' && result) {
            return result;
        } else {
            return new Error('Download text failed: ' + url);
        }
    };
} else {
    var urlAppendTimestamp = require('./utils').urlAppendTimestamp;

    module.exports = function (item,callback) {
        var url = item.url;
        url = urlAppendTimestamp(url);

        var xhr = cc.loader.getXMLHttpRequest(),errInfo = 'Load ' + url + ' failed!',navigator = window.navigator;
        xhr.open('GET',true);
        if (/msie/i.test(navigator.userAgent) && !/opera/i.test(navigator.userAgent)) {
            // IE-specific logic here
            xhr.setRequestHeader('Accept-Charset','utf-8');
            xhr.onreadystatechange = function () {
                if(xhr.readyState === 4) {
                    if (xhr.status === 200 || xhr.status === 0) {
                        callback(null,xhr.responseText);
                    } else {
                        callback({status:xhr.status,errorMessage:errInfo});
                    }
                }
            };
        } else {
            if (xhr.overrideMimeType) xhr.overrideMimeType('text\/plain; charset=utf-8');
            xhr.onload = function () {
                if(xhr.readyState === 4) {
                    if (xhr.status === 200 || xhr.status === 0) {
                        callback(null,errorMessage:errInfo});
                    }
                }
            };
            xhr.onerror = function(){
                callback({status:xhr.status,errorMessage:errInfo});
            };
        }
        xhr.send(null);
    };
}
  • downloadUuid

Creator中的资源都会有它的uuid,都会调用该方法进行下载。而uuid资源可能以2种形式存在,第一种是单独的json文件,比如一个prefab或spriteFrame资源,都有自己的json文件。而另一种则是打包资源,所谓的Pack就是将多个json文件合并为一个json文件,把各个json文件中的json对象组合到一个json数组中,从而达到减少IO的作用。downloadUuid方法会使用PackDownloader进行下载,如果下载失败则使用json的下载方式,也就是downloadText。

function downloadUuid (item,callback) {
    var result = PackDownloader.load(item,callback);
    if (result === undefined) {
        return this.extMap['json'](item,callback);
    } else if (!!result) {
        return result;
    }
}

PackDownloader的load方法实现如下,根据uuidToPack中的uuid取出packUuid,如果packUuid不存在,则说明这个uuid没有被打包,直接使用json的方式加载即可。接下来再根据globalUnpackers[packUuid]取出unpacker,调用unpacker.retrieve(uuid)解析出json并返回。

load: function (item,callback) {
        var uuid = item.uuid;
        var packUuid = uuidToPack[uuid];
        if (!packUuid) {
            // 返回undefined以让调用者知道它未被识别。
            // 不返回false,因为改变返回值类型可能会导致jit失败,尽管返回undefined可能有相同的问题
            return;
        }

        // 一个uuid有可能被重复打包到多个json文件中,《从编辑器到运行时》一章会介绍这种情况如何产生
        if (Array.isArray(packUuid)) {
            // 这里会遍历多个Pack,从中选择状态最接近加载完成的Pack(谁先加载完用谁)。
            packUuid = this._selectLoadedPack(packUuid);
        }

        // 取出unpacker,如果加载完成了,从unpacker中取出对应uuid的json对象返回。
        var unpacker = globalUnpackers[packUuid];
        if (unpacker && unpacker.state === PackState.Loaded) {
            var json = unpacker.retrieve(uuid);
            if (json) {
                return json;
            } else {
                return error(uuid,packUuid);
            }
        } else { // 其他情况为未加载完成
            // unpacker为空则创建一个
            if (!unpacker) {
                if (!CC_TEST) {
                    console.log('Create unpacker %s for %s',packUuid,uuid);
                }
                unpacker = globalUnpackers[packUuid] = new JsonUnpacker();
                unpacker.state = PackState.Downloading;
            }
            // 如果正在加载中或未加载,会走_loadNewPack也就是cc.loader.load,但cc.loader中规避了重复加载。
            this._loadNewPack(uuid,callback);
        }
        // 返回null,让调用者知道它正在异步加载
        return null;
    }

接下来我们进一步了解一下PackDownloader这个类做了什么?Pack又是什么?globalUnpackers和packIndices又是什么?

  • PackDownloader
    • initPacks接受packs变量进行初始化,packIndices变量引用了packs,遍历packs来初始化uuidToPack,建立了uuid到pack的映射。
    • _loadNewPack根据packUuid调用cc.AssetLibrary.getLibUrlNoExt(packUuid) + ‘.json‘;获取packUrl,并调用cc.loader.load加载json文件,加载完成后调用 _doLoadNewPack以及callback。
    • _doLoadNewPack根据packUuid从globalUnpackers中取出unpacker,并返回unpacker.retrieve(uuid)

PackDownloader做的事情主要是对Json文件的解析、管理和获取。在某些情况下多个json文件会被打包成一个json文件,如AnimationClip文件,在编辑器制作的时候每个动画都是一个Clip文件(json文件),而在打包之后这些Clip会被合并成一个新的json文件(这样做的目的是节省IO),这就是Pack。

当我们发布项目时Creator自动帮我们进行合并,多个json对象组成一个数组对象,packIndices记录了每个packUuid对应的一组uuid(也就是一个pack文件中合并了哪些文件),每个文件的uuid对应这个json数组对象的下标。packIndices[packUuid]的下标1是该packUuid对应合并后的json数组下标1这个json对象的uuid。

每个Clip都有一个uuid,通过uuidToPack的索引获取这个Clip对应的packUuid,也就是合并Json的uuid,这个uuid会对应一个JsonUnpacker,JsonUnpacker会将合并后的json进行解析并缓存,同时保持一个映射,在这里就是每个Clip的uuid对应的json对象。

// 初始化Packs,这里传入的packs是一个二维数组,首先它是一个uuids的数组,一组uuid被视为一个pack,packs就是一组pack
    // 每个uuids都是一个数组,记录了这个pack中合并的所有uuid。
    initPacks: function (packs) {
        packIndices = packs;
        for (var packUuid in packs) {
            var uuids = packs[packUuid];
            for (var i = 0; i < uuids.length; i++) {
                var uuid = uuids[i];
                // the smallest pack must be at the beginning of the array to download more first
                // 最小的pack必须放在数组的前面,以便下载更多的包。
                var pushFront = uuids.length === 1;
                // map - uuidToPack,key - uuid,value - packUuid (如果已存在该key,value会添加到数组中)
                pushToMap(uuidToPack,uuid,pushFront);
            }
        }
    },// 加载一个新的Pack时会调用该方法,根据packUuid去获取url,并立即下载(ignoreMaxConcurrency为true)
    _loadNewPack: function (uuid,callback) {
        var self = this;
        var packUrl = cc.AssetLibrary.getLibUrlNoExt(packUuid) + '.json';
        cc.loader.load({ url: packUrl,ignoreMaxConcurrency: true },packJson) {
            if (err) {
                cc.errorID(4916,uuid);
                return callback(err);
            }
            var res = self._doLoadNewPack(uuid,packJson);
            if (res) {
                callback(null,res);
            } else {
                callback(error(uuid,packUuid));
            }
        });
    },// 当一个Pack加载完之后,会回调该方法
    _doLoadNewPack: function (uuid,packJson) {
        var unpacker = globalUnpackers[packUuid];
        // double check cache after load
        // 只要unpacker的状态不是PackState.Loaded,进行解析并切换状态
        if (unpacker.state !== PackState.Loaded) {
            unpacker.read(packIndices[packUuid],packJson);
            unpacker.state = PackState.Loaded;
        }
        return unpacker.retrieve(uuid);
    },// 遍历多个packUuid,只要找到第一个状态为PackState.Loaded的unpacker
    // 找不到则找一个最接近PackState.Loaded的unpacker
    _selectLoadedPack: function (packUuids) {
        var existsPackState = PackState.Invalid;
        var existsPackUuid = '';
        for (var i = 0; i < packUuids.length; i++) {
            var packUuid = packUuids[i];
            var unpacker = globalUnpackers[packUuid];
            if (unpacker) {
                var state = unpacker.state;
                if (state === PackState.Loaded) {
                    return packUuid;
                } else if (state > existsPackState) {
                    existsPackState = state;
                    existsPackUuid = packUuid;
                }
            }
        }

        return existsPackState !== PackState.Invalid ? existsPackUuid : packUuids[0];
    },
  • globalUnpackers
    • globalUnpackers根据packUuid为索引,保存着JsonUnpacker对象。
    • JsonUnpacker记录了jsons和state,关键的read方法和retrieve方法的职责是解析json数据以及根据key从jsons中查询信息。
JsonUnpacker.prototype.read = function (indices,data) {
    var jsons = typeof data === 'string' ? JSON.parse(data) : data;
    if (jsons.length !== indices.length) {
        cc.errorID(4915);
    }
    for (var i = 0; i < indices.length; i++) {
        var key = indices[i];
        var json = jsons[i];
        this.jsons[key] = json;
    }
};

JsonUnpacker.prototype.retrieve = function (key) {
    return this.jsons[key] || null;
};

这里传入的data是一个数组json对象,indices是一个uuid数组,read的职责就是将indices[i]作为uuid,对应的jsons[i]作为json对象,记录到this.jsons这个容器中,那么后面的retrieve就可以用uuid来获取对应的json对象了。

  • 关于packIndices
    • 在AssetLibrary的init方法中,调用了PackDownloader.initPacks(options.packedAssets);
    • 项目发布时会生成一个巨大的settings.js文件,该文件内容如下图所示,其中的packedAssets就是我们的packIndices。
    • 例如图中的key 01204b0d7可以在发布后的res/import/01目录中找到01204b0d7.json,这个文件是一个有5个对象的json数组,他们的uuid分别为0、205、207、1、473。

image

01204b0d7.json文件对应的内容在格式化查看工具中打开如下所示,正好是一个拥有5个对象的json数组,第一个对象是Array、后面是4个Object对象。而上图对应的packedAssets下的01204b0d7对象数组为这个json数组的uuid,按下标一一对应。

image

  • 原生Downloader处理

在原生平台下会执行jsb-loader.js下的内容,对于字体、音效、脚本和图片使用新的下载方法。

// 字体使用了empty
function empty (item,callback) {
    return null;
}

// 下载脚本直接使用require即可
function downloadScript (item,callback) {
    require(item.url);
    return null;
}

// 声音不需要下载,声音的加载流程包含了下载
function downloadAudio (item,callback) {
    return item.url;
}

// 图片分3种情况,textureCache中缓存直接使用、远程图片使用jsb.loadRemoteImg、本地图片使用textureCache的addImageAsync方法加载。
function loadImage (item,callback) {
    var url = item.url;

    var cachedTex = cc.textureCache.getTextureForKey(url);
    if (cachedTex) {
        return cachedTex;
    } else if (url.match(jsb.urlRegExp)) {
        jsb.loadRemoteImg(url,function(succeed,tex) {
            if (succeed) {
                tex.url = url;
                callback && callback(null,tex);
            } else {
                callback && callback(new Error('Load image failed: ' + url));
            }
        });
    } else {
        var addImageCallback = function (tex) {
            if (tex instanceof cc.Texture2D) {
                tex.url = url;
                callback && callback(null,tex);
            }
            else {
                callback && callback(new Error('Load image failed: ' + url));
            }
        };
        cc.textureCache._addImageAsync(url,addImageCallback);
    }
}

在项目发布时,会根据发布平台生成最终的执行代码。构建原生平台时Creator1.x会指定engine/jsb目录下的脚本,而Creator2.x指定的是engine/bin目录下的jsb脚本。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


    本文实践自 RayWenderlich、Ali Hafizji 的文章《How To Create Dynamic Textures with CCRenderTexture in Cocos2D 2.X》,文中使用Cocos2D,我在这里使用Cocos2D-x 2.1.4进行学习和移植。在这篇文章,将会学习到如何创建实时纹理、如何用Gimp创建无缝拼接纹
Cocos-code-ide使用入门学习地点:杭州滨江邮箱:appdevzw@163.com微信公众号:HopToad 欢迎转载,转载标注出处:http://blog.csdn.netotbaron/article/details/424343991.  软件准备 下载地址:http://cn.cocos2d-x.org/download 2.  简介2.1         引用C
第一次開始用手游引擎挺激动!!!进入正题。下载资源1:从Cocos2D-x官网上下载,进入网页http://www.cocos2d-x.org/download,点击Cocos2d-x以下的Download  v3.0,保存到自定义的文件夹2:从python官网上下载。进入网页https://www.python.org/downloads/,我当前下载的是3.4.0(当前最新
    Cocos2d-x是一款强大的基于OpenGLES的跨平台游戏开发引擎,易学易用,支持多种智能移动平台。官网地址:http://cocos2d-x.org/当前版本:2.0    有很多的学习资料,在这里我只做为自己的笔记记录下来,错误之处还请指出。在VisualStudio2008平台的编译:1.下载当前稳
1.  来源 QuickV3sample项目中的2048样例游戏,以及最近《最强大脑》娱乐节目。将2048改造成一款挑战玩家对数字记忆的小游戏。邮箱:appdevzw@163.com微信公众号:HopToadAPK下载地址:http://download.csdn.net/detailotbaron/8446223源码下载地址:http://download.csdn.net/
   Cocos2d-x3.x已经支持使用CMake来进行构建了,这里尝试以QtCreatorIDE来进行CMake构建。Cocos2d-x3.X地址:https://github.com/cocos2d/cocos2d-x1.打开QtCreator,菜单栏→"打开文件或项目...",打开cocos2d-x目录下的CMakeLists.txt文件;2.弹出CMake向导,如下图所示:设置
 下载地址:链接:https://pan.baidu.com/s/1IkQsMU6NoERAAQLcCUMcXQ提取码:p1pb下载完成后,解压进入build目录使用vs2013打开工程设置平台工具集,打开设置界面设置: 点击开始编译等待编译结束编译成功在build文件下会出现一个新文件夹Debug.win32,里面就是编译
分享一下我老师大神的人工智能教程吧。零基础!通俗易懂!风趣幽默!还带黄段子!希望你也加入到我们人工智能的队伍中来!http://www.captainbed.net前言上次用象棋演示了cocos2dx的基本用法,但是对cocos2dx并没有作深入的讨论,这次以超级马里奥的源代码为线索,我们一起来学习超级马里奥的实
1. 圆形音量button事实上作者的本意应该是叫做“电位计button”。可是我觉得它和我们的圆形音量button非常像,所以就这么叫它吧~先看效果:好了,不多解释,本篇到此为止。(旁白: 噗。就这样结束了?)啊才怪~我们来看看代码:[cpp] viewplaincopyprint?CCContro
原文链接:http://www.cnblogs.com/physwf/archive/2013/04/26/3043912.html为了进一步深入学习贯彻Cocos2d,我们将自己写一个场景类,但我们不会走的太远,凡是都要循序渐进,哪怕只前进一点点,那也至少是前进了,总比贪多嚼不烂一头雾水的好。在上一节中我们建
2019独角兽企业重金招聘Python工程师标准>>>cocos2d2.0之后加入了一种九宫格的实现,主要作用是用来拉伸图片,这样的好处在于保留图片四个角不变形的同时,对图片中间部分进行拉伸,来满足一些控件的自适应(PS: 比如包括按钮,对话框,最直观的形象就是ios里的短信气泡了),这就要求图
原文链接:http://www.cnblogs.com/linji/p/3599478.html1.环境和工具准备Win7VS2010/2012,至于2008v2版本之后似乎就不支持了。 2.安装pythonv.2.0版本之前是用vs模板创建工程的,到vs2.2之后就改用python创建了。到python官网下载版本2.7.5的,然后
环境:ubuntu14.04adt-bundle-linux-x86_64android-ndk-r9d-linux-x86_64cocos2d-x-3.0正式版apache-ant1.9.3python2.7(ubuntu自带)加入环境变量exportANDROID_SDK_ROOT=/home/yangming/adt-bundle-linux/sdkexportPATH=${PATH}:/$ANDROID_SDK_ROOTools/export
1开发背景游戏程序设计涉及了学科中的各个方面,鉴于目的在于学习与进步,本游戏《FlappyBird》采用了两个不同的开发方式来开发本款游戏,一类直接采用win32底层API来实现,另一类采用当前火热的cocos2d-x游戏引擎来开发本游戏。2需求分析2.1数据分析本项目要开发的是一款游
原文链接:http://www.cnblogs.com/linji/p/3599912.html//纯色色块控件(锚点默认左下角)CCLayerColor*ccc=CCLayerColor::create(ccc4(255,0,0,128),200,100);//渐变色块控件CCLayerGradient*ccc=CCLayerGradient::create(ccc4(255,0,0,
原文链接:http://www.cnblogs.com/linji/p/3599488.html//载入一张图片CCSprite*leftDoor=CCSprite::create("loading/door.png");leftDoor->setAnchorPoint(ccp(1,0.5));//设置锚点为右边中心点leftDoor->setPosition(ccp(240,160));/
为了答谢广大学员对智捷课堂以及关老师的支持,现购买51CTO学院关老师的Cocos2d-x课程之一可以送智捷课堂编写图书一本(专题可以送3本)。一、Cocos2d-x课程列表:1、Cocos2d-x入门与提高视频教程__Part22、Cocos2d-x数据持久化与网络通信__Part33、Cocos2d-x架构设计与性能优化内存优
Spawn让多个action同时执行。Spawn有多种不同的create方法,最终都调用了createWithTwoActions(FiniteTimeAction*action1,FiniteTimeAction*action2)方法。createWithTwoActions调用initWithTwoActions方法:对两个action变量初始化:_one=action1;_two=action2;如果两个a
需要环境:php,luajit.昨天在cygwin上安装php和luajit环境,这真特么是一个坑。建议不要用虚拟环境安装打包环境,否则可能会出现各种莫名问题。折腾了一下午,最终将环境转向linux。其中,luajit的安装脚本已经在quick-cocos2d-x-develop/bin/中,直接luajit_install.sh即可。我的lin
v3.0相对v2.2来说,最引人注意的。应该是对触摸层级的优化。和lambda回调函数的引入(嗯嗯,不枉我改了那么多类名。话说,每次cocos2dx大更新。总要改掉一堆类名函数名)。这些特性应该有不少人研究了,所以今天说点跟图片有关的东西。v3.0在载入图片方面也有了非常大改变,仅仅只是