当 "matching" 用户进行多人游戏时避免冲突
Avoid conflicting when "matching" users for multiplayer game
我尝试过 FireBase 和 PubNub 来创建这个简单的多人游戏。创建时只有 两名玩家。一个大的(也是合理的)问题是用户冲突。让我解释一下:
每个 "game" 仅由两名玩家组成(不多)。如果 4 名玩家同时登录。每个玩家搜索一个"match"。玩家一可能会匹配玩家 two.while 玩家二可能会匹配玩家三,依此类推。
我怎样才能避免它,并保证每个玩家都会得到一个单一且唯一的匹配?,或者换句话说,防止一个用户与另一个用户匹配
匹配
[p1] - [p2] - [p3] - [p4] - [p5] - 等等...
好的,您将奇数编号的玩家 (N) 与下一个偶数编号的玩家 (N + 1) 配对。
当然P5一个人待着等下一轮,那轮让他P1。这样他就不用等两轮了
您可以为这些对创建一个元组,但我会让 Player
class 也有一个类型为 Player
的字段 oponent
edit1: 您跟踪常规玩家数组中的原始队列。一旦数组达到所需的大小,您就会触发上述算法,该算法将停止更改为当前玩家池的能力,并且所有匹配都将是确定的。
空闲检查
好的,让玩家进入队列并显示计时器/空槽计数器,这样他们就可以 feedback
等待多长时间。
比赛一开始你就让他们lock in
(英雄联盟也是这样)
如果 1 名或更多玩家没有 lock in
你重新开始排队过程,可能会减少计时器,这样玩家就不必等待太久。
如果你使这个基于时间(而不是基于插槽),那么如果 1 个玩家没有响应(假设 P2),你将最后一个玩家(P5)移动到他的插槽(P5 现在是 P2)并且每个人都可以玩。
如果您有更多问题,我会编辑这个答案。
使用 Firebase,security rules and transactions 将是优雅解决方案的关键。
如果您愿意设置 node.js 脚本或其他 server-side worker,这非常简单。当玩家想要比赛时,他们会写信给 "lobby"。服务器脚本将执行匹配并写回他们将要加入的 "game room"。结构基本上是这样的:
/games/$game_id/users/user1/<user_id>
/games/$game_id/users/user2/<user_id>
/lobby/$user_id/false (an unmatched user)
/lobby/$user_id/$game_id (a matched user)
现在客户端想加入游戏时只需写信到大厅,然后等待服务器分配给他们一个游戏ID:
var ref = new Firebase("https://<YOUR-FIREBASE-APP>.firebaseio.com/");
var lobbyRef = ref.child('lobby/' + <my user id>);
lobbyRef.set(false); // I want to join a game
lobbyRef.on('value', function(snap) {
if( snap.val() !== null ) {
console.log('I joined a game!', snap.val());
}
});
服务器几乎一样简单。假设 node.js:
var ref = new Firebase("https://<YOUR-FIREBASE-APP>.firebaseio.com/");
var lobbyRef = ref.child('lobby');
var gamesRef = ref.child('games');
var player1 = null;
// listen for requests to join
lobbyRef.on('child_added', function(snap) {
// assign as player1 if nobody is waiting
if( !player1 ) {
player1 = snap.ref();
}
// if someone is already waiting, assign both players a room
else {
var player2 = snap.ref();
var gameRef = gamesRef.push({
players: {
player1: player1.key(),
player2: snap.key()
}
}, function(err) {
if( err ) { throw err; } // a bug
// let the players know they have been matched and which room to join
player1.set(gameRef.key());
player2.set(gameRef.key());
});
}
});
显然有一些工作可以使所有这些容错并且需要 security rules 来防止作弊。
完全在客户端执行此操作稍微复杂一些,但肯定是可管理的。
让每位玩家尝试将自己与大厅中的任何人配对。如果大厅里没有人,那就在那里等着。这是通过 transaction 来防止冲突的。
var ref = new Firebase("https://<YOUR-FIREBASE-APP>.firebaseio.com/");
var lobbyRef = ref.child('lobby');
function waitInLobby() {
lobbyRef.once('value', lobbyUpdated);
}
function lobbyUpdated(snap) {
if( snap.val() === null ) {
// lobby is empty, so join and wait
var ref = lobbyRef.child('<my user id>').push(false);
ref.on('value', someoneMatchedMe);
function someoneMatchedMe(snap) {
if( snap.val() !== false ) {
console.log('I got matched in room', snap.val());
ref.off('value', someoneMatchedMe); // stop monitoring
snap.ref().remove(); // leave the lobby
}
}
}
else {
// lobby is not empty, so try to match someone
var possibles = [];
snap.forEach(function(ss) {
possibles.push(ss.ref());
});
}
}
function matchUser(possibles) {
if( !possibles.length ) { waitInLobby(); }
var opponentRef = lobbyRef.child(possibles.shift());
opponentRef.transaction(function(currentVal) {
if( currentVal !== false ) {
// already claimed, start over
matchUser(possibles);
}
});
}
除了交易之外,一些安全规则在这里也很重要。还有很大的优化空间,但在您针对生产进行优化时,这是您的工程团队的工作,而不是 Stack Overflow 贡献者的工作。
我尝试过 FireBase 和 PubNub 来创建这个简单的多人游戏。创建时只有 两名玩家。一个大的(也是合理的)问题是用户冲突。让我解释一下:
每个 "game" 仅由两名玩家组成(不多)。如果 4 名玩家同时登录。每个玩家搜索一个"match"。玩家一可能会匹配玩家 two.while 玩家二可能会匹配玩家三,依此类推。
我怎样才能避免它,并保证每个玩家都会得到一个单一且唯一的匹配?,或者换句话说,防止一个用户与另一个用户匹配
匹配
[p1] - [p2] - [p3] - [p4] - [p5] - 等等...
好的,您将奇数编号的玩家 (N) 与下一个偶数编号的玩家 (N + 1) 配对。
当然P5一个人待着等下一轮,那轮让他P1。这样他就不用等两轮了
您可以为这些对创建一个元组,但我会让 Player
class 也有一个类型为 Player
oponent
edit1: 您跟踪常规玩家数组中的原始队列。一旦数组达到所需的大小,您就会触发上述算法,该算法将停止更改为当前玩家池的能力,并且所有匹配都将是确定的。
空闲检查
好的,让玩家进入队列并显示计时器/空槽计数器,这样他们就可以 feedback
等待多长时间。
比赛一开始你就让他们lock in
(英雄联盟也是这样)
如果 1 名或更多玩家没有 lock in
你重新开始排队过程,可能会减少计时器,这样玩家就不必等待太久。
如果你使这个基于时间(而不是基于插槽),那么如果 1 个玩家没有响应(假设 P2),你将最后一个玩家(P5)移动到他的插槽(P5 现在是 P2)并且每个人都可以玩。
如果您有更多问题,我会编辑这个答案。
使用 Firebase,security rules and transactions 将是优雅解决方案的关键。
如果您愿意设置 node.js 脚本或其他 server-side worker,这非常简单。当玩家想要比赛时,他们会写信给 "lobby"。服务器脚本将执行匹配并写回他们将要加入的 "game room"。结构基本上是这样的:
/games/$game_id/users/user1/<user_id>
/games/$game_id/users/user2/<user_id>
/lobby/$user_id/false (an unmatched user)
/lobby/$user_id/$game_id (a matched user)
现在客户端想加入游戏时只需写信到大厅,然后等待服务器分配给他们一个游戏ID:
var ref = new Firebase("https://<YOUR-FIREBASE-APP>.firebaseio.com/");
var lobbyRef = ref.child('lobby/' + <my user id>);
lobbyRef.set(false); // I want to join a game
lobbyRef.on('value', function(snap) {
if( snap.val() !== null ) {
console.log('I joined a game!', snap.val());
}
});
服务器几乎一样简单。假设 node.js:
var ref = new Firebase("https://<YOUR-FIREBASE-APP>.firebaseio.com/");
var lobbyRef = ref.child('lobby');
var gamesRef = ref.child('games');
var player1 = null;
// listen for requests to join
lobbyRef.on('child_added', function(snap) {
// assign as player1 if nobody is waiting
if( !player1 ) {
player1 = snap.ref();
}
// if someone is already waiting, assign both players a room
else {
var player2 = snap.ref();
var gameRef = gamesRef.push({
players: {
player1: player1.key(),
player2: snap.key()
}
}, function(err) {
if( err ) { throw err; } // a bug
// let the players know they have been matched and which room to join
player1.set(gameRef.key());
player2.set(gameRef.key());
});
}
});
显然有一些工作可以使所有这些容错并且需要 security rules 来防止作弊。
完全在客户端执行此操作稍微复杂一些,但肯定是可管理的。
让每位玩家尝试将自己与大厅中的任何人配对。如果大厅里没有人,那就在那里等着。这是通过 transaction 来防止冲突的。
var ref = new Firebase("https://<YOUR-FIREBASE-APP>.firebaseio.com/");
var lobbyRef = ref.child('lobby');
function waitInLobby() {
lobbyRef.once('value', lobbyUpdated);
}
function lobbyUpdated(snap) {
if( snap.val() === null ) {
// lobby is empty, so join and wait
var ref = lobbyRef.child('<my user id>').push(false);
ref.on('value', someoneMatchedMe);
function someoneMatchedMe(snap) {
if( snap.val() !== false ) {
console.log('I got matched in room', snap.val());
ref.off('value', someoneMatchedMe); // stop monitoring
snap.ref().remove(); // leave the lobby
}
}
}
else {
// lobby is not empty, so try to match someone
var possibles = [];
snap.forEach(function(ss) {
possibles.push(ss.ref());
});
}
}
function matchUser(possibles) {
if( !possibles.length ) { waitInLobby(); }
var opponentRef = lobbyRef.child(possibles.shift());
opponentRef.transaction(function(currentVal) {
if( currentVal !== false ) {
// already claimed, start over
matchUser(possibles);
}
});
}
除了交易之外,一些安全规则在这里也很重要。还有很大的优化空间,但在您针对生产进行优化时,这是您的工程团队的工作,而不是 Stack Overflow 贡献者的工作。