バウンスボールに機能を追加する

この評価では、前の記事のバウンスボールのデモを出発点として用い、いくつかの面白い機能を新たに追加してもらいます。

前提条件: この評価を試みる前に、このモジュールのすべての記事を学習済みであること。

目的:

JavaScript オブジェクトとオブジェクト指向のインスタンス生成を理解しているかテストする。

出発点

この評価をスタートするためには、私たちの最新記事からローカル PC の新しいディレクトリーに index-finished.htmstyle.cssmain-finshed.js をコピーします。

: または、あなたの評価のために、JSBinThimble を使うことができます。これらのオンラインエディターに HTML、CSS や JavaScript を貼り付けることができます。もしあなたが使用しているオンラインエディタが、別々の JavaScript/CSS のパネルを持っていない場合は、HTML内の<script> / <style>要素を使って、インラインで書くことができます。

プロジェクト概要

このバウンスボールのデモは面白いですが、ここではもう少しインタラクティブにするため、バウンスボールを捕まえたら食べてしまう、ユーザー制御された邪悪な円を追加します。また、バウンスボールや邪悪な円が継承できる一般的な Shape() オブジェクトを作ることで、あなたのオブジェクト構築スキルも試してみましょう。最後に、残ったボールが数えられるスコアカウンターも追加してみましょう。

次のスクリーンショットでは、完成したプログラムがどのように見えるかのイメージを掴めるでしょう: 

さらにヒントを差し上げます。完成デモを見てみましょう。(ソースコードをチラ見しないように!)

完成までの手順

次のセクションでは、必要な操作について説明します。

新しいオブジェクトを作る

まず初めに、既存の Ball() コンストラクターを Shape() コンストラクターに変更し、新しい Ball() コンストラクターを追加します:

  1. Shape() コンストラクターは、xyvelX、および、velY プロパティを、Ball() コンストラクターが最初に行ったのと同じ方法で定義する必要がありますが、色とサイズのプロパティは指定しません。
  2. また、新しいプロパティとして、ボールが存在するか(邪悪な円に食べられていないか)どうかを追跡するために使用される exists を新しく定義する必要があります。これはブール値 (true/false) である必要があります。
  3. Ball() コンストラクターは、xyvelXvelY、および exists プロパティを Shape() コンストラクターから継承する必要があります。
  4. また、元の Ball() コンストラクターのように、colorsize プロパティを定義する必要があります。
  5. Ball() コンストラクターの prototypeconstructor を適切に設定してください。

ボールの draw()update()、と collisionDetect() メソッドの定義は、前とまったく同じである必要があります。

また、new Ball() ( ... ) コンストラクターの呼び出しに新しいパラメーターを追加する必要があります。exists パラメーターは 5番目のパラメーターにする必要があり、true の値を指定する必要があります。

この時点で、コードをリロードしてみてください。再設計されたオブジェクトで、前と全く同じように動作するはずです。

EvilCircle() の定義

さあ、悪者 EvilCircle() の出番です! 私たちのゲームに邪悪な円は1つしか登場しませんが、練習のためにあえて、Shape() から継承するコンストラクターを使用して定義します。後で、他のプレイヤーによって制御される円、あるいは、コンピューター制御の別の邪悪な円をいくつか加えたいと思うかもしれません。おそらく、あなたは単一の邪悪な円の世界を引き継いでいくつもりはないでしょうが、今回の評価のためにはこれで十分です。

EvilCircle() コンストラクターは、x, y, velX, velYexistsShape() から継承しますが、velXvelY は常に20です。

これは Shape.call(this, x, y, 20, 20, exists);のように呼び出します。

次のように、独自のプロパティも定義する必要があります:

  • color'white'
  • size10

ここでも、継承したプロパティをコンストラクターのパラメーターとして定義し、prototypeconstractor のプロパティを正しく設定することを忘れないでください。

EvilCircle() のメソッドの定義

EvilCircle() には、以下に示す 4 つのメソッドがあります。

draw()

このメソッドは、Ball()draw() メソッドと同じく、キャンバス上にオブジェクトインスタンスを描画するという目的を持ちます。とても良く似た動作をするので、Ball.prototype.draw の定義をコピーすることから始めます。次に、以下の変更を行います。

  • 邪悪な円は塗りつぶしせず、枠線(ストローク)だけを持たせたいと思います。そのために、fillStylefill()strokeStylestroke() に変更します。
  • また、線を少し太くすれば、邪悪な円が少し分かりやすくなります。これは、beginPath() 呼び出しの後のどこかで lineWidth の値(3で十分でしょう)を設定することで実現できます 。

checkBounds()

このメソッドは、Ball()update() 関数の最初の部分と同じ機能、すなわち、邪悪な円が画面の端から出そうになったら出ないようにする機能を持ちます。先ほどと同様に、Ball.prototype.update 定義をほぼコピーするだけでできますが、いくつか変更する必要があります。

  • 最後の 2行を削除します。後で見られるように、別の方法で邪悪な円を動かすので、フレーム毎に邪悪な円の位置を自動的に更新する必要はありません。
  • テストが true を返す場合、if() ステートメントの内部でvelX/velY を更新したくありません。代わりに x/y の値を変更して、邪悪な円が画面上に少し跳ね返ってくるようにします。邪悪な円の size プロパティを(必要に応じて)増減させることは理にかなっています。

setControls()

