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

用原生 JS 寫一個(gè)簡(jiǎn)易版的臺(tái)球

開發(fā) 前端
requestAnimationFrame就是一個(gè)JS動(dòng)畫幀,簡(jiǎn)單來說和定時(shí)器有點(diǎn)相似,但是動(dòng)畫呈現(xiàn)出來的效果比定時(shí)器更流暢,性能更好。

前言

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

1、繪制游戲元素

CSS

// CSS
.table {
position: relative;
margin: 100px auto;
width: 1080px;
height: 596px;
background: url(./臺(tái)球桌.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
// 設(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、球桿跟隨鼠標(biāo)旋轉(zhuǎn)

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

//聲明鼠標(biāo)相對(duì)坐標(biāo)變量
let x, y
// 獲取鼠標(biāo)的坐標(biāo),來計(jì)算球桿的角度
document.addEventListener('mousemove', function (e) {
const position = box1.getBoundingClientRect()
// 獲取鼠標(biāo)相對(duì)球心的坐標(biāo),因?yàn)楹凶拥?span style="color: #005cc5;">position原點(diǎn)在左上角,所以要減去自身寬高的一半才是球心
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)); // 勾股定理計(jì)算斜邊值
let cos = y / z;// 余弦
let radian = Math.acos(cos);//用反三角函數(shù)求弧度
let angle = 180 / (Math.PI / radian);//將弧度轉(zhuǎn)換成角度
if (x > 0 && y > 0) {//鼠標(biāo)在第四象限
angle = 90 - angle
}
if (x == 0 && y > 0) {//鼠標(biāo)在y軸負(fù)方向上
angle = 90;
}
if (x == 0 && y < 0) {//鼠標(biāo)在y軸正方向上
angle = 270;
}
if (x > 0 && y == 0) {//鼠標(biāo)在x軸正方向上
angle = 0;
}
if (x < 0 && y > 0) {//鼠標(biāo)在第三象限
angle = 90 + angle
}
if (x < 0 && y == 0) {//鼠標(biāo)在x軸負(fù)方向
angle = 180;
}
if (x < 0 && y < 0) {//鼠標(biāo)在第二象限
angle = 90 + angle
}
if (x > 0 && y < 0) {//鼠標(biāo)在第一象限
angle = 450 - angle
}
// 把計(jì)算出來的角度取模后賦值給球桿旋轉(zhuǎn)角度
gan.style.transform = `rotate(${angle % 360}deg)`
})

3、球桿的擊球動(dòng)畫

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

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

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

4、球桿擊球后,母球的移動(dòng)

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

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

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

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

// 判斷是否進(jìn)洞的函數(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、母球撞擊子球移動(dòng)

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

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

//母球移動(dòng)
// 獲取坐標(biāo),要把字符串里的數(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
// 控制子球移動(dòng)函數(shù)的調(diào)用
let p = true
function raf(item, x, y, num) {
//擊球后隱藏球桿
gan3.style.display = 'none'
// item是目標(biāo)元素,xy對(duì)應(yīng)移動(dòng)方向的系數(shù),i用來控制移動(dòng)速度
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,球?qū)捀?span style="color: #005cc5;">50,所以邊界就是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就停止移動(dòng),然后顯示球桿
if (i <= 50) return gan3.style.display = 'block'
// 判斷球是否進(jìn)洞
if (test(fx, fy)) {
return item.style.display = 'none'
}
//兩個(gè)球撞擊時(shí)的判斷
if (fx < gx + 50 && fx > gx - 50 && fy < gy + 50 && fy > gy - 50) {
// 子球前進(jìn)的角度,就是撞擊時(shí),兩個(gè)圓心連線的夾角
n = Math.abs(gx - fx) >= Math.abs(gy - fy) ? Math.abs(gx - fx) : Math.abs(gy - fy)
// n用來控制調(diào)用函數(shù)時(shí)x,y的大小,不能大于1,否則移動(dòng)速度會(huì)異常
if (p) raf2(box2, (gx - fx) / n, (gy - fy) / n, i)
// 只有第一次碰撞時(shí),會(huì)調(diào)用一次子球移動(dòng)的函數(shù),避免一次擊球產(chǎn)生多次撞擊時(shí),這個(gè)函數(shù)被多次調(diào)用
p = false
return raf(item, -x, y, i)
}
return raf(item, x, y, i)
})
}
//子球移動(dòng)
function raf2(item, x, y, num) {
let i = num
requestAnimationFrame(() {
//獲取元素的坐標(biāo)值,要把字符串里的數(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)
}
//兩個(gè)球觸碰判斷
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 // 移動(dòng)函數(shù)執(zhí)行完后,重置p這個(gè)變量
// 判斷球是否進(jìn)洞
if (test(gx, gy)) {
return item.style.display = 'none'
}
return raf2(item, x, y, i)
})
}

圖片

總結(jié)

圖片

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

這個(gè)破產(chǎn)版的臺(tái)球主要就是寫著玩一玩,嘗試了一下JS動(dòng)畫的實(shí)現(xiàn) , 不喜勿噴。

責(zé)任編輯:姜華 來源: 前端YUE
相關(guān)推薦

2024-02-06 10:04:49

Express框架repo

2020-09-29 09:41:50

Spring Boot項(xiàng)目代碼

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平臺(tái)搭建平臺(tái)

2018-12-04 13:30:28

Javascript編譯原理前端

2022-02-11 13:44:56

fiber架構(gòu)React

2020-10-29 16:00:03

Node.jsweb前端

2023-04-07 15:45:13

Emojicode開源編碼語言

2023-04-10 14:20:47

ChatGPTRESTAPI

2017-06-08 15:53:38

PythonWeb框架

2011-12-05 10:37:53

Linux服務(wù)器Shell腳本

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運(yùn)行V8

2023-09-06 09:54:12

AI模型

2021-05-06 15:05:57

Python自動(dòng)化工具
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

主站蜘蛛池模板: 免费欧美 | 午夜精品一区二区三区在线观看 | 日韩在线视频观看 | 2020亚洲天堂 | 天天影视亚洲综合网 | 欧美日韩在线视频一区 | 日本a∨精品中文字幕在线 亚洲91视频 | 国产欧美一区二区三区在线播放 | 国产精品视频在线观看 | 国产精品乱码一区二三区小蝌蚪 | 99久久久久 | 91视频在线| 在线免费小视频 | 国产精品影视在线观看 | 欧美乱人伦视频 | 91在线影院| 久久精品国产一区二区电影 | 国产日韩欧美 | 中文字幕亚洲视频 | 国内精品久久久久久久影视简单 | 国产不卡在线播放 | 福利视频大全 | 日韩在线欧美 | 久久久久亚洲av毛片大全 | 精品91久久| 国产成人在线一区二区 | 伊人免费观看视频 | 国产区在线 | 成人精品网| 日韩欧美精品在线 | 99精品一区二区三区 | 超碰成人免费 | 婷婷免费视频 | 一级毛片视频在线观看 | 一区二区三区国产 | 一级免费在线视频 | 日韩国产一区 | 亚洲欧美日韩精品久久亚洲区 | 黄网免费 | 久久久日韩精品一区二区三区 | 国产精品一区二区在线播放 |