2015-11-23 16:27:27 qq_22984991 阅读数 593
  • Cocos2d-x 3.x项目实战:星空大战(射击类游戏)

    本课程是《从零开始掌握Cocos2d-x 3.0(基于3.0正式版) 》课程配套的项目实战课程。建议对Cocos2d-x 3.0不了解的同学先学习该课程。本课程将利用《从零开始掌握Cocos2d-x 3.0(基于3.0正式版) 》中讲解的知识开发一款射击类游戏:星空大战(与打飞机游戏类似)。从这个项目中可学习到如何利用Cocos2d-x 3.0的相关知识开发一款完整的射击类游戏。

    18362 人正在学习 去看看 李宁

上次向大家介绍了底层的地图逻辑,这次来看看视觉层面,也就是地图图片的绘制。

大型地图肯定不能一次绘制,我的方法是运用缓存然后绘制出“看得到”的地图部分。

· 地图分为很多张屏幕大小的图片,在我这里是1000*500,每次只需要绘制四张图片必然能包括屏幕的所有范围,如图:

这里写图片描述
中间的黑色线框为屏幕,黑白色的方块为地图图片

下面是具体实现:

#ifndef _VISION_MAP_H_
#define _VISION_MAP_H_
#include"GlobalEntity.h"
#include"NullSprite.h"
#include"Hero.h"
using namespace cocos2d;

#define MAX_VISION_LAND 10
#define MAX_VISION_PLANTS 10
#define MAP_Z_ORDER 0
#define LAND_Z_ORDER 10
#define CONTROLLER_Z_ORDER 100
#define VISION_FPS 40
//from 0 to 401 are entities' Z-ORDERs
typedef std::function<void()> VisionMapCB;//回调函数对象

class VisionMap:public Layer
{
public:
    VisionMap();
    ~VisionMap();
    virtual bool init();
    CREATE_FUNC(VisionMap);
    void handleMessage();
    virtual bool setCache();
    bool drawMap();
    void clearCache();
    void scheduleFunc(float fl);
    void getVisionLand();
    void getVisionPlant();
protected:
    static Point _lastHeroPosition;
private:
    Sprite* _displayedMap[4];
    Vector<Sprite*> _displayedLand;
    Vector<Sprite*> _displayedPlant;
    bool _firstDraw;
    Hero* _hero;
};



#endif

· 消息处理器和init里的初始化暂且不表,先看加载缓存:

bool VisionMap::setCache()
{
    //normal map Img
    for (int i = 0; i < 4; i++)
    {
        Texture2D* texture = Director::getInstance()->getTextureCache()->addImage(String::createWithFormat("Environment_%d.png", i)->getCString());
        auto spriteBatchNode = SpriteBatchNode::createWithTexture(texture);
        this->addChild(spriteBatchNode);
        SpriteFrameCache::getInstance()->addSpriteFramesWithFile(String::createWithFormat("Environment_%d.plist", i)->getCString(),
            String::createWithFormat("Environment_%d.png", i)->getCString());
    }
    //end

    //land Img test
    auto pSpriteFrame = SpriteFrame::create("land_0.png", Rect(0, 0, 200, 200));
    SpriteFrameCache::getInstance()->addSpriteFrame(pSpriteFrame, "land_0.png");
    //end

    return true;
}

此处使用了texturePacker把图片压缩,land的美术资源还没画好,先用圆形代替,以便测试碰撞检测

· 重点来了,地图的绘制:

