Cocos2dx

前言



单元格即显示2、4、8等数字的不同颜色的方格,如下图。本项目中Grid类实现单元格的相关内容,包括数字、背景更新,移动、新增、消除特效

image

设计



1#pragma once 2#include "cocos2d.h" 3 4USING_NS_CC; 5 6class Grid:public cocos2d::Layer 7{ 8public: 9 static char* G2U(const char* gb2312); 10 static void changeType(int type); 11 static int getType(); 12 CREATE_FUNC(Grid); 13 virtual bool init(); 14 15 CC_SYNTHESIZE(int, _value, ScoreValue); 16 //void updateValue(); 17 void initValue(int value); 18 void initValue(int value, int row, int column); 19 bool compareTo(Grid* grid); 20 21 void setLocalPosition(int row, int column); 22 23 void moveAndClear(int targetRow, int targetColumn); 24 void moveAndUpdate(); 25 26private: 27 void loadMap(); 28 void updateBg(); 29 Label* _label; 30 LayerColor* _bg; 31};

首先我们看一下头文件中Grid类包含的函数,并对此进行说明介绍:

  • G2U用于中文显示,前一篇博文有说明
  • changeType, 和 getType主要是游戏类型的更换,由于本项目包含三个不同的模式(经典模式,小兵传奇,颜色模式),因此在模式切换时,需要更新单元格的显示内容;
  • initValue 显然是设置单元各显示值的函数;
  • compareTo 判断两个单元格的显示内容是否相同(用于游戏移动单元格时,判断是否可以合并两个单元格)’
  • setLocalPosition 根据所在的行列设置该单元格在父节点中的坐标;
  • moveAndClear移动并消除单元格的特效
  • moveAndUpdate合并单元格后新增单元格的特效
  • loadMap 获取单元格显示内容的所有值(本处函数取名有问题,因为包含所有显示内容的不是map数据结构,建议修改一下)
  • updateBg 设置背景色

1. 基本功能设计


单元格的基本类容就是显示数字和背景色,也就是绘制一个LayerColor和一个Label的问题,在init函数中初始化实现;初始化完毕后就是设置显示值以及坐标的问题,由initValue实现

initValue函数,用于初始化单元格的显示值、背景色、和显示位置,因此其结构如下:

1void Grid::initValue(int value, int row, int column) 2{ 3 setLocalPosition(row, column); 4 initValue(value); 5} 6 7// init the first value of the grid, and the value should be 0 or 1 8void Grid::initValue(int value) 9{ 10 _value = value; 11 updateBg(); 12} 13 14void Grid::setLocalPosition(int row, int column) 15{ 16 this->setPosition(column*73 + 8, row*73 + 8); 17} 18 19void Grid::updateBg() 20{ 21 ... 22}

其中,由于显示值改变必然涉及到背景色的更新,因此设计的时候将initValue(int value)时,直接内调用了updateBg(且该函数设置为私有),更新背景色代码为:

