Browse Source

Merge branch

[baozhangchao] 3 years ago
parent
commit
9d749ae8fd
4 changed files with 245 additions and 90 deletions
  1. 1
    50
      src/App.vue
  2. 58
    0
      src/Game.vue
  3. BIN
      src/assets/BackImage/framebg.jpg
  4. 186
    40
      src/game.js

+ 1
- 50
src/App.vue View File

@@ -1,56 +1,7 @@
1 1
 <template>
2
-  <div>
3
-    <router-view />
4
-  </div>
5
-  <!-- <div ref="el"></div> -->
6
-  <!-- <FireWorkListVue :center="center" :raduis="raduis" ref="firesRef" /> -->
2
+  <router-view />
7 3
 </template>
8 4
 
9
-<script setup>
10
-import { onBeforeUnmount, onMounted, ref } from 'vue'
11
-import Two from 'two.js'
12
-import FireWorkListVue from './components/FireWorkList.vue'
13
-import homePage from './homePage.vue'
14
-import game from './game'
15
-
16
-const el = ref()
17
-const destroyRef = ref()
18
-const firesRef = ref()
19
-
20
-const raduis = 150
21
-const center = {
22
-  x: document.body.offsetWidth / 2,
23
-  y: document.body.offsetHeight / 2
24
-}
25
-
26
-const gameInit = () => {
27
-  destroyRef.value = game({
28
-    el: el.value,
29
-    center,
30
-    raduis,
31
-    onError: () => {
32
-      alert('oo ~')
33
-    },
34
-    onSuccess: () => {
35
-      alert('你真牛逼')
36
-    },
37
-    onBingo: () => {
38
-      firesRef.value.toggle()
39
-    }
40
-  })
41
-}
42
-
43
-onMounted(() => {
44
-  // gameInit()
45
-})
46
-
47
-onBeforeUnmount(() => {
48
-  if (destroyRef.value) {
49
-    destroyRef.value()
50
-  }
51
-})
52
-</script>
53
-
54 5
 <style>
55 6
 html,
56 7
 body,

+ 58
- 0
src/Game.vue View File

@@ -0,0 +1,58 @@
1
+<template>
2
+  <div ref="el"></div>
3
+  <FireWorkListVue :center="center" :raduis="raduis" ref="firesRef" />
4
+</template>
5
+
6
+<script setup>
7
+  import { onBeforeUnmount, onMounted, ref } from 'vue';
8
+  import Two from "two.js";
9
+  import FireWorkListVue from './components/FireWorkList.vue';
10
+  import game from "./game";
11
+
12
+  const el = ref()
13
+  const destroyRef = ref()
14
+  const firesRef = ref()
15
+
16
+  const raduis = 145
17
+  const center = {
18
+        x: document.body.offsetWidth / 2,
19
+        y: document.body.offsetHeight / 2,
20
+      }
21
+
22
+
23
+  const gameInit = () => {
24
+    destroyRef.value = game({
25
+      el: el.value,
26
+      center,
27
+      raduis,
28
+      onError: () => {
29
+        alert('oo ~')
30
+      },
31
+      onSuccess: () => {
32
+        alert('你真牛逼')
33
+      },
34
+      onBingo: () => {
35
+        firesRef.value.toggle()
36
+      }
37
+    })
38
+  }
39
+
40
+  onMounted(() => {
41
+    gameInit()
42
+  })
43
+
44
+  onBeforeUnmount(() => {
45
+    if (destroyRef.value) {
46
+      destroyRef.value();
47
+    }
48
+  })
49
+
50
+</script>
51
+
52
+<style>
53
+html, body, #app {
54
+  height: 100%;
55
+  margin: 0;
56
+  padding: 0;
57
+}
58
+</style>

BIN
src/assets/BackImage/framebg.jpg View File


+ 186
- 40
src/game.js View File

@@ -2,7 +2,40 @@ import Two from "two.js";
2 2
 
3 3
 const colorList = ['#f5222d', '#d4380d', '#d46b08', '#d48806', '#d4b106', '#7cb305', '#389e0d', '#08979c', '#096dd9', '#531dab']
4 4
 