bool VisionMap::drawMap()
{
    int col = (int)(_hero->getPosition().x*RECT_SIZE - HERO_LOCATION_SCREEN.x) / IMG_WIDTH;
    int row = (int)(_hero->getPosition().y*RECT_SIZE - HERO_LOCATION_SCREEN.y) / IMG_HEIGHT;
    int colRemainder = (int)(_hero->getPosition().x*RECT_SIZE - HERO_LOCATION_SCREEN.x) % IMG_WIDTH;
    int rowRemainder = (int)(_hero->getPosition().y*RECT_SIZE - HERO_LOCATION_SCREEN.y) % IMG_HEIGHT;

    if (!_firstDraw)
    {
        _displayedMap[0] = Sprite::createWithSpriteFrameName(String::createWithFormat("environment_%d_%d.png", row, col)->getCString());
        _displayedMap[1] = Sprite::createWithSpriteFrameName(String::createWithFormat("environment_%d_%d.png", row + 1, col)->getCString());
        _displayedMap[2] = Sprite::createWithSpriteFrameName(String::createWithFormat("environment_%d_%d.png", row + 1, col + 1)->getCString());
        _displayedMap[3] = Sprite::createWithSpriteFrameName(String::createWithFormat("environment_%d_%d.png", row, col + 1)->getCString());
        for (int i = 0; i < 4; i++)
        {
            this->addChild(_displayedMap[i],-100);
            _displayedMap[i]->setAnchorPoint(Point::ZERO);
        }
        _firstDraw = true;
    }
    //if (_lastHeroPosition.x + IMG_WIDTH <= _hero->getPosition().x || _hero->getPosition().x <= _lastHeroPosition.x - IMG_WIDTH ||
    //  _lastHeroPosition.y + IMG_HEIGHT <= _hero->getPosition().y || _hero->getPosition().y <= _lastHeroPosition.y - IMG_HEIGHT)
    //{
    //  

    //  _lastHeroPosition = _hero->getPosition();
    //}
    _displayedMap[0]->setDisplayFrame(SpriteFrameCache::getInstance()->getSpriteFrameByName((String::createWithFormat("environment_%d_%d.png", row, col)->getCString())));
    _displayedMap[1]->setDisplayFrame(SpriteFrameCache::getInstance()->getSpriteFrameByName((String::createWithFormat("environment_%d_%d.png", row + 1, col)->getCString())));
    _displayedMap[2]->setDisplayFrame(SpriteFrameCache::getInstance()->getSpriteFrameByName((String::createWithFormat("environment_%d_%d.png", row + 1, col + 1)->getCString())));
    _displayedMap[3]->setDisplayFrame(SpriteFrameCache::getInstance()->getSpriteFrameByName((String::createWithFormat("environment_%d_%d.png", row, col + 1)->getCString())));

    _displayedMap[0]->setPosition(-1 * colRemainder, -1 * rowRemainder);
    _displayedMap[1]->setPosition(-1 * colRemainder, IMG_HEIGHT - rowRemainder - 1);
    _displayedMap[2]->setPosition(IMG_WIDTH - colRemainder - 1, IMG_HEIGHT - rowRemainder - 1);
    _displayedMap[3]->setPosition(IMG_WIDTH - colRemainder - 1, -1 * rowRemainder);
//end normal map

    //hero
    _hero->getSkeleton()->setZOrder(400 - _hero->getPosition().y);
    //end

    //land
    getVisionLand();
    //end

    //plants
    getVisionPlant();
    //end
    return true;
}

在我的游戏中,hero一直在中间的位置,所以所有的图片都要根据hero的坐标来计算相对坐标,drawMap()函数将在每一帧被调用,这里有一点小小的优化,当hero移动不超过屏幕大小时,不会重新计算地图图片,只有超过了1000*500这个范围,才会重新计算图片ID,因为需要加载新的图片了。

· 然后是绘制各种实体对象,例如plant,其余的实体逻辑类似:

void VisionMap::getVisionPlant()
{
//由于实体对象每帧都会绘制,所以要把上一帧的同一个实体从层里删除。
    if (_firstDraw)
    {

        for (int i = 0; i < _displayedPlant.size(); i++)
        {
            this->removeChild(_displayedPlant.at(i));
        }
    }
    int count = 0;
    _displayedPlant.clear();
    while (count < globalEntity->_virtualMap->_plants.size())
    {
        Rect screenRect
            (
            _hero->getPosition().x*RECT_SIZE - HERO_LOCATION_SCREEN.x,
            _hero->getPosition().y*RECT_SIZE - HERO_LOCATION_SCREEN.y,
            IMG_WIDTH,
            IMG_HEIGHT
            );
        Rect plantRect
            (
            globalEntity->_virtualMap->_plants.at(count)->getPosition().x*RECT_SIZE - 300,
            globalEntity->_virtualMap->_plants.at(count)->getPosition().y*RECT_SIZE,
            globalEntity->_virtualMap->_plants.at(count)->Plants::getContentSize().x,
            globalEntity->_virtualMap->_plants.at(count)->Plants::getContentSize().y
            );
        if (screenRect.intersectsRect(plantRect))
        {
            auto po = globalEntity->_virtualMap->_plants.at(count)->getPosition();
            auto sp = Sprite::createWithSpriteFrameName(globalEntity->_virtualMap->_plants.at(count)->spriteName_);
            sp->setPosition(
                po.x*RECT_SIZE - _hero->getPosition().x*RECT_SIZE + HERO_LOCATION_SCREEN.x,
                po.y*RECT_SIZE - _hero->getPosition().y*RECT_SIZE + HERO_LOCATION_SCREEN.y
                );
            sp->setAnchorPoint(Point(0.5, 0));
            sp->setZOrder(400 - po.y);
            _displayedPlant.pushBack(sp);
            this->addChild(sp);
        }
        count++;
    }
}

根据实体对象的矩形边框与屏幕边框是否相交,可以确定是否需要绘制。

百闻不如一见,最后的结果(补上上次的像素检测):

这里写图片描述

移动一下
这里写图片描述

2014-10-22 09:56:28 c201038795050 阅读数 583
  • Cocos2d-x 3.x项目实战:星空大战(射击类游戏)

    本课程是《从零开始掌握Cocos2d-x 3.0(基于3.0正式版) 》课程配套的项目实战课程。建议对Cocos2d-x 3.0不了解的同学先学习该课程。本课程将利用《从零开始掌握Cocos2d-x 3.0(基于3.0正式版) 》中讲解的知识开发一款射击类游戏:星空大战(与打飞机游戏类似)。从这个项目中可学习到如何利用Cocos2d-x 3.0的相关知识开发一款完整的射击类游戏。

    18362 人正在学习 去看看 李宁
