cocos2dx游戲開發(fā)學(xué)習(xí)——虛擬搖桿(8方向)講解

寫這篇博客的目的主要是記錄一下 虛擬搖桿的實現(xiàn)過程畏梆。虛擬搖桿一般分文四方向和八方向,也主要根據(jù)項目需求來決定。直接進入主題吧钾麸。

先上效果圖:


這里寫圖片描述

  • 方向的思路分析
    這里寫圖片描述

    看圖船殉,說先我們可以將8個方向在坐標(biāo)系中畫出來鲫趁,這里涉及到余弦定理,高中數(shù)學(xué)可能經(jīng)忱妫看到這樣的圖挨厚,從圖中可以很明確的看出每45°一個方向。這里我們只需要根據(jù)a的度數(shù)來判別方向糠惫。(例如:022.5°和337.5°360° 是右方向疫剃,67.5112.5是上方向,等等)硼讽。那么問題來了巢价,怎么去算出a的角度呢,根據(jù)反余弦定義固阁,我們一般只能算出0180°壤躲,那如何才能得到0~360°呢?這里隱約記得當(dāng)a大于180度時候备燃,a的余弦值等于(360-a)的余弦(如果不熟悉的話記得補一補高中數(shù)學(xué)了)碉克。這樣就解決了方向問題啦。
    上代碼:
/**
 * 獲取以p1為圓心赚爵,p2p1與x軸的弧度值
 */
float GameRocker::getRad(cocos2d::Point p1, cocos2d::Point p2)
{
    float xx = p2.x - p1.x;
    float yy = p2.y - p1.y;
    // 斜邊
    float xie = sqrt(pow(xx, 2) + pow(yy, 2));
    // yy >= 0 弧度在于 0 到 π 之間棉胀。(0~180°)
    // yy < 0 弧度在于 π 到 2π 之間。(180°~360°)
    float rad = yy >= 0 ? (acos(xx / xie)) : (PI * 2 - acos(xx / xie));
    return rad;
}

  • 虛擬搖桿類的實現(xiàn)
    有一個問題需要思考冀膝,這個遙感類應(yīng)該繼承Node呢還是Layer唁奢,為什么我會考慮這個,因為我看到一些遙感類都是繼承Layer窝剖,而我一般喜歡繼承Node麻掸。而且我覺得繼承Node要比繼承Layer更加靈活(這個問題希望大牛過來指點)。
    無論繼承Node還是Layer還是其他赐纱,我們都需要添加觸摸監(jiān)聽事件脊奋。
    很多游戲中,我們發(fā)現(xiàn)虛擬搖桿會有個額外狀態(tài)疙描,常見的就是加速诚隙,玩兒過手機版的2K的應(yīng)該熟悉。我也考慮做類似的功能起胰,還有許多游戲中搖桿可能不是固定的久又。這里我就直接上代碼了。

GameRocker.h

/**
 *  搖桿狀態(tài)類
 */
class GameRockerState
{
public:
    
    /**
     *  搖桿方向枚舉
     */
    enum class DirectionType
    {
        StayType,
        LeftType,
        LeftUpType,
        UpType,
        RightUpType,
        RightType,
        RightDownType,
        DownType,
        LeftDownType,
    };
    
    /**
     *  速度狀態(tài)
     */
    enum class SpeedState
    {
        StayState,
        NormalState,
        QuickState,
    };
    
public:
    GameRockerState():_direction(GameRockerState::DirectionType::StayType),_speedState(GameRockerState::SpeedState::StayState) {};
    GameRockerState(const GameRockerState& state){
        _direction = state.getDirection();
        _speedState = state.getSpeedState();
    };
    ~GameRockerState(){};
    
    GameRockerState operator = (const GameRockerState& state)
    {
        if(this == &state) return *this;
        _direction = state.getDirection();
        _speedState = state.getSpeedState();
        return *this;
    };
    
    bool operator == (const GameRockerState& state)
    {
        return (_direction == state.getDirection() && _speedState == state.getSpeedState());
    }
    
