RPG遊戲製作-06-與NPC的互動
指令碼觸發方式分為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); }
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; }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;
}
//是否點選了相同優先順序的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,且將要到達
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的函式。