今天开始做战斗,回合制战斗代码实现第四篇 刀塔传奇战斗模式(即时卡牌战斗模式)

来源:互联网 发布:淘宝店铺图片尺寸页头 编辑:程序博客网 时间:2024/04/28 12:18

        说是即时卡牌战斗,其实在我看来这种玩法也是回合制战斗的一种,差不多算是九宫格战斗的一种变种,在一个回合120秒内,分成了3次小规模的遇怪自动战斗,而这种自动战斗不在是回合而是即时的,但整个战斗过程,都不是做即时通信的,所以对网络要求也不是很高,一般2d网络就可以很流畅的玩这种弱联网的手游,所以近期这种模式成为了手机网游的比较流行的一种战斗模式,当然卡牌类游戏还有几种比如就秒开源的暗黑世界类,采用这种战斗方式的手游现在有去吧比卡丘这样的作品,再就是益智类战斗卡牌代表作品炉石传说,这两种卡牌的实现方式我们后面来研究(本来许诺的仙剑demo3d回合战斗我们先等等),循例我们先看看这种战斗模式是什么样子的。这种游戏的模式呢,我最早玩的游戏并非出现在手游上,而是页游的萌战记不同的是,萌战记的玩法还介于即使卡牌和九宫格之间,技能的释放还是跟九宫格战斗一样,自动完成,不过整个战斗大概可分为站位,出手,状态,BUFF及战斗数值五种构成的方式已经有了新战斗系统的雏形了,而且可能萌战记的玩法更注重职业的选择比如tank就是tank,治疗,dps也划分非常清晰,所以跟刀塔传奇玩法差别还是很明显,不过萌战记在vip收费的环节策划疏漏严重,运营也相对失败,所以玩过的玩家可能不多,好了,我们回到刀塔传奇的战斗模式上,

1.站位:英雄出场的位置应该在数值表中进行了固定的配置,英雄移动差不多遵循,选定技能---选定技能目标----判断技能攻击距离-----寻路,

2.出手:

  1) 第一次出手顺序:除了有白虎这种一出场就buff的,一般都是固定。那么做法有两种,一种是英雄表中配置出手权重值,还有一种就是出场阵容中计算出手顺序。

  2) 英雄技能出手顺序:这个是根据英雄数据表中配置的,如普通攻击→第二个技能→第三个技能→第四个技能→普通攻击→第二个技能,被动技能则是战斗一开始就释放,效果持续到战斗结束。

  3) 英雄攻击间隔:实际上刀塔传奇中是没有攻击速度这一概念的,那么果断推测每个英雄的攻击间隔应该都是固定。即英雄出手顺序,普攻,小技能之间的间隔也是固定的。有个特例就是手动释放的大招,在这里英雄普攻之后,也能立即释放大招,没有间隔。那么释放完大招后,攻击间隔怎样算,我的理解是大招刷新了攻击间隔,英雄开始重复之前的攻击间隔。(不过新手游《那年那兔那些事》加入了攻击速度这个概念,所以并不是绝对的)

3. 状态及buff

  dota和刀塔传奇的魅力就在这里,几十种状态,几十种buff。

  我们把战斗场景中的会造成模型动作改变的称为状态,而只造成属性上改变的称为buff,这个概念在这里再提一下。一个英雄的状态是唯一性的,buff却有很多种。例如,一个英雄同时中了眩晕和毒技能,那么这个时候他拥有一个眩晕的显示状态,以及眩晕带来的不可攻击不可释放技能不可行动的buff和毒技能带来的减速和持续掉血的buff。

  其中刀塔传奇还做了一个特殊的状态,就是受击,当敌方英雄对此英雄造成的伤害超过到最大血量的一定百分比的时候,此英雄处于受击状态,并刷新攻击间隔。

整个战斗流程可以归为,出场->站位->出手->状态及buff改变->结束战斗,

以上这些只是刀塔传奇一些皮毛的分析,因为如果做大规模的分析可能要分析很多问题,拿站位来说,那年那兔那些事里面的火力支援大招经常会出现攻击位置判定跟敌人位置不同步的情况,经常出现大招放完,没有对敌人造成伤害,这种情况可能刀塔传奇和其他类似的游戏也有,我们这里就不讨论了,因为这个到底是什么情况,可能只有游戏的当值策划才能说的明白,在制作游戏过程中,可能需要策划进行长时间讨论和调试才能确定。

好了,我们现在开始搞干货,如果就是上面这几行网上也能看到的文字,可能观众要骂人了,当然这也不符合我博客的习惯,既然是讲做战斗,当然要做了,所以下面我们看看如何实现这种战斗模式,(事先说明,代码不是我写的,代码来自于网络,作者不详,我这里只贴出代码和相应的讲解,无法提供代码工程,需要工程请度娘自寻,或者私信留言给我,不是我小气,因为不是我的代码可能涉及一些商业利益等问题,所以没办法开源共享),原作者的开发环境是winxp+vs2012+cocos2dx3.0,因为xp系统的编译和win7以后的编译有差别,直接编译会报错,所以我对代码做了重构(开始想过直接修改平台工具集的选项,不过报error LNK2038: 检测到“_MSC_VER”的不匹配项: 值“1700”不匹配,重新生成解决方案也没有成功,所以只好重新搭建工程,编译器改变为v110),并且升级引擎(我电脑里没有装cocos2dx3.0版本)改为了win764位+vs2012+cocos2dx3.5,因为引擎升级到了cocos2dx3.5,所以适当的修改了些代码,好了废话就到这我们开始。

代码的目录结构非常清晰,注意开发语言是c++11(有兴趣的小伙伴可以改成lua或者js等等),

代码可以说写的非常完整,战斗需要实现的东西都实现了,下面我们就来看看这个东东到底是怎么写的,.H文件就不贴出来,我们直接帖Cpp,如果真对这段代码感兴趣,可以百度或者私信给我,我们开始,首先建好工程,之后我们开始代码之旅

先是AppDelegate这个类不用多说吧,cocos2dx生成之后需要这个来做启动,如果不懂请去看cocos2dx基础,我们直接看代码

