搭建好cocos2d-js环境后,主要是html5环境,接下来步入入门阶段,许多人都说官网demo是最好的入门资料,今天就剖析官风提供的js-test入口,界面如下:
这上面有个关闭按钮和一个列表,列表列出了demo中所有的功能演示,这个js-tests工程在cocos2d-js的SDK中,以前是在samples目录下,现在最新版的放在tests目录中,目录结构如下:
我们切换到目录js-tests下,在终端运行:cocos run -p web就能打开浏览器查看效果,如果你部署了服务器的话,可以把tests和web目录直接拷贝到服务器目录下,访问对应的目录也能查看效果,注意,要拷贝tests和web目录,因为js-tests中用到了web目录及cpp-tests目录下的资源,如果不确定,把cocos2d-x-3.x.x目录全拷贝吧,也可以做个虚拟目录指向这个目录。
查看效果后,接下来分析主要的几个文件,在此列出你要分析的几个文件,即是需要我们手动修改维护的文件:index.html(首页,入口页),main.js(cocos2d-js的启动与入口),project.json(项目的配置文件),res(资源目录),src(源文件存放目录,一般是js文件,一般有app.js,resource.js)。
1.index.html文件,这里给出源码:
<html>
<head>
<meta charset="utf-8">
<title>Cocos2d-HTML5 Test Casestitle>
<link rel="icon"
type="image/GIF"
href="../cpp-tests/Resources/Images/favicon.ico"/>
head>
<body style="text-align: center;background: #f2f6f8;">
<img style="clear:both;margin-bottom: 20px" src="../cpp-tests/Resources/Images/cocos2dbanner.png"/>
<div>div>
<div style="display:inline-block;width:auto; margin: 0 auto; background: black; position:relative; border:5px solid black; border-radius: 10px; box-shadow: 0 5px 50px #333">
<canvas id="gameCanvas" width="800" height="450">canvas> *(1)*
div>
<script src="../../web/CCBoot.js">script>*(2)*
<script src="main.js">script>*(3)*
body>
html>
在这里注意三个地方,第一个标签canvas,id为gameCanvas,id会在project.json中配置用到。
第二个../../web/CCBoot.js,注意他的路径,此文件不需要我们维护。
第三个main.js,游戏的真正入口函数来了。
2.介绍下project.json项目配置文件,文件太大,截图说明下:
从向往下:第一个是否显示左下角FPS的信息,自己测试。第二个就是index.html中canvas的id 。第三个引擎的目录,注意他的路径,第四个使用到的模块,注意在引擎目录下查找,最后个键头js文件的列表,如果你有自己的js文件,请记得添加。
3.你们在看helloword项目是,看到有resource.js文件,里面定义了用到的资源,但是这里没有这个文件,他把资源定义到了其他文件中,这里定义到src/tests_resources.js中,看下他的截图:
如图,第一个键头指明了js文件存放的路径,注意if判断条件,他是web浏览器才执行的,不是的话情况另当别论,不过本人分析的就是html5的,所以他就进来了,第二个键头指示资源的定义,他使用了相对路径,注意他的res路径指向了tests/cpp-tests/Resources,这个在main.js中指定,到时在说明。这就是资源的定义,代替了我们常见的resource.js文件。
4.进入到main.js,真正的入口,看下他的源码:
if(cc.sys){
var scene3SearchPaths = cc.sys.localStorage.getItem("Scene3SearchPaths");
//html5项目判断不成立,直接跳过,想了解请查看api
if (scene3SearchPaths)
jsb.fileUtils.setSearchPaths(JSON.parse(scene3SearchPaths));
}
cc.game.onStart = function(){
//是否支持retina屏
cc.view.enableRetina(false);
//判断是不是本地化的还是web的,这里判断为否,直接查看else
if (cc.sys.isNative) {
var resolutionPolicy = (cc.sys.os == cc.sys.OS_WP8 || cc.sys.os == cc.sys.OS_WINRT) ? cc.ResolutionPolicy.SHOW_ALL : cc.ResolutionPolicy.FIXED_HEIGHT;
cc.view.setDesignResolutionSize(800, 450, resolutionPolicy);
cc.view.resizeWithBrowserSize(true);
var searchPaths = jsb.fileUtils.getSearchPaths();
searchPaths.push('script');
searchPaths.push('src');
var paths = [
'res/resjs',
'res',
'res/scenetest',
'res/scenetest/ArmatureComponentTest',
'res/scenetest/AttributeComponentTest',
'res/scenetest/BackgroundComponentTest',
'res/scenetest/EffectComponentTest',
'res/scenetest/LoadSceneEdtiorFileTest',
'res/scenetest/ParticleComponentTest',
'res/scenetest/SpriteComponentTest',
'res/scenetest/TmxMapComponentTest',
'res/scenetest/UIComponentTest',
'res/scenetest/TriggerTest'
];
for (var i = 0; i < paths.length; i++) {
searchPaths.push(paths[i]);
}
jsb.fileUtils.setSearchPaths(searchPaths);
}
else
{
//注意这里,他设置了res的路径,即资源目录,在资源文件中定义时访问的是这个目录下文件
cc.loader.resPath = '../cpp-tests/Resources'
}
//预加载资源,g_resources在test_resource.js中定义,加载完成后会调用第二个参数,一个匿名函数
cc.LoaderScene.preload(g_resources, function () {
//判断条件不成立,进入else
if(window.sideIndexBar && typeof sideIndexBar.start === 'function'){
sideIndexBar.start();
}else{
//这里设置场景,添加layer,运行场景,查看TestController类做了什么,他在test-main.js中定义
var scene = new cc.Scene();
scene.addChild(new TestController());
cc.director.runScene(scene);
}
}, this);
};
//运行游戏
cc.game.run();
看代码,运行逻辑注释已经解释了,这是针对浏览器的,其他的情况没有去分析,其他情况也就是设置资源的搜索路径,现在理着他运行的步骤一步一步跟踪他到底做了什么。
5.从main.js中看到他创建了个类TestController添加到主场景中,接下来查看这个类都做了什么,他的路径为:
js-tests/src/tests-main.js。查看可知道他继承了类LayerGradient,这是个支持两种颜色的渐变的层,单颜色请查看LayerColor类。这里定义了一些全局变量和类的属性,在此不一一列举了,请看源文件,要分析这个类做了什么,就看下他的生命周期中相应的回调函数,第一个构造函数(ctor),第二个onEnter(进入函数),看下构造函数的源码,解释放在注释中:
//下面三个属性在类中定义
_itemMenu:null,
_beginPos:0,
isMouseDown:false,
//构造函数
ctor:function() {
//两种渐变的颜色,可修改看效果
this._super(cc.color(0,0,0,255), cc.color(0x46,0x82,0xB4,255));
// globals 这两个为全局属性
director = cc.director;
winSize = director.getWinSize();
// add close menu 关闭按钮参数一,二为两种状态的图标,在资源文件中定义,第三个点击时的回调函数
var closeItem = new cc.MenuItemImage(s_pathClose, s_pathClose, this.onCloseCallback, this);
//设置他的坐标,左下角为原点,向右X轴增大,向上Y轴增大,这个在右上角
closeItem.x = winSize.width - 30;
closeItem.y = winSize.height - 30;
//动画是否自动切换图标
var subItem1 = new cc.MenuItemFont("Automated Test: Off");
subItem1.fontSize = 18;
var subItem2 = new cc.MenuItemFont("Automated Test: On");
subItem2.fontSize = 18;
//自动切换按钮属性设置,回调函数,坐标,是否可见,这里设置不可见,我们看不到效果,请设置可见查看效果
var toggleAutoTestItem = new cc.MenuItemToggle(subItem1, subItem2);
toggleAutoTestItem.setCallback(this.onToggleAutoTest, this);
toggleAutoTestItem.x = winSize.width - toggleAutoTestItem.width / 2 - 10;
toggleAutoTestItem.y = 20;
toggleAutoTestItem.setVisible(false);
if( autoTestEnabled )
toggleAutoTestItem.setSelectedIndex(1);
var menu = new cc.Menu(closeItem, toggleAutoTestItem);//pmenu is just a holder for the close button
menu.x = 0;
menu.y = 0;
// add menu items for tests
this._itemMenu = new cc.Menu();//item menu is where all the label goes, and the one gets scrolled
//d在这里添加主界面中,各个demo的入口,其中testNames是个数组,存放了对象,接下来在分析
for (var i = 0, len = testNames.length; i < len; i++) {
var label = new cc.LabelTTF(testNames[i].title, "Arial", 24);
var menuItem = new cc.MenuItemLabel(label, this.onMenuCallback, this);
this._itemMenu.addChild(menuItem, i + 10000);
menuItem.x = winSize.width / 2;
menuItem.y = (winSize.height - (i + 1) * LINE_SPACE);
// enable disable
if ( !cc.sys.isNative) {
if( 'opengl' in cc.sys.capabilities ){
menuItem.enabled = (testNames[i].platforms & PLATFORM_HTML5) | (testNames[i].platforms & PLATFORM_HTML5_WEBGL);
}else{
menuItem.setEnabled( testNames[i].platforms & PLATFORM_HTML5 );
}
} else {
if (cc.sys.os == cc.sys.OS_ANDROID) {
menuItem.setEnabled( testNames[i].platforms & ( PLATFORM_JSB | PLATFROM_ANDROID ) );
} else if (cc.sys.os == cc.sys.OS_IOS) {
menuItem.setEnabled( testNames[i].platforms & ( PLATFORM_JSB | PLATFROM_IOS) );
} else if (cc.sys.os == cc.sys.OS_OSX) {
menuItem.setEnabled( testNames[i].platforms & ( PLATFORM_JSB | PLATFORM_MAC) );
} else {
menuItem.setEnabled( testNames[i].platforms & PLATFORM_JSB );
}
}
}
this._itemMenu.width = winSize.width;
this._itemMenu.height = (testNames.length + 1) * LINE_SPACE;
this._itemMenu.x = curPos.x;
this._itemMenu.y = curPos.y;
this.addChild(this._itemMenu);
this.addChild(menu, 1);
// 'browser' can use touches or mouse. 事件的处理,即滚动处理
// The benefit of using 'touches' in a browser, is that it works both with mouse events or touches events
if ('touches' in cc.sys.capabilities)
cc.eventManager.addListener({
event: cc.EventListener.TOUCH_ALL_AT_ONCE,
onTouchesMoved: function (touches, event) {
var target = event.getCurrentTarget();
var delta = touches[0].getDelta();
target.moveMenu(delta);
return true;
}
}, this);
else if ('mouse' in cc.sys.capabilities) {
cc.eventManager.addListener({
event: cc.EventListener.MOUSE,
onMouseMove: function (event) {
if(event.getButton() == cc.EventMouse.BUTTON_LEFT)
event.getCurrentTarget().moveMenu(event.getDelta());
},
onMouseScroll: function (event) {
var delta = cc.sys.isNative ? event.getScrollY() * 6 : -event.getScrollY();
event.getCurrentTarget().moveMenu({y : delta});
return true;
}
}, this);
}
}
代码注释说最大部分功能,其他的查看源码,构造函数完成后,会调用onEnter函数,下面给出他的源码:
onEnter:function(){
//调用父类方法
this._super();
//设置主界面菜单y坐标
this._itemMenu.y = TestController.YOffset;
},
接下来查看testNames数组的结构:
var testNames = [
{
title:"ActionManager Test",
platforms: PLATFORM_ALL,
linksrc:"src/ActionManagerTest/ActionManagerTest.js",
testScene:function () {
return new ActionManagerTestScene();
}
},
{
title:"Actions Test",
platforms: PLATFORM_ALL,
linksrc:"src/ActionsTest/ActionsTest.js",
testScene:function () {
return new ActionsTestScene();
}
}
到了这一步,主界面已经显示出来了,现在就是最后一步,当点击其中一项是,他又干了什么,即回调函数onMenuCallback,查看源码:
onMenuCallback:function (sender) {
TestController.YOffset = this._itemMenu.y;
var idx = sender.getLocalZOrder() - 10000;
// get the userdata, it's the index of the menu item clicked
// create the test scene and run it
autoTestCurrentTestName = testNames[idx].title;
var testCase = testNames[idx];
var res = testCase.resource || [];
cc.LoaderScene.preload(res, function () {
var scene = testCase.testScene();
console.log(scene);
if (scene) {
scene.runThisTest();
}
}, this);
}
到了这一步,接下来要分析的是scene.runThisTest()函数做了什么,怎么切换场景了,自己选择感兴趣地模块分析吧。GOOD LUCK!!!!!1