找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

5468

积分

0

好友

721

主题
发表于 2 小时前 | 查看: 3| 回复: 0

欢迎来到网络对战的魔法世界!之前我们都是和AI对战,今天我们要实现真正的联机对战,让你能和远方的朋友实时对弈!想象一下,即使相隔千里,也能和朋友来一局紧张刺激的五子棋!

更重要的是,今天学的 Godot 网络编程技术,可以应用到任何多人游戏中。学会这个,你就能为自己的多人游戏项目打下坚实基础。

今日核心目标

我们将实现:

  1. Godot网络系统:理解客户端-服务器架构
  2. 基础通信协议:设计游戏消息格式
  3. 简单服务器:实现房间管理和玩家匹配
  4. 网络客户端:连接服务器并同步游戏状态

第一步:理解Godot网络架构

1.1 网络模型选择

Godot支持多种网络模型,我们选择最适合棋类游戏的:

  • 权威服务器模型:服务器是"真理之源",所有游戏逻辑由服务器验证
  • 点对点模型:每个客户端都运行完整游戏逻辑(简单但容易被作弊)

我们的架构:

┌─────────┐    ┌─────────┐    ┌─────────┐
│ 客户端 A  │────│  服务器  │────│ 客户端 B  │
└─────────┘    └─────────┘    └─────────┘
│              │              │
└───────   同步游戏状态  ───────┘

1.2 网络传输协议

  • TCP:可靠连接,保证数据顺序,适合棋类游戏
  • UDP:快速但不保证顺序,适合射击游戏
  • WebSocket:基于HTTP,适合网页游戏

我们使用 TCP ,因为五子棋需要保证每一步的顺序正确。

第二步:创建网络消息协议

2.1 设计消息格式

在scripts文件夹下创建 network_protocol.gd

# network_protocol.gd
# 网络消息协议定义
class_name NetworkProtocol

# 消息类型枚举
enum MessageType {
    CONNECT,        # 连接
    DISCONNECT,     # 断开连接
    JOIN_ROOM,      # 加入房间
    LEAVE_ROOM,     # 离开房间
    READY,          # 准备
    MOVE,           # 落子
    CHAT,           # 聊天
    GAME_STATE,     # 游戏状态
    ERROR           # 错误
}

# 游戏状态枚举(用于网络传输)
enum GameStateNet {
    WAITING,        # 等待玩家
    READY,          # 准备开始
    PLAYING,        # 游戏中
    BLACK_WIN,      # 黑方胜
    WHITE_WIN,      # 白方胜
    DRAW,           # 平局
    DISCONNECTED    # 连接断开
}

# 消息结构
class NetworkMessage:
    var type: int
    var data: Dictionary

    func _init(msg_type: int, msg_data: Dictionary = {}):
        type = msg_type
        data = msg_data

    func to_json() -> String:
        var dict = {
            "type": type,
            "data": data
        }
        return JSON.stringify(dict)

    static func from_json(json_str: String):
        var json = JSON.new()
        var error = json.parse(json_str)
        if error != OK:
            print("JSON解析错误: ", json.get_error_message())
            return null

        var dict = json.get_data()
        return NetworkMessage.new(dict["type"], dict["data"])

# 创建特定消息的辅助函数
static func create_connect_message(player_name: String) -> NetworkMessage:
    return NetworkMessage.new(
        MessageType.CONNECT,
        {"player_name": player_name}
    )

static func create_join_message(room_id: String = "") -> NetworkMessage:
    return NetworkMessage.new(
        MessageType.JOIN_ROOM,
        {"room_id": room_id}
    )

static func create_move_message(x: int, y: int, player: int) -> NetworkMessage:
    return NetworkMessage.new(
        MessageType.MOVE,
        {"x": x, "y": y, "player": player}
    )

static func create_game_state_message(state: int, current_player: int, board: Array = []) -> NetworkMessage:
    return NetworkMessage.new(
        MessageType.GAME_STATE,
        {
            "state": state,
            "current_player": current_player,
            "board": board
        }
    )

static func create_chat_message(message: String, sender: String) -> NetworkMessage:
    return NetworkMessage.new(
        MessageType.CHAT,
        {"message": message, "sender": sender}
    )

static func create_error_message(code: int, message: String) -> NetworkMessage:
    return NetworkMessage.new(
        MessageType.ERROR,
        {"code": code, "message": message}
    )

