Real Time Game 트러블 슈팅
WebSocket 통신을 사용한 게임 구현하기
● Chrome 브라우저에서 네트워크 연결 에러 페이지에서 등장하는 이스터 에그 게임

● 구현 해야하는 것
- WebSocket을 사용하여 다음 스테이지로 넘어가야 한다.
- 현재 상태 : 하드 코딩으로 넘어가고 있는 상태

처음에는...
● 처음에는 assets을 모아둔 폴더에서 직접적으로 가져오려고 했다. 그랬더니...
GET http://localhost:3001/assets/stage.json net::ERR_ABORTED 404 (Not Found)
위와 같은 에러가 발생했다.
→ 왜 그런가 했더니

- 현재 서버에서는 'public' 이라는 디렉토리 안에 있는 파일들만 외부에서 사용되도록 정적처리 되어 있다.
- 'assets' 디렉토리는 'public' 에서는 접근 허용이 불가함으로 stage.json을 자체적으로 가져와서 사용할 수는 없다.

그럼 어떻게 해야하는가?
● 다시 생각해보자!
- 현재 WebSocket 통신에 대한 개념으로 게임을 구현하려고 하고 있다!
- 구현하려는 코드에는 현재 클라이언트 상에서 플레이하는 유저의 점수 정보와 스테이지 점수를
서버에서 보내주는 데이터와 비교하여 맞는지 비교해야한다.
1) 클라이언트에서 userId를 판별해야한다
// public/Score.js
initUser() {
const socket = io(); // Socket.io 연결
// 서버로부터 userId 수신
socket.on('connection', (data) => {
this.userId = data.uuid; // 서버에서 받은 userId 저장
console.log('Initialized user with ID:', this.userId);
});
}
- 서버에서 userId를 uuid 형태로 저장
- Socket 통신을 사용하여 'connection'으로 정의한 곳에서 데이터를 가져온다
// src/handlers/register.handler.js
const registerHandler = (io) => {
io.on('connection', (socket) => {
// 접속을 했을 때 이벤트
console.log('A user connected:', socket.id);
// UUID 생성
const userUUID = uuidv4();
if (!userUUID) {
console.error('Failed to generate userUUID');
return;
}
// 스테이지 초기화 및 유저 추가
createStage(userUUID);
console.log('Generated userUUID: ', userUUID);
addUser({ uuid: userUUID, socketId: socket.id });
2) userId를 가져왔으니 currentStage와 targetStage에 대한 데이터를 stage.json의 내용과 비교해야한다
// public/Score.js
// score에 대한 데이터
async getTargetScore(stageId) {
return new Promise((resolve, reject) => {
this.socket.emit('request-game-assets', (response) => {
if (response.status === 'success') {
const stage = response.data.stages?.data?.find((s) => s.id === stageId);
if (!stage) {
console.error(`Stage with ID ${stageId} not found`);
resolve(null); // 해당 스테이지가 없으면 null 반환
} else {
resolve(stage.score); // 스테이지의 목표 점수 반환
}
} else {
console.error('Failed to fetch target score:', response.message);
reject(null);
}
});
});
}
// stage에 대한 데이터
async getStageData(stageId) {
return new Promise((resolve, reject) => {
this.socket.emit('request-game-assets', (response) => {
if (response.status === 'success') {
const stage = response.data.stages?.data?.find((s) => s.id === stageId);
if (!stage) {
console.error(`Stage with ID ${stageId} not found`);
resolve(null); // 해당 스테이지가 없으면 null 반환
} else {
resolve(stage); // 스테이지 데이터 반환
}
} else {
console.error('Failed to fetch stage data:', response.message);
reject(null);
}
});
});
}
- 'public' 에서 Socket 통신을 통해 'request-game-assets'에 대한 데이터를 요청한다.
// src/handlers/register.handler.js
// 클라이언트가 game assets 요청 시 처리
socket.on('request-game-assets', (callback) => {
try {
const assets = getGameAssets(); // gameAssets 가져오기
callback({ status: 'success', data: assets });
} catch (error) {
console.error('Failed to retrieve game assets:', error.message);
callback({ status: 'fail', message: 'Failed to retrieve game assets' });
}
});
- 'request-game-assets'(으)로 가져올 데이터를 서버에서 불러온 후 클라이언트에게 데이터를 넘겨준다.
3) 필요한 데이터들을 전부 가져왔으니 점수에 따라 스테이지를 변경!
const stageData = await this.getStageData(this.currentStageId); // 현재 스테이지 데이터 가져오기
if (!stageData) {
console.error('Stage data is null. Cannot proceed.');
return;
}
const { score: targetScore, scorePerSecond } = stageData;
// 점수 증가 (스테이지별 scorePerSecond를 반영)
this.score += deltaTime * 0.001 * scorePerSecond;
// 목표 점수에 도달하고 stageChange 플래그가 true일 때 다음 스테이지로 이동
if (this.score >= targetScore && this.stageChange) {
console.log('Condition met: Moving to next stage');
this.stageChange = false; // 플래그 비활성화
sendEvent(
11,
{ currentStage: this.currentStageId, targetStage: this.currentStageId + 1 },
(response) => {
if (response.status === 'success') {
this.currentStageId += 1; // 다음 스테이지로 이동
} else {
console.error('Failed to move to the next stage:', response.message);
}
this.stageChange = true; // 다시 요청 가능하도록 플래그 초기화
},
);
}
- sendEvent를 통해 11으로 정의한 handlerMapping을 통해 stage가 넘어가는 handler에 클라이언트의 데이터 제공
- 제공된 데이터를 토대로 currentStage를 비교해서 다음 스테이지로 넘어가는 stage.handler 함수 실행
막혔던 부분
● response 호출
// public/Socket.js
const sendEvent = (handlerId, payload) => {
socket.emit(
'event',
{
userId,
clientVersion: CLIENT_VERSION,
handlerId,
payload,
},
- response으로 status : 'success'를 받아 스테이지가 넘어갔는지 판별을 하려고 했지만 response가 불러와지지 않았다.
● 왜 그런가?
- 위의 getStageData, getTargetScore, update에서 모두 response를 사용하여 서버에서 보내준 데이터를 사용하고 있는데
public/Socket.js에서 서버와 통신을 담당하는 코드에서는 정작 response를 불러오는 코드가 존재하지 않았다.
어떻게 수정했는가?
const sendEvent = (handlerId, payload, callback) => {
socket.emit(
'event',
{
userId,
clientVersion: CLIENT_VERSION,
handlerId,
payload,
},
(response) => {
if (callback) callback(response); // 클라이언트 콜백 호출
},
);
};
- callback 함수를 사용하여 해결
- socket.emit으로 'evnet'로 정의된 곳에 데이터를 전송하고, 서버가 요청을 처리한 후
'response'를 클라이언트로 반환
어떻게든 돌아간다!
● 하드 코딩, http 호출 등 여러가지 방법을 사용해보았고 지금 현재 공부하고 적용해야할 것은
결국 Socket 통신을 사용해서 서버와 클라이언트 간에 통신이 이루어져야 한다.
● 처음에 http 사용 방식과 비슷하다고 잘못 이해하고 있었고 이는 결국 여러가지를 테스트해야하는
삽질을 하게 되었다.
● Socket 통신으로 구현하니 실시간으로 통신이 가능하여 최고 점수 비교 등 실시간에 필요한 기능들을
구현할 수 있을 것이다.
'Project_xxx' 카테고리의 다른 글
| [Project_] TD Trouble Shooting - 1 (1) | 2024.12.30 |
|---|---|
| [Project_] RTG - Trouble Shooting 2 (1) | 2024.12.19 |
| [Project_] FO - 2 (Trouble Shooting) (1) | 2024.12.06 |
| [Project_] FO - 1 (1) | 2024.12.03 |
| [Project_] Item-Simulator [Trouble Shooting] (0) | 2024.11.28 |