成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

用原生 JS 寫一個簡易版的臺球

開發 前端
requestAnimationFrame就是一個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
<div class="table">
<div class="big">
<div class="box">
<div class="gan">
<div class="gan2"></div>
<div class="gan3"></div>
</div>
</div>
<div class="box2"></div>
</div>
</div>

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)
})
}

圖片

總結

圖片

這個小游戲實現的并不完美,因為用到了太多的遞歸,很多細節方面不好控制,球的運動軌跡也很難計算,在某些角度下會出現BUG。球雖然是圓的,但是它的盒子是正方形,所以撞擊有的時候會看著很奇怪。移動的函數寫的也有缺陷,它不能復用,如果想添加多個球,函數就得改。

這個破產版的臺球主要就是寫著玩一玩,嘗試了一下JS動畫的實現 , 不喜勿噴。

責任編輯:姜華 來源: 前端YUE
相關推薦

2024-02-06 10:04:49

Express框架repo

2020-09-29 09:41:50

Spring Boot項目代碼

2023-12-29 08:31:49

Spring框架模塊

2017-01-13 08:37:57

PythonAlphaGoMuGo

2021-04-23 16:40:49

Three.js前端代碼

2021-07-12 15:50:55

Go 語言netstat命令

2013-06-18 09:51:52

PomeloPomelo平臺搭建平臺

2018-12-04 13:30:28

Javascript編譯原理前端

2022-02-11 13:44:56

fiber架構React

2020-10-29 16:00:03

Node.jsweb前端

2023-04-07 15:45:13

Emojicode開源編碼語言

2023-04-10 14:20:47

ChatGPTRESTAPI

2011-12-05 10:37:53

Linux服務器Shell腳本

2017-06-08 15:53:38

PythonWeb框架

2022-03-24 14:42:19

Python編程語言

2018-10-31 10:11:24

Python編程語言語音播放

2022-04-06 18:29:58

CSSJS輸入框

2022-10-08 00:06:00

JS運行V8

2021-05-06 15:05:57

Python自動化工具

2023-09-06 09:54:12

AI模型
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 久久久久久亚洲精品 | 91av在线免费看 | 欧美精品久久 | 久草网视频 | 夜夜爽夜夜操 | 亚洲一二三区在线观看 | 欧美二区在线 | 四虎成人免费视频 | 欧美在线一区二区三区四区 | 国产91丝袜在线播放 | 精品欧美乱码久久久久久1区2区 | 超碰天天 | 久久小视频 | 日韩精品在线观看网站 | 精品国产一级片 | 久久精品久久精品久久精品 | 国产福利在线 | 成人免费视频 | 国产精品视频在线播放 | 日韩三级视频 | 国产精品成人国产乱一区 | 综合色播| 翔田千里一区二区 | 999精品视频 | 国产第二页 | 国产一区二区视频免费在线观看 | 午夜大片 | www日韩欧美 | 久久久久国产精品免费免费搜索 | 丁香六月激情 | 99视频免费在线观看 | 91一区二区 | 日本黄色免费片 | 天天干天天操天天爽 | 久久精品欧美一区二区三区麻豆 | 中文字幕11页 | www国产亚洲精品久久网站 | 国产欧美一区二区久久性色99 | 国产精品免费一区二区三区 | 精品1区| 日日操操 |