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
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
|
---
title: 实践对象构造
slug: Learn/JavaScript/Objects/Object_building_practice
tags:
- 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="/zh-CN/docs/Learn/JavaScript/First_steps">入门</a> 和 <a href="/zh-CN/docs/Learn/JavaScript/Building_blocks">构建块结构</a>)和面向对象的JavaScript (OOJS) 基础 (请参阅 <a href="/zh-CN/docs/Learn/JavaScript/Objects/Basics">对象基础</a>)。</td>
</tr>
<tr>
<th scope="row">目标:</th>
<td>练习使用对象,在真实环境中应用面向对象开发技术。</td>
</tr>
</tbody>
</table>
<h2 id="弹跳吧!小彩球!">弹跳吧!小彩球!</h2>
<p>本文通过编写一个弹球 demo 来展示 JavaScript 中对象的重要性。我们的小球会在屏幕上弹跳,当它们碰到彼此时会变色。最终会像这样:</p>
<div class="hidden">
<h6 id="Bouncing_balls">Bouncing balls</h6>
<pre class="brush: html"><!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<style>
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;
}
</style>
</head>
<body>
<h1>弹球</h1>
<canvas></canvas>
<script>
const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');
const width = canvas.width = window.innerWidth;
const height = canvas.height = window.innerHeight;
function random(min,max) {
const num = Math.floor(Math.random() * (max - min)) + min;
return num;
}
function randomColor() {
const color = 'rgb(' +
random(0, 255) + ',' +
random(0, 255) + ',' +
random(0, 255) + ')';
return color;
}
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;
}
Ball.prototype.draw = function() {
ctx.beginPath();
ctx.fillStyle = this.color;
ctx.arc(this.x, this.y, this.size, 0, 2 * Math.PI);
ctx.fill();
};
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;
};
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 = randomColor();
}
}
}
};
let balls = [];
while(balls.length < 25) {
const size = random(10,20);
let ball = new Ball(
random(0 + size, width - size),
random(0 + size, height - size),
random(-7, 7),
random(-7, 7),
randomColor(),
size
);
balls.push(ball);
}
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();
balls[i].collisionDetect();
}
requestAnimationFrame(loop);
}
loop();
</script>
</body>
</html>
</pre>
</div>
<ol>
</ol>
<p>{{ EmbedLiveSample('Bouncing_balls', '100%', 480, "", "", "hide-codepen-jsfiddle") }}</p>
<p>这个实例将会利用 <a href="/zh-CN/docs/Learn/JavaScript/Client-side_web_APIs/Drawing_graphics">Canvas API</a> 来在屏幕上画小球, 还会用到 <a href="/zh-CN/docs/Web/API/window/requestAnimationFrame">requestAnimationFrame</a> API 来使整个画面动起来 —— 我们并不要求你事先学习过这些 API 的相关知识,希望你完成这个练习之后会想去探索更多。这个过程中我们会用到一些漂亮的小东西并向你展示一些技巧,比如小球从墙上反弹,检查它们是否撞到了对方 (也就是碰撞检测)。</p>
<h2 id="让我们开始吧">让我们开始吧</h2>
<p>首先下载 <a class="external external-icon" href="https://raw.githubusercontent.com/roy-tian/learning-area/master/javascript/oojs/bouncing-balls/bouncing-balls-start.zip">bouncing-balls-start.zip</a>,其中包含以下三个文件:index.html、style.css 和 main.js。它们分别包含以下内容:</p>
<ol>
<li>一个非常简单的 HTML 文档,包括一个 <code><h1></code> 元素、一个{{HTMLElement("canvas")}} 元素来画小球,还有一些元素将 CSS 和 JavaScript 运用到我们的 HTML 中。</li>
<li>一些非常简单的样式,主要是 <code><h1></code> 元素的样式和定位,另外还能使画面填充整个页面从而摆脱滚动条和边缘的空白(这样看起来非常简洁)</li>
<li>一些 JavaScript 用来设置 <code><canvas></code> 元素,并提供我们要用到的基本函数。</li>
</ol>
<p>脚本的第一部分是这样的:</p>
<pre class="brush: js">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="/en-US/docs/Web/API/HTMLCanvasElement/getContext">getContext()</a></code> 从而我们获得一个开始画画的环境。存储以上操作结果的变量(<code>ctx</code>)是一个对象,直接代指画布上的一块允许我们绘制 2D 图形的区域。</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">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) + ')';
}</pre>
<p>第一个函数为我们生成一个 <code>min</code> 至 <code>max</code> 之间的随机整数,第二个函数为我们生成一个随机的颜色值。</p>
<h2 id="为程序中的小球建立模型">为程序中的小球建立模型</h2>
<p>我们的项目中会有很多小球在屏幕上跳来跳去。因此这些小球会以相同的方式运作,从而我们可以通过一个对象实例化它们。首先,我们将下面的构造器加入到代码的底部。</p>
<pre class="brush: js">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="画小球">画小球</h3>
<p>首先给小球的原型加上 <code>draw()</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>通过使用这个函数,通过使用我们之前定义的 <code>ctx</code>对象 的方法,我们就可以让在屏幕上画出小球了。<code>ctx</code> 的内容区域就像是一张纸,现在我们就可以命令我们的笔画一点东西。</p>
<ul>
<li>首先,我们使用 <code><a href="/zh-CN/docs/Web/API/CanvasRenderingContext2D/beginPath">beginPath()</a></code> 来声明我们现在要开始在纸上画一个图形了。</li>
<li>然后,我们使用 <code><a href="/zh-CN/docs/Web/API/CanvasRenderingContext2D/fillStyle">fillStyle</a></code> 来定义这个图形的颜色 — 这个值正是小球的颜色属性。</li>
<li>接下来,我们使用 <code><a href="/zh-CN/docs/Web/API/CanvasRenderingContext2D/arc">arc()</a></code> 方法来在纸上画出一段圆弧。有这些参数:
<ul>
<li><code>x</code> 和 <code>y</code> 是圆弧的中心的坐标 —— 也就是小球的中心坐标。</li>
<li>圆弧的半径 —— 小球的半径。</li>
<li>最后两个参数是开始和结束,也就是圆弧对应的夹角,单位以弧度表示。这里我们用的是 0 和 <code>2 * PI</code>,也就是 360 度(如果你设置成 0 和 <code>1 * PI</code>,则只会出现一个半圆,也就是 180 度)</li>
</ul>
</li>
<li>最后,我们使用 <code><a href="/zh-CN/docs/Web/API/CanvasRenderingContext2D/fill">fill()</a></code> 方法,也就是声明我们结束了以 <code>beginPath()</code> 开始的绘画,并且使用我们之前设置的颜色进行填充。 </li>
</ul>
<p>现在你已经可以测试你的对象了。</p>
<ol>
<li>保存代码,将 HTML 加载到浏览器中。</li>
<li>打开浏览器中的 JavaScript 控制台,刷新页面,从而画布可以根据可视的区域调整自己的大小。</li>
<li>通过下面的代码创建一个小球实例。
<pre class="brush: js">let testBall = new Ball(50, 100, 4, 4, 'blue', 10);</pre>
</li>
<li>你可以调用实例的这些属性。
<pre class="brush: js">testBall.x
testBall.size
testBall.color
testBall.draw()</pre>
</li>
<li>当你键入最后一行的时候,你会在你的画布上看到一个小球被画出来了。</li>
</ol>
<h3 id="更新小球的数据">更新小球的数据</h3>
<p>我们可以在一个固定位置画出小球,但是他们不会动,我们需要一个函数来更新一些东西。在 JavaScript 文件底部加上下面的代码,也就是给小球原型加上一个 update() 方法。</p>
<pre class="brush: js">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>函数的前四个部分用来检查小球是否碰到画布的边缘。如果碰到,我们反转小球的速度方向来让它向反方向移动。就比如说,如果小球正向上移动 (正 <code>velY</code>),然后垂直速度发生改变,小球就向下移动 (负 <code>velY</code>)。</p>
<p>在这四部分中,我们:</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>x</code>/<code>y</code> 坐标是小球中心的坐标,我们希望小球在其边界接触浏览器窗口的边界时反弹,而不是小球的一部分都不见了再返回。</p>
<p>最后两行,我们将 <code>velX</code> 的值加到 <code>x</code> 的坐标上,将 <code>velY</code> 的值加到 <code>y</code> 坐标上 —— 每次调用这个方法的时候小球就移动这么多。</p>
<p>暂时先这样做; 让我们继续做一些动画!</p>
<h2 id="让球动起来">让球动起来 </h2>
<p>现在就变得非常有趣了。我们在画布上加上一些小球,并且让他们动起来。</p>
<ol>
<li>首先我们需要一个地方储存小球,下面的数组会干这件事 —— 现在将它添加到你的代码底部:
<pre class="brush: js">let balls = [];
while (balls.length < 25) {
let size = random(10, 20);
let ball = new Ball(
// 为避免绘制错误,球至少离画布边缘球本身一倍宽度的距离
random(0 + size, width - size),
random(0 + size, height - size),
random(-7, 7),
random(-7, 7),
randomColor(),
size
);
balls.push(ball);
}
</pre>
</li>
<li>
<p>几乎所有的动画效果都会用到一个运动循环,也就是每一帧都自动更新视图。这是大多数游戏或者其他类似项目的基础。</p>
</li>
<li>现在将它添加到你的代码底部:
<pre class="brush: js">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>(这四个参数分别是起始的坐标、绘制的矩形的宽和高)画出一个填充满整个画布的矩形。这是在下一个视图画出来时用来遮住之前的视图的。如果不这样做得话,你就会在屏幕上看到一条蛇的形状而不是小球的运动了。用来填充的颜色设置成半透明的<code>rgba(0,0,0,0.25)</code>,也就是让之前的视图留下来一点点,从而你可以看到小球运动时的轨迹。如果你将 0.25 设置成 1 时,你就完全看不到了。试着改变其中的值查看造成的影响。</li>
<li>当且仅当小球数量小于 25 时,将 <code>random()</code> 函数产生的数字传入新的小球实例从而创建一个新的小球,并且加入到数组中。因此当屏幕上有 25 个小球时,不会再出现更多小球。你可以改变这个值,从而看到不同小球个数造成的影响。如果你的电脑或者浏览器性能不怎么样的话,几千个小球的速度就会明显慢下来。</li>
<li>遍历数组中的所有小球,并且让每个小球都调用 <code>draw()</code> 和 <code>update()</code> 函数来将自己画出来,并且再接下来的每一帧都按照其速度进行位置的更新。</li>
<li>使用 <code>requestAnimationFrame()</code> 方法再运行一次函数 —— 当一个函数正在运行时传递相同的函数名,从而每隔一小段时间都会运行一次这个函数,这样我们可以得到一个平滑的动画效果。这主要是通过递归完成的 —— 也就是说函数每次运行的时候都会调用自己,从而可以一遍又一遍得运行。</li>
</ul>
</li>
<li>最后但是非常重要的是,加上下面这一行 —— 让动画开始运行的话我们需要调用这个函数。
<pre class="brush: js">loop();</pre>
</li>
</ol>
<p>完成这些基础的之后在浏览器打开测试一下!</p>
<h2 id="添加碰撞检测">添加碰撞检测</h2>
<p>现在会更加有趣,给我们的项目加上碰撞检测,从而小球会知道他们正在撞击其他的球。</p>
<ol>
<li>首先在 <code>update()</code> 方法后添加以下方法 (即 <code>Ball.prototype.update</code> 的下面)。
<pre class="brush: js">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 = randomColor();
}
}
}
}</pre>
<p>这个方法有一点点复杂,如果不理解的话不必过分担心,下面是对它的解释:</p>
<ul>
<li>对于每个小球,我们都要检查其他的小球是否和当前这个小球相撞了。为了达到此目的,我们构造另外一个 <code>for</code> 循环来遍历 <code>balls[]</code> 数组中的小球。</li>
<li>在循环里面,我们使用一个 <code>if</code> 语句来检查遍历的小球是否是当前的小球。我们不希望检测到一个小球撞到了自己!为了达到这个目的,我们需要检查当前小球 (即正在调用 <code>collisionDetect</code> 方法的球) 是否和被循环到的小球 (<code>for</code> 循环检测中的当前遍历所引用的球) 是不是同一个。我们使用 <code>!</code> 来否定判断,因此只有两个小球<strong>不是</strong>同一个时,条件判断中的代码才会运行。</li>
<li>我们使用了一个常见的算法来检测两个小球是否相撞了,两个小球中心的距离是否小于两个小球的半径之和。这些会在 <a href="/zh-CN/docs/Games/Techniques/2D_collision_detection">2D 碰撞检测</a> 介绍地更加详细。</li>
<li>如果检测到了碰撞,会运行 <code>if</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 class="external external-icon" href="https://github.com/roy-tian/learning-area/tree/master/javascript/oojs/bouncing-balls">最终版本</a>,或者 <a class="external external-icon" href="https://roy-tian.github.io/learning-area/javascript/oojs/bouncing-balls/">在线试用</a>。</p>
</div>
<h2 id="概要">概要</h2>
<p>我们希望你玩得开心,编写出你自己的随机弹跳球的例子,在整个程序中使用各种对象和面向对象的技术! 在你实际运用对象中能提供一些有用的帮助。</p>
<p>对象文章就到这里了。现在剩下的就是在下一节的对象评估中测试你的技能。</p>
<h2 id="另请参阅">另请参阅</h2>
<ul>
<li><a href="/zh-CN/docs/Web/API/Canvas_API/Tutorial">Canvas tutorial</a> —— 2D canvas 初学者指南.</li>
<li><a href="/zh-CN/docs/Web/API/window/requestAnimationFrame">requestAnimationFrame()</a></li>
<li><a href="zh-CN/docs/Games/Techniques/2D_collision_detection">2D 碰撞检测</a></li>
<li><a href="/zh-CN/docs/Games/Techniques/3D_collision_detection">3D 碰撞检测</a></li>
<li><a href="/zh-CN/docs/Games/Tutorials/2D_Breakout_game_pure_JavaScript">纯 JavaScript 编写的 2D 消除游戏</a> —— 一个很好的2D游戏开发初学者教程.</li>
<li><a href="/zh-CN/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>
<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>
|