- 23.04.24
- 23.04.23
- 23.04.21
- 23.04.16
- 23.04.15
- 23.04.14
- 23.04.13
- 23.04.12
- 23.04.11
- 23.04.10
- 23.04.05
- 23.04.04
- 23.04.03
23.04.24
- player2의 키 입력 이벤트
- 소켓 이벤트 정리
- connectRTC로 이벤트 이름 정리
- 각 이벤트의 매개변수
23.04.23
- WebRTC로 게임 화면을 스트리밍한다.
- player1이 게임을 플레이하고, 나머지 유저들은 스트리밍 화면을 본다.
- player2는 키를 입력하여 화면을 조작한다.
-
만약에 viewer가 방에 들어온다면 player1의 화면을 스트림받는다.
- player1, player2, viewer를 구분하기
- 생성 순서대로 role를 정함
- 클라이언트는 자신의 역할대로 동작한다.
- player1
- canvas를 그리며, stream을 생성한다.
- player2의 키 입력 이벤트를 받는다.
- player2
- player1의 stream을 받아서 video로 보여준다.
- 유저가 키를 입력하면 서버로 이벤트를 보낸다.
- viewer
- player1의 stream을 받아서 video로 보여준다.
- player1
-
클라이언트는 들어올때 첫번째 클라이언트의 stream을 받는다.
- player1은 canvas, 나머지는 video
- 가장 먼저 들어온 유저가 player1이며 canvas에 그림을 그린다.
- 나머지 유저는 player1에게 화면 스트리밍을 받는다.
- player1의 canvas에 유저의 블록 그리기 (상단과 하단)
- 유저 블록과 공의 충돌 구현
- player1는 하단의 블록을 조작한다.
- player2는 상단의 블록을 조작한다.
23.04.21
pingpong 게임
- 벽에 부딪히는 공
- 다른 클라이언트에도 똑같은 궤적으로 움직이는 공
- 첫번째 클라이언트는 공이 충돌 시, 정보를 서버에 보낸다.
- 게임을 보고 있는 클라이언트의 소켓은 모두 배열로 저장된다.
- 게임 도중에 들어온 유저는 공이 벽에 부딪히기 전까지 공을 볼 수 없다.
- 클라이언트들은 첫번째가 보낸 공의 충돌 값을 받는다.
- 게임 데이터 처리를 서버로 옮긴다.
- 첫째 클라이언트의 역할을 서버가 한다.
- 게임 시작시, 서버에서 프레임 인터벌이 시작된다.
- 서버 상에서 공이 충돌시, 모든 클라이언트에 공 데이터를 보낸다.
- 공 움직임 보정
- 클라이언트에 따라서 공의 속도가 다르다.
- 클라이언트
- 클라이언트는 플레이어와 관전자로 나뉜다.
- [ ]
- …
- 클라이언트는 충돌지점에 도달했을때 서버에서 충돌값을 받았는지 확인한다.
- 값을 받지 못했다면 기다린다.
- 값을 받았다면 값을 공에 적용시킨다.
- 유저는 블록을 움직인다.
-
소켓에서 보내는 데이터를 직렬화한다.
- 모든 클라이언트에서 보이는 공의 위치는 큰 차이가 있지 않다.
- 약간 느리거나 약간 빠른 정도로 차이가 난다.
- 이상적인 상황은 저장하고 있는 소켓 순서로 빠른 정도이다.
- 간극을 줄일 수 있는 방법은 벽에 부딪힐 때마다 첫째가 공의 데이터를 보내고, 다른 클라이언트들이 이를 받는 것이다.
- 충돌 데이터를 첫째에서 보내는 것이 아니라 서버에서 보내면 더 빠르다.
- 첫째는 화면을 그리고, 소켓 메세지를 서버에 보내야 하므로 시간이 걸린다.
- 화면은 프레임이 존재한따.
- 캔버스에서 애니메이션을 동작시키는 방법은 물체의 변화값을 한 프레임을 그릴때마다 더해주는 방식이다.
- 만약에 클라이언트의 프레임이 어느 간격으로 그려지는지 알고, 서버에서도 똑같이 데이터로 가지고 있을 수 있다면 공의 위치를 서버에서 계산하여 보내주면 된다.
- 서버에서 게임이 운영된다면 여러 문제가 해결된다.
- 게임 플레이에 대한 테스트가 가능해진다.
- 클라이언트에 대한 의존성이 낮아지기 때문에 서버는 별도로 테스트를 진행할 수 있다.
- 서버에서 인터벌을 사용하여 클라이언트와 동일하게 프레임을 구현하므로 이돝
- 클라이언트의 키 입력 처리를 할 수 있다.
이동 키 입력에서 전송받는 데이터를 줄일 수 있는 방법은 on, off를 받는 방법이다.
- 클라이언트에서는 반복되는 키 입력을 막아주어야한다.
-
서버에서는 공과 마찬가지로 블록의 변화값을 가지고 있는다.
- 2차원 벡터를 각도와 속도로 변환하는 방법
- 벡터의 크기 :
const magnitude = Math.sqrt(x * x + y * y); - 벡터의 각도 :
const angleInRadians = Math.atan2(y, x); const angleInDegrees = angleInRadians * 180 / Math.PI;- 벡터의 속도 :
const speed = magnitude / deltaTime; deltaTime: 벡터의 이전 위치에서 현재 위치까지 걸린 시간
23.04.16
- socket.io-mock-ts
- socket mock 라이브러리로 테스트할 수 있다.
- socket 객체를 생성하고, clientMock 메서드를 사용하여 클라이언트 동작을 할 수 있다.
- 아래의 예시는 socket.io 공식 문서에 나온 테스트 방법이다.
- 서버를 생성하고, 서버 소켓과 클라이언트 소켓 객체를 생성한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
const { createServer } = require("http");
const { Server } = require("socket.io");
const Client = require("socket.io-client");
describe("my awesome project", () => {
let io, serverSocket, clientSocket;
beforeAll((done) => {
const httpServer = createServer();
io = new Server(httpServer);
httpServer.listen(() => {
const port = httpServer.address().port;
clientSocket = new Client(`http://localhost:${port}`);
io.on("connection", (socket) => {
serverSocket = socket;
});
clientSocket.on("connect", done);
});
});
afterAll(() => {
io.close();
clientSocket.close();
});
// ...
});
- invite game
game에 invited를 관리하지 않으면 game을 제거할 때, invitedUser를 제거할 수 없다. invited를 수락하고 게임을 시작하는 기준을 무엇으로 두어야 하는가?
게임에서 player를 가지고 있다. 만약에 유저가 게임방에 초대되면 gameModel의 players에 추가된다.
게임방은 대기열과 초대로 이루어진다. 대기열은 순서가 필요하므로 array에 저장되고, 초대는 순서가 필요없으므로 따로 관리되지 않는다.
invitedUser를 players에서 관리하면 연결 상태를 확인하게 되므로 추가하면 안된다. 유저가 이미 초대받았음을 확인하는 방법이 필요하다.
23.04.15
- 게임 플레이어는 온라인 상태이어야 한다.
- 유저가 온라인 상태인지 확인할 수 있는 방법
- 온라인이란 socket이 연결된 상태를 말한다.
- socket 연결을 관리하는 곳은 app.gateway이다.
- app.gateway에 유저 소켓을 저장하고, 소켓이 있다면 온라인이다.
23.04.14
- 채팅 api를 구현한 다음, 게임 api를 구현하고 있다.
- 유저 데이터에 대하여 몇가지 생각해 볼 사항이 있다.
- 채팅에서 구현된 메서드로 게임에 필요한 유저 데이터를 가져와도 되는가?
- 채팅과 게임이 가지고 있는 유저 데이터 타입이 같아야 하는가?
- 채팅에 있는 메서드를 사용하면 빠르게 개발할 수 있다. 하지만 게임이 채팅에 의존하게 되기 때문에 문제가 된다.
- 유저 데이터를 관리하는 레이어를 채팅과 게임의 상위에 둘 수 있다. 이 상황에서는 하나의 유저 객체에 채팅과 게임 데이터가 존재하게 된다. 유저 객체가 커져서 복잡해질 수 있으므로 나누는 것이 좋다.
- 채팅과 게임에서 요구하는 유저 데이터는 다르다.
- 데이터가 필요할 때는 DB에서 데이터를 가져와야 한다.
- prisma로 원하는 데이터를 필요한 부분만 가져올 수 있다.
23.04.13
- 채널 데이터를 서버 메모리에 저장하여 사용하고 있다.
- 서버가 시작되는 순간에 데이터 베이스를 읽어온다.
- 데이터들을 서버 메모리에 저장한 상태에서 클라이언트의 GET 요청을 처리했다.
- 생각해보니 이런 방법을 사용할 필요가 없다.
- GET 요청이 실시간으로 이루어지지 않아도 되기 때문이다.
- 실시간 데이터 통신이 필요한 부분은 소켓으로 처리된다.
- 어느 유저가 메세지를 보내면 채널에 있는 모든 유저의 소켓 메세지 이벤트를 발생시킨다.
- 소켓으로 메세지 내용을 전달받기 때문에 클라이언트는 서버에 GET 요청을 보내지 않는다.
- 그러므로 API의 모든 GET 요청은 DB에서 데이터를 가져와야한다.
23.04.12
- REST API에서 보낸 수정 요청에 대한 응답
- PUT, PATCH로 수정된 내용을 응답으로 보내주어 수정할 수 있도록 한다.
- socket message code
- 클라이언트는 소켓 이벤트 리스너를 등록하고 기다린다.
- 클라이언트는 이벤트 이름만으로 수정사항을 파악하기 힘들다.
- 예를 들어, 채널에 대한 공지로 ‘notice’ 이벤트가 있다.
- ‘notice’ 이벤트는 한정되지 않고, 다양한 용도로 사용된다.
- 유저의 채널 참여, 퇴장, 차단, 강퇴 등의 이벤트를 ‘notice’로 처리할 수 있다.
- 이 상황은 두가지 방법으로 해결된다.
- 이벤트 이름을 길고 상세하게 정한다.
- 이벤트 data에 상황에 대한 code를 포함시킨다.
- 첫번째 방법은 문자열로 다양한 이벤트를 관리해야한다는 점에서 제외된다.
- 두번째 방법은 HTTP의 status code와 비슷하게 구현되어 개발자가 이해하기 쉽다.
- 그러므로 두번째 방법을 사용한다.
23.04.11
- 서버와 클라이언트의 연결 상태
- ping 간격 3초
- 서버가 ping을 클라이언트에게 보내면 응답으로 pong을 받는다.
- 서버가 응답을 받지 못하는 경우 처리 방법이 필요하다.
- 여러 상황이 존재한다.
- 게임 대기열에 있는 경우
- 큐 제거
- 게임 설정방에 있는 경우
- 플레이어가 나감으로 처리
- 게임 플레이하고 있는 경우
- 플레이어가 나감으로 처리
- 게임 대기열에 있는 경우
- notice
- code를 추가
- 대상에게 해야하는지
- 예를 들어, mute
- mute를 데이터 베이스에서 제거
- socket data 정리
23.04.10
유저의 게임 대기열에 추가 플로우
- 유저가 대기열에 들어가겠다는 요청을 보낸다.
- 대기열을 확인하여 들어갈 수 있는 방이 있는지 확인한다.
- 방에 들어갈 수 없는 조건은 다음과 같다.
- 유저가 서로 차단한 상태
- 방의 모드가 다른 경우
- 만약에 방이 없다면 새로운 방을 만들고, 있다면 들어간다.
-
초대방인 경우에는 방이 대기열에 존재하지 않는다.
- 대기열에 유저가 들어간 후에는 인터벌이 시작된다.
- 네트워크 연결 상태 확인, 방이 생성된 시간 체크
- 네트워크 연결 상태를 확인하는 것은 게임이 종료되기 전까지 지속된다.
-
방이 생성된 시간을 체크하는 것은 player2가 들어오기 전까지 지속된다.
- 클라이언트의 네트워크 상태 확인
- ping 메세지를 socketio로 보낸다.
- 클라이언트는 pong 메세지를 서버에 보내어 이상이 없음을 알린다.
- 서버는 클라이언트들의 pong 메세지를 저장한다.
- 다음 ping 메세지를 보내기 전에 해당 클라이언트가 pong을 보냈는지 확인한다.
- 만약에 pong을 보내지 않았다면 네트워크에 문제가 생긴 것이다.
prisma migration
- 스키마가 변경되어도 이전 데이터를 유지해야한다.
- prisma로 postgresql DB를 조작하고 있다.
-
prisma schema에서 정의한 모델을 바탕으로 실제 DB의 테이블을 생성할 수 있다.
- prisma migrate 동작 원리
- schema.prisma 생성
- draft migration file 생성
- draft migration을 DB schema에 적용
1. schema.prisma 생성
schema.prisma에 수정사항이 발생되었다.
1
2
3
id Int
age Int
name String
2. draft migration file 생성
migration을 실제로 DB schema에 적용하지 않고 초안을 작성한다.
다음 명령어를 실행하면 생성된다.
1
prisma migrate dev --create-only --preview-feature
migration 이름을 입력하면 prisma/migrate/에 sql파일이 생성된다.
prisma가 알아서 sql 파일을 생성해준다.
3. draft migration file을 DB schema에 적용
DB schema를 다음 명령어를 사용하여 적용할 수 있다.
1
npx prisma migrate deploy --preview-feature
23.04.05
- 데이터의 흐름
- DB -> 서버 -> 클라이언트
- 데이터는 다른 레이어로 이동될 때, 형태가 변한다.
-
변형되는 형태에 따라서 정의된 객체가 필요하다.
- 문제는 각각의 타입에서 다른 타입의 메서드를 사용해야하는 경우가 있다.
- 만약에 channel에서 user의 객체를 확인하려면 userId를 받아서 직접 찾고 가져와서 객체를 확인했다.
- 이제는 Channel의 메소드에서 user 객체를 직접받아야 한다.
23.04.04
- 채팅을 구현하며 클라이언트의 데이터를 서버에 저장하고 있다.
- 서버에서 클라이언트의 상태 데이터를 가지고 있다.
- 만약에 서버가 종료된다면 데이터가 사라진다.
- 그러므로 서버가 종료되어도 데이터를 보존하려면 다른 곳에 저장해야 한다.
-
보통 캐시 서버를 사용하지만 이번에는 DB 서버에 저장하는 방식을 사용한다.
- 서버의 데이터와 데이터 베이스의 데이터의 형태는 다르다.
- 차이를 처리하는 방법이 필요하다.
- 서버에서 사용하는 데이터의 인터페이스를 만들면 간극을 메울 수 있다.
- 지금은 Map에 데이터를 저장하여 관리하고 있다.
- Map의 메소드를 가진 클래스를 만들면 코드의 수정없이 데이터 베이스와 연결할 수 있다.
23.04.03
- 서버에서 소켓 테스트하는 방법
- jest에서 제공하는 it메서드를 사용하여 테스트 케이스를 작성한다.
- it 메서드에 title과 callback 함수를 보낸다.
- callback 함수에서 done 파라미터를 받을 수 있다.
- done 파라미터는 함수로써 테스트 케이스를 마치는 위치에서 호출한다.
- 테스트 케이스의 전과 후로 호출되는 메소드가 제공된다.
- afterAll, beforeAll은 describe 스코프 안에서 전후로 한번씩 호출된다.
-
afterEach, beforeEach는 it이 호출되기 전후로 한번씩 호출된다.
- describe로 테스트 카테고리를 나눌 수 있다.
- 카테고리는 전후 처리가 같은 테스트로 묶는다.