使用Cocos2d-x-3.0游戏引擎。编写一个塔防游戏 part06

Previous: http://www.cocoachina.com/bbs/read.php?tid=203362

  1. Game Hud
    游戏HUD

首先,我们需要调整一下屏幕,毕竟我们创建了一些放置在地图外的物体。这么一块黑色区域确实会让一些人不太喜欢偶尔也会引起一些bug。

把这些代码输入AppDelete.cpp

bool AppDelegate::applicationDidFinishLaunching()
{
    ……
glview->setDesignResolutionSize(864, 640, kResolutionExactFit);
    ……
}
```

 
  

  现在我们就要新建两对类,Tower.h和相应cpp还有GameHud.h及cpp,所以我们将有机会选择不同类型的防御者。(就像我们建立不同类型的敌人那样,一旦你掌握了这个教程你就能自己动手试着完成这一任务)。现在将他们部署在地图上。

  
取消我们在教程第二部分里的DataModel.h代码注释:
//Vector towers;    // We will deal with it later.
```


在Tower.h里面

#pragma once
#include "cocos2d.h"
#include "Creep.h"

class Tower: public Sprite 
{
public:
    int range;
    Sprite* sprite;
    Creep* target;
    Sprite * selSpriteRange;
    Creep* getClosesTarget();
    CREATE_FUNC(Tower);
};

class MachineGunTower : public Tower 
{
public:
    static Tower* tower();
    bool virtual init();
    void towerLogic(float dt);
};

```


正如你所看到的,在这里我们首先创建了一个基本的类,里面包括它的射程,距离最近目标,并继承自类Sprite

在Tower.cpp里面

#include "Tower.h"
#include "DataModel.h"

Tower* MachineGunTower::tower()
{
    Tower* tower = Tower::create();
    tower->sprite = Sprite::create("MachineGunTurret.png");
    tower->setScale(0.5);
    tower->addChild(tower->sprite, 0);
    tower->range = 200;
    tower->schedule(schedule_selector(towerLogic), 0.2);
    return tower;
}

```

 
就像之前那样。‘MachineGunTower’继承自‘Tower’,并载入图片素材,设置它的比例大小和射程。最后用‘schedule’功能每0.2秒刷新‘towerLogic’功能。

bool MachineGunTower::init()
{
    if (!Sprite::init()) 
    {
        return false;
    }
    return true;
}


void MachineGunTower::towerLogic(float dt)
{
    // Coming soon…
}
```



取消我们在教程第二部分DataModel.h 里的注释:
//GameHUD* _gameHUDLayer;  // We will deal with it later.
```



在GameHUD.h中

#pragma once
#include "cocos2d.h"

USING_NS_CC;

class GameHUD: public Layer 
{
public:
    Sprite* background;
    Sprite* selSpriteRange;
    Sprite* selSprite;
    Vector movableSprites;
    static GameHUD* _sharHUD;
    virtual bool init();
    static GameHUD* shareHUD();
    CREATE_FUNC(GameHUD);
    virtual void onEnter();
    bool onTouchBegan(Touch *touch, Event *event);
    void onTouchMoved(Touch *touch, Event *event);
    void onTouchEnded(Touch* touch, Event* event);
};
```


  

是的。他应该能能够被拖拽到任何地方,也因此我们需要监听事件‘Listener Event’(onTouchXXX)。
然后在GameHUD.cpp里面:

#include "GameHUD .h"
#include "DataModel.h"

GameHUD* GameHUD::_sharHUD;

bool GameHUD::init()
{
    if (!Layer::init()) 
    {
        return false;
    }

    Size winSize = CCDirector::getInstance()->getWinSize();

    // Draw the background of the game HUD
    CCTexture2D::setDefaultAlphaPixelFormat(kCCTexture2DPixelFormat_RGBA8888);
    background = Sprite::create("hud.png");
    background->setScaleX (2);
    background->setAnchorPoint(ccp(0, 0));
    this->addChild(background); 
    //CCTexture2D::setDefaultAlphaPixelFormat(kCCTexture2DPixelFormat_Default);
```

   
Background

  
MachineGunTower…

  // 读取防御塔的图片素材,我们会把他们绘制在游戏的HUD层上。

    Vector p_w_picpath;
    p_w_picpath.pushBack(StringMake("MachineGunTurret.png"));
    p_w_picpath.pushBack(StringMake("MachineGunTurret.png"));
    p_w_picpath.pushBack(StringMake("MachineGunTurret.png"));
    p_w_picpath.pushBack(StringMake("MachineGunTurret.png"));
    for (int i = 0; i < p_w_picpath.size(); ++i)
    {
        String* image = p_w_picpath.at(i);
        auto *sprite = Sprite::create(image->getCString());
        float offsetFraction = ((float)(i + 1)) / (p_w_picpath.size() + 1);
        sprite->setScale(0.6);
        sprite->setPosition(ccp(winSize.width*offsetFraction, 35));
        this->addChild(sprite);
        movableSprites.pushBack(sprite);
    }

    return true;
}

