aboutsummaryrefslogtreecommitdiff
path: root/files/zh-tw/learn/javascript/objects/object_building_practice/index.html
diff options
context:
space:
mode:
Diffstat (limited to 'files/zh-tw/learn/javascript/objects/object_building_practice/index.html')
-rw-r--r--files/zh-tw/learn/javascript/objects/object_building_practice/index.html283
1 files changed, 283 insertions, 0 deletions
diff --git a/files/zh-tw/learn/javascript/objects/object_building_practice/index.html b/files/zh-tw/learn/javascript/objects/object_building_practice/index.html
new file mode 100644
index 0000000000..4a47aa39eb
--- /dev/null
+++ b/files/zh-tw/learn/javascript/objects/object_building_practice/index.html
@@ -0,0 +1,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>