Added NetworkManager Client and GameServer class

This commit is contained in:
2025-12-27 20:16:57 +08:00
parent 4f058ae768
commit 6432e3af8d
10 changed files with 483 additions and 3 deletions

View File

@@ -46,6 +46,11 @@ set(SOURCE_FILES
src/core/DebugManager.cpp
src/ui/managers/debug/DebugOverlay.cpp
src/core/Time.cpp
src/scenes/gameplay/OnlineGameScene.cpp
src/ui/managers/OnlineGameUIManager.cpp
src/network/client/Client.cpp
src/network/server/GameServer.cpp
src/network/NetworkManager.cpp
)
@@ -56,10 +61,11 @@ target_include_directories(${PROJECT_NAME} PRIVATE
${asio_SOURCE_DIR}/asio/include
)
# ========== Windows: 定义宏 & 链接库 ==========
if (WIN32)
target_compile_definitions(${PROJECT_NAME} PRIVATE
ASIO_STANDALONE
_WIN32_WINNT=0x0A00
)
target_link_libraries(${PROJECT_NAME}
@@ -68,8 +74,11 @@ target_link_libraries(${PROJECT_NAME}
SDL3_ttf::SDL3_ttf
SDL3_image::SDL3_image
nlohmann_json::nlohmann_json
ws2_32
mswsock
advapi32
)
endif()
# ========== Windows: 复制 DLL ==========
if (WIN32)
# 查找 MinGW 运行时库

View File

@@ -2,6 +2,7 @@
一个棋类游戏,与孢子分裂有关,本为**cpp重构版**,游戏核心主要玩法由**cold1840**制定的,可以看他的[python版本](https://github.com/cold1840/SporeBG),这个版本旨在用**cpp**完全重写这个游戏,**注意本项目并未使用到里面的代码,核心逻辑实现是不一样的**,且本项目打算加入许多功能,**整体玩法也是完全不一样的**,同时**cold1840**也在一定程度上参与项目的开发与维护
## 构建指南
**mingw老版本有重大问题导致asio内部状态损坏引起段错误如果是mingw请使用最新版本**
确保电脑安装了`cmake``ninja`,使用`gcc``g++`
```bash
git clone https://github.com/zhenyan121/SporeBG-Conid.git

26
src/network/NetData.h Normal file
View File

@@ -0,0 +1,26 @@
#pragma once
#include <utility>
struct NetData {
std::pair<int, int> clickPosition = {0, 0};
// 序列化,转换成字节数组
void serialize(char* buffer) const {
// 暴力转换
int* ptr = reinterpret_cast<int*>(buffer);
ptr[0] = clickPosition.first;
ptr[1] = clickPosition.second;
}
// 反序列化,从字节数组恢复
static NetData deserialize(const char* buffer) {
// 暴力转换
const int* ptr = reinterpret_cast<const int*>(buffer);
return NetData{{ptr[0], ptr[1]}};
}
// 大小固定16字节4个int
static constexpr size_t size() { return 4 * sizeof(int); }
};
enum class NetType {
HOST,
CLIENT
};

View File

@@ -0,0 +1,74 @@
#include "NetworkManager.h"
#include <iostream>
NetworkManager::~NetworkManager() {
}
void NetworkManager::init(NetType type) {
m_netType = type;
if (type == NetType::HOST) {
m_gameServer = std::make_shared<GameServer>(m_ioContext);
//std::cout << "try to start server\n";
startServer();
}
m_client = std::make_shared<Client>(m_ioContext);
}
/*
void NetworkManager::init(NetType type) {
// 先启动 io_context 线程
m_ioThread = std::thread([this]() {
std::cout << "Network thread starting: "
<< std::this_thread::get_id() << std::endl;
m_ioContext.run();
});
std::cout << "Main thread after starting network thread: "
<< std::this_thread::get_id() << std::endl;
// 等待网络线程启动
std::this_thread::sleep_for(std::chrono::milliseconds(100));
std::cout << "Initializing NetworkManager on thread: "
<< std::this_thread::get_id() << std::endl;
// 在网络线程中初始化
asio::post(m_ioContext, [this, type]() {
std::cout << "NetworkManager init running on thread: "
<< std::this_thread::get_id() << std::endl;
// 在这里创建 GameServer 和调用 async_accept
if (type == NetType::HOST) {
m_gameServer = std::make_shared<GameServer>(m_ioContext);
m_gameServer->startServer(52025);
}
});
}*/
void NetworkManager::startServer() {
if (!m_gameServer) {
std::cerr << "gameServer is not esist\n";
}
m_gameServer->startServer(52025);
std::cout << "start server success\n";
m_ioContext.run();
}
void NetworkManager::startClient() {
m_client->setCallbackes(
[](const NetData& click) {
/* 处理对手棋步 */
},
[]() {
/* 提示用户走棋 */
std::cout << "It's your turn now!\n";
}
);
if (m_netType == NetType::HOST) {
m_client->connect("127.0.0.1", 52025, true);
}
if (m_netType == NetType::CLIENT) {
m_client->connect("127.0.0.1", 52025, false);
m_ioContext.run();
}
}

View File

@@ -0,0 +1,32 @@
#pragma once
#include "network/client/Client.h"
#include "network/server/GameServer.h"
class NetworkManager {
public:
NetworkManager() = default;
~NetworkManager();
void init(NetType type);
private:
//asio::ip::tcp::acceptor m_acceptor;
std::shared_ptr<GameServer> m_gameServer = nullptr;
std::shared_ptr<Client> m_client = nullptr;
NetType m_netType;
std::thread m_ioThread;
asio::io_context m_ioContext;
void startServer();
void startClient();
};

View File

@@ -0,0 +1,99 @@
#include "Client.h"
#include <iostream>
Client::Client(asio::io_context& ioContext):
m_resolver(ioContext),
m_socket(ioContext)
{
// 构造函数实现
}
void Client::setCallbackes(MoveCallback onOpponentMove, TurnCallback onMyTurn) {
m_onOpponentMove = onOpponentMove;
m_onMyTurn = onMyTurn;
}
void Client::connect(const std::string& host, int port, bool iAmFirst) {
m_host = host;
m_port = port;
//用shared_ptr保持对象存活
auto self = shared_from_this();
std::cout << "try to resolve: " << host << ":" << port << std::endl;
// 屎山一样的lambda
m_resolver.async_resolve(host, std::to_string(port),
[this, self, iAmFirst](const asio::error_code& ec, asio::ip::tcp::resolver::results_type results) {
if (!ec) {
asio::async_connect(m_socket, results,
[this, self, iAmFirst](const asio::error_code& ec, const asio::ip::tcp::endpoint& /*endpoint*/) {
if (!ec) {
onConnected(iAmFirst);
} else {
// 处理连接错误
std::cerr << "connect failed: " << ec.message() << std::endl;
}
});
} else {
std::cerr << "resolve failed: " << ec.message() << std::endl;
}
});
}
void Client::onConnected(bool iAmFirst) {
std::cout << "Connected to server " << m_host << ":" << m_port << std::endl;
// 如果是先手,触发回调
if (iAmFirst && m_onMyTurn) {
m_onMyTurn();
} else {
// 等待对手动作
waitForOpponent();
}
}
// 发送点击位置数据给对手
void Client::sentClickPosition(const NetData& data) {
auto self = shared_from_this();
char buffer[NetData::size()];
data.serialize(buffer);
asio::async_write(m_socket, asio::buffer(buffer, NetData::size()),
[this, self](const asio::error_code& ec, std::size_t /*bytesTransferred*/) {
if (!ec) {
// 发送成功,等待对手动作
waitForOpponent();
} else {
std::cerr << "send failed: " << ec.message() << std::endl;
}
});
}
void Client::waitForOpponent() {
auto self = shared_from_this();
m_socket.async_read_some(
asio::buffer(m_readBuffer, NetData::size()),
[this, self](const asio::error_code& ec, std::size_t bytesTransferred) {
if (!ec) {
if (bytesTransferred == NetData::size()) {
NetData netData = NetData::deserialize(m_readBuffer);
// 触发对手移动回调
if (m_onOpponentMove) {
m_onOpponentMove(netData);
}
// 轮到我了
if (m_onMyTurn) {
m_onMyTurn();
}
// 重置读取缓冲区以准备下一次读取
} else {
std::cerr << "Incomplete data received from opponent." << std::endl;
}
} else {
std::cerr << "read failed: " << ec.message() << std::endl;
}
}
);
}

View File

@@ -0,0 +1,42 @@
#pragma once
#include <asio.hpp>
#include <memory>
#include <string>
#include <vector>
#include "network/NetData.h"
class Client : public std::enable_shared_from_this<Client> {
public:
// 事件回调类型
using MoveCallback = std::function<void(const NetData&)>;
using TurnCallback = std::function<void()>;
Client(asio::io_context& ioContext);
~Client() = default;
void setCallbackes(MoveCallback onOpponentMove, TurnCallback onMyTurn);
void connect(const std::string& host, int port, bool iAmFirst = true);
void sentClickPosition(const NetData& data);
private:
asio::ip::tcp::resolver m_resolver;
asio::ip::tcp::socket m_socket;
std::string m_host;
int m_port;
MoveCallback m_onOpponentMove;
TurnCallback m_onMyTurn;
char m_readBuffer[NetData::size()];
void onConnected(bool iAmFirst);
void waitForOpponent();
};

View File

@@ -0,0 +1,116 @@
#include "GameServer.h"
#include <iostream>
//#include <sstream>
GameServer::GameServer(asio::io_context& ioContext)
: m_ioContext(ioContext),
m_player1(ioContext),
m_player2(ioContext),
m_acceptor(ioContext)
{
}
void GameServer::startServer(int port) {
std::cout << "Starting server on port " << port << std::endl;
//std::cout << "startServer called on thread: "
// << std::this_thread::get_id() << std::endl;
// 同步初始化(通常这部分是同步的)
asio::ip::tcp::endpoint endpoint(asio::ip::tcp::v4(), port);
m_acceptor.open(endpoint.protocol());
m_acceptor.set_option(asio::ip::tcp::acceptor::reuse_address(true));
m_acceptor.bind(endpoint);
m_acceptor.listen();
// 开始异步接受连接
waitForPlayers(1);
}
void GameServer::waitForPlayers(int playerNum) {
/*if (!m_acceptor) {
std::cerr << "ERROR: m_acceptor is null in waitForPlayers!" << std::endl;
return;
}*/
std::cout << "waitForPlayers called for player " << playerNum << std::endl;
//auto& socket = (playerNum == 1) ? m_player1 : m_player2;
std::cout << "accepting player " << playerNum << std::endl;
// 捕获 shared_ptr 确保对象存活
//auto self = shared_from_this();
try {
std::cout << "Before async_accept for player " << playerNum << std::endl;
/*if (!m_acceptor || !m_acceptor->is_open()) {
std::cerr << "Acceptor is not open!" << std::endl;
return;
}*/
// 保存当前对象的原始指针(用于调试)
/*static std::atomic<void*> lastThisPtr = nullptr;
lastThisPtr = this;
std::cout << "waitForPlayers: this=" << this
<< ", playerNum=" << playerNum << std::endl;*/
//std::cout << "acceptor open: " << m_acceptor->is_open() << "\n";
//std::cout << "socket open: " << socket.is_open() << "\n";
// mingw老版本有重大问题导致asio内部状态损坏引起段错误
m_acceptor.async_accept(
[this, playerNum](const asio::error_code& ec, asio::ip::tcp::socket peerSocket) {
if (!ec) {
std::cout << "player" << playerNum << " connect successfully\n";
if (playerNum == 1) {
m_player1 = std::move(peerSocket);
std::cout << "try to accept player2\n";
waitForPlayers(2);
} else {
m_player2 = std::move(peerSocket);
startGame();
}
} else {
std::cerr << "Error accepting player " << playerNum << ": " << ec.message() << std::endl;
}
}
);
std::cout << "After async_accept for player " << playerNum << std::endl;
} catch (const std::exception& e) {
std::cerr << "Exception in waitForPlayers: " << e.what() << std::endl;
}
}
void GameServer::startGame() {
std::cout << "Gmae Start player1 is the first\n";
forwardMoves();
}
void GameServer::forwardMoves() {
listenPlayer(m_player1, m_player2);
listenPlayer(m_player2, m_player1);
}
void GameServer::listenPlayer(asio::ip::tcp::socket& fromPlayer, asio::ip::tcp::socket& toPlayer) {
auto self = shared_from_this();
fromPlayer.async_read_some(
asio::buffer(m_buffer, NetData::size()),
[this, self, &fromPlayer, &toPlayer](const asio::error_code& ec, size_t bytes) {
if (!ec) {
if (bytes == NetData::size()) {
// 转发给对手
asio::async_write(
toPlayer,
asio::buffer(m_buffer, bytes),
[](const asio::error_code& ec, size_t) {
}
);
listenPlayer(fromPlayer, toPlayer);
}
}
}
);
}

View File

@@ -0,0 +1,30 @@
#pragma once
#include <asio.hpp>
#include "network/NetData.h"
class GameServer : public std::enable_shared_from_this<GameServer> {
public:
GameServer(asio::io_context& ioContext);
~GameServer() = default;
void startServer( int port);
private:
asio::io_context& m_ioContext;
//std::unique_ptr<asio::ip::tcp::acceptor> m_acceptor;
asio::ip::tcp::acceptor m_acceptor;
asio::ip::tcp::socket m_player1;
asio::ip::tcp::socket m_player2;
char m_buffer[NetData::size()];
void waitForPlayers(int playerNumber);
void startGame();
void forwardMoves();
void listenPlayer(asio::ip::tcp::socket& fromPlayer, asio::ip::tcp::socket& toPlayer);
};

View File

@@ -0,0 +1,51 @@
#include "OnlineGameUIManager.h"
#include "ui/base/UIWidgetFactory.h"
OnlineGameUIManager::OnlineGameUIManager(SceneEventCallback eventCallback)
: GameUIManager(eventCallback) {
}
OnlineGameUIManager::~OnlineGameUIManager() {
}
void OnlineGameUIManager::init() {
GameUIManager::init();
// 在这里可以添加在线游戏特有的UI组件
auto it = m_buttons.find(makeHash("ActionButton"));
if (it != m_buttons.end()) {
auto& actionButton = it->second;
actionButton->setEnabled(false); // 在线游戏中初始禁用
}
auto onlineHostButton = UIWidgetFactory::createStandardButton(
"OnlineHostButton",
"Host Game",
200,
100,
[this]() {
// host 模式
if(m_onlineTypeEvent) {
m_onlineTypeEvent(NetType::HOST);
}
}
);
m_buttons.emplace(onlineHostButton->getNameHash(), std::move(onlineHostButton));
auto onlineJoinButton = UIWidgetFactory::createStandardButton(
"OnlineJoinButton",
"Join Game",
400,
100,
[this]() {
if(m_onlineTypeEvent) {
m_onlineTypeEvent(NetType::CLIENT);
}
}
);
m_buttons.emplace(onlineJoinButton->getNameHash(), std::move(onlineJoinButton));
}
void OnlineGameUIManager::setOnlineTypeCallback(OnlineTypeEvent onlineTypeEvent) {
m_onlineTypeEvent = onlineTypeEvent;
}