#include "AppDelegate.h"//#include "HelloWorldScene.h"#include "GameScene.h"#include "LoadScene.h"#include "OverScene.h"USING_NS_CC;AppDelegate::AppDelegate() {}AppDelegate::~AppDelegate() {}bool AppDelegate::applicationDidFinishLaunching() {    // initialize director    auto director = Director::getInstance();    auto glview = director->getOpenGLView();    if(!glview) {glview = GLViewImpl::create("GameDT");       // glview = GLView::create("My Game");glview->setFrameSize(600, 360);        director->setOpenGLView(glview);    }    // turn on display FPS    director->setDisplayStats(true);    // set FPS. the default value is 1.0/60 if you don't call this    director->setAnimationInterval(1.0 / 60);glview->setDesignResolutionSize(800, 480, kResolutionExactFit);    // create a scene. it's an autorelease object   // auto scene = HelloWorld::createScene();//auto scene = GameScene::createScene();auto scene = LoadScene::createScene();//auto scene = OverScene::createScene();    // run    director->runWithScene(scene);    return true;}// This function will be called when the app is inactive. When comes a phone call,it's be invoked toovoid AppDelegate::applicationDidEnterBackground() {    Director::getInstance()->stopAnimation();    // if you use SimpleAudioEngine, it must be pause    // SimpleAudioEngine::getInstance()->pauseBackgroundMusic();}// this function will be called when the app is active againvoid AppDelegate::applicationWillEnterForeground() {    Director::getInstance()->startAnimation();    // if you use SimpleAudioEngine, it must resume here    // SimpleAudioEngine::getInstance()->resumeBackgroundMusic();}
这个类代码没什么可说的,创建工程自动就可以生成,我们修改的除了包含头文件之外,其实最重要的只有改auto scene = LoadScene::createScene();这句,因为cocos2dx工作的方式是,AppDelegate这个类启动一个Scene类,然后建立很多Layer类,然后把画出的Layer添加到Scene上,所以我们现在开始看scene类,在我们这叫做LoadScene

#include "LoadScene.h"#include "GameScene.h"LoadScene::LoadScene(){numOfRes = 12;numOfLoadedRes = 0;}Scene* LoadScene::createScene(){auto scene = Scene::create();auto layer = LoadScene::create();scene->addChild(layer);return scene;}bool LoadScene::init(){if (!Layer::init()){return false;}auto size = Director::getInstance()->getWinSize();labelLoad = Label::createWithTTF("loading...", "fonts/Marker Felt.ttf", 25);labelPercent = Label::createWithTTF("0", "fonts/Marker Felt.ttf", 25);labelLoad->setPosition(size.width/2, size.height/2 -140);labelPercent->setPosition(size.width/2, size.height/2 -100);this->addChild(labelLoad);this->addChild(labelPercent);this->loadRes();return true;}//异步加载void LoadScene::loadRes(){//加载精灵动画SpriteFrameCache::getInstance()->addSpriteFramesWithFile("plist/magic.plist");SpriteFrameCache::getInstance()->addSpriteFramesWithFile("plist/magic2.plist");SpriteFrameCache::getInstance()->addSpriteFramesWithFile("plist/magic3.plist");SpriteFrameCache::getInstance()->addSpriteFramesWithFile("plist/magic4.plist");SpriteFrameCache::getInstance()->addSpriteFramesWithFile("plist/monster1.plist");SpriteFrameCache::getInstance()->addSpriteFramesWithFile("plist/monster2.plist");SpriteFrameCache::getInstance()->addSpriteFramesWithFile("plist/monster3.plist");SpriteFrameCache::getInstance()->addSpriteFramesWithFile("plist/monster4.plist");SpriteFrameCache::getInstance()->addSpriteFramesWithFile("plist/monster5.plist");SpriteFrameCache::getInstance()->addSpriteFramesWithFile("plist/monster6.plist");TextureCache::getInstance()->addImageAsync("plist/magic.png",                         CC_CALLBACK_1(LoadScene::loadCallback, this));TextureCache::getInstance()->addImageAsync("plist/magic2.png",                         CC_CALLBACK_1(LoadScene::loadCallback, this));TextureCache::getInstance()->addImageAsync("plist/magic3.png",                         CC_CALLBACK_1(LoadScene::loadCallback, this));TextureCache::getInstance()->addImageAsync("plist/magic4.png",                         CC_CALLBACK_1(LoadScene::loadCallback, this));TextureCache::getInstance()->addImageAsync("plist/monster1.png",                                      CC_CALLBACK_1(LoadScene::loadCallback, this));TextureCache::getInstance()->addImageAsync("plist/monster2.png",                                      CC_CALLBACK_1(LoadScene::loadCallback, this));TextureCache::getInstance()->addImageAsync("plist/monster3.png",  CC_CALLBACK_1(LoadScene::loadCallback, this));TextureCache::getInstance()->addImageAsync("plist/monster4.png",  CC_CALLBACK_1(LoadScene::loadCallback, this));TextureCache::getInstance()->addImageAsync("plist/monster5.png",                                      CC_CALLBACK_1(LoadScene::loadCallback, this));TextureCache::getInstance()->addImageAsync("plist/monster6.png",                                      CC_CALLBACK_1(LoadScene::loadCallback, this));//加载地图TextureCache::getInstance()->addImageAsync("map/bbg_spring2.png",  CC_CALLBACK_1(LoadScene::loadCallback, this));//战斗场景组件TextureCache::getInstance()->addImageAsync("card/panel.png",                                      CC_CALLBACK_1(LoadScene::loadCallback, this));}void LoadScene::loadCallback(Texture2D* texture){numOfLoadedRes++;char tmp[10];sprintf(tmp, "%d", (int)(((float)numOfLoadedRes/numOfRes) * 100));labelPercent->setString(tmp);if (numOfLoadedRes == numOfRes){//this->removeChild(labelLoad, true);//this->removeChild(labelPercent, true);this->scheduleOnce(schedule_selector(LoadScene::toStartScene), 1.0f);}}void LoadScene::toStartScene(float dt){auto gameScene = GameScene::createScene();Director::getInstance()->replaceScene(TransitionCrossFade::create(0.5f, gameScene));}
这个类看上去代码量不大,是干什么用的呢?其实是画进度条,要实现的东西也很简单,画一个loading文字,然后,画一个动态0到100的递增,那下面的代码是干什么的呢?
我们看toStartScene方法是跳转到我们真正的游戏Scene界面类GameScene,而loadRes就是异步加载资源,当然别忘了回调函数loadCallback,函数里numOfLoadedRes == numOfRes就是加载完成时,调用跳转方法toStartScene,好了我们看下一个类GameScene

