【JavaScriptを好きになろう】JavaScriptでスマホ対応のピンポンゲームを作ろう



【JavaScriptを好きになろう】JavaScriptでスマホ対応のピンポンゲームを作ろう

前回 「JavaScriptでピンポンゲームを作ろう」 でご紹介させて頂いだ Webベースのピンポンゲーム、パソコンでは操作可能ですが、スマホでは ❌。

今回はこちらのピンポンゲームをスマホでもプレイできるようにセットしてみたいと思います。

目次
  1. 【JavaScriptを好きになろう】JavaScriptでスマホ対応のピンポンゲームを作ろう
  2. 今回の目標:モバイル&パソコンでプレイ可能なJavaScriptピンポンゲーム
  3. ブラウザ上の画面タップをJavaScriptで認識してもらう
  4. スマホ用の操作ボタンを用意
  5. ボタン・タップでバーを操作
  6. ピンポンゲームに操作ボタンを追加
  7. 実装
  8. まとめ

【JavaScriptを好きになろう】JavaScriptでスマホ対応のピンポンゲームを作ろう

今回の目標:モバイル&パソコンでプレイ可能なJavaScriptピンポンゲーム

https://pythonchannel.com/myapp/pingpong

一般的な JavaScript ゲーム、実はパソコンでは操作可能なもののスマホには対応していないケースがほとんど。

今回は、スマホの場合には上図のようにバーの移動ボタンを表示し、操作できるようにしてみました。また Web ベースのピンポンゲームになりますので、当然 PC で利用されるケースも想定されます。

その場合は操作ボタンを非表示にし、パソコンのキーボード操作でバーを動かせるように制御してみました。

パソコンの場合はボタンを非表示に

まずはスマホのタップをブラウザが認識するところからはじめ、ボタンタップでバーを動かし、そして最終的に従来のパソコン用ピンポンゲームにタップアクションをセットする流れでご紹介したいと思います。

ブラウザ上の画面タップをJavaScriptで認識してもらう

上図のコードを今確認する

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8" />
        <style>
            h1{
                text-align:center;
            }
            canvas {
                background: #eee;
                }
            #canvas-wrapper{
                width:480px;
                margin:0 auto;
            }
        </style>
    </head>
<body>
    <h1>タッチのみ</h1>
    <div id="canvas-wrapper">
    <canvas id="myCanvas" width="480" height="320"></canvas>
    </div>
    <script>
        var canvas = document.getElementById("myCanvas");
        var ctx = canvas.getContext("2d");
        canvas.addEventListener("touchstart", touchStart);
        function touchStart(e){
            console.log(e.touches);
        }
    </script>
</body>
</html>

ブラウザ上の画面タップを JavaScript で認識してもらうためには、 addEventListener() の touchstarttouched という機能が利用可能。

  • touchstart ・・・ タップした時
  • touched ・・・ タップ離した時
  • touchmove ・・・ タップしたまま移動中
  • touchcancel ・・・ タップがキャンセルされた時

試しにピンポンゲームを構成する <canvas> 内をタップすると、 JavaScript が認識するようにしてみると上図の様に。

<canvas> 部分をタップするとコンソールログに反応の様子が出力され、 <canvas> 以外をタップしても何も起きません。上図コードを実際にコピペして、ご自身のブラウザでも確かめてみて下さい。

タッチイベント(タップイベント)のプログラムを少し見てみますと、26行目の canvas.addEventListener("touchstart", touchStart);"touchstart" でタッチ内容を指定し、 それが行われた時の処理内容を function touchStart(e) に取りまとめ。

今はログを出力しているだけですが、今回のピンポンゲームを想定すると 「タッチ(タップ)されたらバーを移動」 という処理を行っていきます。

touchstart 以外の touchedtouchmove などを行う場合のコードは以下のように。

上図のコードを今確認する

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8" />
        <style>
            h1{
                text-align:center;
            }
            canvas {
                background: #eee;
                }
            #canvas-wrapper{
                width:480px;
                margin:0 auto;
            }
        </style>
    </head>
<body>
    <h1>タッチ 3種</h1>
    <div id="canvas-wrapper">
    <canvas id="myCanvas" width="480" height="320"></canvas>
    </div>
    <script>
        var canvas = document.getElementById("myCanvas");
        var ctx = canvas.getContext("2d");
        canvas.addEventListener("touchstart", touchStart);
        function touchStart(e){
            console.log('タップした:' + e.touches);
        }
        canvas.addEventListener("touchend", touchEnd);
        function touchEnd(e){
            console.log('タップ離した:' + e.touches);
        }
        canvas.addEventListener("touchmove", touchMove);
        function touchMove(e){
            console.log('タップしたまま移動中:' + e.touches);
        }
    </script>
</body>
</html>

パソコンで閲覧中の方は、コードをコピペして、ブラウザのコンソールログを上図の様に確かめてみて下さい。 尚、今回は touchstarttouched の 2つを使い、 touchmove は使用しません。こちらの touchmove は、線を引いたりするときのお絵かきに使えそうですね。

スマホ用の操作ボタンを用意