第三步:实现简单游戏服务器

3.1 创建服务器场景

新建场景,添加Node节点,重命名为 GameServer

附加脚本 scripts/game_server.gd

# game_server.gd
extends Node

# 网络配置
const PORT = 9080
const MAX_PLAYERS = 100

# 服务器状态
var server: TCPServer
var clients = []  # 连接的客户端
var rooms = {}    # 房间字典:room_id -> Room对象

# 玩家信息类
class PlayerInfo:
    var peer_id: int
    var player_name: String
    var room_id: String = ""
    var is_ready: bool = false
    var player_color: int = 0  # 0=未分配,1=黑,2=白

    func _init(id: int, name: String):
        peer_id = id
        player_name = name

# 房间信息类
class RoomInfo:
    var room_id: String
    var players = []  # PlayerInfo列表
    var game_board = []  # 棋盘状态
    var game_state = NetworkProtocol.GameStateNet.WAITING
    var current_player: int = 1  # 1=黑,2=白

    func _init(id: String):
        room_id = id
        # 初始化棋盘
        initialize_board()

    func initialize_board():
        game_board = []
        for x in range(15):
            game_board.append([])
            for y in range(15):
                game_board[x].append(0)

func _ready():
    print("游戏服务器启动中...")
    start_server()

func _process(_delta):
    # 处理新连接
    if server.is_connection_available():
        var client = server.take_connection()
        if client:
            handle_new_client(client)

    # 处理客户端消息
    for client in clients:
        if client.get_status() == StreamPeerTCP.STATUS_CONNECTED:
            process_client_messages(client)

func start_server():
    server = TCPServer.new()
    var err = server.listen(PORT)

    if err != OK:
        print("服务器启动失败: ", err)
        return

    print("服务器启动成功,监听端口: ", PORT)
    print("服务器IP: ", get_local_ip())

# 获取本地IP地址
func get_local_ip() -> String:
    var interfaces = IP.get_local_interfaces()
    for interface in interfaces:
        for address in interface["addresses"]:
            # 取第一个非本地回环的IPv4地址
            if address.find("127.") == -1 and address.find(":") == -1:
                return address
    return "127.0.0.1"

# 处理新客户端连接
func handle_new_client(client: StreamPeerTCP):
    var client_id = clients.size()
    clients.append(client)

    print("新客户端连接: ID=", client_id)

    # 发送欢迎消息
    var welcome_msg = NetworkProtocol.create_connect_message("Server")
    welcome_msg.data["client_id"] = client_id
    send_to_client(client_id, welcome_msg)

# 处理客户端消息
func process_client_messages(client: StreamPeerTCP):
    # 检查是否有数据可读
    var available_bytes = client.get_available_bytes()
    if available_bytes > 0:
        # 读取数据
        var data = client.get_data(available_bytes)
        if data[0] == OK:
            var message_str = data[1].get_string_from_utf8()

            # 解析消息
            var message = NetworkProtocol.from_json(message_str)
            if message:
                handle_message(client, message)

# 处理特定消息
func handle_message(client: StreamPeerTCP, message: NetworkProtocol.NetworkMessage):
    # 找到客户端ID
    var client_id = get_client_id(client)
    if client_id == -1:
        return

    match message.type:
        NetworkProtocol.MessageType.CONNECT:
            handle_connect(client_id, message.data)

        NetworkProtocol.MessageType.JOIN_ROOM:
            handle_join_room(client_id, message.data)

        NetworkProtocol.MessageType.READY:
            handle_ready(client_id, message.data)

        NetworkProtocol.MessageType.MOVE:
            handle_move(client_id, message.data)

        NetworkProtocol.MessageType.CHAT:
            handle_chat(client_id, message.data)

        NetworkProtocol.MessageType.DISCONNECT:
            handle_disconnect(client_id)

# 获取客户端ID
func get_client_id(client: StreamPeerTCP) -> int:
    for i in range(clients.size()):
        if clients[i] == client:
            return i
    return -1

# 处理连接消息
func handle_connect(client_id: int, data: Dictionary):
    var player_name = data.get("player_name", "Player_" + str(client_id))

    # 创建玩家信息
    var player_info = PlayerInfo.new(client_id, player_name)
    # 暂时不保存,等加入房间时再处理

    print("玩家连接: ", player_name, " (ID:", client_id, ")")

    # 发送确认消息
    var response = NetworkProtocol.create_connect_message("Server")
    response.data["status"] = "connected"
    response.data["player_id"] = client_id
    send_to_client(client_id, response)

