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);

//3Sprite转为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,建塔---首先要在GameSCeneh中定义记录的塔

GameSceneH中:

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 =(offXoffY)。__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));}

相关文章

    本文实践自 RayWenderlich、Ali Hafizji 的文章《...
Cocos-code-ide使用入门学习地点:杭州滨江邮箱:appdevzw@1...
第一次開始用手游引擎挺激动!!!进入正题。下载资源1:从C...
    Cocos2d-x是一款强大的基于OpenGLES的跨平台游戏开发...
1.  来源 QuickV3sample项目中的2048样例游戏,以及最近《...
   Cocos2d-x3.x已经支持使用CMake来进行构建了,这里尝试...