GameHUD* GameHUD::shareHUD()
{
    if (_sharHUD == NULL)
    {
        _sharHUD = GameHUD::create();
    }

    return _sharHUD;
}

void GameHUD::onEnter()
{
    Layer::onEnter();

    auto listener = EventListenerTouchOneByOne::create();
    listener->setSwallowTouches(true);

    listener->onTouchBegan = CC_CALLBACK_2(GameHUD::onTouchBegan, this);
    listener->onTouchMoved = CC_CALLBACK_2(GameHUD::onTouchMoved, this);
    listener->onTouchEnded = CC_CALLBACK_2(GameHUD::onTouchEnded, this);

    auto dispatcher = Director::getInstance()->getEventDispatcher();

    dispatcher->addEventListenerWithSceneGraphPriority(listener, this);
}


bool GameHUD::onTouchBegan(Touch *touch, Event *event)
{
    Point touchLocation = this->convertToWorldSpace(this->convertTouchToNodeSpace(touch));

    Sprite * newSprite = NULL;
    for each(Sprite* sprite in this->movableSprites)  //  C++11
    //for (int i = 0; i < movableSprites.size(); i++)  // Use this if your VC doesn’t support C++11. 如果你的VC不支持C++11,用这行代码
    {
    // Sprite* sprite = (Sprite*)(movableSprites.at(i));  // Use this if your VC doesn’t support C++11. 如果你的VC不支持C++11,用这行代码

                Rect pos_rect = Rect((sprite->getPositionX()-sprite->getContentSize().width/2), (sprite->getPositionY()-sprite->getContentSize().height/2), sprite->getContentSize().width, sprite->getContentSize().height);
                // This determines the area which you can drag our ‘tower’ 这里是决定了你能够将“塔”拖拽的区域。

        float xMin = pos_rect.getMinX();
        float xMax = pos_rect.getMaxX();
        float yMIn = pos_rect.getMinY();
        float yMax = pos_rect.getMaxY();
        if (pos_rect.containsPoint(touchLocation))
        {
            DataModel *m = DataModel::getModel();
            
            selSpriteRange = Sprite::create("Range.png");
            selSpriteRange->setScale(4);
            this->addChild(selSpriteRange, -1);
            selSpriteRange->setPosition(sprite->getPosition());

            newSprite = Sprite::createWithTexture(sprite->getTexture()); //sprite;
            newSprite->setPosition(sprite->getPosition());
            newSprite->setScale(0.6);
            selSprite = newSprite;
            this->addChild(newSprite);
        }
    }
        
    return true;
}

void GameHUD::onTouchMoved(Touch* touch,Event* event) 
{
    Point touchLocation = this->convertToWorldSpace(this->convertTouchToNodeSpace(touch));

    Point oldTouchLocation = touch->getPreviousLocationInView();
    oldTouchLocation = Director::getInstance()->convertToGL(oldTouchLocation);
    oldTouchLocation = this->convertToNodeSpace(oldTouchLocation);

    Point translation = ccpSub(touchLocation,oldTouchLocation);

    if (selSprite) 
    {
        Point newPos = selSprite->getPosition()+translation;
        selSprite->setPosition(newPos);
        selSpriteRange->setPosition(newPos);

        DataModel *m = DataModel::getModel();
        Point touchLocationInGameLayer = m->_gameLayer->convertTouchToNodeSpace(touch);

        BOOL isBuildable = m->_gameLayer->canBuildOnTilePosition(touchLocationInGameLayer);
        if (isBuildable) 
        {
            selSprite->setOpacity(200);
        }
        else 
        {
            selSprite->setOpacity(50);
        }
    }
}
```



等一下。你是否注意到了‘bool isBuildable’? 我们接下来应该要为我们的瓷砖设置一些属性。
 
  
void GameHUD::onTouchEnded(Touch* touch, Event* event)
{
    Point touchLocation = this->convertTouchToNodeSpace(touch);
    DataModel *m = DataModel::getModel();

    if (selSprite) 
    {
        Rect backgroundRect = Rect(background->getPositionX(),
            background->getPositionY(),
            background->getContentSize().width,
             background->getContentSize().height);

        if (!backgroundRect.containsPoint(touchLocation) && m->_gameLayer->canBuildOnTilePosition(touchLocation))
        {
            Point touchLocationInGameLayer = m->_gameLayer->convertTouchToNodeSpace(touch);
            m->_gameLayer->addTower(touchLocationInGameLayer);
        }

        this->removeChild(selSprite,true);
        selSprite = NULL;
        this->removeChild(selSpriteRange,true);
        selSpriteRange = NULL;
    }
}
```



