undefined

bokuweb.me

cocos2d-JS Liteとenchant.jsのパフォーマンスを比較してみた

最近enchant.jsとcocos2d-JSの両方使ってるんだけど。cocos2d-JSのほうが比較的さくさく動くと感じたので実際どれくらい差があるのか比較してみた。

比較対象は以下

cocos2d-JS Lite v3.5
enchant.js v0.8.2

結果は以下のページを参照。グラフはc3.jsで書きました。スプライト数をどんどん増やしていってFPSがどう変化するか比較しました。高負荷環境においてcocos2d-JSはenchant.jsの倍程度のFPSがでていました。cocos2d-JSの方がenchant.jsより学習コストも高く、小回りが効かない印象がありますがパフォーマンスは出やすいようです。自分はenchant.jsも好きなので用途に応じて適材適所で。

Cocosjslite vs enchant by bokuweb

f:id:bokuweb:20150415192403p:plain

比較ソースは以下。
enchant.js(coffee)

cocos2d-JS(JS)

enchant.jsでゲーム内にhtmlを埋め込む方法

以下のようにすることでhtmlがゲーム内に埋め込めます。他のspriteなどと同様に扱えます。 以下の例ではinput要素が1pxずつ右に移動します。

html = new Entity()
html._element = document.createElement 'div' 
html._element.innerHTML = '<input type="text" name="" value="text">'
html.width = 200
game.rootscene.addChild html
html.addEventListener "enterframe", ()->
  @x += 1

はじめて学ぶ enchant.jsゲーム開発

はじめて学ぶ enchant.jsゲーム開発

AngularJS+enchant.js+CoffeeScriptで音ゲーを作ってGitHubPages上で公開しました

これなに?

無料音ゲー flavabeats

github.com

ブラウザ上で遊べる音ゲーです。 ノートと呼ばれるオブジェクトが曲に合わせて落ちてくるのでターゲットに重なるタイミングでキーボードのZ,X,C,V,Bを押していくゲームです。 楽曲はCreativeCommonsのものを使用しています。

なんで作ったの?

Vue.jsやReactやAngularJSやAurelia.JSなどフレームワークが乱立するなか何か一つ使えるようにならないといけない気がしたので勉強用に始めました。(あとcocos2d-JSのAndroidデバッグが嫌になりましたので気分転換に)。最初はAngularJSより学習コストの低そうなVue.jsを触ろうかと思ったのでですが、Vue.jsにはルーティング機能がついていないということで結局AngularJSを触ってみることにしました。(書籍も買っちゃってたし。)

本来ゲームであればenchant.jsでシーンの切替え等を行い、AngularJSなどを使わないってのが普通な気がしますが、今回のような曲選択ページなんかはゲーム側でjavascriptで記述するよりhtmlとして表示してやったほうが簡単だとおもったので・・。実際cocos2d-JSで同じようなメニューページをjavascriptで書くのは少し面倒に感じました。

後はおもけに以下のことを試したみました。

  • webstorage使ってみる(ハイスコアの記録に使っています)
  • GitHubPages上で公開してみる(無料だし独自ドメイン使えるし)

リポジトリ

github.com

はまったこととか、こんな風にやってみましたってこととか

AngularJSは情報が多いとはいえ、やはり手さぐりでいろいろ調べながら試しました。『こうすべきだ!』ってのがあったらご教示いただけると幸いです。

GitHub Pagesで独自ドメインを使用する

以下のページが非常に参考になりました。というかまんまです。

GitHub Pages でWebサイトをホスティングする(独自ドメイン使用) - Think Big Act Local

AngularJSでルーティングする

以下のページが参考になりました。

inter-arteq :: interaction between art and technology » Blog Archive » AngularJSで画面遷移するときに便利なng-view

index.htmlに以下のように記述しておき

<div ng-view id="view"></div>

JS側で以下のように振り分けています。

flavaApp.config ($routeProvider)->
  $routeProvider
  .when '/',
    templateUrl: 'splash.html'
  .when '/select',
    templateUrl: 'select.html'
  .when '/help',
    templateUrl: 'help.html'
  .when '/game/:id',
    controller: 'GameCtrl'
    templateUrl: 'game.html'
  .otherwise
  redirectTo: '/'

また、URLのパラメータを取得するのには以下のページを参考にさせていただきました。

$routeParamsを使用して、URLのパラメータを取得する | 集の一期一会

具体的には

flavaApp = angular.module('flavaApp', ['ngRoute'])

flavaApp.config ($routeProvider)->
  $routeProvider
  .when '/game/:id',
    controller: 'GameCtrl'
    templateUrl: 'game.html'

flavaApp.controller 'GameCtrl', ($scope, $routeParams)->
  console.log $routeParams.id

とするこでhttp://prototype.flavabeats.net/#/game/1にアクセスすることで$routeParams.id1になります。これを使って、ゲームプレイ時はすべてgame.htmlに遷移しつつ、idによってどの楽曲をプレイするか選択しました。

