Cocos2d-x3.x塔防游戏(保卫萝卜)从零开始(一)

一、前提:

完成Hello Game项目的创建编译。

具体参考:Cocos2dx.3x_Hello Game项目创建篇

二、本篇目标:

l  说说关于塔防游戏的想法和思路

l  实现一个简单的塔防游戏原型

三、内容:

l  说说关于塔防游戏的想法和思路

首先上一张塔防游戏PSD设计效果图

游戏故事设定:

这个游戏说是保卫萝卜,但不能真的是保卫萝卜了,因为保卫萝卜的游戏已经有了,只是借用一下这个大名鼎鼎的塔防游戏宣传和参照一下。现在网络上主流游戏都会先讲一下故事让玩家有一种入戏感,那我们的这个故事是这样的:很久很久以前,在美丽的大学宿舍区住着一群美丽天真的女孩,但是邪恶的色狼大叔们总想对她们做一些坏事,那么我们的英雄善良勇敢的女生宿舍管理员利用生活中的武器菜刀、皮鞋、玩具飞机等在大叔必经的路上狙击他们,保护女孩们免受这些大叔的伤害。

游戏元素组成:

1、地图:每一关地图均不相同,主要是道路不同和炮台位的不同。

2、炮塔:水果刀、菜刀、老鼠药、高跟鞋、玩具飞机等,不同的炮台具备不同的价格、攻击速度、攻击属性、攻击方式。

3、子弹:由炮台发射的,具备不同的攻击值、扩散值、迟缓值、攻击范围值等。

4、怪物:各类猥琐大叔、叫兽、色狼,不同的色狼具备不同的速度值、伤害值、耐揍值,沿着地图上的道路不断的靠近道路终点的女主角。

5、女主角:道路终点的女孩,不具备攻击力需要炮塔的保护,具有一定的纯洁值,当纯洁值被大叔玷污光了就自杀了,游戏也就结束了。

6、分数&资源:杀死不同的色狼能获得一定的分数,分数可以用来购买新的炮台,每一关都会有一定的初始分数用来支撑游戏最初的消耗,每一关的分数只限在本关使用,下一关开启时前面积累的分数清空。

7、宝箱:用分数资源购买宝箱,能有一定几率获得比投入分数几倍的回报。

8、医生:用分数购买医疗,对女主角的纯洁值进行修补。

游戏开发模式:

整个游戏开发的方式是这样,首先实现一个很小的游戏核心原型,然后不断的修改扩大这个游戏原型直至游戏完成为止。本人认为这样的方式比较适合读者理解,并且跟着文章自己学会理解这个游戏的开发。

l  实现一个简单的游戏原型

新建游戏工程名为DefendTheGirl(保卫女孩),包名为:com.game. defendthegirl。如果还不会创建工程请参照:Cocos2dx.3x入门三部曲-Hello Game项目创建(二)篇

本篇原型需要实现内容:

1、  在主场景中载入一张地图。

2、  在地图终点放置一个女主角,在地图的起点放置一个色狼大叔。

3、  让色狼大叔沿着地图指定的路线向女主角的位置靠近。

素材图片准备:

地图图片level_bg_1.png  960px, 640px

女主角图片 girl.png

色狼大叔图片 dashu.png

把这几张素材图片拷贝到文件夹Resources下面即可

1、在主场景中载入一张地图

第一步:

用Microsoft Visual Studio 2012打开proj.win32工程,然后在src下新建MainScene.h、MainScene.cpp作为游戏的主场景(Scene不会建?参考:Cocos2dx.3x入门三部曲-Hello Game项目解析(三)篇)

第二步:在init()方法里载入地图图片level_bg_1.png,代码如下:

bool MainScene::init()
{
    if ( !Layer::init() )
    {
        return false;
    }
    Size visibleSize = Director::getInstance()->getVisibleSize();
    Vec2 origin = Director::getInstance()->getVisibleOrigin();
    //载入地图背景
    auto sprite = Sprite::create("level_bg_1.png");
    sprite->setPosition(Vec2(visibleSize.width/2 + origin.x, visibleSize.height/2 + origin.y));
    this->addChild(sprite, 0);
    return true;
}

