今天就來實現一個雷霆戰機打字游戲,玩法很簡單,每一個“敵人”都是一些英文單詞,鍵盤正確打出單詞的字母,飛機就會發射一個個子彈消滅“敵人”,每次需要擊斃當前“敵人”后才能擊斃下一個,一個比手速和單詞熟練度的游戲。
首先來看看最終效果圖:
emmmmmmmmmmmmm,界面UI做的很簡單,先實現基本功能,再考慮高大上的UI吧。
首先依舊是來分析界面組成:
(1)固定在畫面底部中間的飛機;
(2)從畫面上方隨機產生的敵人(單詞);
(3)從飛機頭部發射出去,直奔敵人而去的子彈;
(4)游戲結束后的分數顯示。
這次的游戲和之前的比,運動的部分貌似更多且更復雜了。在flappy bird中,雖然管道是運動的,但是小鳥的x坐標和管道的間隔、寬度始終不變,比較容易計算邊界;在彈球消磚塊游戲中,木板和磚塊都是相對簡單或者固定的坐標,只用判定彈球的邊界和磚塊的觸碰面積就行。在雷霆戰機消單詞游戲中,無論是降落的目標單詞,還是飛出去的子彈,都有著各自的運動軌跡,但是子彈又要追尋著目標而去,所以存在著一個實時計算軌道的操作。
萬丈高樓平地起,說了那么多,那就從最簡單的開始著手吧!
1、固定在畫面底部中間的飛機
這個很簡單沒啥好說的,這里默認飛機寬度高度為40像素單位,然后將飛機畫在畫面的底部中間:
1
2
3
4
5
6
7
8
9
10
11
12
|
drawPlane() { let _this = this ; _this.ctx.save(); _this.ctx.drawImage( _this.planeImg, _this.clientWidth / 2 - 20, _this.clientHeight - 20 - 40, 40, 40 ); _this.ctx.restore(); }, |
2、從畫面上方隨機產生的敵人
這里默認設置每次在畫面中最多只出現3個單詞靶子,靶子的y軸移動速度為1.3,靶子的半徑大小為10:
1
2
3
4
5
6
7
8
9
10
|
const _MAX_TARGET = 3; // 畫面中一次最多出現的目標 const _TARGET_CONFIG = { // 靶子的固定參數 speed: 1.3, radius: 10 }; |
然后我們一開始要隨機在詞庫數組里取出_MAX_TARGET個不重復的單詞,并把剩下的詞放進循環詞庫this.wordsPool中去:
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
|
generateWord(number) { // 從池子里隨機挑選一個詞,不與已顯示的詞重復 let arr = []; for (let i = 0; i < number; i++) { let random = Math.floor(Math.random() * this .wordsPool.length); arr.push( this .wordsPool[random]); this .wordsPool.splice(random, 1); } return arr; }, generateTarget() { // 隨機生成目標 let _this = this ; let length = _this.targetArr.length; if (length < _MAX_TARGET) { let txtArr = _this.generateWord(_MAX_TARGET - length); for (let i = 0; i < _MAX_TARGET - length; i++) { _this.targetArr.push({ x: _this.getRandomInt( _TARGET_CONFIG.radius, _this.clientWidth - _TARGET_CONFIG.radius ), y: _TARGET_CONFIG.radius * 2, txt: txtArr[i], typeIndex: -1, hitIndex: -1, dx: (_TARGET_CONFIG.speed * Math.random().toFixed(1)) / 2, dy: _TARGET_CONFIG.speed * Math.random().toFixed(1), rotate: 0 }); } } } |
可以看出,this.targetArr是存放目標對象的數組:
一個初始的目標有隨機分布在畫面寬度的x;
y軸值為直徑;
txt記錄靶子代表的單詞;
typeIndex記錄“轟炸”這個單詞時正在敲擊的字符索引下標(用來分離已敲擊字符和未敲擊字符);
hitIndex記錄“轟炸”這個單詞時子彈轟炸的索引下標(因為子彈真正轟炸掉目標是在打完單詞之后,畢竟子彈有飛行時間,所以通過hitIndex來判斷子彈什么時候被擊碎消失);
dx是靶子每幀在x軸偏移距離;
dy是靶子每幀在y軸偏移距離;
rotate設置靶子自轉角度。
好了,生成了3個靶子,我們就讓靶子先從上往下動起來吧:
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
|
drawTarget() { // 逐幀畫目標 let _this = this ; _this.targetArr.forEach((item, index) => { _this.ctx.save(); _this.ctx.translate(item.x, item.y); //設置旋轉的中心點 _this.ctx.beginPath(); _this.ctx.font = "14px Arial" ; if ( index === _this.currentIndex || item.typeIndex === item.txt.length - 1 ) { _this.drawText( item.txt.substring(0, item.typeIndex + 1), -item.txt.length * 3, _TARGET_CONFIG.radius * 2, "gray" ); let width = _this.ctx.measureText( item.txt.substring(0, item.typeIndex + 1) ).width; // 獲取已敲擊文字寬度 _this.drawText( item.txt.substring(item.typeIndex + 1, item.txt.length), -item.txt.length * 3 + width, _TARGET_CONFIG.radius * 2, "red" ); } else { _this.drawText( item.txt, -item.txt.length * 3, _TARGET_CONFIG.radius * 2, "yellow" ); } _this.ctx.closePath(); _this.ctx.rotate((item.rotate * Math.PI) / 180); _this.ctx.drawImage( _this.targetImg, -1 * _TARGET_CONFIG.radius, -1 * _TARGET_CONFIG.radius, _TARGET_CONFIG.radius * 2, _TARGET_CONFIG.radius * 2 ); _this.ctx.restore(); item.y += item.dy; item.x += item.dx; if (item.x < 0 || item.x > _this.clientWidth) { item.dx *= -1; } if (item.y > _this.clientHeight - _TARGET_CONFIG.radius * 2) { // 碰到底部了 _this.gameOver = true ; } // 旋轉 item.rotate++; }); } |
這一步畫靶子沒有什么特別的,隨機得增加dx和dy,在碰到左右邊緣時反彈。主要一點就是單詞的繪制,通過typeIndex將單詞一分為二,敲擊過的字符置為灰色,然后通過measureText獲取到敲擊字符的寬度從而設置未敲擊字符的x軸偏移量,將未敲擊的字符設置為紅色,提示玩家這個單詞是正在攻擊中的目標。
3、從飛機頭部發射出去,直奔敵人而去的子彈
子彈是這個游戲的關鍵部分,在繪制子彈的時候要考慮哪些方面呢?
(1)目標一直是運動的,發射出去的子彈要一直“追蹤”目標,所以路徑是動態變化的;
(2)一個目標需要被若干個子彈消滅掉,所以什么時候子彈才從畫面中抹去;
(3)當一個目標單詞被敲擊完了后,下一批子彈就要朝向下一個目標射擊,所以子彈的路徑是單獨的;
(4)如何繪制出子彈的拖尾效果;
(5)如果鎖定正在敲擊的目標單詞,使玩家敲完當前單詞才能去擊破下一個單詞
這里先設置幾個變量:
bulletArr: [], // 存放子彈對象
currentIndex: -1 //當前鎖定的目標在targetArr中的索引
首先我們先來寫一下鍵盤按下時要觸發的函數:
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
|
handleKeyPress(key) { // 鍵盤按下,判斷當前目標 let _this = this ; if (_this.currentIndex === -1) { // 當前沒有在射擊的目標 let index = _this.targetArr.findIndex(item => { return item.txt.indexOf(key) === 0; }); if (index !== -1) { _this.currentIndex = index; _this.targetArr[index].typeIndex = 0; _this.createBullet(index); } } else { // 已有目標正在被射擊 if ( key === _this.targetArr[_this.currentIndex].txt.split( "" )[ _this.targetArr[_this.currentIndex].typeIndex + 1 ] ) { // 獲取到目標對象 _this.targetArr[_this.currentIndex].typeIndex++; _this.createBullet(_this.currentIndex); if ( _this.targetArr[_this.currentIndex].typeIndex === _this.targetArr[_this.currentIndex].txt.length - 1 ) { // 這個目標已經別射擊完畢 _this.currentIndex = -1; } } } }, // 發射一個子彈 createBullet(index) { let _this = this ; this .bulletArr.push({ dx: 1, dy: 4, x: _this.clientWidth / 2, y: _this.clientHeight - 60, targetIndex: index }); } |
這個函數做的事情很明確,拿到當前鍵盤按下的字符,如果currentIndex ===-1,證明沒有正在被攻擊的靶子,所以就去靶子數組里看,哪個單詞的首字母等于該字符,則設置currentIndex為該單詞的索引,并發射一個子彈;如果已經有正在被攻擊的靶子,則看還未敲擊的單詞的第一個字符是否符合,若符合,則增加該靶子對象的typeIndex,并發射一個子彈,若當前靶子已敲擊完畢,則重置currentIndex為-1。
接下來就是畫子彈咯:
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
|
drawBullet() { // 逐幀畫子彈 let _this = this ; // 判斷子彈是否已經擊中目標 if (_this.bulletArr.length === 0) { return ; } _this.bulletArr = _this.bulletArr.filter(_this.firedTarget); _this.bulletArr.forEach(item => { let targetX = _this.targetArr[item.targetIndex].x; let targetY = _this.targetArr[item.targetIndex].y; let k = (_this.clientHeight - 60 - targetY) / (_this.clientWidth / 2 - targetX); // 飛機頭和目標的斜率 let b = targetY - k * targetX; // 常量b item.y = item.y - bullet.dy; // y軸偏移一個單位 item.x = (item.y - b) / k; for (let i = 0; i < 15; i++) { // 畫出拖尾效果 _this.ctx.beginPath(); _this.ctx.arc( (item.y + i * 1.8 - b) / k, item.y + i * 1.8, 4 - 0.2 * i, 0, 2 * Math.PI ); _this.ctx.fillStyle = `rgba(193,255,255,${1 - 0.08 * i})`; _this.ctx.fill(); _this.ctx.closePath(); } }); }, firedTarget(item) { // 判斷是否擊中目標 let _this = this ; if ( item.x > _this.targetArr[item.targetIndex].x - _TARGET_CONFIG.radius && item.x < _this.targetArr[item.targetIndex].x + _TARGET_CONFIG.radius && item.y > _this.targetArr[item.targetIndex].y - _TARGET_CONFIG.radius && item.y < _this.targetArr[item.targetIndex].y + _TARGET_CONFIG.radius ) { // 子彈擊中了目標 let arrIndex = item.targetIndex; _this.targetArr[arrIndex].hitIndex++; if ( _this.targetArr[arrIndex].txt.length - 1 === _this.targetArr[arrIndex].hitIndex ) { // 所有子彈全部擊中了目標 let word = _this.targetArr[arrIndex].txt; _this.targetArr[arrIndex] = { // 生成新的目標 x: _this.getRandomInt( _TARGET_CONFIG.radius, _this.clientWidth - _TARGET_CONFIG.radius ), y: _TARGET_CONFIG.radius * 2, txt: _this.generateWord(1)[0], typeIndex: -1, hitIndex: -1, dx: (_TARGET_CONFIG.speed * Math.random().toFixed(1)) / 2, dy: _TARGET_CONFIG.speed * Math.random().toFixed(1), rotate: 0 }; _this.wordsPool.push(word); // 被擊中的目標詞重回池子里 _this.score++; } return false ; } else { return true ; } } |
其實也很簡單,我們在子彈對象中用targetIndex來記錄該子彈所攻擊的目標索引,然后就是一個y = kx+b的解方程得到飛機頭部(子彈出發點)和靶子的軌道函數,計算出每一幀下每個子彈的移動坐標,就可以畫出子彈了;
拖尾效果就是沿軌道y軸增長方向畫出若干個透明度和半徑逐漸變小的圓,就能實現拖尾效果了;
在firedTarget()函數中,用來過濾出已擊中靶子的子彈,為了不影響還在被攻擊的其他靶子在targetArr中的索引,不用splice刪除,而是直接重置被消滅靶子的值,從wordPool中選出新的詞,并把當前擊碎的詞重新丟回池子里,從而保證畫面中不會出現重復的靶子。
4、游戲結束后的得分文字效果
游戲結束就是有目標靶子觸碰到了底部就結束了。
這里其實是個彩蛋了,怎么樣可以用canvas畫出這種閃爍且有光暈的文字呢?切換顏色并且疊buff就行了,不是,疊輪廓stroke就行了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
drawGameOver() { let _this = this ; //保存上下文對象的狀態 _this.ctx.save(); _this.ctx.font = "34px Arial" ; _this.ctx.strokeStyle = _this.colors[0]; _this.ctx.lineWidth = 2; //光暈 _this.ctx.shadowColor = "#FFFFE0" ; let txt = "游戲結束,得分:" + _this.score; let width = _this.ctx.measureText(txt).width; for (let i = 60; i > 3; i -= 2) { _this.ctx.shadowBlur = i; _this.ctx.strokeText(txt, _this.clientWidth / 2 - width / 2, 300); } _this.ctx.restore(); _this.colors.reverse(); } |
好了好了,做到這里就是個還算完整的小游戲了,只是UI略顯粗糙,如果想做到真正雷霆戰機那么酷炫帶爆炸的效果那還需要很多素材和canvas的繪制。
canvas很強大很好玩,只要腦洞夠大,這張畫布上你想畫啥就可以畫啥~
老規矩,po上vue的完整代碼供大家參考學習:
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
|
<template> <div class= "type-game" > <canvas id= "type" width= "400" height= "600" ></canvas> </div> </template> <script> const _MAX_TARGET = 3; // 畫面中一次最多出現的目標 const _TARGET_CONFIG = { // 靶子的固定參數 speed: 1.3, radius: 10 }; const _DICTIONARY = [ "apple" , "orange" , "blue" , "green" , "red" , "current" ]; export default { name: "TypeGame" , data() { return { ctx: null , clientWidth: 0, clientHeight: 0, bulletArr: [], // 屏幕中的子彈 targetArr: [], // 存放當前目標 targetImg: null , planeImg: null , currentIndex: -1, wordsPool: [], score: 0, gameOver: false , colors: [ "#FFFF00" , "#FF6666" ] }; }, mounted() { let _this = this ; _this.wordsPool = _DICTIONARY.concat([]); let container = document.getElementById( "type" ); _this.clientWidth = container.width; _this.clientHeight = container.height; _this.ctx = container.getContext( "2d" ); _this.targetImg = new Image(); _this.targetImg.src = require( "@/assets/img/target.png" ); _this.planeImg = new Image(); _this.planeImg.src = require( "@/assets/img/plane.png" ); document.onkeydown = function (e) { let key = window.event.keyCode; if (key >= 65 && key <= 90) { _this.handleKeyPress(String.fromCharCode(key).toLowerCase()); } }; _this.targetImg.onload = function () { _this.generateTarget(); ( function animloop() { if (!_this.gameOver) { _this.drawAll(); } else { _this.drawGameOver(); } window.requestAnimationFrame(animloop); })(); }; }, methods: { drawGameOver() { let _this = this ; //保存上下文對象的狀態 _this.ctx.save(); _this.ctx.font = "34px Arial" ; _this.ctx.strokeStyle = _this.colors[0]; _this.ctx.lineWidth = 2; //光暈 _this.ctx.shadowColor = "#FFFFE0" ; let txt = "游戲結束,得分:" + _this.score; let width = _this.ctx.measureText(txt).width; for (let i = 60; i > 3; i -= 2) { _this.ctx.shadowBlur = i; _this.ctx.strokeText(txt, _this.clientWidth / 2 - width / 2, 300); } _this.ctx.restore(); _this.colors.reverse(); }, drawAll() { let _this = this ; _this.ctx.clearRect(0, 0, _this.clientWidth, _this.clientHeight); _this.drawPlane(0); _this.drawBullet(); _this.drawTarget(); _this.drawScore(); }, drawPlane() { let _this = this ; _this.ctx.save(); _this.ctx.drawImage( _this.planeImg, _this.clientWidth / 2 - 20, _this.clientHeight - 20 - 40, 40, 40 ); _this.ctx.restore(); }, generateWord(number) { // 從池子里隨機挑選一個詞,不與已顯示的詞重復 let arr = []; for (let i = 0; i < number; i++) { let random = Math.floor(Math.random() * this .wordsPool.length); arr.push( this .wordsPool[random]); this .wordsPool.splice(random, 1); } return arr; }, generateTarget() { // 隨機生成目標 let _this = this ; let length = _this.targetArr.length; if (length < _MAX_TARGET) { let txtArr = _this.generateWord(_MAX_TARGET - length); for (let i = 0; i < _MAX_TARGET - length; i++) { _this.targetArr.push({ x: _this.getRandomInt( _TARGET_CONFIG.radius, _this.clientWidth - _TARGET_CONFIG.radius ), y: _TARGET_CONFIG.radius * 2, txt: txtArr[i], typeIndex: -1, hitIndex: -1, dx: (_TARGET_CONFIG.speed * Math.random().toFixed(1)) / 2, dy: _TARGET_CONFIG.speed * Math.random().toFixed(1), rotate: 0 }); } } }, getRandomInt(n, m) { return Math.floor(Math.random() * (m - n + 1)) + n; }, drawText(txt, x, y, color) { let _this = this ; _this.ctx.fillStyle = color; _this.ctx.fillText(txt, x, y); }, drawScore() { // 分數 this .drawText( "分數:" + this .score, 10, this .clientHeight - 10, "#fff" ); }, drawTarget() { // 逐幀畫目標 let _this = this ; _this.targetArr.forEach((item, index) => { _this.ctx.save(); _this.ctx.translate(item.x, item.y); //設置旋轉的中心點 _this.ctx.beginPath(); _this.ctx.font = "14px Arial" ; if ( index === _this.currentIndex || item.typeIndex === item.txt.length - 1 ) { _this.drawText( item.txt.substring(0, item.typeIndex + 1), -item.txt.length * 3, _TARGET_CONFIG.radius * 2, "gray" ); let width = _this.ctx.measureText( item.txt.substring(0, item.typeIndex + 1) ).width; // 獲取已敲擊文字寬度 _this.drawText( item.txt.substring(item.typeIndex + 1, item.txt.length), -item.txt.length * 3 + width, _TARGET_CONFIG.radius * 2, "red" ); } else { _this.drawText( item.txt, -item.txt.length * 3, _TARGET_CONFIG.radius * 2, "yellow" ); } _this.ctx.closePath(); _this.ctx.rotate((item.rotate * Math.PI) / 180); _this.ctx.drawImage( _this.targetImg, -1 * _TARGET_CONFIG.radius, -1 * _TARGET_CONFIG.radius, _TARGET_CONFIG.radius * 2, _TARGET_CONFIG.radius * 2 ); _this.ctx.restore(); item.y += item.dy; item.x += item.dx; if (item.x < 0 || item.x > _this.clientWidth) { item.dx *= -1; } if (item.y > _this.clientHeight - _TARGET_CONFIG.radius * 2) { // 碰到底部了 _this.gameOver = true ; } // 旋轉 item.rotate++; }); }, handleKeyPress(key) { // 鍵盤按下,判斷當前目標 let _this = this ; if (_this.currentIndex === -1) { // 當前沒有在射擊的目標 let index = _this.targetArr.findIndex(item => { return item.txt.indexOf(key) === 0; }); if (index !== -1) { _this.currentIndex = index; _this.targetArr[index].typeIndex = 0; _this.createBullet(index); } } else { // 已有目標正在被射擊 if ( key === _this.targetArr[_this.currentIndex].txt.split( "" )[ _this.targetArr[_this.currentIndex].typeIndex + 1 ] ) { // 獲取到目標對象 _this.targetArr[_this.currentIndex].typeIndex++; _this.createBullet(_this.currentIndex); if ( _this.targetArr[_this.currentIndex].typeIndex === _this.targetArr[_this.currentIndex].txt.length - 1 ) { // 這個目標已經別射擊完畢 _this.currentIndex = -1; } } } }, // 發射一個子彈 createBullet(index) { let _this = this ; this .bulletArr.push({ dx: 1, dy: 4, x: _this.clientWidth / 2, y: _this.clientHeight - 60, targetIndex: index }); }, firedTarget(item) { // 判斷是否擊中目標 let _this = this ; if ( item.x > _this.targetArr[item.targetIndex].x - _TARGET_CONFIG.radius && item.x < _this.targetArr[item.targetIndex].x + _TARGET_CONFIG.radius && item.y > _this.targetArr[item.targetIndex].y - _TARGET_CONFIG.radius && item.y < _this.targetArr[item.targetIndex].y + _TARGET_CONFIG.radius ) { // 子彈擊中了目標 let arrIndex = item.targetIndex; _this.targetArr[arrIndex].hitIndex++; if ( _this.targetArr[arrIndex].txt.length - 1 === _this.targetArr[arrIndex].hitIndex ) { // 所有子彈全部擊中了目標 let word = _this.targetArr[arrIndex].txt; _this.targetArr[arrIndex] = { // 生成新的目標 x: _this.getRandomInt( _TARGET_CONFIG.radius, _this.clientWidth - _TARGET_CONFIG.radius ), y: _TARGET_CONFIG.radius * 2, txt: _this.generateWord(1)[0], typeIndex: -1, hitIndex: -1, dx: (_TARGET_CONFIG.speed * Math.random().toFixed(1)) / 2, dy: _TARGET_CONFIG.speed * Math.random().toFixed(1), rotate: 0 }; _this.wordsPool.push(word); // 被擊中的目標詞重回池子里 _this.score++; } return false ; } else { return true ; } }, drawBullet() { // 逐幀畫子彈 let _this = this ; // 判斷子彈是否已經擊中目標 if (_this.bulletArr.length === 0) { return ; } _this.bulletArr = _this.bulletArr.filter(_this.firedTarget); _this.bulletArr.forEach(item => { let targetX = _this.targetArr[item.targetIndex].x; let targetY = _this.targetArr[item.targetIndex].y; let k = (_this.clientHeight - 60 - targetY) / (_this.clientWidth / 2 - targetX); // 飛機頭和目標的斜率 let b = targetY - k * targetX; // 常量b item.y = item.y - 4; // y軸偏移一個單位 item.x = (item.y - b) / k; for (let i = 0; i < 15; i++) { // 畫出拖尾效果 _this.ctx.beginPath(); _this.ctx.arc( (item.y + i * 1.8 - b) / k, item.y + i * 1.8, 4 - 0.2 * i, 0, 2 * Math.PI ); _this.ctx.fillStyle = `rgba(193,255,255,${1 - 0.08 * i})`; _this.ctx.fill(); _this.ctx.closePath(); } }); } } }; </script> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped lang= "scss" > .type-game { #type { background: #2a4546; } } </style> |
以上就是如何用VUE和Canvas實現雷霆戰機打字類小游戲的詳細內容,更多關于VUE雷霆戰機小游戲的資料請關注服務器之家其它相關文章!
原文鏈接:https://blog.csdn.net/denglouhen/article/details/115629918