Арканоид (JavaScript)
Материал из Department of Theoretical and Applied Mechanics
Авторы
Вертелецкая Анастасия
Камила Карамуллина
Описание
Арканоид - это тип классических компьютерных игр, целью в которых является уничтожение всех игровых блоков отскакивающим шариком.
Именно к этому типу видеоигр относится этот проект.
Для реализации культовой игры были задействованы языки программирования: JavaScript, HTML, CSS.
Данный проект отличается от своей классической версии - в управление были добавлены некоторые особые функции, перечисленные ниже. Также
дизайн работы был вдохновлен борьбой с пандемией COVID-19.
Особенности управления
- Подсчет очков
- Подсчет времени
- Возможность менять скорость таблетки, нажав "вверх" или "вниз".
- Запуск анимации блоков при нажатии "Ctrl"
Игровой процесс
- На игровом поле расположены блоки-вирусы, шарик-таблетка, список игровых клавиш и платформа.
- Игрок начинает игру по кнопке "Space". После нажатия шарик отрывается от платформы и начинает полет.
- При столкновении шарика с блоком, блок-вирус пропадает и игрок зарабатывает установленное количество очков.
- Игра продолжается до момента попадания шарика-таблетки на нижний край игрового поля или до полного уничтожения блоков.
Далее реализуется два сценария:
- При попадании шарика-таблетки на нижний край игрового поля выводится экран поражения. Игрок наблюдает время прохождения и количество заработанных очков.
- При полном уничтожении блоков выводится экран победы. Таймер останавливается и игрок может наблюдать свои итоговые результаты.
Визуализация
Нажмите курсором на игровой экран, а затем кнопку Space для начала игры
Код игры
Код программы на языке JavaScript:
1 const GAME_RUN= 1;
2 const GAME_LOST= 2;
3 const GAME_WIN= 3;
4
5 const KEY_START=32;
6 const KEY_LEFT=37;
7 const KEY_RIGHT=39;
8 const KEY_UP=38;
9 const KEY_DOWN=40;
10 const KEY_ANIM=17;
11
12 var game = {
13 state: GAME_RUN,
14 width: 640,
15 height: 400,
16 score: 0,
17 sprites: { platform: undefined, background: undefined, block: undefined, pill: undefined, bg_lost: undefined, bg_win:undefined},
18 pill: {},
19 platform: {},
20 blocks: [],
21 rows: 5, // 5,
22 cols: 8, // 8,
23 startTime: null,
24 blockAnimFreq: 400,
25 needAnimBlocks: false,
26 needPaddleSidesEval: false, //true= Если мячик ударился в левую половину платформы, то отразится он влево (откуда бы ни прилетел), если в правую - отразится вправо
27 init: function(){
28 this.ctx = document.getElementById("mycanvas").getContext("2d");
29 this.ctx.font = '16px Roboto';
30 this.ctx.fillStyle = '#177398';
31 window.addEventListener('keydown', function(e){
32 if ( e.keyCode == KEY_START ) {
33 if (game.state==GAME_RUN){
34 game.platform.releaseBall();
35 } else {
36 window.location.reload();
37 }
38
39 } else if ( e.keyCode == KEY_LEFT ) {
40 game.platform.dx = -game.platform.velocity;
41 } else if ( e.keyCode == KEY_RIGHT ) {
42 game.platform.dx = game.platform.velocity;
43 } else if ( e.keyCode == KEY_UP ) {
44 game.pill.incSpeed();
45 } else if ( e.keyCode == KEY_DOWN ) {
46 game.pill.decSpeed();
47 } else if ( e.keyCode == KEY_ANIM ) {
48 game.animateBlocks();
49 }
50
51 });
52 window.addEventListener('keyup', function(e){
53 game.platform.stop();
54 });
55 },
56 preload: function(){
57 for (var key in this.sprites) {
58 this.sprites[key] = new Image();
59 this.sprites[key].src = 'images/' + key + '.png';
60 }
61 },
62 create: function(){
63 for ( var row = 0; row < this.rows; row++ ) {
64 for ( var col = 0; col < this.cols; col++ ) {
65 this.blocks.push({
66 x: 68 * col + 50,
67 y: 36 * row + 35,
68 width: 64,
69 height: 32,
70 frame: getRandomInt(0, 3),
71 isAlive: true
72 });
73 }
74 }
75 },
76 update: function() {
77 if ( this.pill.collide(this.platform) ) {
78 this.pill.bumpPuddle(this.platform);
79 }
80
81 if( this.pill.dx || this.pill.dy ) {
82 this.pill.move();
83 }
84
85 if( this.platform.dx ) {
86 this.platform.move();
87 }
88
89 this.blocks.forEach(function(element, index){
90 if ( element.isAlive ) {
91 if( this.pill.collide(element) ) {
92 this.pill.bumpBlock(element);
93 }
94 }
95 }, this);
96
97 this.pill.checkBounds();
98 },
99 render: function() {
100 this.ctx.clearRect(0, 0, this.width, this.height);
101 this.ctx.drawImage(this.sprites.background, 0, 0);
102 this.ctx.drawImage(this.sprites.pill, this.pill.width * this.pill.frame, 0, this.pill.width, this.pill.height, this.pill.x, this.pill.y, this.pill.width, this.pill.height);
103 this.ctx.drawImage(this.sprites.platform, this.platform.x, this.platform.y);
104
105 this.blocks.forEach(function(element){
106 if ( element.isAlive ) {
107 this.ctx.drawImage(this.sprites.block, element.width * element.frame, 0, element.width, element.height, element.x, element.y, element.width, element.height);
108
109 }
110 }, this);
111
112 this.drawCounters();
113
114 },
115
116
117 run: function() {
118 switch (this.state){
119 case GAME_RUN:
120 this.update();
121 this.render();
122 window.requestAnimationFrame(function(){
123 game.run();
124 });
125 break;
126 case GAME_LOST:
127 this.showLostScreen();
128 break;
129 case GAME_WIN:
130 this.showWinScreen();
131 break;
132 }
133 },
134
135 start: function(){
136 this.init();
137 this.preload();
138 this.create();
139 this.run();
140 },
141
142 startTimer: function(){
143 this.startTime = new Date();
144 },
145
146 animateBlocks: function (){
147 if (!this.needAnimBlocks){
148 this.needAnimBlocks=true;
149 this.blocks.forEach(function(element, index){
150 if ( element.isAlive ) {
151 element.animation = setInterval(function(){
152 ++element.frame;
153 if ( element.frame > 2 ) {
154 element.frame = 0;
155 }
156 }, this.blockAnimFreq);
157 }
158 }, this);
159 } else {
160 this.needAnimBlocks=false;
161 this.blocks.forEach(function(element, index){
162 if ( element.isAlive ) {
163 clearInterval(element.animation);
164 }
165 }, this);
166 }
167 },
168
169 drawCounters(){
170 this.ctx.fillText('Score: ' + this.score, 15, this.height - 20);
171 this.ctx.fillText('Speed: ' + this.pill.velocity, 15, this.height - 5);
172 this.ctx.fillText('Time: ' + getTimeStr(game.startTime), 530, this.height - 5);
173 },
174
175 showWinScreen(){
176 this.ctx.clearRect(0, 0, this.width, this.height);
177 this.ctx.drawImage(this.sprites.bg_win, 0, 0);
178
179
180 this.drawCounters();
181
182 },
183 showLostScreen(){
184 this.ctx.clearRect(0, 0, this.width, this.height);
185 this.ctx.drawImage(this.sprites.bg_lost, 0, 0);
186
187
188 this.drawCounters();
189 }
190
191 };
192
193
194 game.pill = {
195 frame: 0,
196 x: 310,
197 y: game.height-72, //288,
198 velocity: 3,
199 vel_step: 1,
200 dx: 0,
201 dy: 0,
202 width: 22,
203 height: 22,
204 move: function(){
205 this.x += this.dx;
206 this.y += this.dy;
207 },
208 animate: function(){
209 var self = this;
210 this.animation = setInterval(function(){
211 ++self.frame;
212 if ( self.frame > 3 ) {
213 self.frame = 0;
214 }
215 }, 100);
216 },
217 jump: function(){
218 this.dy = this.dx = -this.velocity;
219 this.animate();
220 },
221 collide: function(element) {
222 var x = this.x + this.dx;
223 var y = this.y + this.dy;
224
225 if ( x + this.width > element.x &&
226 x < element.x + element.width &&
227 y + this.height > element.y &&
228 y < element.y + element.height) {
229 return true;
230 }
231 return false;
232 },
233 onTheLeftSide: function(platform) {
234 return (this.x + this.width / 2) < (platform.x + platform.width / 2);
235 },
236 checkBounds: function(){
237 var x = this.x + this.dx;
238 var y = this.y + this.dy;
239
240 if ( x < 0 ) {
241 this.x = 0;
242 this.dx = this.velocity;
243 } else if ( x + this.width > game.width ) {
244 this.x = game.width - this.width;
245 this.dx = -this.velocity;
246 } else if ( y < 0 ) {
247 this.y = 0;
248 this.dy = this.velocity;
249 } else if( this.y + this.height >= game.height ) {
250 this.over(GAME_LOST);
251 }
252 },
253 bumpPuddle: function(platform){
254 this.dy = -this.velocity;
255 if (game.needPaddleSidesEval){
256 this.dx = this.onTheLeftSide(platform) ? -this.velocity : this.velocity;
257 }
258 },
259 bumpBlock: function(block){
260 block.isAlive = false;
261 this.dy *= -1;
262 ++game.score;
263 if ( game.score == game.blocks.length ) {
264 this.over(GAME_WIN);
265 }
266 },
267 over: function(state) {
268 game.state = state;
269 },
270 incSpeed: function() {
271 this.velocity=this.velocity + this.vel_step;
272 if (this.dx!=0) {this.dx=this.dx > 0 ? this.dx+this.vel_step : this.dx-this.vel_step;}
273 if (this.dy!=0) {this.dy=this.dy > 0 ? this.dy+this.vel_step : this.dy-this.vel_step;}
274 },
275 decSpeed: function() {
276 if (this.velocity>this.vel_step){
277 this.velocity=this.velocity- this.vel_step;
278 if (this.dx!=0) {this.dx=this.dx > 0 ? this.dx-this.vel_step : this.dx+this.vel_step;}
279 if (this.dy!=0) {this.dy=this.dy > 0 ? this.dy-this.vel_step : this.dy+this.vel_step;}
280 }
281 }
282 };
283
284
285 game.platform = {
286 x: 270,
287 y: game.height-50, //310,
288 dx: 0,
289 velocity: 6,
290 width: 104,
291 height: 12,
292 pill: game.pill,
293 stop: function(){
294 this.dx = 0;
295
296 if ( this.pill ) {
297 this.pill.dx = 0;
298 }
299 },
300 move: function(){
301 var stop_pill=false;
302 this.x += this.dx;
303 if ( this.x < 0 ) {
304 this.x = 0; stop_pill=true;
305 } else if ( this.x + this.width > game.width ) {
306 this.x = game.width - this.width;
307 stop_pill=true;
308 }
309
310 if ( this.pill && !stop_pill ) {
311 this.pill.x += this.dx;
312 }
313 },
314 releaseBall: function(){
315 if ( this.pill ) {
316 game.startTimer();
317 this.pill.jump();
318 this.pill = false;
319 }
320 }
321 };
322
323
324 function getRandomInt(min, max) {
325 return Math.floor(Math.random() * (max - min)) + min;
326 }
327
328 function getTimeStr(startDate) {
329 var out="0:00:00";
330 if (startDate) {
331 var dateObj = new Date();
332 var time=dateObj.getTime()-startDate.getTime();
333
334 var seconds = Math.floor(time / 1000);
335 var minutes = Math.floor(time / (1000 * 60));
336 var hours = Math.floor(time / (1000 * 60 * 60));
337
338 if (minutes < 10) minutes = "0" + minutes;
339 if (seconds < 10) seconds = "0" + seconds;
340 out = hours + ":" + minutes + ":" + seconds;
341 }
342 return out;
343 }
344
345
346 window.addEventListener("load",function() {
347 game.start();
348 });
Код программы на языке HTML:
1 <html>
2 <head>
3 <title>Kill the virus</title>
4 <link rel="icon" href="images/ico.png">
5 <link rel="stylesheet" type="text/css" href="style.css" >
6 </head>
7 <body>
8 <center>
9 <br/>
10 <img src="images/game.png">
11 <table width=500>
12 <tr><td><font color=red>Platform:</font></td><td><font color=red>Pill Speed:</font></td><td><font color=red>Animation:</font></td></tr>
13 <tr><td>Arrow LEFT, Arrow RIGHT</td><td>Arrow UP, Arrow DOWN</td><td>CTRL</td></tr>
14 </table>
15
16 <h3>Press SPACE to START</h3>
17 <canvas id="mycanvas" width="640" height="400" ></canvas>
18 </center>
19 <script src="game.js" type="text/javascript"></script>
20 </body>
21 </html>
Код программы на языке CSS:
1 body {
2 background:white;
3 padding: 0;
4 margin: 0;
5 }
6 canvas {
7 //width: 100%;
8 }
9 h3 {
10 color: #9d0c15; // white;
11 font-family: "Times New Roman", Georgia, Serif";
12 line-height: 0.5em;
13 font-size: 1em;
14 }
15 table {
16 color: #9d0c15;
17 font-family: "Times New Roman", Georgia, Serif";
18 line-height: 0.5em;
19 font-size: 0.9em;
20 }