zoukankan      html  css  js  c++  java
  • 忍者无敌-实例解说Cocos2d-x瓦片地图

    实例比較简单,如图所看到的,地图上有一个忍者精灵,玩家点击他周围的上、下、左、右,他能够向这个方向行走。

    当他遇到障碍物后是无法穿越的,障碍物是除了草地以为部分,包含了:树、山、河流等。




    忍者实例地图(TODO用这个精灵替换图中的



    设计地图
    我们採用David Gervais提供开源免费瓦片集。下载的文件dg_grounds32.gif,gif文件格式会有一定的问题,我们须要转换为.jpg或.png文件。本实例中我是使用PhotoShop转换为dg_grounds32.jpg。
    David Gervais提供的瓦片集中的瓦片是32 x 32像素,我们创建的地图大小是32 x 32瓦片。

    我们先为地图加入普通层和对象层,普通层依照上图设计。对象层中加入几个矩形区域对象,这里不再赘述。这个阶段设计完毕的结果如图所看到的。

    保存文件名称为MiddleMap.tmx,保存文件夹Resourcesmap。


    设计地图


    程序中载入地图
    地图设计完毕我们就能够在程序中载入地图了。以下我们再看看详细的程序代码,首先看一下HelloWorldScene.h文件,它的代码例如以下: 
    #ifndef __HELLOWORLD_SCENE_H__
    #define __HELLOWORLD_SCENE_H__
    
    
    #include "cocos2d.h"
    
    
    class HelloWorld : public cocos2d::Layer
    {
    	cocos2d::TMXTiledMap* _tileMap;										①
         cocos2d::Sprite *_player; 											②
    public:
        static cocos2d::Scene* createScene();
    
    
        virtual bool init(); 
    
    
        CREATE_FUNC(HelloWorld);
    };
    
    
    #endif // __HELLOWORLD_SCENE_H__


    上述代码第①行代码是定义成员变量地图成员_tileMap。。第②行代码是定义精灵成员变量_player。
    HelloWorldScene的实现代码HelloWorldScene.ccp文件,它的HelloWorld::init()代码例如以下: 
    bool HelloWorld::init()
    {
    	if ( !Layer::init() )
    	{
    		return false;
    	}
    
    
    	Size visibleSize = Director::getInstance()->getVisibleSize();
    	Point origin = Director::getInstance()->getVisibleOrigin();
    
    
    	_tileMap = TMXTiledMap::create("map/MiddleMap.tmx");							①
    	addChild(tileMap,0,100);												②
    
    
    	TMXObjectGroup* group = _tileMap ->getObjectGroup("objects");					③
    	ValueMap spawnPoint = group->getObject("ninja");								④
    
    
    	float x = spawnPoint["x"].asFloat();										⑤
    	float y = spawnPoint["y"].asFloat();										⑥
    
    
    	_player = Sprite::create("ninja.png");										⑦
    	_player ->setPosition(Point(x,y));											⑧
    	addChild(_player, 2,200);
    
    
    	return true;
    }


    上述第①代码是创建TMXTiledMap对象。地图文件是MiddleMap.tmx。map是资源文件夹Resources下的子文件夹。

    TMXTiledMap对象也是Node对象,须要通过第②行代码加入到当前场景中。
    第③行代码是通过对象层名objects获得层中对象组集合。第④行代码是从对象组中,通过对象名获得ninja对象信息,它的返回值类型是ValueMap,ValueMap是一种“键-值”对结构。第⑤行代码float x = spawnPoint["x"].asFloat()中的spawnPoint["x"]就是从依照x键取出它的值,即x轴坐标。spawnPoint["x"]的返回值是Value类型,还须要使用asFloat()函数转换为主要的int类型。

    相似地,第⑥行代码是获得y轴坐标。
    第⑦行代码是创建精灵_player,第⑧行代码是设置精灵位置。这个位置是从对象层中ninja对象信息获取的。




    载入地图(TODO又一次截取,或者换精灵)


    移动精灵
    移动精灵是通过触摸事件实现移动的,须要在层中进行事件处理,我们须要在层中重写例如以下函数:
    bool onTouchBegan(Touch * touch, Event* unused_event)
    void onTouchEnded(Touch * touch,Event* unused_event)
    void onTouchMoved(Touch * touch,Event* unused_event)


    以下我们再看看详细的程序代码,首先看一下HelloWorldScene.h文件,它的代码例如以下: 
    #ifndef __HELLOWORLD_SCENE_H__
    #define __HELLOWORLD_SCENE_H__
    
    
    #include "cocos2d.h"
    
    
    class HelloWorld : public cocos2d::Layer
    {
    	cocos2d::TMXTiledMap* _tileMap;										
         cocos2d::Sprite *_player; 											
    public:
        static cocos2d::Scene* createScene();
    
    
        virtual bool init(); 
    
    
    	virtual bool onTouchBegan(cocos2d::Touch* touch, cocos2d::Event* event); 			①
        virtual void onTouchMoved(cocos2d::Touch *touch, cocos2d::Event *event);
        virtual void onTouchEnded(cocos2d::Touch *touch, cocos2d::Event *event); 			②
    
    
        CREATE_FUNC(HelloWorld);
    };
    
    
    #endif // __HELLOWORLD_SCENE_H__


    上述代码第①~②行代码是声明触摸事件函数。HelloWorldScene的实现代码HelloWorldScene.ccp文件。它的HelloWorld::init()代码例如以下:
    bool HelloWorld::init()
    {
    	… …
    	setTouchEnabled(true);
        //设置为单点触摸
       setTouchMode(Touch::DispatchMode::ONE_BY_ONE);
    
    
    	return true;
    }


    上述代码setTouchEnabled(true)是使层開始触摸事件支持。代码setTouchMode(Touch::DispatchMode::ONE_BY_ONE)是设置触摸模式为单点触摸。
    HelloWorldScene.ccp文件的触摸事件函数代码例如以下:
    bool HelloWorld::onTouchBegan(Touch* touch, Event* event)
    {
        log("onTouchBegan");
        return true;
    }
    
    
    void HelloWorld::onTouchMoved(Touch *touch, Event *event)
    {
        log("onTouchMoved");
    }
    
    
    void HelloWorld::onTouchEnded(Touch *touch, Event *event)
    {
        log("onTouchEnded");
    		
    	Point touchLocation = touch->getLocation();    								①
    
    
        Point playerPos = _player->getPosition();									②
        Point diff = touchLocation - playerPos;									③
        
        if (abs(diff.x) > abs(diff.y)) {											④
            if (diff.x > 0) {													⑤
                playerPos.x += _tileMap->getTileSize().width;
                _player->runAction(FlipX::create(false));								⑥
            } else {
                playerPos.x -= _tileMap->getTileSize().width;
                _player->runAction(FlipX::create(true));								⑦
            }
        } else {
            if (diff.y > 0) {													⑧
                playerPos.y += _tileMap->getTileSize().height;
            } else {
                playerPos.y -= _tileMap->getTileSize().height;
            }
        }
    	_player->setPosition(playerPos);											⑨
    }


    上述第①代码touch->getLocation()是获得在Open GL坐标。Open GL坐标的坐标原点是左下角,touch对象封装了触摸点对象。第②行代码_player->getPosition()是获得精灵的位置。
    第③行代码是获得触摸点与精灵位置之差。第④行代码是比較一下触摸点与精灵位置之差,是y轴之差大还是x轴之差大,那个轴之差大就沿着那个轴移动,(abs(diff.x) > abs(diff.y))情况是x轴之差大。否则是y轴之差大。

    第⑤行代码,diff.x > 0情况是沿着x轴正方向移动。否则情况是沿着x轴负方向移动。第⑥行代码_player->runAction(FlipX::create(false))是把精灵翻转回原始状态。

    第⑦行代码_player->runAction(FlipX::create(true))是把精灵是沿着y轴水平翻转。
    第⑧行代码是沿着y轴移动。diff.y > 0是沿着y轴正方向移动。否则是沿着y轴负方向移动。


    第⑨行代码是又一次设置精灵坐标。


    检測碰撞
    到眼下为止我们游戏中的精灵,能够穿越不论什么障碍物。为了能够检測到精灵是否碰撞到障碍物,我们须要再加入一个普通层(collidable),它的目的不是现实地图,而是检測碰撞。

    我们在检測碰撞层中使用瓦片覆盖background层中的障碍物之上,如图所看到的。


    检測碰撞层
    检測碰撞层中的瓦片集能够是不论什么的满足格式要求的图片文件。

    在本例中我们使用一个32 x 32像素单色jpg图片文件collidable_tiles. jpg,它的大小与瓦片大小一样,也就是说这个瓦片集中仅仅有一个瓦片。导入这个瓦片集到地图后。我们须要为瓦片加入一个自己定义属性,瓦片本身也有一些属性。比如:坐标属性x和y。
    我们要加入的属性名为“Collidable”,属性值为“true”。

    加入过程如图所看到的。首先,选择collidable_tiles瓦片集中的要设置属性的瓦片。然后,点击属性视图中左下角“+”button。加入自己定义属性,这时候会弹出一个对话框,我们在对话框中输入自己定义属性名“Collidable”。点击确定button。这时候回到属性视图,Collidable在属性后面是能够输入内容的,这里我们输入“true”。


    加入检測碰撞属性
    地图改动完毕后,我们还要改动代码。

    首选在头文件HelloWorldScene.h中加入一个成员变量和两个函数的声明。

    #ifndef __HELLOWORLD_SCENE_H__
    #define __HELLOWORLD_SCENE_H__
    
    
    #include "cocos2d.h"
    #include "SimpleAudioEngine.h"
    
    
    
    
    class HelloWorld : public cocos2d::Layer
    {
    	cocos2d::TMXTiledMap* _tileMap;
    	cocos2d::TMXLayer* _collidable;											①
       cocos2d::Sprite *_player;
    public:
        static cocos2d::Scene* createScene();
        virtual bool init(); 
    
    
    	virtual bool onTouchBegan(cocos2d::Touch* touch, cocos2d::Event* event);
        virtual void onTouchMoved(cocos2d::Touch *touch, cocos2d::Event *event);
        virtual void onTouchEnded(cocos2d::Touch *touch, cocos2d::Event *event);
    	
    	void setPlayerPosition(cocos2d::Point position);								②
    cocos2d::Point tileCoordFromPosition(cocos2d::Point position);					③
    
    
        CREATE_FUNC(HelloWorld);
    };
    
    
    #endif // __HELLOWORLD_SCENE_H__


    上述代码第①行是声明一个TMXLayer类型的成员变量,它是用来保存地图碰撞层对象。第②行代码setPlayerPosition函数是又一次设置精灵的位置,在这个函数中能够检測精灵是否与障碍物碰撞。第③行代码tileCoordFromPosition函数是把像素坐标点转换为地图瓦片坐标点。
    改动HelloWorldScene.cpp中的HelloWorld::init()代码例如以下:
    bool HelloWorld::init()
    {
    	if ( !Layer::init() )
    	{
    		return false;
    	}
    
    
    	Size visibleSize = Director::getInstance()->getVisibleSize();
    	Point origin = Director::getInstance()->getVisibleOrigin();
    
    
    	_tileMap = TMXTiledMap::create("map/MiddleMap.tmx");
    	addChild(_tileMap,0,100);
    
    
    	TMXObjectGroup* group = _tileMap->getObjectGroup("objects");
    	ValueMap spawnPoint = group->getObject("ninja");
    
    
    	float x = spawnPoint["x"].asFloat();
    	float y = spawnPoint["y"].asFloat();
    
    
    	_player = Sprite::create("ninja.png");
    	_player->setPosition(Point(x,y));
    	addChild(_player, 2, 200);
    
    
    	_collidable = _tileMap->getLayer("collidable");									①
       _collidable->setVisible(false);											②
    
    
    	setTouchEnabled(true);
    	//设置为单点触摸
    	setTouchMode(Touch::DispatchMode::ONE_BY_ONE);
    
    
    	return true;
    }


    我们须要在HelloWorld::init()函数中创建并初始化碰撞层。第①行代码是_collidable = _tileMap->getLayer("collidable")是通过层名字collidable创建层,第②行代码_collidable->setVisible(false)是设置层隐藏,我们要么在这里隐藏的,要么在地图编辑的时候,将该层透明,如图所看到的,在层视图中选择层。然后通过滑动上面的透明度滑块来改变层的透明度,在本例中是须要将透明度设置为0,那么_collidable->setVisible(false)语句就不再须要了。
    注意  在地图编辑器中。设置层的透明度为0与设置层隐藏。在地图上看起来一样,可是有着本质的差别。设置层隐藏是无法通过_collidable = _tileMap->getLayer("collidable")语句訪问的。




    设置层透明度


    我们在前面也介绍过。collidable层不是用来显示地图内容的,而是用来检測碰撞的。改动HelloWorldScene.cpp中的
    HelloWorld::onTouchEnded代码例如以下:
    void HelloWorld::onTouchEnded(Touch *touch, Event *event)
    {
    	log("onTouchEnded");
    
    
    	//获得在OpenGL坐标
    	Point touchLocation = touch->getLocation();		
    
    
    	Point playerPos = _player->getPosition();	
    	Point diff = touchLocation - playerPos;	
    
    
    	if (abs(diff.x) > abs(diff.y)) {		
    		if (diff.x > 0) {
    			playerPos.x += _tileMap->getTileSize().width;
    			_player->runAction(FlipX::create(false));
    		} else {
    			playerPos.x -= _tileMap->getTileSize().width;
    			_player->runAction(FlipX::create(true));
    		}
    	} else {
    		if (diff.y > 0) {
    			playerPos.y += _tileMap->getTileSize().height;
    		} else {
    			playerPos.y -= _tileMap->getTileSize().height;
    		}
    	}
    	this->setPlayerPosition(playerPos);										①
    }


    HelloWorld::onTouchEnded有一些变化。第①行代码this->setPlayerPosition(playerPos)替换了_player->setPosition(playerPos)。setPlayerPosition是我们自定的函数。这个函数的作用是移动精灵和检測碰撞。
    setPlayerPosition代码例如以下: 
    void HelloWorld::setPlayerPosition(Point position)								
    {
    	//从像素点坐标转化为瓦片坐标
    	Point tileCoord =  this->tileCoordFromPosition(position);							①
    	//获得瓦片的GID
    	int tileGid = _collidable->getTileGIDAt(tileCoord);								②
    
    
    	if (tileGid > 0) {														③
    		Value prop = _tileMap->getPropertiesForGID(tileGid);							④
    		ValueMap propValueMap = prop.asValueMap();								⑤
    
    
    		std::string collision = propValueMap["Collidable"].asString();						⑥
    		
    		if (collision == "true") { //碰撞检測成功									⑦
    			CocosDenshion::SimpleAudioEngine::getInstance()->playEffect("empty.wav");		⑧
    			return ;
    		}
    	}
    	_player->setPosition(position);
    }


    上述代码第①行this->tileCoordFromPosition(position)是调用函数,实现从像素点坐标转化为瓦片坐标。

    第②行代码_collidable->getTileGIDAt(tileCoord)是通过瓦片坐标获得GID值。
    第③行代码tileGid > 0能够推断瓦片是否存在,tileGid == 0是瓦片不存在情况。第④行代码_tileMap->getPropertiesForGID(tileGid)是通过地图对象的getPropertiesForGID返回,它的返回值是Value类型。


    因为Value类型能够代表非常多类型。因此第⑤行代码prop.asValueMap()是将Value类型转换成为ValueMap。ValueMap类是“键-值”对。第⑥行代码propValueMap["Collidable"].asString()是将propValueMap变量中的Collidable属性取出来。asString()函数能够将Value类型转换成为std::string类型。

    第⑦行代码collision == "true"是碰撞检測成功情况。第⑧行代码是碰撞检測成功情况下处理。在本例中我们是播放一下音效。


    tileCoordFromPosition代码例如以下: 

    Point HelloWorld::tileCoordFromPosition(Point pos) 								
    {
    	int x = pos.x / _tileMap->getTileSize().width; 									①
    	int y = ((_tileMap->getMapSize().height * _tileMap->getTileSize().height) - pos.y) /
    													 _tileMap->getTileSize().height; 		②
    	return Point(x,y);
    }


    在该函数中第①行代码pos.x / _tileMap->getTileSize().width是获得x轴瓦片坐标(单位是瓦片数)。pos.x是触摸点x轴坐标(单位是像素),_tileMap->getTileSize().width是每一个瓦片的宽度,单位是像素。

    代码第②行是获得y轴瓦片坐标(单位是瓦片数)。这个计算有点麻烦。瓦片坐标的原点在左上角,而触摸点使用的坐标是Open GL坐标。坐标原点在左下角。表达式(_tileMap->getMapSize().height * _tileMap->getTileSize().height) - pos.y)是反转坐标轴,结果除以每一个瓦片的高度_tileMap->getTileSize().height,就得到y轴瓦片坐标了。


    滚动地图
    因为地图比屏幕要大,当我们移动精灵到屏幕的边缘时候,那些处于屏幕之外的地图部分,应该滚动到屏幕之内。

    这些须要我们又一次设置视点(屏幕的中心点)。使得精灵一直处于屏幕的中心。

    可是精灵太靠近地图的边界时候。他有可能不在屏幕的中心。

    精灵与地图的边界距离的规定是,左右边界距离不小于屏幕宽度的一半,否则会出现图所看到的的左右黑边问题。

    上下边界距离不小于屏幕高度的一半。否则也会在上下黑边问题。
    又一次设置视点实现的方式非常多,本章中採用移动地图位置实现这样的效果。
    我们在HelloWorldScene.cpp中再加入一个函数setViewpointCenter,加入后代码例如以下: 

    void HelloWorld::setViewpointCenter(Point position)
    {
        Size visibleSize = Director::getInstance()->getVisibleSize();
    int x = MAX(position.x, visibleSize.width / 2);								①
        int y = MAX(position.y, visibleSize.height / 2);								②  
        x = MIN(x, (_tileMap->getMapSize().width * _tileMap->getTileSize().width)
                - visibleSize.width / 2);											③
        y = MIN(y, (_tileMap->getMapSize().height * _tileMap->getTileSize().height)
                - visibleSize.height/2);											④
    
    
        //屏幕中心点
        Point pointA = Point(visibleSize.width/2, visibleSize.height/2); 					⑤
    	//使精灵处于屏幕中心。移动地图目标位置
        Point pointB = Point(x, y); 											⑥
    		log("目标位置 (%f ,%f) ",pointB.x,pointB.y);
    
    
        //地图移动偏移量
    Point offset =pointA - pointB; 											⑦
    
    
        log("offset (%f ,%f) ",offset.x, offset.y);
    	this->setPosition(offset);												⑧
    }


    在上述代码①~④是保障精灵移动到地图边界时候不会再移动,防止屏幕超出地图之外,这一点非常重要。当中第①行代码是防止屏幕左边超出地图之外。MAX(position.x, visibleSize.width / 2)语句表示当position.x < visibleSize.width / 2情况下。x轴坐标始终是visibleSize.width / 2,即精灵不再向左移动。第②行代码与第①行代码相似,不再解释。第③行代码是防止屏幕右边超出地图之外,MIN(x, (_tileMap->getMapSize().width * _tileMap->getTileSize().width) - visibleSize.width / 2)语句表示当x > (_tileMap->getMapSize().width * _tileMap->getTileSize().width) - visibleSize.width / 2时候。x轴坐标始终是(_tileMap->getMapSize().width * _tileMap->getTileSize().width) - visibleSize.width / 2表达式计算的结果。


    提示visibleSize 是表示屏幕的宽度,_tileMap->getMapSize().width * _tileMap->getTileSize().width) - visibleSize.width / 2表达式计算的是地图的宽度减去屏幕宽度的一半。
    第④行代码与第③行代码相似,不再解释。




    屏幕左边超出地图


    屏幕右边超出地图
    代码⑤~⑧行实现了移动地图效果。使得精灵一直处于屏幕的中心。

    A点是眼下屏幕的中心点,也是精灵的位置。

    玩家触摸B点。精灵会向B点移动。为了让精灵保持在屏幕中心,地图一定要向相反的方向移动。


    第⑤行代码Point pointA = Point(visibleSize.width/2, visibleSize.height/2)是获取屏幕中心点(A点)。第⑥行代码是获取移动地图目标位置(B点)。第⑦行代码是计算A点与B点两者之差。这个差值就是地图要移动的距离。

    因为精灵的世界坐标就是地图层的模型坐标,即精灵的坐标原点是地图的左下角,因此第⑧行代码this->setPosition(offset)是将地图坐标原点移动offset位置。




    移动地图




    很多其它内容请关注最新Cocos图书《Cocos2d-x实战 C++卷》
    本书交流讨论站点:http://www.cocoagame.net
    很多其它精彩视频课程请关注智捷课堂Cocos课程:http://v.51work6.com
    欢迎加入Cocos2d-x技术讨论群:257760386


    《Cocos2d-x实战 C++卷》现已上线,各大商店均已开售:

    京东:http://item.jd.com/11584534.html

    亚马逊:http://www.amazon.cn/Cocos2d-x%E5%AE%9E%E6%88%98-C-%E5%8D%B7-%E5%85%B3%E4%B8%9C%E5%8D%87/dp/B00PTYWTLU

    当当:http://product.dangdang.com/23606265.html

    互动出版网:http://product.china-pub.com/3770734

    《Cocos2d-x实战 C++卷》源代码及样章下载地址:

    源代码下载地址:http://51work6.com/forum.php?

    mod=viewthread&tid=1155&extra=page%3D1 

    样章下载地址:http://51work6.com/forum.php?

    mod=viewthread&tid=1157&extra=page%3D1

    欢迎关注智捷iOS课堂微信公共平台


  • 相关阅读:
    Python中所有的关键字
    关于selenium的8种元素定位
    对提示框的操作
    selenium+webservice进行百度登录
    MISCONF Redis is configured to save RDB snapshots, but is currently not able to persist on disk. Commands that may modify the data set are disabled...报错解决
    Vue中使用echarts
    npm WARN deprecated request@2.88.2: request has been deprecated, see https://github.com/request/request/issues/3142解决方法
    插入排序
    冒泡排序优化
    roject 'org.springframework.boot:spring-boot-starter-parent:XXX' not found 解决
  • 原文地址:https://www.cnblogs.com/zhchoutai/p/8385673.html
Copyright © 2011-2022 走看看