这一篇要为英雄创造一些小伙伴了,并且需要让机器人会巡逻,会偷懒,会行走,还会攻击英雄,当然也能受伤。其实机器人和英雄有一些共同的属性:攻击力、生命值和行走速度。但机器人是由电脑控制,状态是随机切换的,所以还需要指定巡逻区域、攻击区域、行走方向、决策时间等。

1. 添加机器人

首先更新BaseSprite类,添加攻击力和生命值属性,在BaseSprite.h中添加:

 CC_SYNTHESIZE(unsigned int, m_hp, HP);
 CC_SYNTHESIZE(unsigned int, m_attack, Attack);

创建Enemy类,代表敌方机器人,这里需要实现简单的AI,让机器人能自动思考,根据具体环境切换自己的状态:

Enemy.h
typedef enum {
 AI_IDLE = 0,
 AI_PATROL,
 AI_ATTACK,
 AI_PURSUIT
}AiState;
class Enemy : public BaseSprite
{
public:
 Enemy();
 ~Enemy();
 bool init();
 CREATE_FUNC(Enemy);
 CC_SYNTHESIZE(cocos2d::Point, m_moveDirection, MoveDirection);
 CC_SYNTHESIZE(float, m_eyeArea, EyeArea);
 CC_SYNTHESIZE(float, m_attackArea, AttackArea)
 CC_SYNTHESIZE(AiState, m_aiState, AiState);
 void execute(const cocos2d::Point& target, float targetBodyWidth);
private:
 void decide(const cocos2d::Point& target, float targetBodyWidth);
 unsigned int m_nextDecisionTime;
};

AiState表示机器人的四种状态:休闲、巡逻、攻击、跟随。机器人还有几个变量,分别表示:行走方向、巡逻范围、攻击范围、当前AI状态。
m_nextDecisionTime表示机器人距离下一次决策的时间,execute函数是在GameLayer.cpp中update函数调用的,定期执行更新机器人状态。decide函数实现机器人怎么决策,是机器人的内心世界。
这里重点分析机器人AI该怎么实现,因为只是一个demo,所以就尽可能的简单些吧。首先机器人需要根据自己的朝向和英雄的位置来思考,如果机器人背对着英雄或者英雄处于机器人巡逻范围之外,那么此时机器人是看不到英雄的,机器人就会随机的选择继续巡逻或者站着偷懒。如果英雄在机器人的巡逻范围内,且被机器人看到了,则机器人需要判断英雄是否处于自己的攻击范围,来决策是攻击还是追过去。每种状态下的思考时间最好设置成随机的,这样更真实。看源码实现:

Enemy.cpp
Enemy::Enemy():
 m_nextDecisionTime(0)
{}

Enemy::~Enemy()
{}

bool Enemy::init()
{
 bool ret = false;
 do {
  this->initWithSpriteFrameName("robot_idle_00.png");
  Animation *pIdleAnim = this->createAnimation("robot_idle_%02d.png", 5, 12);
  this->setIdleAction(RepeatForever::create(Animate::create(pIdleAnim)));
  Animation *pWalkAnim = this->createAnimation("robot_walk_%02d.png", 6, 12);
  this->setWalkAction(RepeatForever::create(Animate::create(pWalkAnim)));

  Animation *pAttackAnim = this->createAnimation("robot_attack_%02d.png", 5, 20);
  this->setAttackAction(Sequence::create(Animate::create(pAttackAnim), BaseSprite::createIdleCallbackFunc(), NULL));
  Animation *pHurtAnim = this->createAnimation("robot_hurt_%02d.png", 3, 20);
  this->setHurtAction(Sequence::create(Animate::create(pHurtAnim), BaseSprite::createIdleCallbackFunc(), NULL));
  Animation *pDeadAnim = this->createAnimation("robot_knockout_%02d.png", 5, 12);
  this->setDeadAction(Sequence::create(Animate::create(pDeadAnim), Blink::create(3, 9), NULL));
  ret = true;
 } while(0);
 return ret;
}

void Enemy::execute(const Point& target, float targetBodyWidth)
{
 if(m_nextDecisionTime == 0)
 {
  this->decide(target, targetBodyWidth);
 }else {
  -- m_nextDecisionTime;
 }
}