# 处理加入房间消息
func handle_join_room(client_id: int, data: Dictionary):
    var room_id = data.get("room_id", "")

    # 如果room_id为空,创建新房间
    if room_id == "":
        room_id = generate_room_id()
        rooms[room_id] = RoomInfo.new(room_id)
        print("创建新房间: ", room_id)

    # 检查房间是否存在
    if not rooms.has(room_id):
        # 房间不存在,创建新房间
        rooms[room_id] = RoomInfo.new(room_id)

    var room = rooms[room_id]

    # 检查房间是否已满(最大2人)
    if room.players.size() >= 2:
        send_to_client(client_id, NetworkProtocol.create_error_message(1, "房间已满"))
        return

    # 创建玩家信息
    var player_name = "Player_" + str(client_id)
    var player_info = PlayerInfo.new(client_id, player_name)
    player_info.room_id = room_id

    # 分配棋子颜色
    if room.players.size() == 0:
        player_info.player_color = 1  # 第一个玩家为黑方
    else:
        player_info.player_color = 2  # 第二个玩家为白方

    # 加入房间
    room.players.append(player_info)

    print("玩家 ", player_name, " 加入房间 ", room_id, " (颜色:", player_info.player_color, ")")

    # 通知客户端加入成功
    var response = NetworkProtocol.create_join_message(room_id)
    response.data["player_color"] = player_info.player_color
    response.data["room_players"] = get_room_players_info(room)
    send_to_client(client_id, response)

    # 通知房间内其他玩家
    broadcast_to_room(room_id, NetworkProtocol.create_game_state_message(
        room.game_state,
        room.current_player,
        room.game_board
    ), client_id)

    # 如果房间有2人,开始游戏
    if room.players.size() == 2:
        start_game(room_id)

# 获取房间玩家信息
func get_room_players_info(room: RoomInfo) -> Array:
    var players_info = []
    for player in room.players:
        players_info.append({
            "id": player.peer_id,
            "name": player.player_name,
            "color": player.player_color,
            "ready": player.is_ready
        })
    return players_info

# 处理准备消息
func handle_ready(client_id: int, data: Dictionary):
    # 找到玩家所在的房间
    var room_id = find_player_room(client_id)
    if room_id == "":
        return

    var room = rooms[room_id]

    # 找到玩家并设置准备状态
    for player in room.players:
        if player.peer_id == client_id:
            player.is_ready = true
            break

    print("玩家 ", client_id, " 准备就绪")

    # 广播准备状态
    var ready_msg = NetworkProtocol.create_game_state_message(
        room.game_state,
        room.current_player,
        room.game_board
    )
    ready_msg.data["player_ready"] = client_id
    broadcast_to_room(room_id, ready_msg)

    # 检查是否所有玩家都准备
    var all_ready = true
    for player in room.players:
        if not player.is_ready:
            all_ready = false
            break

    if all_ready and room.players.size() == 2:
        start_game(room_id)

# 处理落子消息
func handle_move(client_id: int, data: Dictionary):
    var x = data.get("x", -1)
    var y = data.get("y", -1)
    var player = data.get("player", 0)

    if x == -1 or y == -1 or player == 0:
        return

    # 找到玩家所在的房间
    var room_id = find_player_room(client_id)
    if room_id == "":
        return

    var room = rooms[room_id]

    # 验证落子
    if not is_valid_move(room, x, y, player):
        send_to_client(client_id, NetworkProtocol.create_error_message(2, "无效的落子"))
        return

    # 更新棋盘
    room.game_board[x][y] = player

    # 检查胜负
    if check_win(room.game_board, x, y, player):
        if player == 1:
            room.game_state = NetworkProtocol.GameStateNet.BLACK_WIN
        else:
            room.game_state = NetworkProtocol.GameStateNet.WHITE_WIN
    else:
        # 切换当前玩家
        room.current_player = 2 if room.current_player == 1 else 1

    print("房间 ", room_id, " 落子: (", x, ",", y, ") 玩家:", player)

    # 广播落子消息
    broadcast_to_room(room_id, NetworkProtocol.create_move_message(x, y, player))

    # 广播游戏状态
    broadcast_to_room(room_id, NetworkProtocol.create_game_state_message(
        room.game_state,
        room.current_player,
        room.game_board
    ))