    bool operator != (const GameRockerState& state)
    {
        return !(*this==state);
    }

protected:
    CC_SYNTHESIZE_PASS_BY_REF(GameRockerState::DirectionType,_direction,Direction);
    CC_SYNTHESIZE_PASS_BY_REF(GameRockerState::SpeedState,_speedState,SpeedState);
};





/**
 *  搖桿類
 */
class GameRocker:public cocos2d::Node
{
public:
    typedef std::function<void(const GameRockerState&)> StateChangeCallback;
    
    /**
     *  速度狀態(tài)
     */
    enum class KeysType
    {
        Key_4,// 四鍵位
        Key_8,// 八鍵位
    };
    
    
public:
    GameRocker();
    ~GameRocker();
    static GameRocker* createWithFilename(const std::string& barFilename,const std::string& bgFilename);
    virtual const cocos2d::Size& getContentSize()const;
    
    /**
     *  設(shè)置狀態(tài)改變的監(jiān)聽回調(diào)
     */
    void setupDirectionStateCallback(const StateChangeCallback& callback);
    
    /**
     *  獲取當(dāng)前搖桿狀態(tài)
     */
    inline const GameRockerState& getRockerState() const { return _rockerState; };
    
    
protected:
    bool initWithFilename(const std::string& barFilename,const std::string& bgFilename);
    
    virtual void onEnter() override;
    
    void addTouchEvent();
    
    void changeRockerState(const GameRockerState& state);
    
    float getRad(cocos2d::Point p1,cocos2d::Point p2);

private:
    cocos2d::Sprite* rockerBar;
    cocos2d::Sprite* rockerBG;
    
    /** 搖桿的正常活動半徑 (指用于正常跑)*/
    float _normalRadius;
    /** 搖桿的加速活動半徑(指用于快跑) */
    float _quickRadius;
    
    
    /** 搖桿狀態(tài) */
    GameRockerState _rockerState;
    /** 搖桿狀態(tài)改變的回調(diào)函數(shù) */
    StateChangeCallback _changeCallback;
    //** 最初的坐標(biāo) *//
    cocos2d::Vec2 _initPosition;
    
    /** 搖桿是否能夠使用 */
    CC_SYNTHESIZE(bool,_enable,Enable);
    /** 搖桿是否是可移動 */
    CC_SYNTHESIZE(bool, _movable, Movable);
    /** 可移動的范圍地消,GL坐標(biāo)系 */
    CC_SYNTHESIZE_PASS_BY_REF(cocos2d::Rect,_moveRect,MoveRect);
    /** 鍵位方向 */
    CC_SYNTHESIZE(GameRocker::KeysType,_keysType,KeysType);
    
};

GameRocker.cpp

#define PI 3.141592654

GameRocker::GameRocker(){}

GameRocker::~GameRocker(){}


GameRocker* GameRocker::createWithFilename(const std::string &barFilename, const std::string &bgFilename)
{
    GameRocker* rocker = new (std::nothrow) GameRocker();
    if (rocker && rocker->initWithFilename(barFilename, bgFilename)) {
        rocker->autorelease();
        return rocker;
    }else{
        CC_SAFE_DELETE(rocker);
        return nullptr;
    }
}


const Size& GameRocker::getContentSize() const
{
    if (rockerBG) {
        return rockerBG->getContentSize();
    }
    return Size::ZERO;
}

void GameRocker::setupDirectionStateCallback(const GameRocker::StateChangeCallback &callback)
{
    _changeCallback = callback;
}


bool GameRocker::initWithFilename(const std::string &barFilename, const std::string &bgFilename)
{
    if (Node::init())
    {
        rockerBG = Sprite::create(bgFilename);
        rockerBar = Sprite::create(barFilename);
        rockerBar->setPosition(rockerBG->getContentSize().width/2,rockerBG->getContentSize().height/2);
        rockerBG->addChild(rockerBar);
        this->addChild(rockerBG);
        setContentSize(rockerBG->getContentSize());
        _normalRadius = rockerBG->getContentSize().width/2;
        _quickRadius = rockerBG->getContentSize().width * 0.65;
        _enable = true;
        _movable = false;
        _moveRect = Rect(0, 0, 0, 0);
        _keysType = GameRocker::KeysType::Key_4;
        addTouchEvent();
        return true;
    }
    return false;
}