#include "GameScene.h"//#include "json/document.h"//#include "GameInstance.h"GameScene::GameScene(){}GameScene::~GameScene(){}Scene* GameScene::createScene(){auto scene = Scene::create();auto layer = GameScene::create();scene->addChild(layer);return scene;}bool GameScene::init(){if (!Layer::init()){return false;}this->initMap();spriteSystem = SpriteSystem::create();this->addChild(spriteSystem, 1);/*magicSystem = MagicSystem::create();this->addChild(magicSystem, 2);*/return true;}void GameScene::initMap(){auto size = Director::getInstance()->getVisibleSize();auto texMap1 = TextureCache::getInstance()->getTextureForKey("map/bbg_spring2.png");auto map1 = Sprite::createWithTexture(texMap1);map1->setPosition(size.width/2, size.height/2);this->addChild(map1);}
真是一个类比一个类简单,这个类看上去代码更少,我们看看都干了什么东东,

this->initMap();

spriteSystem = SpriteSystem::create();

this->addChild(spriteSystem, 1);看过之后发现init这3行最重要,具体工作是,initMap加载绘制地图,其实就是画一张背景图,然后对spriteSystem类进行初始化,然后把这个Layer类绘制到Scene上面,下面我们开始研究这个这段代码中最重要的类,