// 图层
bool HelloWorld::init()
{
    if ( !CCLayer::init() )
    {
        return false;
    }
    
    CCSize winSize = CCDirector::sharedDirector()->getWinSize();
    
    CCTMXTiledMap *map = CCTMXTiledMap::create("map1.tmx");
    this->addChild(map);
    
    CCSize mapSize = map->getMapSize();
    CCSize tilesSize = map->getTileSize();
    
    CCLOG("mapSize:%f_%f, tilesSize:%f_%f", mapSize.width, mapSize.height, tilesSize.width, tilesSize.height);
    
    CCArray *pChildArray = map->getChildren();
    CCSpriteBatchNode *child = NULL;
    CCObject *pObject = NULL;
    
    // 遍历每个图块,并且设置抗锯齿
    CCARRAY_FOREACH(pChildArray, pObject)
    {
        child = (CCSpriteBatchNode *)pObject;
        
        if (!child)
        {
            break;
        }
        else
        {
            child->getTexture()->setAntiAliasTexParameters();
        }
    }
    
    // 取出图层中的最后一个图块进行操作
    CCTMXLayer *layer = map->layerNamed("layer1");
    CCSprite *pSpr = layer->tileAt(ccp(9, 9));
    pSpr->setScale(1);
    pSpr->setRotation(-90);
    
    return true;
}




2014-03-31 20:50:59 youchengyuanzhi 阅读数 627
  • Cocos2d-x 3.x项目实战:星空大战(射击类游戏)

    本课程是《从零开始掌握Cocos2d-x 3.0(基于3.0正式版) 》课程配套的项目实战课程。建议对Cocos2d-x 3.0不了解的同学先学习该课程。本课程将利用《从零开始掌握Cocos2d-x 3.0(基于3.0正式版) 》中讲解的知识开发一款射击类游戏:星空大战(与打飞机游戏类似)。从这个项目中可学习到如何利用Cocos2d-x 3.0的相关知识开发一款完整的射击类游戏。

    18362 人正在学习 去看看 李宁

在cocos2d-x中,瓦片地图一般是用TileMap方案实现

对于Tiled的使用,本文不做介绍,本文着重介绍下cocos2dx中瓦片地图的使用

首先,我们需要知道几个类:CCTMXTiledMap、CCTMXLayer、CCTMXObjectGroup

CCTMXTiledMap代表一个完整的瓦片地图,它负责地图文件的载入、管理以及呈现。CCTMXLayer代表一个瓦片地图中的图层,可以从图层对象获取图层信息,其隶属于CCTMXTiledMap。CCTMXObjectGroup是地图精灵类,用于代表地图中的精灵组。

对于这三个类的常见函数如下

1.CCTMXTiledMap

create(const char *tmxFile)

createWithXML(const char *tmxString, const char *resourcePath)

getMapOrientation

getMapSize(void)

getObjectGroups(void)

getProperties(void)

getTileSize(void)

initWithTMXFile(const char *tmxFile)

initWithXML(const char *tmxString, const char *resourcePath)

layerNamed(const char *layerName)

objectGroupNamed(const char *groupName)

propertyNamed(const char *propertyName)

propertiesForGID(int GID)

函数意思比较简单,在此补做过多介绍。当然既然有getter访问器,自然也有setter访问器,在此就不列出了.

2.CCTMXLayer

get/setLayerSize

get/setMapTileSize

get/setTileSet

get/setProperties

releaseMap

tileAt

tileGIDAt

setTileGID

removeTileA:根据横纵坐标索引删除该位置图素

positionAt:根据图素横纵坐标索引获取图素位置

setuptiles::创建设置空像素

propertyNamed:根据属性名称获得属性值

3.CCTMXObjectGroup

get/setPositionOffset

get/setObjects

get/setGroupName

get/setProperties

objectNamed

propertyNamed

 在此补充下坐标和图素行列数的转换

indexx=Point.x/map->getTileSize.width;

indexy=map->getMapSIze().height-(Point.y)/map->getTileSize().height

简单来说,就是具体位置除以图素的宽高,不过由于地图中的的行列数是从上到下,而cocos2dx中是从下到上,所以有个小小的转换。

2018-06-02 23:43:07 yaoxh6 阅读数 613
  • Cocos2d-x 3.x项目实战:星空大战(射击类游戏)

    本课程是《从零开始掌握Cocos2d-x 3.0(基于3.0正式版) 》课程配套的项目实战课程。建议对Cocos2d-x 3.0不了解的同学先学习该课程。本课程将利用《从零开始掌握Cocos2d-x 3.0(基于3.0正式版) 》中讲解的知识开发一款射击类游戏:星空大战(与打飞机游戏类似)。从这个项目中可学习到如何利用Cocos2d-x 3.0的相关知识开发一款完整的射击类游戏。

    18362 人正在学习 去看看 李宁

