다시 실습이다.
이번에는 이전에 기획했던 "점핑 액션 게임"을 웹소켓을 이용해 만들어 볼 것이다.
다만, 이전과는 다르게 yarn 대신 npm을 사용할 예정이다.
환경 설정
npm init -y
npm install express socket.io
npm install -D nodemon
이번에는 HTTP 모듈을 사용할 것이다.
해당 모듈은 Node.js에서 기본적으로 제공한다.
import express from "express";
import { createServer } from 'http';
const app = express();
const server = createServer(app); // 이걸 이용해 서버를 키고, 웹 소켓도 연결하는 등 이것저것 할 것이다.
const PORT = 3000;
app.use(express.json());
// "query string"(일명 qs) 라이브러리로 URL-encoded 데이터를 파싱할지(true) 안 할지에 대한 설정
app.use(express.urlencoded({extended: false}));
server.listen(PORT, async () => {
console.log(`Server is running on port ${PORT}`);
})
다음은 socket.io를 세팅해볼 것이다.(soket.io가 궁금하다면 링크를 통해 게시물 확인 바람)
init 폴더를 새로 생성한 뒤, 그 안에다 socket.js 파일을 생성한다.
init 폴더는 서버가 시동이 될 때, 또느 ㄴ서버가 최초로 떴을 때, 시동·호출할 함수들을 모아두는 곳이다.
import { Server as SocketIO } from "socket.io";
const initSocket = (server) => {
const io = new SocketIO(); // 소켓IO 서버 생성
io.attach(server); // 서버에 연결
};
export default initSocket;
이렇게 생성된 걸, 다시 app.js에서 호출한다.
import initSocket from "./init/socket.js";
// 중략
app.use(express.json());
app.use(express.urlencoded({extended: false}));
initSocket(server);
데이터 테이블 가져오기
환경 설정이 끝났다면, 이젠 지난 시간에 만들었던 게임 데이터 테이블을 서버 메모리에 가져올 것이다.
이를 위해서 파일 시스템을 사용할건데, 그러기 위해 "fs"라는 모듈을 사용할 것이다.
- FS
- Node.js의 fs(파일 시스템) 모듈은 파일 시스템에 접근하고, 파일을 읽고 쓰는 기능을 제공한다.
- 동기적 및 비동기적 방식 모두로 파일 I/O 작업을 수행할 수 있다.
- 파일 생성, 읽기, 쓰기, 삭제, 수정 등의 작업을 할 수 있다.
- 다양한 형태의 파일 기반 작업을 가능하게 한다.
데이터 테이블의 관리 방법
DB, CDN, file 등으로 테이블을 관리하는데, 여기에서는 file로 테이블을 관리할 예정이다.
여기서 잠깐 알아보자면, 유저들이 게임을 접속할 때, 바로 서버에 접속하는 방법도 있겠지만 클라이언트에서 어떠한 파일들이 필요한 경우가 있다.(ex: 게임 데이터 테이블) 왜냐하면 클라이언트에서도 어느정도 정보를 알고 있어야 서버에 요청을 보낼 수 있기 때문이다.
먼저, 지난번에 만들어둔 데이터 테이블을 JSON 형태로 집어 넣는다. 그러기 위해 최상위 경로에 assets라는 폴더를 만들고, 아래 JSON 파일들을 집어 넣는다.
- item.json
{
"name": "item",
"version": "1.0.0",
"data": [
{ "id": 1, "score": 10 },
{ "id": 2, "score": 20 },
{ "id": 3, "score": 30 },
{ "id": 4, "score": 40 },
{ "id": 5, "score": 50 },
{ "id": 6, "score": 60 }
]
}
- stage.json
{
"name": "stage",
"version": "1.0.0",
"data": [
{ "id": 1000, "score": 0 },
{ "id": 1001, "score": 100 },
{ "id": 1002, "score": 200 },
{ "id": 1003, "score": 300 },
{ "id": 1004, "score": 400 },
{ "id": 1005, "score": 500 },
{ "id": 1006, "score": 600 }
]
}
- item_unlock.json
{
"name": "item_unlock",
"version": "1.0.0",
"data": [
{ "id": 101, "stage_id": 1001, "item_id": 1 },
{ "id": 201, "stage_id": 1002, "item_id": 2 }
]
}
파일이 준비되었다면, 해당 파일들을 서버가 올라올 때 읽어서 서버 메모리사엥 존재해야 한다.
따라서 init 폴더 안에 assets.js라는 파일을 만들어 준다.
파일을 만들었다면, 이제 assets 폴더 안에 있는 3개의 json 파일을 가져와야 한다.
그러기 위해서는 경로를 사용해야 하는데, 해당 경로를 일일이 하드코드 하는 것에도 한계가 있다. 따라서 우리는 파일의 절대 경로를 얻어 사용해볼 것이다.
// node.js에서 제공하는 모듈이다.
import path from 'path';
import {fileURLToPath} from 'url';
// import.meta.url = 현재 파일의 절대 경로. 즉, assets.js의 위치
const __filename = fileURLToPath(import.meta.url);
// 현재 파일이 위치한, 파일 이름을 제외한 경로. 즉, 위에서 디렉토리 경로만 추출
const __dirname = path.dirname(__filename);
// 최상위 경로 + assets 폴더
const basePath = path.join(__dirname, '../../assets/');
이렇게 파일이 있는 assets 폴더까지의 경로를 얻었다면, 이제는 해당 경로를 이용해 파일에 접근해야 할 때다.
그러기 위해 readFileAsync()를 만들었다. 하지만 해당 함수에서는 파일을 한 개 밖에 가져오지 못하므로, loadGameAssets()를 새로 만들어 파일을 한번에 여러개 읽을 수 있도록 할 것이다. 그러기 위해서 Promise.all()을 사용할 것인데, 해당 메서드에 대한 설명은 이쪽 게시물을 참고하길 바란다.(링크)
하여, 완성된 전체 코드는 다음과 같다.
- assets.js
import fs from 'fs';
import path from 'path';
import {fileURLToPath} from 'url';
let gameAssets = {};
// import.meta.url = 현재 파일의 절대 경로. 즉, assets.js의 위치
const __filename = fileURLToPath(import.meta.url);
// 현재 파일이 위치한, 파일 이름을 제외한 경로. 즉, 위에서 디렉토리 경로만 추출
const __dirname = path.dirname(__filename);
// 최상위 경로 + assets 폴더
const basePath = path.join(__dirname, '../../assets');
// 파일 읽기
// 비동기 병렬로 파일을 읽는다.(하나만...)
const readFileAsync = (filename) => {
return new Promise((resolve, reject) => {
fs.readFile(path.join(basePath, filename), 'utf8', (err, data) => {
if(err) {
reject(err);
return ;
}
resolve(JSON.parse(data));
});
});
};
// readFileAsync가 파일을 하나만 읽을 수 있기 때문에,
// promise.all을 사용해 한번에 읽을 수 있도록 한다.
// 서버가 실행되자마자 바로 같이 호출되어야 한다.
export const loadGameAssets = async () => {
try {
const [stages, items, itemUnlocks] = await Promise.all([
readFileAsync('stage.json'),
readFileAsync('item.json'),
readFileAsync('item_unlock.json')
]);
gameAssets = {stages, items, itemUnlocks};
return gameAssets;
} catch (error) {
throw new Error('Failed to load game assets: ' + error.message);
}
};
export const getGameAssets = () => {
return gameAssets;
};
- app.js
import express from "express";
import { createServer } from 'http';
import initSocket from "./init/socket.js";
import { loadGameAssets } from "./init/assets.js";
// 중략
server.listen(PORT, async () => {
console.log(`Server is running on port ${PORT}`);
// 이곳에서 JSON파일을 읽음
try {
const assets = await loadGameAssets();
console.log(assets);
console.log('Assets loaded successfully');
} catch(error) {
console.log('Failed to load game assets: ' + error);
}
});
'프로젝트' 카테고리의 다른 글
24/09/30 - [실습] 점핑 액션 게임(4): 커넥션 핸들러, 이벤트 핸들러(클라이언트 버전 확인, 스테이지 이동, 점수 계산) (0) | 2024.09.30 |
---|---|
24/09/29 - [실습] 점핑 액션 게임(3): UUID와 유저 접속 관리 (1) | 2024.09.29 |
24/09/26 - [실습] 점핑 액션 게임(1): 게임 기획 (0) | 2024.09.26 |
24/09/25 - [팀] 풋살 온라인(4): 최종 회고 (0) | 2024.09.25 |
24/09/24 - [팀] 풋살 온라인(3): 보유한 선수 목록 & 랭킹 조회 (0) | 2024.09.24 |