Webブラウザで簡単ゲームを作ってみよう: 壁打ちテニス編 #7「スマホでラケット操作」

2021年10月13日

テクノロジー

eyecatch 時代はやっぱスマホっしょ!を実感する、ユゲタです。 今どきのブラウザゲームは、キーボード操作とマウス操作に加えて、スマホ用のタッチ操作も同時に実装しないといけません。 スマホアプリであれば、マウス操作などは意識しなくてもいいのですが、ブラウザは、今やどの端末にも備わっているオールマイティインターフェイスを意識する必要があります。 そんなわけで、今回の壁打ちテニスでも、スマホ操作を実装しておきます。 ラケットの操作をタッチで動かすのは、これまでやってきたマウス操作とキーボード操作を組み合わせるだけで、比較的簡単にできるので、 コツさえ掴んでおけば、色々な操作方法の応用がきくとおもいます。

ソースコード

(function(){ function MAIN(){ this.get_racket(); this.get_ball(); this.set_space(); this.set_event(); } MAIN.prototype.get_tennis = function(){ if(!this.tennis){ this.tennis = document.getElementById("tennis"); } return this.tennis; }; MAIN.prototype.get_racket = function(){ if(!this.racket){ this.racket = document.getElementById("racket"); } return this.racket; }; MAIN.prototype.get_ball = function(){ if(!this.ball){ this.ball = document.getElementById("ball"); } return this.ball; }; MAIN.prototype.set_space = function(){ let tennis = this.get_tennis(); this.space = tennis.getBoundingClientRect(); let border_width_left = document.defaultView.getComputedStyle(tennis,'').getPropertyValue("border-left-width").replace("px",""); let border_width_right = document.defaultView.getComputedStyle(tennis,'').getPropertyValue("border-right-width").replace("px",""); let border_width_top = document.defaultView.getComputedStyle(tennis,'').getPropertyValue("border-top-width").replace("px",""); let border_width_bottom = document.defaultView.getComputedStyle(tennis,'').getPropertyValue("border-bottom-width").replace("px",""); this.space.border_width = { left:Number(border_width_left), right:Number(border_width_right), top:Number(border_width_top), bottom:Number(border_width_bottom) }; }; MAIN.prototype.set_event = function(){ // キーボード操作 window.addEventListener("keydown" , this.keydown.bind(this)); window.addEventListener("keyup" , this.keyup.bind(this)); // マウス操作 window.addEventListener("mousemove" , this.mousemove.bind(this)); // スマホ操作 window.addEventListener("touchstart" , this.touchstart.bind(this)); window.addEventListener("touchmove" , this.touchmove.bind(this)); window.addEventListener("touchend" , this.touchend.bind(this)); }; MAIN.prototype.keydown = function(e){ // 押しっぱなし防止 if(e.repeat === true){return;} this.racket_offset = 0; switch(this.keyType(e.keyCode)){ case "left": // < this.racket_offset = -1; break; case "right": // > this.racket_offset = +1; break; } this.racket_move(); }; MAIN.prototype.keyup = function(e){ if(this.racket_flg){ clearTimeout(this.racket_flg); delete this.racket_flg; } }; MAIN.prototype.racket_move = function(){ let offset = this.racket_offset; if(!offset){return;} let x = this.get_racket_x(); let min = this.space.border_width.left; let max = this.space.width - this.space.border_width.left - this.space.border_width.right - this.racket.offsetWidth; x = x + (offset * 10); if(x < min){x = 0;} else if(x > max){x = max;} this.racket.style.setProperty("left" , x +"px" , ""); this.racket_flg = setTimeout(this.racket_move.bind(this) , 10); if(this.mouse_pos){ delete this.mouse_pos; } }; // lib MAIN.prototype.keyType = function(keycode){ switch(keycode){ case 0: return "¥"; case 8: return "backspace"; case 9: return "tab"; case 13: return "return"; case 16: return "shift"; case 18: return "option"; case 32: return "space"; case 37: return "left"; case 38: return "up"; case 39: return "right"; case 40: return "down"; case 48: return "0"; case 49: return "1"; case 50: return "2"; case 51: return "3"; case 52: return "4"; case 53: return "5"; case 54: return "6"; case 55: return "7"; case 56: return "8"; case 57: return "9"; case 65: return "a"; case 66: return "b"; case 67: return "c"; case 68: return "d"; case 69: return "e"; case 70: return "f"; case 71: return "g"; case 72: return "h"; case 73: return "i"; case 74: return "j"; case 75: return "k"; case 76: return "l"; case 77: return "m"; case 78: return "n"; case 79: return "o"; case 80: return "p"; case 81: return "q"; case 82: return "r"; case 83: return "s"; case 84: return "t"; case 85: return "u"; case 86: return "v"; case 87: return "w"; case 88: return "x"; case 89: return "y"; case 90: return "z"; case 91: return "command"; case 187: return "^"; case 189: return "-"; } return null; }; MAIN.prototype.get_racket_x = function(){ let left = document.defaultView.getComputedStyle(this.racket,'').getPropertyValue("left"); return left ? Number(left.replace("px" , "")) : 0; }; MAIN.prototype.mousemove = function(e){ if('ontouchstart' in window === true){return;} if(typeof this.mouse_pos === "undefined"){ this.mouse_pos = e.pageX; } let base_offset = -this.space.left; this.racket_mouse_move(e.pageX + base_offset); }; MAIN.prototype.racket_mouse_move = function(x){ let min = this.space.border_width.left; let max = this.space.width - this.space.border_width.left - this.space.border_width.right - this.racket.offsetWidth; if(x < min){x = 0;} else if(x > max){x = max;} this.racket.style.setProperty("left" , x +"px" , ""); this.mouse_pos = x; }; MAIN.prototype.touchstart = function(e){ if('ontouchstart' in window === false){return;} if(!e.touches || !e.touches.length){return;} this.touch_pos = e.touches[0].pageX; }; MAIN.prototype.touchmove = function(e){ if(typeof this.touch_pos === "undefined"){return} let touch_move_pos = e.touches[0].pageX; let diff = touch_move_pos - this.touch_pos; let x = this.get_racket_x(); this.racket_mouse_move(x + diff); this.touch_pos = touch_move_pos; }; MAIN.prototype.touchend = function(e){ if(typeof this.touch_pos !== "undefined"){ delete this.touch_pos; } }; switch(document.readyState){ case "complete" : new MAIN();break; default : window.addEventListener("load" , (function(options){new MAIN()}).bind(this));break; } })();

デモ

解説

今回追加した内容は、イベント設定で"touchstart"、"touchmove"、"touchend"の3種類と、それぞれを実行する関数です。 これまで、キーボード操作では、"keydown"と"keyup"、マウス操作では、"mousemove"とやってきましたが、 タッチ操作は、それらを組み合わせたような処理になります。 まず、touchstartは、画面に指が触れたタイミングのイベントで、keydownと同じようなイベントだと考えてください。 この時に、タッチした座標を記憶しておき、touchendで、画面から指が離れたタイミングで、記憶しておいた変数を開放(削除)しています。 touchmoveは、tauchdownとtouchendの間に指を動かしたイベントになるので、この動かした座標位置を、ラケットの座標に変換しています。 ただ、少しややこしいのが、touchdownのタイミングで、何故かmousemoveイベントが発動してしまう時があり、ラケットが思わぬ動きをしてしまうことがあるので、 それぞれのイベントで、タッチイベントが使える状態かどうかを判定して、必ずどちらかの処理しか行わないようにするために、mousemove関数と、touchstart関数に、次の処理をいれています。 MAIN.prototype.mousemove = function(e){ if('ontouchstart' in window === true){return;} ... } MAIN.prototype.touchstart = function(e){ if('ontouchstart' in window === false){return;} ... } それぞれに、同じような判定を入れてい対応しているのですが、これは、windowというブラウザのイベント(property)の中に、"ontouchstart"イベントが存在しているかを確認できる判定処理です。 連想配列で同じ処理が使えるので、覚えておくと、コーディングが少し便利になりますよ。 ただし、この処理には一つだけ注意点があって、GoogleChromeブラウザのように、スマホ画面と、PC画面をデバッグモードとして切り替えられるような場合に、 この判定はページを読み込んだ時の判定になるので、読み込んだ後、pcモードからスマホモードにしても、タッチ操作はできなくなってしまいます。 一般的ではないので、これに対応する必要はないのですが、デバッグなどで、モードを切り替えた場合には、一度ブラウザをリロードするという事をルール付けておけばいいでしょう。

まとめ

とりあえず、今回までの処理で、操作をする処理は完了になります。 これだけでも、なんとなく、ゲームが出来上がったイメージができたんではないでしょうか? 操作感って大事ですよね。 そして次は、ボールを動かしてみたいと思います。 一気にゲーム感が増しますよ。 お楽しみに!!!

人気の投稿

このブログを検索

ごあいさつ

このWebサイトは、独自思考で我が道を行くユゲタの少し尖った思考のTechブログです。 毎日興味がどんどん切り替わるので、テーマはマルチになっています。 もしかしたらアイデアに困っている人の助けになるかもしれません。

ブログ アーカイブ