作业要求

  • 随机产生怪物并且怪物会向角色靠近
  • 怪物碰到角色后,角色掉血,角色血量为空则播放死亡动画并解除所有事件
  • 角色可以攻击怪物
  • 使用tilemap创建地图
  • 加分项:使用本地数据存储,记录打到的怪物数量,同时在游戏中显示打倒数量

游戏演示

游戏演示

Resources文件夹截图

Resources

TileMap软件界面截图

TileMap

UserDefault.xml位置及内容截图

UserDefault

解决的有关问题

问题的解决方法在代码的注释中

  • tmx文件无法显示问题
  • UserDefault.xml文件位置问题
  • 死亡动画和停止所有动画的冲突问题

具体实现

HelloWorldScene.h

#pragma once
#include "cocos2d.h"
using namespace cocos2d;

class HelloWorld : public cocos2d::Scene
{
public:
    static cocos2d::Scene* createScene();

    virtual bool init();

    // implement the "static create()" method manually
    CREATE_FUNC(HelloWorld);
private:
    cocos2d::Sprite* player;
    cocos2d::Vector<SpriteFrame*> attack;
    cocos2d::Vector<SpriteFrame*> dead;
    cocos2d::Vector<SpriteFrame*> run;
    cocos2d::Vector<SpriteFrame*> idle;
    cocos2d::Size visibleSize;
    cocos2d::Vec2 origin;
    cocos2d::Label* time;
    int dtime;
    cocos2d::ProgressTimer* pT;

    //判断是否在播放动作
    bool isAnimating;
    //移动
    void moveCallback(Ref* pSender, std::string direction);
    //攻击
    void attackCallback(Ref* pSender);
    //死亡
    void deadCallback(Ref* pSender);
    //重写update,实现倒计时
    void update(float time)override;

    //下面是新加的变量
    //用来显示打倒的敌人数
    cocos2d::Label* score;
    //判断是否在攻击
    bool isAttack = false;
    //判断是否在死亡
    bool isDead = false;
    //产生一个怪物,并且移动
    void createMonster(float time);
    //被怪物打击
    void hitByMonster(float time);
    //停止所有动作
    void stop(float time);
};

HelloWorldScene.cpp

#include "HelloWorldScene.h"
#include "SimpleAudioEngine.h"
#include "Monster.h"

//宏定义用于本地数据持久化
#define database UserDefault::getInstance()

//因为string要用,或者std::string也行
using namespace std;
#pragma execution_character_set("utf-8")

USING_NS_CC;

Scene* HelloWorld::createScene()
{
    return HelloWorld::create();
}

// Print useful error message instead of segfaulting when files are not there.
static void problemLoading(const char* filename)
{
    printf("Error while loading: %s\n", filename);
    printf("Depending on how you compiled you might have to add 'Resources/' in front of filenames in HelloWorldScene.cpp\n");
}

