aboutsummaryrefslogtreecommitdiff
path: root/files/zh-tw/learn/javascript/objects/object_building_practice/index.html
blob: 4a47aa39ebe0641be7e8479b39cc8d09a7067a31 (plain)
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
---
title: 物件建構實作
slug: Learn/JavaScript/Objects/Object_building_practice
tags:
  - Canvas
  - JavaScript
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="/en-US/docs/Learn/JavaScript/First_steps">First steps</a>〉與〈<a href="/en-US/docs/Learn/JavaScript/Building_blocks">Building blocks</a>〉) 與 OOJS 基本概念 (參閱〈<a href="/en-US/docs/Learn/JavaScript/Object-oriented/Introduction">Introduction to objects</a>〉)。</td>
  </tr>
  <tr>
   <th scope="row">要點:</th>
   <td>親手實作物件與物件導向 (OO) 技術。</td>
  </tr>
 </tbody>
</table>

<h2 id="弄一些彈跳彩球">弄一些彈跳彩球</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="/en-US/docs/Web/API/Canvas_API">Canvas API</a> 在畫面上繪製球體,<a href="/en-US/docs/Web/API/window/requestAnimationFrame">requestAnimationFrame</a> API 則是繪製整個動畫;而且你不需先了解此兩個 API。但我們希望在看完本文之後,能引起大家深入探究此兩個 API 的興趣。整個過程會利用某些花俏的物件,並讓你看到幾項有趣技術,像是球體從牆上回彈,並檢查球體是否互相碰撞 (也就是碰撞偵測)。</p>

<h2 id="著手開始">著手開始</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 文件,具備 1 個 {{HTMLElement("h1")}} 元素、1 個 {{HTMLElement("canvas")}} 元素可繪製彩球,以及其他元素可將 CSS 與 JavaScript 套用到 HTML 之上。</li>
 <li>一些極簡單的樣式,主要可作為 <code>&lt;h1&gt; 的樣式風格與定位之用,並省去網頁邊緣的捲動棒或空白</code> (看起來更簡約)。</li>
 <li>某些 JavaScript 可用以設定 <code>&lt;canvas&gt;</code> 元素,另有通用函式可供我們往後使用。</li>
</ol>

<p>指令碼第一部分就像:</p>

<pre class="brush: js">var canvas = document.querySelector('canvas');

var ctx = canvas.getContext('2d');

var width = canvas.width = window.innerWidth;
var height = canvas.height = window.innerHeight;</pre>

<p>此指令碼將為 <code>&lt;canvas&gt;</code> 元素提供參照,接著於其上呼叫 <code><a href="/en-US/docs/Web/API/HTMLCanvasElement/getContext">getContext()</a></code> 函式,藉以提供能開始繪圖的內文 (Context)。所產生的變數 (<code>ctx</code>) 也就是物件,將直接呈現 canvas 的繪圖區域,讓我們繪製 2D 圖像。</p>

<p>接著設定 <code>width</code><code>height</code> 共 2 個變數,也就是 canvas 元素的寬度與高度 (透過 <code>canvas.width</code><code>canvas.height</code> 屬性呈現) 即等於瀏覽器可視區的寬度與高度 (也就是網頁顯示的區域 — 可經由 {{domxref("Window.innerWidth")}}{{domxref("Window.innerHeight")}} 屬性得知)。</p>

<p>你會看到我們在這裡串連了多個指定式,以快速設定所有變數,而且運作無虞。</p>

<p>剛開始的指令碼後半部如下:</p>

<pre class="brush: js">function random(min, max) {
  var num = Math.floor(Math.random()*(max-min)) + min;
  return num;
}</pre>

<p>此函式共有 2 組參數 (argument),並會回傳此範圍之內的任意值。</p>

<h2 id="在程式中設定球體的模型">在程式中設定球體的模型</h2>

<p>我們的程式會讓一堆彩球在畫面中彈來彈去。因為這些球體的行動方式均相同,所以透過物件呈現這些彩球也合情合理。先在程式碼底部加入下列建構子:</p>