5
-export default function game({ el, center, raduis, onError, onSuccess, onBingo }) {
5
+import backImage from './assets/BackImage/framebg.jpg'
6
+import wheelImage from './assets/RoundaboutImage/2-09.png';
7
+import cageImage from './assets/RoundaboutImage/2-13.png';
8
+import pedestalImage from './assets/RoundaboutImage/2-14.png'
9
+import countImage from './assets/RoundaboutImage/计数.png'
10
+import ropeImage1 from './assets/RoundaboutImage/2-15.png'
11
+import ropeImage2 from './assets/RoundaboutImage/2-16.png'
12
+import ropeImage3 from './assets/RoundaboutImage/2-17.png'
13
+import ropeImage4 from './assets/RoundaboutImage/2-18.png'
14
+import ropeImage5 from './assets/RoundaboutImage/2-19.png'
15
+import ropeImage6 from './assets/RoundaboutImage/2-20.png'
16
+import ropeImage7 from './assets/RoundaboutImage/2-21.png'
17
+import ropeImage8 from './assets/RoundaboutImage/2-22.png'
18
+import ropeImage9 from './assets/RoundaboutImage/2-23.png'
19
+
20
+const designWidth = 640; // 设计稿宽度
21
+const desginRadius = 246; // 设计稿轮盘半径
22
+const pedestalDistance = 424; // 底座与轮盘圆心距离
23
+const primaryColor = '#F19805';  // 设计稿主题色
24
+
25
+const scale = window.screen.width / designWidth;
26
+const ropeImageList = [
27
+  ropeImage1,
28
+  ropeImage2,
29
+  ropeImage3,
30
+  ropeImage4,
31
+  ropeImage5,
32
+  ropeImage6,
33
+  ropeImage7,
34
+  ropeImage8,
35
+  ropeImage9,
36
+];
37
+
38
+export default function game({ el, center, onError, onSuccess, onBingo }) {
6 39
 
7 40
   // 是否游戏结束
8 41
   let isFinished = false;
@@ -11,11 +44,16 @@ export default function game({ el, center, raduis, onError, onSuccess, onBingo }
11 44
   // eslint-disable-next-line no-unused-vars
12 45
   let isError = false;
13 46
 
14
-  // 每个区域的旋转角度, 10 个轿厢, 分 20 等份
15
-  const perAngle = 2 * Math.PI / 20 // 分20等份
47
+  // 每个区域的旋转角度, 8 个轿厢, 分 16 等份
48
+  const cageNum = 8;
49
+  const perAngle = Math.PI / cageNum // 2 * Math.PI / ( cageNum * 2 ) 分成 轿厢 * 2 的分数, 最终效果是每隔一个,显示一个轿厢
50
+  const offsetAngle = perAngle / 2
16 51
 
17 52
   // 大转盘半径
18
-  // const raduis = 150;
53
+  const raduis = desginRadius * scale;
54
+
55
+  // 轿厢宽度 = 大轮盘的周长 / 间隔份数
56
+  const cageRadius = Math.PI * 2 * raduis / (cageNum * 2)
19 57
 
20 58
   // 转盘中心坐标
21 59
   // const center;
@@ -27,7 +65,7 @@ export default function game({ el, center, raduis, onError, onSuccess, onBingo }
27 65
   let isShooting = false
28 66
   
29 67
   // 子弹飞行速度, 因为是向上飞, y 值是不端减小的过程
30
-  const speed = -0.01;
68
+  const speed = -5;
31 69
 
32 70
   // 目标轿厢列表, 该列表主要用来判断子弹是否击中目标
33 71
   const cageList = [];
@@ -47,17 +85,30 @@ export default function game({ el, center, raduis, onError, onSuccess, onBingo }
47 85
     autostart: true
48 86
   }).appendTo(el);
49 87
 
88
+  // 绘制背景
89
+  makeBg({two, center});
90
+  
50 91
   // 绘制转盘
92
+  const wheelArc = two.makeCircle(center.x, center.y, 0);
51 93
   const drawRoundAbout = () => {
52 94
     const { x, y } = center
53 95
 
54
-    // 绘制扇形区域
55
-    // 该区域对游戏实际上没有用, 主要是梳理逻辑
56
-    const arcList = Array(10).fill().map((_, inx) => {
57
-      const startAngle = 2 * inx * perAngle
96
+    // 绘制图片转盘
97
+    const wheelTexture = two.makeTexture(wheelImage);
98
+    wheelArc.noStroke();
99
+    wheelArc.fill = wheelTexture;
100
+    wheelTexture.bind('load', () => {
101
+      wheelArc.radius = wheelTexture.image.naturalWidth / 2;
102
+      wheelArc.scale = scale;
103
+    })
104
+
105
+    // 绘制辅助扇形区域
106
+    const arcList = Array(cageNum).fill().map((_, inx) => {
107
+      const startAngle = 2 * inx * perAngle - offsetAngle; // 实际效果图是圆形有固定大小的, 不能直接从 0° 开始, 需要从 0 - offset 开始
58 108
       const endAngle = startAngle + perAngle
59 109
       const arc = two.makeArcSegment(x, y, 0, raduis, startAngle, endAngle)
60
-      arc.fill = colorList[inx]
110
+      // arc.fill = colorList[inx] // 取消这个注释, 有助于梳理逻辑
111
+      arc.noFill()
61 112
       arc.noStroke()
62 113
 
63 114
       // 绘制目标轿厢
@@ -65,12 +116,14 @@ export default function game({ el, center, raduis, onError, onSuccess, onBingo }
65 116
       const cx = x + Math.cos(startAngle + perAngle / 2) * raduis
66 117
       const cy = y + Math.sin(startAngle + perAngle / 2) * raduis
67 118
 
68
-      // 2、轿厢的宽度, 等于圆周长的 1/20
69
-      const w = Math.PI * 2 * raduis / 20 // 周长 / 20
70
-      const rect = two.makeRectangle(cx, cy, w, w);
119
+      // 2、轿厢
120
+      const rect = two.makeCircle(cx, cy, cageRadius / 2);
71 121
       rect.id = `mnt-${inx}`
72
-      rect.fill = colorList[inx]
73
-      rect.noStroke()
122
+      // rect.fill = colorList[inx] // 取消这个注释, 有助于梳理逻辑
123
+      rect.noFill()
124
+      rect.stroke = primaryColor
125
+      rect.linewidth = 2
126
+      rect.dashes = [4]
74 127
       rect.__$angle = startAngle  // 轿厢的初始弧度
75 128
 
76 129
       cageList.push(rect)
@@ -85,6 +138,7 @@ export default function game({ el, center, raduis, onError, onSuccess, onBingo }
85 138
   // 旋转
86 139
   const rotate = () => {
87 140
     rotateAngle += rotateSpeed
141
+    wheelArc.rotation = rotateAngle
88 142
     roundAbout.forEach((x, inx) => {
89 143
       x.rotation = rotateAngle
90 144
             
@@ -102,25 +156,81 @@ export default function game({ el, center, raduis, onError, onSuccess, onBingo }
102 156
     })
103 157
   }
104 158
 
159
+  let ropeStart = null;
160
+  let ropeStop = null;
161
+
162
+  // 绘制底座
163
+  const drawPedestal = (x, y) => {
164
+    //
165
+    const pedestalTexture = two.makeTexture(pedestalImage);
166
+    const pedestalBox = two.makeRectangle(x, y, 0, 0);
167
+    pedestalBox.noStroke();
168
+    pedestalBox.fill = pedestalTexture;
169
+    pedestalTexture.bind('load', () => {
170
+      const { naturalWidth, naturalHeight } = pedestalTexture.image
171
+      pedestalBox.width = naturalWidth
172
+      pedestalBox.height = naturalHeight
173
+      pedestalBox.scale = scale
174
+    })
175
+
176
+    //
177
+    const ropeSequence = two.makeImageSequence(ropeImageList, x, y, 40); // 40 是不断调式出来的频率, 需要跟 speed 匹配
178
+    ropeSequence.scale = new Two.Vector(scale, scale);
179
+
180
+    ropeStart = () => ropeSequence.play();
181
+    ropeStop = () => ropeSequence.stop();
182
+
183
+    return pedestalBox;
184
+  }
185
+  const pedestalBox = drawPedestal(center.x, center.y + pedestalDistance * scale)
186
+
105 187
   // 绘制子弹
106 188
   const drawBullets = (x, y) => {
107
-    const w = 20, h = 20;
189
+    const cageTexture = two.makeTexture(cageImage);
108 190
 
109
-    const list = Array(10).fill().map((_, inx) => {
110
-      const rect = two.makeRectangle(x, y, w, h)
111
-      rect.id = `bullet-${inx}`
112
-      rect.fill = 'black'
113
-      rect.noStroke()
191
+    const list = Array(cageNum).fill().map((_, inx) => {
192
+      const arc = two.makeCircle(x, y, cageRadius / 2)
193
+      arc.id = `bullet-${inx}`
194
+      // arc.fill = 'black'
195
+      arc.fill = cageTexture
196
+      arc.noStroke()
197
+      arc.visible = inx === 0; // 第一个显示, 其余默认不显示
114 198
 
115
-      return rect
199
+      return arc
200
+    })
201
+    
202
+    cageTexture.bind('load', () => {
203
+      const r = cageTexture.image.naturalWidth / 2;
204
+      list.forEach(it => {
205
+        it.radius = r;
206
+        it.scale = scale;
207
+      });
116 208
     })
117 209
 
118 210
     bullets.push(...list)
119 211
   }
120
-  drawBullets(center.x, center.y + 400) // 400 是距离转盘中心的长度
121
-  
212
+  drawBullets(center.x, center.y + pedestalDistance * scale)  
122 213
   // 待发射子弹
123 214
   const clip = bullets.slice()
215
+  
216
+  // 绘制计数器
217
+  const drawCounter = (x, y) => {
218
+    const counterTexture = two.makeTexture(countImage);
219
+    const counterBox = two.makeRectangle(x, y, 0, 0);
220
+    counterBox.noStroke();
221
+    counterBox.fill = counterTexture;
222
+    counterTexture.bind('load', () => {
223
+      const { naturalWidth, naturalHeight } = counterTexture.image
224
+      counterBox.width = naturalWidth
225
+      counterBox.height = naturalHeight
226
+      counterBox.scale = scale
227
+    })
228
+    // 初始值是弹夹中子弹数量
229
+    const counter = two.makeText(clip.length, x, y, { fill: primaryColor, weight: 700 })
230
+
231
+    return counter
232
+  }
233
+  const counter = drawCounter(pedestalBox.position.x + (224 * scale), pedestalBox.position.y); // 224 是设计稿上底座与计数器之间距离
124 234
 
125 235
   // 挂载到轮盘
126 236
   const mountToCage = (targ, cage) => {
@@ -129,17 +239,17 @@ export default function game({ el, center, raduis, onError, onSuccess, onBingo }
129 239
     mntMap[cage.id] = targ
130 240
   }
131 241
   
132
-  // 当前第几个子弹
242
+  // 当前子弹
133 243
   let currentBullet = null;
134 244
   // 射击
135 245
   const shooting = () => {
136
-    isShooting = true
137
-    const { top } = currentBullet.getBoundingClientRect()
138
-    currentBullet.position = new Two.Vector(currentBullet.position.x, top + speed)
246
+    const { top, height } = currentBullet.getBoundingClientRect()
247
+    currentBullet.position = new Two.Vector(currentBullet.position.x, top + height / 2 + speed)
139 248
 
140 249
     // 是否击中
141 250
     const hitted = isHit(currentBullet)
142 251
     if (hitted === 'error') {
252
+      ropeStop();
143 253
       const t = setTimeout(() => {
144 254
         isError = true
145 255
         isFinished = true
@@ -152,12 +262,21 @@ export default function game({ el, center, raduis, onError, onSuccess, onBingo }
152 262
     // 击中之后, 需要挂载轿厢到对应的位置
153 263
     if (hitted) {
154 264
       mountToCage(currentBullet, hitted)
155
-      currentBullet = null;
265
+      // currentBullet = null;
266
+      currentBullet = clip.shift();
267
+      if (currentBullet) {
268
+        currentBullet.visible = true
269
+        counter.value = clip.length + 1
270
+      } else {
271
+        isFinished = true
272
+        counter.value = 0
273
+      }
274
+
156 275
       isShooting = false;
276
+      ropeStop();
157 277
 
158 278
       const t = setTimeout(() => {
159
-        if (!clip.length) {
160
-          isFinished = true
279
+        if (isFinished) {
161 280
           onSuccess()
162 281
         } else {
163 282
           onBingo()
@@ -197,8 +316,8 @@ export default function game({ el, center, raduis, onError, onSuccess, onBingo }
197 316
   two.bind('update', () => {
198 317
     if (!isFinished) {
199 318
       rotate()
200
-  
201
-      if (currentBullet) {
319
+
320
+      if (isShooting) {
202 321
         shooting()
203 322
       }
204 323
     }
@@ -206,16 +325,43 @@ export default function game({ el, center, raduis, onError, onSuccess, onBingo }
206 325
 
207 326
   // 绑定 dom click 事件 触发子弹发射
208 327
   el.addEventListener('click', () => {
209
-    if (!isFinished) {
210
-      if (clip.length && !isShooting) {
211
-        currentBullet = clip.pop()
328
+    if (!isFinished && !isShooting) {
329
+      isShooting = true
330
+      // 启动绳子效果
331
+      ropeStart()
332
+      if (!currentBullet) {
333
+        currentBullet = clip.shift()
334
+        currentBullet.visible = true
212 335
       }
213 336
     }
214 337
   })
215 338
 
216
-  return () => {
217
-    two.unbind('update');
218
-    two.pause();
219
-    el.removeChild(two.renderer.domElement);
339
+  return {
340
+    start: () => {
341
+      isFinished = false;
342
+    },
343
+    stop: () => {
344
+      isFinished = true;
345
+    },
346
+    destroy: () => {
347
+    
348
+      two.unbind('update');
349
+      two.pause();
350
+      el.removeChild(two.renderer.domElement);
351
+    }
220 352
   }
221 353
 }
354
+
355
+function makeBg({two, center}) {
356
+  const bgTexture = two.makeTexture(backImage);
357
+
358
+  const bgBox = two.makeRectangle(center.x, center.y, 0, 0);
359
+  bgBox.noStroke();
360
+  bgBox.fill = bgTexture;
361
+
362
+  bgTexture.bind('load', () => {
363
+    bgBox.width = bgTexture.image.naturalWidth;
364
+    bgBox.height = bgTexture.image.naturalHeight;
365
+    bgBox.scale = scale;
366
+  })
367
+}