React Native Firebase多人卡牌游戏dealCards函数重复执行问题求助
解决React Native + Firebase多人卡牌游戏发牌函数重复执行问题
问题描述
第一次开发多人游戏,基于React Native和Firebase数据库做一款多人卡牌游戏。在游戏页面的useEffect中调用dealCards函数,原本希望所有玩家进入后只触发一次发牌,但现在函数执行次数和玩家数量一致(共3次),每加载一个玩家就调用一次,界面上能看到卡牌多次变化:第一个玩家进入时发牌,第二个玩家进入时再次发牌,以此类推。需要让该函数仅执行一次。
相关代码片段
dealCards函数
const dealCards=(playerOnGame,deck)=>{ deck.shuffle() // 给所有玩家发第一张牌 for(let i=0;i<playerOnGame.length;i++){ let card=deck.pop(); if(playerOnGame[i].money!==0){ playerOnGame[i].card1 = card } } // 给所有玩家发第二张牌 for(let i=0;i<playerOnGame.length;i++){ let card=deck.pop(); if(playerOnGame[i].money!==0){ playerOnGame[i].card2=card } } let newPlayersOnGame=[...playerOnGame] functions.playersInGameUpdating(newPlayersOnGame) // setAllCardOfPlayersInGame(newPlayersOnGame) }
游戏页面的useEffect
useEffect(() => { dealCards() }, [])
UI代码片段
return( <ImageBackground source={image} resizeMode='cover' style={{height:'100%'}}> <SafeAreaView > <View style={styles.upRow}> <ImageBackground // player1 位置2 source={require('../assets/circle2.png')} resizeMode='center' style={styles.circleCornerLeftUp}> {showCards ?( <View style={styles.fishCardRow}> <Image source={playersInTable[0].card1.cardImage} resizeMode='center' style={styles.fishCard}/> <Image source={playersInTable[0].card2.cardImage} resizeMode='center' style={styles.fishCard}/> </View> ) : null } <Image source={playersInTable[0].character} resizeMode='center' style={styles.charcter}/> <Text style={styles.charcterText}>{playersInTable[0].userName}</Text> <Text style={styles.moneyText}><MaterialCommunityIcons name="currency-ils" size={12} color="white" />{playersInTable[0].money.toFixed(2)}</Text> <Text style={styles.blindText}>{playersInTable[0].blind}</Text> </ImageBackground> <ImageBackground // player2 source={require('../assets/circle2.png')} resizeMode='center' style={styles.imageIconCircleUp}> {showCards ?( <View style={styles.fishCardRow}> <Image source={playersInTable[1].card1.cardImage} resizeMode='center' style={styles.fishCard}/> <Image source={playersInTable[1].card2.cardImage} resizeMode='center' style={styles.fishCard}/> </View> ) : null } <Image source={playersInTable[1].character} resizeMode='center' style={styles.charcter}/> <Text style={styles.charcterText}>{playersInTable[1].userName}</Text> <Text style={styles.moneyText}><MaterialCommunityIcons name="currency-ils" size={12} color="white" />{playersInTable[1].money.toFixed(2)}</Text> <Text style={styles.blindText}>{playersInTable[1].blind}</Text> </ImageBackground> <ImageBackground // player3 source={require('../assets/circle2.png')} resizeMode='center' style={styles.imageIconCircleUp}> {showCards ?( <View style={styles.fishCardRow}> <Image source={playersInTable[2].card1.cardImage} resizeMode='center' style={styles.fishCard}/> <Image source={playersInTable[2].card2.cardImage} resizeMode='center' style={styles.fishCard}/> </View> ) : null } <Image source={playersInTable[2].character} resizeMode='center' style={styles.charcter}/> <Text style={styles.charcterText}>{playersInTable[2].userName}</Text> <Text style={styles.moneyText}><MaterialCommunityIcons name="currency-ils" size={12} color="white" />{playersInTable[2].money.toFixed(2)}</Text> <Text style={styles.blindText}>{playersInTable[2].blind}</Text> </ImageBackground>
解决方案
核心思路
多人游戏中不能依赖客户端本地状态控制发牌,必须用Firebase的全局房间状态同步所有玩家的逻辑,避免重复触发。具体步骤如下:
1. 给Firebase游戏房间添加发牌状态字段
在游戏房间的Firebase文档中新增isDealt字段,初始值设为false,用来标记是否已经完成发牌。
2. 修改dealCards函数,增加状态校验
调用发牌前先从Firebase读取房间状态,只有未发牌时才执行逻辑,执行完成后更新isDealt为true:
const dealCards = async (playerOnGame, deck, gameRoomRef) => { // 获取当前房间的发牌状态 const gameRoomSnapshot = await gameRoomRef.get(); const gameRoomData = gameRoomSnapshot.data(); if (gameRoomData.isDealt) return; // 已发牌则直接终止函数 deck.shuffle(); // 发第一张牌 for(let i=0;i<playerOnGame.length;i++){ let card=deck.pop(); if(playerOnGame[i].money!==0){ playerOnGame[i].card1 = card } } // 发第二张牌 for(let i=0;i<playerOnGame.length;i++){ let card=deck.pop(); if(playerOnGame[i].money!==0){ playerOnGame[i].card2=card } } let newPlayersOnGame=[...playerOnGame] // 同时更新玩家数据和发牌状态,保证原子性 await Promise.all([ functions.playersInGameUpdating(newPlayersOnGame), gameRoomRef.update({ isDealt: true }) ]) }
3. 调整useEffect的触发逻辑
让useEffect依赖玩家列表,当玩家数量达到预设人数(比如3人)时,才调用发牌函数:
useEffect(() => { // 假设需要3名玩家全部进入后才发牌 if (playersInTable.length === 3) { // 替换为你的游戏房间ID const gameRoomRef = firebase.firestore().collection('gameRooms').doc('your-game-room-id'); dealCards(playersInTable, deck, gameRoomRef); } }, [playersInTable, deck])
额外注意事项
- 必须用Firebase的全局状态控制,否则不同玩家的客户端本地状态无法同步,可能导致重复发牌。
- 如果支持玩家中途加入,需要调整逻辑:新玩家进入时直接从Firebase获取已发好的卡牌数据,不再触发发牌逻辑。
内容的提问来源于stack exchange,提问作者PoPapas
相关产品推荐
相关产品推荐

