← 返回项目列表

Vaporum

用TCP连接服务器和客户端。通过使用字符缓冲区,我可以从服务器向客户端或从客户端向服务器发送消息。我把大部分逻辑放在本地机器上处理,唯一的缓冲区是两个玩家的键盘和鼠标输入。为了获取另一个玩家发送的缓冲区,我使用 CallbackFunction 在服务器/客户端获得字符串时 Fire Event 并进行解析。

TCP

网络系统

使用网络系统玩 Vaporum

void NetSystem::BeginFrame()
{
	//Client
	if (m_mode == Mode::CLIENT)
	{
		int result = 0;
		if (m_clientSocket == ~0ull)
		{
			m_clientSocket = socket( AF_INET, SOCK_STREAM, IPPROTO_TCP );
			unsigned long blockingMode = 1;
			ioctlsocket( m_clientSocket, FIONBIO, &blockingMode );
			m_clientState = ClientState::DISCONNECTED;
		}
		if (m_clientState == ClientState::DISCONNECTED)
		{
			sockaddr_in addr;
			addr.sin_family = AF_INET;
			addr.sin_addr.S_un.S_addr = m_hostAddress;
			addr.sin_port = m_hostPort;
			result = connect( m_clientSocket, (sockaddr*)(&addr), (int)sizeof( addr ) );
			if (result == SOCKET_ERROR)
			{
				int errorCode = WSAGetLastError();
				if (errorCode == WSAEWOULDBLOCK)
				{
					m_clientState = ClientState::CONNECTING;
				}
				else
				{
					m_clientState = ClientState::TERMINATED;
				}
			}
			else
			{
				m_clientState = ClientState::CONNECTED;
			}
		}

		if (m_clientState == ClientState::CONNECTING)
		{
			fd_set writeSockets;
			fd_set exceptSockets;
			FD_ZERO( &writeSockets );
			FD_ZERO( &exceptSockets );
			FD_SET( m_clientSocket, &writeSockets );
			FD_SET( m_clientSocket, &exceptSockets );
			timeval waitTime = { };
			int selectResult = select( 0, NULL, &writeSockets, &exceptSockets, &waitTime );

			if (selectResult == SOCKET_ERROR)
			{
				sockaddr_in addr;
				addr.sin_family = AF_INET;
				addr.sin_addr.S_un.S_addr = m_hostAddress;
				addr.sin_port = m_hostPort;
				result = connect( m_clientSocket, (sockaddr*)(&addr), (int)sizeof( addr ) );
				if (result == SOCKET_ERROR)
				{
					int errorCode = WSAGetLastError();
					if (errorCode == WSAEWOULDBLOCK)
					{
						m_clientState = ClientState::CONNECTING;
					}
					else
					{
						m_clientState = ClientState::TERMINATED;
						DebuggerPrintf( "Error: %d", errorCode );
					}
				}
			}
			else if (selectResult > 0)
			{
				if (FD_ISSET( m_clientSocket, &exceptSockets ))
				{
					sockaddr_in addr;
					addr.sin_family = AF_INET;
					addr.sin_addr.S_un.S_addr = m_hostAddress;
					addr.sin_port = m_hostPort;
					result = connect( m_clientSocket, (sockaddr*)(&addr), (int)sizeof( addr ) );
					if (result == SOCKET_ERROR)
					{
						int errorCode = WSAGetLastError();
						if (errorCode == WSAEWOULDBLOCK)
						{
							m_clientState = ClientState::CONNECTING;
						}
						else
						{
							m_clientState = ClientState::TERMINATED;
							DebuggerPrintf( "Error: %d", errorCode );
						}
					}
					else
					{
						m_clientState = ClientState::CONNECTED;
					}
				}
				else if (FD_ISSET( m_clientSocket, &writeSockets ))
				{
					m_clientState = ClientState::CONNECTED;
				}
			}
			else
			{
				m_clientState = ClientState::CONNECTING;
			}
		}

		if (m_clientState == ClientState::CONNECTED)
		{
			DebuggerPrintf( "clientConnected\n" );

			int bufferUsed = 0;
			while (!m_sendQueue.empty() && bufferUsed < m_config.m_sendBufferSize)
			{
				std::string& message = m_sendQueue.front();
				int messageSize = (int)message.length();

				if (!message.empty())
				{
					int spaceRemain = m_config.m_sendBufferSize - bufferUsed;
					int bytesToCopy = MIN( messageSize, spaceRemain );

					if (message.length() == spaceRemain)
					{
						if (bytesToCopy == 1)
							break;

						std::memcpy( &m_sendBuffer[bufferUsed], message.c_str(), bytesToCopy - 1 );
						bufferUsed += bytesToCopy - 1;

						message.erase( 0, bytesToCopy - 1 );

						if (message.empty())
						{
							m_sendQueue.pop();
							ERROR_AND_DIE( "This shouldn't get called, debug" );
						}
					}
					else if (message.length() < spaceRemain)
					{
						std::memcpy( &m_sendBuffer[bufferUsed], message.c_str(), bytesToCopy + 1 );
						bufferUsed += bytesToCopy + 1;

						message.erase( 0, bytesToCopy );

						if (message.empty())
						{
							m_sendQueue.pop();
						}
					}
					else
					{
						std::memcpy( &m_sendBuffer[bufferUsed], message.c_str(), bytesToCopy );
						bufferUsed += bytesToCopy;

						message.erase( 0, bytesToCopy );
					}
				}
			}


			//for (int i = 0; i < (int)m_sendQueue.size(); i++)
			if (bufferUsed > 0)
			{
				//sprintf_s( m_sendBuffer, m_config.m_sendBufferSize, m_sendQueue[i].c_str() );
				int sendResult = send( m_clientSocket, m_sendBuffer, static_cast(bufferUsed), 0 );
				if (sendResult == SOCKET_ERROR)
				{
					int sendError = WSAGetLastError();
					if (sendError != WSAEWOULDBLOCK)
					{
						m_clientState = ClientState::TERMINATED;
						m_sendQueue = {};
					}
				}
			}

			//m_sendQueue.clear();
			int recvResult = recv( m_clientSocket, m_recvBuffer, m_config.m_sendBufferSize, 0 );
			if (recvResult == SOCKET_ERROR)
			{
				int recvError = WSAGetLastError();
				if (recvError != WSAEWOULDBLOCK)
				{
					m_clientState = ClientState::TERMINATED;
				}
			}
			else if (recvResult > 0)
			{
				m_recvQueue += std::string( m_recvBuffer, recvResult );
			}
			else
			{
				m_clientState = ClientState::DISCONNECTED;
				closesocket( m_clientSocket );
				m_clientSocket = INVALID_SOCKET;
			}

			while (true)
			{
				size_t pos = m_recvQueue.find( '\0' );
				if (pos == std::string::npos)
					break;

				std::string firstMessage = m_recvQueue.substr( 0, pos );
				//g_theConsole->AddLine( DevConsole::HINTS, firstMessage );
				//g_theConsole->AddLine( Rgba8::PINK, HandleMessage( firstMessage ) );
				m_recvQueue.erase( 0, pos + 1 );
				g_theConsole->Execute( firstMessage );
				g_theConsole->AddLine( DevConsole::INFO_MINOR, firstMessage );
			}
		}

		if (m_clientState == ClientState::TERMINATED)
		{
			closesocket( m_clientSocket );
			m_clientSocket = INVALID_SOCKET;
			m_clientState = ClientState::DISCONNECTED;
		}
	}
	else if (m_mode == Mode::SERVER)
	{
		//Server
		if (m_serverState == ServerState::LISTENING)
		{
			if (m_clientSocket == INVALID_SOCKET)
			{
				m_clientSocket = accept( m_listenSocket, NULL, NULL );
				if (m_clientSocket == INVALID_SOCKET)
				{
					int errorCode = WSAGetLastError();
					if (errorCode != WSAEWOULDBLOCK)
					{
						m_serverState = ServerState::DISCONNECTED;
						ERROR_AND_DIE( "clientSocket is invalid " );
					}
				}
				else
				{
					unsigned long blockingMode = 1;
					ioctlsocket( m_clientSocket, FIONBIO, &blockingMode );
					m_serverState = ServerState::CONNECTED;
				}
			}
			else
			{
				m_serverState = ServerState::CONNECTED;
			}
		}
		if (m_serverState == ServerState::CONNECTED)
		{
			DebuggerPrintf( "serverConnected\n" );

			int bufferUsed = 0;
			while (!m_sendQueue.empty() && bufferUsed < m_config.m_sendBufferSize)
			{
				std::string& message = m_sendQueue.front();
				int messageSize = (int)message.length();

				if (!message.empty())
				{
					int spaceRemain = m_config.m_sendBufferSize - bufferUsed;
					int bytesToCopy = MIN( messageSize, spaceRemain );

					if (message.length() == spaceRemain)
					{
						if (bytesToCopy == 1)
							break;

						std::memcpy( &m_sendBuffer[bufferUsed], message.c_str(), bytesToCopy - 1 );
						bufferUsed += bytesToCopy - 1;

						message.erase( 0, bytesToCopy - 1 );

						if (message.empty())
						{
							m_sendQueue.pop();
							ERROR_AND_DIE( "This shouldn't get called, debug" );
						}
					}
					else if (message.length() < spaceRemain)
					{
						std::memcpy( &m_sendBuffer[bufferUsed], message.c_str(), bytesToCopy + 1 );
						bufferUsed += bytesToCopy + 1;

						message.erase( 0, bytesToCopy );

						if (message.empty())
						{
							m_sendQueue.pop();
						}
					}
					else
					{
						std::memcpy( &m_sendBuffer[bufferUsed], message.c_str(), bytesToCopy );
						bufferUsed += bytesToCopy;

						message.erase( 0, bytesToCopy );
					}
				}
			}


			//for (int i = 0; i < (int)m_sendQueue.size(); i++)
			if (bufferUsed > 0)
			{
				//sprintf_s( m_sendBuffer, m_config.m_sendBufferSize, m_sendQueue[i].c_str() );
				int sendResult = send( m_clientSocket, m_sendBuffer, static_cast(bufferUsed), 0 );
				if (sendResult == SOCKET_ERROR)
				{
					int sendError = WSAGetLastError();
					if (sendError != WSAEWOULDBLOCK)
					{
						m_serverState = ServerState::TERMINATED;
						m_sendQueue = {};
					}
				}
			}

			//m_sendQueue.clear();
			int recvResult = recv( m_clientSocket, m_recvBuffer, m_config.m_sendBufferSize, 0 );

			if (recvResult == SOCKET_ERROR)
			{
				int recvError = WSAGetLastError();
				if (recvError != WSAEWOULDBLOCK)
				{
					m_serverState = ServerState::TERMINATED;
				}
			}
			else if (recvResult > 0)
			{
				m_recvQueue += std::string( m_recvBuffer, recvResult );
			}
			else
			{
				m_serverState = ServerState::LISTENING;
				closesocket( m_clientSocket );
				m_clientSocket = INVALID_SOCKET;
			}

			while (true)
			{
				size_t pos = m_recvQueue.find( '\0' );
				if (pos == std::string::npos)
					break;

				std::string firstMessage = m_recvQueue.substr( 0, pos );

				m_recvQueue.erase( 0, pos + 1 );
				g_theConsole->Execute( firstMessage );
			}
		}

		if (m_serverState == ServerState::TERMINATED)
		{
			closesocket( m_clientSocket );
			m_clientSocket = INVALID_SOCKET;
			m_serverState = ServerState::LISTENING;
		}
		if (m_serverState == ServerState::DISCONNECTED)
		{
			closesocket( m_clientSocket );
			m_clientSocket = INVALID_SOCKET;
			m_serverState = ServerState::LISTENING;
		}
	}

}

