环境:
win7 64位
Cocos2d-JS v3.1
Cocos Code IDE v1.0.0.Final
自带的跟随函数cc.follow,跟随的时候总是以目标为中心位置,而且在非移动范围边缘进行移动时,目标只要稍微进行移动,也会触发跟随动作。
本文没有在web上进行测试、改善
本文以X轴为例,目标移动时超出某个范围才进行跟随动作,如图:
本文只是经验分享,Y轴的跟随本文没有实现,而且响应的事件为键盘上的左右方向。另外,程序写的比较随意,显得略搓.......大家别在意....
另外这里的视窗指这个hellowWorldLayer,也就是这个helloWorld层,是一个层,不是opengl的视口,写的时候没注意让大家混乱了不好意思。
(其实也就是层的移动跟随,本文的视窗不是指opengl的视口,让层进行移动来达到看起来是视窗跟随的效果)
最终效果如下(暂时没有黑线):
上传的时候不知道为何变成JPG格式了= =那就传到相册空间好了,这里放一个链接吧...:
http://my.csdn.net/my/album/detail/1795641
背景图片用的是Tiled制作的地图,本文最后会提供资源,瓦片地图的大小为:
制作该地图的过程就省略了,网上好多教程。
正文:
1.新建一个工程,本文的工程为横向,分辨率为960*640,不过显示的是main.js里800*450的默认分辨率。把app.js里用不上的删掉,剩下这些:
var HelloWorldLayer = cc.Layer.extend({ ctor:function () { this._super(); var size = cc.winSize; return true; } }); var HelloWorldScene = cc.Scene.extend({ onEnter:function () { this._super(); var layer = new HelloWorldLayer(); this.addChild(layer); } });
2.把.tmx地图文件和对应的.png文件放到res文件夹下。 接着修改tmx文件引用的图片路径为相对路径,改路径这里介绍两种方法。
然后在main.js里面添加res路径到文件搜索,添加后整个文件代码为:
cc.game.onStart = function(){ cc.view.adjustViewPort(true); cc.view.setDesignResolutionSize(800,450,cc.ResolutionPolicy.SHOW_ALL); cc.view.resizeWithBrowserSize(true); var searchPaths = jsb.fileUtils.getSearchPaths(); var paths = [ 'res' ]; for (var i = 0; i < paths.length; i++) { searchPaths.push(paths[i]); } jsb.fileUtils.setSearchPaths(searchPaths); //load resources cc.LoaderScene.preload(g_resources,function () { cc.director.runScene(new HelloWorldScene()); },this); }; cc.game.run();
方法二:直接在路径写上res/ :
<image source="res/tmw_desert_spacing.png"
3.加载地图,在app.js的HelloWorldLayer构造函数ctor里添加如下代码:
var tiled = new cc.TMXTiledMap("res/test1111.tmx");//直接加载路径下tmx文件 this.addChild(tiled);
4.添加目标,这里用helloworld图片,先在HelloWorldLayer里面添加一个变量:
hero:null,
然后在第三步后面继续添加:
this.hero = new cc.Sprite("res/HelloWorld.png"); this.hero.scale = 0.2;//因为原图片太大,于是缩小为原大小的20% this.hero.setPosition(size.width * 0.5,size.height * 0.3); this.addChild(this.hero,1);
5.添加键盘事件和目标左右移动:
(关于更多的事件,请参考这个:http://www.cocos2d-x.org/docs/manual/framework/html5/v3/eventManager/en,或者其他demo、资料。)
在HelloWorldLayer里添加以下变量,用作判断左移或者右移、目标缩放后的宽度:
isLeft:null,isRight:null,heroWidth:null,
再添一个目标移动速度:(好吧我表示直接初始化更加省事了)
heroSpeed:5,
在ctor里初始化:
this.isLeft = false; this.isRight = false;
注意在创建hero后才赋值,经过测试,在缩放后获取的宽度为不缩放时大小,所以要乘以0.2:
this.heroWidth = this.hero._getWidth() * 0.2;在第四步后添加事件监听和处理:
cc.eventManager.addListener({ event: cc.EventListener.KEYBOARD,//事件为键盘按键 onKeyPressed: function(keyCode,event){ var helloWorldLayer = event.getCurrentTarget();//获取当前操作的作用域,与最后一行的那个参数有关,this则为该HelloWorldLayer。 cc.log("Key " + keyCode.toString() + " was pressed!");//测试出按下的键的编号,额,,,没去找按键对应编号的表 if (keyCode == 23) {//左方向 cc.log("left"); helloWorldLayer.isLeft = true; helloWorldLayer.isRight = false; } if (keyCode == 24) {//右方向 cc.log("right"); helloWorldLayer.isRight = true; helloWorldLayer.isLeft = false; } },onKeyReleased: function(keyCode,event){ //var label = event.getCurrentTarget(); var helloWorldLayer = event.getCurrentTarget(); if (keyCode == 23) helloWorldLayer.isLeft = false; if (keyCode == 24) helloWorldLayer.isRight = false; cc.log("Key " + keyCode.toString() + " was released!"); } },this);
这里特别说明一下:为了在按下一个方向时,按下另一个方向,目标会立刻转向运动,所以在键盘按下时除了要将需要前进的方向值为true,另一个方向同时置为false,实现的方法有好多种,大家不要在意本文的写法......
接着开启update,在update里面更新目标的位置。在后面继续添加:
this.scheduleUpdate();然后再在 HelloWorldLayer里重写update方法(注意在ctor的最后加逗号,):
update : function(dt) { var pos = this.hero.getPositionX(); if (this.isLeft && !this.isRight) { if ((pos - this.heroWidth *0.5) > 0 ) //超出左边移动边界就不让目标左移 this.hero.setPositionX(pos - this.heroSpeed); } if (this.isRight && !this.isLeft) { if ( (this.mapLength ) > (pos + this.heroWidth)) //超出右边界就不让目标右移 this.hero.setPositionX(pos + this.heroSpeed); } },
如无意外目标可以左右移动了。
注意,按键事件和update在每一帧的处理顺序:先处理按键,再到update。
5.5这里说一下cc.Follow
用new创建的话,控制台会报这样的问题:jsb_create_apis.js:437:ReferenceError: ret is not defined
改为.ctreate创建就好了
接着不想做自定义视窗跟随函数的同学,直接添加以下代码即可完事:
var followAction = cc.Follow.create(this.hero,cc.rect(0,3800,size.height)); this.runAction(followAction);
若有黑线问题第七步有讲
6.添加自定义的视窗跟随函数(也就是层的移动跟随,本文的视窗不是指opengl的视口,让层进行移动来达到看起来是视窗跟随的效果):
先交代一下cc.Follow,这个动作创建的时候最好用.create方法,用new的话
先在HelloWorldLayer里添加一个变量,用来表示视窗移动的状态,本人比较懒,用一个整形表示,0为不动,1为左移,2为右移,理论上用枚举类型会比较符合规范= =...
cameraStatue:null,
再加个变量保存地图的宽度
mapLength:null,
接着在ctor初始化为0:
this.cameraStatue = 0;
在创建tiled(地图)后保存他们的宽度
this.mapLength = tiled.getContentSize().width;然后在 HelloWorldLayer里添加自定义的视窗跟随函数:
setCamera : function(targetPositionX,offsetLeft,offsetRight,windowWidthHalf,tiledW) { var cameraX = this.getPositionX();//获取视窗的X坐标 var isOut = Math.abs(targetPositionX - (-cameraX));//得出视窗与目标X轴上的距离 var ofx = windowWidthHalf - 105;//实际上减去的值为80+25,下面介绍如何得出 if (targetPositionX > (ofx) && (targetPositionX < (tiledW - windowWidthHalf +25))) {//视窗移动范围判定 if (isOut < offsetLeft) {//视窗是否移动判定 this.cameraStatue = 1; } if (isOut >= offsetLeft && isOut <= offsetRight) {//视窗是否移动判定 this.cameraStatue = 0; } if (isOut > offsetRight) { this.cameraStatue = 2; } if (this.cameraStatue == 1) { this.setPositionX(cameraX + this.cameraSpeed); } if (this.cameraStatue == 2) { this.setPositionX(cameraX - this.cameraSpeed); } } }接下来讲解一下这个函数(锚点默认):
参数targetPositionX为需要跟随目标的X坐标,
参数offsetLeft为左边移动边界,也就是目标和视窗X轴上的相对位置,
参数windowWidthHalf为视窗宽度的一半,
参数tiledW为地图的宽度。
以下所有的分析都是以锚点为默认的情况下(Layer默认锚点是(0,0),里面的node默认(0.5,0.5)),而且main.js里显示的画布为:800*450,所以cc.winSize的宽度为800。(大家可以用cc.log打印cc.winSize.width看看)
相机起始X坐标:0,
目标起始X坐标400,(在屏幕宽度(800)一半(400),恩,应该是这样算的)
var isOut = Math.abs(targetPositionX - (-cameraX));这句的作用是取得跟随的目标和视窗X轴上的差值,这里视窗,也就是cameraX值取反,是因为视窗往右移,X轴的坐标是越来越小的,也就是和目标的方向相反,X轴的原点一致,大家可以自己输出视窗的X轴坐标观察结果。这里把最后的值取绝对值,在本文中其实加不加绝对值也没问题,因为目标都是在X大于0的时候进行移动,取绝对值是为了应对在目标移动到X轴负坐标的时候,但是本文没有这种情况= =...
var ofx = windowWidthHalf - 105;
这句等号右边完整的表达式是:windowWidthHalf - 80 -25,减去80是为了补偿图片一半的宽度,因为锚点为目标中点,所以要补回左边边缘到中点的长度。然后这里的25为人工调试测出的结果(猜测跟分辨率从960*640的3:2变到800*450的16:9有关),不然会出现视窗移动后再移回来,回不到视窗原点。
(上传后不知为何变成JPG格式= =那就传到相册空间,放个链接吧)
http://my.csdn.net/my/album/detail/1795643
if (targetPositionX > (ofx) && (targetPositionX < (tiledW -windowWidthHalf +25))) {
再次强调目标右移的X坐标为增量,而视窗右移为减量。大家可以自己输出看看结果
这句为视窗可以移动的范围,由于判断是根据目标的X轴,所以向右为正。视窗不能移到地图外面,所以左边界不是0,而是视窗宽度的一半,加上目标锚点和测试时出现的偏移影响,所以左边界为上一句表达式所示;右边界同理。
之后就是判断目标和视窗的相对距离有没有超出预设的左右值,由于视窗X轴向右为负,所以向左移动时加上移动速度,向右移动时减去移动速度。
this.setCamera(pos,300,500,cc.winSize.width * 0.5,this.mapLength);
7.去除移动时出现的黑线
上面的动态图最后会出现这样的黑线:
在本博客的另一篇文章讲解了如何修复黑线的问题:http://www.jb51.cc/article/p-ackiqksi-qm.html
8.源码和资源
main.js:
cc.game.onStart = function(){ cc.view.adjustViewPort(true); cc.view.setDesignResolutionSize(800,this); }; cc.game.run();
app.js:
var HelloWorldLayer = cc.Layer.extend({ isLeft:null,hero:null,cameraStatue:null,mapLength:null,heroSpeed:20,cameraSpeed:20,ctor:function () { this._super(); var size = cc.winSize; this.isLeft = false; this.isRight = false; this.cameraStatue = 0; var tiled = new cc.TMXTiledMap("res/test1111.tmx"); this.addChild(tiled); this.hero = new cc.Sprite("res/HelloWorld.png"); this.hero.scale = 0.2; this.hero.setPosition(size.width * 0.5,1); this.mapLength = tiled.getContentSize().width; this.heroWidth = this.hero._getWidth() * 0.2; cc.log(this.hero._getWidth()*0.2*0.5); cc.eventManager.addListener({ event: cc.EventListener.KEYBOARD,onKeyPressed: function(keyCode,event){ var helloWorldLayer = event.getCurrentTarget(); var pos = helloWorldLayer.hero.getPositionX(); cc.log("Key " + keyCode.toString() + " was pressed!"); if (keyCode == 23) { cc.log("left"); helloWorldLayer.isLeft = true; helloWorldLayer.isRight = false; } if (keyCode == 24) { cc.log("right"); helloWorldLayer.isRight = true; helloWorldLayer.isLeft = false; } },this); this.scheduleUpdate(); return true; },update : function(dt) { var pos = this.hero.getPositionX(); if (this.isLeft && !this.isRight) { if ((pos - this.heroWidth *0.5) > 0 ) this.hero.setPositionX(pos - this.heroSpeed); } if (this.isRight && !this.isLeft) { if ( (this.mapLength ) > (pos + this.heroWidth)) this.hero.setPositionX(pos + this.heroSpeed); } this.setCamera(pos,this.mapLength); },setCamera : function(targetPositionX,tiledW) { var cameraX = this.getPositionX(); var isOut = Math.abs(targetPositionX - (-cameraX)); var ofx = windowWidthHalf - 105; if (targetPositionX > (ofx) && (targetPositionX < (tiledW -windowWidthHalf +25))) { if (isOut < offsetLeft) { this.cameraStatue = 1; } if (isOut >= offsetLeft && isOut <= offsetRight) { this.cameraStatue = 0; } if (isOut > offsetRight) { this.cameraStatue = 2; } if (this.cameraStatue == 1) { this.setPositionX(cameraX + this.cameraSpeed); } if (this.cameraStatue == 2) { this.setPositionX(cameraX - this.cameraSpeed); } } } }); var HelloWorldScene = cc.Scene.extend({ onEnter:function () { this._super(); var layer = new HelloWorldLayer(); this.addChild(layer); } });
用到的tmx和tmx对应的png文件: http://download.csdn.net/detail/et_sandy/8164981