寫這篇博客的目的主要是記錄一下 虛擬搖桿的實現(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ā)揮一下半夷。另外有什么不足的地方婆廊,大家可以提出來