上図のコードを今確認する

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8" />
        <style>
            h1{
                text-align:center;
            }
            canvas {
                background: #eee;
                }
            #canvas-wrapper{
                max-width:900px;
                margin:0 auto;
            }
            .canvas-responsive{
                /* width:480px;
                margin:0 auto; */
                width:100%;
            }

            .button {
                display: inline-block;
                vertical-align: middle;
                text-align:center;
                margin:32px;
            }

            .button a {
                display: inline-block;
                border-radius: 50%;
            }

            .button a:hover .left,  a:hover .right{
                border: 0.5em solid #e74c3c;
            }

            .button a:hover .left:after,  a:hover .right:after {
                border-top: 0.5em solid #e74c3c;
                border-right: 0.5em solid #e74c3c;
            }

            .left {
                display: inline-block;
                width: 4em;
                height: 4em;
                border: 0.5em solid #333;
                border-radius: 50%;
                margin-right: 1.5em;
            }

            .left:after {
                content: '';
                display: inline-block;
                margin-top: 1.05em;
                margin-left: 0.6em;
                width: 1.4em;
                height: 1.4em;
                border-top: 0.5em solid #333;
                border-right: 0.5em solid #333;
                -moz-transform: rotate(-135deg);
                -webkit-transform: rotate(-135deg);
                transform: rotate(-135deg);
            }

            .right {
                display: inline-block;
                width: 4em;
                height: 4em;
                border: 0.5em solid #333;
                border-radius: 50%;
                margin-left: 1.5em;
            }

            .right:after {
                content: '';
                display: inline-block;
                margin-top: 1.05em;
                margin-left: -0.6em;
                width: 1.4em;
                height: 1.4em;
                border-top: 0.5em solid #333;
                border-right: 0.5em solid #333;
                -moz-transform: rotate(45deg);
                -webkit-transform: rotate(45deg);
                transform: rotate(45deg);
            }
        </style>
    </head>
<body>
    <h1>タッチ ボタンの用意</h1>
    <div id="canvas-wrapper">
    <canvas id="myCanvas" class="canvas-responsive"></canvas>
        <div class="button" style="float:left">
            <a href="#">
                <span class="left"></span>
            </a>
        </div>

        <div class="button" style="float:right">
            <a href="#">
                <span class="right"></span>
            </a>
        </div>
    </div>

    <script type="text/javascript" src="https://pythonchannel.com/static/js/jquery.min.js"></script>

    <script>
        function resize(){    
          $(".canvas-responsive").outerHeight($(window).height()/2-$(".canvas-responsive").offset().top- Math.abs($(".canvas-responsive").outerHeight(true) - $(".canvas-responsive").outerHeight()));
        }
        $(document).ready(function(){
          resize();
          $(window).on("resize", function(){                      
              resize();
          });
        });
        //outerHeight() 高さ取得、 .offset()  位置、 Math.abs() 絶対値、 

        var canvas = document.getElementById("myCanvas");
        var ctx = canvas.getContext("2d");
        canvas.addEventListener("touchstart", touch);
        canvas.addEventListener("touchend", touch);
        function touch(e){
            console.log(e.touches, e.type);
        }
    </script>  
</body>
</html>

ピンポンゲームのバーを動かすための 「ボタン」 をこちらでは用意します。スマホ画面にはキーボードがありませんので、操作ボタンがあるとゲームしやすいですよね。

ボタンレイアウトはいろいろあると思いますが、今回は CSS でレイアウトしたボタンを使用。

CSS レイアウトでボタンを表示することで、カラーやサイズを制御しやすい特徴がありますね。

次はこのボタンをタップしたらログの出力、という風にプログラムを組んでいきます。

ボタン・タップでバーを操作

上図のコードを今確認する

<!-- ボタン押しっぱなしにすると、ずっと移動中となりバグる、実機テストでは OK。ただしボタンタップしたままではリンクが開く、 a タグを div に  -->
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8" />
        <style>
            h1{
                text-align:center;
            }
            canvas {
                background: #eee;
                }
            #canvas-wrapper{
                max-width:900px;
                margin:0 auto;
            }
            .canvas-responsive{
                /* width:480px;
                margin:0 auto; */
                width:100%;
            }

            .button {
                display: inline-block;
                vertical-align: middle;
                text-align:center;
                margin:32px;
            }

            .button a {
                display: inline-block;
                border-radius: 50%;
            }

            .button a:hover .left,  a:hover .right{
                border: 0.5em solid #e74c3c;
            }

            .button a:hover .left:after,  a:hover .right:after {
                border-top: 0.5em solid #e74c3c;
                border-right: 0.5em solid #e74c3c;
            }

            .left {
                display: inline-block;
                width: 4em;
                height: 4em;
                border: 0.5em solid #333;
                border-radius: 50%;
                margin-right: 1.5em;
            }

            .left:after {
                content: '';
                display: inline-block;
                margin-top: 1.05em;
                margin-left: 0.6em;
                width: 1.4em;
                height: 1.4em;
                border-top: 0.5em solid #333;
                border-right: 0.5em solid #333;
                -moz-transform: rotate(-135deg);
                -webkit-transform: rotate(-135deg);
                transform: rotate(-135deg);
            }

            .right {
                display: inline-block;
                width: 4em;
                height: 4em;
                border: 0.5em solid #333;
                border-radius: 50%;
                margin-left: 1.5em;
            }

            .right:after {
                content: '';
                display: inline-block;
                margin-top: 1.05em;
                margin-left: -0.6em;
                width: 1.4em;
                height: 1.4em;
                border-top: 0.5em solid #333;
                border-right: 0.5em solid #333;
                -moz-transform: rotate(45deg);
                -webkit-transform: rotate(45deg);
                transform: rotate(45deg);
            }
        </style>
    </head>