// on "init" you need to initialize your instance
bool HelloWorld::init()
{
    //////////////////////////////
    // 1. super init first
    if ( !Scene::init() )
    {
        return false;
    }

    visibleSize = Director::getInstance()->getVisibleSize();
    origin = Director::getInstance()->getVisibleOrigin();

    //这里需要特别注意
    /***********************************************************************/
    /*这里差不多花了三个小时的时间
    主要解决,图片没有显示的问题,上网查了很多资料,没有一个有用的。
    有的说png图片不能用,有的说bmp图片不能用,我暂时用的jpg图片。
    据说新建层没有添加物体会奔溃,发现并没有,不知道网上的问题是怎么回事,全部都是错误答案。
    也可能是版本的问题,毕竟那个时候还是CC前缀还在的时候
    至于除去以上问题,画面还是没法显示,怎么解决?
    经过尝试,这种方法是可以的:
    一开始将所有要用到的资源放在Resources,不能直接把生成好的map.tmx放在Resources,
    否则没有显示,但是tmx != NULL。也不能将生成好的map.tmx先放在Resources下,再把用到
    的资源放在Resources,亲测不行。只能先把要用到的资源放在Resources下,我用到的资源是
    Test.jpg,然后打开TileMap,新建地图,保存地址也要保存到Resources,最好不要保存到其他位置,然后
    再拷贝到Resources,接下来导入图块,一定要用Resources下的图片,这样生成的图片才可以在Cocos中显示。*/
    /***********************************************************************/
    TMXTiledMap *tmx = TMXTiledMap::create("map.tmx");
    tmx->setPosition(visibleSize.width / 2, visibleSize.height / 2);
    CCLOG("%f,%f", visibleSize.width / 2, visibleSize.height / 2);
    tmx->setAnchorPoint(Vec2(0.5, 0.5));
    tmx->setScale(Director::getInstance()->getContentScaleFactor());
    addChild(tmx, 0);

    //创建一张贴图
    auto texture = Director::getInstance()->getTextureCache()->addImage("$lucia_2.png");
    //从贴图中以像素单位切割,创建关键帧
    auto frame0 = SpriteFrame::createWithTexture(texture, CC_RECT_PIXELS_TO_POINTS(Rect(0, 0, 113, 113)));
    //使用第一帧创建精灵
    player = Sprite::createWithSpriteFrame(frame0);
    player->setPosition(Vec2(origin.x + visibleSize.width / 2,
        origin.y + visibleSize.height / 2));
    addChild(player, 3);

    //hp条
    Sprite* sp0 = Sprite::create("hp.png", CC_RECT_PIXELS_TO_POINTS(Rect(0, 320, 420, 47)));
    Sprite* sp = Sprite::create("hp.png", CC_RECT_PIXELS_TO_POINTS(Rect(610, 362, 4, 16)));

    //使用hp条设置progressBar
    pT = ProgressTimer::create(sp);
    pT->setScaleX(90);
    pT->setAnchorPoint(Vec2(0, 0));
    pT->setType(ProgressTimerType::BAR);
    pT->setBarChangeRate(Point(1, 0));
    pT->setMidpoint(Point(0, 1));
    pT->setPercentage(100);
    pT->setPosition(Vec2(origin.x + 14 * pT->getContentSize().width, origin.y + visibleSize.height - 2 * pT->getContentSize().height));
    addChild(pT, 1);
    sp0->setAnchorPoint(Vec2(0, 0));
    sp0->setPosition(Vec2(origin.x + pT->getContentSize().width, origin.y + visibleSize.height - sp0->getContentSize().height));
    addChild(sp0, 0);

    // 静态动画
    idle.reserve(1);
    idle.pushBack(frame0);

    // 攻击动画
    attack.reserve(17);
    for (int i = 0; i < 17; i++) {
        auto frame = SpriteFrame::createWithTexture(texture, CC_RECT_PIXELS_TO_POINTS(Rect(113 * i, 0, 113, 113)));
        attack.pushBack(frame);
    }
    //这里又加入了frame0,原因是,动作结束之后要处理静止状态,而不是动作结束之后的状态
    attack.pushBack(frame0);
    auto attackAnimation = Animation::createWithSpriteFrames(attack, 0.1f);
    AnimationCache::getInstance()->addAnimation(attackAnimation, "attack");

    // 可以仿照攻击动画
    // 死亡动画(帧数:22帧,高:90,宽:79)
    auto texture2 = Director::getInstance()->getTextureCache()->addImage("$lucia_dead.png");
    // Todo
    dead.reserve(22);
    for (int i = 0; i < 22; i++) {
        auto frame = SpriteFrame::createWithTexture(texture2, CC_RECT_PIXELS_TO_POINTS(Rect(79 * i, 0, 79, 90)));
        dead.pushBack(frame);
    }
    //dead.pushBack(frame0);
    auto deadAnimation = Animation::createWithSpriteFrames(dead, 0.1f);
    AnimationCache::getInstance()->addAnimation(deadAnimation, "dead");

    // 运动动画(帧数:8帧,高:101,宽:68)
    auto texture3 = Director::getInstance()->getTextureCache()->addImage("$lucia_forward.png");
    // Todo
    run.reserve(8);
    for (int i = 0; i < 2; i++) {
        auto frame = SpriteFrame::createWithTexture(texture3, CC_RECT_PIXELS_TO_POINTS(Rect(68 * i, 0, 68, 101)));
        run.pushBack(frame);
    }
    run.pushBack(frame0);
    auto runAnimation = Animation::createWithSpriteFrames(run, 0.1f);
    AnimationCache::getInstance()->addAnimation(runAnimation, "run");

    auto menu = Menu::create();
    menu->setPosition(80, 50);
    addChild(menu);

    //参考以往代码,感觉这样写比较好
    auto createDirectionLabel = [this, &menu](string c) {
        int x = 0, y = 0;
        auto label = Label::create(c, "arial", 36);
        auto menuItem = MenuItemLabel::create(label, CC_CALLBACK_1(HelloWorld::moveCallback, this, c));
        if (c == "W") {
            y += 1.2 * label->getContentSize().height;
        }
        else if (c == "A") {
            x -= 1.5 * label->getContentSize().width;
        }
        else if (c == "D") {
            x += 1.5 * label->getContentSize().width;
        }
        menuItem->setPosition(x, y);
        menu->addChild(menuItem);
    };

    //创建方向键
    createDirectionLabel("W");
    createDirectionLabel("S");
    createDirectionLabel("A");
    createDirectionLabel("D");

    //X按钮
    auto labelX = Label::create("X", "fonts/arial.ttf", 36);
    auto menuItem = MenuItemLabel::create(labelX, CC_CALLBACK_1(HelloWorld::attackCallback, this));
    menuItem->setPosition(origin.x + visibleSize.width - 120, -15);
    menu->addChild(menuItem);

    //Y按钮
    auto labelY = Label::create("Y", "fonts/arial.ttf", 36);
    menuItem = MenuItemLabel::create(labelY, CC_CALLBACK_1(HelloWorld::deadCallback, this));
    menuItem->setPosition(origin.x + visibleSize.width - 100, 15);
    menu->addChild(menuItem);

    //倒计时
    time = Label::createWithTTF("180", "fonts/arial.ttf", 36);
    time->setPosition(origin.x + visibleSize.width / 2, origin.y + visibleSize.height -50);
    addChild(time);

    score = Label::createWithTTF("0", "fonts/arial.ttf", 36);
    score->setPosition(origin.x + visibleSize.width / 2, origin.y + visibleSize.height - 100);
    addChild(score);

    //每一秒时间减少一
    schedule(schedule_selector(HelloWorld::update), 1.0f);
    //每两秒生成一个怪物,并且移动
    schedule(schedule_selector(HelloWorld::createMonster), 2.0f);
    //每0.1秒检测有没有被怪物攻击
    schedule(schedule_selector(HelloWorld::hitByMonster), 0.1f);
    //每0.1秒检测主角有没有死
    //为什么要这么做,其实也是无奈的办法
    //因为如果死亡就要播放死亡动画,死亡动画结束后,取消所有动作,
    // 将死亡动画和取消所有动作放在一个函数中的时候,因为死亡动画是要时间的,函数会
    //直接执行,所以取消所有动作覆盖了死亡动画,导致没有播放,有人可能会依靠isDead来实现
    //先死亡再停止,但是本质没有改变,用一个序列来实现,序列结束的时候改变isDead的值,再用这个值
    //决定是否停止动画,同样,停止所有动画不会等isDead的值改变再执行。所有直接用调度器了。
    schedule(schedule_selector(HelloWorld::stop), 0.1f);
    return true;
}

