1. 程式人生 > >cocos2dx 利用遮罩層來實現地圖的簡單尋路

cocos2dx 利用遮罩層來實現地圖的簡單尋路


談到地圖不少人都說要做地圖編輯器了,但是我暫時繞過這一步,如果不用尋路地圖就不能移動?尋路就是會繞過障礙物的演算法。

我做了一個簡單的地圖的思想,就是地圖分層3層:背景層、可行區域層、遮罩層,但是地圖就不尋路了,通過設定可行區域層來

實現地圖障礙物的方法。下面看一個檢視,我把地圖詳細的分層了:

OK,有了這個思路,大家應該也知道我要怎麼做了?程式碼實現上怎麼處理呢?

重點:可行區域層原理是根據點選螢幕上的座標點來取得這個點是否透明!如果不透明那就不讓他進行移動,透明則為不可行區域;

首先感謝一下為我提供取色原始碼的哥們(firedragonpzy),幫助我實現了這個另類的地圖設計;下面我貼一下他的原始碼,

新建了FDPixelSprite.cpp,FDPixelSprite.h程式碼如下:

FDPixelSprite.h

//
//  FDPixelSprite.h
//  PixelDemo
//
//  Created by firedragonpzy on 13-2-19.
//
//

#ifndef __PixelDemo__FDPixelSprite__
#define __PixelDemo__FDPixelSprite__
#include "cocos2d.h"
USING_NS_CC;

class FDPixelSprite : public CCSprite, public CCTargetedTouchDelegate {
public:
    FDPixelSprite();
    virtual ~FDPixelSprite();
    
    void onEnter();
    void onExit();
    void setimg(CCString Url);
    
    FDPixelSprite* create(CCString Url);
    CCImage* img ;
    
    CCRect atlasRect();
    bool isContainTouchLocation(CCTouch *pTouch);
    
    bool ccTouchBegan(CCString thismapurl,CCTouch *pTouch, CCEvent *pEvent);
    void ccTouchMoved(CCTouch *pTouch, CCEvent *pEvent);
    void ccTouchEnded(CCTouch *pTouch, CCEvent *pEvent);

    CC_SYNTHESIZE(const char*, m_pName,Name);
};

#endif /* defined(__PixelDemo__FDPixelSprite__) */

FDPixelSprite.cpp

//
//  FDPixelSprite.cpp
//  PixelDemo
//
//  Created by firedragonpzy on 13-2-19.
//
//

#include "FDPixelSprite.h"
#include "FontChina.h"

FDPixelSprite::FDPixelSprite()
{}
FDPixelSprite::~FDPixelSprite()
{}

FDPixelSprite* FDPixelSprite::create(CCString Url)
{
    FDPixelSprite *sprite = new FDPixelSprite();
    if (sprite && sprite->initWithFile(Url.getCString())) {
        sprite->setName(Url.getCString());
        sprite->autorelease();
        return sprite;
    }
    CC_SAFE_DELETE(sprite);
    sprite = NULL;
    
    
    return NULL;
}

void FDPixelSprite::setimg(CCString Url){
    img= new CCImage();
    img->initWithImageFileThreadSafe(CCFileUtils::sharedFileUtils()->fullPathForFilename(Url.getCString()).c_str());

}

void FDPixelSprite::onEnter()
{
    CCSprite::onEnter();
    CCDirector::sharedDirector()->getTouchDispatcher()->addTargetedDelegate(this, 0, true);
}


void FDPixelSprite::onExit()
{
    CCSprite::onExit();
    CCDirector::sharedDirector()->getTouchDispatcher()->removeDelegate(this);
}


bool FDPixelSprite::ccTouchBegan(CCString thismapurl,cocos2d::CCTouch *pTouch, cocos2d::CCEvent *pEvent)
{
    if (this->isContainTouchLocation(pTouch) ) {
        ccColor4B c = {0, 0, 0, 0};
        
        CCSize winSize = CCDirector::sharedDirector()->getWinSize();
        
        CCPoint touchPoint = pTouch->getLocationInView();
        
        CCSize cSize = this->getContentSize();
        CCPoint point =this->getAnchorPointInPoints();
        point = ccp(cSize.width - point.x,cSize.height- point.y);
        CCPoint pos(this->getPositionX() - point.x,winSize.height-this->getPositionY()- point.y);
        
        CCPoint localPoint = ccp(touchPoint.x - pos.x,
                                 touchPoint.y -pos.y);
        
        float scaleFactor = CCDirector::sharedDirector()->getContentScaleFactor();
        unsigned int x = localPoint.x  * scaleFactor, y = localPoint.y * scaleFactor;
        
        float _width = this->getContentSize().width*scaleFactor;

        //This method is currently only supports symmetric image
        //unsigned char *data_ = this->getTexture()->getFDImageData();
        
        //Efficiency of this method is relatively low
        //CCImage * img = new CCImage();
        //img->initWithImageFileThreadSafe(CCFileUtils::sharedFileUtils()->fullPathForFilename(thismapurl.getCString()).c_str());
        unsigned char *data_ = img->getData();
        
        
        unsigned int *pixel = (unsigned int *)data_;
        pixel = pixel + (y * (int)_width)* 1 + x * 1;

        c.r = *pixel & 0xff;
        c.g = (*pixel >> 8) & 0xff;
        c.b = (*pixel >> 16) & 0xff;
        c.a = (*pixel >> 24) & 0xff;
        if (c.a == 0) {
            CCLog(FontChina::G2U("不可點選!"));
            return false;
        }else
        {
            CCLog(FontChina::G2U("可點選!"));
            return true;
        }
    }
    return false;
}


