1、创建箭塔
完成基类建设以后,接下来我们来创建一个最普通的炮塔——ArrowTower 箭塔。
一个箭塔最起码应该由以下的三部分组成,1)箭塔底座,2)弓箭,3)子弹。如下图所示:
2、放塔的逻辑思路和过程:
1.选中屏幕位置
2.判断该位置是否可以放塔
3.如果不可以放则显示 X 1秒钟
如果可以放并且在当前则在用户选中的行列没有放过(此时需要一个map[r][c]来记录)
显示3种可以选择的塔
4.选择要放的塔
5.在选中位置出现塔 (添加到图层 添加到集合 修改map[r][c]的标记)
6.建塔的面板
设定坐标
三个塔就是三个按钮,
•autoImage= ImageView::create ("towerPos.png");
•bt01=Button::create ("ArrowTower1.png");
•bt02=Button::create ("AttackTower1.png");
•bt02=Button::create ("MultiDirTower1.png");
•使用Button 如果选中某种塔则在+位置创建
•如果点击了屏幕其他位置整个Layout消失
3、在GameScene中添加触摸侦听
.h文件定义
virtual bool onTouchBegan(Touch *touch,Event*unused_event); virtual bool onTouchBegan(Touch *touch,Event *unused_event);
.cpp中实现
//触摸
auto listener=EventListenerTouchOneByOne::create();
listener->onTouchBegan=CC_CALLBACK_2(GameScene::onTouchBegan,this);
listener->onTouchMoved=CC_CALLBACK_2(GameScene::onTouchMoved,this);
listener->onTouchEnded=CC_CALLBACK_2(GameScene::onTouchEnded,this);
Director::getInstance()->getEventDispatcher()->addEventListenerWithSceneGraPHPriority(listener,this);
bool GameScene::onTouchBegan(Touch *touch, Event*unused_event){
if(this->getChildByTag(1001)!=NULL){
this->removeChildByTag(1001); //1001是那个精灵。当我们每次点击的时候,我们就移除前一个塔的面板
}
// CCLOG("点击了%f,%f",touch->getLocation().x,touch->getLocation().y);
//CCLOG("点击了地图的第%d行,第%d列",(int)touch->getLocation().y/71,
//(int)touch->getLocation().x/71);
nowRow=8-(int)(touch->getLocation().y/71);//确定点击的位置,tiled地图编辑器的0,0点是从左上角开始的
nowCol=(int)(touch->getLocation().x/71);//确定你点击点得行列,71是我缩小之后算出的尺寸
auto map=(TMXTiledMap*)this->getChildByTag(888);//得到地图
//CCLOG("点击了地图的第%d行,第%d列,地图的编号是%d",nowRow,nowCol,map->getLayer("bg")->getTileGIDAt(Vec2(nowCol,nowRow)));
bool canTouch=false;
int tid=map->getLayer("bg")->getTileGIDAt(Vec2(nowCol,nowRow));//得到的是点下点得编号
if(!map->getPropertiesForGID(tid).isNull()){//获取属性
auto tileTemp=map->getPropertiesForGID(tid).asValueMap();//获取属性的值
if(!tileTemp.empty()){ //如果值不为空,就获取canTouch编号是1
//tileTemp.at("canTouch").asInt();
canTouch=true; //canTouch=1这里就可以放塔
//CCLOG("这里可以放塔tidcanTouch=%d",tileTemp.at("canTouch").asInt());
}
}
if (canTouch) {
CCLOG("塔的选择面板");
addTDSelect(8-nowRow,nowCol);//弹出箭塔的提示
}else{
//显示那个叉号
auto tips=Sprite::createWithSpriteFrameName("no.png");//根据帧来添加一个精灵
tips->setPosition(nowCol*71,(8-nowRow)*71);
tips->setAnchorPoint(Vec2(0,0));
this->addChild(tips);
auto act=DelayTime::create(0.5);//必须加延迟否则就会刚产生就消失
auto act1=CallFunc::create(CC_CALLBACK_0(Sprite::removeFromParent,tips));
tips->runAction(Sequence::create(act,act1,NULL));
}
return true;
}
4、弹出塔的选择面板
5、GameScene.h中
void addTDSelect(int r,int c);//添加塔的选择面板
GameScene.cpp中
void GameScene::addTDSelect(int r,int c){
auto Image= Sprite::createWithSpriteFrameName("towerPos.png");//创建一个精灵
int height=Image->getContentSize().height;
int width=Image->getContentSize().width;
auto bt01= Sprite::createWithSpriteFrameName("ArrowTower1.png");
auto bt01_select= Sprite::createWithSpriteFrameName("ArrowTower1.png");
bt01_select->setScale(1.1);
auto bt02= Sprite::createWithSpriteFrameName("AttackTower1.png");
auto bt02_select= Sprite::createWithSpriteFrameName("AttackTower1.png");
bt02_select->setScale(1.1);
auto bt03= Sprite::createWithSpriteFrameName ("MultiDirTower1.png");
auto bt03_select= Sprite::createWithSpriteFrameName ("MultiDirTower1.png");
bt03_select->setScale(1.1);
//将3个Sprite转为Menu接收用户事件
auto mitem01=MenuItemSprite::create(bt01,bt01_select,CC_CALLBACK_1(GameScene::selectTD,this));
auto mitem02=MenuItemSprite::create(bt02,bt02_select,this));
auto mitem03=MenuItemSprite::create(bt03,bt03_select,this));//回调selectTD函数
mitem01->setTag(10);
mitem02->setTag(11);
mitem03->setTag(12);
mitem01->setAnchorPoint(Vec2(1,0));
mitem02->setAnchorPoint(Vec2(0.5,0));
mitem03->setAnchorPoint(Vec2(0,0));
auto menuTD=Menu::create(mitem01,mitem02,mitem03,nullptr);
menuTD->setPosition(Vec2::ZERO);
Image->addChild(menuTD);
mitem01->setPosition(Vec2(0,height));
mitem02->setPosition(Vec2(width/2,height));
mitem03->setPosition(Vec2(width,height));
Image->setTag(1001);
this->addChild(Image);
Image->setAnchorPoint(Vec2(0,0));
Image->setPosition(c*71,r*71);
}
//选择塔的时候会回调这个selectTD,建塔---首先要在GameSCene。h中定义记录的塔
GameScene。H中:
void selectTD(Ref*obj);//回调是用的--建塔
int mapinfo[9][16]={
{0,0},
{0,
{0,0}
};
static Vector<Bullet *>allBullet;-----保存所有的子弹
staticVector<Enemy *>allEnemy;----这是为了做检测时保存所有的怪物
//我们在cpp中进行初始化,
void GameScene::selectTD(Ref*obj){
auto item=(MenuItemSprite*)obj;
switch (item->getTag()) {
case 10://第一种类型的塔
{
auto newTd=TD::createTD(1,8-nowRow,nowCol);
this->addChild(newTd);
if (this->money>=newTd->price) {//判断剩余money是否大于第一种类型塔的价格
//是---箭塔,,,不是---不能建设
mapinfo[nowRow][nowCol]=1;//标记这个位置已经有塔----这时候我们要在GameSCene中定义mapinfo
this->money-=newTd->price;//如果钱够的话,就钱数-=塔的价格---花钱了就是
auto moneyLabel=(Label*)this->getChildByTag(2000)->getChildByTag(2002);
moneyLabel->setString(StringUtils::format("%d",money));//改变钱数的标签
}else{
//充值
this->removeChild(newTd);//钱不够就移除刚建的
auto tips = Sprite::createWithSpriteFrameName("nomoney_mark.png");
tips->setAnchorPoint(Vec2(0,0));
tips->setPosition(nowCol*71,(8-nowRow)*71);
this->addChild(tips);
tips->runAction(Sequence::create(DelayTime::create(0.8f),
CallFunc::create(CC_CALLBACK_0(Sprite::removeFromParent,tips)),
NULL));
}
} break;
case 11://第er种类型的塔
{
auto newTd=TD::createTD(2,nowRow,nowCol);
this->addChild(newTd);
} break;
case 12://第san种类型的塔
{
auto newTd=TD::createTD(3,nowCol);
this->addChild(newTd);
} break;
default:
break;
}
//移除旧的塔的选择
if(this->getChildByTag(1001)!=nullptr)
{
this->getChildByTag(1001)->removeFromParentAndCleanup(true);
}
}
6.定义一个TD的类
//在TD的.h文件中
#include"cocos2d.h"
#include"GameScene.h"
USING_NS_CC;
class TD:public Node{
public:
int tx,ty;
int trow,tcol;
int type;
int price;
int dx,dy;//目标点
int act;
int ang;//角度
CREATE_FUNC(TD);
bool init();
static TD *createTD(int t,int r,int c);
void moveAndShot(float t);//移动
void fire();//开火
};
//旋转和攻击敌人:这里我们需要算出旋转角度,
//检测炮塔视线范围内距离它最近的敌人。
//如果最近的敌人nearestEnemy存在,弓箭则会旋转,所以我们需要计算弓箭旋转的角度和旋转时间。__关于旋转角度,可以利用三角正切函数来计算,如下图所示:
//炮塔与敌人的之间的角度关系可以表示为: tan a = offY/offX,而rotateVector =(offX,offY)。__getAngle方法将返回rotateVector向量与X轴之间的弧度数。但旋转弓箭我们需要的是角度,所以这就需要把弧度rotateRadians转化为角度。不过还好,Cocos2d-x中提供了能把弧度转化为角度的宏CC_RADIANS_TO_DEGREES,这样我们就可以很方便的转化了。__另外,Cocos2d-x中规定顺时针方向为正,这显然与我们计算出的角度方向相反,所以转化的时候需要把角度a变为-a。
//speed表示炮塔旋转的速度,0.5 / M_PI其实就是 1 / 2PI,它表示1秒钟旋转1个圆。__rotateDuration表示旋转特定的角度需要的时间,计算它用弧度乘以速度
TD.cpp文件中
#include"TD.h"
bool TD::init(){
if (!Node::init()) {
return false;
}
return true;
}
TD *TD::createTD(int t,int c){
TD * td=TD::create();
switch (t) {
case 1://弓箭塔
{ td->price=200;
auto plate1 = Sprite::createWithSpriteFrameName("baseplate.png");//底座
plate1->setAnchorPoint(Vec2::ZERO);
td->addChild(plate1);
plate1->setTag(10);
td->setAnchorPoint(Vec2::ZERO);
td->setPosition(c*71,r*71);
td->type=t;
td->trow=r;
td->tcol=c;
td->tx=c*71;
td->ty=r*71;
auto rotateArrow = Sprite::createWithSpriteFrameName("arrow.png");//剑
rotateArrow->setPosition(36,55);
rotateArrow->setTag(11);
plate1->addChild(rotateArrow);
break;
}
default:
break;
}
// 计划任务每隔0.5秒旋转这个塔发射子弹---1—每各塔都会产生子弹—所以需要定义一个所有子弹的向量
td->schedule(schedule_selector(TD::moveAndShot),0.5);
return td;
}
void TD::moveAndShot(float t){
if (GameScene::allEnemy.size()==0) {
return;
}
//找到理你最近的敌人攻击
int index=0;
int min=GameScene::allEnemy.at(0)->getPosition().getDistance(this->getPosition());
for (int i=0; i<GameScene::allEnemy.size(); i++) {
int far=GameScene::allEnemy.at(i)->getPosition().getDistance(this->getPosition());
if (far<min) {
index=i;
min=far;
}
}
dx=GameScene::allEnemy.at(index)->getPosition().x;//敌人的坐标就是目标点
dy=GameScene::allEnemy.at(index)->getPosition().y;//目标点
//2
Vec2 rotateVector = GameScene::allEnemy.at(index)->getPosition() - this->getPosition();//得到距离
float rotateRadians = rotateVector.getAngle();//旋转获得弧度----getAngle方法将返回rotateVector与x轴间的弧度数
float rotateDegrees = CC_RADIANS_TO_DEGREES(-1 * rotateRadians);//我们把弧度数转化成角度
ang= rotateDegrees;
// 3
float speed = 0.5 / M_PI;// speed表示炮塔旋转的速度,0.5 / M_PI其实就是 1 / 2PI,它表示1秒钟旋转1个圆。
float rotateDuration = fabs(rotateRadians * speed);
// rotateDuration表示旋转特定的角度需要的时间,计算它用弧度乘以速度。
// 4
//这句话的意思是0.5秒的时间内箭也要旋转这么大得角度
this->getChildByTag(10)->getChildByTag(11)->runAction( Sequence::create(RotateTo::create(rotateDuration,rotateDegrees),CallFunc::create(CC_CALLBACK_0(TD::fire,this)),NULL));
}//移动和攻击
void TD::fire(){
Bullet*b=Bullet::createBullet(1,ang,this->tx,this->ty, dx,dy);
GameScene::allBullet.pushBack(b);
this->getParent()->addChild(b);
}//开火
6、这时我们同样需要定义一个子弹类
//Bullet。H文件中
#include"cocos2d.h"
USING_NS_CC;
class Bullet:public Node{
public:
int bx,by;
int objx,objy;//目标点
int type;
CREATE_FUNC(Bullet);
bool init();
static Bullet*createBullet(int t,int ang,int x,int y,int dx,int dy);
void killMe();
};
//Bullet.cpp文件中
#include"Bullet.h"
Bullet*Bullet::createBullet(int t,int dy){
Bullet*newb=Bullet::create();
switch (t) {
case 1:{
Sprite*spbullet=Sprite::createWithSpriteFrameName("arrowBullet.png");
newb->bx=x;
newb->by=y;
newb->setPosition(x+35,y+45);
spbullet->setRotation(ang);//角度
newb->addChild(spbullet);
newb->objx=dx;
newb->objy=dy;
} break;
default:
break;
}
float far=Vec2(x,y).getDistance(Vec2(dx,dy));
float time=far/300;
auto act1=MoveTo::create(time,Vec2(dx,dy));
auto act2=CallFunc::create(CC_CALLBACK_0(Bullet::killMe,newb));//如果是移动到目标点,那么我们就让子弹消失自杀
newb->runAction(Sequence::create(act1,act2,NULL));
return newb;
}//发射子弹到塔里去发
bool Bullet::init(){
if (!Node::init()){
return false;
}
return true;
}
void Bullet::killMe(){
this->removeFromParent();
}
7.//我们发射子弹实在塔中发射,所以在塔中有一个计划任务
// 计划任务每隔0.5秒旋转这个塔发射子弹---1—每各塔都会产生子弹—所以需要定义一个所有子弹的向量
td->schedule(schedule_selector(TD::moveAndShot),0.5);
staticVector<Bullet *>allBullet;-----保存所有的子弹
staticVector<Enemy *>allEnemy;----这是为了做检测时保存所有的怪物
//我们需要在GameSCene.cpp中进行初始化,--并且每当产生一个敌人,我们都要把它添加到敌人的向量中—
而且在敌人的类中,敌人如果消失,那么记得要让向量中的敌人也消失
Vector<Bullet *>GameScene::allBullet;
Vector<Enemy *> GameScene::allEnemy;
因为我们把敌人添加到了向量中,所以我们就可以找到敌人---当我们找到离我们最近的敌人,我们就调用fire的方法
8.在GameScene中加入碰撞检测—--游戏逻辑
void update(float t);//碰撞
GameScene.cpp中
//碰撞
this->scheduleUpdate();
void GameScene::update(float t){
for (int i=0; i<GameScene::allBullet.size(); i++) {
Bullet*b=GameScene::allBullet.at(i);
for (int j=0; j<GameScene::allEnemy.size(); j++) {
Enemy*e=GameScene::allEnemy.at(j);
Rect rb(b->getPosition().x,b->getPosition().y,35,32);
Rect re(e->getPosition().x,e->getPosition().y,127,151);
if (rb.intersectsRect(re)){
e->hp--;
e->changeHp();
if(e->hp<=0){//如果怪物的hp<0,敌人死了
//赚钱--可以做一个switch
this->money+=100;
auto moneyLabel=(Label *)this->getChildByTag(2000)->getChildByTag(2002);
moneyLabel->setString(StringUtils::format("%d",money));
//爆炸效果
auto boom=Boom::newBoom(e->getPosition().x,e->getPosition().y);
this->addChild(boom);
//移除敌人
e->removeFromParent();
allEnemy.eraSEObject(e);
}
b->removeFromParent();
GameScene::allBullet.eraSEObject(b);
i--;
break;//记得break;
}
}
}
}
#include"cocos2d.h"
USING_NS_CC;
class Boom:public Node
{
public:
int bx,by;
CREATE_FUNC(Boom);
bool init();
static Boom* newBoom(int x,int y);
void killMe();
};
#include"Boom.h"
Boom* Boom::newBoom(int x, int y){
Boom*boom=Boom::create();
Vector<SpriteFrame*>allf;
Sprite*sp=Sprite::create();
boom->addChild(sp);
sp->setPosition(x,y);
for (int i=1; i<6; i++) {
SpriteFrame*sf=SpriteFrameCache::getInstance()->getSpriteFrameByName(StringUtils::format("explode1_%d.png",i));
allf.pushBack(sf);
}
auto animation=Animation::createWithSpriteFrames(allf);
animation->setDelayPerUnit(0.3);
auto animate=Animate::create(animation);
auto act2=CallFunc::create(CC_CALLBACK_0(Boom::killMe,boom));
sp->runAction(Sequence::create(animate,NULL));
return boom;
}
bool Boom::init(){
if (!Node::init()) {
return false;
}
return true;
}
void Boom::killMe(){
this->removeFromParent();
}
//这样的话打死敌人时就会产生爆炸效果
10.给敌人添加血槽
//添加血槽,记得在敌人类中引用ui;
auto hp=LoadingBar::create("sliderProgress2.png");
hp->setTag(1000);
hp->setPercent(100);//满血100
newe->addChild(hp,1);
hp->setPositionY(60)
void changeHp();//改变血
int fullhp;//满血
void Enemy::changeHp(){
auto hp=(LoadingBar*)this->getChildByTag(1000);
int progress=(this->hp/(float)fullhp*100);//当前的hp除以fullhp*100
hp->setPercent(progress);//剩余的血量
}
//在碰撞检测中,敌人的hp--,那么就改变hp
//在碰撞检测中调用chanehp;
e->chanep();
//在GameScene中
//初始化钱数
this->money=500;
auto spritetool = Sprite::createWithSpriteFrameName("toolbg.png");//建显示钱工具
spritetool->setAnchorPoint(Point(0.5f,1));
spritetool->setPosition (Vec2(Director::getInstance()->getWinSize().width /2,
Director::getInstance()->getWinSize().height));
this->addChild(spritetool);
spritetool->setTag(2000);
//
auto moneyLabel = Label::createWithBMFont("bitmapFontChinese.fnt"," ");//显示钱数量的标签
moneyLabel->setPosition(Vec2(spritetool->getContentSize().width /8,spritetool->getContentSize().height /2));
moneyLabel->setAnchorPoint(Point(0,0.5f));
auto moneyText = std::to_string(money);
moneyLabel->setString(moneyText);
moneyLabel->setTag(2002);
spritetool->addChild(moneyLabel);
//在建塔的逻辑处判断减钱
if (this->money>=newTd->price) {//判断剩余money是否大于第一种类型塔的价格
//是---箭塔,,,不是---不能建设
mapinfo[nowRow][nowCol]=1;//标记这个位置已经有塔
this->money-=newTd->price;//如果钱够的话,就钱数-=塔的价格---花钱了就是
auto moneyLabel=(Label*)this->getChildByTag(2000)->getChildByTag(2002);
moneyLabel->setString(StringUtils::format("%d",(8-nowRow)*71);
this->addChild(tips);
tips->runAction(Sequence::create(DelayTime::create(0.8f),
CallFunc::create(CC_CALLBACK_0(Sprite::removeFromParent,NULL));}