服务器之家:专注于VPS、云服务器配置技术及软件下载分享
分类导航

node.js|vue.js|jquery|angularjs|React|json|js教程|

服务器之家 - 编程语言 - JavaScript - js教程 - 用原生 JS 写一个简易版的台球

用原生 JS 写一个简易版的台球

2022-10-20 18:07前端YUE逍丶 js教程

突发奇想想用JS写一个台球小游戏,磕磕碰碰之后,算是实现了一个简易版的。用到的知识主要是通过递归来调用requestAnimationFrame,以及一些简单的三角函数角度计算。requestAnimationFrame就是一个JS动画帧,简单来说和定时器有点相似

用原生 JS 写一个简易版的台球

前言

突发奇想想用JS写一个台球小游戏,磕磕碰碰之后,算是实现了一个简易版的。用到的知识主要是通过递归来调用requestAnimationFrame,以及一些简单的三角函数角度计算。requestAnimationFrame就是一个JS动画帧,简单来说和定时器有点相似,但是动画呈现出来的效果比定时器更流畅,性能更好。

1、绘制游戏元素

CSS

 

// CSS .table { position: relative; margin: 100px auto; width: 1080px; height: 596px; background: url(./台球桌.jpg) no-repeat; background-size: 100%;
} .big { position: absolute; width: 1000px; height: 500px; left: 43px; top: 48px;
} .box, .box2 { width: 50px; height: 50px; border-radius: 50%; box-shadow: inset 0 0 10px rgba(255, 255, 255, 0.5); position: absolute;
} .box { background: radial-gradient(circle at 75% 30%, #fff 5px, #fffbfef1 8%, #aaaaaac4 60%, #faf6f9bd 100%);
} .box2 { background: radial-gradient(circle at 75% 30%, #fff 5px, #ff21f4f1 8%, #d61d1dc4 60%, #ff219b 100%);
} .big .box::before, .box2::before { content: ''; position: absolute; width: 100%; height: 100%; transform: scale(0.25) translate(-70%, -70%); background: radial-gradient(#fff, transparent); border-radius: 50%;
} .gan { display: flex; height: 20px; position: absolute; left: 25px; top: 15px; transform-origin: 0 50%; transform: rotate(50deg); cursor: pointer;
} .gan2 { width: 25px; height: 20px;
} .gan3 { width: 375px; height: 20px; background: url(./Snipaste_2022-07-18_19-52-54.jpg) no-repeat center; background-size: 100%;
}

 

html

 

//html
		
 
 
 

 

JS

 

//JS
// 设置球的位置
//母球
const box1 = document.querySelector('.box')
box1.style.left = '300px'
box1.style.top = '150px'
//子球
const box2 = document.querySelector('.box2')
box2.style.left = '700px'
box2.style.top = '300px'
//球杆
const gan = document.querySelector('.gan')
const gan2 = document.querySelector('.gan2')
const gan3 = document.querySelector('.gan3')

 

2、球杆跟随鼠标旋转

先获取鼠标在页面的坐标,然后减去球心的坐标,就得到了一个相对坐标。然后把球心当成原点,计算出鼠标相对球心的角度,最后把这个角度赋值给球杆的transform属性,就可以实现球杆跟随鼠标旋转的效果了

 

//声明鼠标相对坐标变量 let x, y // 获取鼠标的坐标,来计算球杆的角度 document.addEventListener('mousemove', function (e) { const position = box1.getBoundingClientRect()
  // 获取鼠标相对球心的坐标,因为盒子的position原点在左上角,所以要减去自身宽高的一半才是球心 x = e.pageX - position.left - 25 y = e.pageY - position.top - 25 - document.documentElement.scrollTop let z = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2)); // 勾股定理计算斜边值 let cos = y / z;// 余弦 let radian = Math.acos(cos);//用反三角函数求弧度 let angle = 180 / (Math.PI / radian);//将弧度转换成角度 if (x > 0 && y > 0) {//鼠标在第四象限 angle = 90 - angle } if (x == 0 && y > 0) {//鼠标在y轴负方向上 angle = 90;
  } if (x == 0 && y < 0) {//鼠标在y轴正方向上 angle = 270;
  } if (x > 0 && y == 0) {//鼠标在x轴正方向上 angle = 0;
  } if (x < 0 && y > 0) {//鼠标在第三象限 angle = 90 + angle } if (x < 0 && y == 0) {//鼠标在x轴负方向 angle = 180;
  } if (x < 0 && y < 0) {//鼠标在第二象限 angle = 90 + angle } if (x > 0 && y < 0) {//鼠标在第一象限 angle = 450 - angle }
  // 把计算出来的角度取模后赋值给球杆旋转角度 gan.style.transform = `rotate(${angle % 360}deg)`
})

 

3、球杆的击球动画

球杆其实是由 3 个盒子组成的,最外面的大盒子来控制球杆的旋转,大盒子里面有两个盒子 gan2 和 gan3, gan3 这个盒子用来放球杆的图片。gan2 这个盒子是看不到的,它负责把球杆向外面撑开。所以球杆的动画就很简单了,只要增加和减少 gan2 盒子的宽,就能实现球杆的伸缩了。

实现动画就是用尾递归来重复调用 requestAnimationFrame 函数。

 

// // 球杆点击事件 document.querySelector('.gan3').addEventListener('click', function () { moveGan(gan2, 0)
})
// 球杆打击动画 function moveGan(item, num) {
  // i来控制函数的结束条件 let i = num requestAnimationFrame(() {
    //获取元素的坐标值,要把字符串里的数字提取出来 let moveX = parseFloat(item.style.width) || 25 moveX += 15 // 每一次调用这个函数,就让元素的宽+15px item.style.width = moveX + 'px' i++ if (i >= 10) {
      // i>10时,就让球杆再缩回去 return returnGan(item, 0)
    }
    // 使用尾递归来重复调用 return moveGan(item, i)
  })
} function returnGan(item, num) { let i = num requestAnimationFrame(() { let moveX = parseFloat(item.style.width) || 0 moveX -= 15 // 每一次调用这个函数,就让元素的宽-15px item.style.width = moveX + 'px' i++ if (i >= 10) { return tick() //tick是击球的函数
    } return returnGan(item, i)
  })
}

 

