기본 콘텐츠로 건너뛰기

RxJS 에서 GameScene 을 구현하고 있다.

결과물은 여기에
http://jsbin.com/wuhega/edit?js,output

Reactive Programming 의 장점+효용이 극대화되는 분야는 아무래도 게임이지 않을까 싶어서 짬을 내어 RxJS로 슈팅게임을 만들고 있다.

  1. 배경+플레이어+총알: https://jsbin.com/worewow/edit?js,output
  2. 1+적+폭파에니메이션: https://jsbin.com/modedic/edit?js,output
  3. 2+coffee+적 움직임등 : https://jsbin.com/yobuka/
대략, 이런 흐름으로 살을 조금씩 붙여가면서 올리고 있는데
어느정도 튜토리얼을 만들려는 생각으로 ECMA6를 가지고 시작을 했지만 {}, (), [] 괄호노동을 통한 손목터널증후근과 코딩하는 시간 만큼 쌍 맞추는 시간이 소모되는 자신을 발견하고 개인의 행복을 위해(...) 다시 coffeescript로 바꿔서 진행하고 있다. 살 것 같다. 아효.

아마도 곧 합쳐질 것 같지만 어느정도 내용물이 만들어졌으니 이것들을 감싸는 껍데기를 어떻게 만들지 코드를 짜면서 고민해보았다.

일단 크게 나누어 보기를
  1. Splash Scene - 한번만 노출. 게임명과 제작자에 대해 알리는 화면
  2. Title Scene - 게임시작을 위해 대기하는 화면
  3. Game Scene - 실제 게임 화면
  4. GameOver Scene - 게임을 더 이상할 수 없는 상황.
이정도로 잡고 1>2>3>4>2>... 의 반복 구조로 설계를 해보기로 했다.

아무런 레퍼런스도 없고 검색해 본 것도 없어서 매우 삽질이 예상되지만 그래도 FRP(Functional Reactive Programming)원칙에 충실하려고 노력했다.

먼저, 화면을 준비한다.
'use strict'
### setup canvas ###
c = document.createElement 'canvas'
document.body.style.margin = 0
document.body.style.height = '100%'
document.body.style.overflow = 'hidden'
document.body.appendChild c
c.width = window.innerWidth
c.height = window.innerHeight
ctx = c.getContext '2d'

캔버스를 만들고 scroll이 되지 않게 margin을 0, 높이를 100%, 그리고 스크롤바도 숨겼다.
캔버스의 크기는 윈도우의 크기만큼 꽉 차게.

두번째로 각각의 Scene을 Observable로 준비한다.
FPS = 16
SplashScene = Rx.Observable.interval FPS
TitleScene = Rx.Observable.interval FPS
GameScene = Rx.Observable.interval FPS
GameOverScene = Rx.Observable.interval FPS
FPS(Frame per Second)를 60frame으로 잡기 위해 1000msec/60frame 한 값이 16.666을 거칠게 16으로 주고 각 Scene 들이 16msec 에 한번씩 canvas 를 그리는 구조를 잡기 위해 위와 같이 설정했다.

이번엔 말 그대로 "껍데기"를 만들 것이므로 최대한 각 Scene들을 단순하게 구현하고자 했다.

시작은 Splash부터
paintSplash = (t)->
  int2hex = (i)-> "0#{i.toString(16)}".toString(16).substr(-2)
  q = int2hex (255-t*3)>0 && 255-t*3 || 0
  ctx.fillStyle = "##{q}#{q}#{q}"
  ctx.fillRect 0, 0, c.width, c.height
  ctx.fillStyle = "#eaeaea"
  ctx.font = "3rem arial"
  ctx.textAlign = "center"
  [x,y]=[c.width/2, c.height/2]
  ctx.fillText "Appsoulute", x, y - 25
  ctx.fillText "games", x, y + 25 if t>150
goSplash = ->
  console.log "enter splash"
  SplashScene
  .takeUntil Rx.Observable.timer 5000
  .subscribe paintSplash, (->), goTitle
goSplash()
각 Scene들은 여기에서 크게 다르지 않은 구조다.
  1. 해당 Scene을 subscribe 하도록 하는 함수를 만들어 호출할 것이며 (goSplash())
  2. subscribe 시 canvas에 요소들을 그릴 것이며 (paintSplash)
  3. 각 Scene을 종료하는 제약조건을 가질 것이며 (takeUntil)
  4. 3의 제약조건으로 종료되고 난 뒤 다음 Scene을 생성할 함수를 호출할 것이다. (goTitle)
이게 전부고 큰 뼈대라고 보면 되겠다.
SplashScene 을 Rx.Observable.interval FPS 로 정의했다.
FPS값, 즉 16msec (0.016초) 마다 subscribe에선 주기적으로 증가하는 값을 받을 것이며 이는 paintSplash 에 산술적으로 증가하는 정수값을 전달할 것이다.
Rx.Observable.interval(100).takeUntil(Rx.Observable.timer(5000)).subscribe(t=>console.log(t))
하고 콘솔에 한 번 입력해보자. 5000msec(5초)동안 0부터 100msec 마다 1씩 값이 증가하면서 출력되는 것을 확인할 수 있다.

