Cocos2d-x 3.3塔防游戏《保卫萝卜》教程04:实现简单的游戏原型

2015年03月18日 09:19 0 点赞 0 评论 更新于 2025-11-21 17:33

简单回顾

在之前的教程中,我们完成了以下步骤:

  1. 环境搭建
  2. 项目创建
  3. 项目解析

本章前提

完成Hello Game项目的创建编译。具体参考:Cocos2d-x 3.3塔防游戏《保卫萝卜》教程03:Hello Game项目解析

本篇目标

  • 阐述关于塔防游戏的想法和思路
  • 实现一个简单的塔防游戏原型

内容

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

游戏PSD设计效果图

首先展示一张塔防游戏PSD设计效果图(此处应补充效果图,如果有)。

游戏故事设定

本游戏借用了《保卫萝卜》的大名进行宣传和参照,但游戏内容有所不同。故事设定为:很久很久以前,在美丽的大学宿舍区住着一群美丽天真的女孩,邪恶的色狼大叔们总想对她们图谋不轨。我们的英雄——善良勇敢的女生宿舍管理员,利用生活中的武器,如菜刀、皮鞋、玩具飞机等,在大叔必经的路上狙击他们,保护女孩们免受伤害。

游戏元素组成

  1. 地图:每一关的地图都不相同,主要体现在道路和炮台位的差异。
  2. 炮塔:包括水果刀、菜刀、老鼠药、高跟鞋、玩具飞机等。不同的炮台具有不同的价格、攻击速度、攻击属性和攻击方式。
  3. 子弹:由炮台发射,具备不同的攻击值、扩散值、迟缓值和攻击范围值等。
  4. 怪物:各类猥琐大叔、叫兽、色狼。不同的色狼具有不同的速度值、伤害值和耐揍值,它们会沿着地图上的道路不断靠近道路终点的女主角。
  5. 女主角:位于道路终点的女孩,没有攻击力,需要炮塔的保护。她具有一定的纯洁值,当纯洁值被大叔玷污光后,她会自杀,游戏也随之结束。
  6. 分数&资源:杀死不同的色狼能获得一定的分数,分数可用于购买新的炮台。每一关都有一定的初始分数,用于支撑游戏最初的消耗。每一关的分数仅限在本关使用,下一关开启时,前面积累的分数将被清空。
  7. 宝箱:可以用分数资源购买宝箱,有一定几率获得比投入分数几倍的回报。
  8. 医生:用分数购买医疗服务,可对女主角的纯洁值进行修补。

游戏开发模式

整个游戏的开发方式是先实现一个很小的游戏核心原型,然后不断修改和扩大这个原型,直至游戏完成。这种方式更适合读者理解,也便于读者跟着文章学会游戏开发。

实现一个简单的游戏原型

新建游戏工程

新建游戏工程名为DefendTheGirl(保卫女孩),包名为:com.game.defendthegirl。如果还不会创建工程,请参照:Hello Game项目创建篇(此处应补充实际链接,如果有)。

本篇原型需要实现的内容

  1. 在主场景中载入一张地图。
  2. 在地图终点放置一个女主角,在地图的起点放置一个色狼大叔。
  3. 让色狼大叔沿着地图指定的路线向女主角的位置靠近。

素材图片准备

准备地图图片、女主角图片和色狼大叔图片,并将这几张素材图片拷贝到文件夹Resources下面。

具体实现步骤

1. 在主场景中载入一张地图
  • 第一步:用Microsoft Visual Studio 2012打开proj.win32工程,然后在src下新建MainScene.h、MainScene.cpp作为游戏的主场景。如果不会创建Scene,可参考: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);
    ...
    
  • 第二步:运行程序,即可在画面上看到色狼大叔和女主角。
3. 让色狼大叔沿着地图指定的路线向女主角的位置靠近
  • 路径坐标分析:以地图的右下顶点为坐标系原点,整个道路分成12个坐标点,女主角在1号坐标点,色狼大叔在12号坐标点。色狼大叔将沿着图中黄色的线路从12点开始,依次经过11点、10点、9点……直至到达1点。实现思路是:色狼从12点出发时,告知它目标点是11点;当色狼到达11点时,再告知它下一个目标点是10点,以此类推,直到到达1点。
  • 实现技术点
  • 对Sprite(色狼)进行setPosition操作,可以改变Sprite在图上的位置,从而实现Sprite的移动。
  • 计算Sprite(色狼)位置到目标点的向量值,然后根据这个向量值和色狼移动速度计算Sprite在x、y方向上的距离偏移值,用这个偏移值调整Sprite的位置。
  • 判断Sprite(色狼)到达目标点:当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()方法中创建路径点集合,编写如下代码:

... // 获得色狼大叔的高 float dsh = dsSprite->getTextureRect().size.height; // 初始化地图路径点集合 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); // 依次添加其他路径点... ...

- **第四步**:在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文件(参考:Cocos2d-x 3.3开发《保卫萝卜》03:Hello Game项目解析)。完成打包后,在eclipse中连接手机运行。发现真机上运行时,女主角、色狼的位置相对于道路有点偏上,并且背景地图的顶部和底部有一部分没有显示出来,而在Windows下运行正常。这是由于不同手机屏幕分辨率适配问题导致的,在下一篇中,我们将继续完善和修改这个游戏原型,以解决这个问题。

作者信息

boke

boke

共发布了 3994 篇文章