脚本触发方式分为Touch和Click,上一节实现了Touch,接下来实现Click。由于本游戏使用的是鼠标/手指来操纵人物,不存在什么虚拟摇杆,因此如果想要实现Click的话,就需要点击NPC,之后角色根据A星算法得到的路径行走,在NPC面前停下,然后对应NPC调用execute(),来调用对应的脚本。不过我们发现,目前的碰撞检测中是不包含NPC在内的,即无论NPC是否是PRIORITY_SAME,角色都会从NPC身上穿过,这显然不合理。因此,需要修改isPassing函数,来让它判断在一定范围内是否存在优先级为PRIORITY_SAME的NPC。
bool GameScene::isPassing(const Point& tilePos)
{
auto tiledMap = m_pMapLayer->getTiledMap();
auto mapSize = tiledMap->getMapSize();
//不可超出地图
if (tilePos.x < 0 || tilePos.x > (mapSize.width - 1)
|| tilePos.y > (mapSize.height - 1) || tilePos.y < 0)
{
return false;
}
auto layer = m_pMapLayer->getCollisionLayer();
auto gid = layer->getTileGIDAt(tilePos);
bool ret = m_pMapLayer->isPassing(gid);
//可通过则检查是否存在NPC
if (ret)
{
auto tileSize = tiledMap->getTileSize();
//获取矩形
Rect r;
r.origin = Point(tileSize.width * (tilePos.x + 0.5f), tileSize.height * (tilePos.y + 0.5f));
r.size = Size(1.f, 1.f);
auto npc = m_pScriptLayer->getClickedNPC(r, PRIORITY_SAME);
ret = ( npc == nullptr ? true : false);
}
return ret;
}
isPassing函数额外添加了一个判断,在对应图块可通过的情况下,会对当前的矩形来判断是否存在NPC,如果存在,则表示不可穿过。
NonPlayerCharacter* ScriptLayer::getClickedNPC(const Rect& r, int priority)
{
NonPlayerCharacter* npc = nullptr;
auto it = find_if(m_npcList.begin(), m_npcList.end(), [&r, &priority](NonPlayerCharacter* npc)
{
return npc->getPriority() == priority && npc->intersectRect(r);
});
if (it != m_npcList.end())
npc = *it;
return npc;
}
getClickedNPC()函数返回优先级相同并且矩阵发生碰撞的NPC,如果没有则返回nullptr。
接下来运行程序会发现,当直接点击优先级为PRIORITY_SAME的NPC时,人物没有动作。原因是GameScene::onTouchBegan()函数中检测到目的点不可通过,而应该认为该点可通过(前面的A星算法中默认目的点可到达就是为了当前这种情况)。
bool GameScene::onTouchBegan(Touch* touch,SDL_Event* event)
{
auto location = touch->getLocation();
auto tiledMap = m_pMapLayer->getTiledMap();
auto nodePos = tiledMap->convertToNodeSpace(location);
auto tilePos = m_pMapLayer->convertToTileCoordinate(nodePos);
auto player = m_pPlayerLayer->getPlayer();
(!this->isPassing(tilePos))//目标不可达
return false;
//主角运动
player->moveToward(tilePos);
return true;
}
接下来就应该修改onTouchBegan();先判断目的点是否有NPC,如果有,则认为可通过(不清楚是否能到达),直接调用主角的moveToward()函数即可。
bool GameScene::onTouchBegan(Touch* touch,SDL_Event* event)
{
auto location = touch->getLocation();
auto tiledMap = m_pMapLayer->getTiledMap();
auto nodePos = tiledMap->convertToNodeSpace(location);
auto tilePos = m_pMapLayer->convertToTileCoordinate(nodePos);
auto player = m_pPlayerLayer->getPlayer();
//是否点击了相同优先级的NPC
auto npc = m_pScriptLayer->getClickedNPC(Rect(nodePos, Size(1.f, 1.f)), PRIORITY_SAME);
player->setTriggerNPC(npc);
//TODO
if (npc != nullptr)
;
else if (!this->isPassing(tilePos))//目标不可达
return false;
//主角运动
player->moveToward(tilePos);
//显示点击特效
auto collisionLayer = m_pMapLayer->getCollisionLayer();
auto tileSize = tiledMap->getTileSize();
Point pos((tilePos.x + 0.5f) * tileSize.width, (tilePos.y + 0.3f) * tileSize.height);
m_pEffectLayer->showAnimationEffect("click_path", pos, collisionLayer);
return true;
}
之后还需要修改Character类,来支持我们的操作。
NonPlayerCharacter* m_pTriggerNPC;
//设置npc
void setTriggerNPC(NonPlayerCharacter* npc);
Character类中添加一个NPC指针,在onTouchBegan()中,如果点击了NPC,则会把该NPC传送给主角。
void Character::popStepAndAnimate()
{
m_bMoving = false;
//存在待到达目的点,转入
if (m_bHavePendingMove)
{
m_bHavePendingMove = false;
this->clearShortestPath();
//滞后改变
this->moveToward(m_pendingMove);
return ;
}//运动结束
else if (m_nStepIndex >= m_shortestPath.size())
{
this->clearShortestPath();
//站立动画
this->changeState(State::Idle);
return ;
}//点击了NPC,且将要到达
else if (m_pTriggerNPC != nullptr && m_nStepIndex == m_shortestPath.size() - 1)
{
auto delta = m_shortestPath.back()->getTilePos() - m_lastStep->getTilePos();
auto newDir = this->getDirection(delta);
//改变方向
if (newDir != m_dir)
{
m_bDirty = true;
m_dir = newDir;
}
this->clearShortestPath();
this->changeState(State::Idle);
m_pTriggerNPC->execute(this->getUniqueID());
m_pTriggerNPC = nullptr;
return ;
}
//...
}
在这里判断下主角是否走到了对应NPC的面前,如果是的话,先判断主角朝向是否需要改变,然后停止运动,之后触发NPC的函数。