void HelloWorld::moveCallback(Ref* pSender, string direction)
{
    if (isDead == true) {
        return;
    }
    auto position = player->getPosition();
    //事实证明,在区间(0,visibleSize.width)之间还是会出界,所以再缩小一点
    if (isAnimating == false && ((position.x > 50 && direction == "A") || (position.x < visibleSize.width-50 && direction == "D") || (position.y > 50 && direction == "S") || (position.y < visibleSize.height-50 && direction == "W"))) {
        isAnimating = true;
        int x, y;
        if (direction == "W") {
            x = 0;
            y = 50;
        }
        else if (direction == "A") {
            x = -50;
            y = 0;
            player->setFlippedX(true);
        }
        else if (direction == "S") {
            x = 0;
            y = -50;
        }
        else if (direction == "D") {
            x = 50;
            y = 0;
            player->setFlippedX(false);
        }
        //移动和动画是同时执行的
        auto spawn = Spawn::createWithTwoActions(Animate::create(AnimationCache::getInstance()->getAnimation("run")), MoveBy::create(0.5f, Vec2(x, y)));
        //执行完之后要将isAnimating置为false
        auto sequence = Sequence::create(spawn, CCCallFunc::create(([this]() { isAnimating = false; })), nullptr);
        player->runAction(sequence);
    }
    return;
}

/*
实现攻击动作,攻击工作结束将isAnimating置为false
加分项是实现血条的变化
*/
/*
这次作业将血条和攻击分离了
*/
void HelloWorld::attackCallback(Ref * pSender)
{
    if (isDead == true) {
        return;
    }
    if (isAnimating == false) {
        isAnimating = true;
        isAttack = true;
        auto sequence = Sequence::create(Animate::create(AnimationCache::getInstance()->getAnimation("attack")),
            CCCallFunc::create(([this]() {
            isAnimating = false;
            isAttack = false;
        })), nullptr);
        player->runAction(sequence);
    }
}

/*
死亡动作的实现
*/
/*
死亡之后,处理调度器
*/
void HelloWorld::deadCallback(Ref * pSender)
{
    if (isDead == false) {
        isAnimating = true;
        auto sequence = Sequence::create(Animate::create(AnimationCache::getInstance()->getAnimation("dead")),
            CCCallFunc::create(([this]() {
            isAnimating = false;
            isDead = true;
        })), nullptr);
        player->runAction(sequence);
        unschedule(schedule_selector(HelloWorld::createMonster));
        unschedule(schedule_selector(HelloWorld::hitByMonster));
        unschedule(schedule_selector(HelloWorld::update));
    }
}


/*
update实现倒计时的功能,其中开始的值是180
需要每秒减一,所以需要拿到当前的值,并且减一再赋值回去
其中需要int和string的相互转换
由于是cocos2d,还有一个新的类型是CCString
这三者之间的转换参考下面链接
https://www.cnblogs.com/leehongee/p/3642308.html
*/

void HelloWorld::update(float dt)
{
    string str = time->getString();
    int timeLength = atoi(str.c_str());
    if (timeLength > 0) {
        timeLength--;
        CCString* ns = CCString::createWithFormat("%d", timeLength);
        string s = ns->_string;
        time->setString(s);
    }
    else {
        unschedule(schedule_selector(HelloWorld::update));
    }
}