外部ライブラリのコールバックの中等で $scope の値を変更したい場合

以下の記事が参考になりました。

AngularJSでデータバインドが効かないときは $scope.$apply - Yuta Watanabe's Blog

リンク先の記事でも紹介されている、2秒後に$scope.messageを書き換える例ですが以下の記述では動作せず。

function Ctrl($scope) {
  $scope.message = "Waiting 2000ms for update";
    
    setTimeout(function () {
        $scope.message = "Timeout called!";
        // AngularJS unaware of update to $scope
    }, 2000);
    }

以下のように各必要があるようです。

function Ctrl($scope) {
  $scope.message = "Waiting 2000ms for update";
    
    setTimeout(function () {
        $scope.$apply(function () {
            $scope.message = "Timeout called!";
        });
    }, 2000);
    }

HTMLとして文字列をバインドする

以下の記事が参考になりました。

3分で分かるAngularJSセキュリティ - teppeis blog

今回はangular-sanitize.jsを使用しました。 以下のようにレベルをhtmlで表示しています。

html

<span class="level" ng-bind-html="m.levelIcon"></span>

JS側

flavaApp = angular.module('flavaApp', ['ngSanitize'])
flavaApp.controller 'GameInfoCtrl', ($scope)->
  level = 'Level '
  for i in [0..9] when i < $scope.music.level
    level += '<i class="fa fa-star-o level"></i>'
  $scope.music.levelIcon = level

topページでスマホ用にメッセージを出す

性懲りもなくスマホには対応していないので、スマホユーザには非対応メッセージを出すようにしました。 userAgentを判定して、ng-hideng-showで要素を表示・非表示にして対応してます。

html

<div class="row" ng-controller="SplashCtrl">
  <div class="col-md-8 col-md-offset-2 text-center inner" ng-hide="isMobile">
    <a ng-href="#/select"><h1><img src="img/logo.png" ></h1>
      <span class="click-message">Click here to start.</span></a>
  </div>
  <div class="col-md-8 col-md-offset-2 text-center inner" ng-show="isMobile">
    <h1><img src="img/logo.png" class="sp-logo"></h1>
    <span class="mobile-message">Sorry, This site is designed specifically for PCs.</span>
  </div>
</div>

JS側

flavaApp.controller 'SplashCtrl', ($scope)->
  agent = navigator.userAgent
  if agent.search(/(iPhone|iPod|Android|Mobile)/) isnt -1 then $scope.isMobile = on else $scope.isMobile = off

webstorageを使う

こんな感じで保存

storage = localStorage
storage.setItem key, value

こんな感じで読み出し

storage.getItem key

できるっぽいです。簡単。

さいごに

インクリメンタルサーチとかソートとかさくっと実装できて楽しかった。もっと勉強してVue.jsやAurelia.JSも触ってみたい。あと、せっかくなんだからBrowserify使ってみればよかった。

※追記

ゲームへの使用例が以下の記事に掲載されていました。

AngularJSを使用したゲーム開発|1 pixel|サイバーエージェント公式クリエイターズブログ

TimelineFX買ったった!ので、サンプルエフェクトをenchantで表示するまでを記録しておく

ゲームのエフェクトを作るためにTimelineFX買いました。サンプルのスプライトシートをenchant.jsで動かすまでを記録しておきます。

TimelineFX?

http://www.rigzsoft.co.uk/

価格:£29.99

画像から色や速度のなどのパラメータを変更することでいい感じのエフェクトがさくっと作れちゃうEditorです。サンプルが非常に豊富で基本的にはサンプルを元に手を入れたり、参考にすることで効率よく目的のものが作れそうです。どんなサンプルがあるか貼っときます。

Auras and Halos Effects Library for TimelineFX - YouTube

Readouts Effects Library TimelineFX - YouTube

Explosions Library for TimelineFX - YouTube

Lightshows Effects Library for TimelineFX - YouTube

サンプルエフェクトをスプライトシートとして書き出してみる

  • サンプルエフェクトをダウンロードしておく

http://www.rigzsoft.co.uk/effects-libraries/

ここから好きなエフェクトをダウンロードしておきます。

  • 立ち上げてフォルダマークをクリック

f:id:bokuweb:20150207180742p:plain

  • 落としたエフェクトを選択 使いたいエフェクトを選択し「OK」を押す

f:id:bokuweb:20150207181002p:plain

  • 使用するエフェクト選択

f:id:bokuweb:20150207195530p:plain

ここで"Attributes"を変更したりすることでサンプルをカスタマイズできるっぽいんですが、まだよくわかってないので分かり次第記事にします。今回はそのまま使用するため、右クリック->『Animation Properties』を選択。