4、球杆击球后,母球的移动

母球的击球动画同样是通过尾递归来重复调用 requestAnimationFrame 函数,但是涉及到墙壁反弹,以及撞击子秋,母球的移动函数的参数会复杂一点。

母球移动的速度和距离,是通过i这个变量来控制的,这个函数每调用一次,i 会递减。x 和 y 这两个参数会接收一个 -1 到 1 之间的值,起到一个方向系数的效果,通过参数把球杆的撞击方向传递进来。碰到边界之后,就把对应的系数取负,然后用新系数执行移动函数,就能起到反弹的效果了。

 

// 击打母球的函数 function tick() {
  // 通过绝对值判断打击角度,xy就是鼠标相对球心的坐标 if (Math.abs(x) > Math.abs(y)) {
    // 通过判断x,y是否大于0,判断打击方向 if (x > 0 && y > 0 || x > 0 && y < 0) { raf(box1, -1, -1 / (x / y), 1000)
    } else { raf(box1, 1, 1 / (x / y), 1000)
    }
  } else { if (y > 0 && x > 0 || y > 0 && x < 0) { raf(box1, -1 / (y / x), -1, 1000)
    } else { raf(box1, 1 / (y / x), 1, 1000)
    }
  }
}

//..... 母球移动的函数里面还要加代码,所以这里就先不贴出来了。 

// 判断是否进洞的函数 function test(x, y) { if (x < 10 && y < 10 || x > 940 && y < 10 || x > 940 && y > 440 || x < 10 && y > 440 || x > 475 && x < 525 && y < 5 || x > 475 && x < 525 && y > 445) { return true }
}

 

5、母球撞击子球移动

这是最麻烦的一步,撞击后两个球的运动轨迹都会发生变化。只考虑最普通的撞击,子球的运动方向应该是撞击点与子球球心这条直线的方向,这个比较好计算。母球的撞击后的方向应该是以撞击点的那条切线进行反弹,三角函数几乎忘光了,这个我也不知道怎么计算了,所以用了个简易的算法,就和撞墙壁一样直接反弹,这样会导致某些角度下,母球撞击之后的方向不正常。

把这个撞击判断加到母球移动的函数里面,然后再补充一个子球的移动函数,整个代码就写完了

 