//参考PPT
void HelloWorld::createMonster(float dt) 
{
    auto fac = Factory::getInstance();
    auto m = fac->createMonster();
    float x = random(origin.x, visibleSize.width);
    float y = random(origin.y, visibleSize.height);
    m->setPosition(x, y);
    addChild(m, 3);
    fac->moveMonster(player->getPosition(), 0.5f);
}

//参考PPT
void HelloWorld::hitByMonster(float dt) 
{
    auto fac = Factory::getInstance();
    Rect playerRect = player->getBoundingBox();
    Rect attackRect = Rect(playerRect.getMinX() - 40, playerRect.getMinY(), playerRect.getMaxX() - playerRect.getMinX() + 80, playerRect.getMaxY() - playerRect.getMinY());
    Sprite *attackCollision = fac->collider(attackRect);
    //这边注意判断的时候,是嵌套关系,不是并列关系
    //就是说不能将attackCollison和playerCollision放在并列关系判断,否则会出错
    if (attackCollision != NULL) {
        if (isAttack) {
            fac->removeMonster(attackCollision);
            float percentage = pT->getPercentage();
            if (percentage < 100) {
                auto to = ProgressFromTo::create(1.0f, percentage, percentage + 20);
                pT->runAction(to);
            }
            string str = score->getString();
            int scoreLength = atoi(str.c_str());
            scoreLength++;
            CCString* ns = CCString::createWithFormat("%d", scoreLength);
            string s = ns->_string;
            score->setString(s);
            //放在本地文件UserDefault.xml中
            database->setIntegerForKey("score",scoreLength);
            //这里又要吐槽一下网上的解答,说UserDefault.xml文件在Debug文件夹里面之类的
            //直接用下面的输出语句就可以看到地址,实现本地数据持久化
            CCLOG("%s", FileUtils::getInstance()->getWritablePath().c_str());
        }
        else {
            Sprite *playerCollision = fac->collider(playerRect);
            if (playerCollision != NULL) {
                fac->removeMonster(playerCollision);
                float percentage = pT->getPercentage();
                if (percentage > 0) {
                    auto to = ProgressFromTo::create(1.0f, percentage, percentage - 20);
                    pT->runAction(to);
                }
                else {
                    deadCallback(NULL);
                }
            }
        }
    }
}

//停止所有动作
void HelloWorld::stop(float dt) 
{
    if (isDead == true) {
        player->stopAllActions();
    }
}

资源及视频

资源及视频

2015-11-23 15:33:27 qq_22984991 阅读数 487
  • Cocos2d-x 3.x项目实战:星空大战(射击类游戏)

    本课程是《从零开始掌握Cocos2d-x 3.0(基于3.0正式版) 》课程配套的项目实战课程。建议对Cocos2d-x 3.0不了解的同学先学习该课程。本课程将利用《从零开始掌握Cocos2d-x 3.0(基于3.0正式版) 》中讲解的知识开发一款射击类游戏:星空大战(与打飞机游戏类似)。从这个项目中可学习到如何利用Cocos2d-x 3.0的相关知识开发一款完整的射击类游戏。

    18362 人正在学习 去看看 李宁

所谓大型地图,是指类似于pc端沙盒游戏那样的地图。需要加载大量的图片资源。而cocos并不是针对这样的游戏设计(现目前手机端很少有2d的类似游戏)。

笔者最近再开发一款类似《饥荒》的生存类游戏,所以涉及到大型地图的实现,下面来和大家聊一聊我自己的实现方式。

· 首先是底层地图实体的逻辑,要实现物体的碰撞,我使用了方格数组来储存地图的相应信息:

struct MapRect
{
    LandType landType_ = DRY_LAND;
    short int positionX_ = 0;
    short int positionY_ = 0;
};

底层的数据结构要越简单越好,因为要大量的使用.

· 然后是地图上面的实体,在我设计的游戏中有entity和land两种,entity是人为定义的碰撞体积,而land是不规则的图片,我用了像素检测来判断它的边缘,并且存入底层的方格数组中:

class VirtualMap
{
public:
    VirtualMap();
    ~VirtualMap();
public://inlitialize map data
    bool recordData();//将地图数据存入文件
    bool loadData();//from xml;to be done
    bool loadLandIndex();//from Imgs
    bool loadPlantIndex();//from plant.xml
    bool setCache();//set Map sprite cache
public://部分成员部分暴露出来供绘制使用
    //map data
    MapRect _landIndex[401][401];//地图网格的数据
    SimpleArray<Land*, MAX_LAND_NUMBER> _land;
    //entities data
    Vector<Animals*> _animal;
    Vector<Plants*> _plants;
    Vector<Products*> _droppedOutProducts;
protected://handle imgs
    int getAlpha(Point, Image*);//图片上的位置和图片在地图上的位置
};