<body>
    <h1>ボタンタップでバーを動かす</h1>
    <div id="canvas-wrapper">
    <canvas id="myCanvas" class="canvas-responsive"></canvas>
        <div id="leftButton" class="button" style="float:left">
            <a href="#">
                <span class="left"></span>
            </a>
        </div>

        <div id="rightButton" class="button" style="float:right">
            <a href="#">
                <span class="right"></span>
            </a>
        </div>
    </div>

    <script type="text/javascript" src="https://pythonchannel.com/static/js/jquery.min.js"></script>

    <script>
        // canvas のレスポンシブ処理
        function resize(){    
          $(".canvas-responsive").outerHeight($(window).height()/2-$(".canvas-responsive").offset().top- Math.abs($(".canvas-responsive").outerHeight(true) - $(".canvas-responsive").outerHeight()));
        }
        $(document).ready(function(){
          resize();
          $(window).on("resize", function(){                      
              resize();
          });
        });
        //outerHeight() 高さ取得、 .offset()  位置、 Math.abs() 絶対値、 


        // ゲーム バーのみ
        var canvas = document.getElementById("myCanvas");
        var ctx = canvas.getContext("2d");
        var paddleHeight = 10;  //バーのサイズ
        var paddleWidth = 75;  //バーのサイズ
        var paddleX = (canvas.width-paddleWidth)/2;  //バーの位置、中央から
        var rightPressed = false;  //右 移動ボタンをリセット
        var leftPressed = false;  //左 移動ボタンをリセット

        // 右左のボタン
        //var canvas = document.getElementById("myCanvas");
        //var ctx = canvas.getContext("2d");
        var leftButton = document.getElementById("leftButton");
        leftButton.addEventListener("touchstart", touchLeft);
        leftButton.addEventListener("touchend", touchLeft);
        function touchLeft(e){
            console.log('左はば' + canvas.width)
            console.log('左:' + e.touches, e.type);
            if(e.type == 'touchstart'){
                leftPressed = true;
            } else if(e.type == 'touchend'){
                leftPressed = false;
            }
        }
        var rightButton = document.getElementById("rightButton");
        rightButton.addEventListener("touchstart", touchRight);
        rightButton.addEventListener("touchend", touchRight);
        function touchRight(e){
            console.log('右はば' + canvas.width)
            console.log('右:' + e.touches, e.type);
            if(e.type == 'touchstart'){
                rightPressed = true;
            } else if(e.type == 'touchend'){
                rightPressed = false;
            }
        }

/*
        document.addEventListener("keydown", keyDownHandler, false); //ボタン押されたら...
        document.addEventListener("keyup", keyUpHandler, false);  //ボタンから指離れたら...

        function keyDownHandler(e) {
            if(e.key == "Right" || e.key == "ArrowRight") {
                rightPressed = true;  //右矢印ボタンが押されたことをコンピューターに伝達
            }
            else if(e.key == "Left" || e.key == "ArrowLeft") {
                leftPressed = true;  //左矢印ボタンが押されたことをコンピューターに伝達
            }
        }

        function keyUpHandler(e) {
            if(e.key == "Right" || e.key == "ArrowRight") {
                rightPressed = false;  //右矢印ボタンを押していない状態をコンピューターに伝達
            }
            else if(e.key == "Left" || e.key == "ArrowLeft") {
                leftPressed = false;  //左矢印ボタンを押していない状態をコンピューターに伝達
            }
        }
*/
        function drawPaddle() {
            ctx.beginPath();
            ctx.rect(paddleX, canvas.height-paddleHeight*5, paddleWidth, paddleHeight);
            ctx.fillStyle = "blue";
            ctx.fill();
            ctx.closePath();
        }

        function draw() {
            ctx.clearRect(0, 0, canvas.width, canvas.height);
            drawPaddle();
            if(rightPressed) {  //右矢印押された場合
                paddleX += 20;  // バーの動きスピード
                console.log('→ 右移動中 横の位置 paddleX :' + paddleX)
                if (paddleX + paddleWidth > canvas.width){ //右にはみ出るまでの処理
                    paddleX = canvas.width - paddleWidth;
                }
            }
            else if(leftPressed) {  //左矢印押された場合
                paddleX -= 20; // バーの動きスピード
                console.log('← 左移動中 横の位置 paddleX :' + paddleX)
                if (paddleX < 0){  //左にはみ出たら 0 に
                    paddleX = 0;
                }
            }
        }
        setInterval(draw, 50);
    </script>
</body>
</html>

先ほど作成したボタンを使って、タップしたらバーが動くようにしてみました。

上記コードをコピペすれば、上図の様にログが表示され、バーが動きます。バーの位置情報をログ出力するようにしていますので、ちょっとプログラム感、ありますよね。

バーの動き制御については、以前作成した 「JavaScriptでピンポンゲームを作ろう」 の addEventListener("keydown")addEventListener("keyup") の部分を addEventListener("touchstart") に変更しただけ。

パット見た目はいろいろ変更点があって大変そうですが、プログラムの意味としてはシンプルで、キーボード操作をタップイベントに置き換えただけ。キーボードの右が押されたら 変数:rightPressed を true 、ボタンタップの場合も 右ボタンが touchstart されたら 変数:rightPressed を true という内容。

プログラムの制御内容を理解していると、いろいろ応用ができるいい例ですね。

尚、本プログラム、パソコン上のブラウザ検証機能で操作する場合と実機で操作する場合では、ボタンを押しっぱなしにした場合に違いが。