void FDPixelSprite::ccTouchMoved(cocos2d::CCTouch *pTouch, cocos2d::CCEvent *pEvent)
{
    //CCPoint pos = this->getPosition();
    //CCPoint sub = pTouch->getDelta();
    //this->setPosition(ccpAdd(pos, sub));
}


void FDPixelSprite::ccTouchEnded(cocos2d::CCTouch *pTouch, cocos2d::CCEvent *pEvent)
{
    //CCLog("firedragonpzy:ccTouchEnded");
}


CCRect FDPixelSprite::atlasRect()
{
    CCSize cSize = this->getContentSize();
    CCPoint point = this->getAnchorPointInPoints();
    return CCRectMake( -point.x, -point.y, cSize.width,cSize.height);
}


bool FDPixelSprite::isContainTouchLocation(cocos2d::CCTouch *pTouch)
{
   return this->atlasRect().containsPoint(convertTouchToNodeSpaceAR(pTouch));
}

有了他們我們就能判斷地圖上是否可行了。OK廢話不多,繼續走向精彩,詳細解決一下背景層我們應該做些什麼東西,有什麼內容?

這裡有人說,那其他不帶主角功能的怎麼辦?繼承你寫的精靈類拓展成怪物類(智慧AI攻擊操作),NPC(任務功能模組),可拓展行是

槓槓滴,繼承下來NPC都能移動,和你打起來,我的思路是把地圖做成一個大容器,每一塊新地圖繼承一個MapsBase的同時他自己也有

有自己的特殊邏輯和特殊業務;

繼續貼程式碼,地圖是這麼實現的:

Maps_Diyu.h

#include "cocos2d.h"
#include "../Commen/FDPixelSprite.h"
#include "../Spirits/SpiritsPlayer.h"

USING_NS_CC;

class Maps_Diyu :public cocos2d::CCSprite
{
public:
    Maps_Diyu(CCLayer* layer,CCString mapsurl,CCString mapsurl_1,int zOrder,CCPoint cp);
    ~Maps_Diyu(void);
  
    CCSprite* nowmap;
    CCSprite* nowmap_zhezhao;
    //基本資料
    float lastmove_x,lastmove_y;
    bool moveMapto(CCPoint cp,FDPixelSprite* mainmap_Touch);
    CCActionInterval* act_moveto_maps;
    CCActionInterval* act_moveto_maps_touch; 
    CCActionInterval* act_moveto_maps_zhezhao; 
    
private:
    
    SpiritsPlayer* role_main;
    CCAnimate* playdonghua;
};

Maps_Diyu.cpp

#include "Maps_Diyu.h"
#include "../GameData/GetNPCData.h"
#include "../Commen/FontChina.h"
#include "../Spirits/SpiritsMonster.h"
#include "../Effects/SkillEffects.h"

