捕鱼游戏的基本设计思路

一、场景

以基本的捕鱼游戏为例,功能做得比较简单,因为我是个新人,刚学完C++不久,后面做的也不是很好,在高级工程师唐哥指导下还是完成了游戏。

首先创建两个场景,第一个设置为主界面,包含背景,开始游戏和退出的按钮。后一个设置为游戏界面,包含背景,炮台,切换炮台等级按钮,返回按钮。其实后面鱼类的游动,捕鱼网的动画以及子弹都是在游戏界面里面设计。在这里先不讲,只讲场景吧。

添加背景图片:

  1. <span style=“white-space:pre”> </span>auto background = Sprite::create(“background.png”);
  2. background->setPosition(Vec2(visibleSize.width / 2, visibleSize.height / 2));
  3. this->addChild(background);

这里的visiblesize就是主场景的规格,创建背景精灵设置坐标后添加上去。对于两个界面都是一样的用法。

然后就是切换场景:

  1. void HelloWorld::onStart(Ref* pSender){
  2. auto ss = (MenuItemImage*)pSender;
  3. string str = ss->getName();
  4. //Sound->playEffect(“21.mp3”); //切换音效
  5. if (str == “start”){ //切换场景
  6. auto scene = GameScene::createScene();
  7. Director::getInstance()->pushScene(scene);
  8. }
  9. else{
  10. Director::getInstance()->end();
  11. }
  12. }
这里切换音效被我屏蔽了,可以根据自己的需要添加。根据图片设置的名字获取到名字,判断点击的东西获取到的字符串是否等于获取到的名字,从而结束或者切换场景。
主场景用到的pushScene();就是方便与后面场景来回切换,对应于游戏场景的popScene()。
Director::getInstance()->popScene();
二、炮台
炮台是添加在游戏界面的,当主界面切换到游戏界面的时候就会显示一个初始的炮台,而炮弹有着自己多个类型。玩过捕鱼游戏的人应该知道炮台通过两个按钮去实现炮台的循环切换。
先讲炮台吧:
  1. <span style=”white-space:pre”> </span>//增加炮台等级的按钮
  2. auto right = MenuItemImage::create(“+.png”, “+++.png”, CC_CALLBACK_1(GameScene::changeBattery, this));
  3. right->setName(“right”);
  4. auto menu1 = Menu::create(right,nullptr);
  5. //减少炮台等级的按钮
  6. auto left = MenuItemImage::create(“-.png”, “—.png”, CC_CALLBACK_1(GameScene::changeBattery, this));
  7. left->setName(“left”);
  8. auto menu2 = Menu::create(left, nullptr);
  9. this->addChild(menu1, 9);
  10. menu1->setPosition(Vec2(visibleSize.width / 2 + 230, visibleSize.height / 2 – 365));
  11. this->addChild(menu2, 9);
  12. menu2->setPosition(Vec2(visibleSize.width / 2 – 50, visibleSize.height / 2 – 365));
  13. //添加一个初始炮台
  14. battery = Sprite::create(“paotai1.png”);
  15. battery->setPosition(Vec2(visibleSize.width / 2 + 90, visibleSize.height / 2 – 340));
  16. addChild(battery, 9);</span>

首先创建两个按钮精灵,就是放上两个图片,设置名字为right和left,用来控制炮台的等级,后面创建添加一个初始炮台到游戏界面。后面给按钮写上回调方法changeBattery();

  1. <span style=”font-size:14px;”>void GameScene::changeBattery(Ref* pSender){
  2. Size visibleSize = Director::getInstance()->getVisibleSize();
  3. auto ss = (MenuItemImage*)pSender;
  4. string str = ss->getName();
  5. if (str == “right”){ //增加炮台等级
  6. if (type == 3){
  7. type = 1;
  8. }
  9. else{
  10. type++;
  11. }
  12. }
  13. else if (str == “left”){ //减少炮台等级
  14. if (type == 1){
  15. type = 3;
  16. }
  17. else{
  18. type–;
  19. }
  20. }
  21. <span style=”color:#ff0000;”>makebattery(type)</span>;
  22. }</span>

传入点击的对象转化字符串,判断字符串与设置的名字是否相等,进入不同的语句。这里的type是炮台类型,设置的是3个炮台,以上就是控制炮台类型,后面写了makebattery(type);
也就是生成炮台的关键:

  1. <span style=”font-size:14px;”>void GameScene::makebattery(int type){
  2. Size visibleSize = Director::getInstance()->getVisibleSize();
  3. if (battery){
  4. battery->removeFromParent();
  5. battery = NULL;
  6. }
  7. if (type == 1 && battery == NULL){
  8. battery = Sprite::create(“paotai1.png”);
  9. }
  10. else if (type == 2 && battery == NULL){
  11. battery = Sprite::create(“paotai2.png”);
  12. }
  13. else if (type == 3 && battery == NULL){
  14. battery = Sprite::create(“paotai3.png”);
  15. }
  16. battery->setPosition(Vec2(visibleSize.width / 2 + 90, visibleSize.height / 2 – 340));
  17. this->addChild(battery,9);
  18. }</span>