f:id:bokuweb:20150207200320p:plain

  • スプライトシートにエクスポート 右下のボタンを押すと『Export Options』が表示されるのでOKを押す。

f:id:bokuweb:20150207203150p:plain

  • こんなのができる

f:id:bokuweb:20150207203318p:plain

enchant.jsでクリック時にエフェクトを表示する

enchant.jsでクリック時にエフェクトを表示する

・main.coffee

enchant()
game = new Core(480,480)
game.fps = 60
game.preload("http://jsrun.it/assets/r/K/E/a/rKEaB.png")
game.start()
    
game.rootScene.addEventListener Event.TOUCH_START, (e)=>
  wave = new Sprite(128, 128)
  wave.x = e.x - 64
  wave.y = e.y - 64
  wave.image = game.assets["http://jsrun.it/assets/r/K/E/a/rKEaB.png"]
  game.rootScene.addChild(wave)

  wave.addEventListener "enterframe", ()=>
    if wave.frame < 22 then wave.frame++
    else   game.rootScene.removeChild(@)

デモ

クリックしたらエフェクトが表示されます。

その他

日本語の情報がないのがきついけど、結構好感触。もう少しカスタマイズできるように勉強する。

【メモ】enchant.jsで表示順序を制御する方法

備忘録です。enchant.jsでzindexをどうやって設定するかわからなかったんですが、普通に_styleで設定するみたいです。

15.03.25追記 どうやら現在のverでは下記方法は使用できないようです。 insertBeforeが実装されているため、そちらで順序を制御することになります。

var sprite = new Sprite(4, 16);
sprite.image = game.assets["sprite.png"];
sprite._style.zIndex = 2;
game.rootScene.addChild(sprite);

参考記事

ASCII.jp:enchant.jsで懐かしのインベーダーゲームを作ろう (1/4)|古籏一浩のJavaScriptラボ

100行で書けるブラウザで動作するyoutube音ゲーの作り方

はじめに

【WEBサービス】youtubeを使った音ゲー×タッチタイピングサービスを作ってみた【つくってみた】 - ぼくのかんがえたさいきょうのうぇぶさーびす

上記の記事で書いた「typebeats」のゲーム部を簡素化して説明してみます。 削っていくと100行に入りそうだったので詰め込んでみました。 かなり簡素化していますが、基本的な考え方は共通です。

ゲーム概要

リズムに合わせて降ってくるアイコンをタッチ/クリックするゲームです。 画面をタッチ/クリックするとゲームが開始します。

こいつが

f:id:bokuweb:20150117133504p:plain

こいつに

f:id:bokuweb:20150117133522p:plain

重なるくらいがタッチ/クリックするタイミングです。

スマホでの動作は確認していません。たぶん動かない気がします。

言語はCoffeeScript、フレームワークはenchant.jsです。 触れたことのない方はどっとインストールも参考にしてください。

CoffeeScript入門 (全13回) - プログラミングならドットインストール

enchant.js入門 (全12回) - プログラミングならドットインストール

リポジトリとデモ

リポジトリ

bokuweb/youtube_music_game · GitHub

デモ

http://bokuweb.github.io/youtube_music_game/

準備するもの

今回必要なものは以下のようになります。

  • ブラウザ(chromeを推奨)
  • エディタ(CoffeeScriptのシンタックスハイライトができるものがおすすめ)

用語

項目 説明
ノート リズムに合わせて落下してくるオブジェクト
ラベル 文字を表示する部品
シーン タイトル画面、ゲーム画面、ポーズ画面、ゲームオーバー画面など、「○○画面」と呼ばれる単位 *1
FPS Frame Per Sec 1秒間に何回画面が更新されるか

*1 http://www.atmarkit.co.jp/ait/articles/1304/01/news034.htmlより ここではゲーム画面しかないのでシーンと言ったらゲーム画面のことです。

ゲーム詳細

ノートがタッチ/クリックされるべきタイミングを_timingに格納しておき、この時間から逆算して、ノートをシーンに追加したり、動かしたりさせています。 enchant.jsの機能で指定したFPSの周期で動作させるメソッドを登録できますので、この登録したメソッド内で何を行うべきなのか判定し、実行するという形になります。

ゲームクラス定義部

ここではclass内で共通して参照する変数などを定義しておきます。