例えば、パソコンのブラウザ検証機能で上記コードを実行し、ボタンを押しっぱなしにすると、バグります。反対方向のボタンを押しても、動きません。 しかし、同じコードを実機で実行するとバグが発生することなく、スムーズに動きます。

実際にタップイベントを実装した Webアプリを開発する際は、実機を持ちて確認することをオススメしますね。

また画面上のボタン操作に <a> タグを使っていたら上図の様にボタン操作でリンク操作のポップアップが開きます。これではゲームしにくいので、 <div> など要素をタップしても反応しない HTMLタグ を使うようにしましょう。

ボタンタップ長押しでも、リンクのポップアップが表示されないようにしたプログラム

上図のコードを今確認する

<!-- バーの動きOK -->
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8" />
        <style>
            h1{
                text-align:center;
            }
            canvas {
                background: #eee;
                }
            #canvas-wrapper{
                max-width:900px;
                margin:0 auto;
            }
            .canvas-responsive{
                /* width:480px;
                margin:0 auto; */
                width:100%;
            }

            .button {
                display: inline-block;
                vertical-align: middle;
                text-align:center;
                margin:32px;
            }

            .button div {
                display: inline-block;
                border-radius: 50%;
            }

            .button div:hover .left{
                border: 0.5em solid #e74c3c;
            }
            .button div:hover .right{
                border: 0.5em solid #e74c3c;
            }
            .button div:hover .left:after{
                border-top: 0.5em solid #e74c3c;
                border-right: 0.5em solid #e74c3c;
            }
            .button div:hover .right:after {
                border-top: 0.5em solid #e74c3c;
                border-right: 0.5em solid #e74c3c;
            }

            .left {
                display: inline-block;
                width: 4em;
                height: 4em;
                border: 0.5em solid #333;
                border-radius: 50%;
                margin-right: 1.5em;
            }

            .left:after {
                content: '';
                display: inline-block;
                margin-top: 1.05em;
                margin-left: 0.6em;
                width: 1.4em;
                height: 1.4em;
                border-top: 0.5em solid #333;
                border-right: 0.5em solid #333;
                -moz-transform: rotate(-135deg);
                -webkit-transform: rotate(-135deg);
                transform: rotate(-135deg);
            }

            .right {
                display: inline-block;
                width: 4em;
                height: 4em;
                border: 0.5em solid #333;
                border-radius: 50%;
                margin-left: 1.5em;
            }

            .right:after {
                content: '';
                display: inline-block;
                margin-top: 1.05em;
                margin-left: -0.6em;
                width: 1.4em;
                height: 1.4em;
                border-top: 0.5em solid #333;
                border-right: 0.5em solid #333;
                -moz-transform: rotate(45deg);
                -webkit-transform: rotate(45deg);
                transform: rotate(45deg);
            }
        </style>
    </head>
<body>
    <h1>ボタンタップでバーを動かす</h1>
    <div id="canvas-wrapper">
    <canvas id="myCanvas" class="canvas-responsive"></canvas>
        <div id="leftButton" class="button" style="float:left">
            <div>
                <span class="left"></span>
            </div>
        </div>

        <div id="rightButton" class="button" style="float:right">
            <div>
                <span class="right"></span>
            </div>
        </div>
    </div>

    <script type="text/javascript" src="https://pythonchannel.com/static/js/jquery.min.js"></script>

    <script>
        // canvas のレスポンシブ処理
        function resize(){    
          $(".canvas-responsive").outerHeight($(window).height()/2-$(".canvas-responsive").offset().top- Math.abs($(".canvas-responsive").outerHeight(true) - $(".canvas-responsive").outerHeight()));
        }
        $(document).ready(function(){
          resize();
          $(window).on("resize", function(){                      
              resize();
          });
        });
        //outerHeight() 高さ取得、 .offset()  位置、 Math.abs() 絶対値、 


        // ゲーム バーのみ
        var canvas = document.getElementById("myCanvas");
        var ctx = canvas.getContext("2d");
        var paddleHeight = 10;  //バーのサイズ
        var paddleWidth = 75;  //バーのサイズ
        var paddleX = (canvas.width-paddleWidth)/2;  //バーの位置、中央から
        var rightPressed = false;  //右 移動ボタンをリセット
        var leftPressed = false;  //左 移動ボタンをリセット

        // 右左のボタン
        //var canvas = document.getElementById("myCanvas");
        //var ctx = canvas.getContext("2d");
        var leftButton = document.getElementById("leftButton");
        leftButton.addEventListener("touchstart", touchLeft);
        leftButton.addEventListener("touchend", touchLeft);
        function touchLeft(e){
            console.log('左はば' + canvas.width)
            console.log('左:' + e.touches, e.type);
            if(e.type == 'touchstart'){
                leftPressed = true;
            } else if(e.type == 'touchend'){
                leftPressed = false;
            }
        }
        var rightButton = document.getElementById("rightButton");
        rightButton.addEventListener("touchstart", touchRight);
        rightButton.addEventListener("touchend", touchRight);
        function touchRight(e){
            console.log('右はば' + canvas.width)
            console.log('右:' + e.touches, e.type);
            if(e.type == 'touchstart'){
                rightPressed = true;
            } else if(e.type == 'touchend'){
                rightPressed = false;
            }
        }