<pre class="brush: js">function Ball() {
  this.x = random(0,width);
  this.y = random(0,height);
  this.velX = random(-7,7);
  this.velY = random(-7,7);
  this.color = 'rgb(' + random(0,255) + ',' + random(0,255) + ',' + random(0,255) +')';
  this.size = random(10,20);
}</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> 座標值,以利在各畫格(frame)中移動彩球。</li>
 <li><code>color</code> — 彩球的顏色是隨機產生。</li>
 <li><code>size</code> — 各彩球大小亦為隨機,從 10 到 20 像素不等。</li>
</ul>

<p>屬性講完了,那函式呢?程式中的彩球要實際運作才行。</p>

<h3 id="繪製球體">繪製球體</h3>

<p>先將下列 <code>draw()</code> 函式加到 <code>Ball() 的</code> <code>prototype 之中:</code></p>

<pre class="brush: js">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 canvas 內文(ctx)中的物件成員,就能讓球體自己在螢幕上畫出自己。此內文就像是白紙一樣,接著就用筆在紙上畫出點東西:</p>

<ul>
 <li>首先以 <code><a href="/en-US/docs/Web/API/CanvasRenderingContext2D/beginPath">beginPath()</a></code> 聲明我們要在紙上畫出來的形狀。</li>
 <li>接著用 <code><a href="/en-US/docs/Web/API/CanvasRenderingContext2D/fillStyle">fillStyle</a></code> 定義該形狀所要呈現的顏色 — 設定為球體的 <code>color</code> 屬性。</li>
 <li>再用 <code><a href="/en-US/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>最後 2 項參數則指定弧形繪製時的圓圈起、終點角度。我們這裡指定 0 度與 <code>2 * PI</code> 度,這也等於半徑繞了 360 度 (你必須在半徑中指定,有點煩)。如此構成完整的圓。如果你只設定了 <code>1 * PI,就會只有半球體</code> (即 180 度)。</li>
  </ul>
 </li>
 <li>最後使用 <code><a href="/en-US/docs/Web/API/CanvasRenderingContext2D/fill">fill()</a></code> 函式,基本上是用來聲稱「完成我們以 <code>beginPath()</code> 開始的繪圖路徑,再用 <code>fillStyle 中指定的色彩將之填滿</code>」。</li>
</ul>

<p>你已經可以開始測試自己的物件了。</p>

<ol>
 <li>儲存目前的程式碼,在瀏覽器中載入此 HTML 檔案。</li>
 <li>開啟瀏覽器的 JavaScript 主控台,並在主控台開啟時重新整理網頁,讓 canvas 尺寸變更為左側較小型的可視區域。</li>
 <li>鍵入下列程式碼以建立新的球體實例:
  <pre class="brush: js">var testBall = new Ball();</pre>
 </li>
 <li>再呼叫其成員:
  <pre class="brush: js">testBall.x
testBall.size
testBall.color
testBall.draw()</pre>
 </li>
 <li>輸入最後一行之後,應該就能看到 canvas 上出現自己產生的球體。</li>
</ol>

<h3 id="更新球體的資料">更新球體的資料</h3>

<p>現在可以繪製彩球了。但在讓球彈跳之前,我們必須先更新幾個函式。將下列程式碼加到 JavaScript 檔案底端,把 <code>update()</code> 函式加到 <code>Ball()</code><code>prototype</code> 之中:</p>

<pre class="brush: js">Ball.prototype.update = function() {
  if((this.x + this.size) &gt;= width) {
    this.velX = -(this.velX);
  }

  if((this.x - this.size) &lt;= 0) {
    this.velX = -(this.velX);
  }

  if((this.y + this.size) &gt;= height) {
    this.velY = -(this.velY);
  }

  if((this.y - this.size) &lt;= 0) {
    this.velY = -(this.velY);
  }

  this.x += this.velX;
  this.y += this.velY;
}</pre>

<p>函式的前 4 個部分負責檢查球體是否碰到 canvas 邊緣。如果球體抵達邊緣,我們就反轉相對加速度的方向,讓球反方向行進。以球體向上 (正向 <code>velX</code>) 時為例,接著就會改變水平速度,球體也就反向運動。</p>

<p>在這 4 個情境中,我們:</p>