void Enemy::decide(const Point& target, float targetBodyWidth)
{
 Point location = this->getPosition();
 float distance = location.getDistance(target);
 distance = distance - (targetBodyWidth / 2 + this->getDisplayFrame()->getRect().size.width / 2) + 30;
 //log("distance=%f, m_fVelocity=%f", distance, m_fVelocity);
 bool isFlippedX = this->isFlippedX();
 bool isOnTargetLeft = (location.x < target.x ? true : false);
 if((isFlippedX && isOnTargetLeft) || (!isFlippedX && !isOnTargetLeft)) {
  this->m_aiState = CCRANDOM_0_1() > 0.5f ? AI_PATROL : AI_IDLE;
 }else {
  if(distance < m_eyeArea)
  {
   this->m_aiState = distance < m_attackArea ? AI_ATTACK : AI_PURSUIT;
  }else {
   this->m_aiState = CCRANDOM_0_1() > 0.5f ? AI_PATROL : AI_IDLE;
  }
 }
 switch(m_aiState)
 {
 case AI_ATTACK:
  {
   this->runAttackAction();
   this->attack();
   this->m_nextDecisionTime = 50;
  }
  break;
 case AI_IDLE:
  {
   this->runIdleAction();
   this->m_nextDecisionTime = CCRANDOM_0_1() * 100;
  }
  break;
 case AI_PATROL:
  {
   this->runWalkAction();
   this->m_moveDirection.x = CCRANDOM_MINUS1_1();
   this->m_moveDirection.y = CCRANDOM_MINUS1_1();
   m_moveDirection.x = m_moveDirection.x > 0 ? (m_moveDirection.x + m_fVelocity.x) : (m_moveDirection.x - m_fVelocity.x);
   m_moveDirection.y = m_moveDirection.y > 0 ? (m_moveDirection.y + m_fVelocity.y) : (m_moveDirection.y - m_fVelocity.y);
   this->m_nextDecisionTime = CCRANDOM_0_1() * 100;
  }
  break;
 case AI_PURSUIT:
  {
   this->runWalkAction();
   //v.normalize() function return the unit vector of v
   this->m_moveDirection = (target - location).normalize();
   this->setFlippedX(m_moveDirection.x < 0 ? true : false);
   m_moveDirection.x = m_moveDirection.x > 0 ? (m_moveDirection.x + m_fVelocity.x) : (m_moveDirection.x - m_fVelocity.x);
   m_moveDirection.y = m_moveDirection.y > 0 ? (m_moveDirection.y + m_fVelocity.y) : (m_moveDirection.y - m_fVelocity.y);
   this->m_nextDecisionTime = 10;
  }
  break;
 }
}

当机器人思考接下来该做什么时,就会执行相应的操作和动画。
机器人创造完成了,现在把它添加到游戏中去,修改GameLayer.h,添加下面的代码:

#define MIN_ENEMY_COUNT 5

 void updateEnemies(float dt);
 void addEnemy();
 void onEnemyAttack(BaseSprite *pSprite);

 cocos2d::Array *m_pEnemies;

updateEnemies表示每一次循环都会更新每个机器人的状态,onEnemyAttack是机器人攻击英雄时执行的函数,暂时不实现。m_pEnemies为保存机器人的容器。
修改GameLayer.cpp,添加下面的函数:

void GameLayer::addEnemy()
{
 Size winSize = Director::getInstance()->getWinSize();
 Point location = Point::ZERO;
 Enemy *pEnemy = Enemy::create();
 //log("m_pTiledMap->getMapSize() mapSize=%f", m_pTiledMap->getMapSize().width);
 float halfEnemyFrameHeight = (pEnemy->getDisplayFrame()->getRect().size.height) / 2;
 float heroPosX = m_pHero->getPositionX();
 float halfWinWidth = (winSize.width / 2);
 while(fabsf(heroPosX - location.x) < 150)
 {
  if(heroPosX < halfWinWidth)
  {
   location.x = m_pHero->getPositionX() + CCRANDOM_0_1() * halfWinWidth;
  }else if(heroPosX > (m_pTiledMap->getMapSize().width * m_fTileWidth - halfWinWidth)) {
   location.x = m_pHero->getPositionX() - CCRANDOM_0_1() * halfWinWidth;
  }else {
   location.x = m_pHero->getPositionX() + CCRANDOM_MINUS1_1() * halfWinWidth;
  }
 }
 float maxY = m_fTileHeight * 3 + halfEnemyFrameHeight;
 location.y = CCRANDOM_0_1() * maxY;
 if(location.y < halfEnemyFrameHeight)
 {
  location.y = halfEnemyFrameHeight;
 }
 pEnemy->attack = CC_CALLBACK_0(GameLayer::onEnemyAttack, this, pEnemy);
 pEnemy->setPosition(m_origin + location);
 pEnemy->setZOrder(m_fScreenHeight - pEnemy->getPositionY());
 pEnemy->runIdleAction();
 pEnemy->setAttack(5);
 pEnemy->setHP(30);
 pEnemy->setVelocity(Point(0.5f, 0.5f));
 pEnemy->setEyeArea(200);
 pEnemy->setAttackArea(25);
 m_pEnemies->addObject(pEnemy);
 m_pSpriteNodes->addChild(pEnemy);
}

