1. 程式人生 > >RPG遊戲製作-06-與NPC的互動

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);
	}
return ret; }
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;
}	//是否點選了相同優先順序的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的函式。