寫在最前
本次的分享是一個基於jQuery實現的一個移動端射箭類小遊戲。主要實現了目標物、障礙物的隨機渲染,以及中箭效果的判定等。
歡迎關注我的博客,不定期更新中——
效果預覽
點我查看源碼倉庫。
主要結構規劃
...
//基礎屬性
defaultOption = {}
//繪製整體畫面
function drawGame(defaultOption){}
//障礙物、目標物類
function Hinder(){}
Hinder.prototype.xxx
function Target(){}
Target.prototype.xxx
//循環渲染
function eventLoopHinder(){}
function eventLoopTarget(){}
//過期去除障礙物、目標物
function clearHinder(){}
function clearTarget(){}
//觸摸射箭事件監聽
function touchEvent(){}
...
通過以上的結構劃分及效果圖的展示我們可以大概瞭解到,這個遊戲主要涉及的三個較爲關鍵的地方就是:
- 目標物的渲染
- 障礙物的飛行
- 定時清除過期對象
- 中箭效果的判定
- 箭射到對象身上
故接下來作者會介紹下實現思路,至於具體細節有興趣的同學可以在issues下交流。
目標物的渲染
對於目標物作者用了下面的類來表示:
//以下代碼只作爲例子說明,與源碼有較大刪改
//爲了方便表示 random 爲一個假想的隨機值
function Target(id, nowTime, width, score, x, y, time) {
this.id = id //id即唯一標示
this.nowTime = nowTime //當前時間
this.score = score //代表分數
this.width = width || random // 可隨機一個寬度
this.x = x || random //可隨機一個x座標
this.y = y || random //可隨機一個y座標
this.time = time || random //可隨機一個過期時間
}
在上面的示例代碼中我們可以知道,通過new Target()可以得到一個位置隨機,大小隨機,過期時間等等均爲隨機的一個目標物。當然這個隨機作者在代碼中做過限定,都是在一個特定的範圍內隨機出來的。那麼在這期間唯一需要我們控制的,應該就是出現的位置了,爲什麼這麼說呢,因爲出現的位置不應該重合。而如果只憑這個類中的屬性來部署目標物,勢必會各種重合。
粗略判斷目標物的重合
簡單看下實現過程:
//targetArray爲存放目標物對象的數組,每次new出實例後均會放入該數組
Target.prototype.draw = function(newTarget) {
var img = new Image(),
...
img.onload = function() {
...
targetArray.forEach(function(obj, index) {
var x = obj.x - newTarget.x,
y = obj.y - newTarget.y,
dis = Math.sqrt(x*x + y*y)
if(dis < newTarget.width / 2 + obj.width / 2) {
isOk = false //那麼這個對象就不要渲染了
}
if(isOk) {
//渲染該對象到dom
}
})
...
}
}
var newTarget = new Target(id, nowTime, width, scoreTarget)
newTarget.draw(newTarget)
當new出一個目標物後,在調用draw方法時將其傳入到方法中。之後遍歷存放目標物對象的數組,暫且將目標物想象成圓,通過兩圓圓心距離與半徑之和的比來判定要不要將其插入dom節點進行展示。如果童鞋們想要更精確的檢測“碰撞”的方式,可以將遊戲在canvas畫布中進行實現。canvas中getImageData
方法可以獲取圖像的像素點,通過兩對象其像素點是否重合可以更爲精準的判斷。
障礙物的飛行
關於障礙物的飛行採用了css中的transition來進行控制:
function setTransition(property, timing, speed) {
return {
'transition-property': property,
'-moz-transition-property': property,
'-webkit-transition-property': property,
'-o-transition-property': property,
'transition-timing-function': timing,
'-moz-transition-timing-function': timing,
'-webkit-transition-timing-function': timing,
'-o-transition-timing-function': timing,
'transition-duration': speed + 's',
'-moz-transition-duration': speed + 's',
'-webkit-transition-duration': speed + 's',
'-o-transition-duration': speed + 's',
}
}
Hinder.prototype.draw = function() {
var img = new Image(),
...
img.onload = function() {
...
$(img).css(setTransition('left', 'linear', this.speed))
...
}
}
通過一個較通用的方法返回一個可配置的transition動畫對象,在障礙物類的原型方法中動態改變其css樣式,其中動畫的持續時間設定爲障礙物的速度屬性。這樣來達到每個新生成的障礙物有着不同的飛行速度。
定時清除過期對象
由於整個遊戲會一直跑循環來快速的生成新的目標和障礙物。同時會維護一個存儲目標和障礙物對象的數組,當有需求如碰撞檢測時需要遍歷整個數組。那麼這麼操作就意味着我們的數組不能太大不然會極大地佔用內存。同時當障礙物移出屏幕後理應清理掉這個節點,釋放內存。故作者用兩個定時器來跑兩個檢測對象過期的方法:
function clearTarget() { //清理過期目標
var nowTime = new Date().getTime()
targetArray.forEach(function(item, index) {
if(item.time + item.now < nowTime) {
//當生成對象時間+持續時間 > 當前時間即爲過期
$('#' + item.id).remove()
targetArray.splice(index, 1)
}
})
targetTimer = setTimeout(clearTarget, xxx)
}
function clearHinder() { //清理過期障礙
hinderArray.forEach(function(item, index) {
if(item.shot) {
if('障礙物移出屏幕') {
$('#' + item.id).remove()
hinderArray.splice(index, 1)
}
}
})
setTimeout(clearHinder, xxx)
}
中箭的效果判定
同樣是採用粗略計算,採用弓箭的中心點和目標物/障礙物水平線兩側的點,如下圖的(X2, Y2)的連線,與水平線的夾角來判斷。
從圖上可以看出按照上面的方法來判斷的話。圖中的α角是一個最小角。最大角應是目標對象水平線上左側的點到弓箭中心點連線與水平線的夾角。故作者將計算當前對象與弓箭的夾角方法放到了對象原型中,以便射箭後遍歷對象判斷角度使用。
//yMax, yMin, xMax, xMin 爲方便展示使用。具體數值從上圖可以很快求出
Target.prototype.angle = function() {
var anglemax = Math.atan2(yMax, xMax)
var anglemin = Math.atan2(yMin, xMin)
function angleCal(angle) { //轉化爲°數
return (angle / Math.PI * 180) < 0 ? angle / Math.PI * 180 - 90 : angle / Math.PI * 180
}
var angleMax = angleCal(anglemax)
var angleMmin = angleCal(anglemin)
return {
max: angleMax,
min: angleMmin
}
}
需注意的是裏面使用了Math.atan2(y, x)方法。注意裏面參數順序哦=。=
箭射到對象身上
不知道嘗試了這個遊戲的童鞋有沒有注意到,當點擊了某一個方向後,箭會順着這個方向射出,並且會沿途判斷有沒有射中什麼東西,如果射中了那麼就要停在那個對象身上,沒射中就無所謂了。那麼這一點的實現我們就不能是使用如目標物身上綁定監聽事件來解決了。那麼現在再來看這個圖:
(X1, Y1)爲點擊的位置,沿着這個方向延伸就會延伸到(X2, Y2),那麼應該是射中了,這個時候需要渲染箭到(X2, Y2)這個點。如何實現呢,其實圖中已經有了結果。X2爲當前對象的left + width, X1爲觸摸點的client.X, Y1爲畫布高 - client.Y - 弓箭高/2;那麼結果是不是已經很明瞭了呢。再加上之前的角度我們已經有原型方法計算過,可以通過css中的rotate使得射出的箭改變方向,那麼至此這個需求也算是基本完成。
小結
本次原本打算採用canvas來做這個遊戲,但是初步嘗試之後發現可能由於繪製得太頻繁,手機顯示出來效果實在是。。當然也因爲作者對這種小遊戲的實現經驗不足。只借此分享一個小case,歡迎指正。
最後
慣例po作者的博客,不定時更新中——
有問題歡迎在issues下交流,捂臉求star=。=