#include "SpriteSystem.h"#include "GameInstance.h"#include "Magic.h"#include "OverScene.h"#include "Resource.h"SpriteSystem::SpriteSystem(){m_bIsAttack = false;m_fElapseTime = 0;}SpriteSystem::~SpriteSystem(){}bool SpriteSystem::init(){if (!Layer::init()){return false;}m_bIsAttack = false;m_fElapseTime = 0; m_fOverTime = 0;bool spSel[6] = {true, true, true, true, true, true};for (unsigned int i = 0; i < 6; i++){GameInstance::getInstance()->spSel[i] = spSel[i];m_fOldScheTime[i] = 0;m_fScheduleTime[i] = 0;}GameInstance::getInstance()->heroNum = 3;GameInstance::getInstance()->enemyNum = 3;this->readJson(spConf);this->readMagicJson(magConf);this->initSprite();this->setMagicPanel();return true;}_stSpriteConfig SpriteSystem::readConfig(SpriteId id){_stSpriteConfig tmp;tmp = spConf[id-1];return tmp;}void SpriteSystem::readJson(_stSpriteConfig (&spConf)[6]){//_stSpriteConfig spConf[6];rapidjson::Document doc;std::string str = FileUtils::getInstance()->getStringFromFile(s_spConfJson);doc.Parse<rapidjson::kParseDefaultFlags>(str.c_str());for(unsigned int i = 0; i < 6; i++){rapidjson::Value &val =doc[(rapidjson::SizeType)(i)];if (val.HasMember("id")&&val.HasMember("name")&&val.HasMember("type")&&val.HasMember("life")&&val.HasMember("attack")&&val.HasMember("attackSpeed")&&val.HasMember("power")&& val.HasMember("magicGroup")){spConf[i].id = (SpriteId)val["id"].GetInt();spConf[i].name = val["name"].GetString();spConf[i].type = (SpriteType)val["type"].GetInt();spConf[i].life = val["life"].GetInt();spConf[i].attack = val["attack"].GetInt(); spConf[i].attackSpeed = val["attackSpeed"].GetDouble(); spConf[i].power = val["power"].GetInt();}rapidjson::Value& num = val["stateNum"][(rapidjson::SizeType)0];spConf[i].stateNum.idleNum = num["idleNum"].GetInt();spConf[i].stateNum.runNum = num["runNum"].GetInt();spConf[i].stateNum.attackNum = num["attackNum"].GetInt();spConf[i].stateNum.hurtNum = num["hurtNum"].GetInt();spConf[i].stateNum.deadNum = num["deadNum"].GetInt();rapidjson::Value& group = val["magicGroup"][(rapidjson::SizeType)0];spConf[i].magicGroup.magic1 = (MagicId)group["magic1"].GetInt();spConf[i].magicGroup.magic2 = (MagicId)group["magic2"].GetInt();}//return spConf;}_stMagicConfig SpriteSystem::readMagConfig(MagicId id){_stMagicConfig tmp;tmp = magConf[id-1];return tmp;}void SpriteSystem::readMagicJson(_stMagicConfig (&magConf)[11]){rapidjson::Document doc;std::string str = FileUtils::getInstance()->getStringFromFile(s_spMagJson);doc.Parse<rapidjson::kParseDefaultFlags>(str.c_str());for (unsigned int i = 0; i < 11; i++){rapidjson::Value &val =doc[(rapidjson::SizeType)(i)];if (val.HasMember("id")&&val.HasMember("name")&&val.HasMember("count")){magConf[i].id = (MagicId)val["id"].GetInt();magConf[i].name = val["name"].GetString();magConf[i].count = val["count"].GetInt();}}}void SpriteSystem::update(float dt){float fOldTime = m_fElapseTime;m_fElapseTime  = m_fElapseTime + (dt*1000);if(fOldTime < 5000.0f && m_fElapseTime >= 5000.0f && GameInstance::getInstance()->isGameOver == false){m_bIsAttack = true;}if (GameInstance::getInstance()->isGameOver == false && m_bIsAttack == true){this->spAttackSchedule(dt);}for (unsigned int i = 0; i < 6; i++){if (m_bsSp[i]->getCurLife() < 0 && m_bsSp[i]->getSpriteState() != SPRITESTATE_DEAD){m_bsSp[i]->startAction(SPRITESTATE_DEAD);if (i < 3){m_itItem[i]->setEnabled(false);m_ptLife[i]->setPercentage(0);m_ptPower[i]->setPercentage(0);//CCLOG("Set false%d", m_ptPower[i]->getPercentage());}if (m_bsSp[i]->getSpConf().id == SPRITEID_1 ||m_bsSp[i]->getSpConf().id == SPRITEID_2 || m_bsSp[i]->getSpConf().id == SPRITEID_3){GameInstance::getInstance()->heroNum--;}else{GameInstance::getInstance()->enemyNum--;}}}if (GameInstance::getInstance()->isGameOver == false){if (GameInstance::getInstance()->heroNum == 0){GameInstance::getInstance()->isWin = false;GameInstance::getInstance()->isGameOver = true;m_fOverTime = m_fElapseTime;}if (GameInstance::getInstance()->enemyNum == 0){GameInstance::getInstance()->isWin = true;GameInstance::getInstance()->isGameOver = true;m_fOverTime = m_fElapseTime;} }if (GameInstance::getInstance()->isGameOver == true){if (m_fElapseTime >= m_fOverTime + 2000.0f){unscheduleUpdate();Director::getInstance()->replaceScene(TransitionCrossFade::create(1.5f, OverScene::createScene()));}}for (unsigned int i = 0; i < 6; i++){if (m_bsSp[i]->getSpriteState() != SPRITESTATE_DEAD){if (i < 3){this->ergodicSprite(m_bsSp[i], m_bsSp[3], m_bsSp[4], m_bsSp[5]);}else{this->ergodicSprite(m_bsSp[i], m_bsSp[0], m_bsSp[1], m_bsSp[2]);}}else if(i < 3){m_itItem[i]->setEnabled(false);}}for (unsigned int i = 0; i < 3; i++){if (m_bsSp[i]->getSpriteState() != SPRITESTATE_DEAD){this->showBottomLife(m_bsSp[i]);this->showPower(m_bsSp[i]);}}}void SpriteSystem::initSprite(){struct _stSpConf{float spScale[6];Point spPosStart[6];Point spPosEnd[6];bool  spFlip[6];_stSpConf(){spScale[0] = 0.8f; spScale[1] = 1.0f; spScale[2] = 1.0f; spScale[3] = 1.0f; spScale[4] = 1.0f; spScale[5] = 1.0f;spFlip[0] = true; spFlip[1] = true; spFlip[2] = true;spFlip[3] = false; spFlip[4] = false; spFlip[5] = false;spPosStart[0] = PointS_1; spPosStart[1] = PointS_2; spPosStart[2] = PointS_3;spPosStart[3] = PointS_4; spPosStart[4] = PointS_5; spPosStart[5] = PointS_6;spPosEnd[0] = PointE_1; spPosEnd[1] = PointE_2; spPosEnd[2] = PointE_3;spPosEnd[3] = PointE_4; spPosEnd[4] = PointE_5; spPosEnd[5] = PointE_6;}}_stSpConf;for (unsigned int i = 0; i < 6; i++){if (GameInstance::getInstance()->spSel[i]){m_bsSp[i] = BaseSprite::create(spConf[i]);m_bsSp[i]->setScale(_stSpConf.spScale[i]);m_bsSp[i]->setPosition(_stSpConf.spPosStart[i]);m_bsSp[i]->setFlippedX(_stSpConf.spFlip[i]);m_bsSp[i]->startAction(SPRITESTATE_IDLE);m_bsSp[i]->move(MoveType_To, _stSpConf.spPosEnd[i], 4.5f);//m_fScheduleTime[i] = m_bsSp[i]->getSpConf().attackSpeed;i < 3 ? m_bsSp[i]->setTargetSp(m_bsSp[3]) : m_bsSp[i]->setTargetSp(m_bsSp[0]);this->addChild(m_bsSp[i], 1);}}scheduleUpdate();}void SpriteSystem::spAttackSchedule(float dt){for (unsigned int i = 0; i <6; i++){//释放魔法状态,将定时器时间设为0if (m_bsSp[i]->getIsMagic() == true){m_fScheduleTime[i] = 0;}//普通攻击状态if (m_bsSp[i]->getSpriteState() != SPRITESTATE_DEAD && m_bsSp[i]->getIsMagic() == false){m_fOldScheTime[i]  = m_fScheduleTime[i];m_fScheduleTime[i] += dt;if (m_fOldScheTime[i] < m_bsSp[i]->getSpConf().attackSpeed && m_fScheduleTime[i] >= m_bsSp[i]->getSpConf().attackSpeed){m_bsSp[i]->startAction(SPRITESTATE_ATTACK);//远距离攻击if (m_bsSp[i]->getSpConf().type != SPRITETYPE_FRONT){this->magicMove(m_bsSp[i]->getSpConf().magicGroup.magic1, m_bsSp[i], m_bsSp[i]->getTargetSp());}//直接攻击else{m_bsSp[i]->getTargetSp()->setCurLife(m_bsSp[i]->getTargetSp()->getCurLife() - m_bsSp[i]->getSpConf().attack);this->showLife(m_bsSp[i]->getTargetSp());if (m_bsSp[i]->getCurPower() <= m_bsSp[i]->getSpConf().power){m_bsSp[i]->setCurPower(m_bsSp[i]->getCurPower() + m_bsSp[i]->getSpConf().attack*2);}}m_fOldScheTime[i] = 0;m_fScheduleTime[i] = 0;}}}}void SpriteSystem::magicMove(MagicId id, BaseSprite* attackSprite, BaseSprite* hurtSprite){auto attackPos = attackSprite->getPosition();auto hurtPos = hurtSprite->getPosition();auto size = Director::getInstance()->getVisibleSize().width;std::string name = GameInstance::getInstance()->getMagicName(id);name += "_001.png";    Sprite* magic = CCSprite::createWithSpriteFrameName(name.c_str());magic->setScale(0.6f);if (id == MAGIC_9){magic->setRotation(-90);}magic->setPosition(attackPos);this->addChild(magic, 2);float s = ccpDistance(attackPos, hurtPos);float v = 300.0f;auto move = MoveTo::create(s/v, hurtPos);auto callfunc = CallFunc::create(std::bind(&SpriteSystem::magicMoveCallback, this, magic,attackSprite));magic->runAction(Sequence::create(move, callfunc, NULL));}void SpriteSystem::magicMoveCallback(Node* node, BaseSprite* sp){node->setVisible(false);this->removeChild(node);if (sp){sp->getTargetSp()->setCurLife(sp->getTargetSp()->getCurLife() - sp->getSpConf().attack);this->showLife(sp->getTargetSp());if (sp->getCurPower() <= sp->getSpConf().power){sp->setCurPower(sp->getCurPower() + sp->getSpConf().attack*2);}}}void SpriteSystem::showLife(BaseSprite* sp){auto startPos = Point(sp->getPositionX(), sp->getPositionY() + sp->getContentSize().height/2 + 5);auto endPos = Point(sp->getPositionX(),sp->getPositionY() + sp->getContentSize().height/2 + 20);//资源尺寸不一导致if (sp->getSpConf().id == 1){startPos = Point(sp->getPositionX() - 30, sp->getPositionY() + sp->getContentSize().height/2 + 5 - 90);endPos = Point(sp->getPositionX() - 30,sp->getPositionY() +sp->getContentSize().height/2 + 20 - 90);}if (sp->getSpConf().id == 6){startPos = Point(sp->getPositionX(), sp->getPositionY() + sp->getContentSize().height/2 - 30);endPos = Point(sp->getPositionX(),sp->getPositionY() +sp->getContentSize().height/2  -15);}float per = float(sp->getCurLife() )/ (float)sp->getSpConf().life;Node* node = Node::create();Sprite* lifebg = Sprite::create(s_ptBg);ProgressTimer* life = ProgressTimer::create(Sprite::create(sp->getSpConf().id<3 ? s_ptLife : s_ptLife2));life->setType(ProgressTimer::Type::BAR);life->setMidpoint(Point::ANCHOR_MIDDLE_LEFT);life->setBarChangeRate(Point::ANCHOR_MIDDLE_RIGHT);life->setPercentage(per*100);node->setPosition(startPos);node->addChild(lifebg, 0);node->addChild(life, 1);this->addChild(node, 2);auto moveUp = MoveTo::create(0.5f, endPos);auto fadeout = FadeOut::create(0.5f);auto spawn = Spawn::create(moveUp, fadeout, NULL);auto callfunc = CallFunc::create(std::bind(&SpriteSystem::showLifeCallback, this, node));node->runAction(Sequence::create(spawn, callfunc, NULL));}void SpriteSystem::showLifeCallback(Node* node){node->setVisible(false);this->removeChild(node);}void SpriteSystem::ergodicSprite(BaseSprite* attack,BaseSprite* tmp1, BaseSprite* tmp2, BaseSprite* tmp3){BaseSprite* tmps[3] = {tmp1, tmp2, tmp3};for (unsigned int i = 0; i < 3; i++){if (attack && tmps[i] && tmps[i]->getSpriteState() != SPRITESTATE_DEAD){attack->setTargetSp(tmps[i]);break;}}}void SpriteSystem::setMagicPanel(){Sprite* panel = Sprite::create(s_magPan);panel->setPosition(400, 90);struct _stMagPan{float scale[3];Point spPos[3];bool  enable[3];Point ptPos[6];_stMagPan(){scale[0] = 0.8f; scale[1] = 0.8f; scale[2] = 0.8f;spPos[0] = Point(534, 90); spPos[1] = Point(318, 90); spPos[2] = Point(106, 90);enable[0] = false; enable[1] = false; enable[2] = false;ptPos[0] = Point(106, 30); ptPos[1] = Point(318, 30); ptPos[2] = Point(534, 30); ptPos[3] = Point(106, 10); ptPos[4] = Point(318, 10); ptPos[5] = Point(534, 10);}}_stMagPan;Sprite* spNor[3];Sprite* spDis[3];Sprite* life[3];Sprite* power[3];Sprite* ptBg[6];for (unsigned int i = 0; i < 3; i++){//英雄头像设为按钮spNor[i] = Sprite::create(s_panSpNor[i]);spDis[i] = Sprite::create(s_panSpDis[i]);m_itItem[i] = MenuItemSprite::create(spNor[i], spNor[i], spDis[i], std::bind(&SpriteSystem::itemCallback, this, std::placeholders::_1,i));m_itItem[i]->setScale(_stMagPan.scale[i]);m_itItem[i]->setPosition(_stMagPan.spPos[i]);m_itItem[i]->setEnabled(_stMagPan.enable[i]);//生命值,魔法值m_ptLife[i] =  ProgressTimer::create(Sprite::create(s_ptLife));m_ptLife[i]->setAnchorPoint(Point::ANCHOR_BOTTOM_LEFT);m_ptLife[i]->setType(ProgressTimer::Type::BAR);m_ptLife[i]->setMidpoint(Point::ANCHOR_MIDDLE_LEFT);m_ptLife[i]->setBarChangeRate(Point::ANCHOR_MIDDLE_RIGHT);m_ptLife[i]->setPercentage(100);m_ptPower[i] = ProgressTimer::create(Sprite::create(s_ptPower));m_ptPower[i]->setAnchorPoint(Point::ANCHOR_BOTTOM_LEFT);m_ptPower[i]->setType(ProgressTimer::Type::BAR);m_ptPower[i]->setMidpoint(Point::ANCHOR_MIDDLE_LEFT);m_ptPower[i]->setBarChangeRate(Point::ANCHOR_MIDDLE_RIGHT);m_ptPower[i]->setPercentage(0);}auto menu = Menu::create(m_itItem[0], m_itItem[1], m_itItem[2], NULL);menu->setPosition(Point::ZERO);panel->addChild(menu,1);for (unsigned int i = 0; i < 6; i++){ptBg[i] = Sprite::create(s_ptBg);ptBg[i]->setPosition(_stMagPan.ptPos[i]);panel->addChild(ptBg[i]);}ptBg[0]->addChild(m_ptLife[0],2);ptBg[1]->addChild(m_ptLife[1],2);ptBg[2]->addChild(m_ptLife[2],2);ptBg[3]->addChild(m_ptPower[0],2);ptBg[4]->addChild(m_ptPower[1],2);ptBg[5]->addChild(m_ptPower[2],2);this->addChild(panel, 1);}void SpriteSystem::itemCallback(Ref* pSender,int Num){m_itItem[Num]->setEnabled(false);CCLOG("Num%d", Num);m_bsSp[Num]->setCurPower(0);m_bsSp[Num]->startAction(SPRITESTATE_MAGIC);this->startMagic(m_bsSp[Num], m_bsSp[Num]->getSpConf().magicGroup.magic2, 3);}void SpriteSystem::showBottomLife(BaseSprite* sprite){float per = (((float)sprite->getCurLife()/sprite->getSpConf().life)*100);SpriteId id = sprite->getSpConf().id;switch (id){case SPRITEID_1:m_ptLife[2]->setPercentage(per);break;case SPRITEID_2:m_ptLife[1]->setPercentage(per);break;case SPRITEID_3:m_ptLife[0]->setPercentage(per);break;case SPRITEID_4:break;case SPRITEID_5:break;case SPRITEID_6:break;default:break;}}void SpriteSystem::showPower(BaseSprite* sprite){float per = (((float)sprite->getCurPower()/sprite->getSpConf().power)*100);SpriteId id = sprite->getSpConf().id;switch (id){case SPRITEID_1:m_ptPower[2]->setPercentage(per);if (per >= 100){m_itItem[0]->setEnabled(true);}break;case SPRITEID_2:m_ptPower[1]->setPercentage(per);if (per >= 100){m_itItem[1]->setEnabled(true);}break;case SPRITEID_3:m_ptPower[0]->setPercentage(per);if (per >= 100){m_itItem[2]->setEnabled(true);}break;case SPRITEID_4:break;case SPRITEID_5:break;case SPRITEID_6:break;default:break;}}void SpriteSystem::startMagic(BaseSprite* tmp,MagicId id, int times){Magic* magic = Magic::create(magConf[tmp->getSpConf().magicGroup.magic2-1]);magic->setPosition(600, 300);this->addChild(magic, 1);magic->startMagic(12, times);this->magicHurt(tmp, m_bsSp[3], m_bsSp[4], m_bsSp[5]);}void SpriteSystem::magicHurt(BaseSprite* attack,BaseSprite* tmp1, BaseSprite* tmp2, BaseSprite* tmp3){BaseSprite* tmps[3] = {tmp1, tmp2, tmp3};for (unsigned int i = 0; i < 3; i++){if (tmps[i] && tmps[i]->getSpriteState() != SPRITESTATE_DEAD){tmps[i]->setCurLife(tmps[i]->getCurLife() - attack->getSpConf().attack);CCLOG("showlifes");this->showLife(tmps[i]);}}}
一下代码量就变多了,差不多700行,我们慢慢来看,init开始看里面开始都是些初始化数据