void GameLayer::updateEnemies(float dt) {
    Object *pObj = NULL;
 Point distance = Point::ZERO;

 Point heroLocation = m_pHero->getPosition();
 Array *pRemovedEnemies = Array::create();
    CCARRAY_FOREACH(m_pEnemies, pObj)
 {
  Enemy *pEnemy = (Enemy*)pObj;
  pEnemy->execute(heroLocation, m_pHero->getDisplayFrame()->getRect().size.width);
  if(pEnemy->getCurrActionState() == ACTION_STATE_WALK)
  {
   Point location = pEnemy->getPosition();
   Point direction = pEnemy->getMoveDirection();

   Point expect = location + direction;
   float halfEnemyFrameHeight = (pEnemy->getDisplayFrame()->getRect().size.height) / 2;
   if(expect.y < halfEnemyFrameHeight || expect.y > (m_fTileHeight * 3 + halfEnemyFrameHeight) )
   {
    direction.y = 0;
   }
   pEnemy->setFlippedX(direction.x < 0 ? true : false);
   pEnemy->setPosition(location + direction);
   pEnemy->setZOrder(pEnemy->getPositionY());
  }
 }
 CCARRAY_FOREACH(pRemovedEnemies, pObj)
 {
  Enemy *pEnemy = (Enemy*)pObj;
  m_pEnemies->removeObject(pEnemy);
  m_pSpriteNodes->removeChild(pEnemy, true);
 }
 pRemovedEnemies->removeAllObjects();
}

void GameLayer::onEnemyAttack(BaseSprite *pSprite)
{
}

在GameLayer.cpp的update函数中添加:

 this->updateEnemies(dt);

在init函数中添加:

 m_pEnemies = Array::createWithCapacity(MIN_ENEMY_COUNT);
  m_pEnemies->retain();
  for(int i = 0; i < MIN_ENEMY_COUNT; ++ i)
  {
   this->addEnemy();
  }

OK,现在编译运行项目,就可以看到屏幕上有5个机器人追着英雄打了。

目前机器人和英雄都没有攻击效果,都是无敌状态,不过他们好日子快到头了,下一篇我们就来让他们接受现实的残酷吧。

cocos2d-x手游性能优化总结

近段时间在使用cocos2d-x开发2D手游,技术方案使用的是cocos2d-x+lua,因为游戏使用的是cocos2d-x 2.1.5版本,有些优化方案在最新版的cocos2d-x版本已经实现...

阅读全文

cocos2dx-html5 实现网页版flappy bird游戏

我也是第一次使用cocos2d_html5,对js和html5也不熟,看引擎自带的例子和引擎源码,边学边做,如果使用过cocos2d-x的话,完成这个游戏还是十分简单的。游戏体...

阅读全文

【cocos2d-x开发实战 特训99-终结篇】移植到android平台和添加admob广告

上一篇已经完成特性99在win32平台下的开发,现在把它移植到android上,首先修改Android.mk文件,内容如下: LOCAL_PATH := $(call my-dir) include $(CLEAR_...

阅读全文

2 条评论

  1. 是不是因为Enemy.h和HeroSprite.h中都包含了BaseSprite.h,而GameLayer.h中包含了Enemy.h和HeroSprite.h导致重定义

  2. 为什么在把Enemy添加到GameLayer中时会出现这种情况呢
    “ACTION_STATE_NONE”: 重定义;以前的定义是“枚举数” (..\Classes\GameLayer.cpp)
    f:\cocos2d-x-3.2\project\justtogan\classes\basesprite.h(5) : 参见“ACTION_STATE_NONE”的声明

欢迎留言