※修正->jubeats
jubeat
成果物
ここで遊べる(スマホもしくはタッチが有効な環境)
http://bokuweb-sandbox.github.io/mithril-game-prototype/
jubeat
はこんなゲーム。
目的
bmsをreactで際実装したいという欲求
— Bokuweb(求職中) (@bokuweb17) 2015, 9月 28
React
でbms
できりゃテーマとか全部css
でカスタマイズできるし、cocos2d-JS
(現在はcocos2d-x
)のクソみたいなUI使わなくていいし最高じゃんって思ったんだけど、凶悪な譜面を思い浮かべると「あーやっぱ無理だろうな・・。」って、横道に逸れてこれ作った。
いつ飽きるかわからないけど。あと、結果はどうあれ、bms
の方もMithril
でひとまず簡単な譜面を再生するとこまでやってみたい。
本当はReact
で作りたいけど描画のベンチマーク見せられちゃうとまずはMithril
だろうかって思ってる。
作り
譜面情報をこんな感じで持っとく。
m = require 'mithril' App = require './app' app = m.component new App(), { audio : "./audio/Ouroboros.mp3" notes : [ {row : 0, column : 0, time : 2.329} {row : 1, column : 1, time : 4.521} {row : 2, column : 2, time : 6.759} {row : 3, column : 2, time : 8.982} {row : 3, column : 3, time : 11.269} {row : 1, column : 1, time : 13.500} {row : 2, column : 2, time : 15.731} {row : 2, column : 2, time : 18.002} {row : 0, column : 0, time : 20.242} {row : 1, column : 1, time : 22.448} {row : 2, column : 2, time : 24.753} {row : 2, column : 2, time : 26.960} {row : 0, column : 0, time : 29.180} {row : 1, column : 1, time : 31.455} {row : 2, column : 2, time : 33.730} {row : 2, column : 2, time : 35.899} {row : 0, column : 0, time : 38.170} {row : 1, column : 1, time : 40.377} {row : 2, column : 2, time : 42.665} {row : 2, column : 2, time : 44.886} {row : 3, column : 3, time : 47.127} {row : 1, column : 1, time : 49.354} {row : 2, column : 2, time : 51.654} {row : 2, column : 2, time : 53.825} {row : 0, column : 3, time : 56.081} {row : 1, column : 1, time : 58.306} {row : 2, column : 2, time : 60.544} {row : 2, column : 2, time : 62.781} {row : 1, column : 1, time : 65.039} {row : 2, column : 2, time : 67.278} {row : 2, column : 2, time : 69.498} {row : 3, column : 2, time : 71.773} {row : 1, column : 1, time : 74.010} {row : 2, column : 2, time : 76.216} {row : 2, column : 2, time : 80.793} {row : 0, column : 3, time : 83.015} {row : 1, column : 3, time : 85.224} {row : 2, column : 2, time : 89.752} {row : 2, column : 2, time : 92.022} {row : 2, column : 2, time : 94.294} ] } m.mount document.body, app
ゲーム開始前に各座標へノートを配置しておいて、経過時間に応じてノートのclassをnote-show
->note-hide
->note-delete
で変化させる。タッチされた場合はnote-hit
->note-delete
で変化させる。audio.currentTime
の変化をView
に伝えるのをどうすべきかわかんなくて以下のように同期してる。スマートなやり方あったら教えてください。
do update = => @time @audio.currentTime m.redraw() window.requestAnimationFrame update
m = require 'mithril' class SixTeenNotes constructor : (score) -> return m.prop score class SixTeenViewModel init : (score) => @isPlaying = false @score = new SixTeenNotes score @judge = m.prop "" @audio = new Audio score.audio @time = m.prop @audio.currentTime do update = => @time @audio.currentTime m.redraw() window.requestAnimationFrame update onTouchNote : (note, event) => note.clearTime = @time() judge = if note.time - 0.1 < note.clearTime < note.time + 0.1 "great" else if note.time - 0.2 < note.clearTime < note.time + 0.2 "good" else "bad" @judge judge startGame : => unless @isPlaying @isPlaying = true @audio.play() class SixTeen constructor : -> @_vm = new SixTeenViewModel() return { controller : (score) => @_vm.init score view : @_view } _view : (ctrl) => getNoteClass = (note) => if note.clearTime? if note.clearTime + 0.2 < @_vm.time() then return "note-delete" else return "note-hit" if @_vm.time() >= note.time + 0.6 then return "note-delete" if @_vm.time() >= note.time + 0.4 then return "note-hide" if @_vm.time() >= note.time - 0.2 then return "note-show" addTouchNoteEvent = (note, element, initialized, context) -> unless initialized element.addEventListener 'touchstart', @_vm.onTouchNote.bind(this, note), false m "div#game", [ m "button", {onclick: @_vm.startGame}, "start game" m "span#judge", @_vm.judge() m "div#notes", @_vm.score().notes.map (note) => m "img.note.row-#{note.row}.column-#{note.column}", { src : "./image/dest.png" class : getNoteClass note config : addTouchNoteEvent.bind this, note } ] module.exports = SixTeen
スタイルはstylusで以下のように設定。
rowNum
とcolumnNum
でマスの数は自由に弄れる。
アニメーションはCSSアニメーションでやらしてるけど、どの程度の精度があるのか分かっていない。
width = 300px height = 300px rowNum = 4 columnNum = 4 vendor(prop, args) -webkit-{prop} args -moz-{prop} args {prop} args html, body margin 0 padding 0 height 100% width 100% overflow hidden #game width 100% height 100% position relative background #f5f5f5 #notes width width height height background #ccc top 100% margin -(height + 20)px auto position relative img.note width (width / rowNum) height (width / rowNum) position absolute vendor('transition', all 0.2s ease-out) vendor('transform', scale3d(0,0,0)) img.note-show vendor('transition', all 0.2s ease-out) vendor('transform', scale3d(1,1,1)) img.note-hit vendor('transition', all 0.2s ease-out) vendor('transform', scale3d(1.5,1.5,1)) opacity 0 img.note-hide vendor('transition', all 0.2s ease-out) vendor('transform', scale3d(1,1,1)) opacity 0 img.note-delete display none for row in 0 ... rowNum img.row-{row} left : row * (width / rowNum) for column in 0 ... columnNum img.column-{column} top : column * (width / rowNum)
最後に
次はbmsでやってみる。