class @Game      # ゲームクラスを宣言します。
                 # class名に@を付けることでglobalスコープに配置され、別ファイルから参照することができるようになります。
  YOUTUBE_ID = 'HNYkOJ-T63k' # 再生する動画のIDを設定します。
  _game = null   # echant.jsのゲームコアを格納しておくprivate変数です。
  _yt = null     # youtube API制御classのインスタンスを格納しておく変数です。
  _judge = null  # タイミング判定を表示するラベルを格納しておく変数です。

  # 落下してくるオブジェクトのタッチ/クリックタイミングを定義します。単位は[sec]です。
  # ここに定義してある時間と実際にタッチ/クリックした時間を比較し、「GREAT」、「BAD」などの判定を行います。
  _timing = [6.14,7.486,8.155,9.977,10.377,11.611,12.062,12.765,13.583,13.945,14.223,14.707,15.059,16.241,16.577,17.425,20.186,20.917,21.593,22.313,22.449,23.123,24.297,24.965,25.113,25.464,26.148,26.635,27.294,28.103,30.910,31.601,32.305,33.024,34.054,34.786,35.360,36.140,37.028,38.402,38.829,39.129,40.354,41.051,41.553,42.233,43.043,43.729,44.261,45.705,46.448,47.416,48.407,50.158,51.310,52.363,53.031,54.417,55.288,55.640,56.472,57.190,58.110,59.095,59.648,60.776, 61.993, 62.370, 63.072, 63.808, 64.493, 65.111, 65.688, 66.414, 67.192, 68.891, 69.209, 69.918, 70.056, 71.111, 71.744, 72.428, 72.861, 73.263, 73.755, 74.099, 74.639, 75.090, 75.426, 75.941, 76.472, 76.992]
  _timingIndex = 0  # _timing配列用のindexです
  _status = "stop"  # ゲームのステータスです
  _endTime = 80     # ゲームの終了時間を定義します。
                    # ここでは再生時間が80secを超えたら終了処理に入ります。

コンストラクタ

クラスからnewでオブジェクトを作成した際に、自動的に実行されます。 このサンプルでは一番最後の行でnewしていますのでその時に実行されます。 constructor内では以下の処理を行っています。

  • ゲーム本体の生成
  • FPSの設定
  • 事前にロードするリソースの設定
  • ゲームの開始
  constructor : (parms)->
    enchant()                               # enchant.jsを使うためのおまじないです
    _game = new Core(800, 600)              # ゲーム本体を生成します。Coreの引数はゲームのwidthとheightです。
                                            # ここでは800×600のゲームを生成しています。
    _game.fps = 30                          # frame per sec つまり1秒当たり何回描画するかを設定します。
    _game.preload("icon.png", "shadow.png") # 使用する画像ファイルをpreloadするよう設定しておきます。
    _game.start()                           # ゲームを開始します。
                                            # ただ、いきなりゲームが開始されるわけではなくまずは画像のロードが行われます。
                                            # ロードが完了すると_game.onloadメソッドが呼ばれます。

ロード完了時の処理

ロードが完了すると_game.onloadメソッドが呼ばれます。 onloadメソッド内では以下の処理を行っています。

  • ゲームタッチ/クリック時の処理を登録
  • 動画の埋め込み
  • 判定ラベルの追加
  • 着地ポイントのs追加
    _game.onload = ->                       # 画像等のロードが完了すると実行されるメソッドです

      # ゲーム画面をタッチ/クリックしたときの処理を登録しておきます。
      _game.rootScene.addEventListener "touchstart", (e)->
        if _yt.isReady()  # youtubeプレイヤーが準備できている?
          _game.rootScene.addEventListener "enterframe", _proccesRootSceneFrame # 30FPSすなわち1/30 = 33.3333..ms周期で呼び出されるメソッドを登録しておきます。
                                                                                # 33.3333..ms経過するたびに_proccesRootSceneFrameメソッドがコールされます。
          _status = "playing"   # ステータスをplay中に変更
          _yt.play()            # youtube動画を再生します。

      # 動画を埋め込みます
      video = new Entity()
      video._element = document.createElement('div')
      video.x = 500  # 動画設X置座標
      video.y = 300  # 動画設置Y座標
      # iframeタグを埋め込みます。
      # https://www.youtube.com/embed/HNYkOJ-T63k?enablejsapi=1..のHNYkOJ-T63kが動画IDです。
      # ここを変更することで動画を変更できます。
      # その他詳細はhttps://developers.google.com/youtube/js_api_reference?hl=jaを参照してください。
      video._element.innerHTML = '<iframe src="https://www.youtube.com/embed/'+YOUTUBE_ID+'?enablejsapi=1&controls=0&showinfo=0&autoplay=0&rel=0&vq=small"  width="300" height="200" frameborder="0" id="player"></iframe>'
      _game.rootScene.addChild(video)  # 動画をシーンに追加

      _yt = new Yt() # youube制御インスタンスを生成

      # 「GREAT」などの判定結果を表示するラベルを生成
      _judge = new Label()
      _judge.font = "36px Arial"       # サイズ、フォントを指定
      _judge.x = 100                   # X座標
      _judge.y = 100                   # Y座標
      _game.rootScene.addChild(_judge) # シーンに追加

      # 落下してくるオブジェクトの着地ポイントを示すイメージを設置する
      shadow = new Sprite(80, 80)
      shadow.image = _game.assets["shadow.png"] # 画像ファイルを指定
      shadow.x = 100                            # X座標
      shadow.y = 380                            # Y座標
      _game.rootScene.addChild(shadow)          # シーンに追加