第三步:打开AppDelegate.cpp文件,引入MainScene.h头文件,并且在applicationDidFinishLaunching方法中把auto scene = HelloWorld::createScene(); 改成auto scene = MainScene::createScene();然后运行。

2、在地图终点放置一个女主角,在地图的起点放置一个色狼大叔

第一步:在init()方法里添加如下代码

//载入地图背景
    ……
    auto sprite = Sprite::create("level_bg_1.png");
    sprite->setPosition(Vec2(visibleSize.width/2 + origin.x, visibleSize.height/2 + origin.y));
    this->addChild(sprite, 0);

    //在地图起点处放置一个色狼
    auto dsSprite = Sprite::create("dashu.png");
    dsSprite->setPosition(Vec2(40, 390));
    this->addChild(dsSprite, 0);

    //在地图终点处放置一个女主角
    auto nhSprite = Sprite::create("girl.png");
    nhSprite->setPosition(Vec2(920, 480));
    this->addChild(nhSprite, 0);
…...

第二步:然后运行就可以在画面上看到色狼大叔和女主角了

1、 让色狼大叔沿着地图指定的路线向女主角的位置靠近

这个一个有点难度的任务,首先我们对这张地图的道路路径进行一下坐标分析:

如上图所示,以地图的右下顶点为坐标系原点,整个道路分成12个坐标点,女主角在1号坐标点,色狼大叔在12号坐标点。现在色狼大叔将沿着图中黄色的线路从12点开始 11点、10点、9点…直至到达1点,我的实现思路是这样,色狼从12点出发时告诉它目标点是11点,当色狼到达11点的时候继续告诉它下一个目标点是10点直到1点。

几个实现技术点:

1、  对Sprite(色狼)进行setPosition的方式可以改变Sprite在图上的位置从而实现Sprite的移动

2、  计算Sprite(色狼)位置到目标点的向量值,然后根据这个向量值和色狼移动速度计算Sprite在x,y方向上的距离偏移值,用这个偏移值调整Sprite的位置

判断Sprite(色狼)到达目标点(如:11点),如下图所示通过Sprite坐标点和目标点之间的距离小于一定值的时就判定为到达目标点,需要设置新的下一个目标点。

有了这些实现思路,现在开始代码编写:

第一步:由上面是实现思路可知我们的基本实现是路径点,那么这里先新建一个路径点类对象,名称为:Waypoint.h、Waypoint.cpp,继承自:cocos2d::CCNode。

Waypoint.h:

class Waypoint: public cocos2d::CCNode
{
public:
    Waypoint(void);
    ~Waypoint(void);
    //初始化方法
    static Waypoint* nodeWithTheLocation(cocos2d::Point location);
    bool initWithTheLocation(cocos2d::Point location);
    //设置当前点的下一个路径点
    void setNextWaypoint(Waypoint* waypoint);
    //获取当前点的下一个路径点
    Waypoint* getNextWaypoint();

    //当前路径点位置
    CC_SYNTHESIZE(cocos2d::Point,_myPosition,MyPosition);

private:
    //下一个路径点
    Waypoint* _nextWaypoint;
};
Waypoint.cpp:
Waypoint::Waypoint(void)
{
    _nextWaypoint=NULL;
}
Waypoint::~Waypoint(void)
{
}
Waypoint* Waypoint::nodeWithTheLocation(cocos2d::Point location)
{
    Waypoint* pRet=new Waypoint();
    if (pRet && pRet->initWithTheLocation(location))
    {
        pRet->autorelease();
        return pRet;
    }
    else
    {
        delete pRet;
        pRet=NULL;
        return NULL;
    }
}