部分函数将数据内容存入本地文件,这样地图加载,例如像素检测功能只需要在游戏第一次启动时执行就行

(SimpleArray是自己设计的数据结构,线程安全的动态数组,此处不表)

· 函数的实现,此处用了rapidxml解析本地的xml文件

#include"VirtualMap.h"

VirtualMap::VirtualMap() :_land(NullLand::create())
{
    for (int i = 0; i < 20; i++)
    {
        _land[i] = NullLand::create();
    }
}

VirtualMap::~VirtualMap()
{

}

bool VirtualMap::loadLandIndex()
{
    //加载地图数据
    rapidxml::xml_document<> doc_;
    rapidxml::file<> docl_(FileUtils::getInstance()->fullPathForFilename("land.xml").c_str());
    doc_.parse<0>(docl_.data());

    if (!doc_.first_node())
    {
        log("ERROR_LAND_XML_NULL");
        return false;
    }
    else
    {
        rapidxml::xml_node<>*  node_root = doc_.first_node("ROOT");//获取根节点
        rapidxml::xml_node<>* node_land_first = node_root->first_node("land");//指向第一个元素

        int count = 0;
        while (node_land_first)
        {
            auto land = new Land(DRY_LAND);
            rapidxml::xml_node<>* first_sibling = node_land_first->first_node("landType");

            land->_landType = (LandType)atoi(first_sibling->value());//字符串转换int

            first_sibling = first_sibling->next_sibling("positionX");
            land->_imgPosition.x = atoi(first_sibling->value());

            first_sibling = first_sibling->next_sibling("positionY");
            land->_imgPosition.y = atoi(first_sibling->value());

            first_sibling = first_sibling->next_sibling("sprite");
            land->_imgName = first_sibling->value();

            first_sibling = first_sibling->next_sibling("width");
            land->_contentSize.x = atoi(first_sibling->value());

            first_sibling = first_sibling->next_sibling("height");
            land->_contentSize.y = atoi(first_sibling->value());

            //end
            _land.push(land);
            count++;
            node_land_first = node_land_first->next_sibling("land");
            //移动指针到下一个节点
        }
    }
    //加载完毕

    //处理像素信息
    int count = 0;
    while (_land[count]->_imgName != NullLand::create()->_imgName)
    {
        auto img = new Image;
        auto path = FileUtils::getInstance()->fullPathForFilename("land_0.png");
        img->initWithImageFile(path);
        int w = _land[count]->_contentSize.x / RECT_SIZE;
        int h = _land[count]->_contentSize.y / RECT_SIZE;
        int x = _land[count]->_imgPosition.x / RECT_SIZE;
        int y = _land[count]->_imgPosition.y / RECT_SIZE;

        for (int a = 0; a < w; a++)
        {
            for (int b = 0; b < h; b++)
            {
                if (getAlpha(Point(a*RECT_SIZE, b*RECT_SIZE),img)>0)//>20则为图片非透明内容
                {
                    _landIndex[a+x][b+y].landType_ = _land[count]->_landType;
                }
            }
        }
        count++;
    }
    return true;
}

bool VirtualMap::loadPlantIndex()
{
    rapidxml::xml_document<> doc_;
    rapidxml::file<> docl_(FileUtils::getInstance()->fullPathForFilename("plant.xml").c_str());
    doc_.parse<0>(docl_.data());

    if (!doc_.first_node())
    {
        log("ERROR_LAND_XML_NULL");
        return false;
    }
    else
    {
        rapidxml::xml_node<>*  node_root = doc_.first_node("ROOT");//获取根节点
        rapidxml::xml_node<>* node_land_first = node_root->first_node("plant");//指向第一个元素
        int count = 0;
        while (node_land_first)
        {

            rapidxml::xml_node<>* first_sibling = node_land_first->first_node("plantType");

            auto pp = Plants::create((PlantType)atoi(first_sibling->value()));//字符串转换int

            first_sibling = first_sibling->next_sibling("positionX");
            pp->ActiveEntity::getPosition().x = atoi(first_sibling->value())/RECT_SIZE;

            first_sibling = first_sibling->next_sibling("positionY");
            pp->ActiveEntity::getPosition().y = atoi(first_sibling->value()) / RECT_SIZE;

            _plants.pushBack(pp);
            count++;
            node_land_first = node_land_first->next_sibling("plant");
            //移动指针到下一个节点
        }
    }
    return true;
}

int VirtualMap::getAlpha(Point position,Image* img)
{
    position.x += 5;
    position.y += 5;
    unsigned char* imgPixelData_ = img->getData();
    int w = img->getWidth();
    int h = img->getHeight();
    int pa = (((h - position.y) - 1)*w + position.x) * 4 + 3;
    unsigned int alpha = imgPixelData_[pa];
    return alpha;
}

bool VirtualMap::setCache()
{
    return true;
}

关于像素检测,我做了小小的测试,可以看到地图上的渲染图(CSDN图片上传炸了,伤感,下次补上)

没有更多推荐了,返回首页