这一篇要为英雄创造一些小伙伴了,并且需要让机器人会巡逻,会偷懒,会行走,还会攻击英雄,当然也能受伤。其实机器人和英雄有一些共同的属性:攻击力、生命值和行走速度。但机器人是由电脑控制,状态是随机切换的,所以还需要指定巡逻区域、攻击区域、行走方向、决策时间等。
首先更新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个机器人追着英雄打了,效果如下图:
目前机器人和英雄都没有攻击效果,都是无敌状态,不过他们好日子快到头了,下一篇我们就来让他们接受现实的残酷吧。