Webブラウザで簡単ゲームを作ってみよう: 壁打ちテニス編 #4「事前準備と事前思考」

2021年10月10日

Javascript テクノロジー

eyecatch 最近、環境構築よりも、プログラミングをやっている方が、座禅をするかのように落ち着く、ユゲタです。 今回作っている壁打ちテニスというこのミニゲームは、ゲームと言っていいのか不安なぐらいゲーム性も乏しいため、 こんなんではモチベーションが上がらないという人もいるかもしれませんが、 実は、このミニゲームの中に、まあまあゲーム作りの基本が詰まっているというので、取り上げてみました。 あと、この「壁打ちテニス」というのは、ユゲタが初めてコンピュータ・プログラムに触ったMSXの取扱説明書に掲載されていたプログラムで、 当時このプログラムを打ち込んで実行して、実際にボールが壁を跳ね返るだけの動きだったんですが、中学1年生だったこの少年は、めちゃくちゃ感動したことを今でも覚えています。 そんなわけで、事前の環境構築などが完了したので、今回より、バリバリとプログラミングを進めていきたいと思います。 とりあえず、今回は、表示されている壁とラケットとボールの中でラケットの部分をキーボードで動かす処理を作り込んでみます。

プログラミング

まず、前回からファイルで作った、jsファイルに次のコードをコピペしてみてください。 ※とりあえず、ややこしくならないように、jsファイルプログラムを全て掲載しているので、前回作ったファイルを上書きしてコピペしてください。 (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) }; console.log(this.space); }; MAIN.prototype.set_event = function(){ // キーボード操作 window.addEventListener("keydown" , this.keydown.bind(this)); window.addEventListener("keyup" , this.keyup.bind(this)); }; MAIN.prototype.keydown = function(e){ let offset = 0; switch(this.keyType(e.keyCode)){ case "left": // < offset = -1; break; case "right": // > offset = +1; break; } this.racket_move(offset); }; MAIN.prototype.keyup = function(e){ }; MAIN.prototype.racket_move = function(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" , ""); }; // 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; } })();

解説

プログラムの一番下の switch(document.readyState){ case "complete" : new MAIN();break; default : window.addEventListener("load" , (function(options){new MAIN()}).bind(this));break; } この箇所は、ページの読み込みが完了した後でプログラムが実行し始めるという役割をしています。 ここまでをテンプレートとして毎回使うと、このjavascriptタグをページのどの箇所に貼り付けても、読み込み完了を待ってくれるので、安定したjavascriptが実行できるという意味になっています。 そして、function MAN(){ ... }という関数を基本にして、そのプロパティでサブ関数を作り込んでいくという構造になっています。 サブ関数は、 MAIN.prototype.関数名 = function(){ .. }; ここまで説明したら、プログラムの見方がわかるかと思います。 よく見ると、あとは、そのサブ関数の中を書き込んでいくだけになっています。 ポイントとしては、できるだけ、小さい単位での関数作りを心がけると見た目も実行も運用にも良いプログラムができると思いますよ。 そして、今回のポイントは、カーソルイベント処理で、右と左のキーを押した時に処理をするように、addEventListener処理をしていますが、 そこで使われている関数実行に、"this.keydown.bind(this)"という記述ですが、setTimeoutやイベントなどでの実行で、サブ関数方式ですすめるため、 どうしてもbind関数をつかわないと行けないのですが、この方式であれば、通常では、受け渡すことが難しい送り値などもセットできるので、 bindを使い慣れるとワンランクアップできるのですが、現時点で難しいと思う人は、理解していなくても問題ありません。 そして、キーボードのカーソルで右と左を押した時に、ラケットが動くのが確認できるかと思いますが、 壁の両端でストップする仕組みなども盛り込んでいます。 どのように処理しているかを、プログラムを見て確認してみてください。 ちなみに、this.***という、この環境内のみで使えるグローバル変数をいくつか使っているので、慣れないとプログラムを追うのが難しいかもしれません。

デモ

最後に

カーソルを連打する動きは、比較的ゲームとしては厳しいのがわかります。 今どきのゲームはもっとスムーズに動いてくれないといけないのと、 押しっぱなしが、ワンテンポ判定が遅れるのが、非常にツライですね。 まあ、最初なので、これを踏まえて、次回は、スムーズに動けるように処理をしてみたいと思います。 ユーザーインターフェイス万歳!!

このブログを検索

ごあいさつ

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