比如GameInstance::getInstance()->heroNum = 3;
GameInstance::getInstance()->enemyNum = 3;关于GameInstance这个静态类我们后面再来看

this->readJson(spConf);
this->readMagicJson(magConf);
this->initSprite();
this->setMagicPanel();看看这4个方法调用,先看看readJson方法,这个方法用来读取sp精灵的数据(这个数据在网游中应该由服务器传过来),这里是本地的我们看看

如图,我们看到这个数据由外部json文件得到,网络游戏流程大致也差不多,我们后面以暗黑世界对象看看网络通信怎么来解析这个地方,这里就先不讲,具体的解析过程readJson里面很详细

接着readMagicJson方法跟上面的读取方式几乎相同,,我们看下json文件里面写了什么东西,先是spriteConfig.json

[    {        "id": 1,        "name": "yemanshouren",        "type": 1,        "life": 100,        "attack": 15,        "attackSpeed": 2.5,        "power": 100,        "magicGroup": [            {                "magic1": 0,                "magic2": 8            }        ],        "stateNum": [            {                "idleNum": 1,                "runNum": 6,                "attackNum": 12,                "hurtNum": 1,                "deadNum": 6            }        ]    },    {        "id": 2,        "name": "quanjiniao",        "type": 2,        "life": 100,        "attack": 18,        "attackSpeed": 5.2,        "power": 100,        "magicGroup": [            {                "magic1": 9,                "magic2": 3            }        ],        "stateNum": [            {                "idleNum": 1,                "runNum": 6,                "attackNum": 10,                "hurtNum": 1,                "deadNum": 3            }        ]    },    {        "id": 3,        "name": "duyanguai",        "type": 3,        "life": 100,        "attack": 10,        "attackSpeed": 6.3,        "power": 100,        "magicGroup": [            {                "magic1": 9,                "magic2": 10            }        ],        "stateNum": [            {                "idleNum": 1,                "runNum": 8,                "attackNum": 12,                "hurtNum": 1,                "deadNum": 10            }        ]    },    {        "id": 4,        "name": "manzutu",        "type": 1,        "life": 100,        "attack": 13,        "attackSpeed": 3.1,        "power": 100,        "magicGroup": [            {                "magic1": 9,                "magic2": 3            }        ],        "stateNum": [            {                "idleNum": 1,                "runNum": 6,                "attackNum": 6,                "hurtNum": 1,                "deadNum": 6            }        ]    },    {        "id": 5,        "name": "xiaoemo",        "type": 2,        "life": 100,        "attack": 15,        "attackSpeed": 6.3,        "power": 100,        "magicGroup": [            {                "magic1": 5,                "magic2": 1            }        ],        "stateNum": [            {                "idleNum": 1,                "runNum": 6,                "attackNum": 10,                "hurtNum": 1,                "deadNum": 12            }        ]    },    {        "id": 6,        "name": "jielingqishi",        "type": 3,        "life": 100,        "attack": 18,        "attackSpeed": 7.5,        "power": 100,        "magicGroup": [            {                "magic1": 5,                "magic2": 4            }        ],        "stateNum": [            {                "idleNum": 1,                "runNum": 2,                "attackNum": 6,                "hurtNum": 1,                "deadNum": 9            }        ]    }]
之后是magicConfig.json
[    {        "id": 1,        "name": "fireblast",        "count": 10    },    {        "id": 2,        "name": "burn",        "count": 6    },    {        "id": 3,        "name": "icepiton",        "count": 8    },    {        "id": 4,        "name": "shocked",        "count": 4    },    {        "id": 5,        "name": "firedevil",        "count": 6    },    {        "id": 6,        "name": "lightstrike",        "count": 6    },    {        "id": 7,        "name": "bossfire",        "count": 4    },    {        "id": 8,        "name": "firedevilblast",        "count": 6    },    {        "id": 9,        "name": "fireball",        "count": 4    },    {        "id": 10,        "name": "bossstore",        "count": 5    },    {        "id": 11,        "name": "magicring",        "count": 1    }]

