JavaScript 实践对象构造
目标:练习面向对象的开发技术
一、实现一些弹跳球
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Bouncing balls</title>
<style type="text/css">
html, body {
margin: 0;
}
html {
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
height: 100%;
}
body {
overflow: hidden;
height: inherit;
}
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>bouncing balls</h1>
<canvas></canvas>
<script type="text/javascript">
// setup canvas
var canvas = document.querySelector('canvas');
var ctx = canvas.getContext('2d');
var width = canvas.width = window.innerWidth;
var height = canvas.height = window.innerHeight;
// function to generate random number
function random(min,max) {
var num = Math.floor(Math.random()*(max-min)) + min;
return num;
}
// 构造器中定义了每个小球需要的参数
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();
}
// 创建实例
var testBall = new Ball(50, 100, 4, 4, 'blue', 10);
// 调用实例的这些属性
testBall.x
testBall.size
testBall.color
testBall.draw()
// 给小球原型加上一个 update() 方法,使其可以变化
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(var j = 0; j < balls.length; j++){
if (!(this === balls[j])) {
var dx = this.x - balls[j].x;
var dy = this.y - balls[j].y;
var 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) + ')';
}
}
}
}
// 让球动起来
var balls = [];
function loop(){
ctx.fillStyle = 'rgba(0, 0, 0, 0.25)';
ctx.fillRect(0, 0, width, height);
while (balls.length < 25){
var ball = new Ball(
random(0,width),
random(0,height),
random(-7,7),
random(-7,7),
'rgb(' + random(0,255) + ',' + random(0,255) + ',' + random(0,255) + ')',
random(10,20)
);
balls.push(ball);
}
for (var i = 0; i < balls.length; i++){
balls[i].draw();
balls[i].update();
balls[i].collisionDetect();
}
requestAnimationFrame(loop);
}
loop();
balls[i].collisionDetect();
</script>
</body>
</html>
这个实例将会利用 Canvas API 来在屏幕上画小球, 还会用到 requestAnimationFrame API 来使整个画面动起来 —— 我们并不要求你事先学习过这些 API 的相关知识,希望你完成这个练习之后会想去探索更多。这个过程中我们会用到一些漂亮的小东西并向你展示一些技巧,比如小球从墙上反弹,检查它们是否撞到了对方 (也就是碰撞检测)。
二、让我们开始吧
第一部分的代码先这样:
var canvas = document.querySelector('canvas');
var ctx = canvas.getContext('2d');
var width = canvas.width = window.innerWidth;
var height = canvas.height = window.innerHeight;
这个脚本使用变量代指了 <canvas>
元素,然后对其调用 getContext() 从而我们获得一个开始画画的环境。存储以上操作结果的变量(ctx)是一个对象,直接代指 canvas 上的一块允许我们绘制 2D 图形的区域。
接下来,我们设置 width 和 height 变量,并且让 canvas 元素的宽和高(分别使用 canvas.width 和 canvas.height 表示)等于浏览器的宽和高(也就是网页显示的区域 — 可以从Window.innerWidth 和 Window.innerHeight参数获得)。
你会看到我们在这里串联了多个赋值表达式在一起,这样能更快地设置变量——这是完全正确的。
最后一点原始代码如下:
function random(min, max) {
var num = Math.floor(Math.random() * (max - min + 1)) + min;
return num;
}
这个函数将两个数字作为参数传入,并且返回一个这两个数字之间的一个随机数。
三、将我们项目中的小球模型化
我们的项目中会有很多小球在屏幕上跳来跳去。因此这些小球会以相同的方式运作,从而我们可以通过一个对象实例化它们。首先,我们将下面的构造器加入到代码的底部。
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;
}
这个构造器中定义了每个小球需要的参数:
- x 和 y 坐标 —— 小球在屏幕上最开始时候的坐标。坐标的范围从 0 (左上角)到浏览器视窗的宽和高(右下角)。
- 水平和竖直速度(velX 和 velY)— 我们会给每个小球一个水平和竖直速度。实际上,当我们让这些球开始运动时候,每过一帧都会给小球的 x 和 y 坐标加一次这些值。
- color —— 每一个小球会有自己的颜色。
- size —— 每一个小球会有自己的大小 — 也就是小球的半径,以像素为单位。
这里说明了小球的属性,那么方法呢?别忘了我们要让小球动起来。
四、画小球
首先给小球的原型加上 draw( ) 方法:
Ball.prototype.draw = function() {
ctx.beginPath();
ctx.fillStyle = this.color;
ctx.arc(this.x, this.y, this.size, 0, 2 * Math.PI);
ctx.fill();
}
通过使用这个函数,通过使用我们之前定义的 ctx对象 的方法,我们就可以让在屏幕上画出小球了。ctx的内容区域就像是一张纸,现在我们就可以命令我们的笔画一点东西。
- 首先,我们使用 beginPath() 来声明我们现在要开始在纸上画一个图形了。
- 然后,我们使用 fillStyle 来定义这个图形的颜色 — 这个值正是小球的颜色属性。
- 接下来,我们使用 arc() 方法来在纸上画出一段圆弧。有这些参数:
- x 和 y 是圆弧的中心的坐标 —— 也就是小球的中心坐标。
- 圆弧的半径 —— 小球的半径。
- 最后两个参数是开始和结束的角度,也就是圆弧对应的夹角。这里我们用的是 0 和 2 * PI,也就是 360 度(如果你设置成 0 和 1 * PI,则只会出现一个半圆,也就是 180 度)
- 最后,我们使用 fill() 方法,也就是声明我们结束了以
beginPath()
开始的绘画,并且使用我们之前设置的颜色进行填充。
现在你已经可以测试你的对象了。保存代码,将 HTML 加载到浏览器中。打开浏览器中的 JavaScript 控制台,刷新页面,从而画布可以根据可视的区域调整自己的大小。
通过下面的代码创建一个小球实例。
var testBall = new Ball(50, 100, 4, 4, 'blue', 10);
4、你可以调用实例的这些属性。
testBall.x
testBall.size
testBall.color
testBall.draw()
5、当你键入最后一行的时候,你会在你的画布上看到一个小球被画出来了。
五、更新小球的数据
我们可以在一个固定位置画出小球,但是他们不会动,我们需要一个函数来更新一些东西。在 JavaScript 文件底部加上下面的代码,也就是给小球原型加上一个 update()
方法。
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;
}
函数的前四个部分用来检查小球是否碰到画布的边缘。如果碰到,我们反转小球的速度方向来让它向反方向移动。就比如说,如果小球正向上移动 (正 velY),然后垂直速度发生改变,小球就向下移动 (负 velY)。
在这四部分中,我们:
- 检查小球的x坐标是否大于画布的宽度(小球会从右边缘离开)
- 检查小球的x坐标是否小于0(小球会从左边缘离开)
- 检查小球的y坐标是否大于画布的高度(小球会从下边缘离开)
- 检查小球的y坐标是否小于0(小球会从上边缘离开)
在每种情况下,我们都会加上小球的半径,因为 x/y 坐标是小球中心的坐标,我们希望小球在其边界接触浏览器窗口的边界时反弹,而不是小球的一部分都不见了再返回。
最后两行,我们将 velX 的值加到 x 的坐标上,将 velY 的值加到 y 坐标上 —— 每次调用这个方法的时候小球就移动这么多。
暂时先这样做; 让我们继续做一些动画!
六、让球动起来
现在就变得非常有趣了。我们在画布上加上一些小球,并且让他们动起来。
1、首先我们需要一个地方储存小球,下面的数组会干这件事 —— 现在将它添加到你的代码底部:
var balls = [];
几乎所有的动画效果都会用到一个运动循环,也就是每一帧都自动更新视图。这是大多数游戏或者其他类似项目的基础。
2、现在将它添加到你的代码底部:
function loop() {
ctx.fillStyle = 'rgba(0, 0, 0, 0.25)';
ctx.fillRect(0, 0, width, height);
while (balls.length < 25) {
var ball = new Ball(
random(0,width),
random(0,height),
random(-7,7),
random(-7,7),
'rgb(' + random(0,255) + ',' + random(0,255) + ',' + random(0,255) +')',
random(10,20)
);
balls.push(ball);
}
for (var i = 0; i < balls.length; i++) {
balls[i].draw();
balls[i].update();
}
requestAnimationFrame(loop);
}
loop()
函数做了下面的事情:
- 将整个画布的颜色设置成半透明的黑色。然后使用 fillRect()(这四个参数分别是起始的坐标、绘制的矩形的宽和高)画出一个填充满整个画布的矩形。这是在下一个视图画出来时用来遮住之前的视图的。如果不这样做得话,你就会在屏幕上看到一条蛇的形状而不是小球的运动了。用来填充的颜色设置成半透明的rgba(0,0,0,0.25),也就是让之前的视图留下来一点点,从而你可以看到小球运动时的尾巴。如果你将 0.25 设置成 1 时,你就完全看不到了。试着改变其中的值查看造成的影响。
- 当且仅当小球数量小于 25 时,将 random() 函数产生的数字传入新的小球实例从而创建一个新的小球,并且加入到数组中。因此当屏幕上有 25 个小球时,不会再出现更多小球。你可以改变这个值,从而看到不同小球个数造成的影响。如果你的电脑或者浏览器性能不怎么样的话,几千个小球的速度就会明显慢下来。
- 遍历数组中的所有小球,并且让每个小球都调用 draw() 和 update() 函数来将自己画出来,并且再接下来的每一帧都按照其速度进行位置的更新。
- 使用 requestAnimationFrame() 方法再运行一次函数 —— 当一个函数正在运行时传递相同的函数名,从而每隔一小段时间都会运行一次这个函数,这样我们可以得到一个平滑的动画效果。这主要是通过递归完成的 —— 也就是说函数每次运行的时候都会调用自己,从而可以一遍又一遍得运行。
3、最后但是非常重要的是,加上下面这一行 —— 让动画开始运行的话我们需要调用这个函数。
loop();
七、增加撞击侦察
现在会更加有趣,给我们的项目加上撞击侦察,从而小球会知道他们正在撞击。、
1、首先在 update() 方法下加上下面的方法 (例如 Ball.prototype.update的下面)。
Ball.prototype.collisionDetect = function() {
for (var j = 0; j < balls.length; j++) {
if (!(this === balls[j])) {
var dx = this.x - balls[j].x;
var dy = this.y - balls[j].y;
var 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) +')';
}
}
}
}
这个方法有一点点复杂,如果不理解的话不必过分担心,下面是对它的解释:
- 对于每个小球,我们都要检查其他的小球是否和当前这个小球相撞了。为了达到此目的,我们构造另外一个循环来遍历数组中的小球。
- 在循环里面,我们使用一个条件判断来检查遍历的小球是否是当前的小球。我们不希望检测到一个小球撞到了自己!为了达到这个目的,我们需要检查当前小球 (即正在调用collisionDetect方法的球) 是否和被循环到的小球 (for循环检测中的当前遍历所引用的球) 是不是同一个。我们使用 ! 来否定判断,因此只有两个小球不是同一个时,条件判断中的代码才会运行。
- 我们使用了一个常见的算法来检测两个小球是否相撞了,两个小球中心的距离是否小于两个小球的半径之和。这些会在 2D collision detection 介绍地更加详细。
- 如果两个小球真的相撞了,会运行 if 下的代码。我们会将两个小球的颜色都设置成随机的一种。我们也可以将这步操作变得复杂一点,比如让两个小球弹开,那样需要植入更加复杂的代码。像这样的物理场景,有以下专门的库比如 PhysicsJS,matter.js,Phaser 等。
2、我们也需要在每一帧动画中都调用这个函数,因此在 balls[i].update()
加上下面的代码:
balls[i].collisionDetect();
3、保存文件,刷新浏览器,你就会看到小球在撞击之后变色了!
八、另可参见
- Canvas tutorial —— 2D canvas 初学者指南
- requestAnimationFrame()
- 2D collision detection
- 3D collision detection
- 2D breakout game using pure JavaScript —— 一个很好的 2D 游戏开发初学者教程
- 2D breakout game using Phaser —— JavaScript 游戏库构建 2D 游戏的基础知识
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论