# 验证落子是否有效
func is_valid_move(room: RoomInfo, x: int, y: int, player: int) -> bool:
    # 检查坐标是否有效
    if x < 0 or x >= 15 or y < 0 or y >= 15:
        return false

    # 检查该位置是否已有棋子
    if room.game_board[x][y] != 0:
        return false

    # 检查是否轮到该玩家
    if room.current_player != player:
        return false

    return true

# 检查胜负(简化版,与客户端相同)
func check_win(board: Array, x: int, y: int, player: int) -> bool:
    var directions = [
        Vector2(1, 0),   # 水平
        Vector2(0, 1),   # 垂直
        Vector2(1, 1),   # 右下
        Vector2(1, -1)   # 右上
    ]

    for direction in directions:
        var count = 1  # 当前棋子

        # 正向计数
        count += count_in_direction(board, x, y, direction, player)

        # 反向计数
        count += count_in_direction(board, x, y, -direction, player)

        if count >= 5:
            return true

    return false

# 方向计数
func count_in_direction(board: Array, start_x: int, start_y: int, direction: Vector2, player: int) -> int:
    var count = 0
    var current_x = start_x + direction.x
    var current_y = start_y + direction.y

    while (current_x >= 0 and current_x < 15 and
           current_y >= 0 and current_y < 15 and
           board[current_x][current_y] == player):
        count += 1
        current_x += direction.x
        current_y += direction.y

    return count

# 处理聊天消息
func handle_chat(client_id: int, data: Dictionary):
    var message = data.get("message", "")
    var sender = data.get("sender", "Player_" + str(client_id))

    # 找到玩家所在的房间
    var room_id = find_player_room(client_id)
    if room_id == "":
        return

    # 广播聊天消息
    broadcast_to_room(room_id, NetworkProtocol.create_chat_message(message, sender))

# 处理断开连接
func handle_disconnect(client_id: int):
    print("客户端断开连接: ", client_id)

    # 找到玩家所在的房间
    var room_id = find_player_room(client_id)
    if room_id != "":
        var room = rooms[room_id]

        # 从房间移除玩家
        for i in range(room.players.size()):
            if room.players[i].peer_id == client_id:
                room.players.remove_at(i)
                break

        # 如果房间空了,删除房间
        if room.players.size() == 0:
            rooms.erase(room_id)
            print("房间 ", room_id, " 已删除")
        else:
            # 通知剩余玩家
            room.game_state = NetworkProtocol.GameStateNet.DISCONNECTED
            broadcast_to_room(room_id, NetworkProtocol.create_game_state_message(
                room.game_state,
                room.current_player,
                room.game_board
            ))

    # 从客户端列表移除
    if client_id < clients.size():
        clients[client_id] = null

# 启动游戏
func start_game(room_id: String):
    var room = rooms[room_id]
    room.game_state = NetworkProtocol.GameStateNet.PLAYING
    room.current_player = 1  # 黑方先手

    print("房间 ", room_id, " 游戏开始")

    # 广播游戏开始
    broadcast_to_room(room_id, NetworkProtocol.create_game_state_message(
        room.game_state,
        room.current_player,
        room.game_board
    ))

# 生成房间ID
func generate_room_id() -> String:
    var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
    var result = ""
    for i in range(4):
        result += chars[randi() % chars.length()]
    return result

# 查找玩家所在的房间
func find_player_room(client_id: int) -> String:
    for room_id in rooms:
        var room = rooms[room_id]
        for player in room.players:
            if player.peer_id == client_id:
                return room_id
    return ""

# 向房间广播消息(排除指定客户端)
func broadcast_to_room(room_id: String, message: NetworkProtocol.NetworkMessage, exclude_client: int = -1):
    if not rooms.has(room_id):
        return

    var room = rooms[room_id]
    for player in room.players:
        if player.peer_id != exclude_client:
            send_to_client(player.peer_id, message)

# 向客户端发送消息
func send_to_client(client_id: int, message: NetworkProtocol.NetworkMessage):
    if client_id < 0 or client_id >= clients.size():
        return

    var client = clients[client_id]
    if client and client.get_status() == StreamPeerTCP.STATUS_CONNECTED:
        var json_str = message.to_json()
        client.put_data(json_str.to_utf8_buffer())