下面的加载sprite精灵方法,initSprite我们加个断点直观的看一下,一看吓一跳,原来这个create方法里面包含了这么多东西,的确,不过这个东西就是我们上面json里面的id1的数据,之后的方法我们截个图来看

把方法折叠起来一下变得直观了,我们一个方法一个方法的看,spAttackSchedule方法是一个攻击的计时器,通过这个计时,来控制自动攻击的频率,里面有个特别的地方就是,当魔法攻击的时候,计时会归0,然后等待魔法之后重新开始计时,magicMove和magicMoveCallback两个方法用来控制攻击子弹的移动及消亡,因为刀塔传奇这一类的游戏,不像格斗类游戏攻击的碰撞盒子安在身上,而是靠子弹(魔法)来攻击,所以这个方法控制能不能打倒怪物就很重要了,showLife这个就不用说了,伤血之后,头上会有个血条,那年那兔那些事这样的游戏会有个选项,是否显示,showLifeCallback就是隐藏方法,setMagicPanel是设置魔法的面板方法,画出面板肯定要有按键响应itemCallback就是用来响应我们的输入的。showBottomLife和showPower是用来画出和控制魔法控制条的(只有集满气才可以释放),释放出startMagic技能,当然要判断伤害了magicHurt,这样这个类就看完了,虽然看上去代码很多,却并不复杂,当然仅有这个类游戏还不能正常运转,这个类里面用到了几个静态类,我们来看看都有哪些,作用又是干什么的。

