Cocos2d-JS 实现X轴自定义视窗跟随

环境:

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, 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轴上的相对位置,

参数offsetRight为右边移动边界,也就是目标和视窗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轴向右为负,所以向左移动时加上移动速度,向右移动时减去移动速度。


最后在update里的最后调用这个函数:

this.setCamera(pos, 300, 500, cc.winSize.width * 0.5, this.mapLength);



7.去除移动时出现的黑线

上面的动态图最后会出现这样的黑线:



在本博客的另一篇文章讲解了如何修复黑线的问题:http://blog.csdn.net/et_sandy/article/details/41446099



8.源码和资源

main.js:

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();

app.js:

var HelloWorldLayer = cc.Layer.extend({
    isLeft:null,
    isRight:null,
    hero:null,
    cameraStatue:null,
    mapLength:null,
    heroWidth: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, size.height * 0.3);
        this.addChild(this.hero, 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;
                }
            },
            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);
        
        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, 300, 500, cc.winSize.width * 0.5, this.mapLength);
    },
    setCamera : function(targetPositionX, offsetLeft, offsetRight, windowWidthHalf,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



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值