脚本触发方式分为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的函数。