/*
        document.addEventListener("keydown", keyDownHandler, false); //ボタン押されたら...
        document.addEventListener("keyup", keyUpHandler, false);  //ボタンから指離れたら...

        function keyDownHandler(e) {
            if(e.key == "Right" || e.key == "ArrowRight") {
                rightPressed = true;  //右矢印ボタンが押されたことをコンピューターに伝達
            }
            else if(e.key == "Left" || e.key == "ArrowLeft") {
                leftPressed = true;  //左矢印ボタンが押されたことをコンピューターに伝達
            }
        }

        function keyUpHandler(e) {
            if(e.key == "Right" || e.key == "ArrowRight") {
                rightPressed = false;  //右矢印ボタンを押していない状態をコンピューターに伝達
            }
            else if(e.key == "Left" || e.key == "ArrowLeft") {
                leftPressed = false;  //左矢印ボタンを押していない状態をコンピューターに伝達
            }
        }
*/
        function drawPaddle() {
            ctx.beginPath();
            ctx.rect(paddleX, canvas.height-paddleHeight*5, paddleWidth, paddleHeight);
            ctx.fillStyle = "blue";
            ctx.fill();
            ctx.closePath();
        }

        function draw() {
            ctx.clearRect(0, 0, canvas.width, canvas.height);
            drawPaddle();
            if(rightPressed) {  //右矢印押された場合
                paddleX += 20;  // バーの動きスピード
                console.log('→ 右移動中 横の位置 paddleX :' + paddleX)
                if (paddleX + paddleWidth > canvas.width){ //右にはみ出るまでの処理
                    paddleX = canvas.width - paddleWidth;
                }
            }
            else if(leftPressed) {  //左矢印押された場合
                paddleX -= 20; // バーの動きスピード
                console.log('← 左移動中 横の位置 paddleX :' + paddleX)
                if (paddleX < 0){  //左にはみ出たら 0 に
                    paddleX = 0;
                }
            }
        }
        setInterval(draw, 50);
    </script>
</body>
</html>

さあバーをボタンで制御できるようになりましたので、あとはこの状態をピンポンゲームにセットすれば OK でしょう。

ピンポンゲームに操作ボタンを追加

上図のコードを今確認する

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8" />
        <style>
            h1{
                text-align:center;
            }
            canvas {
                background: #eee;
                }
            #canvas-wrapper{
                max-width:900px;
                margin:0 auto;
            }
            .canvas-responsive{
                /* width:480px;
                margin:0 auto; */
                width:100%;
            }

            .button {
                display: inline-block;
                vertical-align: middle;
                text-align:center;
                margin:32px;
            }

            .button div {
                display: inline-block;
                border-radius: 50%;
            }

            .button div:hover .left{
                border: 0.5em solid #e74c3c;
            }
            .button div:hover .right{
                border: 0.5em solid #e74c3c;
            }
            .button div:hover .left:after{
                border-top: 0.5em solid #e74c3c;
                border-right: 0.5em solid #e74c3c;
            }
            .button div:hover .right:after {
                border-top: 0.5em solid #e74c3c;
                border-right: 0.5em solid #e74c3c;
            }

            .left {
                display: inline-block;
                width: 4em;
                height: 4em;
                border: 0.5em solid #333;
                border-radius: 50%;
                margin-right: 1.5em;
            }

            .left:after {
                content: '';
                display: inline-block;
                margin-top: 1.05em;
                margin-left: 0.6em;
                width: 1.4em;
                height: 1.4em;
                border-top: 0.5em solid #333;
                border-right: 0.5em solid #333;
                -moz-transform: rotate(-135deg);
                -webkit-transform: rotate(-135deg);
                transform: rotate(-135deg);
            }

            .right {
                display: inline-block;
                width: 4em;
                height: 4em;
                border: 0.5em solid #333;
                border-radius: 50%;
                margin-left: 1.5em;
            }

            .right:after {
                content: '';
                display: inline-block;
                margin-top: 1.05em;
                margin-left: -0.6em;
                width: 1.4em;
                height: 1.4em;
                border-top: 0.5em solid #333;
                border-right: 0.5em solid #333;
                -moz-transform: rotate(45deg);
                -webkit-transform: rotate(45deg);
                transform: rotate(45deg);
            }
        </style>
        <script type="text/javascript" src="https://pythonchannel.com/static/js/jquery.min.js"></script>
    </head>
