← 返回项目列表
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的代码
截图