void GameRocker::onEnter()
{
    Node::onEnter();
    _initPosition = this->getPosition();
}


void GameRocker::addTouchEvent()
{
    auto listener = EventListenerTouchOneByOne::create();
    listener->setSwallowTouches(false);
    listener->onTouchBegan = [this](Touch* touch, Event* evnt)->bool
    {
        if (_movable)
        {
            if (_moveRect.containsPoint(touch->getLocation()))
            {
                this->setPosition(touch->getLocation());
                return true;
            }
            return false;
        }else{
            Rect rockerRect = Rect(this->getPositionX() - _normalRadius, this->getPositionY() - _normalRadius, _normalRadius*2, _normalRadius*2);
            Rect rect = this->getBoundingBox();
            rect.origin = rect.origin - Vec2(rockerBG->getContentSize().width/2,rockerBG->getContentSize().height/2);
            return rockerRect.containsPoint(touch->getLocation());
        }
    };
    
    listener->onTouchMoved = [this](Touch* touch, Event* evnt)
    {
        if (!_enable) return;

        // 獲取角度
        Point p1 = this->getPosition();
        Point p2 = touch->getLocation();
        float rad = getRad(p1, p2);
        // 用戶觸摸點到搖桿的中心的距離
        float touchRadius = sqrt(pow(touch->getLocation().x - this->getPositionX(), 2) + pow(touch->getLocation().y - this->getPositionY(), 2));
        
        float radius = MIN(_normalRadius, touchRadius);
        
        GameRockerState tempState;
        if (touchRadius >= _quickRadius) {
            // 加速狀態(tài)
            radius = _quickRadius;
            rockerBG->setColor(Color3B::RED);
            tempState.setSpeedState(GameRockerState::SpeedState::QuickState);
        }else{
            // 普通狀態(tài)
            rockerBG->setColor(Color3B::WHITE);
            tempState.setSpeedState(GameRockerState::SpeedState::NormalState);
        }
        // 設(shè)置 搖桿的位置
        Point barPoint = Vec2(radius * cos(rad), radius * sin(rad)) + Vec2(rockerBG->getContentSize().width/2,rockerBG->getContentSize().height/2);
        rockerBar->setPosition(barPoint);
        // 弧度轉(zhuǎn)化成腳步
        float angle = 180.f / PI * rad;
        if (_keysType == GameRocker::KeysType::Key_4) {
            if ((angle >= 0 && angle < 45) || (angle >= 315 && angle < 360)) {//右
                tempState.setDirection(GameRockerState::DirectionType::RightType);
            }
            if (angle >= 45 && angle < 135) { //上
                tempState.setDirection(GameRockerState::DirectionType::UpType);
            }
            if (angle >= 135 && angle < 225) { //左
                tempState.setDirection(GameRockerState::DirectionType::LeftType);
            }
            if (angle >= 225 && angle < 315) { //下
                tempState.setDirection(GameRockerState::DirectionType::DownType);
            }
        }
        if (_keysType == GameRocker::KeysType::Key_8) {
            if ((angle >= 0 && angle < 22.5) || (angle >= 337.5 && angle < 360)) {//右
                tempState.setDirection(GameRockerState::DirectionType::RightType);
            }
            if (angle >= 22.5 && angle < 67.5) { //右上
                tempState.setDirection(GameRockerState::DirectionType::RightUpType);
            }
            if (angle >= 67.5 && angle < 112.5) { //上
                tempState.setDirection(GameRockerState::DirectionType::UpType);
            }
            if (angle >= 112.5 && angle < 157.5) { //左上
                tempState.setDirection(GameRockerState::DirectionType::LeftUpType);
            }
            if (angle >= 157.5 && angle < 202.5) { //左
                tempState.setDirection(GameRockerState::DirectionType::LeftType);
            }
            if (angle >= 202.5 && angle < 247.5) { //左下
                tempState.setDirection(GameRockerState::DirectionType::LeftDownType);
            }
            if (angle >= 247.5 && angle < 292.5) { //下
                tempState.setDirection(GameRockerState::DirectionType::DownType);
            }
            if (angle >= 292.5 && angle < 337.5) { //右下
                tempState.setDirection(GameRockerState::DirectionType::RightDownType);
            }
        }
        
        // 改變搖桿Bar的狀態(tài)
        changeRockerState(tempState);
    };
    auto touchEndCallback = [this](Touch* touch, Event* evnt)
    {
        CCLOG("Layer touch  move  (%f,%f)",touch->getLocation().x,touch->getLocation().y);
        rockerBar->setPosition(rockerBG->getContentSize().width/2,rockerBG->getContentSize().height/2);
        rockerBG->setColor(Color3B::WHITE);
        this->setPosition(_initPosition);
        GameRockerState tempState;
        changeRockerState(tempState);
    };
    listener->onTouchEnded = touchEndCallback;
    listener->onTouchCancelled = touchEndCallback;
    
    Director::getInstance()->getEventDispatcher()->addEventListenerWithSceneGraphPriority(listener, this);
}


