ゲームライブラリ構築までの道「ブロック崩し編」#3 : ラケットを動かしてボールを打ち返す

2020年7月6日

Javascript テクノロジー プログラミング 特集

t f B! P L
お気に入りの腕時計のベルトが切れてしまった、ユゲタです。 左手にapplewatch、右手に睡眠計測用の時計を、何年もつけていたんですが、睡眠計測用の時計は、まあまあ安物をネットで買ったため、同じ商品はもう販売すらしておらず、ベルトが手に入らない状態です。 手作りしてもいいんですが、なかなか形状が入り組んでいて、一筋縄に行かない感じです。 そもそも、applewatchで細かな睡眠計測ができれば、なんの問題もないんですが、いっそのこと評価の高い有料アプリを購入してもいいかもしれませんね。

本日のIT謎掛け

「睡眠計測アプリ」と、かけまして・・・ 「脱獄計画」と、ときます。 そのココロは・・・ 毎日継続して、ハカる事(測る、企てるの意味の謀る)が重要ですね。

本日の目的

壁打ちテニスの3回目は、ラケットを動かして、ボールを打ち返します。 キーボードカーソルの左右キーと、マウスの挙動と、スマホ用に、touchmoveの3つに対応させます。 どれもイベント操作になるので、それぞれのやり方のライブラリを作ることを念頭に考えてコーディングしてみたいと思います。

ソースコード

