cocos2d-x-3.2塔防游戏开发2:建塔、角度的旋转、发射箭
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; } } }
} 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,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",(8-nowRow)*71); this->addChild(tips); tips->runAction(Sequence::create(DelayTime::create(0.8f), CallFunc::create(CC_CALLBACK_0(Sprite::removeFromParent,NULL));} (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |