1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
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
---
<div>{{LearnSidebar}}</div>
<div>{{PreviousMenuNext("Learn/JavaScript/Objects/JSON", "Learn/JavaScript/Objects/Adding_bouncing_balls_features", "Learn/JavaScript/Objects")}}</div>
<p class="summary">ここまでの記事で JavaScript オブジェクトの根幹部に関する理論と文法の詳細についてすべてを見てきて、始めていくのに十分な基礎固めをしました。この記事では実練習を行ない、独自の JavaScript オブジェクトを作っていくための実践をしていきましょう — 楽しくてカラフルなものを。</p>
<table class="learn-box standard-table">
<tbody>
<tr>
<th scope="row">前提条件:</th>
<td>基礎的なコンピューターの知識、HTML と CSS への基本的な理解、基礎的な JavaScript の理解 (<a href="https://developer.mozilla.org/ja/docs/Learn/JavaScript/First_steps">JavaScript の第一歩</a>と <a href="https://developer.mozilla.org/ja/docs/Learn/JavaScript/Building_blocks">JavaScript の構成要素</a>を参照) とオブジェクト指向JavaScript の基本 (<a href="https://developer.mozilla.org/ja/docs/Learn/JavaScript/Building_blocks">JavaScript オブジェクトの基本</a>を参照)。</td>
</tr>
<tr>
<th scope="row">目的:</th>
<td>オブジェクトの使い方とオブジェクト指向のテクニックを実世界のコンテストで練習する。</td>
</tr>
</tbody>
</table>
<h2 id="Lets_bounce_some_balls" name="Lets_bounce_some_balls">ボールを弾ませよう</h2>
<p>この記事では伝統的な「弾むボール」のデモを作ってみて、JavaScript でどれほどオブジェクトが役に立つかお見せしましょう。小さなボールは画面じゅうを跳ねまわり、それぞれがぶつかると色が変わります。完成したものはこんな風に見えることでしょう:</p>
<p><img alt="" src="https://mdn.mozillademos.org/files/13865/bouncing-balls.png" style="display: block; height: 614px; margin: 0px auto; width: 800px;"></p>
<ol>
</ol>
<p>この例では画面にボールを描くのに <a href="/ja/docs/Learn/JavaScript/Client-side_web_APIs/Drawing_graphics">Canvas API</a> を使い、画面をアニメーションさせるのに <a href="/ja/docs/Web/API/window/requestAnimationFrame">requestAnimationFrame</a> を使います — これらの API について事前の知識は不要です。この記事を読み終わる頃にはこれら API についてもっと知りたくなっているだろうと期待してますが。道中では、イカしたオブジェクトを活用して、ボールを壁で弾ませる、それぞれがぶつかった事を判定する(<strong>衝突判定</strong>という呼び名で知られています)といった上手いテクニックをいくつかお見せしていきます。</p>
<h2 id="Getting_started" name="Getting_started">始めに</h2>
<p>始める前に <code><a href="https://github.com/mdn/learning-area/blob/master/javascript/oojs/bouncing-balls/index.html">index.html</a></code>, <code><a href="https://github.com/mdn/learning-area/blob/master/javascript/oojs/bouncing-balls/style.css">style.css</a></code>, と <code><a href="https://github.com/mdn/learning-area/blob/master/javascript/oojs/bouncing-balls/main.js">main.js</a></code> ファイルのローカルコピーを作成してください。これらにはそれぞれ、以下が含まれています:</p>
<ol>
<li>とても簡素な HTML文書で、{{HTMLElement("h1")}} 要素と、ボールを描画するための {{HTMLElement("canvas")}} 要素と、この HTML に CSS と JavaScript を適用するための要素だけからなります。</li>
<li>とても簡単なスタイル、主には<code><h1></code>のスタイルとポジションを指定し、スクロールバーやページ端周辺のマージンを消す(素敵にきれいに見せるため)ためのもの。</li>
<li><code><canvas></code>要素を設定し、これから使うことになる汎用の関数を提供する若干の JavaScript。</li>
</ol>
<p>スクリプトの最初の部分はこんな具合です:</p>
<pre class="brush: js notranslate">const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');
const width = canvas.width = window.innerWidth;
const height = canvas.height = window.innerHeight;</pre>
<p>このスクリプトでは<code><canvas></code>要素への参照を取得し、これに対して <code><a href="/ja/docs/Web/API/HTMLCanvasElement/getContext">getContext()</a></code> メソッドを使って描画していくためのコンテキストを取得します。得られる定数 (<code>ctx</code>) はキャンバスの描画可能領域を直接表現しており、ここに二次元の形状を書き込む事ができます。</p>
<p>次に <code>width</code> と <code>height</code> 二つの定数をセットし、キャンバス要素の幅と高さ(<code>canvas.width</code> と <code>canvas.height</code> プロパティで表わされます)をブラウザーのビューポートの幅と高さ(ウェブページが表示される領域です — {{domxref("Window.innerWidth")}} と{{domxref("Window.innerHeight")}} プロパティから取得できます)に等しくします。</p>
<p>変数値をさっと全部同じにするのに、代入が連鎖している事に注意してください — これで全く問題ありません。</p>
<p>初期化スクリプトの最後の部分はこんなのです:</p>
<pre class="brush: js notranslate">function random(min, max) {
const num = Math.floor(Math.random() * (max - min + 1)) + min;
return num;
}</pre>
<p>この関数は二つの数を引数に取り、二つ数の範囲内の乱数を戻します。</p>
<h2 id="Modeling_a_ball_in_our_program" name="Modeling_a_ball_in_our_program">我々のプログラム用のボールを一つモデル化する</h2>
<p>我々のプログラムでは画面中を跳ねまわるたくさんのボールがあります。これらのボールはどれも同じルールで動くので、1つのオブジェクトで表わすのが理に叶っています。まずはコードの最後に以下のコンストラクターを追加するところから始めましょう。</p>
<pre class="brush: js notranslate">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;
}</pre>
<p>ここではいくつかの引数を用意し、我々のプログラムの中で個々のボールが動作するのに必要なプロパティを定義しています:</p>
<ul>
<li><code>x</code>、<code>y</code>座標 — ボールが画面のどこからスタートするか表わす水平と垂直の座標。これは 0(画面左上隅)からブラウザービューポートの幅と高さの(画面右下隅)間の値を取ります。</li>
<li>水平と垂直方向の速度(<code>velX</code> と <code>velY</code>) — 個々のボールには水平と垂直方向の速度が与えられます。実際にはアニメーションが開始されると、これらの値が <code>x</code>/<code>y</code>座標値に定期的に加算され、各フレームでこの値だけ移動していきます。</li>
<li><code>color</code> — 個々のボールには色がつけられます。</li>
<li><code>size</code> — 個々のボールには大きさがあります — ピクセルを単位とする半径で表わします。</li>
</ul>
<p>これはプロパティを取り扱いましたが、メソッドはどうしましょう? プログラムの中ではボールに実際に何かさせたいわけです。</p>
<h3 id="Drawing_the_ball" name="Drawing_the_ball">ボールを描画する</h3>
<p>まず以下の <code>draw()</code> メソッドを <code>Ball()</code> のプロトタイプ(<code>prototype</code>)に追加しましょう:</p>
<pre class="brush: js notranslate">Ball.prototype.draw = function() {
ctx.beginPath();
ctx.fillStyle = this.color;
ctx.arc(this.x, this.y, this.size, 0, 2 * Math.PI);
ctx.fill();
}</pre>
<p>この関数を使って、以前定義した 2D キャンバスコンテキスト(<code>ctx</code>)のメンバーを順に呼び出す方法で、ボール自身が画面に自分を描画する方法を教え込みます。コンテキストは紙のようなもので、ペンを使って何か描くように指示したいわけです:</p>
<ul>
<li>まずは、<code><a href="/ja/docs/Web/API/CanvasRenderingContext2D/beginPath">beginPath()</a></code> を使って紙に形を描きたいと宣言します。</li>
<li>次に <code><a href="/ja/docs/Web/API/CanvasRenderingContext2D/fillStyle">fillStyle</a></code> を使って形を何色にしたいか宣言します — ここではボールの <code>color</code> プロパティを指定します。</li>
<li>次に <code><a href="/ja/docs/Web/API/CanvasRenderingContext2D/arc">arc()</a></code> メソッドを使って紙に円弧形をなぞります。これの引数は:
<ul>
<li>円弧の中心座標、<code>x</code> と <code>y</code> — ボールの <code>x</code>、<code>y</code> プロパティを指定します。</li>
<li>円弧の半径 — ここではボールの <code>size</code> プロパティです。</li>
<li>最後の二つの引数は円弧の開始点から終了点までの角度を円の中心角で指定します。ここでは 0度から <code>2 * PI</code>、これはラジアンで表わした 360度に相当します(ややこしいですがラジアンで指定しなければなりません)。これで一周した円を描けます。もし <code>1 * PI</code> までしか指定しなければ、半円(180度)になるでしょう。</li>
</ul>
</li>
<li>最後の最後に、<code><a href="/ja/docs/Web/API/CanvasRenderingContext2D/fill">fill()</a></code> メソッドを使って、これはおおよそ、「<code>beginPath()</code> から描き始めた線描を終了し、描いた領域を前に <code>fillStyle</code> で指定していた色で塗り潰せ」という指示になります。</li>
</ul>
<p>これでオブジェクトをテストしてみられるようになりました。</p>
<ol>
<li>コードを保存し、HTML ファイルをブラウザーで読み込みます。</li>
<li>ブラウザーの JavaScript コンソールを開いて、ページをリフレッシュし、キャンバスのサイズがコンソール分小さくなったビューポート領域に合うようにします。</li>
<li>次をタイプして、新しいボールのインスタンスを作成します:
<pre class="brush: js notranslate">let testBall = new Ball(50, 100, 4, 4, 'blue', 10);</pre>
</li>
<li>そのメンバを呼び出して見てください:
<pre class="brush: js notranslate">testBall.x
testBall.size
testBall.color
testBall.draw()</pre>
</li>
<li>最後の行を入力すると、キャンバスのどこかにボールが表示されたはずです。</li>
</ol>
<h3 id="Updating_the_balls_data" name="Updating_the_balls_data">ボールのデータを更新する</h3>
<p>ボールを座標に表示できるようになりましたが、ボールを実際に移動させるには、何らかの更新するための関数が必要です。JavaScript ファイルの最後に以下のコードを追加し、<code>update()</code> メソッドを <code>Ball()</code> の <code>prototype</code> に追加します:</p>
<pre class="brush: js notranslate">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;
}</pre>
<p>関数の頭から 4 つの部分でボールがキャンバスの端に達したかどうかチェックします。もしそうであれば、関連する速度の向きを反転してボールが反対の向きに移動するようにします。つまり例えば、ボールが上方向に移動していたならば(<code>velY</code> が正)、垂直方向の速度をボールが下方向に移動するように変更します(<code>velY</code> を負に)。(<strong>訳注</strong>: 左上が原点、右下が座標の正方向ならば、ボールが上に移動する時の velY は負のはずだけど…)</p>
<p>4 つの場合で、次のことを確認しています:</p>
<ul>
<li><code>x</code>座標がキャンバスの幅より大きいか (ボールは右端から飛び出そうとしている)</li>
<li><code>x</code>座標が 0 より小さいか (ボールは左端から飛び出そうとしている)</li>
<li><code>y</code>座標がキャンバスの高さより大きいか (ボールは下端から飛び出そうとしている)</li>
<li><code>y</code>座標が 0 より小さいか (ボールは上端から飛び出そうとしている)</li>
</ul>
<p>それぞれの場合で計算にボールの <code>size</code> を含めていますが、これは <code>x</code>/<code>y</code>座標はボールの中心ですが、ボールの端のところで周囲から跳ね返って欲しいからです — 跳ね返る前に画面外にめり込んで欲しくないからです。</p>
<p>最後の二行では <code>velX</code> を <code>x</code> 座標に、<code>velY</code> を <code>y</code> 座標に加算しています — 結果ボールはこのメソッドが呼ばれる毎に移動します。</p>
<p>とりあえずはここまでで、ちょいとアニメーションさせてみよう!</p>
<h2 id="Animating_the_ball" name="Animating_the_ball">ボールのアニメーション</h2>
<p>さあ、楽しい事をやりましょう。では、キャンバスにボールを追加し、アニメーションさせるところから始めましょう。</p>
<ol>
<li>最初に、ボールを全部保存しておく場所がどこかに必要です。以下がこれをやってくれます — あなたのコードの最後に追加してください:
<pre class="brush: js notranslate">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);
}</pre>
<p><code>while</code> ループは、我々の <code>random()</code>関数で作成したランダムな値を使った新しい <code>Ball()</code> のインスタンスを作成し、ボールの配列の後ろに <code>push()</code> して追加していきますが、これは配列中のボールの数が 25 に満たない間まで繰り返されます。<code>balls.length < 25</code> の数字をいろいろ変えれば表示されるボールの数を増やしたり減らしたりできます。あなたのコンピューターとブラウザーがどれだけ速いかによりますが、ボールを数千にするとアニメーションはかなり遅くなります! 注意してね。</p>
</li>
<li>以下をあなたのコードの末尾に追加してください:
<pre class="brush: js notranslate">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);
}</pre>
<p>ものをアニメーションさせるすべてのプログラムには、大概アニメーションループがあり、プログラム内の情報を更新して、アニメーションの各フレームでその結果を表示します。これは大半のゲームや類似するプログラムの基本になります。コード中の <code>loop()</code> 関数は以下の事を行ないます:</p>
<ul>
<li>キャンバスの塗り潰し色を半透明の黒にし、その色でキャンバスの幅と高さいっぱいの長方形を <code>fillRect()</code> で描きます(これの 4 つの引数は始点の座標と、描画する長方形の幅と高さになります)。これで次のフレームを描く前に、前のフレームで描いた内容を見えなくします。これをしないと、ボールじゃなくて長い蛇がキャンバスの中を這い回る様を見る事になります! 塗り潰す色は半透明の <code>rgba(0,0,0,0.25)</code> なので、以前の何フレーム分かがかすかに残り、ボールが移動した後の軌跡を表現します。もし 0.25 を 1 に変更すると、軌跡は全く見えなくなります。この値を変えて、どんな効果になるか見てみてください。</li>
<li>ループで <code>balls</code>配列のボール全部をなめてそれぞれのボールの <code>draw()</code> と <code>update()</code> 関数を実行し、それぞれを画面に描画してから、次のフレームに備えて必要な位置と速度の更新を行います。</li>
<li>この関数を <code>requestAnimationFrame()</code> メソッドを使って再実行します — このメソッドが繰り返し実行され同じ関数名を与えられると、その関数がスムースなアニメーションを行なうために毎秒設定された回数実行されます。これはたいてい再帰的に行われます — つまり関数は毎回その関数自身を呼び出すので、何度も何度も繰り返し実行されます。</li>
</ul>
</li>
<li>最後に、あなたのコードの最後に次の行を追加します — アニメーションを開始するために、一旦は関数を呼ぶ必要があるのです。
<pre class="brush: js notranslate">loop();</pre>
</li>
</ol>
<p>基本としてはこんなところ — セーブしてリフレッシュして、ボールがはずむのをテストしてみてください!</p>
<h2 id="Adding_collision_detection" name="Adding_collision_detection">衝突判定を追加する</h2>
<p>さあ、もうちょっと面白くするため、プログラムに衝突判定を追加して、ボールに他のボールとぶつかったらどうするのか教えましょう。</p>
<ol>
<li>最初に、以下のメソッド定義を <code>update()</code> メソッドを定義した箇所(つまり <code>Ball.prototype.update</code> ブロック)の下に追加します
<pre class="brush: js notranslate">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) +')';
}
}
}
}</pre>
</li>
<li>
<p>このメソッドはちょっとばかり複雑なので、今はどんな動作をしているのか正確に理解できなくても構いません。説明していきます:</p>
<ul>
<li>それぞれのボールで、他のボールそれぞれとこのボールが衝突していないか調べなければなりません。そのために、<code>balls[]</code>配列すべてのボールを回すために別の <code>for</code> ループを始めます。</li>
<li>内側のループに入ってすぐ、<code>if</code>文でループで回しているボールがチェックしているボールと同じか調べています。ボールがそれ自体とぶつかっているかチェックしたくないですから! これのために、現在のボール(collisionDetect メソッドが実行されているボールです)がループ中のボール(現在の collisionDetect メソッド内のループのくりかえし中で参照されているボール)と一致しているかチェックします。<code>!</code>を使って等価性チェックを逆にしているので、<code>if</code> 文の中のコードはボールが<strong>同じでない</strong>ときだけ実行されます。</li>
<li>そして二つの円が衝突していないか調べるための一般的なアルゴリズムを使っています。基本的には円ないの領域が重なっているかチェックしています。これについて詳しくは <a href="/ja/docs/Games/Techniques/2D_collision_detection">2次元の衝突判定</a>で解説されています。</li>
<li>もし衝突が検出されたら、内側の <code>if</code>文の中のコードが実行されます。この場合では、両方のボールの <code>color</code> プロパティをランダムな新しい色に設定しているだけです。もっと複雑なこと、現実っぽくボールを互いに跳ね返らせたりもできたでしょうが、これを実装したとするともっとずっとに複雑なったでしょう。そのような物理シミュレーションには、<a href="http://wellcaffeinated.net/PhysicsJS/">PhysicsJS</a>, <a href="http://brm.io/matter-js/">matter.js</a>, <a href="http://phaser.io/">Phaser</a> などのゲームや物理用のライブラリを使う開発者が多いです。</li>
</ul>
</li>
<li>あなたはアニメーションの各フレーム毎にこのメソッドを呼ばなければなりません。以下を <code>balls[i].update();</code> の行の後に追加してください:
<pre class="brush: js notranslate">balls[i].collisionDetect();</pre>
</li>
<li>保存とデモのリフレッシュをして、ボールがぶつかった時に色が変わるのを見てください!</li>
</ol>
<div class="note">
<p><strong>注記</strong>: この例題を動かすのに困った時は、あなたの JavaScript コードを私たちの<a href="https://github.com/mdn/learning-area/blob/master/javascript/oojs/bouncing-balls/main-finished.js">完成版</a>と比べてみてください(<a href="http://mdn.github.io/learning-area/javascript/oojs/bouncing-balls/index-finished.html">ライブ実行版</a>も見られます)。</p>
</div>
<h2 id="Summary" name="Summary">まとめ</h2>
<p>自分版の実世界で跳ね回るランダムボール例作り、この全単元で出てきた様々なオブジェクトやオブジェクト指向テクニックを使ったものをあなたに楽しんでいただけていれば、と思います。オブジェクトの実践的な使い方の練習や、実世界のコンテキストについて得られるものがあったはずです。</p>
<p>オブジェクトに関する記事は以上です — 残るのは、あなが自分のスキルをオブジェクトの評価問題で試してみる事だけです。</p>
<h2 id="See_also" name="See_also">参考文献</h2>
<ul>
<li><a href="/ja/docs/Web/API/Canvas_API/Tutorial">Canvas tutorial</a> — 2D キャンバスの初心者向けガイド。</li>
<li><a href="/ja/docs/Web/API/window/requestAnimationFrame">requestAnimationFrame()</a></li>
<li><a href="/ja/docs/Games/Techniques/2D_collision_detection">2D collision detection</a></li>
<li><a href="/ja/docs/Games/Techniques/3D_collision_detection">3D collision detection</a></li>
<li><a href="/ja/docs/Games/Tutorials/2D_Breakout_game_pure_JavaScript">2D breakout game using pure JavaScript</a> — 2D ゲームの作り方に関する、素晴しい初心者向けチュートリアル。</li>
<li><a href="/ja/docs/Games/Tutorials/2D_breakout_game_Phaser">2D breakout game using Phaser</a> — JavaScript ゲームライブラリを使って 2D ゲームを作るための基本を解説しています。</li>
</ul>
<p>{{PreviousMenuNext("Learn/JavaScript/Objects/JSON", "Learn/JavaScript/Objects/Adding_bouncing_balls_features", "Learn/JavaScript/Objects")}}</p>
<h2 id="In_this_module" name="In_this_module">このモジュール</h2>
<ul>
<li><a href="/ja/docs/Learn/JavaScript/Objects/Basics">JavaScript オブジェクトの基本</a></li>
<li><a href="/ja/docs/Learn/JavaScript/Objects/Object-oriented_JS">初心者のためのオブジェクト指向 JavaScript</a></li>
<li><a href="/ja/docs/Learn/JavaScript/Objects/Object_prototypes">Object のプロトタイプ</a></li>
<li><a href="/ja/docs/Learn/JavaScript/Objects/Inheritance">JavaScript での継承</a></li>
<li><a href="/ja/docs/Learn/JavaScript/Objects/JSON">JSON データの操作</a></li>
<li><a href="/ja/docs/Learn/JavaScript/Objects/Object_building_practice">オブジェクト作成の練習</a></li>
<li><a href="/ja/docs/Learn/JavaScript/Objects/Adding_bouncing_balls_features">バウンスボールに機能を追加する</a></li>
</ul>
|