日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
用原生 JS 寫一個簡易版的臺球

前言

突發(fā)奇想想用JS寫一個臺球小游戲,磕磕碰碰之后,算是實現(xiàn)了一個簡易版的。用到的知識主要是通過遞歸來調(diào)用requestAnimationFrame,以及一些簡單的三角函數(shù)角度計算。requestAnimationFrame就是一個JS動畫幀,簡單來說和定時器有點相似,但是動畫呈現(xiàn)出來的效果比定時器更流暢,性能更好。

創(chuàng)新互聯(lián)服務(wù)項目包括雙河網(wǎng)站建設(shè)、雙河網(wǎng)站制作、雙河網(wǎng)頁制作以及雙河網(wǎng)絡(luò)營銷策劃等。多年來,我們專注于互聯(lián)網(wǎng)行業(yè),利用自身積累的技術(shù)優(yōu)勢、行業(yè)經(jīng)驗、深度合作伙伴關(guān)系等,向廣大中小型企業(yè)、政府機構(gòu)等提供互聯(lián)網(wǎng)行業(yè)的解決方案,雙河網(wǎng)站推廣取得了明顯的社會效益與經(jīng)濟效益。目前,我們服務(wù)的客戶以成都為中心已經(jīng)輻射到雙河省份的部分城市,未來相信會繼續(xù)擴大服務(wù)區(qū)域并繼續(xù)獲得客戶的支持與信任!

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
// 設(shè)置球的位置
//母球
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、球桿跟隨鼠標旋轉(zhuǎn)

先獲取鼠標在頁面的坐標,然后減去球心的坐標,就得到了一個相對坐標。然后把球心當(dāng)成原點,計算出鼠標相對球心的角度,最后把這個角度賦值給球桿的transform屬性,就可以實現(xiàn)球桿跟隨鼠標旋轉(zhuǎn)的效果了

//聲明鼠標相對坐標變量
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);//用反三角函數(shù)求弧度
let angle = 180 / (Math.PI / radian);//將弧度轉(zhuǎn)換成角度
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
}
// 把計算出來的角度取模后賦值給球桿旋轉(zhuǎn)角度
gan.style.transform = `rotate(${angle % 360}deg)`
})

3、球桿的擊球動畫

球桿其實是由 3 個盒子組成的,最外面的大盒子來控制球桿的旋轉(zhuǎn),大盒子里面有兩個盒子 gan2 和 gan3, gan3 這個盒子用來放球桿的圖片。gan2 這個盒子是看不到的,它負責(zé)把球桿向外面撐開。所以球桿的動畫就很簡單了,只要增加和減少 gan2 盒子的寬,就能實現(xiàn)球桿的伸縮了。

實現(xiàn)動畫就是用尾遞歸來重復(fù)調(diào)用 requestAnimationFrame 函數(shù)。

// // 球桿點擊事件
document.querySelector('.gan3').addEventListener('click', function () {
moveGan(gan2, 0)
})
// 球桿打擊動畫
function moveGan(item, num) {
// i來控制函數(shù)的結(jié)束條件
let i = num
requestAnimationFrame(() {
//獲取元素的坐標值,要把字符串里的數(shù)字提取出來
let moveX = parseFloat(item.style.width) || 25
moveX += 15
// 每一次調(diào)用這個函數(shù),就讓元素的寬+15px
item.style.width = moveX + 'px'
i++
if (i >= 10) {
// i>10時,就讓球桿再縮回去
return returnGan(item, 0)
}
// 使用尾遞歸來重復(fù)調(diào)用
return moveGan(item, i)
})
}
function returnGan(item, num) {
let i = num
requestAnimationFrame(() {
let moveX = parseFloat(item.style.width) || 0
moveX -= 15
// 每一次調(diào)用這個函數(shù),就讓元素的寬-15px
item.style.width = moveX + 'px'
i++
if (i >= 10) {
return tick() //tick是擊球的函數(shù)
}
return returnGan(item, i)
})
}

4、球桿擊球后,母球的移動

母球的擊球動畫同樣是通過尾遞歸來重復(fù)調(diào)用 requestAnimationFrame 函數(shù),但是涉及到墻壁反彈,以及撞擊子秋,母球的移動函數(shù)的參數(shù)會復(fù)雜一點。

母球移動的速度和距離,是通過i這個變量來控制的,這個函數(shù)每調(diào)用一次,i 會遞減。x 和 y 這兩個參數(shù)會接收一個 -1 到 1 之間的值,起到一個方向系數(shù)的效果,通過參數(shù)把球桿的撞擊方向傳遞進來。碰到邊界之后,就把對應(yīng)的系數(shù)取負,然后用新系數(shù)執(zhí)行移動函數(shù),就能起到反彈的效果了。

// 擊打母球的函數(shù)
function tick() {
// 通過絕對值判斷打擊角度,x和y就是鼠標相對球心的坐標
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)
}
}
}

//..... 母球移動的函數(shù)里面還要加代碼,所以這里就先不貼出來了。

// 判斷是否進洞的函數(shù)
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、母球撞擊子球移動

這是最麻煩的一步,撞擊后兩個球的運動軌跡都會發(fā)生變化。只考慮最普通的撞擊,子球的運動方向應(yīng)該是撞擊點與子球球心這條直線的方向,這個比較好計算。母球的撞擊后的方向應(yīng)該是以撞擊點的那條切線進行反彈,三角函數(shù)幾乎忘光了,這個我也不知道怎么計算了,所以用了個簡易的算法,就和撞墻壁一樣直接反彈,這樣會導(dǎo)致某些角度下,母球撞擊之后的方向不正常。

把這個撞擊判斷加到母球移動的函數(shù)里面,然后再補充一個子球的移動函數(shù),整個代碼就寫完了

//母球移動
// 獲取坐標,要把字符串里的數(shù)字提取出來
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
// 控制子球移動函數(shù)的調(diào)用
let p = true
function raf(item, x, y, num) {
//擊球后隱藏球桿
gan3.style.display = 'none'
// item是目標元素,x和y對應(yīng)移動方向的系數(shù),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
// 邊界判斷,球桌寬1000高500,球?qū)捀?0,所以邊界就是0-950
if (fx > 950) { // 右邊界,讓x系數(shù)反過來
fx = 950
return raf(item, -x, y, i)
} else if (fy > 450) { // 下邊界,讓y系數(shù)反過來
fy = 450
return raf(item, x, -y, i)
} else if (fx < 0) { // 左邊界,讓x系數(shù)反過來
fx = 0
return raf(item, -x, y, i)
} else if (fy < 0) { // 上邊界,讓y系數(shù)反過來
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用來控制調(diào)用函數(shù)時x,y的大小,不能大于1,否則移動速度會異常
if (p) raf2(box2, (gx - fx) / n, (gy - fy) / n, i)
// 只有第一次碰撞時,會調(diào)用一次子球移動的函數(shù),避免一次擊球產(chǎn)生多次撞擊時,這個函數(shù)被多次調(diào)用
p = false
return raf(item, -x, y, i)
}
return raf(item, x, y, i)
})
}
//子球移動
function raf2(item, x, y, num) {
let i = num
requestAnimationFrame(() {
//獲取元素的坐標值,要把字符串里的數(shù)字提取出來
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 // 移動函數(shù)執(zhí)行完后,重置p這個變量
// 判斷球是否進洞
if (test(gx, gy)) {
return item.style.display = 'none'
}
return raf2(item, x, y, i)
})
}

總結(jié)

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

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


新聞標題:用原生 JS 寫一個簡易版的臺球
分享路徑:http://m.5511xx.com/article/cdiicij.html