bool Waypoint::initWithTheLocation(cocos2d::Point location)
{
    bool bRet=false;
    do
    {
        _myPosition=location;
        this->setPosition(Point::ZERO);
        bRet=true;
    } while (0);

    return bRet;
}

void Waypoint::setNextWaypoint(Waypoint* waypoint)
{
    _nextWaypoint=waypoint;
}

Waypoint* Waypoint::getNextWaypoint()
{
    return _nextWaypoint;
}

第二步:在MainScene.h里声明如下代码

public:
    ……
    //重写Layer的update方法
    //我们主要在这个方法里实现色狼移动
    virtual void update(float delta);

    CREATE_FUNC(MainScene);

private:
    //路径开始点
    Waypoint *beginningWaypoint;
    //路径目标点
    Waypoint *destinationWaypoint;
    //色狼的移动速度
    float walkingSpeed;
    //色狼当前位置
    cocos2d::Vec2 myPosition;
    //色狼大叔
    cocos2d::Sprite* dsSprite;
    //路径点集合
    cocos2d::Vector<Waypoint*> wayPositions;
    //判断2个点是否靠近
    bool collisionWithCircle(cocos2d::Vec2 circlePoint,float radius,cocos2d::Vec2 circlePointTwo, float radiusTwo);

这里代码,也对之前代码进行了修改重构,比如把之前在init()方法里声明的dsSprite(色狼)改到了这里变成了一个全局变量,因为在后续的update方法中需要对它进行操作。

第二步:在MainScene.cpp的init()方法中创建路径点集合编写如下代码

……
//初始化地图路径点集合
    this->wayPositions = Vector<Waypoint*>();

    //添加地图1号路径点到集合中
    Waypoint *waypoint1=Waypoint::nodeWithTheLocation(Point(920, 435+dsh/2.0f));
    if(this->wayPositions.size()>0)
    {
        //设置下一个节点
        waypoint1->setNextWaypoint(this->wayPositions.back());
    }
    this->wayPositions.pushBack(waypoint1);
    //添加地图2号路径点到集合中
    Waypoint *waypoint2=Waypoint::nodeWithTheLocation(Point(762, 435+dsh/2.0f));
    if(this->wayPositions.size()>0)
    {
        waypoint2->setNextWaypoint(this->wayPositions.back());
    }
    this->wayPositions.pushBack(waypoint2);
    //添加地图3号路径点到集合中
    Waypoint *waypoint3=Waypoint::nodeWithTheLocation(Point(762, 360+dsh/2.0f));
    if(this->wayPositions.size()>0)
    {
        waypoint3->setNextWaypoint(this->wayPositions.back());
    }
    this->wayPositions.pushBack(waypoint3);
    //添加地图4号路径点到集合中
    Waypoint *waypoint4=Waypoint::nodeWithTheLocation(Point(685, 360+dsh/2.0f));
    if(this->wayPositions.size()>0)
    {
        waypoint4->setNextWaypoint(this->wayPositions.back());
    }
    this->wayPositions.pushBack(waypoint4);
    //添加地图5号路径点到集合中
    Waypoint *waypoint5=Waypoint::nodeWithTheLocation(Point(685, 116+dsh/2.0f));
    if(this->wayPositions.size()>0)
    {
        waypoint5->setNextWaypoint(this->wayPositions.back());
    }
    this->wayPositions.pushBack(waypoint5);
    //添加地图6号路径点到集合中
    Waypoint *waypoint6=Waypoint::nodeWithTheLocation(Point(520, 116+dsh/2.0f));
    if(this->wayPositions.size()>0)
    {
        waypoint6->setNextWaypoint(this->wayPositions.back());
    }
    this->wayPositions.pushBack(waypoint6);
    //添加地图7号路径点到集合中
    Waypoint *waypoint7=Waypoint::nodeWithTheLocation(Point(520, 180+dsh/2.0f));
    if(this->wayPositions.size()>0)
    {
        waypoint7->setNextWaypoint(this->wayPositions.back());
    }
    this->wayPositions.pushBack(waypoint7);
    //添加地图8号路径点到集合中
    Waypoint *waypoint8=Waypoint::nodeWithTheLocation(Point(285, 180+dsh/2.0f));
    if(this->wayPositions.size()>0)
    {
        waypoint8->setNextWaypoint(this->wayPositions.back());
    }
    this->wayPositions.pushBack(waypoint8);
    //添加地图9号路径点到集合中
    Waypoint *waypoint9=Waypoint::nodeWithTheLocation(Point(285, 268+dsh/2.0f));
    if(this->wayPositions.size()>0)
    {
        waypoint9->setNextWaypoint(this->wayPositions.back());
    }
    this->wayPositions.pushBack(waypoint9);
    //添加地图10号路径点到集合中
    Waypoint *waypoint10=Waypoint::nodeWithTheLocation(Point(204, 268+dsh/2.0f));
    if(this->wayPositions.size()>0)
    {
        waypoint10->setNextWaypoint(this->wayPositions.back());
    }
    this->wayPositions.pushBack(waypoint10);
    //添加地图11号路径点到集合中
    Waypoint *waypoint11=Waypoint::nodeWithTheLocation(Point(204, 350+dsh/2.0f));
    if(this->wayPositions.size()>0)
    {
        waypoint11->setNextWaypoint(this->wayPositions.back());
    }
    this->wayPositions.pushBack(waypoint11);
    //添加地图12号路径点到集合中
    Waypoint *waypoint12=Waypoint::nodeWithTheLocation(Point(50, 350+dsh/2.0f));
    if(this->wayPositions.size()>0)
    {
        waypoint12->setNextWaypoint(this->wayPositions.back());
    }
    this->wayPositions.pushBack(waypoint12);
