From 33058f2b292b3a581333bdfb21b8f671898c5060 Mon Sep 17 00:00:00 2001 From: Peter Bengtsson Date: Tue, 8 Dec 2020 14:40:17 -0500 Subject: initial commit --- .../objects/object_building_practice/index.html | 314 +++++++++++++++++++++ 1 file changed, 314 insertions(+) create mode 100644 files/ja/learn/javascript/objects/object_building_practice/index.html (limited to 'files/ja/learn/javascript/objects/object_building_practice') diff --git a/files/ja/learn/javascript/objects/object_building_practice/index.html b/files/ja/learn/javascript/objects/object_building_practice/index.html new file mode 100644 index 0000000000..af94a8eede --- /dev/null +++ b/files/ja/learn/javascript/objects/object_building_practice/index.html @@ -0,0 +1,314 @@ +--- +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 +--- +
{{LearnSidebar}}
+ +
{{PreviousMenuNext("Learn/JavaScript/Objects/JSON", "Learn/JavaScript/Objects/Adding_bouncing_balls_features", "Learn/JavaScript/Objects")}}
+ +

ここまでの記事で JavaScript オブジェクトの根幹部に関する理論と文法の詳細についてすべてを見てきて、始めていくのに十分な基礎固めをしました。この記事では実練習を行ない、独自の JavaScript オブジェクトを作っていくための実践をしていきましょう — 楽しくてカラフルなものを。

+ + + + + + + + + + + + +
前提条件:基礎的なコンピューターの知識、HTML と CSS への基本的な理解、基礎的な JavaScript の理解 (JavaScript の第一歩JavaScript の構成要素を参照) とオブジェクト指向JavaScript の基本 (JavaScript オブジェクトの基本を参照)。
目的:オブジェクトの使い方とオブジェクト指向のテクニックを実世界のコンテストで練習する。
+ +

ボールを弾ませよう

+ +

この記事では伝統的な「弾むボール」のデモを作ってみて、JavaScript でどれほどオブジェクトが役に立つかお見せしましょう。小さなボールは画面じゅうを跳ねまわり、それぞれがぶつかると色が変わります。完成したものはこんな風に見えることでしょう:

+ +

+ +
    +
+ +

この例では画面にボールを描くのに Canvas API を使い、画面をアニメーションさせるのに requestAnimationFrame を使います — これらの API について事前の知識は不要です。この記事を読み終わる頃にはこれら API についてもっと知りたくなっているだろうと期待してますが。道中では、イカしたオブジェクトを活用して、ボールを壁で弾ませる、それぞれがぶつかった事を判定する(衝突判定という呼び名で知られています)といった上手いテクニックをいくつかお見せしていきます。

+ +

始めに

+ +

始める前に index.html, style.css, と main.js ファイルのローカルコピーを作成してください。これらにはそれぞれ、以下が含まれています:

+ +
    +
  1. とても簡素な HTML文書で、{{HTMLElement("h1")}} 要素と、ボールを描画するための {{HTMLElement("canvas")}} 要素と、この HTML に CSS と JavaScript を適用するための要素だけからなります。
  2. +
  3. とても簡単なスタイル、主には<h1>のスタイルとポジションを指定し、スクロールバーやページ端周辺のマージンを消す(素敵にきれいに見せるため)ためのもの。
  4. +
  5. <canvas>要素を設定し、これから使うことになる汎用の関数を提供する若干の JavaScript。
  6. +
+ +

スクリプトの最初の部分はこんな具合です:

+ +
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) はキャンバスの描画可能領域を直接表現しており、ここに二次元の形状を書き込む事ができます。

+ +

次に widthheight 二つの定数をセットし、キャンバス要素の幅と高さ(canvas.widthcanvas.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;
+}
+ +

ここではいくつかの引数を用意し、我々のプログラムの中で個々のボールが動作するのに必要なプロパティを定義しています:

+ + + +

これはプロパティを取り扱いましたが、メソッドはどうしましょう? プログラムの中ではボールに実際に何かさせたいわけです。

+ +

ボールを描画する

+ +

まず以下の 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)のメンバーを順に呼び出す方法で、ボール自身が画面に自分を描画する方法を教え込みます。コンテキストは紙のようなもので、ペンを使って何か描くように指示したいわけです:

+ + + +

これでオブジェクトをテストしてみられるようになりました。

+ +
    +
  1. コードを保存し、HTML ファイルをブラウザーで読み込みます。
  2. +
  3. ブラウザーの JavaScript コンソールを開いて、ページをリフレッシュし、キャンバスのサイズがコンソール分小さくなったビューポート領域に合うようにします。
  4. +
  5. 次をタイプして、新しいボールのインスタンスを作成します: +
    let testBall = new Ball(50, 100, 4, 4, 'blue', 10);
    +
  6. +
  7. そのメンバを呼び出して見てください: +
    testBall.x
    +testBall.size
    +testBall.color
    +testBall.draw()
    +
  8. +
  9. 最後の行を入力すると、キャンバスのどこかにボールが表示されたはずです。
  10. +
+ +

ボールのデータを更新する

+ +