传入一个炮台类型,先判断炮台是否存在,如果存在就删除它,再根据类型type进入不同语句从而生成新的炮台,给它设置坐标,添加到游戏界面,就可以通过两个按钮实现炮台循环切换,这样就完成了炮台部分。

三、炮弹

分析炮弹对象,首先炮弹肯定是有它的类型,然后根据点击的坐标发射,每点击一次发射一颗炮弹。它有着自己的动作属性,例如速度和方向,还有金币不足时不允许发射炮弹。
void GameScene::onTouchEnded(Touch* touch, Event* event){
Size visibleSize = Director::getInstance()->getVisibleSize();
auto Location = touch->getLocation(); ? ? ? //获得坐标getLocation();
auto dx = Location.x – battery->getPositionX();
auto dy = Location.y – battery->getPositionY();
auto radian = atan2(dy , dx);
auto inclination = radian * 180 / 3.14;
rotation = -(inclination)+90;
if (rotation <= 90 && rotation >= -90){
battery->setRotation(rotation);
this->makeBullet(type);
auto move2 = MoveBy::create(2, Vec2(dx * 10, dy * 10));
bullet->runAction(move2);
}
}
看代码,设置点击事件,根据点击坐标获取到X和Y,计算出与炮台之间的角度,可以根据炮台的设置点击之后将炮台设置偏转角,重点在于偏转角的弧度与角度之间的转换,atan2(y,x);这个函数先传入偏移Y,再传偏移X。计算出偏转角rotation之后炮台设置该角度,子弹设置该角度。makeBullet(type);type是传入炮台类型从而生成炮弹时就可以根据类型生成不同的炮弹,然后执行炮弹的移动。
void GameScene::makeBullet(int type){
Size visibleSize = Director::getInstance()->getVisibleSize();
if (coin>0){
if (type == 1){
bullet = Sprite::create(“zidan1.png”);
coin -= 1;
}
else if (type == 2){
bullet = Sprite::create(“zidan2.png”);
coin -= 10;
}
else if (type == 3){
bullet = Sprite::create(“zidan3.png”);
coin -= 100;
}
bullet->setRotation(rotation);
bullet->setPosition(Vec2(visibleSize.width / 2 + 90, visibleSize.height / 2 – 333));
this->addChild(bullet, 8);
bulletVec.pushBack(bullet);
}
if (coin<t){
auto gg = Sprite::create(“gg.png”);
gg->setPosition(visibleSize.width / 2, visibleSize.height / 2 + 290);
addChild(gg, 9);
}
}
这是生成炮弹时代码,当金币小于0是加载GG的图片,就是显示金币不足。当金币大于0时才可以创建炮弹,根据传入的炮台类型type判断生成的炮弹类型,扣除不同金币,设置全局的偏转角,将其放入数组bulletVec。其实还是很简单的代码,将其放入数组是用于优化游戏,方便删除,节省资源占用。其实做的时候会有很多细节需要处理,例如不放入数组删除,程序运行久了之后就会有点卡,可以试试。
四、序列帧动画