……

第三步:在MainScene.cpp的init()方法中初始化几个变量以及精灵的初始位置编写如下代码

 ……
   //获取集合中的最后一个点,12号点
    Waypoint *waypoint0=wayPositions.back();
    //设置运动的开始点
    beginningWaypoint=waypoint0;
    //设置运动的目标点为12号点的下一个点,11号点
    destinationWaypoint=waypoint0->getNextWaypoint();
    //设置色狼当前位置值
    myPosition=waypoint0->getMyPosition();
    //设置色狼在地图的初始位置
    dsSprite->setPosition(myPosition);
    //设置女主角在地图的初始位置,为集合中的1号点
    nhSprite->setPosition(wayPositions.front()->getMyPosition());
//设置移动速度
    this->walkingSpeed=0.2f;
    //定时器
    this->scheduleUpdate();
……

第四步:在MainScene.cpp中对collisionWithCircle方法进行实现编写如下代码

//判断2个圆点是否靠近
//circlePoint:第一个圆点坐标
//radius:第一个圆半径
//circlePointTwo:第二个圆点坐标
//radiusTwo:第二个圆半径
bool MainScene::collisionWithCircle(cocos2d::Vec2 circlePoint,float radius,cocos2d::Vec2 circlePointTwo, float radiusTwo)
{
    //2点间距离公式计算
    float xdif = circlePoint.x - circlePointTwo.x;
    float ydif = circlePoint.y - circlePointTwo.y;
    float distance = sqrt(xdif * xdif + ydif * ydif);

    if(distance <= radius + radiusTwo)
    {
        return true;
    }
    return false;
}

第五步:在MainScene.cpp中对update方法进行实现:

//判断色狼大叔是否和目标点碰到
    if (this->collisionWithCircle(myPosition,1,destinationWaypoint->getMyPosition(),1) )
    {
        //是否还有下一个目标点
        if (destinationWaypoint->getNextWaypoint())
        {
            //重新设定开始点和目标点
            beginningWaypoint=destinationWaypoint;
            destinationWaypoint=destinationWaypoint->getNextWaypoint();
        }
    }
    //获取目标点的坐标
    Point targetPoint=destinationWaypoint->getMyPosition();
    //计算目标点的向量
    Point normalized=Point(targetPoint.x-myPosition.x,targetPoint.y-myPosition.y).getNormalized();

    //根据速度和向量分别计算x,y方式上的偏移值
    float ox=normalized.x * walkingSpeed;
    float oy=normalized.y *walkingSpeed;
    myPosition = Point(myPosition.x + ox, myPosition.y +oy);
    //重新设定色狼的位置实现移动
    dsSprite->setPosition(myPosition);

第六步:运行测试游戏效果,看看色狼会不会沿着我们设定的路径移动。

Windows下的效果

在看看android手机下的效果,把新加的2个cpp文件添加到Android.mk文件里然后开始编译打包so文件(不会请参考:Cocos2dx.3x入门三部曲-Hello Game项目解析(三)篇)。完成打包后在eclipse中连接手机运行看到如下效果:

发现在真机上运行时女主角、色狼的位置相当于道路都有点偏上了,并且好像背景地图也没有显示全背景的顶部和底部有一部分没有显示出来,但是在windows下运行确正常,这个是什么原因呢,该怎么调整呢?我的手机分辨率是:960x540 而我们的地图素材图片分辨率是:960x640,所以导致了这个问题,这个是关于不同手机屏幕分辨率适配问题,在下一篇中我们将继续的完善修改这个游戏原型解决这个问题。

作者交流QQ:2303452599

           邮箱:[email protected]

时间: 2024-10-12 05:00:14

Cocos2d-x3.x塔防游戏(保卫萝卜)从零开始(一)的相关文章

Cocos2d-x3.x塔防游戏(保卫萝卜)从零开始(二)

一.前提: 完成前一篇的内容. 具体参考:Cocos2d-x3.x塔防游戏(保卫萝卜)从零开始(一)篇 二.本篇目标: l  说说关于cocos2dx手机分辨率适配 l  对前一篇完成的塔防游戏原型进行屏幕适配完善 三.内容: l  说说关于cocos2dx手机分辨率适配 在上一篇的结尾我们遗留了一个问题,在真机上运行时女主角.色狼的位置相当于道路都有点偏上了,并且好像背景地图也没有显示全背景的顶部和底部有一部分没有显示出来,但是在windows下运行确正常,这个是什么原因呢,该怎么调整呢?我的

Cocos2D:塔防游戏制作之旅(十六)

编译运行你的app,放置一些炮塔在你的地图上吧!你将看到炮塔在敌人移动如攻击范围时如何立即开始攻击,并且敌人的血条将随着攻击不断减少知道它们被人道毁灭!胜利即将来临了! 哦!Okay,这里只有少数细节还未实现你就可以得到一个完整特性的塔防游戏啦!音效应该是一个不错的尝试.并且尽管不可战胜和极端富裕很好,你的基地还是应该有能力持续抗打的能力 - 并且你需要限制玩家的金币供给. 闪耀着的炮塔:Gotta Polish It All! 开始实现显示玩家剩余的命数 - 以及当玩家失败时发生什么! 打开H

Coco2d-x 塔防游戏“贼来了”之开发简档 之 一

原来的教程为<塔防游戏之贼来了>(这是我之前学习Cocos2d-x时候,看到的一个比较好的教程)原文地址目前只在泰然网看到,http://www.tairan.com/archives/6413 ,原作者为任珊.本文是基于这个教程,而编写的游戏开发简档,有了这些图表,你就可以轻松的开发出这款游戏了. 一 编写游戏简档 游戏范围文档 游戏名 塔防游戏 平台 android\ios 玩家数 1 类型(如动作.街机) 塔防游戏 横屏/竖屏 横屏 设计分辨率 480*320 玩法概要 塔防是一类通过在

远古守卫/cocos2d-x 源代码/塔防游戏/高仿王国保卫战