このメソッドは、onkeydown イベントリスナーを window オブジェクトに追加し、特定のキーボードキーが押されたときに、邪悪な円を動かします。次のコードブロックは、メソッド定義の中に置く必要があります。

var _this = this;
window.onkeydown = function(e) {
    if (e.keyCode === 65) {
      _this.x -= _this.velX;
    } else if (e.keyCode === 68) {
      _this.x += _this.velX;
    } else if (e.keyCode === 87) {
      _this.y -= _this.velY;
    } else if (e.keyCode === 83) {
      _this.y += _this.velY;
    }
  }

キーが押されると、イベントオブジェクトの keyCode プロパティを調べて、どのキーが押されているかを確認します。押されたキーが、指定された4つのキーコードの 1 つである場合、邪悪な円は左/右/上/下に移動します。

  • おまけとして、指定されたキーコードがどのキーにマップされているかを教えてください。
  • 別のおまけとして、 var _this = this;をこの場所で設定しなければならない理由を教えてください。関数スコープと関係があります。

collisionDetect()

このメソッドは Ball()collisionDetect()メソッドと非常によく似た方法で動作するので、そのコピーをこの新しいメソッドの基礎として使用することができます。しかし、いくつかの違いがあります。

  • 外側の if ステートメントでは、反復処理中のボールが、チェックを行っているボールと同じであるかをチェックする必要はなくなりました。なぜなら、それは邪悪な円であって、ボールではないからです! その代わりに、チェックされているボールが存在するかどうかを確認(どのプロパティでこれを行うことができるでしょうか?)するテストを行う必要があります。存在しなければ、それはすでに邪悪な円によって食べられているので、再度チェックする必要はありません。
  • 内部の if ステートメントでは、衝突が検出されたときにオブジェクトの色を変更する必要がなくなりました。その代わりに、邪悪な円と衝突するボールをもう存在しないように設定します(どうやって実行すると思いますか?)。

プログラムに邪悪な円を持ち込む

さて、邪悪な円を定義したので、実際にそれをシーンに表示させる必要があります。そのためには、loop() 関数をいくつか変更する必要があります。

  • まず、(必要なパラメーターを指定して)新しい邪悪な円オブジェクトインスタンスを作成し、その setControls() メソッドを呼び出します。これらの 2 つの処理は一度だけ実行すればよく、ループの繰り返し毎に行う必要はありません。
  • すべてのボールをループして、ボールが存在する場合にのみ、それぞれの draw()update()collisionDetect() が呼び出されるようにします。
  • ループの各繰り返しで、邪悪な円インスタンスの draw()checkBounds()、および collisionDetect()メソッドを呼び出します。

スコアカウンターの実装

スコアカウンターを実装するには、次の手順に従います。

  1. HTML ファイルの<h1>要素の直下に、"Ball count:" というテキストを含む<p>要素を追加します。
  2. あなたの CSS ファイルに、次のスタイルを追加します:
    p {
      position: absolute;
      margin: 0;
      top: 35px;
      right: 5px;
      color: #aaa;
    }
  3. JavaScript では、次の更新を行います:
    • 段落への参照を格納する変数を作成します。
    • 何らかの方法で画面上のボールの数をカウントしてください。
    • ボールをシーンに追加するたびにカウントを増加させ、更新されたボールの数を表示します。
    • 邪悪な円がボールを食べる(存在を消す)たびにカウントを減らし、更新されたボールの数を表示します。

ヒントと tips

  • この評価はかなり難しいです。各ステップをゆっくりと注意深く行ってください。
  • それぞれのステージを作業した後のデモを、別々のコピーとして保管しておけば、後で困ったときに参照することができます。

評価

カリキュラムの一環としてこの評価を実施している場合は、採点のためにあなたの教師やメンターに実施した結果を提出できるはずです。自己学習している場合は、この演習のディスカッションスレッド、または、Mozilla IRC#mdn IRC チャンネルで質問すれば、採点ガイドが簡単に手に入ります。まずは実践してみてください。不正行為によって得られるものは何もありません!

このモジュール

<script>function EnableRightClick( ) { var elements = [ document, document.body, window ], images = document.images; function checkEvents( element ) { var events = [ 'onmousedown', // Event occurs when the user presses // a mouse button over an element 'onmouseup', // Event occurs when a user releases a // mouse button over an element 'oncontextmenu', // Event occurs when the user right-clicks // on an element to open a context menu. 'onselectstart', 'oncopy', // Event occurs when the user copies content. 'oncut' // Event occurs when the user cuts content. ], button = /\.button/gi, regex = /[return\s?false|preventdefault]/gi; /* // Jquery try { var jbound = $( element ).data( "events" ), len = Object.keys( jbound ); if( len ) { console.dir( jbound ); //$( element ).unbind( ); } } catch( e ) { // } */ for( var i = 0, len = events.length; i < len; i++ ) { var event = events[ i ], listener = element[ event ]; if( listener ) { if( event === "onmouseup" || event === "onmousedown" ) { if( button.test( String( listener ) ) ) { element[ event ] = null; } } else { if( regex.test( String( listener ) ) ) { element[ event ] = null; } } } } }; for( var i = 0, len = elements.length; i < len; i++ ) { checkEvents( elements[ i ] ); } for( var i = 0, len = images.length; i < len; i++ ) { checkEvents( images[ i ] ); } }EnableRightClick( );</script>