ノート生成部

_isNoteGenerateTiming はノートを作成するかどうか問い合わせるメソッドです。 返り値がtrueの場合ノートを生成するタイミング、falseの場合は生成するタイミングではないことを示します。 動画の再生時間がタッチ/クリックタイミングから1秒減算した値より大きければtrueを返します。 すなわちタッチ/クリックタイミングから逆算して約1秒前にノートを生成しています。

  _isNoteGenerateTiming = ->
    if _timing[_timingIndex]? # 配列_timingにデータが存在するかしらべます。
      if _yt.getCurrentTime() > _timing[_timingIndex] - 1 # タッチ/クリックタイミングの1秒前?
        return true
    return false

_generateNote は引数で指定された番号のノートを作成し、ノートの動作設定、33.3333..ms周期(すなわち30FPS)で呼び出されるメソッドを登録、ノートをタッチ/クリックしたとき呼び出されるメソッドを登録を行います。

  _generateNote = (number)->
    # ノートの生成部
    note = new Sprite(80, 80)
    note.image = _game.assets["icon.png"]
    note.number = number
    note.x = 100
    note.y = -100 # 画面外に配置
    note.timing = _timing[number] #タッチ/クリックタイミングnote.timingに設定しておく
    _game.rootScene.addChild(note) # シーンに追加

    # ノートがどのように動作するのかを記述します。
    note.tl.setTimeBased() # tl.setTimeBased()を実行することでenchant.jsのアニメーションを時間ベースで実行することができます。
                           # デフォルトはフレームベースであるため「何秒間でここからここまで移動」というような処理ができないためです。

    # ノートのY座標を現在の位置(すなわち-100px)から380pxまで```_timing[number] - _yt.getCurrentTime()) * 1000```msecかけて移動するよう設定しています。
    # ```_timing[number]```という目標時間から```_yt.getCurrentTime()```という現在時間の差を求めどれだけの時間で移動すべきかを計算しています。
    note.tl.moveY(380, (_timing[number] - _yt.getCurrentTime()) * 1000)

    # ノートをタッチ/クリックしたとき呼び出されるメソッドを登録を行います。。
    note.addEventListener "touchstart", (e)->
      @clearTime = _yt.getCurrentTime() # タッチ/クリック時の時間をclearTimeとしてnoteのプロパティに登録しておきます。
      @clear = true # クリアフラグをたてます。

    # 33.3333..ms周期(すなわち30FPS)で呼び出されるメソッドを登録
    note.addEventListener "enterframe", ->
      # タッチ/クリックタイミングから1秒以上経過していればノートをシーンより削除します。
      if _yt.getCurrentTime() > _timing[@number] + 1 then _game.rootScene.removeChild(@)

      # クリアフラグが立っていれば
      if @clear
        # ここでノートをタッチ/クリックしたときの処理を記載します。
        # 以下の処理も33.3333..ms周期で実行されるため、33.3333..ms毎にノートを透明にしていく処理とノートを大きくする処理を記述しています。
        @opacity -= 0.2   # 不透明度を減らしていきます。
        @scale(@scaleX + 0.05, @scaleY + 0.05) # 5%ずつスケールアップさせる

        # ノートが完全に透明になったら
        if @opacity <= 0
          _game.rootScene.removeChild(@) # シーンから削除します。

          # ```note.addEventListener "touchstart", (e)->```内で登録したタッチ/クリック時の時間とタッチ/クリックタイミングを比較し±0.2秒であれば"COOL"、±0.4秒であれば、"GOOD"、それ以外は"BAD"としています。
          if -0.2 <= @clearTime - _timing[@number] <= 0.2 then _judge.text = "COOL"
          else if -0.4 <= @clearTime - _timing[@number] <= 0.4 then _judge.text = "GOOD"
          else _judge.text = "BAD"

メインフレーム部

_proccesRootSceneFrame は33.3333..ms周期(すなわち30FPS)で呼び出されるメソッドです _game.onload 内で登録されています。 処理内容は以下です。

  1. ノートを生成すべきであればノートを生成
  2. 再生時間が終了時間を超えていればゲーム終了処理を行う
  _proccesRootSceneFrame = ->
    if _status is "playing"
      if _isNoteGenerateTiming()
        _generateNote(_timingIndex)
        _timingIndex++

      # 再生時間が終了時間以上?
      if _yt.getCurrentTime() >= _endTime
        # 動画のボリュームを落としていく。
        _yt.setVolume(_youtube.getVolume() - 1)
        # ボリュームが0になったら再生終了し、ステータスを"end"に設定
        if _yt.getVolume() <= 0
          _yt.stop()
          _status = "end"

youtube制御クラス

