Webブラウザで簡単ゲームを作ってみよう: 壁打ちテニス編 #5「スムーズなカーソル操作の追求」

2021年10月11日

テクノロジー

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)); }; 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); }; // 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; }; switch(document.readyState){ case "complete" : new MAIN();break; default : window.addEventListener("load" , (function(options){new MAIN()}).bind(this));break; } })();

解説

まず最初に、キーボードのキーを押しっぱなしにすると、少し間が相手から、「ダッ・・・ダダダダ...」というような感じで、文字が繰り返し入力されてしまいますが、 これを防ぐ時に、取得したイベントの"repeat"というプロパティがtrueになっている時に、押しっぱなしで繰り返し入力されているという、フラグ判定ができます。 ということで、MAIN.prototype.keydown関数内の、次の処理で、繰り返し処理は、無効になるようにしています。 if(e.repeat === true){return;} これでは、押しっぱなしの時に、ラケットが動いてくれないので、初回の処理の時に、setTimeoutを動作させておきます。 MAIN.prototype.racket_move関数を少し改造して、その関数の最後にsetTimeoutを追加しておきました。 setTimeoutという関数がわからない人のために説明をしておくと、指定した時間がきたら、してした関数を実行するという命令です。 似たような機能でsetIntervalというのもありますが、setTimeoutは、時間が来たら1回だけ関数を実行するのにたいして、setIntervalは、指定時間ごとに連続して関数を実行してくれます。 この場合は、すぐに停止できるように、setTimeoutを実行するようにしています。 細かい動きに対応できるように、前回offset変数でやり取りしていた値を、this.racket_offsetという、MAIN関数のみ使えるグローバル変数をセットしました。 こうしたグローバル変数は、プログラムを見てどういう働きをしているか追っていくのが、慣れないと難しいのですが、何の値が入っているか想像しながら追って、理解出来るようになりましょう。 ちなみに、setTimeoutの最後の10という引数の値を返ると、ラケットのスピードが早くなったり遅くなったりします。 この値の感覚も合わせて自分で色々な値を入れて実験してみましょう。

デモ

まとめ

前回と比べて、かなりスムーズな動きのラケットになっていると思いますが、いかがでしたでしょうか? こういう、ちょっとしたインターフェイスに気を配る事もプログラミングにとっては重要な要素で有ることを覚えておきましょう。 めんどくさいと思って、後回しにしたり、なんなら「仕様です」で片付けてしまうと、レベルがそこまでのプログラマーという感じで人から見下されてしまいます。 そんな、かゆいところに手が届くプログラミングに気がつく人と気が付かない人、あなたはどちらになりたいですか?

人気の投稿

このブログを検索

ごあいさつ

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

ブログ アーカイブ