paint시리즈의 시작은 canvas 영역만큼 페인트를 확 들이 붓는 것부터 시작하는데 이건 매 frame (정확히 말해 각 Stream의 interval)마다 딱 한번씩만 실행한다.
paintSplash의 인자 t를 기억하자. 애니메이션을 할때 키가 되는 값이다.
  int2hex = (i)-> "0#{i.toString(16)}".toString(16).substr(-2)
  q = int2hex (255-t*3)>0 && 255-t*3 || 0
  ctx.fillStyle = "##{q}#{q}#{q}"
  ctx.fillRect 0, 0, c.width, c.height
배경색을 지정할때 "#000000"식으로 RGB를 표현하는데
255부터 거꾸로 세어서 #ffffff , #fefefe , #fcfcfc .... #020202, #010101, #00000 에 이르도록 배경을 처리하였다.
"#{variable}"은 ECMA6의 `${variable}`과 완전히 같다.
fillStyle 에서 색을 지정하고 fillRect로 0,0 에서 canvas 높이와 너비 만큼 칠한다.

폰트의 경우
  ctx.fillStyle = "#eaeaea"
  ctx.font = "3rem arial"
  ctx.textAlign = "center"
의 세가지 속성을 사용했다.
fillStyle로 글씨에 사용할 색을 변경하고
font로 폰트의 크기(3rem: 해당페이지 기본 폰트에 x3배)와 이름(arial: 기본폰트)을 지정한 뒤
textAlign로 가로 정렬을 잡아준다.
textAlign은

이와 같은 특성을 가지고 있어서 매우 유용하다. start, end, left, center, right 속성을 바꿔가면서 비교해보자.
다시, subscribe 쪽으로 돌아와서 보면
.takeUntil Rx.Observable.timer 5000
takeUntil는 인자로 들어오는 Observable에 Stream이 발생할 때 해당 Observable을 종료하는 Operator이다.
subscribe가 시작되고 5000msec(5초가 지나면) takeUntil이 complete 를 발생시켜 Observable을 중단하고 subscribe에 지정한 complete 콜백을 실행한다.

.subscribe paintSplash, (->), goTitle

subscribe는 세개의 인자를 갖는데 각각 next, error, complete 이다. 세번째 인자인 goTitle 을 지정하여 결과적으로 5초 지나고 난 뒤 해당 함수로 이동한다.
https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/operators/subscribe.md
내용을 참조하여 사용예를 익히자.

Splash가 5초 지나고 Title을 부르면 나머지는 다 비슷한데 시간을 기다리는 것이 아니라 클릭을 하면 다음으로 넘어가도록 구현한다.
goTitle = ->
  TitleStream = Rx.Observable.fromEvent c, "mouseup"
  TitleScene
  .takeUntil TitleStream
  .subscribe paintTitle, (->), goGame
goSplash와 거의 같은 데 제약 조건이 canvas에서 mouseup 이벤트를 fromEvent를 통해 스트림으로 받았다.
그러면 Title Scene은 마우스 클릭이 있기까지 Observable이 유효한 상태이고 마우스를 눌렀다 떼는 순간 complete을 하고 goGame으로 향한다.
goGame = ->
  console.log "enter game"
  GameStream = Rx.Observable.fromEvent document, "keydown"
  .bufferCount 3
  GameScene
  .takeUntil GameStream
  .subscribe paintGame, (->), goGameOver
goGame에선 제약 조건을 다르게 해봤는데 bufferCount 라는 Operator를 사용했다.
bufferCount는 buffer의 일종으로 해당 Observable이 3번 일어날 때 활성화가 되어 takeUntil을 실행한다.
그렇다. 이는 Life 카운트를 구현하기 위한 것으로 적과 충돌 같은 사망 스트림이 3번 발생한 경우로 goGameOver로 향하게 하는 장치라고 보면 되겠다.
구현을 단순화 하기 위해 keydown을 통해 세번 아무 키나 누르면 goGameOver 로 가게 했다.

마지막으로 GameOver를 보자.
사실상 GameOver는 Splash랑 거의 같다.

goGameOver = ->
  console.log "enter gameOver"
  GameOverScene
  .takeUntil Rx.Observable.timer 3500
  .subscribe paintGameOver, (->), goTitle

3500msec(3.5초)만큼 기다리고 그저 goTitle로 향한다.

이와 같이 하나의 루프가 완성되었다.
더 좋은 방법이 있을 법도 하지만 거꾸로 하나하나 Scene을 만드는 것도 꽤 재밌는 작업이었다.
import 나 외부 script 참조를 통해 적절하게 Scene 별로 파일을 나누면 더욱 보기 좋을 것이다.

기실 어떤 프로그램이든 좋은 껍데기 구조는 수익+평판이랑 직결된다. 좀 더 예리하게 다듬어보자.

이 블로그의 인기 게시물

