最新文章
Cocos2d-x游戏开发实例详解7:对象释放时机
03-25 13:59
Cocos2d-x游戏开发实例详解6:自动释放池
03-25 13:55
Cocos2d-x游戏开发实例详解5:神奇的自动释放
03-25 13:49
Cocos2d-x游戏开发实例详解4:游戏主循环
03-25 13:44
Cocos2d-x游戏开发实例详解3:无限滚动地图
03-25 13:37
Cocos2d-x游戏开发实例详解2:开始菜单续
03-25 13:32
制作横版游戏KillBear第3课:添加摇杆并控制英雄
这个系列今天进入第三课。在第一课中,我们学习了添加地图;第二课则学习了添加英雄人物。在本篇文章中,我们将分两部分进行:上半部分,我们会在控制层 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<Touch*>& touches, cocos2d::Event *unused_event);
virtual void onTouchesMoved(const std::vector<Touch*>& touches, cocos2d::Event *unused_event);
virtual void onTouchesEnded(const std::vector<Touch*>& 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;
};
Joystick.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();
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->setVisible(false);
m_pJoystickBg->setVisible(true);
}
void Joystick::onTouchesBegan(const std::vector<Touch*>& touches, Event *unused_event) {
// 按下事件处理
std::vector<Touch*>::const_iterator touchIter = touches.begin();
Touch* touch = *touchIter;
if(m_pJoystick->getBoundingBox().containsPoint(touch->getLocation())) {
this->showJoystick();
updateJoystick(touch);
CCLOG("***************");
CCLOG("update touch:%f \n%f", touch->getLocation().x, touch->getLocation().y);
return;
}
}
void Joystick::onTouchesMoved(const std::vector<Touch*>& touches, Event *unused_event) {
// 移动时处理
std::vector<Touch*>::const_iterator touchIter = touches.begin();
Touch* touch = *touchIter;
if(m_pJoystick->isVisible()) {
updateJoystick(touch);
return;
}
}
void Joystick::onTouchesEnded(const std::vector<Touch*>& touches, Event *unused_event) {
// 离开时处理
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),这样的实现方式更易于理解。该摇杆分为两段:
- 在某个范围A内,摇杆可以随意移动。
- 当接触点超出最大范围B(即移出了摇杆背景)时,摇杆将被设定在最大边沿B处。
- 在范围A和B之间,摇杆会沿着A的边沿放置。
这种设计的最大好处是,可以通过摇杆移动距离控制角色的(走)、(跑)切换,避免了必须按2下才能让角色执行跑动的繁琐操作。
控制层OperateLayer
OperateLayer.h
#include "Joystick.h"
OperateLayer.cpp
// init中
auto m_pjoystick = Joystick::create();
m_pjoystick->setJoystick(Vec2(50, 50));
this->addChild(m_pjoystick);
效果A
至此,摇杆的基本添加已经完成。
接下来,我们面临一个问题:Joystick 和 Hero 处于不同的层,如何让这个摇杆控制 Hero 呢?实现的方法有很多,这里我们通过创建另一个全局单例类 Global,并将 Joystick 和 Hero “注册” 到 Global 上,通过 Joystick 控制 Global 中的 Hero,从而实现对 Hero 的直接控制。
代码构建B
引入Single.h
唯一实例Single
这是一个优秀的单例实现(从其他代码中借鉴而来)。
Single.h
#ifndef _SINGLETON_H
#define _SINGLETON_H
#include <iostream>
template <typename T>
class Singleton {
public:
// 获取类的唯一实例
static inline T* instance();
// 释放类的唯一实例
void release();
protected:
Singleton() {}
~Singleton() {}
static T* _instance;
};
template <typename T>
inline T* Singleton<T>::instance() {
if(NULL == _instance) {
_instance = new T;
}
return _instance;
}
template <typename T>
void Singleton<T>::release() {
if (!_instance)
return;
delete _instance;
_instance = 0;
}
// cpp文件中需要先声明静态变量
#define DECLARE_SINGLETON_MEMBER(_Ty) \
template <> _Ty* Singleton<_Ty>::_instance = NULL;
#endif // _SINGLETON_H
全局类Global
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<Global> {
public:
Global();
~Global();
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
Global.cpp
#include "Global.h"
DECLARE_SINGLETON_MEMBER(Global);
Global::Global() {}
Global::~Global() {
CC_SAFE_DELETE(gameLayer);
CC_SAFE_DELETE(operateLayer);
CC_SAFE_DELETE(stateLayer);
CC_SAFE_DELETE(hero);
CC_SAFE_DELETE(enemies);
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;
}
在需要使用 Global 的地方进行注册,例如在 GameLayer 中:
GameLayer.h
#include "Global.h"
GameLayer.cpp
GameLayer::GameLayer() {
global->gameLayer = this;
}
OperateLayer::OperateLayer() {
global->operateLayer = this;
}
StateLayer::StateLayer() {
global->stateLayer = this;
}
目前需要添加 Global.h 头文件的类有:MapLayer、GameLayer、StateLayer、OperateLayer、Hero、Joystick、Role 等。
角色Role - Hero
Hero.h
void onMove(Vec2 direction, float distance);
void onStop();
void onAttack(int number);
void updateSelf();
Hero.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 方法,就可以让 Hero 移动了。
控制Operate
Joystick
去掉 Joystick 中的 onTouchesEnded 方法里不必要的代码,并去掉 updateJoystick 中关于 global->hero 的注释,以实现 Joystick 对 Global 中 hero 的控制。
效果B
此时,主角的状态虽然可以切换,但无法移动。经过排查,发现原因是 GameLayer 中没有更新主角的坐标。
GameLayer
GameLayer.h
void update(float dt);
void updateHero(float dt);
GameLayer.cpp
// init中启动默认定时器update
this->scheduleUpdate();
void GameLayer::update(float dt) {
this->updateHero(dt);
}
void GameLayer::updateHero(float dt) {
m_pHero->updateSelf(); // 自更新状态
}
效果C
至此,我们终于实现了标题所描述的效果:添加摇杆并控制 Hero。
结语
不过,目前仍然存在一些问题,例如主角能跑到天上、主角移出地图后地图不会移动等。这些问题将在下一篇文章中进行解决。