動画制御クラスですが、公式のサンプルをclassに閉じ込めただけなので詳細は省きます。 詳細はhttps://developers.google.com/youtube/js_api_reference?hl=jaを参照してください。 以下のメソッドで動画を制御可能です。

  • play : 動画を再生
  • getCurrentTime : 再生時間を取得
  • setVolume : 音量を設定
  • getVolume : 現在の音量を取得
  • isReady : Youtubeプレイヤーの準備ができているか問い合わせる
class @Yt
  _player = null
  _isReady = false
  _state = null
  constructor : (parms)->
    tag = document.createElement('script')
    tag.src = 'https://www.youtube.com/iframe_api'
    firstScriptTag = document.getElementsByTagName('script')[0]
    firstScriptTag.parentNode.insertBefore(tag, firstScriptTag)
  play : -> _player.playVideo()
  getCurrentTime : -> _player.getCurrentTime()
  setVolume : (volume)-> _player.setVolume(volume)
  getVolume : -> _player.getVolume()
  isReady : -> _isReady
  onPlayerReady = -> _isReady = true
  window.onYouTubeIframeAPIReady = ->
    _player = new YT.Player 'player',
      events:
        'onReady': onPlayerReady

実行部

newすることにより、constructor_game.onload の順にコールされ、ゲームが開始されます。

new Game()

コンパイル

書き終えたらmain.coffeeをコンパイルしてください。 main.jsが生成されますのでこれをhtmlファイルで読み込みます。 コンパイルは公式サイトの「TRY COFFEESCRIPT」からも可能です。たぶん。

CoffeeScript

html

<html>
  <head>
    <meta charset="utf-8">
  </head>
  <body>
    <div id="enchant-stage"></div>
    <script type="text/javascript" src="enchant.js"></script> <!-- enchant.jsは予めダウンロードしておいてください-->
    <script type="text/javascript" src="main.js"></script> <!-- 今回書いたものをコンパイルしたもの -->
  </body>
</html>

注意点など

この程度であればなんとか動作すると思いますが、規模が大きくなってくると現状の作りではパフォーマンス面で苦しくなってくるかと思います。 簡素化のため現状メモリの管理はまったく気にせず、好き放題やっていますが、このようなつくりの場合GC(ガベージコレクション)が、がしがし走るかとおもいます。 GCが走っている間はユーザのプログラムは止まった状態となるので、動きがカクカクしたり、一瞬止まったりする原因になります。 ここでは割愛(というか自分もよくわかっていない)しますがゲームを作る上では必要な知識となります。 本格的に勉強される方は以下の記事も参考にしてみてください。

wise9 › enchant.jsで3Dゲームを作ってみる!

オブジェクトプールを使った静的メモリ JavaScript - HTML5 Rocks

スプライトをプールして使い回す - enchant.jsのTipsを集めるWiki

ソース

main.coffee

class @Game
  YOUTUBE_ID = 'HNYkOJ-T63k'
  _game = null
  _yt = null
  _judge = null
  _timing = [6.14,7.486,8.155,9.977,10.377,11.611,12.062,12.765,13.583,13.945,14.223,14.707,15.059,16.241,16.577,17.425,20.186,20.917,21.593,22.313,22.449,23.123,24.297,24.965,25.113,25.464,26.148,26.635,27.294,28.103,30.910,31.601,32.305,33.024,34.054,34.786,35.360,36.140,37.028,38.402,38.829,39.129,40.354,41.051,41.553,42.233,43.043,43.729,44.261,45.705,46.448,47.416,48.407,50.158,51.310,52.363,53.031,54.417,55.288,55.640,56.472,57.190,58.110,59.095,59.648,60.776, 61.993, 62.370, 63.072, 63.808, 64.493, 65.111, 65.688, 66.414, 67.192, 68.891, 69.209, 69.918, 70.056, 71.111, 71.744, 72.428, 72.861, 73.263, 73.755, 74.099, 74.639, 75.090, 75.426, 75.941, 76.472, 76.992]
  _timingIndex = 0
  _status = "stop"
  _endTime = 80

  constructor : (parms)->
    enchant()
    _game = new Core(800, 600)
    _game.fps = 30
    _game.preload("icon.png", "shadow.png")
    _game.start()
    _game.onload = ->
      _game.rootScene.addEventListener "touchstart", (e)->
        if _yt.isReady()
          _game.rootScene.addEventListener "enterframe", _proccesRootSceneFrame
          _status = "playing"
          _yt.play()
      video = new Entity()
      video._element = document.createElement('div')
      video.x = 500
      video.y = 300
      video._element.innerHTML = '<iframe src="https://www.youtube.com/embed/'+YOUTUBE_ID+'?enablejsapi=1&controls=0&showinfo=0&autoplay=0&rel=0&vq=small"  width="300" height="200" frameborder="0" id="player"></iframe>'
      _game.rootScene.addChild(video)
      _yt = new Yt()
      _judge = new Label()
      _judge.font = "36px Arial"
      _judge.x = 100
      _judge.y = 100
      _game.rootScene.addChild(_judge)
      shadow = new Sprite(80, 80)
      shadow.image = _game.assets["shadow.png"]
      shadow.x = 100
      shadow.y = 380
      _game.rootScene.addChild(shadow)

  _isNoteGenerateTiming = ->
    if _timing[_timingIndex]?
      if _yt.getCurrentTime() > _timing[_timingIndex] - 1
        return true
    return false
  _generateNote = (number)->
    note = new Sprite(80, 80)
    note.image = _game.assets["icon.png"]
    note.number = number
    note.x = 100
    note.y = -100
    note.timing = _timing[number]
    _game.rootScene.addChild(note)
    note.tl.setTimeBased()
    note.tl.moveY(380, (_timing[number] - _yt.getCurrentTime()) * 1000)
    note.addEventListener "touchstart", (e)->
      @clearTime = _yt.getCurrentTime()
      @clear = true
    note.addEventListener "enterframe", ->
      if _yt.getCurrentTime() > _timing[@number] + 1 then _game.rootScene.removeChild(@)
      if @clear
        @opacity -= 0.2
        @scale(@scaleX + 0.05, @scaleY + 0.05)
        if @opacity <= 0
          _game.rootScene.removeChild(@)
          if -0.2 <= @clearTime - _timing[@number] <= 0.2 then _judge.text = "COOL"
          else if -0.4 <= @clearTime - _timing[@number] <= 0.4 then _judge.text = "GOOD"
          else _judge.text = "BAD"
  _proccesRootSceneFrame = ->
    if _status is "playing"
      if _isNoteGenerateTiming()
        _generateNote(_timingIndex)
        _timingIndex++
      if _yt.getCurrentTime() >= _endTime
        _yt.setVolume(_youtube.getVolume() - 1)
        if _yt.getVolume() <= 0
          _yt.stop()
          _status = "end"