(function(){ var __options = { canvas : "#mycanvas", width : 400, height : 600, wall : { w : 400 , h : 600 , color_stroke : "transparent" , color_fill : "#382B8C" , size : 20 }, ball : { x : 50 , y : 50 , r : 10 , color_stroke : "transparent" , color_fill : "#F2B5A7" , moveX : 2 , moveY : 2 }, bar : { w : 60 , h : 10 , color_stroke : "transparent" , color_fill : "#958ABF" , moveX : 12 }, $:0 }; var MAIN = function(){ if(!this.check()){ alert("htmlに指定のcanvasがありません。"); return; } this.init(); this.draw(); this.animation_set(); this.event_set(); }; MAIN.prototype.getCanvas = function(){ if(typeof this.canvas === "undefined"){ this.canvas = document.querySelector(__options.canvas); } return this.canvas; }; MAIN.prototype.getContext = function(){ if(typeof this.ctx === "undefined"){ var canvas = this.getCanvas(); if(!canvas){return null;} this.ctx = canvas.getContext("2d"); } return this.ctx; }; MAIN.prototype.check = function(){ var ctx = this.getContext(); if(ctx){ return true; } else{ return false; } }; MAIN.prototype.init = function(){ __options.wall.w = window.innerWidth < __options.wall.w ? window.innerWidth : __options.wall.w; __options.wall.h = window.innerHeight < __options.wall.h ? window.innerHeight : __options.wall.h; // 画面サイズ調整 var canvas = this.canvas; canvas.setAttribute("width" , __options.wall.w); canvas.setAttribute("height" , __options.wall.h); }; MAIN.prototype.ctx_clear = function(){ this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); }; MAIN.prototype.draw = function(){ this.ctx_clear(); this.draw_wall(); this.draw_bar(); this.draw_ball(); }; MAIN.prototype.draw_wall = function(){ var ctx = this.ctx; ctx.strokeStyle = __options.wall.color_stroke; ctx.strokeWidth = __options.wall.color_stroke === "transparent" ? 0 : 1; ctx.fillStyle = __options.wall.color_fill; ctx.beginPath(); ctx.moveTo(0,0); ctx.lineTo(__options.wall.w , 0); ctx.lineTo(__options.wall.w , __options.wall.h); ctx.lineTo(__options.wall.w - __options.wall.size , __options.wall.h); ctx.lineTo(__options.wall.w - __options.wall.size , __options.wall.size); ctx.lineTo(__options.wall.size , __options.wall.size); ctx.lineTo(__options.wall.size , __options.wall.h); ctx.lineTo(0 , __options.wall.h); ctx.lineTo(0 , 0); ctx.stroke(); ctx.fill(); }; MAIN.prototype.draw_bar = function(){ var ctx = this.ctx; ctx.strokeStyle = __options.bar.color_stroke; ctx.strokeWidth = __options.bar.color_stroke === "transparent" ? 0 : 1; ctx.fillStyle = __options.bar.color_fill; __options.bar.x = __options.bar.x ? __options.bar.x : (__options.wall.w - __options.bar.w) / 2; __options.bar.y = __options.wall.h - __options.bar.h - 50; ctx.fillRect(__options.bar.x , __options.bar.y , __options.bar.w , __options.bar.h); }; MAIN.prototype.draw_ball = function(){ var ctx = this.ctx; ctx.strokeStyle = __options.ball.color_stroke; ctx.strokeWidth = __options.ball.color_stroke === "transparent" ? 0 : 1; ctx.fillStyle = __options.ball.color_fill; ctx.beginPath(); __options.ball.x = __options.ball.x || __options.wall.w / 2; __options.ball.y = __options.ball.y || __options.wall.h / 2; ctx.arc( __options.ball.x , __options.ball.y , __options.ball.r, 0, Math.PI * 2 ); ctx.fill(); }; MAIN.prototype.animation_set = function(){ new LIB().anim((function(e){this.animation(e)}).bind(this) , 50); }; MAIN.prototype.animation = function(timestamp){ if (!this.time_start){ this.time_start = timestamp; } // debug用(play上限秒数) // if(timestamp - this.time_start > 30000){return;} this.ball_move(); this.draw(); this.animation_set(); }; MAIN.prototype.ball_move = function(){ __options.ball.x += __options.ball.moveX; __options.ball.y += __options.ball.moveY; this.collision(); }; // 当たり判定(壁、ラケット) MAIN.prototype.collision = function(){ // 壁判定 -- // <- : left if(__options.ball.x - __options.ball.r < __options.wall.size){console.log("left"); __options.ball.x = __options.wall.size + __options.ball.r; __options.ball.moveX = __options.ball.moveX * -1; } // ^ : top if(__options.ball.y - __options.ball.r < __options.wall.size){console.log("top"); __options.ball.y = __options.wall.size + __options.ball.r; __options.ball.moveY = __options.ball.moveY * -1; } // -> : right if(__options.ball.x + __options.ball.r > __options.wall.w - __options.wall.size){console.log("right"); __options.ball.x = __options.wall.w - __options.wall.size - __options.ball.r; __options.ball.moveX = __options.ball.moveX * -1; } // v : bottom (test用) if(__options.ball.y + __options.ball.r > __options.wall.h){console.log("bottom"); __options.ball.y = __options.wall.h - __options.ball.r; __options.ball.moveY = __options.ball.moveY * -1; } // ラケット判定 -- // ball-direct-under if(__options.ball.moveY > 0 && __options.ball.y + __options.ball.r > __options.bar.y && __options.ball.y + __options.ball.r < __options.bar.y + __options.bar.h && __options.bar.x < __options.ball.x && __options.bar.x + __options.bar.w > __options.ball.x){console.log("racket-top"); __options.ball.moveY = __options.ball.moveY * -1; } // ball-direct-over else if(__options.ball.moveY < 0 && __options.ball.y - __options.ball.r < __options.bar.y + __options.bar.h && __options.ball.y - __options.ball.r > __options.bar.y && __options.bar.x < __options.ball.x && __options.bar.x + __options.bar.w > __options.ball.x){console.log("racket-bottom"); __options.ball.moveY = __options.ball.moveY * -1; } }; MAIN.prototype.event_set = function(){ new LIB().event(window , "keydown" , (function(e){this.keydown(e)}).bind(this)); new LIB().event(window , "mousemove" , (function(e){this.mousemove(e)}).bind(this)); new LIB().event(window , "touchmove" , (function(e){this.touchmove(e)}).bind(this)); new LIB().event(window , "touchend" , (function(e){this.touchend(e)}).bind(this)); }; MAIN.prototype.bar_limit = function(){ if(__options.bar.x < __options.wall.size){ __options.bar.x = __options.wall.size; } if(__options.bar.x + __options.bar.w > __options.wall.w - __options.wall.size){ __options.bar.x = __options.wall.w - __options.wall.size - __options.bar.w; } }; MAIN.prototype.keydown = function(e){ switch(e.keyCode){ case 37: // <- case 'ArrowLeft': __options.bar.x -= __options.bar.moveX; break; case 39: // -> case 'ArrowRight': __options.bar.x += __options.bar.moveX; break; } this.bar_limit(); this.draw(); }; MAIN.prototype.mousemove = function(e){ this.mousePos = this.mousePos || e.clientX; __options.bar.x += e.clientX - this.mousePos; this.mousePos = e.clientX; this.bar_limit(); this.draw(); }; MAIN.prototype.touchmove = function(e){ if(!e || !e.touches || e.touches.length > 1){ this.mousePos = null; return; } this.mousePos = typeof this.mousePos === "number" ? this.mousePos : e.touches[0].clientX; __options.bar.x += e.touches[0].clientX - this.mousePos; this.mousePos = e.touches[0].clientX; this.bar_limit(); this.draw(); }; MAIN.prototype.touchend = function(e){ this.mousePos = null; }; var LIB = function(){}; LIB.prototype.event = function(target, mode, func , flg){ flg = (flg) ? flg : false; if (target.addEventListener){target.addEventListener(mode, func, flg)} else{target.attachEvent('on' + mode, function(){func.call(target , window.event)})} }; LIB.prototype.anim = function(func , time){ if(window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame){ window.requestAnimationFrame(func); } else{ anim_flg = setTimeout(func , time); } }; LIB.prototype.construct = function(){ switch(document.readyState){ case "complete" : new MAIN();break; case "interactive" : this.event(window , "DOMContentLoaded" , (function(){new MAIN()}).bind(this));break; default : this.event(window , "load" , (function(){new MAIN()}).bind(this));break; } }; new LIB().construct(); })();

本日のまとめ

複数のイベントによって、ラケットの動きを制御していますが、それぞれのイベントでのプログラムが全く違うという点が、今回のポイントになります。 mousemoveとtouchmoveは、動きとしては似ていますが、touchendも必要になる点が今回少し苦労した箇所になります。 この処理を入れないと、2回めにタッチして動かす時に、1回目のタッチの座標を保持してしまうため、touchendで、値をクリアして上げる必要があります。 あと、コリジョン(当たり判定)処理関数を独立化させて、壁判定と、ラケット判定をそれぞれ記述しました。 ただ、ベタに書いているので、あまり効率的ではないですが、今のところはこれでOKにしておきましょう。 このプログラムの発展型として、ブロック崩しができますが、その際には、それぞれのブロックと、ラケットの処理を効率化するのと、もう少し演算処理をしっかりやって、シンプルなコードにする必要がありますね。 次回は、ゲーム開始と終了の処理をつけてみたいと思います。お楽しみに。 全体のソースは、githubにpushしているので、そちらから取得してください。 https://github.com/yugeta/game_block

このブログを検索

プロフィール

自分の写真
プログラミングとサーバーを心の底から楽しむクリエーターです。 経営者であり、開発者でもありますが、得意としているのは、アイデア創出で、出来高は無限大です。

ブログ アーカイブ

QooQ