오늘은 Broadcast기능 과 게임의 최고점수를 저장하는 기능과 각 유저마다의 최고 점수를 기록하게 하였습니다.(유저 정보 연결)
오늘은 먼저 전에 점수검증을 하는 함수를 수정해 보았습니다.
기존에는 에셋데이터와 점수를 비교하여 오차범위가 넘어가는지를 체크하였습니다. 근데 뭔가 점수를 검증함에 있어 에셋데이터의 점수만 비교하는것이 애매한 느낌이 계속 들어 수정을 진행하였습니다.
클라이언트의 점수를 가져와 서버에서 구한 점수와 비교를 하고 오차범위가 넘어가는지 체크하는 것으로 수정하였고, 클라이언트의 점수가 실제 에셋데이터의 점수(해당 스테이지 목표점수)보단 높은지를 체크하는 것으로 수정하였습니다.
src/utils/score.validation.js의 함수
수정 전 코드
export const scoreValidation = (serverTime, userStages, targetStageId, userItems) => {
const errorScope = 3; // 오차 범위
const { stages, items } = getGameAssets();
let totalScore = 0;
// 점수검증에서 1스테이지 점수 와 2스테이지 점수 + 3스테이지 점수 등을 나누어 더해주었을때 오차 범위가 5인 약간 이러한 형태로 가야될것같다
// 현재로는 일단 1스테이지 점수는 현재 플레이어의 총 스테이지만큼 반복문을 돌고 각 스테이지의 점수를 더해서 총 점수를 가져오고 그점수를 오차범위에 들어오는지 비교하여 확인하기
for (let i = 0; i < userStages.length; i++) {
// 각 스테이지 구간에서 점수를 구한다.
const stageEndTime = i === userStages.length - 1 ? serverTime : userStages[i + 1].timestamp; // 마지막 인덱스와 일치하면 즉 현재 스테이지면 현재 시간 serverTime을 할당 그외에는 바로 다음 스테이지 시작시간 즉 해당스테이지의 EndTime
const elapsedTime = (stageEndTime - userStages[i].timestamp) / 1000; // 각 스테이지 마다의 경과한 시간을 구합니다.
// console.log('--------elapsedTime--------', elapsedTime);
const stageScore = elapsedTime * stages.data[i].scorePerSecond; // 경과한 시간에 데이터 테이블의 초당 점수를 곱해줍니다.
// console.log('-------stageScore---------', stageScore);
totalScore += stageScore;
}
// 획득한 아이템 점수를 더해 주기
for (let i = 0; i < userItems.length; i++) {
const itemId = userItems[i].itemId;
const userItemInfo = userItems.find((item) => item.itemId === itemId);
const itemInfo = items.data.find((item) => item.id === itemId);
const itemTotalScore = itemInfo.score * userItemInfo.count;
totalScore += itemTotalScore;
}
// console.log('-------totalScore---------', totalScore);
const targetStage = stages.data.find((stage) => stage.id === targetStageId);
const itemInfo = items.data.find((item) => item.id === userItems[userItems.length - 1].itemId); // 가장 최고점의 아이템 점수 가져와서 오차범위 더해주기
const errorScopeResult = Math.abs(targetStage.score - totalScore);
if (errorScopeResult > errorScope + itemInfo.score) return false;
else return true;
};
수정된 코드
export const scoreValidation = (uuid, serverTime, payload) => {
const errorScope = 5; // 오차 범위
const { stages, items } = getGameAssets();
const userStages = getStage(uuid);
const userItems = getItems(uuid);
let totalScore = 0;
// 스테이지 정렬
userStages.sort((a, b) => a.id - b.id);
// 점수검증에서 1스테이지 점수 와 2스테이지 점수 + 3스테이지 점수 등을 나누어 더해주었을때 오차 범위가 5인 약간 이러한 형태로 가야될것같다
// 현재로는 일단 1스테이지 점수는 현재 플레이어의 총 스테이지만큼 반복문을 돌고 각 스테이지의 점수를 더해서 총 점수를 가져오고 그점수를 오차범위에 들어오는지 비교하여 확인하기
for (let i = 0; i < userStages.length; i++) {
// 각 스테이지 구간에서 점수를 구한다.
const stageEndTime = i === userStages.length - 1 ? serverTime : userStages[i + 1].timestamp; // 마지막 인덱스와 일치하면 즉 현재 스테이지면 현재 시간 serverTime을 할당 그외에는 바로 다음 스테이지 시작시간 즉 해당스테이지의 EndTime
const elapsedTime = (stageEndTime - userStages[i].timestamp) / 1000; // 각 스테이지 마다의 경과한 시간을 구합니다.
// console.log('--------elapsedTime--------', elapsedTime);
const stageScore = elapsedTime * stages.data[i].scorePerSecond; // 경과한 시간에 데이터 테이블의 초당 점수를 곱해줍니다.
// console.log('-------stageScore---------', stageScore);
totalScore += stageScore;
}
// 획득한 아이템 점수를 더해 주기
for (let i = 0; i < userItems.length; i++) {
const itemId = userItems[i].itemId;
const userItemInfo = userItems.find((item) => item.itemId === itemId);
const itemInfo = items.data.find((item) => item.id === itemId);
const itemTotalScore = itemInfo.score * userItemInfo.count;
totalScore += itemTotalScore;
}
const targetStage = stages.data.find((stage) => stage.id === payload.targetStage);
// 클라이언트 점수와 비교 하고 오차범위 5이하인지 확인
// 게임데이터 스테이지 목표점수보다 높은지 확인
const errorScopeResult = Math.abs(payload.score - totalScore);
if (errorScopeResult > errorScope && payload.score >= targetStage.score) return false;
else return true;
};
일단 Broadcast기능은 생각해 보았을때 현재 게임에선 클라이언트들에게 게임의 최고 점수를 알려주면 될 것 같아 연결할떄 한번, 게임오버시 최고 점수 갱신할때 한번, 적용하면 될것 같아
src/helper.js에 추가해 주었습니다.
export const handleConnection = (io, socket, uuid) => {
console.log(`New user connected!: ${uuid} with socket ID ${socket.id}`);
console.log(`Current users: `, getUsers());
// 연결된 소켓에게 connection이라는 이벤트를 통해서 연결된 유저에게 uuid의 데이터를 보내주는 것
socket.emit('connection', { uuid, userScore: getScore(uuid) });
// 연결할때 기록한 최고점수 클라이언트에게 주기
io.emit('connection', { highScore: getHighestScore() });
};
getHighestScore()
는 게임의 최고점수를 구하는 함수입니다.
추가한 코드
// 연결할때 기록한 최고점수 클라이언트에게 주기
io.emit('connection', { highScore: getHighestScore() });
// 클라에서 요청받은 이벤트를 실행하기 위한 함수 data에는 여러, 유저id, payload등이 있을 것이다.
export const handlerEvent = (io, socket, data) => {
// 항상 특정 Event를 실행하기전 클라이언트와의 버전이 일치하는지 체크해야 된다. 그래야 클라이언트에서 작성한 로직들이 제대로 작성할 것이다
// 클라이언트 버전 정보를 항상 주고 일치하게 하기 위해 clientVersion는 기획단계에서 명세를 작성할때 정한다
if (!CLIENT_VERSION.includes(data.clientVersion)) {
socket.emit('response', { status: 'fail', message: 'Client version mismatch' });
return;
}
const handler = handlerMappings[data.handlerId]; // handlerId 무조건 서버에서 핸들러 id를 넘겨 주기로 기획단계에서 결정하였습니다 패킷구조설계단계에서
if (!handler) {
socket.emit('response', { status: 'fail', message: 'Handler not found' });
return;
}
const response = handler(data.userId, data.payload); // data.userId, data.payload 기획에 따라 값을 이렇게 넣어준다
// handlerId가 3이면 게임오버 이벤트이다 게임오버할떄 알려주어 갱신하기
if (data.handlerId === 3 && highScore < getHighestScore()) {
highScore = getHighestScore();
io.emit('response', { highScore: getHighestScore() });
}
if (data.handlerId === 3) {
socket.emit('response', { userScore: getScore(data.userId) });
console.log(data.userId);
}
// socket.emit 해당 유저 한명에게 메세지를 보낸다.
socket.emit('response', response); // 해당 객체를 바로 반환하는 걸로 결정한것이지 이것 또한 맘대로 메세지를 원하는 값을 보내주어도 된다.
};
getHighestScore()
는 게임의 최고점수를 구하는 함수입니다.
추가한 코드
if (data.handlerId === 3) {
socket.emit('response', { userScore: getScore(data.userId) });
console.log(data.userId);
}
최고점수를 저장하는 게임오버시에 클라이언트에서 받은 점수를 검증하고 저장하는 형태로 변경하였습니다.
src/handlers/game.handler.js
export const gameEnd = (uuid, payload) => {
// 클라이언트는 게임 종료시 서버에게 종료시점 timeStpamp와 총 점수 를 줄 것 입니다. 추가(별칭도 넘겨 받기)
const { gameEndTime, score } = payload; // timestamp:gameEndTime 이런 형태로 쓰면 객체 구조 분해 할당으로 받고 나서 그 변수의 이름을 바꿀수 있습니다.
const stages = getStage(uuid);
if (!stages.length) {
return { status: 'fail', message: 'No stages found for user' };
}
const totalScore = getTotalScore(gameEndTime, stages, getItems(uuid));
// 점수와 타임 스탬프 검증
// 오차범위 5
if (Math.abs(score - totalScore) > 5) {
return { status: 'fail', message: 'Score verification failed' };
}
// 최고기록 저장
if (score > getHighestScore()) {
setScore(uuid, score);
return { status: 'success', message: 'The user achieved a new record.', score };
}
// 각 유저 기록갱신시 저장
if (score > getScore(uuid) || !getScore(uuid)) {
setScore(uuid, score);
return { status: 'success', message: 'The user broke his best record.', score };
}
return { status: 'success', message: 'Game ended', score }; // totalScore가 아닌 클라이언트에서 받은 score를 저장해 주면서 유저가 납득할만한 점수를 저장하였다
};
setScore(uuid, score);
점수를 저장하는 함수입니다.
최고점수만을 저장하고 각 유저의 최고기록도 저장하여 현재 유저의 최고기록을 갱신하게하여 클라에게 보여주면 좋을것 같아 이렇게 구현해 보았습니다.
점수검증 부분에 있어 전에 팀원분의 이야기를 듣고 도움을 받아 수정하게 되었습니다. 뭔가 점수검증이 애매하다고 느꼈는데 말씀해 주셔서 알맞게 수정한것 같아 좋았습니다.
그럼 오늘도 화이팅!