跳到內容

回呼函式

本節介紹收集資料、建立特效函式,以及使用 Meter 類別觸發這些函式。本節不會著重說明動畫或特效處理器的細節,如果您需要複習,我建議查看我們的教學API 參考章節。

從頭到尾有效觸發動畫的過程是:

  1. 在 update 函式內使用 Meter.setValue(value) 方法,將原始儀錶資料插入 Meter 類別的實例。
  2. 如果 Meter 判斷自己處於穩定狀態,它將啟動作為其第二個參數傳入的回呼函式。
  3. 此回呼函式包含條件邏輯以評估 Meter 的狀態。如果狀態為真值,您的特效應被添加到特效陣列(effects.push(new yourEffect()))或狀態處理器(steMgr.Push(new yourEffect()))。
  4. update 函式的每次執行都會評估完整的特效陣列和狀態處理器中的最新元素。在這個階段,特效根據狀態變數繪製。
  5. 繪製後,每個特效都參與檢查其生命週期。特效陣列中的特效只需迭代設定為其類別實例的生命週期變數。當生命週期為 0 或更少時,可以在評估特效陣列時輕鬆使用 splice 移除該特效。狀態處理器中的特效可以在需要時自行移除,因為狀態處理器像堆疊一樣運作,這些特效的條件邏輯往往更為複雜。

此儀錶尋找典型的血條,並將匹配顏色的百分比作為 0 到 1 之間的數字返回。在這種情況下,HSL 範圍相當寬以考慮血條顏色中的漸層。這引入了一個問題——隨著血條縮小,它實際上揭示了一個透明背景,可能包含來自遊戲環境的綠色元素,這些元素通過顏色檢查並干擾我們的儀錶。

<head>
<meta meter="health" tags="example" x= ".05" y=".9" width=".189" h="70-140" s="40-100" l="40-100" type="linear"/>
</head>

在這裡,我建立了一個名為 healthMMeter 類別實例來啟動「受到傷害」特效。

<script>
// Meter 實例
var healthM = new Meter(25, healthHelper);
function update () => {
healthM.setValue(health);
window.requestAnimationFrame(update);
}
function healthHelper () => {
// 「受到傷害」特效見下
}
// . . .
</script>

我設定了相當高的穩定性要求(25)以應對上述不穩定問題。附加的回呼函式只有在儀錶值穩定 25 次更新後才會啟動,這將排除大多數不是實際血條的資料,即使玩家靜止不動。這樣做的唯一缺點是特效的增長延遲,這在這種情況下始終是限制因素。對於這個特定的血條,我們可以用至少幾種方式來解決這個問題:

  • 受到攻擊時,部分綠色血條會在縮減到新的綠色血量水平之前立即轉為紅色。如果我們在血條相同位置添加一個尋找紅色的儀錶 healthRed,我們可以結合它們的值以更快觸發。在這種情況下,if ( healthM.decreased && healthRed.increased ) 的條件判斷應能提供良好的準確性。
  • 我們也可以組合幾個具有相同顏色設定的線性儀錶的讀數。我目前在血條中間設置了一個線性儀錶。如果我再設置兩個,一個在原始位置稍微上方,一個稍微下方,但仍在血條內,三個儀錶值的接近程度可以用作額外的穩定性檢查。所有三個儀錶必須讀取 Meter.decreased 且彼此的值在一個小範圍內才能觸發特效。

此回呼函式只有在儀錶驗證其值陣列中的所有元素都相等之後才執行。這確保儀錶值基於穩定的資料。在回呼內部,我將使用條件語句來確定特效是否應該播放。

let healthPrev = 0;
function healthHelper () => {
if (healthM.decreased && healthM.value != healthPrev){
effects.push(new healthEffect());
healthPrev = healthM.value;
}
}

條件語句需要兩個真值條件:儀錶的穩定降低,以及當前儀錶值與先前記錄值之間的不匹配。我添加這第二個條件是為了避免一個錯誤。如果我們只檢查降低,特效將重複播放。由於 Meter.decreased儀錶穩定後設定,只要儀錶不改變,其值保持不變。這樣,特效只有在出現與上次穩定值不同的降低時才會播放。

我將為您建立兩個特效:一個放入特效陣列,另一個放入狀態處理器。

第一個程式碼區塊定義了一個用於特效陣列的特效函式。它設計為快速且輕量,允許多個小特效同時執行。為了減少系統負載,我還透過參數使特效易於自訂以便重用。

