【Cocos游戏实战】功夫小子02:基础类分析和实现
在开始第二节课之前,这里需要声明的是,
首先:本系列课程是为了结合Cocos2d-x 3.x的基本组件和核心模块的学习而制作的,开发所使用的版本是3.0,但是代码稍加修改就可以运用在3.x的其他版本上。
其次:本游戏项目是一个非商业化项目,游戏资源和代码都会在后续的课程中释放出来,供大家参考使用,提供的代码所使用的解决方案是最基础和简单的,没有对引擎源码进行改造,提供给有一定Cocos2d-x的初学者,希望学习的人能再此基础上进行思考,并加以改进。
So,各位大神见笑了,可绕道~~
上一节课我们对游戏的基本需求进行了一个大概的分析,知道了游戏的基本内容,游戏中基本角色属性和游戏的基本规则特点。这节课我们就将对我们游戏项目的基本类进行分析和实现。
主要内容是:
游戏项目基本工具类的分析和实现
游戏项目英雄实体类的分析和实现
具有简单AI的怪物实体类的分析和实现
基本的工具类分析和实现
由于我们的游戏内容和属性决定我们游戏中的主角和游戏中怪物会有各种动作,这些动作的创建我这里采用帧动画的形式,因此对于众多的动作,有一个专门的动画创建器是必须而且有利于减少代码的重复的,下面是关于帧动画(Frame By Frame),其基本的解释是在“连续的关键帧”中分解动画动作,也就是在时间轴的每帧上逐帧绘制不同的内容,使其连续播放而成动画。
比如下面主角跑动作的动画帧:
需要注意的是这些动画帧的命名规律。
关于帧动画:
这里的帧缓存是指我们将游戏的图片帧资源进行打包预加载进入游戏缓存,这部分会在后面的课程中详细讲解,这里不详细赘述。
下面就是动画创建器的一些关键部分的代码:
头文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | /*! * file ActionTool.h * date 2015/05/07 22:10 * * author SuooL * Contact: hu1020935219@gmail.com * * brief 工具类:创建动画 * * TODO: long description * * note */ #ifndef ActionTool_H__ #define ActionTool_H__ #include "cocos2d.h" USING_NS_CC; class ActionTool { public: static Animate* animationWithFrameName(const char *frameName, int iloops, float delay); static Animate* animationWithFrameAndNum(const char *frameName, int num, float delay); }; #endif // ActionTool_H__s |
cpp文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 | /*! * class ActionTool * * ingroup GroupName * * brief 动画创建器 * * TODO: long description * * note * * author SuooL * * version 1.0 * * date 五月 2015 * * Contact: hu1020935219@gmail.com * */ #include "ActionTool.h" Animate* ActionTool::animationWithFrameName(const char *frameName, int iloops, float delay) { SpriteFrame* frame = NULL; Animation* animation = Animation::create(); int index = 1; // 遍历具有frameName特征的图片帧 do { String * name = String::createWithFormat("%s%d", frameName, index++); // 当前是第index帧 frame = SpriteFrameCache::getInstance()->getSpriteFrameByName(name->getCString()); if (frame== NULL) { break; } animation->addSpriteFrame(frame); } while (true); // 设置Animation的一些基本属性 animation->setDelayPerUnit(delay); animation->setRestoreOriginalFrame(true); Animate* animate = Animate::create(animation); // 返回一个Animate return animate; } Animate* ActionTool::animationWithFrameAndNum(const char *frameName, int framecount, float delay) { SpriteFrame* frame = NULL; Animation* animation = Animation::create(); // 遍历图片帧 for (int index = 1; index <= framecount; index++) { String * name = String::createWithFormat("%s%d", frameName, index++); frame = SpriteFrameCache::getInstance()->getSpriteFrameByName(name->getCString()); animation->addSpriteFrame(frame); } animation->setDelayPerUnit(delay); animation->setRestoreOriginalFrame(true); Animate* animate = Animate::create(animation); /* // 第二中实现方式,用一个帧图片的向量数组,创建Animation Vector<SpriteFrame*> animFrames; char str[20]; for (int k = 1; k <= framecount; k++) { sprintf(str, "%s%d.png", frameName, k); SpriteFrame *frame = SpriteFrameCache::getInstance()->spriteFrameByName(str); animFrames.pushBack(frame); } return Animate::create(Animation::createWithSpriteFrames(animFrames, delay));*/ return animate; } |
其次的工具类便是一些基本的全局文件,包括:
枚举文件
全局宏定义文件
全局变量文件
英雄实体类的基本分析和实现
进行的基本步骤是:
确定主角所具有的属性
确定主角所具有的方法
编码实现主角英雄类
首先是英雄的一些基本属性,包括
而具有的方法则分为:
下面是英雄类的一些关键代码(由于代码有些长,且抽象起来都是类似的代码,只贴一些关键部分):
头文件中的一些方法和属性:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 | public: // 根据图片名创建英雄 void InitHeroSprite(char *hero_name, int iLevel); // 返回当前英雄 Sprite* GetSprite(); // 设置动画,run_directon为精灵脸朝向,false朝右,frameName为图片帧名字 void SetAnimation(const char *frameName, float delay, bool run_directon); // 停止动画 void StopAnimation(); // 跳起动画 void JumpUpAnimation(const char *name_each, float delay, bool run_directon); // 跳落动画 void JumpDownAnimation(const char *name_each, float delay, bool run_directon); // 跳落动画结束 void JumpEnd(); // 攻击动画 void AttackAnimation(const char *name_each, float delay, bool run_directon); // 攻击动画结束 void AttackEnd(); // 死亡动画 void DeadAnimation(const char *name_each, float delay, bool run_directon); // 死亡动画结束 void DeadEnd(); // 受伤动画 void HurtByMonsterAnimation(const char *name_each, float delay, bool run_directon); // 受伤动画结束 void HurtByMonsterEnd(); // 判断英雄是否运动到了窗口的中间位置,visibleSize为当前窗口的大小 bool JudgePositosn(Size visibleSize); bool IsDead; // HP & MP 值 float m_iCurrentHp; float m_iTotleHp; float m_iCurrentMp; float m_iTotleMp; float percentage; float m_iSpeed; bool m_bIsAction; // 查看当前是否已经在打怪了 bool m_bIsJumping; // 查看是否在跳 bool IsRunning; // 判断是否在跑动画 bool IsAttack; // 判断是否在攻击动画 bool IsHurt; // 判断是否受伤 bool HeroDirecton; // 英雄运动的方向 bool m_bCanCrazy; // 判断是否处于狂暴状态 CREATE_FUNC(Hero); private: Sprite* m_HeroSprite; // 精灵 char *Hero_name; // 用来保存初始状态的精灵图片名称 |
CPP文件的部分方法的实现代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 | // 受伤 void Hero::HurtByMonsterAnimation(const char *name_each, float delay, bool run_directon) { if (IsHurt || IsDead) return; //受伤优先 if (IsRunning || IsAttack) { m_HeroSprite->stopAllActions();//当前精灵停止所有动画 //恢复精灵原来的初始化贴图 this->removeChild(m_HeroSprite, true);//把原来的精灵删除掉 m_HeroSprite = Sprite::createWithSpriteFrame(SpriteFrameCache::getInstance()->getSpriteFrameByName(Hero_name));//恢复精灵原来的贴图样子 m_HeroSprite->setFlippedX(HeroDirecton); this->addChild(m_HeroSprite); IsRunning = false; IsAttack = false; } Animate* action = ActionTool::animationWithFrameName(name_each, 1, delay); //创建回调动作,受伤动画结束调用HurtEnd() CallFunc* callFunc = CallFunc::create(this, callfunc_selector(Hero::HurtByMonsterEnd)); //创建连续动作 ActionInterval* hurtackact = Sequence::create(action, callFunc, NULL); m_HeroSprite->runAction(hurtackact); IsHurt = true; } // 受伤结束 void Hero::HurtByMonsterEnd() { m_iCurrentHp -= 20.0f; IsHurt = false; percentage = m_iCurrentHp / m_iTotleHp * 100.0f; if (m_iCurrentHp < 0.0f) { DeadAnimation("dead", 0, HeroDirecton); } } // 死亡 void Hero::DeadAnimation(const char *name_each, float delay, bool run_directon) { m_HeroSprite->stopAllActions(); // 调整方向 if (HeroDirecton != run_directon) { HeroDirecton = run_directon; m_HeroSprite->setFlippedX(run_directon); } // 创建动作 Animate* act = ActionTool::animationWithFrameName(name_each, 1, delay); //创建回调动作,攻击结束后调用AttackEnd() CallFunc* callFunc = CallFunc::create(this, callfunc_selector(Hero::DeadEnd)); //创建连续动作 ActionInterval* attackact = Sequence::create(act, callFunc, NULL); m_HeroSprite->runAction(attackact); Director::getInstance()->getScheduler()->setTimeScale(0.5); } // 死亡结束 void Hero::DeadEnd() { IsDead = true; //恢复死亡的样子 this->removeChild(m_HeroSprite, true); //把原来的精灵删除掉 m_HeroSprite = Sprite::createWithSpriteFrame(SpriteFrameCache::getInstance()->getSpriteFrameByName("monsterDie6.png")); //恢复死亡的样子 m_HeroSprite->setFlippedX(HeroDirecton); this->addChild(m_HeroSprite); } // 判断位置 bool Hero::JudgePositosn(Size visibleSize) { if (this->getPositionX() > (visibleSize.width / 2.0 + 2.0) || (this->getPositionX() < visibleSize.width / 2.0 - 2.0)) // 精灵到达mid? return false; else return true;//到达中间位置 } |
怪物类的分析和实现
分析的步骤和上面分析英雄类的是一样的,不同的一部分是我们游戏中的怪物要有一定的AI,关于AI的如何设计这里不详细的展开细说了,而且这里所实现的是十分基础和简单的AI(或者不能称之为AI),设定一个全屏的仇恨范围,怪物在自己活着或者英雄或者的状态下会一直尝试跟随英雄并尝试攻击英雄。具体的AI设计大家可以自行Google或者看视频中的一部分讲解。
贴出一些关键部分的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 | public: Monster(void); ~Monster(void); // 公有属性 // 怪物种类 int m_iType; //判断是否在跑动画 bool IsRunning; //判断是否在攻击动画 bool IsAttack; //判断是否在受伤动画 bool IsHurt; //判断是否死亡 bool Isdead; //怪物运动的方向 bool MonsterDirecton; // 方法 // 根据图片名创建怪物,不带血条的怪物 void InitMonsterSprite(char *name, char *a, char *die, char *walk, char *dieLast, int m_iLevel); //返回英雄 Sprite* GetSprite(); //设置走动动画,num为图片数目,run_directon为精灵脸朝向,false朝右,name_each为name_png中每一小张图片的公共名称部分 void SetAnimation(const char *name_each, bool run_directon, float delay, int iLoops); //停止走动动画 void StopAnimation(); //攻击动画 void AttackAnimation(const char *name_each, bool run_directon, float delay, int iLoops); //攻击动画结束 void AttackEnd(); //受伤动画 void HurtAnimation(const char *name_each, bool run_directon, float delay, int iLoops); //受伤动画结束 void HurtEnd(); //死亡动画 void DeadAnimation(const char *name_each, bool run_directon, float delay, int iLoops); //死亡动画结束 void DeadEnd(); //怪物死亡闪烁结束 void BlinkEnd(); //在可视范围内,怪物跟随英雄运动 void FollowRun(Hero* m_hero, GameMap* m_map); //判断是否攻击 void JudegeAttack(float dt); //怪物启动监听英雄 void StartListen(Hero* m_hero, GameMap* m_map); //监听函数,每隔3秒检测下,计算英雄与怪物的距离 void updateMonster(float delta); //更新函数,如果英雄在可视范围内,不断触发 void update(float delta); CREATE_FUNC(Monster); private: Sprite* m_MonsterSprite; // 怪物精灵 char *Monster_name; // 用来保存初始状态的精灵图片名称 char *Monster_a; // 怪物攻击帧 char *Monster_die; // 死亡帧 char *Monster_walk; // 行走帧 char *Die_name; Hero* my_hero; // 当前英雄 GameMap* my_map; // 当前地图 float dis; // 当前怪物和英雄的距离 int m_iHP; |
这里具体实现部分代码可以参考:http://cn.cocos2d-x.org/tutorial/show?id=2242
这节课的基本内容就是这些,下节课我们将进行学习游戏项目相关界面的分析与实现。上面的代码质量很差,实现的方式也很基础,仅提供给大家一个实现的参考,希望想学习的朋友能够自己思考,不断改进和求精,写出完全属于自己的代码。
感谢本文笔者(振生)的分享,Cocos引擎中文官网欢迎更多的开发者分享开发经验,来稿请发送至support@cocos.org。