<body>
    <h1>ピンポンゲーム(スマホもOK)</h1>
    <div id="canvas-wrapper">
    <canvas id="myCanvas" class="canvas-responsive"></canvas>
        <div id="leftButton" class="button" style="float:left">
            <div>
                <span class="left"></span>
            </div>
        </div>

        <div id="rightButton" class="button" style="float:right">
            <div>
                <span class="right"></span>
            </div>
        </div>
    </div>

    <script>
        // canvas のレスポンシブ処理
        function resize(){    
          $(".canvas-responsive").outerHeight($(window).height()/2-$(".canvas-responsive").offset().top- Math.abs($(".canvas-responsive").outerHeight(true) - $(".canvas-responsive").outerHeight()));
        }
        $(document).ready(function(){
          resize();
          $(window).on("resize", function(){                      
              resize();
          });
        });
        //outerHeight() 高さ取得、 .offset()  位置、 Math.abs() 絶対値、 

        var canvas = document.getElementById("myCanvas");
        var ctx = canvas.getContext("2d");
        var ballRadius = 10;
        var x = canvas.width/2;
        var y = canvas.height-30;
        var dx = 2;
        var dy = -2;
        var paddleHeight = 10;
        var paddleWidth = 75;
        var paddleX = (canvas.width-paddleWidth)/2;
        var rightPressed = false;
        var leftPressed = false;
        var brickRowCount = 8;
        var brickColumnCount = 3;
        var brickWidth = 26;
        var brickHeight = 10;
        var brickPadding = 10;
        var brickOffsetTop = 25;
        var brickOffsetLeft = 10;
        var score = 0;

        var bricks = [];
        for(var c=0; c<brickColumnCount; c++) {
          bricks[c] = [];
          for(var r=0; r<brickRowCount; r++) {
            bricks[c][r] = { x: 0, y: 0, status: 1 };
          }
        }


        // 右左のボタン
        //var canvas = document.getElementById("myCanvas");
        //var ctx = canvas.getContext("2d");
        var leftButton = document.getElementById("leftButton");
        leftButton.addEventListener("touchstart", touchLeft);
        leftButton.addEventListener("touchend", touchLeft);
        function touchLeft(e){
            console.log('左はば' + canvas.width)
            console.log('左:' + e.touches, e.type);
            if(e.type == 'touchstart'){
                leftPressed = true;
            } else if(e.type == 'touchend'){
                leftPressed = false;
            }
        }
        var rightButton = document.getElementById("rightButton");
        rightButton.addEventListener("touchstart", touchRight);
        rightButton.addEventListener("touchend", touchRight);
        function touchRight(e){
            console.log('右はば' + canvas.width)
            console.log('右:' + e.touches, e.type);
            if(e.type == 'touchstart'){
                rightPressed = true;
            } else if(e.type == 'touchend'){
                rightPressed = false;
            }
        }

        function collisionDetection() {
          for(var c=0; c<brickColumnCount; c++) {
            for(var r=0; r<brickRowCount; r++) {
              var b = bricks[c][r];
              if(b.status == 1) {
                if(x > b.x && x < b.x+brickWidth && y > b.y && y < b.y+brickHeight) {
                  dy = -dy;
                  b.status = 0;
                  score++;
                  if(score == brickRowCount*brickColumnCount) {
                    alert("やったね!");
                    document.location.reload();
                    clearInterval(interval); // ゲーム再スタート
                  }
                }
              }
            }
          }
        }

        function drawBall() {
          ctx.beginPath();
          ctx.arc(x, y, ballRadius, 0, Math.PI*2);
          ctx.fillStyle = "red";
          ctx.fill();
          ctx.closePath();
        }
        function drawPaddle() {
          ctx.beginPath();
          ctx.rect(paddleX, canvas.height-paddleHeight, paddleWidth, paddleHeight);
          ctx.fillStyle = "blue";
          ctx.fill();
          ctx.closePath();
        }
        function drawBricks() {
          for(var c=0; c<brickColumnCount; c++) {
            for(var r=0; r<brickRowCount; r++) {
              if(bricks[c][r].status == 1) {
                var brickX = (r*(brickWidth+brickPadding))+brickOffsetLeft;
                var brickY = (c*(brickHeight+brickPadding))+brickOffsetTop;
                bricks[c][r].x = brickX;
                bricks[c][r].y = brickY;
                ctx.beginPath();
                ctx.rect(brickX, brickY, brickWidth, brickHeight);
                ctx.fillStyle = "black";
                ctx.fill();
                ctx.closePath();
              }
            }
          }
        }
        function drawScore() {
          ctx.font = "16px Arial";
          ctx.fillStyle = "blue";
          ctx.fillText("点数: "+score, 8, 20);
        }

        function draw() {
          ctx.clearRect(0, 0, canvas.width, canvas.height);
          drawBricks();
          drawBall();
          drawPaddle();
          drawScore();
          collisionDetection();

          if(x + dx > canvas.width-ballRadius || x + dx < ballRadius) {
            dx = -dx;
          }
          console.log('y値:' + y + dy);
          if(y + dy < ballRadius) {
            dy = -dy;
          }
          else if(y + dy > canvas.height-ballRadius) {
            if(x > paddleX && x < paddleX + paddleWidth) {
              dy = -dy;
            }
            else {
              alert("まけ");
              document.location.reload();
              clearInterval(interval); // Needed for Chrome to end game
            }
          }

          if(rightPressed && paddleX < canvas.width-paddleWidth) {
            paddleX += 7;
          }
          else if(leftPressed && paddleX > 0) {
            paddleX -= 7;
          }

          x += dx;
          y += dy;
        }
        var interval = setInterval(draw, 20);
    </script>
</body>
</html>

パソコン用のピンポンゲームのコードとこれまでのボタン操作のコードを利用すると、上図のようなモバイル対応のピンポンゲームに。

非常にシンプルなゲームですが、スマホでも操作でき自分が作ったとなると、ついつい止まらなくなります...

ボールのスピードやバーの幅を変えてゲームを楽しんでみましょう。

ボールのスピード: 最後の方の var interval = setInterval(draw, 20); の 20。 数字を小さくすると早く、大きくすると遅く。
ボールの大きさ: var ballRadius = 10、 数字を大きくするボールサイズ・アップ
バーの幅: var paddleWidth = 75、 数字を大きくするとバーの幅は拡大

これで完成、とも言えますが、パソコンで開いている場合はキーボードでバーを操作できますので、コントロールボタンは不要(下図参照)。