func _exit_tree():
    # 关闭服务器
    if server:
        server.stop()
    print("服务器已关闭")

第四步:实现网络客户端

4.1 创建客户端场景

新建场景,添加Node节点,重命名为 NetworkClient

附加脚本 scripts/network_client.gd

# network_client.gd
extends Node

# 网络连接
var socket: StreamPeerTCP
var is_connected = false
var client_id = -1

# 游戏状态
var current_room = ""
var player_color = 0  # 0=未分配,1=黑,2=白
var player_name = "Player"

# 信号定义
signal connected_to_server
signal connection_failed
signal room_joined(room_id, player_color)
signal game_state_updated(state, current_player, board)
signal move_received(x, y, player)
signal chat_received(message, sender)
signal error_received(code, message)

func _ready():
    print("网络客户端初始化")

func _process(_delta):
    if socket and socket.get_status() == StreamPeerTCP.STATUS_CONNECTED:
        process_messages()

# 连接到服务器
func connect_to_server(ip: String, port: int, name: String = "Player"):
    player_name = name

    socket = StreamPeerTCP.new()
    var error = socket.connect_to_host(ip, port)

    if error != OK:
        print("连接失败: ", error)
        connection_failed.emit()
        return

    print("正在连接到服务器 ", ip, ":", port)
    is_connected = true

    # 发送连接消息
    var msg = NetworkProtocol.create_connect_message(player_name)
    send_message(msg)

# 处理接收到的消息
func process_messages():
    var available_bytes = socket.get_available_bytes()

    while available_bytes > 0:
        var data = socket.get_data(available_bytes)
        if data[0] == OK:
            var message_str = data[1].get_string_from_utf8()

            # 处理可能的多条消息(以换行分隔)
            var messages = message_str.split("\n", false)
            for msg_str in messages:
                if msg_str.strip_edges() != "":
                    process_single_message(msg_str)

        available_bytes = socket.get_available_bytes()

# 处理单条消息
func process_single_message(message_str: String):
    var message = NetworkProtocol.from_json(message_str)
    if not message:
        print("无法解析消息: ", message_str)
        return

    match message.type:
        NetworkProtocol.MessageType.CONNECT:
            handle_connect_message(message.data)

        NetworkProtocol.MessageType.JOIN_ROOM:
            handle_join_room_message(message.data)

        NetworkProtocol.MessageType.MOVE:
            handle_move_message(message.data)

        NetworkProtocol.MessageType.GAME_STATE:
            handle_game_state_message(message.data)

        NetworkProtocol.MessageType.CHAT:
            handle_chat_message(message.data)

        NetworkProtocol.MessageType.ERROR:
            handle_error_message(message.data)

# 处理连接消息
func handle_connect_message(data: Dictionary):
    if data.get("status") == "connected":
        client_id = data.get("player_id", -1)
        print("已连接到服务器,客户端ID: ", client_id)
        connected_to_server.emit()
    else:
        print("连接失败")

# 处理加入房间消息
func handle_join_room_message(data: Dictionary):
    current_room = data.get("room_id", "")
    player_color = data.get("player_color", 0)

    print("已加入房间: ", current_room)
    print("玩家颜色: ", "黑" if player_color == 1 else "白")

    room_joined.emit(current_room, player_color)

# 处理落子消息
func handle_move_message(data: Dictionary):
    var x = data.get("x", -1)
    var y = data.get("y", -1)
    var player = data.get("player", 0)

    if x != -1 and y != -1 and player != 0:
        print("收到落子: (", x, ",", y, ") 玩家:", player)
        move_received.emit(x, y, player)

# 处理游戏状态消息
func handle_game_state_message(data: Dictionary):
    var state = data.get("state", NetworkProtocol.GameStateNet.WAITING)
    var current_player = data.get("current_player", 1)
    var board = data.get("board", [])

    game_state_updated.emit(state, current_player, board)

# 处理聊天消息
func handle_chat_message(data: Dictionary):
    var message = data.get("message", "")
    var sender = data.get("sender", "")

    if message != "":
        chat_received.emit(message, sender)

# 处理错误消息
func handle_error_message(data: Dictionary):
    var code = data.get("code", 0)
    var message = data.get("message", "")

    print("错误 [", code, "]: ", message)
    error_received.emit(code, message)