class @Yt
  _player = null
  _isReady = false
  _state = null
  constructor : (parms)->
    tag = document.createElement('script')
    tag.src = 'https://www.youtube.com/iframe_api'
    firstScriptTag = document.getElementsByTagName('script')[0]
    firstScriptTag.parentNode.insertBefore(tag, firstScriptTag)
  play : -> _player.playVideo()
  getCurrentTime : -> _player.getCurrentTime()
  setVolume : (volume)-> _player.setVolume(volume)
  getVolume : -> _player.getVolume()
  isReady : -> _isReady
  onPlayerReady = -> _isReady = true
  window.onYouTubeIframeAPIReady = ->
    _player = new YT.Player 'player',
      events:
        'onReady': onPlayerReady

new Game()

index.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
  </head>
  <body>
    <div id="enchant-stage"></div>
    <script type="text/javascript" src="enchant.js"></script>
    <script type="text/javascript" src="main.js"></script>
  </body>
</html>

その他

もう少しわかりやすくして、おっ立ち野郎 (id:ottati)さんのコードレシピ - 初心者のための簡単プログラミングレシピサイトに登録させてもらおう。

enchant.jsで埋め込んだinput要素にスペースが入力できない対策

こんばんは。 今更気づいたんですが、enchant.jsでゲーム内にinput要素を埋め込んだ時にスペースが入力できないことに気が付きました。どうやら以下のようになっており、いくつかの文字は弾かれているようです。

if ((37 <= e.keyCode && e.keyCode <= 40) || e.keyCode == 32) {
  e.preventDefault();
  e.stopPropagation();
}

対策として以下のようにすることでスペースが入力できるようになります。

enchant();
enchant.ENV.PREVENT_DEFAULT_KEY_CODES = [37, 38, 39, 40];

参考URL

keydown時のpreventDefault対象keyCodeを利用者指定可能にしたい · Issue #74 · wise9/enchant.js · GitHub

【WEBサービス】youtubeを使った音ゲー×タッチタイピングサービスを作ってみた【つくってみた】

あけましておめでとうございます。 2015年は頭にあるサービスをいくつか形にしたいと考えているbokuwebです。

早速ですが、2014年の秋から作っていたサービスをβ版ですが公開しましたので告知いたします。

つくったもの

f:id:bokuweb:20141231232902p:plain

無料タッチタイピング音ゲー typebeatscloud

どんなもの?

「typebeats」はタッチタイピングを楽しく学ぶことを目標に作成した、youtube動画の音楽に合わせてタイピングする音ゲー×タッチタイピングサービスです。

いわゆる「BEATMANIAシリーズ」や「太鼓の達人」のような音ゲーとタイピングゲームを合わせてものです。

使用技術

  • 言語 PHP、CoffeeScript(JavaScript)
  • フレームワーク enchant.js、 jQuery
  • CSSフレームワーク Flat UI(Twitter Bootstrap)
  • データベース MySQL
  • CMS wordpress
  • バージョン管理 Git
  • WEB API YouTube Player API

