class App_20220223194144 { /* 「ゲームのためのキー入力処理例」 特徴: ・タッタタタタというキー入力ではなく、スムーズなキー入力。 ・一瞬押しを取りこぼさない。 ・マス目を移動するタイプのゲーム向けで、シューティング等には向かない。 実行方法: app = new App_20220223194144( document.getElementById( "test" ) ); app.start(); */ constructor( canvas ) { this.cc = canvas.getContext( '2d', { alpha : false } ); this.cc.canvas.width = 512; this.cc.canvas.height = 448; //---カスタマイズ--- this.tileW = 32; //マス目の大きさ。2以上で8~64程度(dot) this.walkStep = 8; //歩行ドット量。0より大きくtileW以下でtileWを割り切る数(dot) this.frameSpeed = 75; //requestAnimationFrame内の処理間隔0~100程度(ミリ秒) //------------------ this.cw = Math.floor( this.cc.canvas.width / this.tileW ); //画面のマス目の数を計算 this.ch = Math.floor( this.cc.canvas.height / this.tileW ); this.keyz = new Map(); //入力されているキー this.anms = new Array(); //実行中のアニメ this.anmWalk = { //一歩進むアニメ app : this, count : 0, //現在のアニメのコマ countMax : this.tileW / this.walkStep, addX : 0, //歩行の方向 addY : 0, tweakGX : 0, //ずらし描画の量 tweakGY : 0, frame : function() { //requestAnimationFrame内で呼ばれる this.count ++; this.tweakGX = this.count * this.app.walkStep * this.addX; this.tweakGY = this.count * this.app.walkStep * this.addY; //check. 一歩進むアニメの終了 if( this.count >= this.countMax ) { this.tweakGX = 0; //ずらし描画をやめて、 this.tweakGY = 0; this.app.player.cx += this.addX; //キャラの位置を移動 this.app.player.cy += this.addY; //check. 画面外を画面内へ修正する this.app.player.cx += this.app.cw; this.app.player.cy += this.app.ch; this.app.player.cx %= this.app.cw; this.app.player.cy %= this.app.ch; this.app.player.cx = Math.abs( this.app.player.cx ); this.app.player.cy = Math.abs( this.app.player.cy ); return false; //アニメ終了の意味 } return true; //アニメ継続の意味 }, } //tweak. bindを使い、frame内のthisが何を指すか指定 this.anmWalk.frame = this.anmWalk.frame.bind( this.anmWalk ); this.player = { cx : 0, cy : 0, } }//constructor start() { //requestAnimationFrameを開始 this.frameTimestampBak = 0; this.frame( 0 ); //frameの引数はrequestAnimationFrameで自動的に現在の時刻が渡される。 //最初はframeを手動で呼び出すので引数にダミーを指定している。 //キー入力をバックアップ this.onkeydownBak = window.onkeydown; this.onkeyupBak = window.onkeyup; //キー入力をこのApp_20220223194144が処理する window.onkeydown = this.onkeydownx.bind( this ); window.onkeyup = this.onkeyupx.bind( this ); } stop() { //requestAnimationFrameを終了 cancelAnimationFrame( this.timerId ); //キー入力を元に戻す window.onkeydown = this.onkeydownBak; window.onkeyup = this.onkeyupBak; } onkeydownx( e ) { window.keydowncnt++; //keyzは追加順序を維持するObjectであるMapオブジェクトです。 if( ! this.keyz.has( Number( e.which ) ) ) { this.keyz.set( Number( e.which ), { ran : false, up : false } ); } return [ 37,38,39,40 ].indexOf( e.which ) == -1; //押されたキーがこのプログラムで使うキーに該当しないならtrueを返す //そのとき、ブラウザへキーイベントが伝播される } onkeyupx( e ) { if( this.keyz.has( Number( e.which ) ) ) { this.keyz.get( Number( e.which ) ).up = true; } return false; } frame( timestamp ) { //前回のframe実行からの経過時間がframeSpeedをすぎたら処理実行 if( timestamp - this.frameTimestampBak >= this.frameSpeed ) { this.frameTimestampBak = timestamp; //キー処理 for( let key of Array.from( this.keyz.keys() ).reverse() ) { let prop = this.keyz.get( key ); if( this.keyExec( key ) ) prop.ran = true; //入力されたキーを実行 //ranはrunの過去形のレンです。 //"そのキーは実行したことがある"という意味 if( prop.ran && prop.up ) this.keyz.delete( key ); //一度でも実行に成功し、キーも離されているなら削除してよい } //アニメ処理 for( let i = 0; i < this.anms.length; i++ ) { let anm = this.anms[ i ]; if( ! anm.frame() ) { //アニメの実行結果がfalseならそのアニメは終了 this.anms.splice( i, 1 ); i--; } } this.draw( this.cc ); } this.timerId = requestAnimationFrame( this.frame.bind( this ) ); }//frame keyExec( key ) { //check. 一歩進むアニメ実行中はキー処理しない。キー実行失敗 if( this.anms.indexOf( this.anmWalk ) > -1 ) return false; //↑↓←→キーによる方向判定 let addX = ( key == 39 ) - ( key == 37 ); //論理の真は1、偽は0 let addY = ( key == 40 ) - ( key == 38 ); if( addX || addY ) { //一歩進むアニメの開始 this.anmWalk.addX = addX; this.anmWalk.addY = addY; this.anmWalk.count = 0; this.anms.push( this.anmWalk ); } return true; //キー実行成功 } draw( cc ) { cc.fillStyle = "black"; cc.fillRect( 0, 0, cc.canvas.width, cc.canvas.height ); //マス目を描画 cc.strokeStyle = "gray"; for( let cy = 0; cy < this.ch; cy++ ) { let gy = cy * this.tileW; for( let cx = 0; cx < this.cw; cx++ ) { let gx = cx * this.tileW; cc.strokeRect( gx, gy, this.tileW, this.tileW ); } } //プレイヤーを描画 cc.fillStyle = "red"; let gx = this.player.cx * this.tileW + this.anmWalk.tweakGX; let gy = this.player.cy * this.tileW + this.anmWalk.tweakGY; cc.fillRect( gx, gy, this.tileW, this.tileW ); }//draw }//App_20220223194144