//母球移动
// 获取坐标,要把字符串里的数字提取出来 let fx = parseFloat(box1.style.left) let fy = parseFloat(box1.style.top) let gx = parseFloat(box2.style.left) let gy = parseFloat(box2.style.top)
// 声明用判断撞球角度的变量 let n // 控制子球移动函数的调用 let p = true function raf(item, x, y, num) {
  //击球后隐藏球杆 gan3.style.display = 'none' // item是目标元素,xy对应移动方向的系数,i用来控制移动速度 let i = num requestAnimationFrame(() { fx += x * 5 * i / 500 fy += y * 5 * i / 500 item.style.left = fx + 'px' item.style.top = fy + 'px' i -= 2 // 边界判断,球桌宽1000500,球宽高50,所以边界就是0-950 if (fx > 950) { // 右边界,让x系数反过来 fx = 950 return raf(item, -x, y, i)
    } else if (fy > 450) { // 下边界,让y系数反过来 fy = 450 return raf(item, x, -y, i)
    } else if (fx < 0) { // 左边界,让x系数反过来 fx = 0 return raf(item, -x, y, i)
    } else if (fy < 0) { // 上边界,让y系数反过来 fy = 0 return raf(item, x, -y, i)
    }
    // i<=50就停止移动,然后显示球杆 if (i <= 50) return gan3.style.display = 'block' // 判断球是否进洞 if (test(fx, fy)) { return item.style.display = 'none' }
    //两个球撞击时的判断 if (fx < gx + 50 && fx > gx - 50 && fy < gy + 50 && fy > gy - 50) {
      // 子球前进的角度,就是撞击时,两个圆心连线的夹角 n = Math.abs(gx - fx) >= Math.abs(gy - fy) ? Math.abs(gx - fx) : Math.abs(gy - fy)
      // n用来控制调用函数时x,y的大小,不能大于1,否则移动速度会异常 if (p) raf2(box2, (gx - fx) / n, (gy - fy) / n, i)
      // 只有第一次碰撞时,会调用一次子球移动的函数,避免一次击球产生多次撞击时,这个函数被多次调用 p = false return raf(item, -x, y, i)
    } return raf(item, x, y, i)
  })
}
 //子球移动 function raf2(item, x, y, num) { let i = num requestAnimationFrame(() {
    //获取元素的坐标值,要把字符串里的数字提取出来 gx += x * 5 * i / 700 gy += y * 5 * i / 700 item.style.left = gx + 'px' item.style.top = gy + 'px' i -= 2 if (gx > 950) { gx = 950 return raf2(item, -x, y, i)
    } else if (gy > 450) { gy = 450 return raf2(item, x, -y, i)
    } else if (gx < 0) { gx = 0 return raf2(item, -x, y, i)
    } else if (gy < 0) { gy = 0 return raf2(item, x, -y, i)
    }
    //两个球触碰判断 if (fx < gx + 50 && fx > gx - 50 && fy < gy + 50 && fy > gy - 50) { return raf2(box2, (gx - fx) / n, (gy - fy) / n, i)
    } if (i <= 50) return p = true // 移动函数执行完后,重置p这个变量
    // 判断球是否进洞 if (test(gx, gy)) { return item.style.display = 'none' } return raf2(item, x, y, i)
  })
}

 

用原生 JS 写一个简易版的台球

总结

用原生 JS 写一个简易版的台球

这个小游戏实现的并不完美,因为用到了太多的递归,很多细节方面不好控制,球的运动轨迹也很难计算,在某些角度下会出现BUG。球虽然是圆的,但是它的盒子是正方形,所以撞击有的时候会看着很奇怪。移动的函数写的也有缺陷,它不能复用,如果想添加多个球,函数就得改。

这个破产版的台球主要就是写着玩一玩,尝试了一下JS动画的实现 , 不喜勿喷。

原文地址:https://mp.weixin.qq.com/s/ytlJ8SD7bZ-GoCyN0IXtfQ

延伸 · 阅读

精彩推荐
  • js教程详解CocosCreator华容道数字拼盘

    详解CocosCreator华容道数字拼盘

    这篇文章主要介绍了详解CocosCreator华容道数字拼盘,对华容道感兴趣的同学,看完之后,可以回去亲手试一下...

    搬砖小菜鸟10792022-03-02
  • js教程JS异步观察目标元素方式完成分页加载

    JS异步观察目标元素方式完成分页加载

    这篇文章主要为大家介绍了异步观察目标元素方式完成分页加载示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职...

    jsmask3632022-07-30
  • js教程js实现有趣的倒计时效果

    js实现有趣的倒计时效果

    这篇文章主要为大家详细介绍了js实现有趣的倒计时效果,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...

    搬砖大法4572022-01-04
  • js教程ES6的循环与可迭代对象示例详解

    ES6的循环与可迭代对象示例详解

    这篇文章主要给大家介绍了关于ES6的循环与可迭代对象的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,...

    疯狂的技术宅11802022-01-12
  • js教程原生js实现自定义滚动条组件

    原生js实现自定义滚动条组件

    这篇文章主要为大家详细介绍了原生js实现自定义滚动条组件的开发,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一...

    蒲公英芽11572022-01-05
  • js教程原生js 实现表单验证功能

    原生js 实现表单验证功能

    这篇文章主要介绍了原生js如何实现表单验证功能,帮助大家更好的理解和使用JavaScript,感兴趣的朋友可以了解下...

    蒋伟平7502022-01-19
  • js教程js canvas实现随机粒子特效

    js canvas实现随机粒子特效

    这篇文章主要为大家详细介绍了js canvas随机粒子特效,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...

    莫兮是我10402022-03-06
  • js教程如何利用JS检查元素是否在视口内

    如何利用JS检查元素是否在视口内

    这篇文章主要给大家介绍了关于如何利用JS检查元素是否在视口内的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考...

    冷石4652022-03-01