'use strict' intrnd = (max) -> Math.floor(Math.random() * max) # 整数の乱数を返す sq = (t) -> t * t # 自乗した値を返す d2r = (d) -> d * Math.PI / 180 # degree to radian zpad = (n, w) -> ('0000000' + n).substr(-w) pats = [] # 読み込みを待ち合わせたいイメージ #------------------------------------------------------------------------------- # # キャラクタ基底 # class Character constructor: (@s) -> @ctx = @s['WIND0W'].ctx @active = true draw: -> move: -> check_hit: -> destroy: -> @active = false #------------------------------------------------------------------------------- # # 地面(BG) # class Ground extends Character @pats = ground: 'images/ground.jpg' # 512 x 384 for pat, uri of @pats image = new Image; image.src = uri @pats[pat] = image; pats.push(image) constructor: (s) -> super(s) @ctx.fillStyle = '#FFFFFF' @ctx.fillRect(0, 0, 1024, 768) # 白でクリア @ctx.drawImage(Ground.pats['ground'], 256, 192) # 地面 @bg = @ctx.getImageData(0, 0, 1024, 768) # BG としてキャプチャ draw: -> @ctx.putImageData(@bg, 0, 0) # BG として描画 #------------------------------------------------------------------------------- # # マウスカーソル # class Cursor extends Character @pats = norm: 'images/cursor0.png' # 16 x 24 fire: 'images/cursor1.png' # 16 x 24 burn: 'images/cursor2.png' # 16 x 24 for pat, uri of @pats image = new Image; image.src = uri @pats[pat] = image; pats.push(image) constructor: (s) -> super(s) @pos_x = 0; @pos_y = 0 @show = false; @roll = false; @limit = 600 @pat = Cursor.pats['norm'] draw: -> if(@show and @s['INPUTS']['MOUSE']['ON'] or @roll > @limit) @ctx.drawImage(@pat, @pos_x, @pos_y) if(@s['INPUTS']['MOUSE']['X'] != 0) @show = true move: -> unless(@roll) @pos_x = @s['INPUTS']['MOUSE']['X'] - 1 @pos_y = @s['INPUTS']['MOUSE']['Y'] - 3 else if(++@roll < @limit) r = if(@roll) then Math.sin(d2r(@roll * 5 % 360)) * 8 + 32 else 0 cos = Math.cos(it = d2r(@roll * 16 % 360)); sin = Math.sin(it) @pos_x = cos * r * 3 + @s['INPUTS']['MOUSE']['X'] - 1 @pos_y = sin * r * 2 + @s['INPUTS']['MOUSE']['Y'] - 3 else if(@roll == @limit) @s['SOUNDS'].se('PANG').out() @pat = Cursor.pats['fire'] else if(@roll > @limit + 6) @pat = Cursor.pats['burn'] check_hit: -> button = @s['OBJECTS']['BUTTON'][0] if(button.pos_x < @pos_x and @pos_x < button.pos_x + button.w and button.pos_y < @pos_y and @pos_y < button.pos_y + button.h) @roll or= 1; button.over(true) else @roll and button.over(false) #------------------------------------------------------------------------------- # # ボタン # class Button extends Character @pats = fire0: 'images/fire0.png' # 48 x 21 fire1: 'images/fire1.png' # 48 x 21 stop0: 'images/stop0.png' # 48 x 21 stop1: 'images/stop1.png' # 48 x 21 for pat, uri of @pats image = new Image; image.src = uri @pats[pat] = image; pats.push(image) constructor: (s, x, y) -> super(s) @pat = Button.pats['fire0'] @w = @pat.width; @h = @pat.height @pos_x = x - (@w >> 1); @pos_y = y - (@h >> 1) draw: -> @ctx.drawImage(@pat, @pos_x, @pos_y) over: (yep) -> @pat = Button.pats[if(yep) then 'stop1' else 'stop0'] #------------------------------------------------------------------------------- # # キャラクタ出現タイムテーブル # class Timetables constructor: (@s) -> @objects = @s['OBJECTS'] @tables = [] @bind_stages() @demo() bind: (n, func) -> @tables[n] = func demo: -> @change_stage(0) @state = 'DEMO' change_stage: (stage) -> @stage = stage; @time = 0 d0: -> @time++ @tables[@stage]() # 返り値を素通し bind_stages: -> # 初期化&デモ画面定義 @bind(0, => if(@time == 1) @s['SOUNDS'].off() @objects['CURSOR'] = [new Cursor(@s)] @objects['BUTTON'] = [new Button(@s, 512, 400)] true ) #------------------------------------------------------------------------------- # # 進行処理定義 # class Director constructor: (@s) -> @timetables = new Timetables(@s) @objects = @s['OBJECTS'] d0: -> # 入力処理 # 移動処理 for type, tobjects of @objects n = 0; while(n < tobjects.length) tobjects[n].move() unless(tobjects[n].active) then tobjects.splice(n, 1) else n++ # 登場処理(ステージ毎のキャラクタ出現) @timetables.d0() or @timetables.next_stage() # 衝突判定処理 for type, tobjects of @objects n = 0; while(n < tobjects.length) tobjects[n].check_hit() unless(tobjects[n].active) then tobjects.splice(n, 1) else n++ # サウンド処理 @s['SOUNDS'].d0() #------------------------------------------------------------------------------- # # サウンド関係定義 # class Sounds constructor: -> @enable = document.createElement('audio').canPlayType @sounds = {} set_src: (name, src) -> @sounds[name] = new Sound(src, @enable) ready: -> for name, sound of @sounds if(sound.getReadyState() != 4) then return(false) # 4: HAVE_ENOUGH_DATA true bgm: (name) -> @sounds[name] se: (name) -> @sounds[name] off: -> for name, sound of @sounds sound.off() d0: -> for name, sound of @sounds sound.d0() # TODO: sound.tasks.size > 0 and sound.do #------------------------------------------------------------------------------- # # 各サウンド # class Sound constructor: (src, @enable) -> @audio = new Audio(src) if(@enable) getReadyState: -> unless(@enable) then return(4) # 4: HAVE_ENOUGH_DATA @audio.readyState play: (@times = 1) -> unless(@enable) then return @off(); @audio.play() --@times out: -> @play() loop_play: -> @play(99999) pause: -> @audio.pause() if(@enable) off: -> unless(@enable) then return @audio.pause() @audio.currentTime = 0 setVolume: (@vol) -> unless(@enable) then return @audio.volume = @vol / 100 if(@vol == 0) then @off() setCurrentTime: (t) -> @audio.currentTime = t if(@enable) d0: -> unless(@enable) then return if(@audio.ended) if(--@times > -1) then @audio.play() else @off() #------------------------------------------------------------------------------- # # 描画画面定義 # class Wind0w constructor: (id, @objects) -> @canvas = document.getElementById(id) if(G_vmlCanvasManager?) # for fuckin' IE @canvas = G_vmlCanvasManager.initElement(@canvas) @ctx = @canvas.getContext('2d') r = @canvas.getBoundingClientRect() @ox = r.left; @oy = r.top draw: -> # 描画処理 for type, tobjects of @objects for object in tobjects object.draw() #------------------------------------------------------------------------------- # # イベント定義 # touchstart = (event) -> # タッチ開始 x = event.touches[0].pageX - wind0w.ox y = event.touches[0].pageY - wind0w.oy inputs['LEVEL']['TOUCH'] = [x, y, jiffies] inputs['EDGE'].push(['TOUCH', x, y]) event.preventDefault() touchend = (event) -> # タッチ終了 inputs['LEVEL']['TOUCH'] = false event.preventDefault() mousedown = (event) -> # マウスボタン押下 x = event.clientX - wind0w.ox y = event.clientY - wind0w.oy w = event.which inputs['LEVEL']['MOUSE'] = [x, y, w, jiffies] inputs['EDGE'].push(['MOUSE', x, y, w]) mouseup = (event) -> # マウスボタン開放 inputs['LEVEL']['MOUSE'] = false mousemove = (event) -> # マウスポインタ移動 inputs['MOUSE']['X'] = event.clientX - wind0w.ox inputs['MOUSE']['Y'] = event.clientY - wind0w.oy mouseenter = (event) -> # マウスポインタが入った inputs['MOUSE']['ON'] = true mouseleave = (event) -> # マウスポインタが出た inputs['MOUSE']['ON'] = false mousedclick = (event) -> # マウスダブルクリック x = event.clientX - wind0w.ox y = event.clientY - wind0w.oy w = event.which inputs['EDGE'].push(['DCLICK', x, y, w]) keydown = (event) -> # キー押下 keyCode = if(it = inputs['SP_KEY'][event.keyCode]) then it else String.fromCharCode(event.keyCode) x = inputs['MOUSE']['X'] y = inputs['MOUSE']['Y'] inputs['LEVEL'][keyCode] = [x, y, jiffies] inputs['EDGE'].push([keyCode, x, y]) if(inputs['EDGE_SENSE'][keyCode]) keyup = (event) -> # キー開放 keyCode = if(it = inputs['SP_KEY'][event.keyCode]) then it else String.fromCharCode(event.keyCode) inputs['LEVEL'][keyCode] = false #------------------------------------------------------------------------------- # # 垂直帰線割り込みのシミュレート # ints = []; spf = (1000 / 60) # フレーム速度(1000/FPS) vsync = -> ints.push(start = (new Date).getTime()); next = start + spf ints.shift() while(ints[0] < start - 1000) jiffies++ wind0w.draw() # 描画処理 director.d0() # 各種処理 finish = (new Date).getTime() if(jiffies % 60 == 0) console.log(['now: ', start, 'frames: ', ints.length, ' load: ', Math.floor((finish - start) * 100 / spf), '%'].toString()) setTimeout(vsync, if((it = next - finish) > 0) then it else 0) #=============================================================================== # # メイン # jiffies = 0 s = OBJECTS: objects = GROUND: [] BUTTON: [] CURSOR: [] WIND0W: wind0w = new Wind0w('canvas1', objects) INPUTS: inputs = LEVEL: {} EDGE: [] SP_KEY: { 13: 'ENTER', 32: 'SPACE', 37: 'LEFT', 38: 'UP', 39: 'RIGHT', 40: 'DOWN' } EDGE_SENSE: { ENTER: true, SPACE: true, Z: true } MOUSE: { X: 0, Y: 0 } SOUNDS: sounds = new Sounds el = document.getElementById('canvas1') el.addEventListener('touchstart', touchstart) el.addEventListener('touchend', touchend) el.addEventListener('mousedown', mousedown) el.addEventListener('mouseup', mouseup) el.addEventListener('mousemove', mousemove) el.addEventListener('mouseenter', mouseenter) el.addEventListener('mouseleave', mouseleave) wind0w.canvas.ondblclick = mousedclick document.onkeydown = keydown document.onkeyup = keyup sounds.set_src('PANG', 'sounds/pang.wav') el.style.cursor = 'none' director = false # 各サウンドが読み込まれるまで待つ ssync = -> unless(sounds.ready()) setTimeout(ssync, 10) else isync() # 各イメージが読み込まれるまで待つ isync = -> n = 0; while(n < pats.length) if(pats[n].complete) then pats.splice(n, 1) else n++ unless(pats.length == 0) setTimeout(isync, 10) else objects['GROUND'].push(new Ground(s)) director = new Director(s) vsync() ssync()