Maps_Diyu::Maps_Diyu(CCLayer* layer,CCString mapsurl,CCString mapsurl_1,int zOrder,CCPoint cp)
{
    act_moveto_maps=NULL;
    act_moveto_maps_zhezhao=NULL;
    lastmove_x=0;
    lastmove_y=0;
    float map_x , map_y;

    float center_x,center_y;

    CCPoint origin = CCDirector::sharedDirector()->getVisibleOrigin();
    CCSize size = CCDirector::sharedDirector()->getWinSize();

    nowmap = Maps_Diyu::create(mapsurl.getCString());
    nowmap_zhezhao = Maps_Diyu::create(mapsurl_1.getCString());
    center_x = size.width/2;
    center_y = size.height/2;

    map_y = nowmap->getAnchorPointInPoints().y+origin.y;
    map_x = nowmap->getAnchorPointInPoints().x;

    if(cp.getLength()>0)
    {
        nowmap->setPosition(cp);
        nowmap_zhezhao->setPosition(cp);
    }
    else
    {
        nowmap->setPosition(ccp(map_x,map_y));
        nowmap_zhezhao->setPosition(ccp(map_x,map_y));
    }

    //計算地圖上絕對位置的原點座標
    float map_fornpc_x,map_fornpc_y;
    map_fornpc_x= nowmap->getContentSize().width/2;
    map_fornpc_y=nowmap->getContentSize().height/2;

    //主角載入
    GetNPCData* basedatas = new GetNPCData();
    basedatas->GetNPCchapter1();
    basedatas->role_player.nowpoint= CCPointMake(map_fornpc_x+428,map_fornpc_y+480);
    role_main = new SpiritsPlayer(basedatas->role_player,1,false);
    role_main->npc->retain();

    //載入NPC
    basedatas->role_mengpo.nowpoint= CCPointMake(map_fornpc_x+158,map_fornpc_y+510);
    SpiritsPlayer* role_mengpo= new SpiritsPlayer(basedatas->role_mengpo,1,true);
    nowmap->addChild(role_mengpo->npc, 2);
//-------------------------------------------------------
    nowmap->addChild(role_main->npc, 2,999);
    layer->addChild(nowmap_zhezhao, zOrder+1);
    layer->addChild(nowmap, zOrder);
}

OK,地圖初始化就載入了這些基礎的資料,這些應該大家都能看懂,下面貼最核心的程式碼,如何把他們都關聯起來

並且做移動操作呢??????

/*************************
引數說明:
CCPoint cp  可行區域的座標
mainmap_Touch 可行區域,需要去隨時改變他移動
**************************/
bool Maps_Diyu::moveMapto(CCPoint cp,FDPixelSprite* mainmap_Touch)
{

    float center_x,center_y,move_x,move_y, map_x , map_y ,to_c_x,to_c_y;

    CCPoint origin = CCDirector::sharedDirector()->getVisibleOrigin();
    CCSize size = CCDirector::sharedDirector()->getWinSize();
    center_x = size.width/2;
    center_y = size.height/2;
    move_x = center_x-cp.x;
    move_y = center_y-cp.y;
    map_x = nowmap->getPositionX();
    map_y = nowmap->getPositionY();
    to_c_x = nowmap->getContentSize().width/2;
    to_c_y = nowmap->getContentSize().height/2+origin.y;
    map_x = map_x + move_x;
    map_y  = map_y + move_y-origin.y;

    //計算移動時間,這邊大家可以幫我優化一下
    //現在就這塊移動時間有一些問題
    float a1 , b1 ;
    a1 = fabs(move_x)/size.width;
    b1 = fabs(move_y)/size.height;
    float movetime = ((a1+b1)*8);
    if(movetime<=1)
    {
        movetime=1;
    }

    //這裡是精華,主要是處理任意地圖放進來之後,
    //防止顯示區域超出地圖的長寬,移動到邊界就不能移動了!
    if(map_x>=to_c_x)
    {
        map_x = to_c_x;
    }
    else if(map_x<=-((nowmap->getContentSize().width/2)-size.width))
    {
        map_x =-((nowmap->getContentSize().width/2)-size.width);
    }
    if(map_y>=to_c_y)
    {
        map_y = to_c_y;
    }
    else if(map_y <= -((nowmap->getContentSize().height/2)-size.height))
    {
        map_y = -((nowmap->getContentSize().height/2)-size.height);
    }

    //經典中的經典//
    //主角移動
    CCPoint role_move_pc = nowmap->convertToNodeSpace(ccp(cp.x,cp.y));//此處需要通過地圖的視角把人物移動的座標轉化一下。
    role_main->moveTomap_dir(role_move_pc);    
    role_main->moveTomap_move(movetime,role_move_pc,false);
    //地圖移動
    if(map_x!=lastmove_x&&map_y!=lastmove_y)
    {
        nowmap->stopAction(act_moveto_maps);
        nowmap_zhezhao->stopAction(act_moveto_maps_zhezhao);
        mainmap_Touch->stopAllActions();
        act_moveto_maps = CCMoveTo::create(movetime,ccp((int)map_x,(int)map_y));
        act_moveto_maps_touch = CCMoveTo::create(movetime,ccp((int)map_x,(int)map_y));
        act_moveto_maps_zhezhao = CCMoveTo::create(movetime,ccp((int)map_x,(int)map_y));
        nowmap->runAction(act_moveto_maps);
        nowmap_zhezhao->runAction(act_moveto_maps_zhezhao);
        mainmap_Touch->runAction(act_moveto_maps_touch);
        return true;
    }
    else                  
    {
        return false;
    }

}

核心的地方有三處,幫大家分析一下:

第一,就是計算移動時間,我是根據螢幕長寬來計算,這個地方一直是我心結,這個方法效果現在很不好,跑起來

