如何用Firestore和Swift构建双人游戏数据结构并限制单游戏仅两人?
实现Firestore双人对战房间的玩家数量限制(Swift)
嘿,刚接触Firestore的时候碰到这类并发控制问题太正常了,完全不用不好意思!我之前做类似的双人游戏时也踩过坑,给你分享下最可靠的实现方式——用Firestore事务来确保原子性操作,避免多个玩家同时加入导致超过2人上限。
第一步:定义游戏房间的数据结构
首先,我们需要一个清晰的游戏房间文档结构,用来记录玩家列表和核心状态:
import FirebaseFirestoreSwift struct GameRoom: Codable { @DocumentID var id: String? // 自动映射Firestore文档ID var players: [String] = [] // 存储玩家的UID let maxPlayers: Int = 2 // 固定双人对战上限 var status: String = "waiting" // 标记房间状态(等待中/游戏中) // 可扩展其他游戏字段:当前回合、玩家分数等 }
第二步:用事务实现加入房间的核心逻辑
Firestore的事务能保证读取-修改-写入的操作是原子性的——也就是说,在你的事务执行期间,不会有其他客户端修改这个房间文档。如果有并发修改,事务会自动重试,完美解决多人同时抢房间的问题。
下面是完整的Swift代码示例:
import FirebaseFirestore func joinGameRoom(roomId: String, currentUserId: String, completion: @escaping (Bool, Error?) -> Void) { let db = Firestore.firestore() let roomRef = db.collection("gameRooms").document(roomId) // 启动Firestore事务 db.runTransaction({ (transaction, errorPointer) -> Any? in // 1. 读取目标房间的当前数据 let roomDoc: DocumentSnapshot do { try roomDoc = transaction.getDocument(roomRef) } catch let fetchError as NSError { errorPointer?.pointee = fetchError return nil } // 2. 解析房间数据到自定义模型 guard var gameRoom = try? roomDoc.data(as: GameRoom.self) else { let error = NSError(domain: "GameRoomError", code: -1, userInfo: [NSLocalizedDescriptionKey: "无法解析房间数据"]) errorPointer?.pointee = error return nil } // 3. 检查加入条件:房间未开始+玩家不在房间内+房间有空位 guard gameRoom.status == "waiting", !gameRoom.players.contains(currentUserId), gameRoom.players.count < gameRoom.maxPlayers else { let errorMessage: String switch true { case gameRoom.status != "waiting": errorMessage = "游戏已开始,无法加入" case gameRoom.players.contains(currentUserId): errorMessage = "你已经在这个房间里啦" default: errorMessage = "房间已满" } let error = NSError(domain: "GameRoomError", code: -2, userInfo: [NSLocalizedDescriptionKey: errorMessage]) errorPointer?.pointee = error return nil } // 4. 添加当前玩家到房间,满员后自动切换状态 gameRoom.players.append(currentUserId) if gameRoom.players.count == gameRoom.maxPlayers { gameRoom.status = "playing" } // 5. 提交修改到Firestore do { try transaction.setData(from: gameRoom, forDocument: roomRef) } catch let setError as NSError { errorPointer?.pointee = setError return nil } return gameRoom }) { (result, error) in // 回调返回操作结果 if let error = error { print("加入房间失败:\(error.localizedDescription)") completion(false, error) } else { print("成功加入房间!") completion(true, nil) } } }
额外优化建议
- 玩家退出处理:如果玩家断开连接或主动退出,可通过Firestore的
onDisconnect方法配合事务,从房间的players数组中移除对应UID,同时将房间状态切回等待中。 - 自动匹配房间:若玩家需要自动找房,可先查询
status: waiting且players.count < 2的房间,找到第一个符合条件的房间调用上述方法;若无空房则创建新房间。 - 安全规则加固:在Firestore安全规则中添加验证,比如限制
players数组长度不得超过2,只有房间内玩家才能修改房间数据,防止客户端恶意篡改。
内容的提问来源于stack exchange,提问作者Paul Mayer
相关产品推荐
相关产品推荐

