欢迎来到网络对战的魔法世界!之前我们都是和AI对战,今天我们要实现真正的联机对战,让你能和远方的朋友实时对弈!想象一下,即使相隔千里,也能和朋友来一局紧张刺激的五子棋!
更重要的是,今天学的 Godot 网络编程技术,可以应用到任何多人游戏中。学会这个,你就能为自己的多人游戏项目打下坚实基础。
今日核心目标
我们将实现:
- Godot网络系统:理解客户端-服务器架构
- 基础通信协议:设计游戏消息格式
- 简单服务器:实现房间管理和玩家匹配
- 网络客户端:连接服务器并同步游戏状态
第一步:理解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()
今日核心技术总结
- TCP网络通信:使用
StreamPeerTCP 进行可靠数据传输
- JSON消息协议:定义统一的通信格式
- 客户端-服务器架构:服务器作为权威源验证所有操作。这正是后端架构设计中常见的高可用模式。
- 房间管理系统:管理玩家匹配和游戏状态
- 状态同步:实时同步棋盘状态和游戏进度
下期预告:完善网络对战系统
在下一篇教程中,我们将:
- 实现更完善的房间系统
- 添加断线重连功能
- 优化网络同步性能
- 实现聊天系统
你将拥有一个完整的联机对战平台!
学习检查小测
选择题(每题2分,共10分)
-
在权威服务器模型中,游戏逻辑验证由谁负责?
A) 客户端A
B) 客户端B
C) 服务器
D) 所有客户端
-
为什么五子棋游戏选择TCP而不是UDP?
A) TCP更快
B) TCP保证数据顺序和可靠性
C) UDP更简单
D) TCP适合小数据包
-
JSON在游戏网络通信中的主要作用是什么?
A) 压缩数据
B) 加密数据
C) 结构化数据表示
D) 加速传输
-
在房间系统中,如何防止超过2个玩家加入同一房间?
A) 客户端检查
B) 服务器验证
C) 随机分配
D) 不做限制
-
客户端收到落子消息后,首先应该做什么?
A) 立即显示在屏幕上
B) 验证落子合法性
C) 发送确认消息
D) 更新本地棋盘状态
答案与解析
-
答案:C
- 解析:权威服务器模型中,服务器是唯一真理源,所有游戏逻辑和状态验证都由服务器完成,防止客户端作弊。
-
答案:B
- 解析:五子棋需要保证每一步的顺序正确且不丢失,TCP的可靠性和顺序保证特性正好满足这个需求。
-
答案:C
- 解析:JSON提供了一种结构化的数据表示方式,便于在不同系统间传递复杂的数据结构,如棋盘状态、玩家信息等。
-
答案:B
- 解析:所有验证必须在服务器进行,客户端可以被修改或绕过,服务器是防止作弊和错误的关键。
-
答案:D
- 解析:客户端收到服务器验证过的落子消息后,应首先更新本地棋盘状态,然后才更新显示。服务器已经验证了合法性。
得分评价:
- 10分:完美!你完全理解了游戏网络编程的核心
- 8分:优秀!掌握了客户端-服务器架构的精髓
- 6分:良好!理解了网络基础,建议多实践
- 6分以下:别担心!网络编程较复杂,多运行代码观察实际效果
实践挑战
- 基础挑战:在本地运行服务器和两个客户端,测试网络对战
- 进阶挑战:添加房间列表功能,让玩家能看到可用房间
- 高手挑战:实现NAT穿透,让不同局域网的玩家能直接连接
完成挑战后,邀请朋友一起测试你们的网络对战系统!
篇7结束 | 下一篇:《【Godot五子棋】08-完善网络系统,打造稳定联机对战平台!》
今日成就:你已经实现了基础网络对战系统!虽然还很简单,但这是多人游戏开发的重要一步。保存好项目,我们下期继续完善!🎮
本项目的代码可以在这里进行下载:
https://github.com/seancheng33/GomokuFiveInARow