技能CD、頭像倒計時這類是遊戲中經常出現的一類效果,而具體如何來實現?
本文將以頭像倒計時爲例來講解說明。先上效果圖:
一、實現方案
1、倒計時進度
涉及倒計時,肯定需要考慮倒計時進度推進的表現。這裏我們使用Sprite精靈組件。平時我們經常使用Sprite來顯示圖像,這裏我們同樣通過Sprite來顯示頭像框圖片,
查看Sprite文檔我們發現Sprite有一個渲染模式Type爲填充類型(Filled),填充模式(Filled)支持RADIAL(扇形填充),正好可以滿足我們想要的進度填充效果。而對於具體的進度值的控制我們則可以通過代碼定時控制Fill Start、Fill Range等屬性值的改變填充進度的表現。UI節點屬性設置如下:
基於上述基本思路,具體邏輯實現我們需要解決兩個問題:
(1)進度計算
其實進度計算到思路很簡單,開始倒計時獲取當前時間戳,定時更新過程中不斷獲取對應到時間斷的時間戳,計算已經執行了的時長cur_step,然後與總時長all_step相比較計算已經填充的百分比,然後來設置Sprite的fillRange即可。
但這地方需要注意的一點是實際應用中考慮到斷線重連等情況,可能需要對當前時長cur_step進行額外處理。這裏就涉及到兩個時間:總時間normal_sces、剩餘時間wait_sces。根據兩者之差即可計算出已經執行了到時長cur_step,然後在定時更新時基於該值去計算新到已執行時長即可。
(2)定時更新
提到定時更新,首先可能想到了開啓一個定時器來不斷刷新,雖然可以實現功能,但此處個人傾向於另外一個實現方式:重複的action動作,即將進度更新邏輯封裝爲一個回調函數,然後整合到cc.repeatForever中,通過頭像精靈對象節點執行runAction來實現。
具體代碼實現:
showCountdown()
{
let wait_sces = 30 * 1000; //剩餘倒計時時間
let normal_sces = 30 * 1000; //總倒計時時間
let bStartWithZero = (wait_sces == normal_sces) ? true : false;
let timer = (new Date()).valueOf();
let cur_step = bStartWithZero ? 0 : (normal_sces - wait_sces);
let all_step = normal_sces;
let cur_step_temp = cur_step;
let ui_countdown_comp = this.ui_countdown.getComponent(cc.Sprite);
ui_countdown_comp.node.color = cc.color(0, 255, 0);
ui_countdown_comp.fillStart = 0;
ui_countdown_comp.fillRange = 1 - (cur_step / all_step);
// 更新邏輯
let call_back = cc.callFunc(() => {
cur_step = bStartWithZero ? (new Date()).valueOf() - timer : (cur_step_temp + (new Date()).valueOf() - timer);
if (cur_step > all_step) {
ui_countdown_comp.node.stopAllActions();
this.ui_countdown.active = false;
this.ui_countdown_particle.active = false;
return;
}
let step_fillrange = cur_step / all_step;
ui_countdown_comp.fillRange = 1 - step_fillrange;
})
// 動作執行
let seq = cc.sequence(cc.delayTime(0.1), call_back);
let repeater = cc.repeatForever(seq);
ui_countdown_comp.node.active = true;
ui_countdown_comp.node.runAction(repeater);
}
2、顏色控制
在實際應用中,倒計時顯示可能會想要達到推進過程中顏色漸變到效果。基於上述倒計時進度的實現,我們只需要在更新邏輯中去改變節點顏色即可。
根據更新進度獲取對應的顏色,具體顏色獲取代碼如下:
getColorByFillRange(fill_range: number) :any
{
let color = cc.Color.WHITE;
if (fill_range < 0.33) {
color = cc.Color.GREEN;
} else if (fill_range < 0.66) {
color = cc.Color.YELLOW;
} else {
color = cc.Color.RED;
}
return color;
}
上述顏色改變是根據0.33、0.66的進度分界點來劃分設置顏色。如果想進一步達到漸變效果,可對顏色值進行計算.具體如下:
getColorByFillRange(fill_range: number) {
let one = 500 / 60
let r = 0
let g = 0
let b = 0
if (fill_range < 0.33) {
r = one * fill_range * 90
g = 255
} else if (fill_range < 0.66) {
r = 255
g = 255 - (fill_range * 90 - 30) * one
} else {
r = 255
}
return cc.color(r, g, b);
}
3、尾巴特效
基於上述倒計時進度、顏色控制,我們基本已經實現了倒計時效果。我們爲了達到更好對效果,這裏考慮添加一個倒計時小尾巴特效。這裏我們通過綁定一個粒子特效節點來實現(具體粒子特效如何製作這裏暫時就不講解了)。有了粒子特效,接下來我們需要考慮如何解決粒子特效進度跟隨效果以及具體達到我們想要對效果。
倒計時填充是沿着頭像框邊框不斷在推進,而粒子特效跟隨移動對軌跡也就是頭像邊框上的一個個點,因爲我們需要來計算對應進度時邊框上對應位置,然後同步更新粒子特效節點對位置即可。
1、粒子特效位置計算
我們知道在直角座標系中,利用三角函數,我們根據長、寬、角度以及角對角線等關係,我們可以很輕鬆等計算得到邊框上等座標點。如下圖所示:
而在我們等頭像倒計時中,頭像進度是RADIAL(扇形填充),會產生一個角度,而根據FillRange我們可以計算出該角度。根據角度、邊框長寬,我們也就可以很容易等計算出進度對應邊框上等座標點,也就是粒子特效子節點在邊框上對位置。
實際中節點位置涉及正、負值等問題,對應等我們自然需要考慮區域劃分來計算。首先想到等可能就是直角座標系象限劃分這種方式,但仔細觀察會發現象限劃分方式隨着進度推進,在某個象限內有時會將象限拆分爲兩種不同對圖形,給計算會造成很大麻煩,因而我們不採取該劃分方式。
我們引入對角線,發現對角線正好可以將矩形劃分爲四個三角形區域,而三角形對某一條邊正好就是軌跡在該區域的路徑,而且對角線構造的夾腳可以通過矩形框很容易計算。這種方式可以很好的進行區域劃分計算。具體如下圖所示:
這裏我們可以前提確定幾個已知量:
(1). 頭像框進度fillrange爲‘fr’,進度走過的角度爲a = PI * 2 * fr。
(2). 對角線與x軸構成的夾腳,根據矩形長(H = height/2)、寬(W = width/2)可以計算A = Math.atan(H / W)
區域-1
角度a範圍: a <= A || a >= PI*2 - A
邊框座標:
x = W
-W * tan(a)
區域-2
角度a範圍: a > A && a >= PI - A
邊框座標:
x = H / tan(a)
y = -H
區域-3
角度a範圍: a > PI-A && a <= PI + A
邊框座標:
x = -W
y = W * tan(a)
區域-4
角度a範圍: a > PI+A && a <= PI*2 - A
邊框座標:
x = H / tan(a)
y = H
2、自定義粒子效果
爲了讓粒子特效達到更好的表現效果,我們可以開啓自定義粒子屬性custom進行相關控制
3、代碼實現
refreshParticle(color:any)
{
let particle: cc.ParticleSystem = this.ui_countdown_particle.getComponent(cc.ParticleSystem);
particle.endColor = new cc.Color(color.getR(), color.getG(), color.getB(), 100);
if (!this.ui_countdown_particle.active) {
this.ui_countdown_particle.active = true
this.particlePos = this.ui_countdown_particle.position;
particle.resetSystem();
}
let ui_countdown_comp = this.ui_countdown.getComponent(cc.Sprite)
let W = this.ui_countdown.width / 2 - 2
let H = this.ui_countdown.height / 2 - 2
let A = Math.atan(H / W)
let x = 0;
let y = 0;
let PI = Math.PI;
let cur_fill = 1 - ui_countdown_comp.fillRange;
let a = PI * 2 * cur_fill;
if (PI*2-A <= a || a <= A) {
x = W
y = -W * Math.tan(a)
}
else if (A < a && a < PI - A) {
x = H / Math.tan(a)
y = -H
}
else if (PI-A <= a && a <= PI+A) {
x = -W
y = W * Math.tan(a)
}
else if (PI+A < a && a < PI*2-A) {
x = -H / Math.tan(a)
y = H
}
this.ui_countdown_particle.x = this.ui_countdown.x + x - 1
this.ui_countdown_particle.y = this.ui_countdown.y + y
//自定義粒子效果--根據座標變化 改變重力
particle.custom = true;
let _gravityX = this.particlePos.x - this.ui_countdown_particle.x;
let _gravityY = this.particlePos.y - this.ui_countdown_particle.y;
this.particlePos = this.ui_countdown_particle.position;
particle.gravity = cc.v2(_gravityX * 50, _gravityY * 50);
}
二、總結
1、UI控件選擇
本文例子中使用的是Sprite控件,使用的其填充類型的渲染模式來進行控制處理。實際上cocos creator中很多UI控件都有這一屬性(例如ProgressBar),也都可以用來實現技能CD、頭像倒計時等效果。使用方法也都大同小異。
2、擴展
本例中使用都是矩形框頭像,如果換成圓形頭像呢?實際上實現也很簡單,同樣利用數學知識,利用角度、圓的半徑等信息量去計算即可。
3、計算控制
對於涉及到計算,尤其是分條件、情況到處理,需要特別注意一些臨界條件到判斷處理。本例中只是做了基本到實現,有些臨界情況可能沒處理,需要使用的自行添加補充處理即可。