1void Grid::updateBg() 2{ 3 _label->setString(map[_value]); 4 Color3B color, fcolor = Color3B(255, 255, 255); 5 6 switch(_value) 7 { 8 case 0: 9 color = Color3B(247,213,97); 10 fcolor = Color3B(120, 120, 120); 11 break; 12 case 1: 13 color = Color3B(166, 232, 103); 14 fcolor = Color3B(90, 90, 90); 15 break; 16 case 2: 17 color = Color3B(87, 212, 154); 18 break; 19 case 3: 20 color = Color3B(19, 181, 177); 21 break; 22 case 4: 23 color = Color3B(68, 138, 202); 24 break; 25 case 5: 26 color = Color3B(200, 97, 234); 27 break; 28 case 6: 29 color = Color3B(225,115,181); 30 break; 31 case 7: 32 color = Color3B(238,100, 141); 33 break; 34 case 8: 35 color = Color3B(243, 157, 79); 36 break; 37 case 9: 38 color = Color3B(245, 124, 78); 39 break; 40 case 10: 41 color = Color3B(246, 76, 20); 42 break; 43 case 11: 44 color = Color3B(210, 210, 10); //Color3B(105, 84, 187); 45 break; 46 case 12: 47 color = Color3B(190, 194, 22); // Color3B(50, 86, 204); 48 break; 49 case 13: 50 color = Color3B(160, 160, 10); 51 break; 52 case 14: 53 color = Color3B(50, 86, 204);// Color3B(24, 66, 149); 54 break; 55 default: 56 color = Color3B(24, 66, 149); 57 break; 58 } 59 _label->setColor(fcolor); 60 _bg->setColor(color); 61 62 if(type != 1) 63 return; 64 65 if(_value > 15) 66 _label->setSystemFontSize(20); 67 else if(_value > 12) 68 _label->setSystemFontSize(24); 69 else if(_value > 8) 70 _label->setSystemFontSize(26); 71 72}

2. 显示内容设计


由于本项目有三个模式,不同模式单元格的显示内容不同,如下图:(从左到右,分别是经典模式,小兵传奇,纯色模式)

imageimageimage

三种不同的模式中,显示内容如:

1std::string map[16] = {"0"}; 2std::string guan[] = {"小兵", "下士", "中士", "上士", "少尉", "中尉", "上尉", "少校", "中校", "上校", "大校", "准将", "少将", "中将", "上将", "元帅"}; 3std::string num[] = {"2", "4", "8", "16", "32", "64", "128", "256", "512", "1024", "2048", "4096", "8192", "16384", "32768", "65536", "131072", "262144", "524288"};

然后需要根据当前的模式来选择显示的内容集:

1void Grid::loadMap() 2{ 3 4 if(map[0].compare("0") == 0) //first load, then get the type from save file 5 { 6 type = UserDefault::getInstance()->getIntegerForKey("type", 0); 7 } 8 9 for(int i = 0; i<16; i++) 10 { 11 if(type == 0) 12 map[i] = Grid::G2U(guan[i].c_str()); // soldier mode 13 else if(type == 1) 14 map[i] = num[i]; // classic mode 15 else 16 map[i] = ""; // color mode 17 } 18}

在该函数中,type作为全局变量使用,对于第二个for循环,type直接与0,1其他进行比较是个隐患,建议用enum进行修改(当时顺手直接用了常量)为了阅读简洁改成如下代码:

1enum StateType{ 2 SOLDIER, // 0 3 CLASSIC, // 1 4 COLOR // 2 5}; 6 7void Grid::loadMap() 8{ 9 10 if(map[0].compare("0") == 0) //first load, then get the type from save file 11 { 12 type = UserDefault::getInstance()->getIntegerForKey("type", 0); 13 } 14 15 for(int i = 0; i<16; i++) 16 { 17 if(type == StateType::SOLDIER) 18 map[i] = Grid::G2U(guan[i].c_str()); 19 else if(type == StateType::CLASSIC) 20 map[i] = num[i]; 21 else if(type == StateType::COLOR) 22 map[i] = ""; 23 } 24}

从上面的代码中可以看出,直接影响显示内容的是变量type,其相关的函数为:

1static int type = 0; 2void Grid::changeType(int _type) 3{ 4 type = _type; 5 GameTool::getInstance()->loadScore(type); 6 GameLayer::getInstance()->loadGrids(type); 7} 8 9int Grid::getType() 10{ 11 return type; 12}

当调用changeType时,需要重现加载分数和最高分,以及更新游戏模式后的游戏布局(从save file中获取状态并恢复)

3. 移动特效


单元格的移动特效有一下几种,刚出现时的淡入效果FadeIn,单纯的移动到目的地移动效果MoveTo,移动并消除特效,移动合并出一个新的单元格特效

3.1 新生成单元格特效

    写到这才发现在代码中进入特效实在GameLayer中随机生成一个单元格时,顺手写进去的,因此没有加入Grid函数中,显然是不符合本文的设计要求的,因此将相关代码搬了个家,首先在 Grid.h中添加 initAction(), 并在Grid.cpp中实现:

1void Grid::initAction() 2{ 3 auto a1 = ScaleTo::create(0.3f, 1); 4 auto a2 = FadeIn::create(1); 5 auto a3 = Spawn::create(a1, a2, nullptr); 6 setScale(0); 7 runAction(a3); 8}

3.2 单纯移动特效

    单纯的移动单元格,可以直接调用MoveTo来实现,同样的该处代码直接顺手写在了GameLayer的moveOnly函数中,同样的迁移过来,新的代码为:

1void Grid::moveOnly(int targetRow, int targetColumn) 2{ 3 runAction(MoveTo::create(0.2f, Vec2(73 * targetColumn + 8, 73 * targetRow + 8))); 4}

(插一句,设计很重要,顺手写会对后面调试以及审阅代码增加难度)

3.3 移动并消除特效

    当合并两个单元格时,至少需要消除其中的一个单元格,这里有两种设计思路:

  •         将两个可以合并的单元格都消除,然后新生成一个单元格,值为消除单元格+1;
  •         消除一个单元格,另外一个单元格值+1;

    无论选择哪一种方式,都至少有一个单元格是要调用移动并消除特效的,当然本项目选择了第二种方式,至于为什么,好吧当时写的时候这样更加简单,(当然推荐用第一种,节省了node创建回收开销)

    移动并消除主要包括移动、淡出、自销毁三个特效,具体代码如下:

1void Grid::moveAndClear(int targetRow, int targetColumn) 2{ 3 auto a1 = MoveTo::create(0.2f, Vec2(73 * targetColumn + 8, 73 * targetRow + 8)); 4 auto a2 = FadeOut::create(0.2f); 5 auto a3 = Spawn::create(a1, a2, nullptr); 6 auto a4 = CallFunc::create([&]{ 7 //log("remove child"); 8 this->removeFromParent(); 9 }); 10 this->runAction(Sequence::create(a3, a4, nullptr)); 11}

CallFunc是一个很有意思的类,表示调用函数的特效,上面代码的意思是当单元格移动到目的地时,将该单元格从父节点中移除(引用计数-1, 为0时会自动被回收)

3.4 移动合并特效

由于我们选择第二种方式,因此这个特效就是简单的淡入、放大缩小的特效,如下:

1void Grid::moveAndUpdate() 2{ 3 this->setVisible(false); 4 auto action01 = ScaleTo::create(0.1f, 1.1f); 5 auto action02 = ScaleTo::create(0.1f, 1); 6 auto action03 = FadeIn::create(0.2f); 7 auto action04 = Sequence::create(action03, CallFunc::create([&]{ 8 //log("add child"); 9 this->setVisible(true); 10 }), action01, action02, nullptr); 11 this->runAction(action04); 12}

那么选择第一种方式呢,该如何编写相关代码?其实使用第一种也很简单,其分解过程为:

   移动到目的地 ---》 更新value ---》 放大缩小特效

1void Grid::moveAndUpdate(int targetRow, int targetColumn) 2{ 3 auto a1 = MoveTo::create(0.2f, Vec2(73 * targetColumn + 8, 73 * targetRow + 8)); 4 auto a2 = CallFunc::create([&]{ 5 //log("remove child"); 6 this->updateValue(); // value++ and update bg 7 }); 8 auto a3 = ScaleTo::create(0.1f, 1.1f); 9 auto a4 = ScaleTo::create(0.1f, 1); 10 auto a5 = Sequence::create(a1, a2, a4, a4, nullptr); 11 this->runAction(a5); 12}

4. 判断单元格是否相等


判断单元格是否相等是合并单元格的前提,当两个单元格的value相等,即表示可合并;否则不能合并,代码如下:

1bool Grid::compareTo(Grid* grid) 2{ 3 if(grid == nullptr) 4 return false; 5 6 return _value == grid->getScoreValue(); 7}

看到上面的代码发现可以直接简化为一句:

1bool Grid::compareTo(Grid* grid) 2{ 3 return grid!=nullptr && _value == grid->getScoreValue(); 4}

(说一句,在判断指针参数的时候,需要首先确定指针非空,否则可能出现大家都知道的bug)

实现、小结



上面贴出了部分代码,并给出了说明, 这里不贴代码了, 源码请参考: https://github.com/liuyueyi/2048

小结一下:

Grid实现单元格的所有功能,如基本的绘制、显示,到移动淡入淡出特效,在Grid中实现这些代码可以减轻主游戏逻辑中的代码量,增强代码的阅读性

点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

MySQL部分从库上面因为大量的临时表tmp_table造成慢查询

背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_

皕杰报表之UUID

​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

一篇文章带你了解JavaScript日期

日期对象允许您使用日期(年、月、日、小时、分钟、秒和毫秒)。一、JavaScript的日期格式一个JavaScript日期可以写为一个字符串:ThuFeb02201909:59:51GMT0800(中国标准时间)或者是一个数字:1486000791164写数字的日期,指定的毫秒数自1970年1月1日00:00:00到现在。1\.显示日期使用