長距離用時長,短距離就很快,所以請大家也幫我優化一下,可以往下貼程式碼,

第二,就是計算出地圖移動的區域,你不可能隨便丟一張圖進去,地圖超過邊界會顯示黑色,不能讓黑色顯示出來(除非丟進來的圖小過螢幕地圖);

第三,就是通過地圖移動的標識來進行人物和地圖的移動,在人物移動的時候需要轉化一下成地圖的座標!

map_x!=lastmove_x&&map_y!=lastmove_y
可移動的標識
act_moveto_maps = CCMoveTo::create(movetime,ccp((int)map_x,(int)map_y));
act_moveto_maps_touch = CCMoveTo::create(movetime,ccp((int)map_x,(int)map_y));
act_moveto_maps_zhezhao = CCMoveTo::create(movetime,ccp((int)map_x,(int)map_y));

大家也看到,我移動的時候,移動的是3個層,這下就保證了可行區域也是不停在變動的

然後就是如何傳資料了,幾句話就可以搞定Scene載入的地圖上所有層;

Scene_Diyu.h

#include "cocos2d.h"
#include "ToScene.h"
#include "../MapSpirits/Maps_Diyu.h"
USING_NS_CC;
class Scene_Diyu : public CCLayer
{
public:
    Scene_Diyu(void);
    ~Scene_Diyu(void);
    Maps_Diyu* mainmap;
    FDPixelSprite* mainmap_Touch;
    void nextCallback(CCObject* pSender);
    virtual void registerWithTouchDispatcher(void);
    virtual bool ccTouchBegan(CCTouch *pTouch,CCEvent *pEvent);
    virtual void ccTouchMoved(CCTouch *pTouch,CCEvent *pEvent);
    virtual void ccTouchEnded(CCTouch *pTouch,CCEvent *pEvent);
    virtual void ccTouchCancelled(CCTouch *pTouch,CCEvent *pEvent);
};


Scene_Diyu.cpp

#include "Scene_Diyu.h"
#include "../ImagePaths.h"
#include "../PublicUI/BaseUI.h"

Scene_Diyu::Scene_Diyu(void)
{
    float x,y;
    CCPoint origin = CCDirector::sharedDirector()->getVisibleOrigin();
    CCSize size = CCDirector::sharedDirector()->getWinSize();
    x = size.width;
    y = size.height;
    //地圖
    mainmap = new Maps_Diyu(this,"map_diyu_naihe.jpg","map_diyu_naihe1.png",0,ccp(x/2-308,y/2-486));
    mainmap_Touch =  mainmap_Touch->create("map_diyu_naihe0.png");
    mainmap_Touch->setimg("map_diyu_naihe0.png");
    mainmap_Touch->setPosition(ccp(x/2-308,y/2-486));
    mainmap_Touch->setVisible(false);//是否顯示點選層

    BaseUI* baseui = new BaseUI(this);

    this->addChild(mainmap_Touch, 0);
    setTouchEnabled(true);
}


void Scene_Diyu::registerWithTouchDispatcher()  
{  
    CCDirector::sharedDirector()->getTouchDispatcher()->addTargetedDelegate(this,0,true);  
}  


bool Scene_Diyu::ccTouchBegan(cocos2d::CCTouch *pTouch, cocos2d::CCEvent *pEvent)  
{  
    if(mainmap_Touch->ccTouchBegan("map_diyu_naihe0.png",pTouch,pEvent)==true)
    {
        mainmap->moveMapto(pTouch->getLocation(),mainmap_Touch);
    }
    return true;  
}  
  
void Scene_Diyu::ccTouchMoved(cocos2d::CCTouch *pTouch, cocos2d::CCEvent *pEvent)  
{  
}  
  
void Scene_Diyu::ccTouchEnded(cocos2d::CCTouch *pTouch, cocos2d::CCEvent *pEvent)  
{  
}  
  
void Scene_Diyu::ccTouchCancelled(cocos2d::CCTouch *pTouch, cocos2d::CCEvent *pEvent)  
{  
}  

Scene_Diyu::~Scene_Diyu(void)
{
}


 大家也看到了再Scene裡面控制的點選事件主要就是處理可行區域的:

if(mainmap_Touch->ccTouchBegan("map_diyu_naihe0.png",pTouch,pEvent)==true)
    {
        mainmap->moveMapto(pTouch->getLocation(),mainmap_Touch);
    }

好了,大家如果理解,可以自己研究一下自己的方式去實現一下這樣一套,人物移動,地圖移動的原理,當然,我在這裡宣告一下

這套實現思路其實是很歪門,另類的,應為他並沒有採用尋路,但是你也不能完全說不採用尋路演算法的地圖系統就不行。