--- title: オブジェクト構築の練習 slug: Learn/JavaScript/Objects/Object_building_practice tags: - Article - Beginner - Canvas - CodingScripting - Guide - JavaScript - Learn - Objects - Tutorial - 'l10n:priority' translation_of: Learn/JavaScript/Objects/Object_building_practice ---
ここまでの記事で JavaScript オブジェクトの根幹部に関する理論と文法の詳細についてすべてを見てきて、始めていくのに十分な基礎固めをしました。この記事では実練習を行ない、独自の JavaScript オブジェクトを作っていくための実践をしていきましょう — 楽しくてカラフルなものを。
前提条件: | 基礎的なコンピューターの知識、HTML と CSS への基本的な理解、基礎的な JavaScript の理解 (JavaScript の第一歩と JavaScript の構成要素を参照) とオブジェクト指向JavaScript の基本 (JavaScript オブジェクトの基本を参照)。 |
---|---|
目的: | オブジェクトの使い方とオブジェクト指向のテクニックを実世界のコンテストで練習する。 |
この記事では伝統的な「弾むボール」のデモを作ってみて、JavaScript でどれほどオブジェクトが役に立つかお見せしましょう。小さなボールは画面じゅうを跳ねまわり、それぞれがぶつかると色が変わります。完成したものはこんな風に見えることでしょう:
この例では画面にボールを描くのに Canvas API を使い、画面をアニメーションさせるのに requestAnimationFrame を使います — これらの API について事前の知識は不要です。この記事を読み終わる頃にはこれら API についてもっと知りたくなっているだろうと期待してますが。道中では、イカしたオブジェクトを活用して、ボールを壁で弾ませる、それぞれがぶつかった事を判定する(衝突判定という呼び名で知られています)といった上手いテクニックをいくつかお見せしていきます。
始める前に index.html
, style.css
, と main.js
ファイルのローカルコピーを作成してください。これらにはそれぞれ、以下が含まれています:
<h1>
のスタイルとポジションを指定し、スクロールバーやページ端周辺のマージンを消す(素敵にきれいに見せるため)ためのもの。<canvas>
要素を設定し、これから使うことになる汎用の関数を提供する若干の JavaScript。スクリプトの最初の部分はこんな具合です:
const canvas = document.querySelector('canvas'); const ctx = canvas.getContext('2d'); const width = canvas.width = window.innerWidth; const height = canvas.height = window.innerHeight;
このスクリプトでは<canvas>
要素への参照を取得し、これに対して getContext()
メソッドを使って描画していくためのコンテキストを取得します。得られる定数 (ctx
) はキャンバスの描画可能領域を直接表現しており、ここに二次元の形状を書き込む事ができます。
次に width
と height
二つの定数をセットし、キャンバス要素の幅と高さ(canvas.width
と canvas.height
プロパティで表わされます)をブラウザーのビューポートの幅と高さ(ウェブページが表示される領域です — {{domxref("Window.innerWidth")}} と{{domxref("Window.innerHeight")}} プロパティから取得できます)に等しくします。
変数値をさっと全部同じにするのに、代入が連鎖している事に注意してください — これで全く問題ありません。
初期化スクリプトの最後の部分はこんなのです:
function random(min, max) { const num = Math.floor(Math.random() * (max - min + 1)) + min; return num; }
この関数は二つの数を引数に取り、二つ数の範囲内の乱数を戻します。
我々のプログラムでは画面中を跳ねまわるたくさんのボールがあります。これらのボールはどれも同じルールで動くので、1つのオブジェクトで表わすのが理に叶っています。まずはコードの最後に以下のコンストラクターを追加するところから始めましょう。
function Ball(x, y, velX, velY, color, size) { this.x = x; this.y = y; this.velX = velX; this.velY = velY; this.color = color; this.size = size; }
ここではいくつかの引数を用意し、我々のプログラムの中で個々のボールが動作するのに必要なプロパティを定義しています:
x
、y
座標 — ボールが画面のどこからスタートするか表わす水平と垂直の座標。これは 0(画面左上隅)からブラウザービューポートの幅と高さの(画面右下隅)間の値を取ります。velX
と velY
) — 個々のボールには水平と垂直方向の速度が与えられます。実際にはアニメーションが開始されると、これらの値が x
/y
座標値に定期的に加算され、各フレームでこの値だけ移動していきます。color
— 個々のボールには色がつけられます。size
— 個々のボールには大きさがあります — ピクセルを単位とする半径で表わします。これはプロパティを取り扱いましたが、メソッドはどうしましょう? プログラムの中ではボールに実際に何かさせたいわけです。
まず以下の draw()
メソッドを Ball()
のプロトタイプ(prototype
)に追加しましょう:
Ball.prototype.draw = function() { ctx.beginPath(); ctx.fillStyle = this.color; ctx.arc(this.x, this.y, this.size, 0, 2 * Math.PI); ctx.fill(); }
この関数を使って、以前定義した 2D キャンバスコンテキスト(ctx
)のメンバーを順に呼び出す方法で、ボール自身が画面に自分を描画する方法を教え込みます。コンテキストは紙のようなもので、ペンを使って何か描くように指示したいわけです:
beginPath()
を使って紙に形を描きたいと宣言します。fillStyle
を使って形を何色にしたいか宣言します — ここではボールの color
プロパティを指定します。arc()
メソッドを使って紙に円弧形をなぞります。これの引数は:
x
と y
— ボールの x
、y
プロパティを指定します。size
プロパティです。2 * PI
、これはラジアンで表わした 360度に相当します(ややこしいですがラジアンで指定しなければなりません)。これで一周した円を描けます。もし 1 * PI
までしか指定しなければ、半円(180度)になるでしょう。fill()
メソッドを使って、これはおおよそ、「beginPath()
から描き始めた線描を終了し、描いた領域を前に fillStyle
で指定していた色で塗り潰せ」という指示になります。これでオブジェクトをテストしてみられるようになりました。
let testBall = new Ball(50, 100, 4, 4, 'blue', 10);
testBall.x testBall.size testBall.color testBall.draw()
ボールを座標に表示できるようになりましたが、ボールを実際に移動させるには、何らかの更新するための関数が必要です。JavaScript ファイルの最後に以下のコードを追加し、update()
メソッドを Ball()
の prototype
に追加します:
Ball.prototype.update = function() { if ((this.x + this.size) >= width) { this.velX = -(this.velX); } if ((this.x - this.size) <= 0) { this.velX = -(this.velX); } if ((this.y + this.size) >= height) { this.velY = -(this.velY); } if ((this.y - this.size) <= 0) { this.velY = -(this.velY); } this.x += this.velX; this.y += this.velY; }
関数の頭から 4 つの部分でボールがキャンバスの端に達したかどうかチェックします。もしそうであれば、関連する速度の向きを反転してボールが反対の向きに移動するようにします。つまり例えば、ボールが上方向に移動していたならば(velY
が正)、垂直方向の速度をボールが下方向に移動するように変更します(velY
を負に)。(訳注: 左上が原点、右下が座標の正方向ならば、ボールが上に移動する時の velY は負のはずだけど…)
4 つの場合で、次のことを確認しています:
x
座標がキャンバスの幅より大きいか (ボールは右端から飛び出そうとしている)x
座標が 0 より小さいか (ボールは左端から飛び出そうとしている)y
座標がキャンバスの高さより大きいか (ボールは下端から飛び出そうとしている)y
座標が 0 より小さいか (ボールは上端から飛び出そうとしている)それぞれの場合で計算にボールの size
を含めていますが、これは x
/y
座標はボールの中心ですが、ボールの端のところで周囲から跳ね返って欲しいからです — 跳ね返る前に画面外にめり込んで欲しくないからです。
最後の二行では velX
を x
座標に、velY
を y
座標に加算しています — 結果ボールはこのメソッドが呼ばれる毎に移動します。
とりあえずはここまでで、ちょいとアニメーションさせてみよう!
さあ、楽しい事をやりましょう。では、キャンバスにボールを追加し、アニメーションさせるところから始めましょう。
let balls = []; while (balls.length < 25) { let size = random(10,20); let ball = new Ball( // ball position always drawn at least one ball width // away from the edge of the canvas, to avoid drawing errors random(0 + size,width - size), random(0 + size,height - size), random(-7,7), random(-7,7), 'rgb(' + random(0,255) + ',' + random(0,255) + ',' + random(0,255) +')', size ); balls.push(ball); }
while
ループは、我々の random()
関数で作成したランダムな値を使った新しい Ball()
のインスタンスを作成し、ボールの配列の後ろに push()
して追加していきますが、これは配列中のボールの数が 25 に満たない間まで繰り返されます。balls.length < 25
の数字をいろいろ変えれば表示されるボールの数を増やしたり減らしたりできます。あなたのコンピューターとブラウザーがどれだけ速いかによりますが、ボールを数千にするとアニメーションはかなり遅くなります! 注意してね。
function loop() { ctx.fillStyle = 'rgba(0, 0, 0, 0.25)'; ctx.fillRect(0, 0, width, height); for (let i = 0; i < balls.length; i++) { balls[i].draw(); balls[i].update(); } requestAnimationFrame(loop); }
ものをアニメーションさせるすべてのプログラムには、大概アニメーションループがあり、プログラム内の情報を更新して、アニメーションの各フレームでその結果を表示します。これは大半のゲームや類似するプログラムの基本になります。コード中の loop()
関数は以下の事を行ないます:
fillRect()
で描きます(これの 4 つの引数は始点の座標と、描画する長方形の幅と高さになります)。これで次のフレームを描く前に、前のフレームで描いた内容を見えなくします。これをしないと、ボールじゃなくて長い蛇がキャンバスの中を這い回る様を見る事になります! 塗り潰す色は半透明の rgba(0,0,0,0.25)
なので、以前の何フレーム分かがかすかに残り、ボールが移動した後の軌跡を表現します。もし 0.25 を 1 に変更すると、軌跡は全く見えなくなります。この値を変えて、どんな効果になるか見てみてください。balls
配列のボール全部をなめてそれぞれのボールの draw()
と update()
関数を実行し、それぞれを画面に描画してから、次のフレームに備えて必要な位置と速度の更新を行います。requestAnimationFrame()
メソッドを使って再実行します — このメソッドが繰り返し実行され同じ関数名を与えられると、その関数がスムースなアニメーションを行なうために毎秒設定された回数実行されます。これはたいてい再帰的に行われます — つまり関数は毎回その関数自身を呼び出すので、何度も何度も繰り返し実行されます。loop();
基本としてはこんなところ — セーブしてリフレッシュして、ボールがはずむのをテストしてみてください!
さあ、もうちょっと面白くするため、プログラムに衝突判定を追加して、ボールに他のボールとぶつかったらどうするのか教えましょう。
update()
メソッドを定義した箇所(つまり Ball.prototype.update
ブロック)の下に追加します
Ball.prototype.collisionDetect = function() { for (let j = 0; j < balls.length; j++) { if (!(this === balls[j])) { const dx = this.x - balls[j].x; const dy = this.y - balls[j].y; const distance = Math.sqrt(dx * dx + dy * dy); if (distance < this.size + balls[j].size) { balls[j].color = this.color = 'rgb(' + random(0, 255) + ',' + random(0, 255) + ',' + random(0, 255) +')'; } } } }
このメソッドはちょっとばかり複雑なので、今はどんな動作をしているのか正確に理解できなくても構いません。説明していきます:
balls[]
配列すべてのボールを回すために別の for
ループを始めます。if
文でループで回しているボールがチェックしているボールと同じか調べています。ボールがそれ自体とぶつかっているかチェックしたくないですから! これのために、現在のボール(collisionDetect メソッドが実行されているボールです)がループ中のボール(現在の collisionDetect メソッド内のループのくりかえし中で参照されているボール)と一致しているかチェックします。!
を使って等価性チェックを逆にしているので、if
文の中のコードはボールが同じでないときだけ実行されます。if
文の中のコードが実行されます。この場合では、両方のボールの color
プロパティをランダムな新しい色に設定しているだけです。もっと複雑なこと、現実っぽくボールを互いに跳ね返らせたりもできたでしょうが、これを実装したとするともっとずっとに複雑なったでしょう。そのような物理シミュレーションには、PhysicsJS, matter.js, Phaser などのゲームや物理用のライブラリを使う開発者が多いです。balls[i].update();
の行の後に追加してください:
balls[i].collisionDetect();
自分版の実世界で跳ね回るランダムボール例作り、この全単元で出てきた様々なオブジェクトやオブジェクト指向テクニックを使ったものをあなたに楽しんでいただけていれば、と思います。オブジェクトの実践的な使い方の練習や、実世界のコンテキストについて得られるものがあったはずです。
オブジェクトに関する記事は以上です — 残るのは、あなが自分のスキルをオブジェクトの評価問題で試してみる事だけです。
{{PreviousMenuNext("Learn/JavaScript/Objects/JSON", "Learn/JavaScript/Objects/Adding_bouncing_balls_features", "Learn/JavaScript/Objects")}}