<ul>
 <li>檢查 <code>x</code> 座標是否大於 canvas 的寬度 (球體抵達右側邊緣)。</li>
 <li>檢查 <code>x</code> 座標是否小於 0 (球體抵達左側邊緣)。</li>
 <li>檢查 <code>y</code> 座標是否大於 canvas 的高度 (球體抵達底部邊緣)。</li>
 <li>檢查 <code>y</code> 座標是否小於 0 (球體抵達頂部邊緣)。</li>
</ul>

<p>在各情境中,因為 <code>x</code>/<code>y</code> 座標為球體的中心,所以我們把球體的 <code>size</code> 納入計算,但我們不要球體在回彈之前在半路上就跳出畫面之外。</p>

<p>最後 2 行則是將 <code>velX</code> 與 <code>velY</code> 值分別加入 <code>x\y</code> 座標之中;每次只要呼叫此函式,球體就會依照應有的效果移動。</p>

<p>到這裡沒有問題的話,就開始弄動畫吧!</p>

<h2 id="球體動起來">球體動起來</h2>

<p>接著來玩玩吧。我們要加更多球到 canvas 中並開始動畫效果。</p>

<ol>
 <li>首先要弄個地方儲存所有的彩球。將下方陣列加到現有程式碼底部即可:
  <pre class="brush: js">var balls = [];</pre>

  <p>所有可提供動畫效果的程式,一般都會採用動畫迴圈,可用以更新程式中的資訊,並接著在動畫的各個畫格上繪製產生的結果。這也是大部分遊戲或類似程式的基礎。</p>
 </li>
 <li>再將下列程式碼加到現有程式碼底部:
  <pre class="brush: js">function loop() {
  ctx.fillStyle = 'rgba(0,0,0,0.25)';
  ctx.fillRect(0,0,width,height);

  while(balls.length &lt; 25) {
    var ball = new Ball();
    balls.push(ball);
  }

  for(i = 0; i &lt; balls.length; i++) {
    balls[i].draw();
    balls[i].update();
  }

  requestAnimationFrame(loop);
}</pre>

  <p>我們的 <code>loop()</code> 函式可進行:</p>

  <ul>
   <li>設定 canvas 填滿色彩或是半透明的黑色。接著透<code>過 fillRect()</code> (共 4 個參數提供起始座標,以及繪製矩形的高度與寬度),跨 canvas 的寬度與高度繪製整個矩型的色彩。如此可在繪製下一個畫格之前,先覆蓋前一個已存在的畫格;否則會看到許多隻長長的蛇爬來爬去。填充顏色已設定為半透明狀態:<code>rgba(0,0,0,0.25)</code> 可讓先前的畫格微微發亮,製造出球體移動時的小尾巴效果。如果將 0.25 更改為 1,就會完全消除尾巴。你可自己測試不同的數值,找出自己喜歡的效果。</li>
   <li>可對 <code>Ball()</code> 建立新的實作,接著將之 <code>push()</code> 到球體陣列的最後,且彩球數量必須少於 25 個。所以整個畫面最多顯示 25 個球。你可嘗試變更 <code>balls.length &lt; 25</code> 中的數值,畫面中的彩球數量也會隨著變化。依你所用電腦\瀏覽器處理效能的不同,若繪製上千個彩球就會拖慢整個動畫的速度。</li>
   <li>迴圈將巡過 <code>balls</code> 陣列中的所有彩球,並執行各個彩球的 <code>draw()</code><code>update()</code> 函式,以於畫面中逐一繪製,接著對下個畫格的位置與速度執行必要更新。</li>
   <li>再以 <code>requestAnimationFrame()</code> 函式執行過此函式 — 當此函式持續執行並傳送相同的函式名稱時,就會每秒執行此函式達特定次數,以產生流暢的動畫。接著重複執行此作業,也就是函式每次執行時均會呼叫自身 1 次,進而循環執行。</li>
  </ul>
 </li>
 <li>最後將下列程式碼加入最底端,呼叫函式 1 次讓動畫開始運作。
  <pre class="brush: js">loop();</pre>
 </li>
</ol>

<p>基本就是這樣了。試著儲存並重新整理檔案,讓你的彩球開始跳動吧!</p>