现在,把这些东西扔进TutorialScene.h里面

……
#include "GameHUD .h"
……

class TutorialScene : public cocos2d::Layer
{
public:
……
    void addTower(Point pos);
    Point tileCoordForPosition(Point position);
    bool canBuildOnTilePosition(Point pos);
    Point boundLayerPos(Point newPos);

    Point position; 
    GameHUD *gameHUD; 
……
private:
    …...
};

In TutorialScene.cpp:
在TutorialScene.cpp:

……
#include "Tower.h"
……
Scene* TutorialScene::createScene()
{
……
    auto myGameHUD = GameHUD::shareHUD(); 
    m->_gameHUDLayer = myGameHUD; 

    scene->addChild(myGameHUD, 1);
……
}
```



再一次,我们使用了‘DataModel’来记录‘gameHUDLayer’。

Point TutorialScene::tileCoordForPosition(Point position)   
{
    int x = position.x / this->_tileMap->getTileSize().width;
    int y = ((this->_tileMap->getMapSize().height * this->_tileMap->getTileSize().height) - position.y) / this->_tileMap->getTileSize().height;
    return ccp(x, y);
}
```



对int y的公式感到疑惑么?因为坐标原点是放置在地图的左上角的。所以接下来的图片应该能帮助你的理解。
  
The original coordinate


bool TutorialScene::canBuildOnTilePosition(Point pos)   
{
    Point towerLoc = this->tileCoordForPosition(pos);
    int tileGid = this->_background->getTileGIDAt(towerLoc);
    Value props = this->_tileMap->getPropertiesForGID(tileGid);
    ValueMap map = props.asValueMap();
    int type_int;
    if (map.size() == 0)
    {
        type_int = 0;
    }
    else
    {
        type_int = map.at("buildable").asInt();
    }

    if (1 == type_int)
    {
        return true;
    }
    return false;
}
    

void TutorialScene::addTower(Point pos) 
{ 
    DataModel *m = DataModel::getModel();

    Tower *target = NULL ;
    Point towerLoc = this->tileCoordForPosition(pos);
    int tileGid = this->_background->tileGIDAt(towerLoc);
    Value props = this->_tileMap->propertiesForGID(tileGid);
    ValueMap map = props.asValueMap();

    int type_int = map.at("buildable").asInt();
    if (1 == type_int) 
    {
        target = MachineGunTower::tower();
        target->setPosition(ccp((towerLoc.x * 32) + 16, this->_tileMap->getContentSize().height - (towerLoc.y * 32) - 16));
        this->addChild(target,1);
        target->setTag(1);
        m->towers.pushBack(target);
    }
    else 
    {
        log("Tile Not Buildable");
    }
}
```



检测属性和其相应的值。

Point TutorialScene::boundLayerPos(Point newPos)  
{
    Size winSize = CCDirector::getInstance()->getWinSize();
    Point retval = newPos;
    retval.x = MIN(retval.x, 0);
    retval.x = MAX(retval.x, _tileMap->getContentSize().width + winSize.width);
    retval.y = MIN(0, retval.y);
    retval.y = MAX(_tileMap->getContentSize().height + winSize.height, retval.y);
    return retval;
}
```



这个功能决定了你能拖拽你的Tower的最大范围。


看看结果如何:
  


未完待续~(一日一更)

Next:http://www.cocoachina.com/bbs/read.php?tid=203644

我是来学习的da

:3::3::3:好!!!帖!!!

很好的分享,支持以下

要用foreach特性,这句for each(Sprite* sprite in this->movableSprites) // C++11
这样写不对,难道是编辑器的问题?
应该是for (Sprite* sprint : movableSprites)

你是用哪个编辑器?我的是VC2012 + VA补丁,对C++11不完全支持。注释里的备用代码也不能用么?

我用的是x-code 也是楼上的问题

那我的注释代码能用么?还是4L的代码可行?没有Mac试不了X-code

4L的可以使用

在移动怪物 添加上去的时候总是报错。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。

已解决。地图上的地图块属性 disable没有加。。加上去就好了。

点击白兔,拖动后就出错
Point touchLocationInGameLayer=m->_gameLayer->convertTouchToNodeSpace(touch);
运行到这一行就出错了
0x00EAED34 处的第一机会异常(在 tower.exe 中): 0xC0000005: 读取位置 0xCDCDCDCD 时发生访问冲突。
0x00EAED34 处有未经处理的异常(在 tower.exe 中): 0xC0000005: 读取位置 0xCDCDCDCD 时发生访问冲突。
线程 0x3540 已退出,返回值为 0 (0x0)。
线程 0x31f0 已退出,返回值为 0 (0x0)。
程序“ tower.exe”已退出,返回值为 0 (0x0)。

如果点击其他地方移动后,出错在这行
Point newPos=selSprite->getPosition()+translation;