您的位置:首页 > 移动开发 > Cocos引擎

cocos2d-x-3.2塔防游戏开发2:建塔、角度的旋转、发射箭

2014-12-07 09:58 295 查看
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,
CC_CALLBACK_1(GameScene::selectTD,
this));
    auto mitem03=MenuItemSprite::create(bt03,bt03_select,
CC_CALLBACK_1(GameScene::selectTD,
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,0,0,0,0,0,0,0,0,0,0,0},
       {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
       {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
       {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
       {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
       {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
       {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
       {0,0,0,0,0,0,0,0,0,0,0,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->addCh
1630f
ild(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,nowRow,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 r,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 ang,int x,int
y,int dx,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;
           }
       }
    }
   
   
}
 
 
 
9.需要添加一个爆炸的效果,即定义一个爆炸的雷
 
#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,act2,
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();

 
11.添加钱数的显示
//在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",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));}
 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