网络系统 (TCP) 代码

六边形网格

选择六边形网格

Vec3 Game::CalculateHexPosition( int row, int col ) const
{
	float x = 0.866f * row;
	float y = 0.5f * row + col;
	return Vec3( x, y, 0.f );
}
void Game::GenerateHexGrid( std::vector& verts, std::vector& indexes )
{
	const float LINE_THICKNESS = 0.04f;
	for (int i = 0; i < (int)m_currentMap->m_mapDef.m_tiles.size(); i++)
	{
		Tile& currentTile = m_currentMap->m_mapDef.m_tiles[i];
		if (!currentTile.m_tileDef.m_isBlocked)
		{
			Vec3 hexCenter = CalculateHexPosition( currentTile.m_coordinate.x, currentTile.m_coordinate.y );
			currentTile.m_position = hexCenter;
			if (hexCenter > m_currentMap->m_mapDef.m_worldBoundsMin && hexCenter < m_currentMap->m_mapDef.m_worldBoundsMax)
			{
				AddVertsForOutlineHexigon( verts, indexes, hexCenter, currentTile.m_radius, LINE_THICKNESS );
			}		
		}
		else
		{
			Vec3 hexCenter = CalculateHexPosition( currentTile.m_coordinate.x, currentTile.m_coordinate.y );
			currentTile.m_position = hexCenter;
			if (hexCenter > m_currentMap->m_mapDef.m_worldBoundsMin && hexCenter < m_currentMap->m_mapDef.m_worldBoundsMax)
			{
				AddVertsForHexigon( verts, indexes, currentTile.m_position, currentTile.m_radius - 0.02f, Rgba8::BLACK );
			}
		}
	}
}