#include "GameInstance.h"#include "Resource.h"static GameInstance *s_SharedGameInstance = nullptr;GameInstance::GameInstance(){for (unsigned int i = 0; i <6; i++){spSel[i] = false;}isGameOver = false;isWin = false;}GameInstance::~GameInstance(){}GameInstance* GameInstance::getInstance(){if (!s_SharedGameInstance){s_SharedGameInstance = new GameInstance();//s_SharedGameInstance->init();}return s_SharedGameInstance;}Animate* GameInstance::setAnimate(const char* frameName, int frameCount, int fps, bool restore,int times){using namespace cocos2d;Vector<SpriteFrame*> frames;for (int i = 1; i <= frameCount; i++){const char* imgName = String::createWithFormat(frameName, i)->getCString();SpriteFrame* frame = SpriteFrameCache::getInstance()->getSpriteFrameByName(imgName);frames.pushBack(frame);}auto animation = Animation::createWithSpriteFrames(frames, 1.0f/fps, times);animation->setRestoreOriginalFrame(restore);auto animate = Animate::create(animation);return animate;}//MagicConfig GameInstance::readDataBase(MagicId id)//{//MagicConfig magicConf;//sqlite3* pdb;//std::string path = "database/magic.db3";//int result;//char** re;//int row;//int col;////result = sqlite3_open(path.c_str(), &pdb);////sqlite3_get_table(pdb, "select * from magicConfig", &re, &row,&col, NULL);////magicConf.id = (MagicId)atoi(re[2*col+0]);//magicConf.name = re[id*col+1];//magicConf.count = atoi(re[id*col+2]);////sqlite3_close(pdb);////return magicConf;//}std::string GameInstance::getMagicName(MagicId id){_stMagicConfig mgConf;rapidjson::Document doc;std::string str = FileUtils::getInstance()->getStringFromFile(s_spMagJson);doc.Parse<rapidjson::kParseDefaultFlags>(str.c_str());rapidjson::Value &val =doc[(rapidjson::SizeType)(id-1)];std::string name = val["name"].GetString();return name;}int GameInstance::getMagicNum(MagicId id){_stMagicConfig mgConf;rapidjson::Document doc;std::string str = FileUtils::getInstance()->getStringFromFile(s_spMagJson);doc.Parse<rapidjson::kParseDefaultFlags>(str.c_str());rapidjson::Value &val =doc[(rapidjson::SizeType)(id-1)];int count = val["count"].GetInt();return count;}std::string GameInstance::getSpriteName(SpriteId id){std::string name;_stSpriteConfig spConf;rapidjson::Document doc;std::string str = FileUtils::getInstance()->getStringFromFile(s_spConfJson);doc.Parse<rapidjson::kParseDefaultFlags>(str.c_str());rapidjson::Value &val =doc[(rapidjson::SizeType)(id-1)];if (val.HasMember("name")){name = val["name"].GetString();}return name;}std::string GameInstance::getStateName(SpriteId id,SpriteState state){std::string str = GameInstance::getInstance()->getSpriteName(id);switch (state){case SPRITESTATE_IDLE:str += "_run_%03d.png";break;case SPRITESTATE_RUN:str += "_run_%03d.png";break;case SPRITESTATE_ATTACK:str += "_attack_%03d.png";break;case SPRITESTATE_HURT:str += "_run_%03d.png";break;case SPRITESTATE_DEAD:str += "_dead_%03d.png";break;default:break;}return str;}
这个类很直观,是一个精灵动画控制类,可以看到这样的语句,当xx的时候使用哪张图片,cocos2dx提供的精灵动画画法各有千秋,用哪种自己习惯,
#include "Magic.h"#include "sqlite3.h"#include "GameInstance.h"Magic::Magic():magicAction(nullptr){}Magic::~Magic(){//CC_SAFE_RELEASE_NULL(magicAction);}Magic* Magic::create(_stMagicConfig magConfig){Magic*  sprite = new Magic();sprite->magicConf = magConfig;std::string spriteName = GameInstance::getInstance()->getMagicName(sprite->magicConf.id);spriteName += "_001.png";auto spriteFrame = SpriteFrameCache::getInstance()->spriteFrameByName(spriteName);if (spriteFrame && sprite->initWithSpriteFrame(spriteFrame)){sprite->autorelease();return sprite;}else{delete sprite;sprite = NULL;return NULL;}return NULL;}void Magic::setMagic(){std::string str;str = this->magicConf.name;str += "_%03d.png";this->magicAction = GameInstance::getInstance()->setAnimate(str.c_str(), this->magicConf.count, this->magicFps, false, this->magicTimes);}void Magic::startMagic(int fps, int times){this->magicFps = fps;this->magicTimes = times;this->setMagic();Animate* tmp = (Animate*)this->magicAction;auto callfunc = CallFunc::create(CC_CALLBACK_0(Magic::startMagicCallback, this));auto sequence = Sequence::create(tmp, callfunc, NULL);this->runAction(sequence);}void Magic::startMagicCallback(){this->setVisible(false);}void Magic::move(Point endPoint, int duration, float speed){float s= ccpDistance(this->getPosition(), endPoint);auto move = MoveTo::create(s/speed,endPoint);this->runAction(move);}