<h2 id="另增碰撞偵測">另增碰撞偵測</h2>

<p>現在弄點有趣的東西,就把碰撞偵測 (Collision detection) 加進程式裡,讓彩球知道自己碰到其他球了。</p>

<ol>
 <li>首先將下列函式定義加進你自己定義 <code>update()</code> 函式中 (例如 <code>Ball.prototype.update</code> 區塊):

  <pre class="brush: js">Ball.prototype.collisionDetect = function() {
  for(j = 0; j &lt; balls.length; j++) {
    if( (!(this.x === balls[j].x &amp;&amp; this.y === balls[j].y &amp;&amp; this.velX === balls[j].velX &amp;&amp; this.velY === balls[j].velY)) ) {
      var dx = this.x - balls[j].x;
      var dy = this.y - balls[j].y;
      var distance = Math.sqrt(dx * dx + dy * dy);

      if (distance &lt; this.size + balls[j].size) {
        balls[j].color = this.color = 'rgb(' + random(0,255) + ',' + random(0,255) + ',' + random(0,255) +')';
      }
    }
  }
}</pre>

  <p>這函式有點複雜,所以現在不瞭解如何運作的也別擔心。解釋如下:</p>

  <ul>
   <li>對每個彩球來說,我們必須檢查是否碰撞到其他球。所以要設定另一個 <code>for</code> 迴圈以循環檢視 <code>balls[]</code> 陣列中的所有彩球。</li>
   <li>在我們的 for 迴圈中,我們立刻使用 <code>if</code> 陳述式檢查「現正透過迴圈循環檢查中」的彩球,是否即為我們目前檢查中的同一彩球。我們不需要檢查彩球是否碰撞到自己!為了達到此效果,我們檢查彩球目前的 <code>x</code>/<code>y</code> 座標與速度,是否等同於迴圈檢查的彩球。接著透過「<code>!</code>」否定檢查,所以在 if 陳述式中的程式碼,只有在彩球相異時才會執行。</li>
   <li>接著使用一般演算法檢查 2 個球體之間的碰撞。我們基本上會檢查任 2 個球體的範圍是否重疊。另將透過〈<a href="/en-US/docs/Games/Techniques/2D_collision_detection">2D 碰撞偵測</a>〉一文進一步解釋。</li>
   <li>如果偵測到碰撞,則隨即執行內部 <code>if</code> 陳述式的程式碼。在本範例中,我們剛設定了 2 個球體的 <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">balls[i].collisionDetect();</pre>
 </li>
 <li>儲存並重新整理之後,就能看到球體在碰撞時變更其色彩了!</li>
</ol>

<div class="note">
<p><strong>注意:</strong>如果你無法讓此範例順利運作,可比較我們的<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="摘要">摘要</h2>

<p>希望你喜歡撰寫出隨機彩球碰撞範例,其內並包含我們前面說過的多樣物件與 OO 技術!本文應該已提供你有用的物件實作與絕佳的實際文本。</p>

<p>物件實體就到這裡。接著就是你要磨練自己的物件技術了!</p>

<h2 id="另可參閱">另可參閱</h2>

<ul>
 <li><a href="/en-US/docs/Web/API/Canvas_API/Tutorial">Canvas 線上教學</a> — 2D canvas 初學者指南</li>
 <li><a href="/en-US/docs/Web/API/window/requestAnimationFrame">requestAnimationFrame()</a></li>
 <li><a href="/en-US/docs/Games/Techniques/2D_collision_detection">2D 碰撞偵測</a></li>
 <li><a href="/en-US/docs/Games/Techniques/3D_collision_detection">3D 碰撞偵測</a></li>
 <li><a href="/en-US/docs/Games/Tutorials/2D_Breakout_game_pure_JavaScript">只使用 JavaScript 的 2D 打磚塊遊戲</a> — 2D 遊戲開發初學者的絕佳線上教學</li>
 <li><a href="/en-US/docs/Games/Tutorials/2D_breakout_game_Phaser">剖析器 (Phaser) 的 2D 打磚塊遊戲</a> — 以 JavaScript 遊戲函式庫建構 2D 遊戲的基本概念</li>
</ul>

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