123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386 |
- import Two from "two.js";
-
- const colorList = ['#f5222d', '#d4380d', '#d46b08', '#d48806', '#d4b106', '#7cb305', '#389e0d', '#08979c', '#096dd9', '#531dab']
-
- import backImage from '@/assets/BackImage/framebg.jpg'
- import wheelImage from '@/assets/RoundaboutImage/2-09.png';
- import cageImage from '@/assets/RoundaboutImage/2-13.png';
- import pedestalImage from '@/assets/RoundaboutImage/2-14.png'
- import countImage from '@/assets/RoundaboutImage/计数.png'
- import lightImage from '@/assets/RoundaboutImage/round.png'
- import ropeImage1 from '@/assets/RoundaboutImage/2-15.png'
- import ropeImage2 from '@/assets/RoundaboutImage/2-16.png'
- import ropeImage3 from '@/assets/RoundaboutImage/2-17.png'
- import ropeImage4 from '@/assets/RoundaboutImage/2-18.png'
- import ropeImage5 from '@/assets/RoundaboutImage/2-19.png'
- import ropeImage6 from '@/assets/RoundaboutImage/2-20.png'
- import ropeImage7 from '@/assets/RoundaboutImage/2-21.png'
- import ropeImage8 from '@/assets/RoundaboutImage/2-22.png'
- import ropeImage9 from '@/assets/RoundaboutImage/2-23.png'
-
- const designWidth = 640; // 设计稿宽度
- const desginRadius = 246; // 设计稿轮盘半径
- const pedestalDistance = 424; // 底座与轮盘圆心距离
- const primaryColor = '#F19805'; // 设计稿主题色
-
- const scale = window.screen.width / designWidth;
- const ropeImageList = [
- ropeImage1,
- ropeImage2,
- ropeImage3,
- ropeImage4,
- ropeImage5,
- ropeImage6,
- ropeImage7,
- ropeImage8,
- ropeImage9,
- ];
-
- export default function game({ el, center, onError, onSuccess, onBingo }) {
-
- // 是否游戏结束
- let isFinished = false;
-
- // 是否失败
- // eslint-disable-next-line no-unused-vars
- let isError = false;
-
- // 每个区域的旋转角度, 8 个轿厢, 分 16 等份
- const cageNum = 8;
- const perAngle = Math.PI / cageNum // 2 * Math.PI / ( cageNum * 2 ) 分成 轿厢 * 2 的分数, 最终效果是每隔一个,显示一个轿厢
- const offsetAngle = perAngle / 2
-
- // 大转盘半径
- const raduis = desginRadius * scale;
-
- // 轿厢宽度 = 大轮盘的周长 / 间隔份数
- const cageRadius = Math.PI * 2 * raduis / (cageNum * 2)
-
- // 转盘中心坐标
- // const center;
-
- // 轿厢 - 子弹
- const bullets = []
-
- // 子弹是否在射击状态, 如果是, 不能进行其他操作
- let isShooting = false
-
- // 子弹飞行速度, 因为是向上飞, y 值是不端减小的过程
- const speed = -5;
-
- // 目标轿厢列表, 该列表主要用来判断子弹是否击中目标
- const cageList = [];
-
- // 轿厢的初始旋转弧度
- let rotateAngle = 0
-
- // 轿厢的旋转速度 - 单位弧度
- const rotateSpeed = 0.01
-
- // 目标轿厢与子弹轿厢的映射字典
- const mntMap = {}
-
- // 初始化
- let two = new Two({
- fullscreen: true,
- autostart: true
- }).appendTo(el);
-
- // 绘制背景
- makeBg({two, center});
-
- // 绘制转盘
- const wheelArc = two.makeCircle(center.x, center.y, 0);
- const lightArc = two.makeCircle(center.x, center.y, 0);
- const drawRoundAbout = () => {
- const { x, y } = center
-
- // 绘制图片转盘
- const wheelTexture = two.makeTexture(wheelImage);
- wheelArc.noStroke();
- wheelArc.fill = wheelTexture;
- wheelTexture.bind('load', () => {
- wheelArc.radius = wheelTexture.image.naturalWidth / 2;
- wheelArc.scale = scale;
- })
-
- // 绘制光圈
- const lightTexture = two.makeTexture(lightImage);
- lightArc.noStroke();
- lightArc.fill = lightTexture;
- lightTexture.bind('load', () => {
- lightArc.radius = lightTexture.image.naturalWidth / 2;
- lightArc.scale = scale;
- })
- lightArc.bind('update', () => {
- console.log('-----------lightArc----------')
- })
-
- // 绘制辅助扇形区域
- const arcList = Array(cageNum).fill().map((_, inx) => {
- const startAngle = 2 * inx * perAngle - offsetAngle; // 实际效果图是圆形有固定大小的, 不能直接从 0° 开始, 需要从 0 - offset 开始
- const endAngle = startAngle + perAngle
- const arc = two.makeArcSegment(x, y, 0, raduis, startAngle, endAngle)
- // arc.fill = colorList[inx] // 取消这个注释, 有助于梳理逻辑
- arc.noFill()
- arc.noStroke()
-
- // 绘制目标轿厢
- // 1、计算扇形边上的中心坐标
- const cx = x + Math.cos(startAngle + perAngle / 2) * raduis
- const cy = y + Math.sin(startAngle + perAngle / 2) * raduis
-
- // 2、轿厢
- const rect = two.makeCircle(cx, cy, cageRadius / 2);
- rect.id = `mnt-${inx}`
- // rect.fill = colorList[inx] // 取消这个注释, 有助于梳理逻辑
- rect.noFill()
- rect.stroke = primaryColor
- rect.linewidth = 2
- rect.dashes = [4]
- rect.__$angle = startAngle // 轿厢的初始弧度
-
- cageList.push(rect)
-
- return arc
- })
-
- return arcList
- }
- const roundAbout = drawRoundAbout()
-
- // 成功之后的特效
- const successEffect = () => {
-
- }
-
- // 旋转
- const rotate = () => {
- rotateAngle += rotateSpeed
- wheelArc.rotation = rotateAngle
- roundAbout.forEach((x, inx) => {
- x.rotation = rotateAngle
-
- // 计算旋转后的轿厢坐标
- const cage = cageList[inx]
- const angle = cage.__$angle + rotateAngle + perAngle / 2
- const cx = center.x + Math.cos(angle) * raduis
- const cy = center.y + Math.sin(angle) * raduis
- cage.position = new Two.Vector(cx, cy)
-
- // 如果有对应的挂载物
- if (mntMap[cage.id]) {
- mntMap[cage.id].position = cage.position
- }
- })
- }
-
- let ropeStart = null;
- let ropeStop = null;
-
- // 绘制底座
- const drawPedestal = (x, y) => {
- //
- const pedestalTexture = two.makeTexture(pedestalImage);
- const pedestalBox = two.makeRectangle(x, y, 0, 0);
- pedestalBox.noStroke();
- pedestalBox.fill = pedestalTexture;
- pedestalTexture.bind('load', () => {
- const { naturalWidth, naturalHeight } = pedestalTexture.image
- pedestalBox.width = naturalWidth
- pedestalBox.height = naturalHeight
- pedestalBox.scale = scale
- })
-
- //
- const ropeSequence = two.makeImageSequence(ropeImageList, x, y, 40); // 40 是不断调式出来的频率, 需要跟 speed 匹配
- ropeSequence.scale = new Two.Vector(scale, scale);
-
- ropeStart = () => ropeSequence.play();
- ropeStop = () => ropeSequence.stop();
-
- return pedestalBox;
- }
- const pedestalBox = drawPedestal(center.x, center.y + pedestalDistance * scale)
-
- // 绘制子弹
- const drawBullets = (x, y) => {
- const cageTexture = two.makeTexture(cageImage);
-
- const list = Array(cageNum).fill().map((_, inx) => {
- const arc = two.makeCircle(x, y, cageRadius / 2)
- arc.id = `bullet-${inx}`
- // arc.fill = 'black'
- arc.fill = cageTexture
- arc.noStroke()
- arc.visible = inx === 0; // 第一个显示, 其余默认不显示
-
- return arc
- })
-
- cageTexture.bind('load', () => {
- const r = cageTexture.image.naturalWidth / 2;
- list.forEach(it => {
- it.radius = r;
- it.scale = scale;
- });
- })
-
- bullets.push(...list)
- }
- drawBullets(center.x, center.y + pedestalDistance * scale)
- // 待发射子弹
- const clip = bullets.slice()
-
- // 绘制计数器
- const drawCounter = (x, y) => {
- const counterTexture = two.makeTexture(countImage);
- const counterBox = two.makeRectangle(x, y, 0, 0);
- counterBox.noStroke();
- counterBox.fill = counterTexture;
- counterTexture.bind('load', () => {
- const { naturalWidth, naturalHeight } = counterTexture.image
- counterBox.width = naturalWidth
- counterBox.height = naturalHeight
- counterBox.scale = scale
- })
- // 初始值是弹夹中子弹数量
- const counter = two.makeText(clip.length, x, y, { fill: primaryColor, weight: 700 })
-
- return counter
- }
- const counter = drawCounter(pedestalBox.position.x + (224 * scale), pedestalBox.position.y); // 224 是设计稿上底座与计数器之间距离
-
- // 挂载到轮盘
- const mountToCage = (targ, cage) => {
- targ.position = cage.position
- // 写入映射表
- mntMap[cage.id] = targ
- }
-
- // 当前子弹
- let currentBullet = null;
- // 射击
- const shooting = () => {
- const { top, height } = currentBullet.getBoundingClientRect()
- currentBullet.position = new Two.Vector(currentBullet.position.x, top + height / 2 + speed)
-
- // 是否击中
- const hitted = isHit(currentBullet)
- if (hitted === 'error') {
- ropeStop();
- const t = setTimeout(() => {
- isError = true
- isFinished = true
- onError()
- clearTimeout(t)
- }, 0)
- return
- }
-
- // 击中之后, 需要挂载轿厢到对应的位置
- if (hitted) {
- mountToCage(currentBullet, hitted)
- // currentBullet = null;
- currentBullet = clip.shift();
- if (currentBullet) {
- currentBullet.visible = true
- counter.value = clip.length + 1
- } else {
- isFinished = true
- counter.value = 0
- }
-
- isShooting = false;
- ropeStop();
-
- const t = setTimeout(() => {
- if (isFinished) {
- onSuccess()
- } else {
- onBingo()
- }
- clearTimeout(t)
- }, 0)
- }
- }
-
- // 是否击中
- // 如果击中, 则返回目标轿厢
- const isHit = (bullet) => {
- const rect2 = bullet.getBoundingClientRect();
- const x = rect2.left + rect2.width / 2
- const y = rect2.top + rect2.height / 2
-
- if (y < (center.y + raduis)) {
- return 'error';
- }
-
- const cage = cageList.filter((it) => {
- const rect1 = it.getBoundingClientRect();
-
- // 如果当前子弹的中心点位于目标轿厢矩形范围内
- // 则代表击中
- return x >= rect1.left && x <= rect1.right &&
- y >= rect1.top && y <= rect1.bottom;
- })[0]
-
- if (!cage) return false; // 未找到对应的目标轿厢
- if (mntMap[cage.id]) return false; // 已经挂载过轿厢
-
- return cage
- }
-
- // 重复绘制内容
- two.bind('update', () => {
- if (!isFinished) {
- rotate()
-
- if (isShooting) {
- shooting()
- }
- }
- })
-
- // 绑定 dom click 事件 触发子弹发射
- el.addEventListener('click', () => {
- if (!isFinished && !isShooting) {
- isShooting = true
- // 启动绳子效果
- ropeStart()
- if (!currentBullet) {
- currentBullet = clip.shift()
- currentBullet.visible = true
- }
- }
- })
-
- return {
- start: () => {
- isFinished = false;
- },
- stop: () => {
- isFinished = true;
- },
- destroy: () => {
- two.unbind('update');
- two.pause();
- el.removeChild(two.renderer.domElement);
- two = null;
- }
- }
- }
-
- function makeBg({two, center}) {
- const bgTexture = two.makeTexture(backImage);
-
- const bgBox = two.makeRectangle(center.x, center.y, 0, 0);
- bgBox.noStroke();
- bgBox.fill = bgTexture;
-
- bgTexture.bind('load', () => {
- bgBox.width = bgTexture.image.naturalWidth;
- bgBox.height = bgTexture.image.naturalHeight;
- bgBox.scale = scale;
- })
- }
|