上面画出了人物,下面理所应当的应该把魔法画出来了,魔法精灵控制类就这样出现了,
#include "OverScene.h"#include "GameInstance.h"#include "GameScene.h"OverScene::OverScene(){}Scene* OverScene::createScene(){auto scene = Scene::create();auto layer = OverScene::create();scene->addChild(layer);GameInstance::getInstance()->isGameOver = false;return scene;}bool OverScene::init(){if (!Layer::init()){return false;}auto ui = cocostudio::GUIReader::getInstance()->widgetFromJsonFile("gameover/NewUI4_1.ExportJson");this->addChild(ui);Button* back = (Button*)Helper::seekWidgetByName(ui,"back");Button* fight = (Button*)Helper::seekWidgetByName(ui,"fight");if (GameInstance::getInstance()->isWin){Sprite* title =Sprite::create("map/stagedetail_raid_title.png");title->setPosition(400, 240);this->addChild(title);}else{Sprite* title = Sprite::create("map/failed_title.png");title->setPosition(400, 240);this->addChild(title);}back->addTouchEventListener(this, toucheventselector(OverScene::tbackCallback));fight->addTouchEventListener(this, toucheventselector(OverScene::tfightCallback));/*Sprite* bg = Sprite::create("map/gameover.png");bg->setPosition(400, 240);this->addChild(bg);if (GameInstance::getInstance()->isWin){Sprite* title =Sprite::create("map/stagedetail_raid_title.png");title->setPosition(400, 240);this->addChild(title);}else{Sprite* title = Sprite::create("map/failed_title.png");title->setPosition(400, 240);this->addChild(title);}auto back = MenuItemSprite::create(Sprite::create("button/replaybtn.png"), Sprite::create("button/replaybtn-disabled.png"),CC_CALLBACK_1(OverScene::backCallback,this));back->setPosition(back->getContentSize().width/2, back->getContentSize().height/2);auto fight = MenuItemSprite::create(Sprite::create("button/prepare_go_battle.png"), Sprite::create("button/prepare_go_battle_press.png"),CC_CALLBACK_1(OverScene::fightCallback,this));fight->setPosition(800-back->getContentSize().width/2, back->getContentSize().height/2);auto menu =Menu::create(back, fight, NULL);menu->setPosition(Point::ZERO);this->addChild(menu);*/return true;}void OverScene::backCallback(Ref* sender){Director::getInstance()->end();}void OverScene::fightCallback(Ref* sender){Director::getInstance()->replaceScene(GameScene::createScene());}void OverScene::tbackCallback(Ref* sender, TouchEventType type){switch (type){case TOUCH_EVENT_ENDED:Director::getInstance()->end();break;}}void OverScene::tfightCallback(Ref* sender, TouchEventType type){switch (type){case TOUCH_EVENT_ENDED:Director::getInstance()->replaceScene(GameScene::createScene());break;}}
当战斗全都结束了,自然要看战斗结束了。好了,到这代码差不多快完事了,其中有两个.H文件

#ifndef _CONFIG_#define _CONFIG_USING_NS_CC;//精灵初始位置const Point PointS_1 = Point(355, 317) - Point(380, 0);const Point PointS_2 = Point(190, 290)- Point(380, 0);const Point PointS_3 = Point(80, 300)- Point(380, 0);const Point PointS_4 = Point(445, 300)+ Point(380, 0);const Point PointS_5 = Point(550, 313)+ Point(380, 0);const Point PointS_6 = Point(720, 355)+ Point(380, 0);const Point PointE_1 = Point(355, 317);const Point PointE_2 = Point(190, 290);const Point PointE_3 = Point(80, 300);const Point PointE_4 = Point(445, 300);const Point PointE_5 = Point(550, 313);const Point PointE_6 = Point(720, 355);//精灵的唯一标示enum SpriteId{SPRITEID_1 = 1,SPRITEID_2,SPRITEID_3,SPRITEID_4,SPRITEID_5,SPRITEID_6};//每个技能的唯一标示enum MagicId{MAGIC_1 = 1,MAGIC_2,MAGIC_3,MAGIC_4,MAGIC_5,MAGIC_6,MAGIC_7,MAGIC_8,MAGIC_9,MAGIC_10,MAGIC_11,};//精灵状态enum SpriteState{SPRITESTATE_IDLE,SPRITESTATE_RUN,SPRITESTATE_ATTACK,SPRITESTATE_MAGIC,SPRITESTATE_HURT,SPRITESTATE_DEAD};//精灵类型 前排、中排、后排enum  SpriteType{SPRITETYPE_FRONT = 1,    //前排SPRITETYPE_MIDDLE,   //中排SPRITETYPE_BACK      //后排};enum MoveType{MoveType_To,MoveType_By};//精灵每个状态的动画数量struct StateNum{int idleNum;    //空闲int runNum;     //跑动int attackNum;  //攻击int hurtNum;    //受伤int deadNum;    //死亡};//精灵属性struct MagicGroup{MagicId magic1;MagicId magic2;};struct _stSpriteConfig{SpriteId id;             //精灵ID,唯一识别std::string name;        //名字SpriteType type;         //类型,前排、中排、后排int life;                //生命值int attack;              //攻击力double attackSpeed;      //攻击速度int power;               //魔法值MagicGroup magicGroup;   //魔法类别,远距离、直接攻击StateNum stateNum;       //各动画帧数};//技能属性struct _stMagicConfig{MagicId id;std::string name;int count;};struct AttackId{bool s1;bool s2;bool s3;};#endif
#ifndef _RESOURCE_H_#define _RESOURCE_H_static const char s_magPan[]              =  "panel/panel.png";static const char s_panSpNor[3][64]     =  {"panel/sprite1.png","panel/sprite2.png","panel/sprite3.png"};static const char s_panSpDis[3][64]     =  {"panel/sprite1no.png","panel/sprite2no.png","panel/sprite3no.png"};static const char s_spConfJson[]          =  "json/spriteConfig.json";static const char s_ptBg[]           =  "button/lifebg2.png";static const char s_ptLife[]         =  "button/herolife.png";static const char s_ptLife2[]         =  "button/life.png";static const char s_ptPower[]         =  "button/power.png";static const char s_spMagJson[]          =  "json/magicConfig.json";#endif
到这里全部代码差不多贴完了,不知道为什么原作者代码里有没有用到的东东,我怀疑是这段代码可能是从某个项目中拆出来的,因为我认为跟战斗没啥关系,所以就不贴出来,因为没办法直接开放源码,看过这篇的小伙伴需要代码的话,可以自行搜索,或者私信给我,好了,这篇就到这,我们后面再见。







0 0
原创粉丝点击