오픈소스로 js, nodejs 익히기 part1

오픈소스로 js, nodejs 익히기 part1

나는 2017년 7월부터 웹 프로그래밍을 시작했고, 처음 자바스크립트를 배우기 시작하면서 그것을 익히기 위해 여러 오픈소스 게임을 분석해서 내가 생각하는 구조로 포팅하고 개선시키는 작업을 해 보았다. 그 과정에서 배운점을 적어보려고 한다.

  1. 자바스크립트 강의 듣기

    일단 나는 자바스크립트를 전혀 몰랐기 때문에 생활 코딩에서 자바스크립트 강의를 빠르게 들으며 개념을 익혔다. 이때 그냥 내용 자체를 익힌것이 아닌 내가 기존에 알고 있던 자바나 C++과 비교하며 어떤점이 다른지 비교해가면서 차이점을 생각해가며 익혔다.

  2. 오픈소스 테트리스 분석하기

    테트리스 게임을 처음으로 선택한 이유는

    • 일단 내가 테트리스 게임을 좋아한다.

    • 이미 남이 만들어놓은 소스가 굉장히 많기 때문에 여러 소스를 비교해보고 내가 이해할 수 있고 적당한 사이즈의 소스를 찾기 쉬웠다.

      적당한 크기의 테트리스소스를 찾았고, 그것을 메소드 하나하나 분석하면서 어떻게 돌아가는지 머리속에 그려보고, 또 log를 찍어보며 어떻게 돌아가는지 추적해가며 구조를 파악하였다.

      테트리스 게임의 구조

    • play 라는 main loop에서 0.4초마다 한번씩 테트리스 로직을 돌려서 gameover인지 체크한다.

    • 테트리스 로직에서는 board와 block을 체크하면서 block이 아래로 내려갈 수 있는 상황인지, 더이상 못내려가고 그 자리에서 멈춰야 하는 상황인지, gameover 상황인지를 체크한다.

    • 테트리스 로직에서 gameover라는 판정이 나지 않으면 redraw에서는 board와 block의 데이터를 바탕으로 dom element들을 다시 생성하고 body를 비우고 그것을 추가한다.

      루프를 돌며 update -> draw의 형태로 구성되어 있었다.

      이렇게 파악한 구조를 바탕으로 나에게 맞게 변경하였다.

    1. 일단 나는 로직과 뷰가 붙어있는 것이 마음에 안 들었다. 그래서 뷰와 로직을 각각의 모듈로 분리하였다.

    2. 나는 main loop에 모든것이 다 들어있는 것이 마음에 안들었다. 그래서 tetris game ( main loop, key control ) 과 block 부분을 각각의 모듈로 분리하였다.

    3. 뷰 모듈과 block 모듈을 tetris game 모듈이 가지고 있는 형태로 바꾸어서 tetris game의 main loop을 실행시키는 형태로 변경하였다.

    4. 뷰와 로직이 교환하는 데이터는 game board 전체로 하였다. 이때는 어떻게 해야할지 몰라서 로직에서 모든 데이터를 board에 적용 시키면 그것을 뷰가 받아서 그려주는 방식으로 하였다.

      이렇게 하니 기능별로 모듈이 나뉘어져서 오류가 발생했을 때 어느부분을 고쳐야 할지도 눈에 잘 보였고 구조도 머리속에 잘 그려졌다.

      구조를 변경하고 나니, dom으로 element를 만들고 css를 적용시키는 작업이 나에겐 익숙하지 않고 비효율적으로만 보였다. 매회 만들고 부수고 만들고 부수는 작업이라니!

      그래서 p5.js란 라이브러리의 사용법을 익히기고 그것을 적용해보았다.

      p5.js란?

    • processing을 기반으로 만들어진 라이브러리로서 dom을 조작하는 것이 아닌 canvas를 생성하고 그 위에서 사용자가 원하는 것들을 그려낸다.

    • setup 함수에서는 사용자가 원하는 canvas를 생성하고 기타 게임시 시작되기 전에 해야할 일들을 처리할 수 있다.

    • draw에서는 게임이 진행되면서 그려줘야 할 것들을 여기서 처리한다. 내부적으로 loop이 돌고있으며 noloop메소드를 setup에 추가하면 loop이 돌지 않고 redraw라는 메소드를 호출해주면 draw가 내부적으로 실행된다.

    • 테트리스로직에서 board 데이터와 block 데이터가 오면 그것들의 좌표를 이용해서 draw_block, draw_board와 같은 메소드들을 만들어 각각을 그려주는 부분을 만들고 그것을 draw 메소드에서 실행시켜서 화면에 그려지도록 하였다.

      이렇게 canvas로 그려내니 dom을 조작하는 것도 없어지고, css가 깨져서 여러번 수정해야 할 일도 줄어들어 프로토타입 수준의 게임을 만드는 것이 빨라졌다. 그리고 뷰와 로직이 분리되어있어서 dom element를 생성하는 방식에서 canvas에 그리는 방식으로 바꾸는 것이 수월하였다.

      첫번째 완성 ( 오프라인 1인용 테트리스 )

      나의 목적은 멀티플레이 테트리스를 만들기여서 이 다음으로 nodejs 공부에 들어갔다. 물론 자바스크립트와 마찬가지로 nodejs는 전혀 사용해본적이 없었다.

    1. nodejs 강의 듣기

      이것도 마찬가지로 생활코딩의 강의를 참고하였다. 이전에 서버쪽 프로그래밍을 해본적이 없어서 비교할 대상이 없었지만 기본 철학을 익히고 사용 방법만 익힌 후 내가 하고자하는 것에 필요한 부분만 개념을 익혀서 사용하기로 하였다.

      자바스크립트는 원래 웹 브라우저에서만 지원하는 프로그래밍 언어였다. 하지만 구글에서 크롬 웹 브라우저를 발표하면서 브라우저의 성능을 높이기 위해 V8엔진을 만들고 그것을 오픈소스로 공개하였다.

      V8이 공개되면서 자바스크립트가 웹 브라우저 뿐만아니라 V8엔진이 존재하는 다른 시스템에서도 돌아갈 수 있게 되었고 non-blocking I/O와 event-driven, V8엔진을 조합하여 nodejs라는 기술을 만들어 내었다.

      따라서 서버측에서도(웹 브라우저가 없는 환경에서도) 자바스크립트로 서버사이드 프로그램을 만들 수 있게 된 것이다.

    2. nodejs를 사용한 작은 프로그램을 분석 해 보기

      나는 nodejs의 기본 구조 및 스타일을 알지 못했다. 따라서 nodejs로 작성된 다른 프로그램을 분석 하면서 기본 스타일과 사용법을 익혔고 그것을 내 테트리스 게임에 적용하였다.

      mutiplay flappy bird

      readme.md 파일을 읽어보면 nodejs, socket.io, p5.js를 사용하였다고 하였다. 그리고 코드를 살펴보니 socket 관련 코드들이 있었고 이것을 익힐 필요가 있다고 생각하여 socket.io을 먼저 분석해보고 소스 분석에 들어갔었다.

      websocket과 socket.io 관련 글

      웹이 처음 나왔을 때는 사용자와의 상호작용이 큰 부분이 아니었지만 웹 기술이 발전 해 가면서 상호작용에 대한 비중이 커졌고 수요도 많아져서 웹 브라우저와 웹 서버사이에 더 자유로운 양방향 메세지 송수신 방식이 필요했고 그래서 HTML5의 표준의 일부로 websocket의 일부가 등장 했다고 한다.

      하지만 websocket을 이용하기엔 복잡하고 까다로웠다 그래서 나온것이 socket.io이며 이것은 자바스크립트를 이용해여 브라우저의 종류에 관계없이 실시간 웹을 구현할 수 있도록 한 기술이다. 개발자가 socket.io에 있는 각 기술들을 이해 못해도 이에 관계없이 사용 가능하다 ( 캡슐화 짱! )

      Flappy bird 코드 분석

    • server.js 에서는 socket.on을 통해 클라이언트에서 들어온 이벤트들을 처리하였다. user가 새로 들어오거나, user가 화면이나 버튼을 클릭하거나, disconnect되었을 때에 그에 맞는 데이터가 socket을 통해 들어오고 그것을 서버쪽 로직에서 처리한다.

    • server.js에서는 gameState객체에서 게임의 전체적인 데이터 처리와 로직 처리를 한다. 게임 화면을 초기화하고 mainloop을 통해서 플레이어의 객체를 돌며 플레이어를 움직이게 하고 유저의 in & out을 처리한다.

    • 클라이언트에는 sketch.js가 존재한다. 여기서는 p5가 적용되어있으며 서버에서 온 데이터(state, position)를 바탕으로 화면을 그려낸다.

      이렇게 분석하고 나니 큰 흐름은 nodejs 서버가 돌아가면서 socket으로 클라이언트와 서버가 데이터를 일정한 주기 또는 이벤트에 따라 주고 받고 그것을 모든 클라이언트가 공유하도록 server가 sync를 맞추는 느낌이었다.

    1. 이것을 나의 테트리스 게임에 적용시켜 1인용 온라인 게임으로 먼저 만들어 보았다.

      • 기본 게임 로직을 서버로 옮긴다.

      • 클라이언트는 첫 접속시 서버와 socket으로 메세지를 주고 받으며 연결에 필요한 데이터를 주고 받고 연결을 완료시킨다. 아래 그림은agar.io 소스에서 따온 부분이다. flappy bird이외에 이 소스도 분석해서 데이터를 어떻게하면 더 효율적으로 주고 받을지 분석하였다.

      • 서버에서는 게임이 시작하면 메인 루프가 0.4초마다 한번씩돌아간다.

      • 로직과 뷰가 주고받던 데이터가 gameboard와 block이었고, 로직에서 게임의 상태, hold block next block, score등을 가지고 있었기 때문에 그 데이터들을 gameState라는 객체에 담에서 클라이언트로 전송하였다.

      • 클라이언트는 해당 데이터를 받아서 draw_holdblock, draw_nextblock등 각각에 맞는 메소드에 데이터를 보내주고 draw 함수에 의해 이 모든 것들이 화면에 그려진다.

      • 또한 클라이언트는 key event가 발생하면 그 정보를 서버로 전송하고 움직임을 받아서 해당 블록을 움직이는 로직이 실행되도록 하였다.

      • nodejs에서는 require과 module.export를 통해서 모듈 패턴을 구현할 수 있었다. 그러나 이것을 nodejs가 아닌 클라이언트 환경에서 실행시키기 위해서는 이것을 브라우저에서 돌리기위한 작업이 필요하였다.

      • 이 작업을 도와주는 툴에는 browserify와 webpack이 있다. 이 둘중 browserify를 선택하였다. 그 이유는 webpack을 사용하기엔 내 게임이 너무 작았고 그에비해 webpack의 기능은 너무 많고 복잡했다. browserify는 내 게임과 같은 작은 서비스에서 사용하기 적당했다.

      • browserify를 하니 p5가 작동하지 않았다. p5 github의 issue에서 보니 나와 같은 오류가 발생한 사람들이 있었고 browserify후 동작하게 하려면 전역 객체인 p5를 통해 새로운 p5 객체 (new p5 )를 만들어 주면 해결 된다는 글이 있어서 그렇게 해결하였다.

      두개의 소스를 분석해서 nodejs가 클라이언트와 데이터를 주고 받으려면 어떻게 해야하는지, nodejs의 스타일은 어떠한지 등을 분석해보고 그것을 내 게임에 적용시켜 보았다.

      이제 다른 사람의 소스를 읽고 구조를 파악하고 나에게 맞게 포팅하는 작업이 어떤 것인지 감이 왔다.

    2. 1인용 온라인 게임을 다인용 온라인 게임으로 만들어 보기

      이제 멀티플레이를 위해서 각 클라이언트는 자신 이외에 다른 클라이언트들의 게임 상황을 똑같이 그려주는 일을 하고자 했다.

      그러나 막막했다. 각 클라이언트가 다른 클라이언트의 상태를 계속 동기화 하기 위해서는 어떤 클라이언트가 이벤트를 발생시킬 때 마다 그 내용을 전달받아서 그려줘야 했다.

      기존에는 board와 block등등 로직과 관련된 데이터들을 통째로 클라이언트와 주고 받았기때문에 이벤트가 발생할 때 마다 그것을 다른 클라이언트들 전부에게 보내준다는 것은 말도 안돼게 비효율적인 일이었다. 엄청난 트레픽이 발생하겠다는 생각이 들었다.

      그래서 다른 것들을 만들기 위해 다른 소스를 분석하다가 OT라는 기술이 있다는 것을 알아내었다.

      OT는 operation transformation이라는 말의 줄임말로, 핵심 내용은 한 클라이언트가 이벤트를 발생시키면 그것의 ‘변경사항’만을 어떤 객체의 형태로 만들고 그것을 다른 클라이언트에 전달하여 그 이벤트와 같은 행동을 하는 것이다. 그리고 서버는 각 클라이언트가 만들어낸 해석에 불일치가 발생하는지 확인한다.

      즉 테트리스에서 생각 해 보자면 어떤 플레이어가 블록을 움직이거나 회전시키면 그 움직임을 action이라는 객체에 담아서 서버로 전송한다. 그러면 그 내용을 다른 클라이언트가 받아서 각 클라이언트가 이벤트를 발생시킨 클라이언트 객체에서 같은 일을 하게 함으로써 각 클라이언트는 다른 클라이언트의 상태를 계속해서 동기화 하는 것이다.

      이것을 하려면 서버에 있던 로직을 클라이언트에도 옮기고, 각 클라이언트에서 이벤트가 발생하면, 서버는 그것을 다른 클라이언트에 전달하고, 그 이벤트를 서버 자체에서 해당 클라이언트 객체에 적용시켜서 그 이벤트가 타당한지 적용시켜봐야 했다.

      따라서 클라이언트에서도 로직을 가지고 key event나 loop가 돌 때 마다 해당 데이터를 json 형식의 객체로 만들어서 서버에 보내주었고 모든 클라이언트가 다른 클라이언트의 화면을 공유하게 만들었다.

      그런데 문제점이 생겼다. 4명 이상의 사람이 들어오게 되면 화면이 오른쪽으로 넘어가버려서 게임 플레이를 진행하기 힘들었고, 사람이 무한히 많아지면 한 게임에 대해 너무 많은 데이터가 오고가며 트레픽이 증가하였다.

      따라서 socket의 channel을 room 처럼 생각하여 room manager 구조를 구현하였다. 클라이언트가 접속하면 room mananger는 room을 배정 해 주고, 클라이언트가 4명이 모이면 game start 버튼을 활성화 시킨다. 클라이언트중 한명에 game start 버튼을 누르면 모두가 동시에 game이 시작된다.

      멀티플레이

      • 대기화면

      • 플레이 화면

      소스코드

배운점

  • javascript와 nodejs에 대한 이해도가 높아졌고

  • 객체화 모듈화 이벤트화를 적용하려고 노력하였으며

  • 네트워크에서 데이터 교환시 어떤 방식이 효율적인지 배울 수 있었으며

  • 계속 해서 개선시키다보니 리펙토링 할 부분이 계속 보여서 내 코드를 개선해 나가는 것을 배웠다.

  • 다른 사람의 소스를 읽고 내가 필요한 부분만 추출해서 내 서비스에 적용시키는 것을 배웠다.

  • 다른 사람의 소스의 크기가 크더라도 분석이 가능해졌다.

  • 이 모든 것들이 한번에 척척척 한 것이 아닌, 어느 부분이 문제가 있었고, 그 부분을 해결하려면 어떻게 해야하고, 그 해결하는 과정을 작은 게임으로 나누어서 진행하는 것을 배웠다.

  • 어떻게든 찾으면 찾아진다.

댓글

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×