socket.io, svelte 및 node를 사용하여 멀티플레이어 영화 퀴즈/퀴즈 게임을 만들어 봅시다. 데블로그 #5
34466 단어 devlogwebdevsocketiojavascript
"이륙하세요, 우리는 이륙합니다!"
그래서 어제 계획을 세웠는데, 사건의 흐름을 보여주거나 뭐..
오늘 나는 그것을 구현했거나 아직 게임 중간에 연결을 끊는 플레이어 처리를 구현하지 않았지만 다음이 될 것입니다.
전반적으로 일이 순조롭게 진행되었습니다. :) 순서도를 보고 그것을 구현하는 코드를 작성하세요.
이제 게임 시작부터 끝까지 게임 루프 논리에 대한 첫 번째 작업 프로토타입이 있습니다.
내가 한 일을 살펴보자. Game 클래스부터 시작합니다.
const { nanoid } = require('nanoid');
module.exports = class Game {
constructor({ maxPlayers = 5, rounds = 2 } = {}) {
this.id = nanoid();
this.maxPlayers = maxPlayers;
this.rounds = rounds;
this.round = 1;
this.waitBetweenRounds = 5;
this.roundTime = 30;
this.status = 'open';
this.players = new Map();
this.roundCountDown = null; //will hold the interval timer for the round
this.answers = { 1: {}, 2: {}, 3: {} }; //for now just store answers here in hardcoded way, probably wld be better if stored in player object.
}
startRoundCountDown(io, func) {
let count = this.roundTime + 1;
this.roundCountDown = setInterval(() => {
count--;
io.to(this.id).emit('count-down', count);
if (count === 0) {
this.clearRoundCountDown();
func(io, this);
}
}, 1000);
}
clearRoundCountDown() {
clearInterval(this.roundCountDown);
}
join(player) {
//check if plyer is allowed to join
if (this.status === 'open' && this.players.size < this.maxPlayers) {
this.players.set(player.id, player);
return true;
}
return false;
}
leave(playerid) {
this.players.delete(playerid);
}
resetPlayerReady() {
this.players.forEach((player) => {
player.ready = false;
});
}
howManyPlayersReady() {
let ready = 0;
this.players.forEach((player) => {
if (player.ready) ready++;
});
return ready;
}
allPlayersHaveAnswered() {
let noAnswers = 0;
this.players.forEach((player) => {
if (this.answers?.[this.round]?.[player.id] !== undefined) {
noAnswers++;
}
});
return noAnswers === this.players.size;
}
getPublicData() {
return {
id: this.id,
round: this.round,
rounds: this.rounds,
status: this.status,
};
}
//easier to do stuff on frontend with players as an array instead of a map
getPlayersAsArray() {
let playersArr = [];
//convert the players map to an array.. this could probably be done cleaner and in one line but I am not used to working with maps
//this will probably be overhauled later
this.players.forEach((player) => {
playersArr.push({ ...player });
});
return playersArr;
}
compileResults() {
//later use this to compile the results of the game
return {};
}
};
몇 가지 속성을 추가했는데 가장 중요한 속성은 roundCountDown입니다. 이 소품은 라운드를 카운트다운하기 위한 간격 타이머를 보유합니다. 클래스에 넣은 이유는 게임 인스턴스에 연결되어야 하고 이벤트 처리 코드의 다른 위치에서 시작하고 지울 수 있어야 하기 때문입니다.
방법을 자세히 살펴 보겠습니다.
startRoundCountDown(io, func) {
let count = this.roundTime + 1;
this.roundCountDown = setInterval(() => {
count--;
io.to(this.id).emit('count-down', count);
if (count === 0) {
this.clearRoundCountDown();
func(io, this);
}
}, 1000);
}
그것은 io와 함수를 취하는데, 그것이 취하는 함수는 시간이 다 되었거나 모든 플레이어가 답을 제출했을 때 실행해야 하는 함수입니다. 이 함수는 2개의 인수가 필요합니다. io는 이벤트를 발생시킬 수 있고(메서드에 전달되어 이미 사용 가능함) 다른 하나는 게임입니다. 여기서 "this"는 게임이므로 편리합니다.
Ofc는 모든 플레이어가 응답하기 전에 시간이 다 된 경우에만 실행됩니다. 간격 전에 모든 플레이어가 응답하면 중지되고 제거됩니다. 함수를 트리거할 수 있는 다른 코드는 eventHandler에 있습니다.
아래에서 실행되는 기능을 볼 수 있습니다. 이 기능 ofc는 Game 클래스 외부에 있습니다.
function endRound(io, game) {
game.round++;
if (game.round > game.rounds) {
game.status = 'end-game';
io.to(game.id).emit('end-game', game.compileResults());
games.delete(game.id);
} else {
game.status = 'end-round';
io.to(game.id).emit('end-round'); //need to send with some reuslts later
getReady(io, game);
}
}
아래에는 게임을 실행하는 코드가 있습니다.
게임 생성, 게임 참여 n soo에 대한 내용을 생략했습니다..
따라서 로비에 있는 플레이어가 게임을 시작할 준비가 되면 '플레이어 준비' 이벤트가 전송됩니다.
socket.on('player-ready', (gameId) => {
const game = games.get(gameId);
//maybe we need to do something here later except reurn but probably not, this is a safeguard if socket reconnects n start sending shit when game is in another state
if (game.status !== 'open' && game.status !== 'waiting-for-start') return;
//when player is ready shld.. change the ready variable of player
game.players.get(socket.id).ready = true;
if (game.status !== 'waiting-for-start') game.status = 'waiting-for-start'; //now we do not accept any new players
//if half of players are not ready then just return
if (game.howManyPlayersReady() < game.players.size / 2) return;
//here shld run a function that is reused everytime a new round starts
getReady(io, game);
});
보시다시피 마지막으로 발생하는 것은 getReady 기능을 실행하는 것입니다.
이렇게 하면 게임 시작을 위한 카운트다운이 시작되고 완료되면 '준비 완료'가 표시됩니다.
이 코드는 각 라운드가 완료된 후에도 실행되며 새 라운드에서 계산됩니다.
function getReady(io, game) {
game.status = 'get-ready';
game.resetPlayerReady();
let count = game.waitBetweenRounds + 1;
const counter = setInterval(countdown, 1000, game.id);
function countdown(gameId) {
count--;
console.log(count);
io.to(gameId).emit('count-down', count);
if (count == 0) {
clearInterval(counter);
io.to(gameId).emit('ready-round'); //here neeed to send with some junk later.. like question n metadata about it
}
}
}
다음으로 발생하는 일은 모든 플레이어 클라이언트가 준비가 되었음을 인정할 때까지 기다리는 것입니다. 그들은 '플레이어 준비 라운드' 이벤트를 전송하여 이를 수행합니다.
아래 코드에서 처리됩니다. 모든 플레이어로부터 준비가 완료되면
'round-start'가 출력되고 처음에 썼던 카운트다운 간격이 시작됩니다.
socket.on('player-ready-round', (gameId) => {
const game = games.get(gameId);
if (game.status !== 'get-ready' && game.status !== 'waiting-for-ready') return;
if (game.status !== 'waiting-for-ready') game.status = 'waiting-for-ready';
game.players.get(socket.id).ready = true;
if (game.howManyPlayersReady() !== game.players.size) return;
game.status = 'waiting-for-answer';
io.to(gameId).emit('round-start');
game.startRoundCountDown(io, endRound);
});
이제 우리는 라운드를 마칠 때까지 모든 플레이어가 응답하거나 시간이 다하기를 기다립니다(동일한 endRound() 함수는 내가 조금 더 길게 게시했습니다). 이 endRound 함수는 'end-round'를 방출하여 이 라운드를 종료하고 다음 라운드를 준비할지(이전과 동일한 getReady 함수) 또는 'end-game'을 방출하여 게임을 종료할지 결정합니다.
socket.on('answer', (gameId, answer) => {
const game = games.get(gameId);
if (game.status !== 'waiting-for-answer') return;
//store the answer.. for now it's stored in the game object as an object
game.answers[game.round][socket.id] = answer;
//check if all players have answered
if (game.allPlayersHaveAnswered() == false) return;
//clear the interval for counting down as we now ends the round as all players have answered
game.clearRoundCountDown();
//run endRound logic
endRound(io, game);
});
그리고 그래, 그게 다야.. 내가 그 차트를 만들었다니 다행이야, 맞아!
프런트엔드 코드는 너무 간단해서 지금은 보여줄 가치조차 없는 것 같지만 여기에 있습니다.
socket.on('count-down', (count) => {
currentCount = count;
});
socket.on('ready-round', () => {
socket.emit('player-ready-round', $gameProps.id);
});
socket.on('round-start', () => {
$activeComponent = 'question';
});
socket.on('end-round', () => {
$activeComponent = 'roundresult';
});
socket.on('end-game', () => {
$activeComponent = 'gameresult';
});
대부분은 표시되어야 하는 구성 요소에 대한 저장소를 변경합니다.
모든 카운트다운은 '카운트다운' 리스너에 의해 처리되며 변수를 값으로만 설정합니다. 이 변수는 필요한 구성 요소로 전달됩니다.
나중에 이것을 저장소 변수로 변경할 수 있습니다. 그렇게 하면 모든 소켓 논리를 자체 일반 Javascript 파일로 추출할 수 있어야 합니다. 그러나 라운드, 게임 및 질문의 결과와 같이 나중에 전달되는 더 많은 데이터가 있으므로 Svelte 구성 요소에 유지하는 것이 합리적일 수 있습니다.
다음 작업은 서버의 일부 이벤트 핸들러를 좀 더 세분화하여 플레이어가 게임 중간에 나갈 경우 이를 처리할 수 있도록 하는 것입니다.
그 후에는 이것을 플레이할 수 있는 실제 게임으로 만드는 작업을 계속할 때입니다.
Reference
이 문제에 관하여(socket.io, svelte 및 node를 사용하여 멀티플레이어 영화 퀴즈/퀴즈 게임을 만들어 봅시다. 데블로그 #5), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/zoppatorsk/lets-build-a-multiplayer-movie-triviaquiz-game-with-socketio-svelte-and-node-devlog-5-11h2텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)