vulcanJS - 10. Posts Update/Delete

마지막으로 수정과 삭제를 구현해보면 목록 조회(List), 상세 조회, 쓰기, 수정, 삭제까지 모든 필요한 요소를 아우를 수 있을 것이다.
감이 좋은 분들은 눈치 챘을지도 모르겠지만 사실 수정이란 건 UI면에서 볼때 이미 양식이 채워져있는 신규 쓰기와 별반 다르지 않다.

먼저 해야할 것은 역시나 Component를 만드는 일이다.
$ vulcan g component
? Package name spectrum-simplebb
? Component name PostsEditComponent
? Component type Class Component
? Register component Yes
   create packages/spectrum-simplebb/lib/components/PostsEditComponent.jsx
 conflict packages/spectrum-simplebb/lib/components/index.js
? Overwrite packages/spectrum-simplebb/lib/components/index.js? overwrite
    force packages/spectrum-simplebb/lib/components/index.js PostsEditComponent를 만들었다.
route도 만들자. /posts/edit/:id 이렇게 경로를 만들면 좋겠다. 그러고보니 이전 글에서 만든 상세보기도 /posts/view/:id 형식으로 만들껄 그랬다.
$ vulcan g route
? Package name spectrum-simplebb
? Route name postsEdit
? Route path /posts/edit/:_id
? Component name PostsEditComponent
? Layout name
 conflict packages/spectrum-simplebb/lib/modules/routes.js
? Overwrite packages/spectrum-simplebb/lib/modules/routes.js? overwrite
   …

VulcanJS 특징 번역

VulcanJS 특징http://docs.vulcanjs.org/features.html 의 내용입니다. GraphQL 스키마 생성 Vulcan은 SimpleSchema JSON 스키마를 기반으로 컬렉션의 GraphQL 스키마를 자동으로 생성합니다.
이렇게하면 두 가지 형식으로 스키마를 두 번 지정할 필요가 없습니다. 이 기능은 완전히 선택적이며 필요에 따라 수동으로 스키마를 지정할 수도 있습니다.

자동 생성 폼 Vulcan은 스키마를 사용하여 클라이언트 측 폼을 생성하고 적절한 Apollo Mutation을 통해 제출을 처리합니다.
예를 들어 하나의 동영상을 편집하기위한 양식을 표시하는 방법은 다음과 같습니다.
<VulcanForm
  collection={Movies}
  documentId={props.documentId}
  queryName="moviesListQuery"
  showRemove={true}
/> queryName 옵션은 작업이 완료되면 자동으로 업데이트되는 쿼리를 VulcanForm에 통지하는 한편, showRemove 옵션은 "Delete Movie' 버튼을 폼에 추가합니다.
VulcanForm 클라이언트 저장소에 아직 로드되지 않은 경우 수정할 문서를 읽어오기도 합니다.

쉬운 데이터 적재 Vulcan에는 Apollo 데이터를 쉽게 로드할 수 있도록 데이터 로딩 헬퍼 세트로 withList (복수 문서용)와 withDocument (단일 문서용)를 제공합니다.
예를 들어, withList를 사용하여 MoviesList 구성 요소에 모든 동영상을 포함하는 결과를 prop에 전달하는 방법은 다음과 같습니다.
const listOptions = {
  collection: Movies,
  queryName: 'moviesListQuery',
  fragment: fragment,
};
export default withList(listOptions)(MoviesList); …

vulcanJS - 1. vulcan-cli 설치하고 프로젝트 만들기

vulcanJS를 학습하면서 알아낸 점을 기록하는 의미의 튜토리얼을 써본다.

Telescope도 그랬지만 forum 형태의 예제에서 embedly를 사용하는 URL 요소가 글에 들어가는 점이나 일반인들에게는 익숙하지 않은 Markdown 문법 등을 걷어내고 최소형태에서부터 접근해야할 필요성을 느껴서 단순 post만 목록 열람하고 읽고 쓰는 구현을 해보기로 했다.

먼저, vulcanJS를 github를 통해 clone 하고 meteor create --package를 통해 만들수도 있지만 Vulcan-Cli(https://github.com/VulcanJS/vulcanjs-cli)가 있어서 이걸 일단 사용해보기로 한다.

대충 내용을 읽어보니 Rails 생각이 나기도 하고 재밌어 보인다.
npm install -g vulcanjs-cli 혹은 meteor 에서만 한다면
meteor npm install -g vulcanjs-cli 이런 식으로 설치하고 쓰면 된다.
npm 에 global로 설치한 첫번째 경우 기준으로 진행해보겠다.
설치하고 난 뒤 vulcan 을 실행해보면
$ vulcan
vulcan usage:
Synopsis
  vulcan <action> <object> <...>
    <action>   Operation to perform
    <object>   Asset type (contextual to action)
    <...>      Parameters. If not provided, interactively entered
Project initialisation
  vulcan create <appName>
  vulcan init <appName>
Assets creation
  vulcan (generate|g) package <packageName>
  vulcan (generate|g) model <packageName>…