mirror of
https://github.com/zhenyan121/SporeBG-Conid.git
synced 2026-04-10 06:14:08 +08:00
Added NetworkManager Client and GameServer class
This commit is contained in:
@@ -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 运行时库
|
||||
|
||||
@@ -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
26
src/network/NetData.h
Normal 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
|
||||
|
||||
};
|
||||
74
src/network/NetworkManager.cpp
Normal file
74
src/network/NetworkManager.cpp
Normal 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();
|
||||
}
|
||||
}
|
||||
32
src/network/NetworkManager.h
Normal file
32
src/network/NetworkManager.h
Normal 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();
|
||||
};
|
||||
99
src/network/client/Client.cpp
Normal file
99
src/network/client/Client.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
42
src/network/client/Client.h
Normal file
42
src/network/client/Client.h
Normal 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();
|
||||
|
||||
|
||||
};
|
||||
116
src/network/server/GameServer.cpp
Normal file
116
src/network/server/GameServer.cpp
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
30
src/network/server/GameServer.h
Normal file
30
src/network/server/GameServer.h
Normal 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);
|
||||
};
|
||||
51
src/ui/managers/OnlineGameUIManager.cpp
Normal file
51
src/ui/managers/OnlineGameUIManager.cpp
Normal 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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user