CSS で、パソコン画面でゲームを開いている時はコントロールボタンを表示しないようにセット。そしてキーボードでもバーを移動できるようにプログラムをセット。

上図のコードを今確認する

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8" />
        <style>
            h1{
                text-align:center;
            }
            canvas {
                background: #eee;
                }
            #canvas-wrapper{
                max-width:900px;
                margin:0 auto;
            }
            .canvas-responsive{
                /* width:480px;
                margin:0 auto; */
                width:100%;
            }

            .button {
                display: none;
                vertical-align: middle;
                text-align:center;
                margin:32px;
            }

            .button div {
                display: inline-block;
                border-radius: 50%;
            }

            .button div:hover .left{
                border: 0.5em solid #e74c3c;
            }
            .button div:hover .right{
                border: 0.5em solid #e74c3c;
            }
            .button div:hover .left:after{
                border-top: 0.5em solid #e74c3c;
                border-right: 0.5em solid #e74c3c;
            }
            .button div:hover .right:after {
                border-top: 0.5em solid #e74c3c;
                border-right: 0.5em solid #e74c3c;
            }

            .left {
                display: inline-block;
                width: 4em;
                height: 4em;
                border: 0.5em solid #333;
                border-radius: 50%;
                margin-right: 1.5em;
            }

            .left:after {
                content: '';
                display: inline-block;
                margin-top: 1.05em;
                margin-left: 0.6em;
                width: 1.4em;
                height: 1.4em;
                border-top: 0.5em solid #333;
                border-right: 0.5em solid #333;
                -moz-transform: rotate(-135deg);
                -webkit-transform: rotate(-135deg);
                transform: rotate(-135deg);
            }

            .right {
                display: inline-block;
                width: 4em;
                height: 4em;
                border: 0.5em solid #333;
                border-radius: 50%;
                margin-left: 1.5em;
            }

            .right:after {
                content: '';
                display: inline-block;
                margin-top: 1.05em;
                margin-left: -0.6em;
                width: 1.4em;
                height: 1.4em;
                border-top: 0.5em solid #333;
                border-right: 0.5em solid #333;
                -moz-transform: rotate(45deg);
                -webkit-transform: rotate(45deg);
                transform: rotate(45deg);
            }
            @media (max-width:1000px){
                .button {
                    display: inline-block;
                    vertical-align: middle;
                    text-align:center;
                    margin:32px;
                }
            }
        </style>
        <script type="text/javascript" src="https://pythonchannel.com/static/js/jquery.min.js"></script>
    </head>