# 发送消息到服务器
func send_message(message: NetworkProtocol.NetworkMessage):
    if not socket or socket.get_status() != StreamPeerTCP.STATUS_CONNECTED:
        print("未连接到服务器")
        return

    var json_str = message.to_json()
    socket.put_data(json_str.to_utf8_buffer())

# 加入房间
func join_room(room_id: String = ""):
    if not is_connected:
        print("未连接到服务器")
        return

    var msg = NetworkProtocol.create_join_message(room_id)
    send_message(msg)

# 发送准备消息
func send_ready():
    if not is_connected:
        return

    var msg = NetworkProtocol.NetworkMessage.new(NetworkProtocol.MessageType.READY)
    send_message(msg)

# 发送落子消息
func send_move(x: int, y: int):
    if not is_connected:
        return

    var msg = NetworkProtocol.create_move_message(x, y, player_color)
    send_message(msg)

# 发送聊天消息
func send_chat(message: String):
    if not is_connected:
        return

    var msg = NetworkProtocol.create_chat_message(message, player_name)
    send_message(msg)

# 断开连接
func disconnect_from_server():
    if socket:
        var msg = NetworkProtocol.NetworkMessage.new(NetworkProtocol.MessageType.DISCONNECT)
        send_message(msg)
        socket.disconnect_from_host()
        is_connected = false
        print("已断开服务器连接")

func is_connection_active() -> bool:
    return socket and socket.get_status() == StreamPeerTCP.STATUS_CONNECTED

第五步:修改棋盘脚本支持网络对战

5.1 更新棋盘脚本

board.gd 中添加网络支持。此部分代码过长,以下为核心逻辑摘要,完整实现可参考配套源码。核心思路是让棋盘脚本能根据“服务器”或“客户端”模式来切换逻辑,并结合上面封装好的 NetworkClient 信号处理落子与状态同步。

第六步:创建网络对战界面

6.1 更新主菜单

main_menu.gd 中添加网络对战选项。

# 添加网络对战按钮
func setup_ui():
    # ... 原有的UI代码 ...

    # 网络对战按钮
    var network_container = VBoxContainer.new()
    network_container.position = Vector2(
        (get_viewport_rect().size.x - 200) / 2,
        450
    )
    network_container.size = Vector2(200, 100)

    var network_label = Label.new()
    network_label.text = "网络对战"
    network_container.add_child(network_label)

    var create_server_btn = Button.new()
    create_server_btn.text = "创建服务器"
    create_server_btn.pressed.connect(_on_create_server_pressed)
    network_container.add_child(create_server_btn)

    var join_game_btn = Button.new()
    join_game_btn.text = "加入游戏"
    join_game_btn.pressed.connect(_on_join_game_pressed)
    network_container.add_child(join_game_btn)

    add_child(network_container)

# 创建服务器
func _on_create_server_pressed():
    print("创建本地服务器...")

    # 创建棋盘并设置为服务器模式
    var board = board_scene.instantiate()
    board.setup_network(board.NetworkMode.SERVER)
    add_child(board)

    # 隐藏主菜单
    hide_menu()

# 加入游戏
func _on_join_game_pressed():
    # 显示连接对话框
    show_connection_dialog()

func show_connection_dialog():
    # 创建连接对话框
    var dialog = AcceptDialog.new()
    dialog.title = "连接到服务器"
    dialog.dialog_hide_on_ok = false

    var container = VBoxContainer.new()

    var ip_label = Label.new()
    ip_label.text = "服务器IP:"
    container.add_child(ip_label)

    var ip_input = LineEdit.new()
    ip_input.text = "127.0.0.1"   # 默认本地
    ip_input.placeholder_text = "输入服务器IP地址"
    container.add_child(ip_input)

    var name_label = Label.new()
    name_label.text = "玩家名称:"
    container.add_child(name_label)

    var name_input = LineEdit.new()
    name_input.text = "Player" + str(randi() % 100)
    container.add_child(name_input)

    var connect_btn = Button.new()
    connect_btn.text = "连接"
    connect_btn.pressed.connect(_on_connect_clicked.bind(ip_input, name_input, dialog))
    container.add_child(connect_btn)

    dialog.add_child(container)
    add_child(dialog)
    dialog.popup_centered()