void GameRocker::changeRockerState(const GameRockerState &state)
{
    if (_rockerState == state) return;
    _rockerState = state;
    if (_changeCallback) _changeCallback(_rockerState);
}


/**
 * 獲取以p1為圓心炉峰,p2p1與x軸的弧度值
 */
float GameRocker::getRad(cocos2d::Point p1, cocos2d::Point p2)
{
    float xx = p2.x - p1.x;
    float yy = p2.y - p1.y;
    // 斜邊
    float xie = sqrt(pow(xx, 2) + pow(yy, 2));
    // yy >= 0 弧度在于 0 到 π 之間。(0~180°)
    // yy < 0 弧度在于 π 到 2π 之間脉执。(180°~360°)
    float rad = yy >= 0 ? (acos(xx / xie)) : (PI * 2 - acos(xx / xie));
    return rad;
}

最后提一句疼阔,這個虛擬鍵盤可拓展的東西很多,大家可以發(fā)揮一下半夷。另外有什么不足的地方婆廊,大家可以提出來

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市巫橄,隨后出現(xiàn)的幾起案子否彩,更是在濱河造成了極大的恐慌,老刑警劉巖嗦随,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件列荔,死亡現(xiàn)場離奇詭異,居然都是意外死亡枚尼,警方通過查閱死者的電腦和手機贴浙,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來署恍,“玉大人崎溃,你說我怎么就攤上這事《⒅剩” “怎么了袁串?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長呼巷。 經(jīng)常有香客問我囱修,道長,這世上最難降的妖魔是什么王悍? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任破镰,我火速辦了婚禮,結(jié)果婚禮上压储,老公的妹妹穿的比我還像新娘鲜漩。我一直安慰自己,他們只是感情好集惋,可當(dāng)我...
    茶點故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布孕似。 她就那樣靜靜地躺著,像睡著了一般刮刑。 火紅的嫁衣襯著肌膚如雪喉祭。 梳的紋絲不亂的頭發(fā)上霸饲,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天,我揣著相機與錄音臂拓,去河邊找鬼。 笑死习寸,一個胖子當(dāng)著我的面吹牛胶惰,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播霞溪,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼孵滞,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了鸯匹?” 一聲冷哼從身側(cè)響起坊饶,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎殴蓬,沒想到半個月后匿级,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡染厅,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年痘绎,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片肖粮。...
    茶點故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡孤页,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出涩馆,到底是詐尸還是另有隱情行施,我是刑警寧澤,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布魂那,位于F島的核電站蛾号,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏涯雅。R本人自食惡果不足惜须教,卻給世界環(huán)境...
    茶點故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望斩芭。 院中可真熱鬧轻腺,春花似錦、人聲如沸划乖。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽琴庵。三九已至误算,卻和暖如春仰美,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背儿礼。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工咖杂, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人蚊夫。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓诉字,卻偏偏與公主長得像,于是被迫代替她去往敵國和親知纷。 傳聞我的和親對象是個殘疾皇子壤圃,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,762評論 2 345

推薦閱讀更多精彩內(nèi)容