<body>
    <h1>ピンポンゲーム(スマホもOK)</h1>
    <div id="canvas-wrapper">
    <canvas id="myCanvas" class="canvas-responsive"></canvas>
        <div id="leftButton" class="button" style="float:left">
            <div>
                <span class="left"></span>
            </div>
        </div>

        <div id="rightButton" class="button" style="float:right">
            <div>
                <span class="right"></span>
            </div>
        </div>
    </div>

    <script>
        // canvas のレスポンシブ処理
        function resize(){    
          $(".canvas-responsive").outerHeight($(window).height()*2/3 -$(".canvas-responsive").offset().top- Math.abs($(".canvas-responsive").outerHeight(true) - $(".canvas-responsive").outerHeight()));
        }
        $(document).ready(function(){
          resize();
          $(window).on("resize", function(){                      
              resize();
          });
        });
        //outerHeight() 高さ取得、 .offset()  位置、 Math.abs() 絶対値、 

        var canvas = document.getElementById("myCanvas");
        var ctx = canvas.getContext("2d");
        var ballRadius = 5;
        var x = canvas.width/2;
        var y = canvas.height-30;
        var dx = 2;
        var dy = -2;
        var paddleHeight = 10;
        var paddleWidth = 75;
        var paddleX = (canvas.width-paddleWidth)/2;
        var rightPressed = false;
        var leftPressed = false;
        var brickRowCount = 8;
        var brickColumnCount = 3;
        var brickWidth = 26;
        var brickHeight = 10;
        var brickPadding = 10;
        var brickOffsetTop = 25;
        var brickOffsetLeft = 10;
        var score = 0;

        var bricks = [];
        for(var c=0; c<brickColumnCount; c++) {
          bricks[c] = [];
          for(var r=0; r<brickRowCount; r++) {
            bricks[c][r] = { x: 0, y: 0, status: 1 };
          }
        }


        // スマホ用 右左のボタン
        //var canvas = document.getElementById("myCanvas");
        //var ctx = canvas.getContext("2d");
        var leftButton = document.getElementById("leftButton");
        leftButton.addEventListener("touchstart", touchLeft);
        leftButton.addEventListener("touchend", touchLeft);
        function touchLeft(e){
            console.log('左はば' + canvas.width)
            console.log('左:' + e.touches, e.type);
            if(e.type == 'touchstart'){
                leftPressed = true;
            } else if(e.type == 'touchend'){
                leftPressed = false;
            }
        }
        var rightButton = document.getElementById("rightButton");
        rightButton.addEventListener("touchstart", touchRight);
        rightButton.addEventListener("touchend", touchRight);
        function touchRight(e){
            console.log('右はば' + canvas.width)
            console.log('右:' + e.touches, e.type);
            if(e.type == 'touchstart'){
                rightPressed = true;
            } else if(e.type == 'touchend'){
                rightPressed = false;
            }
        }

        // パソコン用キーボード操作
        document.addEventListener("keydown", keyDownHandler, false);
        document.addEventListener("keyup", keyUpHandler, false);

        function keyDownHandler(e) {
            if(e.key == "Right" || e.key == "ArrowRight") {
                rightPressed = true;
            }
            else if(e.key == "Left" || e.key == "ArrowLeft") {
                leftPressed = true;
            }
        }

        function keyUpHandler(e) {
            if(e.key == "Right" || e.key == "ArrowRight") {
                rightPressed = false;
            }
            else if(e.key == "Left" || e.key == "ArrowLeft") {
                leftPressed = false;
            }
        }

        function collisionDetection() {
          for(var c=0; c<brickColumnCount; c++) {
            for(var r=0; r<brickRowCount; r++) {
              var b = bricks[c][r];
              if(b.status == 1) {
                if(x > b.x && x < b.x+brickWidth && y > b.y && y < b.y+brickHeight) {
                  dy = -dy;
                  b.status = 0;
                  score++;
                  if(score == brickRowCount*brickColumnCount) {
                    alert("やったね!");
                    document.location.reload();
                    clearInterval(interval); // ゲーム再スタート
                  }
                }
              }
            }
          }
        }

        function drawBall() {
          ctx.beginPath();
          ctx.arc(x, y, ballRadius, 0, Math.PI*2);
          ctx.fillStyle = "red";
          ctx.fill();
          ctx.closePath();
        }
        function drawPaddle() {
          ctx.beginPath();
          ctx.rect(paddleX, canvas.height-paddleHeight, paddleWidth, paddleHeight);
          ctx.fillStyle = "blue";
          ctx.fill();
          ctx.closePath();
        }
        function drawBricks() {
          for(var c=0; c<brickColumnCount; c++) {
            for(var r=0; r<brickRowCount; r++) {
              if(bricks[c][r].status == 1) {
                var brickX = (r*(brickWidth+brickPadding))+brickOffsetLeft;
                var brickY = (c*(brickHeight+brickPadding))+brickOffsetTop;
                bricks[c][r].x = brickX;
                bricks[c][r].y = brickY;
                ctx.beginPath();
                ctx.rect(brickX, brickY, brickWidth, brickHeight);
                ctx.fillStyle = "black";
                ctx.fill();
                ctx.closePath();
              }
            }
          }
        }
        function drawScore() {
          ctx.font = "16px Arial";
          ctx.fillStyle = "blue";
          ctx.fillText("点数: "+score, 8, 20);
        }

        function draw() {
          ctx.clearRect(0, 0, canvas.width, canvas.height);
          drawBricks();
          drawBall();
          drawPaddle();
          drawScore();
          collisionDetection();

          if(x + dx > canvas.width-ballRadius || x + dx < ballRadius) {
            dx = -dx;
          }
          console.log('y値:' + y + dy);
          if(y + dy < ballRadius) {
            dy = -dy;
          }
          else if(y + dy > canvas.height-ballRadius) {
            if(x > paddleX && x < paddleX + paddleWidth) {
              dy = -dy;
            }
            else {
              alert("まけ");
              document.location.reload();
              clearInterval(interval);
            }
          }

          if(rightPressed && paddleX < canvas.width-paddleWidth) {
            paddleX += 7;
          }
          else if(leftPressed && paddleX > 0) {
            paddleX -= 7;
          }

          x += dx;
          y += dy;
        }
        var interval = setInterval(draw, 20);
    </script>
</body>
</html>

これでパソコン、スマホでもピンポンゲームが楽しめるようになりました。

実装

今回のピンポンゲームのプログラムは、 HTML、 CSS、 JavaScript でプログラムしていますので、直ぐにレンタルサーバー等で公開可能。

実際にサーバーにセットした様子が上図の通りで、 https://pythonchannel.com/myapp/pingpong にアクセスするとゲームをプレイできます。

Webサイト担当者としてのスキルが身に付く

CodeCampの無料体験はこちら

まとめ

プログラミング × ゲーム に関する情報は多く提供されている一方、 「プログラミング × ゲーム × スマホ」 に関する情報は少ないように感じています。

一般的にスマホでゲームというと、 Swift や Java を使ったネイティブアプリや Unity を使ったゲーム開発が一般的。しかし、今回の様な JavaScript を使ったゲーム、アプリストアの規制や規約に縛られることなく、 Web上で比較的自由にゲームを公開できます。

マネタライズについても Googleアドセンスやゲーム内課金を自作すればアプリストアに余計な手数料を払うことなく、利益率を高めることが可能に。

「ブログはオワコン」と言われる昨今、こうした既存技術を今のプラットフォームに合わせるような手法が必用なのかもしれませんね。

アプリストアからリジェクションをくらった経験のある方、 JavaScript を習得したい方、是非ご自身でもモバイル対応のピンポンゲームを動かしてみて下さい。

「JavaScript 以前に、プログラムをブラウザで動かす方法が分からない...」 「Ruby や Python に比べて、 JavaScript 頭に入らない...」 という方、一人で JavaScript をクリアするのはチョット大変ですね。そんな時は CodeCamp のオンライン・マンツーマン・レッスンを利用してみませんか?

一人で何時間悩んでも解決しなかったことが、数分で解決するかもしれませんよ。まずは無料体験からお試しください。

関連記事

オシママサラ
この記事を書いた人
オシママサラ
\ 無料体験開催中!/自分のペースで確実に習得!
オンライン・プログラミングレッスンNo.1のCodeCamp