生成六边形网格的代码

bool IsPointInsideHexgon( Vec3 const& point, Vec3 const& center, float radius )
{
	const int numSides = 6;
	const float angleStep = 360.0f / numSides;
	const float degToRad = 3.14159265359f / 180.0f;

	std::vector vertices;
	for (int i = 0; i < numSides; ++i) 
	{
		float angle = i * angleStep * degToRad;
		Vec3 vertex( center.x + radius * cos( angle ), center.y + radius * sin( angle ), center.z );
		vertices.push_back( vertex );
	}

	for (int i = 0; i < numSides; ++i) 
	{
		Vec3 v0 = vertices[i];
		Vec3 v1 = vertices[(i + 1) % numSides];

		Vec3 edge = v1 - v0;
		Vec3 toPoint = point - v0;

		//2d cross product
		float crossZ = edge.x * toPoint.y - edge.y * toPoint.x;
		if (crossZ < 0) 
		{
			return false;
		}
	}

	return true;
}

Raycast 与六边形网格求交的代码

坦克移动方式

我使用了BFS得到每个坦克可以移动的区域,使用DFS寻找最近的路径。在移动到目标点时,我使用了CatmullRom Spline来使转弯更加平滑。

Vaporum 中的坦克运动

if (m_isMovingToOriginPos)
{
	if (m_canceledUnit != nullptr)
	{
		if (m_curveTimer->DecrementPeriodIfElapsed())
		{
			m_catmullRomSplineCount++;
			if (m_catmullRomSplineCount >= (int)m_shortestPathCurve.m_positions.size() - 1)
			{
				m_catmullRomSplineCount = 0;	
				m_shortestPathCurve.Clear();
			}
		}

		if (!m_shortestPathCurve.IsEmpty())
		{
			int curveNum = (int)m_shortestPathCurve.m_positions.size() - 1;
			float catmullRomSplineLength = m_shortestPathCurve.GetApproximateLength( 32 );
			float currentLength = (m_curveTimer->GetElapsedFraction() + (float)m_catmullRomSplineCount) / (float)curveNum * catmullRomSplineLength;
			Vec2 currentPos2D = m_shortestPathCurve.EvaluateAtApproximateDistance( currentLength, 32 );
			Vec3 currentPos( currentPos2D.x, currentPos2D.y, 0.f );
			Vec2 nextPos2D = m_shortestPathCurve.EvaluateAtApproximateDistance( currentLength + deltaSeconds, 32 );
			Vec2 currentVelocity2D;// = nextPos2D - currentPos2D;
			if (currentLength + deltaSeconds < catmullRomSplineLength)
			{
				currentVelocity2D = nextPos2D - currentPos2D;
			}
			else
			{
				Vec2 previousPos2D = m_shortestPathCurve.EvaluateAtApproximateDistance( currentLength - deltaSeconds, 32 );
				currentVelocity2D = currentPos2D - previousPos2D;
			}
			Vec3 currentVelocity( currentVelocity2D.x, currentVelocity2D.y, 0.f );
			m_canceledUnit->m_position = currentPos;
			m_canceledUnit->m_orientation = GetLookAtMatrix( currentVelocity ).GetEulerAngles();
			//DebugAddMessage( Stringf( "%.2f", currentLength ), deltaSeconds );
		}
		else//End moving, start turning
		{
			m_isTurningBack = true;
		}		
	}
}

运动过程中如何使用Spline的代码

截图