转载至:http://cn.cocos2d-x.org/tutorial/show?id=1192
上一章我们讲述了场景层中触摸响应的原理和过程,紧接着下面我们来创建一个炮塔选择面板层,该层是触摸场景层后的产物。在该层中玩家可以选择添加不同类型的炮塔,抛开其他层不看,这一层其实就是如下所示的一层:
选择面板的响应
新建一个TowerPanleLayer类,它继承于Layer,其定义如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
// 定义炮塔类型
typedef
enum
{
ARROW_TOWER = 0,
ATTACK_TOWER ,
MULTIDIR_TOWER,
ANOTHER
} TowerType;
class
TowerPanleLayer:
public
Layer
{
public
:
virtual
bool
init() override;
CREATE_FUNC(TowerPanleLayer);
// 重载触摸回调函数
bool
onTouchBegan(Touch *touch, Event *event);
void
onTouchEnded(Touch* touch, Event* event);
CC_SYNTHESIZE(TowerType, chooseTowerType, ChooseTowerType);
//选择的炮塔类型
private
:
// 分别表示箭塔、攻击塔、多方向攻击塔
Sprite* sprite1;
Sprite* sprite2;
Sprite* sprite3;
};
|
选择面板层的触摸响应过程与场景层的响应的原理一样,不过在选择层中我们将调用onTouchBegan(触摸点击开始事件)和onTouchEnded(触摸结束事件)的回调函数。
类似地,我们会先在TowerPanleLayer头文件中声明要响应的事件回调函数,接着在init方法中创建并绑定触摸事件。这里要注意一点:在选择面板中,我们设置了三个炮塔选项供玩家选择,每一个选项都可以处理触摸响应事件。所以,绑定需要绑定到每个选项上。
头文件中定义的三个精灵分别表示我们的三个选项,在init方法中添加如下代码初始化它们:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
auto sprite = Sprite::createWithSpriteFrameName(
"towerPos.png"
);
sprite->setPosition(Point(0, 0));
this
->addChild(sprite);
sprite2 = Sprite::createWithSpriteFrameName(
"mftower.png"
);
sprite2->setAnchorPoint( Point(0.5f, 0));
sprite2->setPosition(Point(0, sprite2->getContentSize().height/2));
this
->addChild(sprite2);
sprite1 = Sprite::createWithSpriteFrameName(
"arrowTower.png"
);
sprite1->setAnchorPoint( Point(0.5f, 0));
sprite1->setPosition(Point(-sprite2->getContentSize().width, sprite2->getContentSize().height/2));
this
->addChild(sprite1);
sprite3 = Sprite::createWithSpriteFrameName(
"multiDirTower.png"
);
sprite3->setAnchorPoint( Point(0.5f, 0));
sprite3->setPosition(Point(sprite2->getContentSize().width, sprite2->getContentSize().height/2));
this
->addChild(sprite3);
|
其中sprite是玩家在场景中选择的那块“瓦片”区域,它所处的位置也是炮塔将要生成的位置所在地。
之后同样是在init方法中,我们为每个选项绑定触摸事件:
1
2
3
4
5
6
7
8
9
10
|
// 创建事件监听器
auto touchListener = EventListenerTouchOneByOne::create();
// 设置是否向下传递触摸,true表示不向下触摸。
touchListener->setSwallowTouches(
true
);
touchListener->onTouchBegan = CC_CALLBACK_2(TowerPanleLayer::onTouchBegan,
this
);
touchListener->onTouchEnded = CC_CALLBACK_2(TowerPanleLayer::onTouchEnded,
this
);
_eventDispatcher->addEventListenerWithSceneGraphPriority(touchListener, sprite1);
_eventDispatcher->addEventListenerWithSceneGraphPriority(touchListener->clone(), sprite2);
_eventDispatcher->addEventListenerWithSceneGraphPriority(touchListener->clone(), sprite3);
|
这里要注意:当我们再次使用事件监听器的时候,需要使用 clone() 方法重新克隆一个,因为每个监听器在添加到事件调度器中时,都会为其添加一个已注册的标记,这就使得它不能够被添加多次。
实现触摸回调函数
绑定好触摸事件后,接下来仍是实现具体的触摸回调。在该层回调函数中我们要做的有:当玩家触碰到某选项时,重新设置其透明度,向玩家表明该项是被选中的;当玩家松开手时,确定其选择的炮塔类型,方便我们后面的获取和使用。代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
bool
TowerPanleLayer::onTouchBegan(Touch *touch, Event *event)
{
// 1
auto target =
static_cast
<Sprite*>(event->getCurrentTarget());
// 2
Point locationInNode = target->convertTouchToNodeSpace(touch);
// 3
Size size = target->getContentSize();
Rect rect = Rect(0, 0, size.width, size.height);
// 4
if
(rect.containsPoint(locationInNode))
{
target->setOpacity(180);
return
true
;
}
return
false
;
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
void
TowerPanleLayer::onTouchEnded(Touch* touch, Event* event)
{
auto target =
static_cast
<Sprite*>(event->getCurrentTarget());
// 5
if
(target == sprite1)
{
chooseTowerType = ARROW_TOWER;
}
else
if
(target == sprite2)
{
chooseTowerType = ATTACK_TOWER;
}
else
if
(target == sprite3)
{
chooseTowerType = MULTIDIR_TOWER;
}
else
{
chooseTowerType = ANOTHER;
}
}
|
- 返回触摸事件当前作用的目标节点。
- 把touch对象中保存的屏幕坐标转换到GL坐标,再转换到目标节点的本地坐标下。
在Node对象中有几个函数可以做坐标转换。convertToNodeSpace方法可以把世界坐标转换到当前node的本地坐标系中;convertToWorldSpace方法可以把基于当前node的本地坐标系下的坐标转换到世界坐标系中;convertTouchToNodeSpace这个函数可以把屏幕坐标系转换到GL坐标系,再转换到父节点的本地坐标下。 - 计算目标节点的矩形区域。
- 判断触碰点在不在目标节点的矩形区域内,即判断是否被选中。
- 根据选择的目标确定炮塔的类型。
判定玩家是否触摸到某炮塔选项的过程可用如下所示的原理图来解释:
如图:在一个240*360的屏幕内,当玩家触摸屏幕时,触摸对象中会保存下此时的屏幕坐标值(150, 210)。但由于Cocos2d-x中处理坐标一般都使用的是OpenGL坐标,所以这就需要把它转换为(150, 150)的Cocos2d-x坐标值。当我们判定触摸点在不在蓝色的Node内时,可以先计算出这个Node的矩形区域(0, 0, Node宽, Node高),再把触摸点(150, 150)转换到它本地坐标系下。这样就可以直接通过containsPoint方法来判断一个点在不在一个矩形区内了。事例中Node的坐标为(100, 100),所以转换后,触碰点的坐标就变成了(50, 50)。显然地,我们就可以判断点(50,50)在这个大小为(100,100)的矩形中了。
到此为止,我们已经得到了玩家选择的炮塔类型,接下来就可以回到场景层添加炮塔了。
添加炮塔
在场景层中,我们把添加炮塔的方法放在update函数体内,这样程序就会逐帧检测是否添加炮塔,其方法如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
|
void
PlayLayer::addTower()
{
if
(chooseTowerpanle != NULL )
{
auto type = chooseTowerpanle->getChooseTowerType();
if
(type == TowerType::ANOTHER)
{
return
;
}
Point matrixCoord = convertToMatrixCoord(towerPos);
int
MatrixIndex =
static_cast
<
int
>( matrixCoord.y * MAP_WIDTH + matrixCoord.x );
bool
noMoneyTips =
false
;
TowerBase* tower = NULL;
if
( type == TowerType::ARROW_TOWER )
{
if
( money >= 200 )
{
tower = ArrowTower::create();
money -= 200;
}
else
noMoneyTips =
true
;
}
else
if
( type == TowerType::ATTACK_TOWER )
{
if
( money >= 150 )
{
tower = AttackTower::create();
money -= 150;
}
else
noMoneyTips =
true
;
}
else
if
( type == TowerType::MULTIDIR_TOWER )
{
if
( money >= 200 )
{
tower = MultiDirTower::create();
money -= 200;
}
else
noMoneyTips =
true
;
}
if
(tower != NULL)
{
tower->setPosition(towerPos);
tower->runAction(Sequence::create(FadeIn::create(1.0f),NULL));
this
->addChild(tower);
instance->towerVector.pushBack(tower);
towerMatrix[MatrixIndex] = tower;
}
type = TowerType::ANOTHER;
chooseTowerpanle->setChooseTowerType(type);
this
->removeChild(chooseTowerpanle);
chooseTowerpanle = NULL;
if
( noMoneyTips ==
true
)
{
auto tips = Sprite::createWithSpriteFrameName(
"nomoney_mark.png"
);
tips->setPosition(towerPos);
this
->addChild(tips);
tips->runAction(Sequence::create(DelayTime::create(0.5f),
CallFunc::create(CC_CALLBACK_0(Sprite::removeFromParent, tips)),
NULL));
}
}
}
|
在这一过程中,我们会根据所选择的炮塔类型来创建炮塔,另外,还会根据当前玩家所持金币数,判断玩家是否有能力购买炮塔。如果玩家金币不足,则会提示玩家。提示方式与前面我们提示不可添加炮塔的方式一样。
对于金币,玩家必须为了它不停的奋斗,因为只有有了足够的金币才能购买到炮塔。当然,进入游戏一般都会有一笔小额的初始使用经费,它能满足玩家刚进入游戏时最基本的要求。在此后的过程,玩家就只有通过努力干掉小偷,从它们身上得到不同经额的回报了。这种游戏机制是不是很熟悉?呵呵,一般的塔防游戏都是这样设计的,我们这也叫遵守传统吧!