تمرین ساخت شیئ در جاوا اسکریپت (بخش دوم) — راهنمای کاربردی
این مقاله در واقع یک آزمون ارزیابی است که در آن انتظار میرود شما بتوانید از دموی توپهای جهنده که در بخش قبلی با استفاده از Canvas API ساختیم به عنوان یک نقطه شروع استفاده کرده و با بهرهگیری از مفاهیم اشیای جاوا اسکریپت برخی ویژگیهای جدید و جذاب را به این توپها اضافه کنید.
شروع
برای شروع ابتدا کدهای زیر را در فایلهایی با نام مشخص شده در یک دایرکتوری جدید روی سیستم خود کپی کنید.
فایل index-finished.html
1<!DOCTYPE html>
2<html>
3 <head>
4 <meta charset="utf-8">
5 <title>Bouncing balls</title>
6 <link rel="stylesheet" href="style.css">
7 </head>
8
9 <body>
10 <h1>bouncing balls</h1>
11 <canvas></canvas>
12
13 <script src="main-finished.js"></script>
14 </body>
15</html>
فایل style.css
1html, body {
2 margin: 0;
3}
4
5html {
6 font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
7 height: 100%;
8}
9
10body {
11 overflow: hidden;
12 height: inherit;
13}
14
15h1 {
16 font-size: 2rem;
17 letter-spacing: -1px;
18 position: absolute;
19 margin: 0;
20 top: -4px;
21 right: 5px;
22
23 color: transparent;
24 text-shadow: 0 0 4px white;
25}
فایل main-finished.js
1// setup canvas
2
3var canvas = document.querySelector('canvas');
4var ctx = canvas.getContext('2d');
5
6var width = canvas.width = window.innerWidth;
7var height = canvas.height = window.innerHeight;
8
9// function to generate random number
10
11function random(min,max) {
12 var num = Math.floor(Math.random()*(max-min)) + min;
13 return num;
14}
15
16// define Ball constructor
17
18function Ball(x, y, velX, velY, color, size) {
19 this.x = x;
20 this.y = y;
21 this.velX = velX;
22 this.velY = velY;
23 this.color = color;
24 this.size = size;
25}
26
27// define ball draw method
28
29Ball.prototype.draw = function() {
30 ctx.beginPath();
31 ctx.fillStyle = this.color;
32 ctx.arc(this.x, this.y, this.size, 0, 2 * Math.PI);
33 ctx.fill();
34};
35
36// define ball update method
37
38Ball.prototype.update = function() {
39 if((this.x + this.size) >= width) {
40 this.velX = -(this.velX);
41 }
42
43 if((this.x - this.size) <= 0) {
44 this.velX = -(this.velX);
45 }
46
47 if((this.y + this.size) >= height) {
48 this.velY = -(this.velY);
49 }
50
51 if((this.y - this.size) <= 0) {
52 this.velY = -(this.velY);
53 }
54
55 this.x += this.velX;
56 this.y += this.velY;
57};
58
59// define ball collision detection
60
61Ball.prototype.collisionDetect = function() {
62 for(var j = 0; j < balls.length; j++) {
63 if(!(this === balls[j])) {
64 var dx = this.x - balls[j].x;
65 var dy = this.y - balls[j].y;
66 var distance = Math.sqrt(dx * dx + dy * dy);
67
68 if (distance < this.size + balls[j].size) {
69 balls[j].color = this.color = 'rgb(' + random(0,255) + ',' + random(0,255) + ',' + random(0,255) +')';
70 }
71 }
72 }
73};
74
75// define array to store balls and populate it
76
77var balls = [];
78
79while(balls.length < 25) {
80 var size = random(10,20);
81 var ball = new Ball(
82 // ball position always drawn at least one ball width
83 // away from the adge of the canvas, to avoid drawing errors
84 random(0 + size,width - size),
85 random(0 + size,height - size),
86 random(-7,7),
87 random(-7,7),
88 'rgb(' + random(0,255) + ',' + random(0,255) + ',' + random(0,255) +')',
89 size
90 );
91 balls.push(ball);
92}
93
94// define loop that keeps drawing the scene constantly
95
96function loop() {
97 ctx.fillStyle = 'rgba(0,0,0,0.25)';
98 ctx.fillRect(0,0,width,height);
99
100 for(var i = 0; i < balls.length; i++) {
101 balls[i].draw();
102 balls[i].update();
103 balls[i].collisionDetect();
104 }
105
106 requestAnimationFrame(loop);
107}
108
109
110
111loop();
شرح پروژه
دموی توپهای جهندهای که در بخش قبل ساختیم بسیار جالب بود، اما اکنون میخواهیم آن را با افزودن یک دایره شیطانی که از سوی کاربر کنترل میشود، تعاملپذیرتر کنیم. این دایره شیطانی توپهایی که به درونش بیفتد را میبلعد. همچنین میخواهیم مهارتهای ساخت شیء شما را از طریق ایجاد یک شیء ()Shape که توپها و دایره شیطانی از آن به ارث میرسند ارزیابی کنیم. در نهایت میخواهیم یک شمارنده امتیاز نیز برای ردگیری تعداد توپهایی که روی صفحه باقیمانده است بسازیم.
در تصویر زیر ایدهای از آن چه قرار است در آخر این مقاله ساخته باشیم، به دست میآورید:
همچنین برای این که ایده بهتری از برنامه نهایی داشته باشید میتوانید به این لینک (+) مراجعه کنید. البته از شما انتظار داریم که سورس کد این مثال را نگاه نکنید و خودتان کار را به پیش ببرید.
مراحل تکمیل پروژه
در این بخش مراحلی که باید انجام دهید را توضیح دادهایم.
ایجاد اشیای جدید
قبل از هر چیز باید سازنده ()Ball قبلی را طوری تغییر دهید که به یک سازنده ()Shape تبدیل شود و یک سازنده ()Ball جدید به آن اضافه کنید:
- سازنده ()Shape باید به همان روشی که سازنده ()Ball در مقاله قبلی انجام داده بود، به تعریف مشخصههای x ،y ،velX و velY بپردازد، اما مشخصههای color و size به روش متفاوتی تعریف خواهند شد.
- در این سازنده جدید باید مشخصههایی به نام exists وجود داشته باشد که برای ردگیری وجود یا عدم وجود توپ در برنامه استفاده میشود. این مشخصه در مواردی که دایره توپها را میبلعد به کار میآید و میبایست نوع بولی (true/false) داشته باشد.
- سازنده ()Ball باید مشخصههای x ،y ،velX ،velY و exists را از سازنده ()Shape به ارث ببرد.
- همچنین باید مشخصههای color و size را به همان روشی که از سوی سازنده ()Ball تعریف شده بود در سازنده ()Shape تعریف کنیم.
- به خاطر داشته باشید که prototype و constructor سازنده ()Ball را به طرز متناسبی تنظیم کنید.
تعاریف متدهای ()draw() ،update و ()collisionDetect میتوانند به همان روشی که در مطلب قبلی تعریف کردیم باقی بمانند.
همچنین باید یک پارامتر جدید به فراخوانی سازنده (...) ()new Ball اضافه کنید. پارامتر exists باید پنجمین پارامتر و دارای مقدار true باشد.
در این مرحله کد را بارگذاری مجدد کنید. عملکرد آن به وسیله شیءهایی که بازطراحی کردهایم، باید مانند دموی قبلی باشد.
تعریف کردن ()EvilCircle
اینک زمان آن رسیده است که شخصیت منفی داستان یعنی ()EvilCircle را طراحی کنیم. در این داستان تنها یک دایره شیطانی به عنوان شخصیت منفی وجود دارد، اما به هر حال باید آن را به وسیله سازندهای که از ()Shape به ارث میرسد تعریف کنیم. شما ممکن است بخواهید در ادامه دایره دیگری به برنامه اضافه کنید که کنترل آن دست بازیکن دیگری باشد و یا چند دایره شیطانی داشته باشید که از سوی رایانه کنترل میشوند. البته شما احتمالاً نمیخواهید همه دنیا را با استفاده از یک دایره شیطانی منفرد ببلعید، اما در این مطلب ارزیابی به همان یک دایره اکتفا میکنیم.
سازنده ()EvilCircle باید x ،y ،velX ،velY و exists را از ()Shape به ارث ببرد، اما velX و velY همواره باید برابر با 20 باشند.
این کار با کدی مانند زیر ممکن است:
1Shape.call(this، x، y، 20، 20، exists);
این کد همچنین باید مشخصههای خود را به صورت زیر تعریف کند:
- color — 'white'
- size — 10
یک بار دیگر به خاطر داشته باشید که باید مشخصههایی که به ارث میرسند را به صورت پارامتر در سازنده تعریف کنید و مشخصههای prototype و constructor را نیز به طور متناسبی تعیین کنید.
تعریف کردن متدهای ()EvilCircle
()EvilCircle باید چهار متد داشته باشد که هر کدام را در ادامه توضیح دادهایم:
متد ()draw
این متد همان منظوری را دنبال میکند که متد ()draw شیء ()Ball داشت. یعنی وهلهای از شیء را روی بوم ترسیم میکند. روش کار آن نیز به صورت مشابه است و از این رو میتوانید تعریف Ball.prototype.draw را کپی کرده و در ادامه تغییرهای زیر را در آن ایجاد کنید:
ما میخواهیم دایره شیطانی تو پر نباشد بلکه صرفاً یک لبه بیرونی داشته باشد. این وضعیت از طریق بهروزرسانی fillStyle و ()fill به strokeStyle و ()stroke ممکن خواهد بود.
همچنین میخواهیم که ضخامت لبه این دایره کمی بیشتر باشد تا بتوان دایره شطانی را راحتتر مشاهده کرد. این وضعیت از طریق تنیم مقدار linewidth در جایی پس از فراخوانی ()beginPath ممکن خواهد بود.
متد ()checkBounds
این متد همان کاری را انجام میدهد که بخش اول تابع ()update برای شیء ()Ball اجرا میکرد، یعنی بررسی میکند که آیا دایره شیطانی با لبه صفحه برخورد میکند یا نه و از این کار ممانعت میکند. در این مورد نیز میتوانید بخش زیادی از تعریف Ball.prototype.update را کپی کنید، اما چند تغییر را به صورت زیر باید در آن ایجاد نمایید:
شما باید دو خط آخر را پاک کنید، چون ما نمیخواهیم موقعیت دایره شیطانی را در هر فریم به صورت خودکار بهروزرسانی کنیم، بلکه آن را به طرز دیگری که در ادامه مشاهده میکنید، جابجا خواهیم کرد.
درون گزارههای ()if، اگر تستها مقدار true بازگشت دهند، لازم نیست velX/velY بهروزرسانی شوند، چون ما میخواهیم به جای آن مقدار x/y را تغییر دهیم تا دایره شیطانی با یک جهش خفیف به صفحه بازگردد. افزودن یا کسر کردن مشخصه size دایره شیطانی نیز میتواند مفید باشد.
متد ()setControls
این متد یک شنونده رویداد onkeydown به شیء window اضافه میکند به طوری که وقتی کلید خاصی روی کیبورد فشرده شود، میتوانیم دایره را به اطراف جابجا کنیم. قطعه کد زیر را میتوانید درون تعریف متد قرار دهید:
1var _this = this;
2window.onkeydown = function(e) {
3 if (e.keyCode === 65) {
4 _this.x -= _this.velX;
5 } else if (e.keyCode === 68) {
6 _this.x += _this.velX;
7 } else if (e.keyCode === 87) {
8 _this.y -= _this.velY;
9 } else if (e.keyCode === 83) {
10 _this.y += _this.velY;
11 }
12 }
بنابراین هر زمان که کلیدی فشرده میشود، مشخصه keyCode شیء رویداد مورد بررسی قرار میگیرد تا مشخص شود کدام کلید فشرده شده است. اگر این کلید یکی از کلیدهای تعریف شده باشد در این صورت دایره شیطانی در جهتهای چپ/راست/بالا/پایین حرکت میکند.
به عنوان نکته جانبی تلاش کنید بدانید که کدهای کلید تعیین شده به کدام کلیدهای کیبورد نگاشت میشوند. نکته جانبی دوم نیز این است که بررسی کنید چرا باید کدی مانند زیر داشته باشیم؟
1var _this = this;