下载地址:spm=686.1000925.0.0.j3MZhz&id=550780702354" style="color:rgb(224,102,102)">下载地址 本源代码高度模仿IOS游戏王国保卫战,由国外IOS商业开发教程站点raywenderlich内部project师制作,有很完整的关卡设计,战斗流程,长达12个关卡,各种敌兵,怪物,箭塔,炮塔一应俱全,还有魔法系统,成就系统以及后台数据库.差点儿达到了能够上线的商业游戏级别. 代码严格规范,提供了

Coco2d-x 塔防游戏“贼来了”之开发简档 之 二

原来的教程为<塔防游戏之贼来了>(这是我之前学习Cocos2d-x时候,看到的一个比较好的教程)原文地址目前只在泰然网看到,http://www.tairan.com/archives/6413 ,原作者为任珊.本文是基于这个教程,而编写的游戏开发简档,有了这些图表,你就可以轻松的开发出这款游戏了. 五游戏对象 游戏场景主要游戏对象. 游戏对象名称 对象特性 地图 地图上有一个瓦片图层,画出了一条路线.有一个对象图层,指出敌人的行进路线.一个触摸图层,指示该瓦片是块空地. 敌人 动态(播放动画

Coco2d-x 塔防游戏“贼来了”之开发简档 之 三(完结)

原来的教程为<塔防游戏之贼来了>(这是我之前学习Cocos2d-x时候,看到的一个比较好的教程)原文地址目前只在泰然网看到,http://www.tairan.com/archives/6413 ,原作者为任珊.本文是基于这个教程,而编写的游戏开发简档,有了这些图表,你就可以轻松的开发出这款游戏了. 最后,如果你能画出类图的话,写代码就不成问题了.不过在写代码的时候,可能发现之前的设计不好,这时可以修改这些图,我觉得至少要维护顺序图(序列图)和类图,保证它们和你的代码是一致的,这样当你的代码出

类Kingdom Rush塔防游戏的数值设计(一)&mdash;&mdash;玩法概述和基础战斗模型建立

玩法概述---- Kingdom Rush,即皇家守卫军,IronHide出的一款塔防游戏.其涂鸦的风格.独特的英雄和士兵系统.较为庞大的战斗场面,在塔防游戏中独树一帜,取得了巨大的成功. 在这里,基于Kingdom Rush的核心玩法,推拟出游戏所需的核心战斗数值(包括战斗内成长数值). 首先,需要明确游戏的玩法. 游戏大概的战斗场景如下 战斗地图区域划分 1.出怪点:2.预设路径:3.防御塔建造点:4.防守点 战斗单位划分 我方 1.防御塔:2.英雄:3.士兵 敌方 1.怪物 游戏玩法概述

cocos2dx塔防游戏逻辑

cocos2dx 塔防游戏逻辑 1.欢迎界面 2.tield制作游戏地图,空块设置cantouch属性为1 3.设置地图锚点,把锚点加入一个锚点容器,给怪物的行走函数传入 该锚点容器参数,让怪物按照锚点行走 4.创建炮塔(触摸坐标转地图,转炮塔数组坐标) 先检查触摸开始位置是否可以创建炮塔,然后创建炮塔选择界面,位置是整块 再根据触摸的选择位于哪一块上,确定创建哪个炮塔 5.碰撞检测,将死亡的敌人加入死亡敌人容器,并且遍历移除 将已碰撞的子弹加入移除子弹容器,并遍历移除 6. 炮塔基类(炮塔分3

使用Unity创建塔防游戏(Part1)

How to Create a Tower Defense Game in Unity - Part1 原文作者:Barbara Reichart 文章原译:http://www.cnblogs.com/le0zh/p/create-tower-defense-game-unity-part-1.html 参考了这篇文章,我打算做一些改进,以及翻译这篇文章的第2部分.如有不恰当的地方,欢迎各位指正. 塔防游戏极为流行,没有什么能比看着自己的防御塔消灭邪恶的入侵者更爽的事了. 你将会学习到 创建一