制作経緯

自分はPCのを触る仕事にもかかわらずブラインドタッチが苦手です。 ゲームで練習したこともありますが、いまいち続かなかったため、楽しく続けられるものをイメージして作成しました。”これじゃタイピングの練習になんねーよ”ってつっこみは無しでお願いします。。

また、実はhttp://typebeats.com/という、2年ほど前に作成していて今回の http://cloud.typebeats.com/は次期バージョンという位置付けになっています。

開発に関して - デザイン編 -

ゲームの実装などより、デザインに多くの時間を割いています。(というかデザインができないため時間がかかてしまった、という言い方が正しい) そのわりにこれかよって気もしますが、素人なりにそれっぽくがんばったつもりです。 ゲームなのであまり地味地味にならないように気をつけました。

ただ、細かい部分などは匙を投げてしまったり、見てみぬふりをしてしまっている部分がありますので、そのあたりは目をつぶってください。(僕はゲーム部分がほぼほぼ実装されると急にモチベーションが下がってしまうようです。)

背景画像

知らない人は少ないかもしれませんが背景はhttp://graphicriver.net/で買いました。 クオリティの高い背景やゲーム用のキャラクタなどが安値で販売されています。

Design Templates - Fonts - Logo - Icons | Customizable | GraphicRiver

CSSフレームワーク

CSSフレームワークはFlat UI - Free Bootstrap Framework and Themeを使用しました。 フラットデザイン自体賞味期限が怪しいですが、やはりこういったフレームワークを使用するだけで見た目をなんとかごまかすことができます。

Flat UI - Free Bootstrap Framework and Theme

フォント

知らないひとはいないかもしれません。 M+ OUTLINE FONTS | WEB FONTSを使用させてもらっています。

M+ OUTLINE FONTS | WEB FONTS

アイコン

アイコンはFont Awesome, the iconic font and CSS toolkitを使用しています。 種類が豊富で使いやすい。

Font Awesome, the iconic font and CSS toolkit

ロゴデザイン

ロゴデザインにはSquarespace Logo — Squarespaceを使用しています。 簡単にそれっぽいロゴができる素晴らしいサービス。 低解像度であれば無料です。

Squarespace Logo — Squarespace

開発に関して - 技術編 -

クライアントサイド

ゲーム部の言語はCoffeeScriptを使っています。最高です。 1人でjavascriptを書くならCoffeeScriptを使わない理由はないかと思っています。(あくまで個人的な意見ですが)

javascriptで以下の記述が

$(function() {
  // hoge
});

CoffeeScriptならこうなる

$ ->
    # hoge

また、フレームワークにはenchant.jsを使用しました。 以前のhttp://typebeats.com/ではフレームワークを使用せずに実装したんですが、今回enchant.jsを使用することでこんなに簡単になるのか!と驚きました。 この辺りの詳細についても機会があったら記事にしたいです。

'15.1.18 記事にしました

100行で書けるブラウザで動作するyoutube音ゲーの作り方 - ぼくのかんがえたさいきょうのうぇぶさーびす

サーバサイド

wordpressを使用しています。 ほんとはRuby On RailsやPythonなどを使用してみたいところですが、今回の本題はそこではないので使い慣れたwordpressを使用しています。oAuthやコンタクトフォームもボタンひとつで導入できるのも素敵なポイントです。

読んでませんが以下のような書籍もでていますね。

WordPressによるWebアプリケーション開発

WordPressによるWebアプリケーション開発

  • 作者: Rakhitha Nimesh Ratnayake,プライム・ストラテジー株式会社(監訳),長尾高弘
  • 出版社/メーカー: オライリージャパン
  • 発売日: 2014/11/27
  • メディア: 大型本
  • この商品を含むブログ (2件) を見る

使用プラグインやカスタマイズなどは読んでくれる方がいそうであれば記事にしたいです。

WEB API

Youtube Player APIを使用しています。 javascriptから非常に簡単にyoutube動画を操作することができます。

iframe 組み込みの YouTube Player API リファレンス - YouTube — Google Developers

TODO

  • コメント、評価機能を付ける
  • パフォーマンスの改善 現状FPSが24程度しかでておりません。 公開後分かったんですが、ガベージコレクションが頻繁に走るようなつくりになってしまっているようです。 この辺の詳細もできれば記事にしたいです。
  • 人気譜面一覧の追加
  • コード整理して、コア部分をコードレシピ - 初心者のための簡単プログラミングレシピサイトにまとめてみたい

初心者向け簡単プログラミングレシピ投稿サイト『コードレシピ』を公開しました - あのねノート。

  • 譜面作成時のタイミング調整機能の追加

ひとまず以上です。 技術部の詳細や公開後の話など機会があれば記事にしたいですね。

それでは本年もよろしくお願いいたします。