func _on_connect_clicked(ip_input: LineEdit, name_input: LineEdit, dialog: AcceptDialog):
    var ip = ip_input.text.strip_edges()
    var name = name_input.text.strip_edges()

    if ip == "":
        print("请输入服务器IP")
        return

    print("连接到服务器: ", ip, " 名称: ", name)

    # 创建棋盘并设置为客户端模式
    var board = board_scene.instantiate()
    board.setup_network(board.NetworkMode.CLIENT, ip, name)
    add_child(board)

    # 隐藏对话框和主菜单
    dialog.queue_free()
    hide_menu()

今日核心技术总结

  1. TCP网络通信:使用 StreamPeerTCP 进行可靠数据传输
  2. JSON消息协议:定义统一的通信格式
  3. 客户端-服务器架构:服务器作为权威源验证所有操作。这正是后端架构设计中常见的高可用模式。
  4. 房间管理系统:管理玩家匹配和游戏状态
  5. 状态同步:实时同步棋盘状态和游戏进度

下期预告:完善网络对战系统

在下一篇教程中,我们将:

  • 实现更完善的房间系统
  • 添加断线重连功能
  • 优化网络同步性能
  • 实现聊天系统

你将拥有一个完整的联机对战平台!

学习检查小测

选择题(每题2分,共10分)

  1. 在权威服务器模型中,游戏逻辑验证由谁负责?
    A) 客户端A
    B) 客户端B
    C) 服务器
    D) 所有客户端

  2. 为什么五子棋游戏选择TCP而不是UDP?
    A) TCP更快
    B) TCP保证数据顺序和可靠性
    C) UDP更简单
    D) TCP适合小数据包

  3. JSON在游戏网络通信中的主要作用是什么?
    A) 压缩数据
    B) 加密数据
    C) 结构化数据表示
    D) 加速传输

  4. 在房间系统中,如何防止超过2个玩家加入同一房间?
    A) 客户端检查
    B) 服务器验证
    C) 随机分配
    D) 不做限制

  5. 客户端收到落子消息后,首先应该做什么?
    A) 立即显示在屏幕上
    B) 验证落子合法性
    C) 发送确认消息
    D) 更新本地棋盘状态

答案与解析

  • 答案:C

    • 解析:权威服务器模型中,服务器是唯一真理源,所有游戏逻辑和状态验证都由服务器完成,防止客户端作弊。
  • 答案:B

    • 解析:五子棋需要保证每一步的顺序正确且不丢失,TCP的可靠性和顺序保证特性正好满足这个需求。
  • 答案:C

    • 解析:JSON提供了一种结构化的数据表示方式,便于在不同系统间传递复杂的数据结构,如棋盘状态、玩家信息等。
  • 答案:B

    • 解析:所有验证必须在服务器进行,客户端可以被修改或绕过,服务器是防止作弊和错误的关键。
  • 答案:D

    • 解析:客户端收到服务器验证过的落子消息后,应首先更新本地棋盘状态,然后才更新显示。服务器已经验证了合法性。

得分评价:

  • 10分:完美!你完全理解了游戏网络编程的核心
  • 8分:优秀!掌握了客户端-服务器架构的精髓
  • 6分:良好!理解了网络基础,建议多实践
  • 6分以下:别担心!网络编程较复杂,多运行代码观察实际效果

实践挑战

  • 基础挑战:在本地运行服务器和两个客户端,测试网络对战
  • 进阶挑战:添加房间列表功能,让玩家能看到可用房间
  • 高手挑战:实现NAT穿透,让不同局域网的玩家能直接连接

完成挑战后,邀请朋友一起测试你们的网络对战系统!


篇7结束 | 下一篇:《【Godot五子棋】08-完善网络系统,打造稳定联机对战平台!》

今日成就:你已经实现了基础网络对战系统!虽然还很简单,但这是多人游戏开发的重要一步。保存好项目,我们下期继续完善!🎮

本项目的代码可以在这里进行下载:
https://github.com/seancheng33/GomokuFiveInARow




上一篇:GLM-5长上下文推理优化实践:修复KV Cache竞态,Coding Agent吞吐提升超2倍
下一篇:Ubuntu 编译 Linux 内核:从源码下载到内核构建的完整操作指南
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-5-1 20:57 , Processed in 0.634796 second(s), 43 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表