aboutsummaryrefslogtreecommitdiff
path: root/files/zh-cn/learn/javascript/objects/adding_bouncing_balls_features/index.html
diff options
context:
space:
mode:
Diffstat (limited to 'files/zh-cn/learn/javascript/objects/adding_bouncing_balls_features/index.html')
-rw-r--r--files/zh-cn/learn/javascript/objects/adding_bouncing_balls_features/index.html468
1 files changed, 468 insertions, 0 deletions
diff --git a/files/zh-cn/learn/javascript/objects/adding_bouncing_balls_features/index.html b/files/zh-cn/learn/javascript/objects/adding_bouncing_balls_features/index.html
new file mode 100644
index 0000000000..2730489d15
--- /dev/null
+++ b/files/zh-cn/learn/javascript/objects/adding_bouncing_balls_features/index.html
@@ -0,0 +1,468 @@
+---
+title: 为“弹球”示例添加新功能
+slug: Learn/JavaScript/Objects/向“弹跳球”演示程序添加新功能
+tags:
+ - JavaScript
+ - 初学者
+ - 对象
+ - 测验
+ - 面向对象
+translation_of: Learn/JavaScript/Objects/Adding_bouncing_balls_features
+---
+<div>{{LearnSidebar}}</div>
+
+<div>{{PreviousMenuNext("Learn/JavaScript/Objects/Object_building_practice", "", "Learn/JavaScript/Objects")}}</div>
+
+<p class="summary">在此次测验中, 你需要将上一节中的“弹球”演示程序作为模板,添加一些新的有趣的功能。</p>
+
+<table class="learn-box standard-table">
+ <tbody>
+ <tr>
+ <th scope="row">预备知识:</th>
+ <td>请确保完整学习本章所有内容后再开始测验。</td>
+ </tr>
+ <tr>
+ <th scope="row">目标:</th>
+ <td>测试你对 JavaScript 对象和面向对象结构的理解。</td>
+ </tr>
+ </tbody>
+</table>
+
+<h2 id="开始">开始</h2>
+
+<p>请先下载 <a class="external external-icon" href="https://github.com/roy-tian/learning-area/blob/master/javascript/oojs/bouncing-balls/index.html">index.html</a>、<a class="external external-icon" href="https://github.com/roy-tian/learning-area/blob/master/javascript/oojs/bouncing-balls/style.css">style.css</a> 和 <a class="external external-icon" href="https://github.com/roy-tian/learning-area/blob/master/javascript/oojs/bouncing-balls/main.js">main.js</a> 三个文件。</p>
+
+<div class="note">
+<p><strong>注:</strong>也可以使用 <a class="external external-icon" href="http://jsbin.com/">JSBin</a> 或 <a class="external external-icon" href="https://thimble.mozilla.org/">Thimble</a> 这样的网站来进行测验。 你可以选择其中一个将HTML,CSS 和JavaScript 粘贴过去。 如果你的版本没有单独的 JavaScript / CSS 板块,可以把它们嵌入 HTML 页面内的 <code>&lt;script&gt;</code>/<code>&lt;style&gt;</code> 元素。</p>
+</div>
+
+<h2 id="项目简介">项目简介</h2>
+
+<p>我们的弹球 demo 很有趣, 但是现在我们想让它更具有互动性,我们为它添加一个由玩家控制的“恶魔圈”,如果恶魔圈抓到弹球会把它会吃掉。我们还想测验你面向对象的水平,首先创建一个通用 <code>Shape()</code> 对象,然后由它派生出弹球和恶魔圈。最后,我们为 demo 添加一个计分器来记录剩下的球数。</p>
+
+<p>程序最终会像这样:</p>
+
+<div class="hidden">
+<h6 id="Evil_circle">Evil circle</h6>
+
+<pre class="brush: html notranslate">&lt;!DOCTYPE html&gt;
+&lt;html lang="zh-CN"&gt;
+ &lt;head&gt;
+ &lt;meta charset="utf-8"&gt;
+ &lt;title&gt;弹球&lt;/title&gt;
+ &lt;style&gt;
+body {
+    margin: 0;
+    overflow: hidden;
+    font-family: 'PingFangSC-Regular', '微软雅黑', sans-serif;
+    height: 100%;
+}
+
+h1 {
+    font-size: 2rem;
+    letter-spacing: -1px;
+    position: absolute;
+    margin: 0;
+    top: -4px;
+    right: 5px;
+
+    color: transparent;
+    text-shadow: 0 0 4px white;
+  }
+
+p {
+    position: absolute;
+    margin: 0;
+    top: 35px;
+    right: 5px;
+    color: #aaa;
+}  &lt;/style&gt;
+ &lt;/head&gt;
+
+ &lt;body&gt;
+ &lt;h1&gt;弹球&lt;/h1&gt;
+ &lt;p&gt;&lt;/p&gt;
+ &lt;canvas&gt;&lt;/canvas&gt;
+
+ &lt;script&gt;
+const BALLS_COUNT = 25;
+const BALL_SIZE_MIN = 10;
+const BALL_SIZE_MAX = 20;
+const BALL_SPEED_MAX = 7;
+
+class Shape {
+    constructor(x, y, velX, velY, exists) {
+        this.x = x;
+        this.y = y;
+        this.velX = velX;
+        this.velY = velY;
+        this.exists = exists;
+    }
+}
+
+class Ball extends Shape {
+    constructor(x, y, velX, velY, color, size, exists) {
+        super(x, y, velX, velY, exists);
+
+        this.color = color;
+        this.size = size;
+    }
+
+    draw() {
+        ctx.beginPath();
+        ctx.fillStyle = this.color;
+        ctx.arc(this.x, this.y, this.size, 0, 2 * Math.PI);
+        ctx.fill();
+    }
+
+    update() {
+        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;
+    }
+
+    collisionDetect() {
+        for (let j = 0; j &lt; 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 &lt; this.size + balls[j].size &amp;&amp; balls[j].exists) {
+                    balls[j].color = this.color = randomColor();
+                }
+            }
+        }
+    }
+}
+
+class EvilCircle extends Shape {
+    constructor(x, y, exists) {
+        super(x, y, exists);
+
+        this.velX = BALL_SPEED_MAX;
+        this.velY = BALL_SPEED_MAX;
+        this.color = "white";
+        this.size = 10;
+        this.setControls();
+    }
+
+    draw() {
+        ctx.beginPath();
+        ctx.strokeStyle = this.color;
+        ctx.lineWidth = 3;
+        ctx.arc(this.x, this.y, this.size, 0, 2 * Math.PI);
+        ctx.stroke();
+    }
+
+    checkBounds() {
+        if ((this.x + this.size) &gt;= width) {
+            this.x -= this.size;
+        }
+
+        if ((this.x - this.size) &lt;= 0) {
+            this.x += this.size;
+        }
+
+        if ((this.y + this.size) &gt;= height) {
+            this.y -= this.size;
+        }
+
+        if ((this.y - this.size) &lt;= 0) {
+            this.y += this.size;
+        }
+    }
+
+    setControls() {
+        window.onkeydown = (e) =&gt; {
+            switch(e.key) {
+                case 'a':
+                case 'A':
+                case 'ArrowLeft':
+                    this.x -= this.velX;
+                    break;
+                case 'd':
+                case 'D':
+                case 'ArrowRight':
+                    this.x += this.velX;
+                    break;
+                case 'w':
+                case 'W':
+                case 'ArrowUp':
+                    this.y -= this.velY;
+                    break;
+                case 's':
+                case 'S':
+                case 'ArrowDown':
+                    this.y += this.velY;
+                    break;
+            }
+        };
+    }
+
+    collisionDetect() {
+        for (let j = 0; j &lt; balls.length; j++) {
+            if (balls[j].exists) {
+                const dx = this.x - balls[j].x;
+                const dy = this.y - balls[j].y;
+                const distance = Math.sqrt(dx * dx + dy * dy);
+
+                if (distance &lt; this.size + balls[j].size) {
+                    balls[j].exists = false;
+                    count--;
+                    para.textContent = '还剩 ' + count + ' 个球';
+                }
+            }
+        }
+    }
+}
+
+const para = document.querySelector('p');
+const canvas = document.querySelector('canvas');
+const ctx = canvas.getContext('2d');
+
+const width = canvas.width = window.innerWidth;
+const height = canvas.height = window.innerHeight;
+
+const balls = [];
+let count = 0;
+
+const evilBall = new EvilCircle(
+    random(0, width),
+    random(0, height),
+    true
+);
+
+loop();
+
+function random(min,max) {
+    return Math.floor(Math.random()*(max-min)) + min;
+}
+
+function randomColor() {
+    return 'rgb(' +
+           random(0, 255) + ', ' +
+           random(0, 255) + ', ' +
+           random(0, 255) + ')';
+}
+
+function loop() {
+    ctx.fillStyle = 'rgba(0, 0, 0, 0.25)';
+    ctx.fillRect(0, 0, width, height);
+
+    while (balls.length &lt; BALLS_COUNT) {
+        const size = random(BALL_SIZE_MIN, BALL_SIZE_MAX);
+        const ball = new Ball(
+            random(0 + size, width - size),
+            random(0 + size, height - size),
+            random(-BALL_SPEED_MAX, BALL_SPEED_MAX),
+            random(-BALL_SPEED_MAX, BALL_SPEED_MAX),
+            randomColor(),
+            size,
+            true
+        );
+        balls.push(ball);
+        count++;
+        para.textContent = '还剩 ' + count + ' 个球';
+    }
+
+    for (let i = 0; i &lt; balls.length; i++) {
+        if (balls[i].exists) {
+            balls[i].draw();
+            balls[i].update();
+            balls[i].collisionDetect();
+        }
+    }
+
+    evilBall.draw();
+    evilBall.checkBounds();
+    evilBall.collisionDetect();
+
+    requestAnimationFrame(loop);
+}
+  &lt;/script&gt;
+ &lt;/body&gt;
+&lt;/html&gt;
+</pre>
+</div>
+
+<ul>
+</ul>
+
+<p>{{ EmbedLiveSample('Evil_circle', '100%', 480, "", "", "hide-codepen-jsfiddle") }}</p>
+
+<p>可以 <a class="external external-icon" href="https://roy-tian.github.io/learning-area/javascript/oojs/assessment/">查看完成版本</a> 来获得更全面的体验。(别偷看源代码哦。)</p>
+
+<h2 id="步骤">步骤</h2>
+
+<p>以下各节介绍你需要完成的步骤。</p>
+
+<h3 id="创建我们的新对象">创建我们的新对象</h3>
+
+<p>首先, 改变你现有的构造器 <code>Ball()</code> 使其成为构造器 <code>Shape()</code> 并添加一个新的构造器 <code>Ball()</code> :</p>
+
+<ol>
+ <li>构造器 <code>Shape()</code> 应该像构造器 <code>Ball()</code> 那样的方式定义 <code>x</code>, <code>y</code>, <code>velX</code>, 和 <code>velY</code> 属性,但不包括 <code>color</code> 和 <code>size</code> 。</li>
+ <li>还应该定义一个叫 <code>exists</code> 的新属性,用来标记球是否存在于程序中 (没有被恶魔圈吃掉)。这应该是一个布尔型((<code>true</code>/<code>false</code>)。</li>
+ <li>构造器 <code>Ball()</code> 应该从构造器 <code>Shape()</code> 继承 <code>x</code>, <code>y</code>, <code>velX</code>, <code>velY</code>,和 <code>exists</code> 属性。</li>
+ <li>构造器 <code>Ball()</code> 还应该像最初的构造器 <code>Ball()</code> 那样定义一个 <code>color</code> 和一个<code>size</code> 属性。</li>
+ <li>请记得给构造器 <code>Ball()</code> 的<code>prototype</code> 和 <code>constructor</code> 属性设置适当的值。</li>
+</ol>
+
+<p><code>draw()</code>, <code>update()</code>, 和<code>collisionDetect()</code> 方法定义应保持不变。</p>
+
+<p>你还需要为 <code>new Ball() { ... }</code> 构造器添加第五个参数—— <code>exists</code>, 且值为 <code>true</code>。</p>
+
+<p>到这里, 尝试重新加载代码(运行程序),程序以及重新设计的对象都应该像之前那样工作。</p>
+
+<h3 id="定义恶魔圈_EvilCircle">定义恶魔圈 EvilCircle()</h3>
+
+<p>现在是时候来看看那个坏蛋了——恶魔圈 <code>EvilCircle()</code>! 我们的游戏中只会有一个恶魔圈,但我们仍然要使用继承自 <code>Shape()</code> 的构造器来定义它,这是为让你得到锻炼。 之后你可能会想再添加一个由另一个玩家控制的恶魔圈到程序中,或者有几个电脑控制的恶魔圈。你可没法通过一个恶魔圈来掌管程序中的这个世界,但这个评估中就先只这么做吧。</p>
+
+<p><code>EvilCircle()</code> 构造器应该从<code>Shape()</code> 继承 <code>x</code>, <code>y</code>, 和 <code>exists</code> ,<code>velX</code> 和 <code>velY</code> 要恒为 20。</p>
+
+<p>可以这样做:<code>Shape.call(this, x, y, 20, 20, exists);</code></p>
+
+<p>它还应该定义自己的一些属性,如:</p>
+
+<ul>
+ <li><code>color</code> —— <code>'white'</code></li>
+ <li><code>size</code> —— <code>10</code></li>
+</ul>
+
+<p>再次记得给你的 <code>EvilCircle()</code> 构造器的传递的参数中定义你继承的属性,并且给<code>prototype</code> 和 <code>constructor</code> 属性设置适当的值。</p>
+
+<h3 id="定义_EvilCircle_的方法">定义 EvilCircle() 的方法</h3>
+
+<p><code>EvilCircle()</code> 应该有以下四个方法:</p>
+
+<h4 id="draw"><code>draw()</code></h4>
+
+<p>这个方法和 <code>Ball()</code>'s <code>draw()</code> 方法有着相同的目的:它们把都是对象的实例画在画布上(canvas) 。它们实现的方式差不多,所以你可以先复制 <code>Ball.prototype.draw</code> 的定义。然后你需要做下面的修改:</p>
+
+<ul>
+ <li>我们不想让恶魔圈是实心的,而是一个圈或者说是环。你可以通过将 <code><a href="/en-US/docs/Web/API/CanvasRenderingContext2D/fillStyle">fillStyle</a></code> 和 <code><a href="/en-US/docs/Web/API/CanvasRenderingContext2D/fill">fill()</a></code> 修改为 <code><a href="/en-US/docs/Web/API/CanvasRenderingContext2D/strokeStyle">strokeStyle</a></code> 和 <code><a href="/en-US/docs/Web/API/CanvasRenderingContext2D/stroke">stroke()</a></code>而实现这个效果。</li>
+ <li>我们还想让这个圈更厚一点, 从而使你能更好地辨认它。 可以在调用 <code><a href="/en-US/docs/Web/API/CanvasRenderingContext2D/beginPath">beginPath()</a></code> 的后面给 <code><a href="/en-US/docs/Web/API/CanvasRenderingContext2D/lineWidth">lineWidth</a></code> 赋值实现这个效果。(赋值为 3 就可以了)</li>
+</ul>
+
+<h4 id="checkBounds"><code>checkBounds()</code></h4>
+
+<p>这个方法和 <code>Ball()</code> 的 <code>update()</code> 函数做相同的事情—— 查看恶魔圈是否将要超出屏幕的边界, 并且禁止它超出。 同样,你可以直接复制 <code>Ball.prototype.update</code> 的定义, 但是你需要做一些修改:</p>
+
+<ul>
+ <li>删除最后两行 — 我们不想要在每一帧中自动的更新恶魔圈的位置,因为我们会以下面所述的方式移动它。</li>
+ <li>在 <code>if()</code> 语句中,如果检测为真(即小恶魔圈超出边界),我们不需要更新 <code>velX</code>/<code>velY</code>;取而代之的是,我们想要修改 <code>x</code>/<code>y</code> 的值,使恶魔圈稍微地弹回屏幕。增加或减去 (根据实际判断)恶魔圈 <code>size</code> 的值即可实现。</li>
+</ul>
+
+<h4 id="setControls"><code>setControls()</code></h4>
+
+<p>这个方法将会一个 <code>onkeydown</code> 的事件监听器给 <code>window</code> 对象,这样当特定的键盘按键按下的时候,我们就可以移动恶魔圈。下面的代码块应该放在方法的定义里:</p>
+
+<pre class="brush: js notranslate">window.onkeydown = e =&gt; {
+  switch(e.key) {
+    case 'a':
+      this.x -= this.velX;
+      break;
+    case 'd':
+      this.x += this.velX;
+      break;
+    case 'w':
+      this.y -= this.velY;
+      break;
+    case 's':
+      this.y += this.velY;
+      break;
+ }
+};</pre>
+
+<p>所以当一个按键按下时, 事件对象的 <a href="/zh-CN/docs/Web/API/KeyboardEvent/key">key</a> 属性 就可以请求到按下的按键值。如果是代码中那四个指定的键值之一, 那么恶魔圈将会左右上下的移动。</p>
+
+<div class="blockIndicator warning">
+<p><strong>译注:</strong>英文页面中使用了事件对象的 <a href="/zh-CN/docs/Web/API/KeyboardEvent/keyCode">keyCode</a> 属性,不推荐在新代码中使用该属性,应使用标准 <a href="/zh-CN/docs/Web/API/KeyboardEvent/key">key</a> 属性代替。(详见介绍页面)</p>
+</div>
+
+<div class="blockIndicator note"><strong>译注:</strong>这里的 <code>window.onkeydown</code> 用一个 <a href="/zh-CN/docs/Web/JavaScript/Reference/Functions/Arrow_functions">箭头函数</a> 代替了英文页面中的匿名函数,从而无需 <code>var _this = this</code>。</div>
+
+<h4 id="collisionDetect"><code>collisionDetect()</code></h4>
+
+<p>这个方法和 <code>Ball()</code>'s <code>collisionDetect()</code> 方法很相似,所以你可以从它那里复制过来作为新方法的基础。但有一些不同之处:</p>
+
+<ul>
+ <li>在外层的 <code>if</code> 语句中,你不需要再检验循环到的小球是否是当前 <code>collisionDetect()</code> 所在的对象 — 因为它不再是一个小球了,它是恶魔圈! 而是检查小球是否存在 (你可以通过哪个属性实现这个呢?)。如果小球不存在,说明它已经被恶魔圈吃掉了,那么就不需要再检测它是否与恶魔圈碰撞了。</li>
+ <li>在里层的 <code>if</code> 语句中,你不再需要在碰撞被检测到时去改变对象的颜色 — 而是需要将与恶魔圈发生碰撞的小球设置为不存在(再次提问,你觉得你该怎么实现呢?)。</li>
+</ul>
+
+<h3 id="把恶魔圈带到程序中">把恶魔圈带到程序中</h3>
+
+<p>现在我们已经定义了恶魔圈,我们需要让它显示到我们的屏幕中。为了做这件事,你需要修改一下 <code>loop()</code> 函数:</p>
+
+<ul>
+ <li>首先,创建一个新的恶魔圈的对象实例 (指定必需的参数),然后调用它的 <code>setControls()</code> 方法。 这两件事你只需要做一次,不需要放在loop的循环中。</li>
+ <li>在你每一次遍历小球并调用 <code>draw()</code>, <code>update()</code>, 和 <code>collisionDetect()</code> 函数的地方进行修改, 使这些函数只会在小球存在时被调用。</li>
+ <li>在每个loop的循环中调用恶魔圈实例的方法 <code>draw()</code>, <code>checkBounds()</code>, 和<code>collisionDetect()</code> 。</li>
+</ul>
+
+<h3 id="计算得分">计算得分</h3>
+
+<p>为了计算得分,需按照以下步骤:</p>
+
+<ol>
+ <li>在你的HTML文件中添加一个{{HTMLElement("p")}} 元素到 {{HTMLElement("h1")}} 元素的下面,其中包含文本 "还剩多少个球"。</li>
+ <li>在你的CSS文件中,添加下面的代码到底部:
+ <pre class="brush: css notranslate">p {
+ position: absolute;
+ margin: 0;
+ top: 35px;
+ right: 5px;
+ color: #aaa;
+}</pre>
+ </li>
+ <li>在你的 JavaScript 文件中,做下列的修改:
+ <ul>
+ <li>创建一个变量存储段落的引用。</li>
+ <li>以同样的方式在屏幕上显示小球的数量。</li>
+ <li>增加球数并在每次将球添加到屏幕里时显示更新的球数量。</li>
+ <li>减少球数并在每次恶魔吃球时显示更新的球数(因为被吃掉的球不存在了)</li>
+ </ul>
+ </li>
+</ol>
+
+<h2 id="提示">提示</h2>
+
+<ul>
+ <li>这个评估非常具有挑战性。请仔细按照步骤慢慢来。</li>
+ <li>每完成一个阶段时,你可以保留程序的副本,这是一种有用的方式。这样当你发现你程序出了问题,你可以参考之前的代码。</li>
+</ul>
+
+<h2 id="评定">评定</h2>
+
+<p>如果你将此评估作为有组织的课程的一部分,你可以将你的成果交给您的老师/导师进行评分。 如果你是自学的,通过在 <a href="https://discourse.mozilla-community.org/t/learning-web-development-marking-guides-and-questions/16294">Learning Area Discourse thread</a>, 或者在 <a href="https://wiki.mozilla.org/IRC">Mozilla IRC</a> 的 <a href="irc://irc.mozilla.org/mdn">#mdn</a> IRC 频道上申请,你可以十分容易地得到评分指南。首先先尝试这个练习,作弊不会有任何收获。</p>
+
+<p>{{PreviousMenuNext("Learn/JavaScript/Objects/Object_building_practice", "", "Learn/JavaScript/Objects")}}</p>
+
+<h2 id="本章目录">本章目录</h2>
+
+<ul>
+ <li><a href="/zh-CN/docs/Learn/JavaScript/Objects/Basics">对象基础</a></li>
+ <li><a href="/zh-CN/docs/Learn/JavaScript/Objects/Object-oriented_JS">适合初学者的 JavaScript 面向对象</a></li>
+ <li><a href="/zh-CN/docs/Learn/JavaScript/Objects/Object_prototypes">对象原型</a></li>
+ <li><a href="/zh-CN/docs/Learn/JavaScript/Objects/Inheritance">JavaScript 中的继承</a></li>
+ <li><a href="/zh-CN/docs/Learn/JavaScript/Objects/JSON">使用 JSON 数据</a></li>
+ <li><a href="/zh-CN/docs/Learn/JavaScript/Objects/Object_building_practice">构建对象实战</a></li>
+ <li><a href="/zh-CN/docs/Learn/JavaScript/Objects/Adding_bouncing_balls_features">向“弹跳球”演示程序添加新功能</a></li>
+</ul>