ember.js tokyo event 2014/09/22 (japanese)
DESCRIPTION
2014年9月22日に行われたEmber.js Tokyoイベントでの発表資料です。 http://emberjs.doorkeeper.jp/events/14856 すごく個人的なネタ、かつEmber.jsの単なるバッドノウハウのカミングアウトと言われる可能性アリですが(汗) 一応公開します。 少しでも参考になれば幸いです。 (英語版: http://www.slideshare.net/yukishimada1/emberjs-tokyo-event-20140922-english )TRANSCRIPT
An example of game developing with Ember.js and WebGL
Yuki Shimada2014/09/22
About me
• 東邦大学でコンピューターグラフィックスを専攻。
• 東大のベンチャー企業で、 PS3 向けのゲームエンジンの開発に従事。
• 株式会社ソピア ( 現アクセンチュア ) に SEとして勤務。
• 某 VFX Studio にて、レンダラーの開発とWebGL サイトの開発案件を担当。
• 現在はフリーランス (Web & CG).
Demo
• ゲームプレーヤーhttp://youtu.be/0iRTk_2Wjp8
• マップエディターhttp://youtu.be/u6F3wGJpnUo
My Web Service• Web 上で簡単に RPG が作れる Web サービス。
• 想定開発者をベーシック開発者とアドバンスド開発者の2つに分けている。
• アドバンスド開発者は RPG をゲームシステムの段階から開発することができる。– ゲームコードは TypeScript で記述してもらう。– 将来的には、ノード接続によるビジュアルプログラミングに対応したい。– UI のカスタマイズも SCSS で可能。
• ベーシック開発者は、アドバンスド開発者が開発したゲームシステムの中から好きなものを選び、その枠内でマウス操作だけで簡単にRPG が作れる。
本 Web サービスの構造• ゲームプレーヤー部分と制作ツール部分の2つから
構成される。• 全体が Ruby on Rails 上で構築されている。– Rails の View 、 Controller は殆ど使っていない。– Ember.js で全てを制御。 Rails はモデルデータを提供するだ
け。– ビューの記述には Emblem を使用。
http://emblemjs.com– Rails からの Ember へのモデルデータ伝達には、 Ember Data
と ActiveModel::Serializers を利用。• 参考 URL : @ursm さんの以下のサンプルが参考になりました。
http://ursm.jp/blog/2013/12/03/ember-js-and-rails/
使用言語• 制作ツール側は CoffeeScript で、ゲームプ
レーヤー部分は TypeScript で記述。• CoffeeScript (制作ツール)、 TypeScript
(ゲームプレーヤー)双方から、 Ember.jsを操作。
• 全体の Router 定義は CoffeeScript 側で行っている。
Using of Ember.js
• UI handling• Data transfer of Rails Model -> Ember Data• Data transfer of Ember Data -> WebGL• Ember.js Observer and WebGL• Computation of Game character’s parameters
UI Handling
UI handling
• UI は HTML(Emblem)+CSS(SCSS) でできている– クリエーターは SCSS で見た目をカスタマイズ可能
• スクリーン (‘uiScreen’)-> テーブル (‘uiTable’)の2段階構造のモデルで UI を管理。
• スクリーン= Ember.js のテンプレートEmber.js でテンプレートを切り替えて、スクリーンの切り替えをしている。
• スクリーン配下のテーブルの表示・非表示はjQuery で。
uiScreen と uiTableテーブル(’ uiTable’ )
スクリーン(’ uiScreen’ )
Data transfer ofRails Model -> Ember Data
Between Rails and Ember Data
trait :as_2 do id 2 ui_screen_id 1 screenIdentifier 'battle' tableIdentifier 'character_0' tableName ' 0 :' tableName_binding 'Tool.EScenarioCharacter.0.name' selectable false trs "[" + "{"+ "'columns':[" + "{" + "'item':'HP:'," + "'binding': 'Tool.EScenarioCharacter.0.hp'" + "}" + "]" + "}," + "{"+ "'columns':[" + "{" + "'item':'MP:'," + "'binding': 'Tool.EScenarioCharacter.0.mp'" + "}" + "]" + "}" + "]" end
var uiTableDefinition = { screenIdentifier: DS.attr('string'), tableIdentifier: DS.attr('string'), tableName: DS.attr('string’), tableName_binding: DS.attr('string'), selectable: DS.attr('boolean'), trs: DS.attr('uiTableTrs'), …};
Definition of Ember Data Model A fixture of Rails Model (Factory Girl)
window.Tool.UiTableTrsTransform = DS.Transform.extend( { deserialize: function(value:any) { var json:any = null; eval("json = " + value); return Ember.create(json); } });
Transform of Ember Data Model
複雑または構造の変更が多いデータは、 JSON テキストデータとして Rails モデルのカラムに保存。 Ember Data の Transform を使い、 JavaScript で eval して、 Ember オブジェクト化している。
Question : Ember Data モデルの一対多関係を Rails のモデルからどう復元する?• Rails と Ember Data の一体多のデータ表現では、一部異なる点がある。• Rails では、子モデルが親モデルの参照を持つだけで、親モデルから子モデルのリス
トにアクセス可能。• Ember Data では、親モデルが子モデルの id 配列を明示的に持つ必要がある。• 上記の違いがあるため、両者のやりとりには変換処理が必要。
App.Post = DS.Model.extend({ comments: DS.hasMany('comment', {async: true})});
{ "post": { "comments": [1, 2, 3] }}
App.Comment = DS.Model.extend({ post: DS.belongsTo('post')});
{ "comment": { "post": 1 }}
create_table ”posts", force: true do |t|end
create_table ”comments", force: true do |t| t.integer "ui_screen_id”end
class Post < ActiveRecord::Base has_many :commentsend
class Comment < ActiveRecord::Base belongs_to :postend
Question : Ember Data モデルの一対多関係を Rails のモデルからどう復元する?
var uiTableDefinition = { screenIdentifier: DS.attr('string'), tableIdentifier: DS.attr('string'), tableName: DS.attr('string’), tableName_binding: DS.attr('string'), selectable: DS.attr('boolean'), trs: DS.attr('uiTableTrs'), …};
var uiScreenDefinition = { identifier: DS.attr('string'), config: DS.attr('uiScreenConfig'), uiTables: DS.hasMany('ui-table') };
Definition of ‘uiScreen’ which has many uiTables
Definition of ‘uiTable’ which belong to ‘uiScreen’
create_table "ui_screens", force: true do |t| t.string "identifier" t.text "config" t.text "uiTables”end
create_table "ui_tables", force: true do |t| t.integer "ui_screen_id" t.string "screenIdentifier" t.string "tableIdentifier" t.string "tableName" t.string "tableName_binding" t.boolean "selectable" t.text "trs”end
class UiScreen < ActiveRecord::Base has_many :ui_tablesend
class UiTable < ActiveRecord::Base belongs_to :ui_screenend
Ember Data Rails Model
Answer : Rails の Controller で Ember Data 向けに has many id 配列を生成する
class Api::UiScreensController < ApplicationController skip_before_action :verify_authenticity_token
def index uiScreens = [] UiScreen.all.each do |uiScreen|
uiTablesStr = "[" uiScreen.ui_tables.each do |uiTable| uiTablesStr += uiTable.id.to_s + ",“ end
uiTablesStr.chop! uiTablesStr += "]"
uiScreen.update_attribute(:uiTables, uiTablesStr) uiScreens.push(uiScreen) end
jsonHash = JSON.parse(uiScreens.to_json) for item in jsonHash item["uiTables"] = eval(item["uiTables"]) end jsonWrapper = {} jsonWrapper["ui_screens"] = jsonStr = jsonWrapper.to_json jsonHash = JSON.parse(jsonStr) render :json => jsonHashend
uiScreen に belong to している全ての uiTable のid を配列文字列に追加。
Ex:”[1, 2, 5, 6, 7]”
そして、 JSON 化して出力{ "ui_screens": [ { "config": "{'firstUI':'command','hiddenUIs':['physics', 'magic', 'target', 'magic-fake'],'calculatePosition': true}", "created_at": "2014-09-08T02:07:53.215Z", "id": 1, "identifier": "battle", "uiTables": [1, 2, 5, 6, 7] } ]}
Data transfer ofEmber Data -> WebGL
WebGL へのデータ受け渡し• WebGL を直接触っているのではな
く、 Three.js を利用。http://threejs.org
• tmlib というゲームライブラリを使用。http://phi-jp.github.io/tmlib.js/
• tmlib は three.js と統合されている。
• 今回は、マップの描画を例に取って解説。
マップの描画のためのマップデータの受け渡し
Tool.PlayRoute = Ember.Route.extend model: (params, transition) -> return Ember.RSVP.hash({ map: @store.find('squareGrid3dMap', params['id']) textures: @.store.find('squareGrid3dMapTexture') tiletypes: @.store.find('squareGrid3dMapTileType') uiScreens: @.store.find('uiScreen') uiTables: @.store.find('uiTable') })
trait :as_2 do id 2 name ' デバッグテスト ' width 5 height 5 type_array "1 N,1 W,1 N,1 W,1 W\n" + "1 N,1 W,1 N,1 W,1 W\n" + "1 N,1 N,1 N,1 N,1 N\n" + "1 W,1 W,1 N,1 W,1 W\n" + "1 W,1 W,1 N,1 W,1 W\n"
height_array "0 1,0 1,0 1,0 1,0 1\n" + "0 1,0 1,0 1,0 1,0 1\n" + "0 1,0 1,0 1,0 1,0 1\n" + "0 1,0 1,0 1,0 1,0 1\n" + "0 1,0 1,0 1,0 1,0 1\n" end
RailsMap Data (Factory Girl)
Tool.SquareGrid3dMap = DS.Model.extend name: attr('string') width: attr('number') height: attr('number') type_array: attr('string') height_array: attr('string')
Ember Data
マップの描画のためのマップデータの受け渡し
Tool.PlayIndexRoute = Ember.Route.extend model: -> @modelFor('play')
afterModel: (model, transition)->
window.WrtGS.main(model)
function systemMain(err:any, args:any){
window.WrtGS = new system.System(args);
}
module system { export class System {
public main(model:any) { this.map = new map.Map(model.map.get(‘type_array’), model.map.get(‘height_array’), model.texture.get(‘gametex_url’));
trait :as_2 do id 2 name ' デバッグテスト ' width 5 height 5 type_array "1 N,1 W,1 N,1 W,1 W\n" + "1 N,1 W,1 N,1 W,1 W\n" + "1 N,1 N,1 N,1 N,1 N\n" + "1 W,1 W,1 N,1 W,1 W\n" + "1 W,1 W,1 N,1 W,1 W\n"
height_array "0 1,0 1,0 1,0 1,0 1\n" + "0 1,0 1,0 1,0 1,0 1\n" + "0 1,0 1,0 1,0 1,0 1\n" + "0 1,0 1,0 1,0 1,0 1\n" + "0 1,0 1,0 1,0 1,0 1\n" end
trait :as_1 do id 1 name ' メタル ' gametex_url ‘metal.jpg’
あとは、ただのテキストデータなので、独自マップテキストフォーマットを解析
し、 Three.js のジオメトリ作成、テクスチャ画像割り当てを行う。
// 1 頂点目 geom.vertices.push( new THREE.Vector3(x-1, this.maxCeilingHeight, y) );
// 2 頂点目 geom.vertices.push( new THREE.Vector3(x-1, ceilingHeight, y) );
// 3 頂点目 geom.vertices.push( new THREE.Vector3(x-1, ceilingHeight, y-1) );
// 4 頂点目 geom.vertices.push( new THREE.Vector3(x-1, this.maxCeilingHeight, y-1) );
// 表三角形の1個目 var face1 = new THREE.Face3( verticesStride+0, verticesStride+1, verticesStride+2 ); face1.normal = new THREE.Vector3(1, 0, 0); geom.faces.push( face1 );
geom.faceVertexUvs[ 0 ].push( [ new THREE.Vector2( 0.75, 0 ), new THREE.Vector2( 0.75, this.texcoordOne * (this.maxCeilingHeight - ceilingHeight) ), new THREE.Vector2( 1, this.texcoordOne * (this.maxCeilingHeight - ceilingHeight) ) ] );
Ember.js から WebGL(Three.js) へのデータ受け渡しは、そんな特別なことはしていない。
Ember Data モデルで、 Fulfilled されたデータから文字列を取り出し、それを文字列解析して、 Three.js のジオメトリ構築用の関数に渡しているだけ。
マップの描画のためのマップデータの受け渡し
Ember.js Observer and WebGL
• 敵がダメージを受けると、敵画像が揺れたりしているが、これは Ember.js の Observerを使っている。
• 敵や味方のステータス値も Ember オブジェクトとして管理しており、 HP が減ると Observer が起動、 Three.js の敵ポリゴンの座標値を振動させている。
Computation of Game character’s parameters
RPG では、あるパラメーターから他のパラメーターを算出する、というケースが非常に多い。→Computed Properties で解決!
本制作ツールでは、キャラクターのパラメーターを自由に新設し、パラメーター間の数学関係を自由に記述できる。
Ember.js と Web ゲームの親和性• UI の表示・更新に最適!
– 初期バージョンは jQuery のみで処理していたが、やはり地獄だった。今はEmber.js のお陰でかなりスマートになった。
– UI の数が多い RPG などには特に向いている。使わない手はない。
• 双方向バインディングが便利。– 大型のゲームになると、複数の場所間でデータを同期する必要が生じるが、同期処理の面倒はすべて Ember.js が見てくれる。
• オブザーバーも便利。– ゲーム UI では単なるテキストではなく、画像を使用することも多い。内部デー
タの再計算だけにデータバインディングを使用し、アニメーションは別描画ライブラリを併用することも可能。表示の更新のタイミングは Observer で。
• RPG など、パラメーター同士の計算が多いゲームでは Computed Properties が活躍する。
Ember.js最高!• 以上、 Ember.js は本 Web サービスのゲー
ム部分、制作ツール部分双方の根底を支えている。
• Ember.js がなければ、ここまでの開発効率とコードの見通しの良さは考えられなかった。
Thanks! Ember.js!
最後に• Ember.js を学びたい方。– Ember.js公式ガイド日本語訳を公開していま
す!https://github.com/emadurandal/emberjs-guides-japanese-translation
– Developers.IO の「シリーズ Ember.js入門」も必見。(渡辺さん素晴らしい記事をありがとう!)http://dev.classmethod.jp/series/getting-started%E2%80%8E-emberjs/
Thank you!