function healthEffect(color, direction, speed){
// 特效的生命週期應在繪製函式中迭代。
this.lifetime = 200;
this.speed = speed;
this.direction = direction;
this.color = color;
this.draw = () => {
// 從參數設定填充
ctx.fillStyle = `hsl(${this.color}, 100%, 50%)`;
// 檢查方向
if (this.direction == "up"){
ctx.fillRect(0, this.lifetime, width, 30);
} else {
ctx.fillRect(0, height - this.lifetime, width, 30);
}
// 隨著生命週期每次更新減少 5,矩形相應移動。
this.lifetime -= speed;
}
}
function update() {
// 背景顏色
ctx.fillStyle = "black";
ctx.fillRect(0, 0, 320, 200);
// 繪製陣列中的所有特效,完成後移除。
effects.forEach((ele, i) => {
ele.draw();
if (ele.lifetime == 0){
effects.splice(i, 1);
}
})
window.requestAnimationFrame(update);
}
function healthHelper () => {
if (healthM.decreased && healthM.value != healthPrev){
// 確保正確設定顏色和方向參數。
effects.push(new healthEffect(1, "down", 5));
healthPrev = healthM.value;
}
}

以下是實際效果:

得益於函式的可編輯設計,我們也可以輕鬆建立治療特效!只需在回呼函式中添加額外的條件即可。

function healthHelper () => {
if (healthM.decreased && healthM.value != healthPrev){
// 受傷特效
effects.push(new healthEffect(1, "down", 5));
healthPrev = healthM.value;
}
if (healthM.increased && healthM.value != healthPrev){
// 治療特效
effects.push(new healthEffect(120, "up", 5));
healthPrev = healthM.value;
}
}

這個特效的簡單性是其對 LightScript 開發者最大的優勢。不需要建立各自需要數百行程式碼的自訂設計師特效,而是建立小型、可重用的特效作為特定觸發器的構建模組。接下來的三個特效是基於我們剛建立的小特效的變體。請隨意試驗,看看您能實現什麼!

我個人將狀態處理器保留給具有非常特定狀態邏輯的大型複雜特效,特別是當您希望特效優先於所有其他特效時。請記住,狀態處理器像堆疊一樣工作,只執行頂部的特效;每個狀態特效負責在完成時移除自己。此範例示範了一個根據您是否正在搜尋而調整的對戰大廳動畫。它還包含其自己的小特效陣列和輔助特效。這裡使用的儀錶是 inLobbyyellowPlayButton,它們只在對戰大廳中啟動。yellowPlayButton 根據您是否正在搜尋而改變顏色。

function lobbyAnimation(){
// 實例變數
this.start = new Date().getTime();
this.elapsed = 0;
this.radius = 0;
this.searching = 1000;
// 實例特效陣列
this.effects = [];
// Process 是允許狀態特效移除自己的方法
this.Process = function () {
// 如果大廳選單和開始按鈕消失,移除特效
if (inLobby.decreased && yellowPlayButton.decreased) {
stateMgr.Pop();
// 全域布林值以防止狀態處理器中出現重複的大廳特效
lobbyAnim = false;
}
// 如果開始按鈕存在,根據其顏色編輯 this.searching
yellowPlayButton.value == 1 ? this.searching = 1000 : this.searching = 100;
// 呼叫附加到特效的 Draw 函式
this.Draw();
};
this.Draw = function(){
// 迭代重要變數
this.elapsed = new Date().getTime() - this.start;
this.radius = 50 + (Math.sin(this.elapsed/this.searching)*10);
// 填充背景以覆寫其他特效
ctx.fillStyle = 'black';
ctx.fillRect(0, 0, 320, 200);
// 繪製主弧形
ctx.beginPath();
ctx.strokeStyle = `hsl(120, 100%, 50%)`;
ctx.lineWidth = 20;
ctx.arc(160, 100, this.radius, 0, 2 * Math.PI);
ctx.stroke();
// 如果主弧形大小合適,將輔助特效推入輔助陣列
if(this.radius > 59 && this.elapsed % 2 == 0){
this.effects.push(new lobbyHelper(160, 100, 69));
}
// 播放並在必要時移除輔助特效
this.effects.forEach((e, i) => {
ctx.lineWidth = 1;
e.draw();
if (e.radius > 360) {
this.effects.splice(i, 1);
}
});
}
}
function lobbyHelper(x, y, radius){
this.x = x;
this.y = y;
this.radius = radius;
this.draw = function(){
// 設定全域透明度值。1 為不透明,0 為不可見。
ctx.globalAlpha = 1 - this.radius/360;
ctx.beginPath();
ctx.strokeStyle = `hsl(120, 100%, ${50 + (this.radius/7)}%)`;
ctx.arc(this.x, this.y, this.radius, 0, 2 * Math.PI);
ctx.stroke();
// 將全域透明度設回 1,使其他特效不透明
ctx.globalAlpha = 1;
this.radius++;
}
}
// 宣告狀態處理器(見 Snippets)
var stateMgr = new StateHandler();
// 防止重複大廳動畫的全域布林值
var lobbyAnim = false;
function update(){
// 執行 Process 函式以繪製動畫
stateMgr.Process();
}
function lobbyAnimationPlay(){
if(inLobby.value == 1 && yellowPlayButton.value == 1 && !lobbyAnim){
stateMgr.Push(new lobbyAnimation());
lobbyAnim = true;
}
};

未搜尋中:

搜尋中:

這就是我們的回呼函式教學的結尾,也是官方 SignalRGB 開發者教學的結尾。請留意我們教學的下一個也是最後一個部分,Lightscript 維護