这个系列今天进入第三课,在第一课中我们学习了添加地图,第二课学习了添加英雄人物。在这一篇中,我们将分两部分进行。 上半部分我们将在控制层OperateLayer中加入一个摇杆,并通过摇杆控制Hero;下半部分我们控制Hero防止其跑出地图和跑上墙。
开发环境
Win64 : vs2010
Cocos2d-x v3.4Final
TexturePackerGUI
MapEdit
代码构建A
管理Operate
我们先做一个Joystick
摇杆Joystick
.h
class Joystick : public Sprite
{
public:
Joystick();
~Joystick();
virtual bool init();
virtual void onTouchesBegan(const std::vector& touches, cocos2d::Event *unused_event);
virtual void onTouchesMoved(const std::vector& touches, cocos2d::Event *unused_event);
virtual void onTouchesEnded(const std::vector& touches, cocos2d::Event *unused_event);
void setJoystick(Vec2 point);
CREATE_FUNC(Joystick);
private:
void showJoystick();
void hideJoystick();
void updateJoystick(Touch* touch);
int m_pJoystickr;
int m_pJoystickR;
Sprite *m_pJoystick;
Sprite *m_pJoystickBg;
Vec2 start;
};
.cpp
bool Joystick::init()
{
bool ret = false;
do {
CC_BREAK_IF( !Sprite::init() );
m_pJoystickBg = Sprite::create("JoystickBg.png");//背景
m_pJoystick = Sprite::create("Joystick.png");//摇杆
this->addChild(m_pJoystickBg, 0);
this->addChild(m_pJoystick, 1);
this->hideJoystick();
//this->showJoystick();
m_pJoystickR= m_pJoystickBg->getContentSize().width/2;
m_pJoystickr= m_pJoystick->getContentSize().width/2;
//新的API注册这么写
auto listener = EventListenerTouchAllAtOnce::create();
listener->onTouchesBegan = CC_CALLBACK_2(Joystick::onTouchesBegan, this);
listener->onTouchesMoved = CC_CALLBACK_2(Joystick::onTouchesMoved, this);
listener->onTouchesEnded = CC_CALLBACK_2(Joystick::onTouchesEnded, this);
_eventDispatcher->addEventListenerWithSceneGraphPriority(listener, this);
ret = true;
} while(0);
return ret;
}
void Joystick::showJoystick()
{
//显示摇杆
m_pJoystick->setVisible(true);
m_pJoystickBg->setVisible(true);
}
void Joystick::hideJoystick()
{
//隐藏摇杆
//m_pJoystick->setPosition(m_pJoystickBg->getPosition());
m_pJoystick->setVisible(false);
//m_pJoystickBg->setVisible(false);
m_pJoystickBg->setVisible(true);
}
void Joystick::onTouchesBegan(const vector& touches, Event *unused_event)
{
//按下事件处理
std::vector::const_iterator touchIter = touches.begin();
Touch* touch = (Touch*)(*touchIter);
if(m_pJoystick->getBoundingBox().containsPoint(touch->getLocation()))
{
this->showJoystick();
updateJoystick(touch);
CCLOG("***************");
CCLOG("update touch:%f %f",touch->getLocation().x,touch->getLocation().y);
return;
}
}
void Joystick::onTouchesMoved(const vector& touches, Event *unused_event)
{
//移动时处理
std::vector::const_iterator touchIter = touches.begin();
Touch* touch = (Touch*)(*touchIter);
if(m_pJoystick->isVisible())
{
updateJoystick(touch);
return;
}
}
void Joystick::onTouchesEnded(const vector& touches, Event *unused_event)
{
//离开是处理
//m_pJoystick->runAction(MoveTo::create(0.08f,start));
//m_pJoystick->setPosition(start);
//global->hero->onStop();
this->hideJoystick();
}
void Joystick::setJoystick(Vec2 point)
{
//将这个摇杆的放在某个坐标上
start =point;
m_pJoystickBg->setPosition(start);
m_pJoystick->setPosition(m_pJoystickBg->getPosition());
}
void Joystick::updateJoystick(Touch* touch)
{
//更新摇杆状态
//我用向量来判断
Vec2 hit = touch->getLocation();
float distance = start.getDistance(hit);
Vec2 direction = (hit - start).getNormalized();
//为了防止摇杆移出摇杆背景
if(distance < m_pJoystickr/2)
{
m_pJoystick->setPosition(start + (direction * distance));
}else if(distance >m_pJoystickr) {
m_pJoystick->setPosition(start + (direction * m_pJoystickr));
}else {
m_pJoystick->setPosition(start + (direction * m_pJoystickr/2));
}
//global->hero->onMove(direction,distance);
}
这个JoyStick的写法我用了向量,不用笛卡尔坐标(xOy),我认为这样写更好理解它。
而且这个摇杆分为2段:
某个范围A内,随意移动
超出最大范围B-接触点移出了摇杆背景时,将其设定在最大边沿B处
在这两个范围A-B之间,会沿着A的边沿放置
最大的好处:我可以通过摇杆移动距离控制角色 (走)、(跑)切换。
相当的讨厌必须按2下才能让角色执行跑动,自己写简单点行不行啊?
控制层OperateLayer
.h
1#include "Joystick.h"
.cpp
init中
1
2
3auto m_pjoystick = Joystick::create();
m_pjoystick->setJoystick(Vec2(50,50));
this->addChild(m_pjoystick);
效果A
大功告成:
接下来我提一个问题:Joystick和我们的Hero在不同层,如何让这个摇杆控制我们的Hero呢?
实现的方法有很多。
这里通过创建另一个Global,并将Joystick和Hero”注册”上去,通过Joystick控制Global中的Hero,也就是直接控制Hero实现。
Global是全局单类。
代码构建B
首先引入一个Single.h
Other
唯一实例Single
这是一个优秀的单例实例(这是从别人的代码中找到的)
.h
#ifndef _SINGLETON_H
#define _SINGLETON_H
using namespace std;
template
class Singleton
{
public:
//获取类的唯一实例
static inline T* instance();
//释放类的唯一实例
void release();
protected:
Singleton(void){}
~Singleton(void){}
static T* _instance;
};
template
inline T* Singleton::instance()
{
if(NULL == _instance){
_instance = new T;
}
return _instance;
}
template
void Singleton::release()
{
if (!_instance)
return;
delete _instance;
_instance = 0;
}
//cpp文件中需要先声明静态变量
#define DECLARE_SINGLETON_MEMBER(_Ty) \
template <> _Ty* Singleton<_Ty>::_instance = NULL;
#endif//_SINGLETON_H
全局类Global
我把后面需要用的都放进去了,头文件好说.
- .h
#ifndef _GLOBAL_H_
#define _GLOBAL_H_
#include "cocos2d.h"
USING_NS_CC;
#include "Singleton.h"
#include "GameLayer.h"
#include "OperateLayer.h"
#include "StateLayer.h"
//需引入以下类,否则在这些类中访问单例对象会报错
class GameLayer;
class OperateLayer;
class StateLayer;
class Hero;
class Enemy;
//全局单例
class Global :public Singleton
{
public:
Global(void);
~Global(void);
//GameScene *gameScene;
GameLayer *gameLayer; //游戏层
OperateLayer *operateLayer; //操作层
StateLayer * stateLayer; //状态层
Hero *hero; //英雄
__Array *enemies; //敌人
TMXTiledMap *tileMap; //地图
Point tilePosFromLocation(Vec2 MovePoint, TMXTiledMap *map = NULL);//将point转换成地图GID的point
bool tileAllowMove(Vec2 MovePoint);
};
#define global Global::instance()
#endif
.cpp
#include "Global.h"
DECLARE_SINGLETON_MEMBER(Global);
Global::Global(void)
{
}
Global::~Global(void)
{
CC_SAFE_DELETE(gameLayer);
CC_SAFE_DELETE(operateLayer);
CC_SAFE_DELETE(stateLayer);
CC_SAFE_DELETE(hero);
CC_SAFE_DELETE(enemies);
//CC_SAFE_DELETE(tileMap);
gameLayer = NULL;
operateLayer= NULL;
stateLayer= NULL;
hero= NULL;
enemies= NULL;
tileMap= NULL;
}
Point Global::tilePosFromLocation(Point MovePoint, TMXTiledMap *map)
{
Point point = MovePoint - map->getPosition();
Point pointGID = Vec2::ZERO;
pointGID.x = (int) (point.x / map->getTileSize().width);
pointGID.y = (int) ((map->getMapSize().height * map->getTileSize().height - point.y) / map->getTileSize().height);
return pointGID;
}
bool Global::tileAllowMove(Point MovePoint)
{
TMXLayer *floor = global->tileMap->getLayer("Floor");
Point tileGid = tilePosFromLocation(MovePoint,global->tileMap);
auto allowpoint =floor->getTileGIDAt(tileGid);
if(0 == allowpoint)
{
return false;
}
return true;
}
在需要用到的地方注册他比如GameLayer中:
.h
1#include "Global.h"
根据Global.h中所定义的,在对应cpp中需要添加诸如
- .cpp
GameLayer::GameLayer()
{
global->gameLayer=this;
}
OperateLayer::OperateLayer()
{
global->operateLayer=this;
}
StateLayer::StateLayer()
{
global->stateLayer = this;
}
………需要添加的地方………太多了,还是看代码吧。
不过后来如果有新的头文件我就扔上一个。
目前需要添加的头文件:
MapLayer
GameLayer
StateLayer
OpreateLayer
Hero
JoyStick
角色Role
Hero
更新我们的Hero,添加下面代码
- .h
void onMove(Vec2 direction, float distance);
void onStop();
void onAttack(int number);
void updateSelf();
.cpp
void Hero::onMove(Vec2 direction, float distance)//移动调用
{
this->setFlippedX(direction.x < 0 ? true : false);
this->runWalkAction();
Vec2 velocity = direction * (distance < 33 ? 1 : 3);
this->setVelocity(velocity);
}
void Hero::onStop()//站立
{
this->runIdleAction();
this->setVelocity(Vec2::ZERO);
}
void Hero::onAttack(int number)//执行攻击
{
this->runNomalAttackA();
}
void Hero::updateSelf()//刷新自己
{
if(this->getCurrActionState() == ACTION_STATE_WALK)
{
Vec2 currentP= this->getPosition(); //当前坐标
Vec2 expectP = currentP + this->getVelocity(); //期望坐标
Vec2 actualP = expectP; //实际坐标
this->setPosition(actualP);
this->setLocalZOrder( Director::getInstance()->getVisibleSize().height - this->getPositionY());
}
}
之后我们在Joystick中调用Hero的onMove,就可以让其移动了。
其他代码我们后期再用。
控制Operate
Joystick
去掉里面的onTouchesEnded,updateJoystick中关于global->hero的//
实现Joystick控制Global中的hero
效果B
额,主角的状态切换了,但是还是不能动?什么情况?
找到原因了,GameLayer中没有更新主角坐标啊。
Game
GameLayer
.h
1
2void update(float dt);
void updateHero(float dt);
.cpp
init中启动默认定时器update
1this->scheduleUpdate();
然后是实现方法:
1
2
3
4void GameLayer::update(float dt)
{
this->updateHero(dt);
}
而updateHero
void GameLayer::updateHero(float dt)
{
m_pHero->updateSelf();//自更新状态
}
效果C
结语
至此,我们终于实现了标题的效果:添加摇杆并控制Hero
不过还是有一堆BUG,比如主角能跑到天上,主角移出了地图,地图不会动等。
将在下一篇中消除这些BUG。