cocos2d-x横版格斗游戏教程3



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

1. 添加机器人

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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,所以就尽可能的简单些吧。首先机器人需要根据自己的朝向和英雄的位置来思考,如果机器人背对着英雄或者英雄处于机器人巡逻范围之外,那么此时机器人是看不到英雄的,机器人就会随机的选择继续巡逻或者站着偷懒。如果英雄在机器人的巡逻范围内,且被机器人看到了,则机器人需要判断英雄是否处于自己的攻击范围,来决策是攻击还是追过去。每种状态下的思考时间最好设置成随机的,这样更真实。看源码实现:

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
83
84
85
86
87
88
89
90
91
92
93
94
95
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,添加下面的代码:

1
2
3
4
5
6
7
#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,添加下面的函数:

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
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函数中添加:

1
this ->updateEnemies(dt);

在init函数中添加:

1
2
3
4
5
6
m_pEnemies = Array::createWithCapacity(MIN_ENEMY_COUNT);
  m_pEnemies->retain();
  for ( int i = 0; i < MIN_ENEMY_COUNT; ++ i)
  {
   this ->addEnemy();
  }

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


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

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值