序列帧动画主要有几个类:
? ? CCSpriteFrame:精灵帧信息,序列帧动画是依靠多个精灵帧信息来显示相应的纹理图像,一个精灵帧信息包包含了所使用的纹理,对应纹理块的位置以及纹理块是否经过旋转和偏移,这些信息可以取得对应纹理中正确的纹理块区域做为精灵帧显示的图像。
? ? CCAnimationFrame:序列帧动画单帧信息,它存储了对应的精灵帧信息。
? ? CCAnimation:序列帧动画信息,它存储了所有的单帧信息,可以对单帧信息进行管理。
? ? CCAnimate:序列帧动画处理类,它是真正完成动画表演的类。
void GameScene::fishAnim(){
<span style=”white-space:pre”> </span>SpriteFrameCache *frameCache = SpriteFrameCache::getInstance();
frameCache->addSpriteFramesWithFile(“fish.plist”, “fish.png”); ? ?//加载Plist缓存
Size visibleSize = Director::getInstance()->getVisibleSize();
auto fish = Sprite::createWithSpriteFrameName(“fish1.png”); ? ? //创建一个鱼的首个帧
fishVec.pushBack(fish); ? ? ?//放进数组
fish->setPosition(Vec2(dd, dt)); ? ? //设置坐标
this->addChild(fish, 5); ? ? //添加到场景
Animation* animation = Animation::create(); ? ? ? //创建序列帧动画
animation->setDelayPerUnit(0.01f); ? ? ? ? ? ? ? //0.01s播放完序列帧动画
animation->addSpriteFrame(frameCache1->getSpriteFrameByName(“fish1.png”));
animation->addSpriteFrame(frameCache1->getSpriteFrameByName(“fish2.png”));
animation->addSpriteFrame(frameCache1->getSpriteFrameByName(“fish3.png”)); ? ? //3个序列帧动画顺序执行
Animate* animate = Animate::create(animation); ? ? ? ? ? ? //帧动画处理
fish->runAction(Sequence::create(animate, CallFunc::create(CC_CALLBACK_0(GameScene::remove, this)), nullptr));
}
最后那个remove回调函数是用于删除动画,优化游戏环境。
五、鱼
鱼是游戏中比较关键的一部分。
一步一步来,先分析游戏,首先鱼是从屏幕外生成,然后它有个移动的动作,而且分为不同的类型。
然后就可以进入代码部分,首先设置一个计时器,控制时间生成鱼,而鱼分种类,所以就用到随机函数,随机输出一个值,传入switch语句,从而生成不同的鱼。
<span style=”font-size:18px;”>void GameScene::xiaoyuupdate(float dt){
Size visibleSize = Director::getInstance()->getVisibleSize();
int s = rand() % 7; ? ? ? //随机的s用于传入switch语句
auto move = MoveTo::create(8, Vec2(visibleSize.width + 650, rand() % 768)); ? ? //下面做两个移动的动作move1和move
auto move1 = MoveTo::create(15, Vec2(-650, rand() % 768));
switch (s){
case 0:
xiaoyu = Sprite::createWithSpriteFrameName(“wugui1.png”);
xiaoyu->setPosition(Vec2(-xiaoyu->getContentSize().width / 2, rand() % 768));
playanim(0);
xiaoyu->runAction(move);
p = 2;
break;
case 1:
xiaoyu = Sprite::createWithSpriteFrameName(“huangyu1.png”);
xiaoyu->setPosition(Vec2(-xiaoyu->getContentSize().width / 2, rand() % 768));
playanim(1);
xiaoyu->runAction(move);
p = 1;
break;
case 2:
xiaoyu = Sprite::createWithSpriteFrameName(“dly01.png”);
xiaoyu->setPosition(Vec2(visibleSize.width + xiaoyu->getContentSize().width / 2, rand() % 768));
playanim(2);
xiaoyu->runAction(move1);
p = 2;
break;
case 3:
xiaoyu = Sprite::createWithSpriteFrameName(“dm01.png”);
xiaoyu->setPosition(Vec2(visibleSize.width + xiaoyu->getContentSize().width / 2, rand() % 768));
playanim(3);
p = 2;
xiaoyu->runAction(move1);
break;
default:
break;
}
addChild(xiaoyu);
}</span>
四只不同的鱼类就可以添加到场景了,然后就是鱼的动作,上面只是有个移动,如果没有动画的鱼就会看起来很尴尬,所以上面看不懂的playanim就是定义的一个动画函数,传入一个int型常量,就是鱼的动画播放动画,看过前一章的就知道也是利用序列帧动画完成的鱼的动作。
<span style=”font-size:18px;”>void GameScene::playanim(int x){
Size visibleSize = Director::getInstance()->getVisibleSize();
if (x == 0){
Animation* animation1 = Animation::create();
animation1->setDelayPerUnit(0.4f);
animation1->addSpriteFrame(frameCache->getSpriteFrameByName(“wugui1.png”));
animation1->addSpriteFrame(frameCache->getSpriteFrameByName(“wugui2.png”));
animation1->addSpriteFrame(frameCache->getSpriteFrameByName(“wugui3.png”));
animation1->addSpriteFrame(frameCache->getSpriteFrameByName(“wugui2.png”));
animation1->addSpriteFrame(frameCache->getSpriteFrameByName(“wugui1.png”));
animation1->addSpriteFrame(frameCache->getSpriteFrameByName(“wugui2.png”));
animation1->addSpriteFrame(frameCache->getSpriteFrameByName(“wugui3.png”));
animation1->addSpriteFrame(frameCache->getSpriteFrameByName(“wugui2.png”));
animation1->addSpriteFrame(frameCache->getSpriteFrameByName(“wugui1.png”));
Animate* animate1 = Animate::create(animation1);
xiaoyu->runAction(RepeatForever::create(animate1));
}
else if (x == 1){
Animation* animation2 = Animation::create();
animation2->setDelayPerUnit(0.1f);
animation2->addSpriteFrame(frameCache->getSpriteFrameByName(“huangyu1.png”));
animation2->addSpriteFrame(frameCache->getSpriteFrameByName(“huangyu2.png”));
animation2->addSpriteFrame(frameCache->getSpriteFrameByName(“huangyu3.png”));
animation2->addSpriteFrame(frameCache->getSpriteFrameByName(“huangyu2.png”));
animation2->addSpriteFrame(frameCache->getSpriteFrameByName(“huangyu1.png”));
animation2->addSpriteFrame(frameCache->getSpriteFrameByName(“huangyu2.png”));
animation2->addSpriteFrame(frameCache->getSpriteFrameByName(“huangyu3.png”));
animation2->addSpriteFrame(frameCache->getSpriteFrameByName(“huangyu2.png”));
animation2->addSpriteFrame(frameCache->getSpriteFrameByName(“huangyu1.png”));
Animate* animate2 = Animate::create(animation2);
xiaoyu->runAction(RepeatForever::create(animate2));
}
else if (x == 2){
Animation* animation3 = Animation::create();
animation3->setDelayPerUnit(0.1f);
animation3->addSpriteFrame(frameCache1->getSpriteFrameByName(“dly01.png”));
animation3->addSpriteFrame(frameCache1->getSpriteFrameByName(“dly02.png”));
animation3->addSpriteFrame(frameCache1->getSpriteFrameByName(“dly03.png”));
animation3->addSpriteFrame(frameCache1->getSpriteFrameByName(“dly04.png”));
animation3->addSpriteFrame(frameCache1->getSpriteFrameByName(“dly05.png”));
animation3->addSpriteFrame(frameCache1->getSpriteFrameByName(“dly06.png”));
animation3->addSpriteFrame(frameCache1->getSpriteFrameByName(“dly07.png”));
animation3->addSpriteFrame(frameCache1->getSpriteFrameByName(“dly08.png”));
animation3->addSpriteFrame(frameCache1->getSpriteFrameByName(“dly09.png”));
animation3->addSpriteFrame(frameCache1->getSpriteFrameByName(“dly10.png”));
Animate* animate3 = Animate::create(animation3);
xiaoyu->runAction(RepeatForever::create(animate3));
}
else if (x == 3){
Animation* animation5 = Animation::create();
animation5->setDelayPerUnit(0.1f);
animation5->addSpriteFrame(frameCache1->getSpriteFrameByName(“dm01.png”));
animation5->addSpriteFrame(frameCache1->getSpriteFrameByName(“dm02.png”));
animation5->addSpriteFrame(frameCache1->getSpriteFrameByName(“dm03.png”));
animation5->addSpriteFrame(frameCache1->getSpriteFrameByName(“dm04.png”));
animation5->addSpriteFrame(frameCache1->getSpriteFrameByName(“dm05.png”));
animation5->addSpriteFrame(frameCache1->getSpriteFrameByName(“dm06.png”));
animation5->addSpriteFrame(frameCache1->getSpriteFrameByName(“dm07.png”));
animation5->addSpriteFrame(frameCache1->getSpriteFrameByName(“dm08.png”));
Animate* animate5 = Animate::create(animation5);
xiaoyu->runAction(RepeatForever::create(animate5));
}
fishVec.pushBack(xiaoyu); ? //将鱼放进数组
}</span>
那我就不讲序列帧动画了,可以去看前面的博客,最后放进数组也是用于优化游戏。
其实这里也做的不是很好,只是一个简单的demo,可以给鱼添加一些动作,让它活灵活现的在游戏里,也可以根据自己的爱好或者需要添加东西。
六、碰撞检测
炮弹和鱼的碰撞可以说是很关键的部分,对这个游戏分析吧,首先炮弹有它的类型,比如1号炮,2号炮……鱼也有它的类型,比如小黄鱼,乌龟,鲸鱼……
然后再分析,当鱼和炮弹碰撞上的时候,鱼会生成一个死亡动画然后消失,炮弹会生成渔网,然后消失,当然渔网和鱼的死亡动画也会消失。
在这里要判断鱼是否和炮弹碰撞到就要用到一个api,那就是getBoundingBox()
void GameScene::update(float dt){
for (int i = 0; i < fishVec.size(); i++){
for (int j = 0; j < bulletVec.size(); j++){
if (fishVec.at(i)->getBoundingBox().intersectsRect(bulletVec.at(j)->getBoundingBox())){
makeyuwang(type, bulletVec.at(j)->getPositionX(), bulletVec.at(j)->getPositionY());
remove2(bulletVec.at(j));
return;
}
}
}
这个用法就是精灵->getBoundingBox().intersectsRect(精灵2->getBoundingBox())。
getBoundingBox()就是获取矩形框,然后intersectsRect就是用于判断两个矩形框是否发生碰撞。
这就是具体的用法。。
?

原创文章,作者:棋牌游戏,如若转载,请注明出处:https://www.qp49.com/2018/07/09/900.html

(0)
上一篇 2018年7月6日 下午4:48
下一篇 2018年7月9日 下午2:42

发表评论

邮箱地址不会被公开。 必填项已用*标注