ボールを座標に表示できるようになりましたが、ボールを実際に移動させるには、何らかの更新するための関数が必要です。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 つの場合で、次のことを確認しています:

+ + + +

それぞれの場合で計算にボールの size を含めていますが、これは x/y座標はボールの中心ですが、ボールの端のところで周囲から跳ね返って欲しいからです — 跳ね返る前に画面外にめり込んで欲しくないからです。

+ +

最後の二行では velXx 座標に、velYy 座標に加算しています — 結果ボールはこのメソッドが呼ばれる毎に移動します。

+ +

とりあえずはここまでで、ちょいとアニメーションさせてみよう!

+ +

ボールのアニメーション

+ +

さあ、楽しい事をやりましょう。では、キャンバスにボールを追加し、アニメーションさせるところから始めましょう。

+ +
    +
  1. 最初に、ボールを全部保存しておく場所がどこかに必要です。以下がこれをやってくれます — あなたのコードの最後に追加してください: +
    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 の数字をいろいろ変えれば表示されるボールの数を増やしたり減らしたりできます。あなたのコンピューターとブラウザーがどれだけ速いかによりますが、ボールを数千にするとアニメーションはかなり遅くなります! 注意してね。

    +
  2. +
  3. 以下をあなたのコードの末尾に追加してください: +
    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() メソッドを使って再実行します — このメソッドが繰り返し実行され同じ関数名を与えられると、その関数がスムースなアニメーションを行なうために毎秒設定された回数実行されます。これはたいてい再帰的に行われます — つまり関数は毎回その関数自身を呼び出すので、何度も何度も繰り返し実行されます。
    • +
    +
  4. +
  5. 最後に、あなたのコードの最後に次の行を追加します — アニメーションを開始するために、一旦は関数を呼ぶ必要があるのです。 +
    loop();
    +
  6. +
+ +

基本としてはこんなところ — セーブしてリフレッシュして、ボールがはずむのをテストしてみてください!

+ +

衝突判定を追加する

+ +

さあ、もうちょっと面白くするため、プログラムに衝突判定を追加して、ボールに他のボールとぶつかったらどうするのか教えましょう。

+ +
    +
  1. 最初に、以下のメソッド定義を 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) +')';
    +      }
    +    }
    +  }
    +}
    +
  2. +
  3. +

    このメソッドはちょっとばかり複雑なので、今はどんな動作をしているのか正確に理解できなくても構いません。説明していきます:

    + +
      +
    • それぞれのボールで、他のボールそれぞれとこのボールが衝突していないか調べなければなりません。そのために、balls[]配列すべてのボールを回すために別の for ループを始めます。
    • +
    • 内側のループに入ってすぐ、if文でループで回しているボールがチェックしているボールと同じか調べています。ボールがそれ自体とぶつかっているかチェックしたくないですから! これのために、現在のボール(collisionDetect メソッドが実行されているボールです)がループ中のボール(現在の collisionDetect メソッド内のループのくりかえし中で参照されているボール)と一致しているかチェックします。!を使って等価性チェックを逆にしているので、if 文の中のコードはボールが同じでないときだけ実行されます。
    • +
    • そして二つの円が衝突していないか調べるための一般的なアルゴリズムを使っています。基本的には円ないの領域が重なっているかチェックしています。これについて詳しくは 2次元の衝突判定で解説されています。
    • +
    • もし衝突が検出されたら、内側の if文の中のコードが実行されます。この場合では、両方のボールの color プロパティをランダムな新しい色に設定しているだけです。もっと複雑なこと、現実っぽくボールを互いに跳ね返らせたりもできたでしょうが、これを実装したとするともっとずっとに複雑なったでしょう。そのような物理シミュレーションには、PhysicsJS, matter.js, Phaser などのゲームや物理用のライブラリを使う開発者が多いです。
    • +
    +
  4. +
  5. あなたはアニメーションの各フレーム毎にこのメソッドを呼ばなければなりません。以下を balls[i].update(); の行の後に追加してください: +
    balls[i].collisionDetect();
    +
  6. +
  7. 保存とデモのリフレッシュをして、ボールがぶつかった時に色が変わるのを見てください!
  8. +
+ +
+

注記: この例題を動かすのに困った時は、あなたの JavaScript コードを私たちの完成版と比べてみてください(ライブ実行版も見られます)。

+
+ +

まとめ

+ +

自分版の実世界で跳ね回るランダムボール例作り、この全単元で出てきた様々なオブジェクトやオブジェクト指向テクニックを使ったものをあなたに楽しんでいただけていれば、と思います。オブジェクトの実践的な使い方の練習や、実世界のコンテキストについて得られるものがあったはずです。

+ +

オブジェクトに関する記事は以上です — 残るのは、あなが自分のスキルをオブジェクトの評価問題で試してみる事だけです。

+ +

参考文献

+ + + +

{{PreviousMenuNext("Learn/JavaScript/Objects/JSON", "Learn/JavaScript/Objects/Adding_bouncing_balls_features", "Learn/JavaScript/Objects")}}

+ +

このモジュール

+ + -- cgit v1.2.3-54-g00ecf