回调
本节涵盖收集数据、构建效果函数以及使用 Meter 类触发这些函数的内容。本节不会重点解释动画或效果处理程序的细节,如果您需要复习,请查阅我们的教程或 API 参考部分。
从头到尾有效触发动画的过程是:
- 在更新函数内使用 Meter.setValue(value) 方法将原始计量器数据插入 Meter 类的实例中。
- 如果 Meter 判断自身处于稳定状态,它将激活作为第二个参数传入的回调函数。
- 此回调函数包含评估 Meter 状态的条件逻辑。如果状态为真,您的效果应被添加到效果数组(effects.push(new yourEffect()))或状态处理程序(steMgr.Push(new yourEffect()))。
- 更新函数的每次运行都会评估完整的效果数组和状态处理程序中的最新元素。在此阶段,效果基于状态变量被绘制。
- 绘制后,每个效果都参与检查其生命周期。效果数组中的效果只需迭代设置为类实例的生命周期变量。当生命周期为 0 或更少时,效果在评估效果数组期间通过 splice 轻松删除。状态处理程序中的效果可以在需要时自行删除,因为状态处理程序充当栈,这些效果的条件逻辑往往更为复杂。
计量器(meta 标签)
Section titled “计量器(meta 标签)”此计量器正在查找典型的血量条,并将返回匹配颜色的百分比,值介于 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>Meter 类
Section titled “Meter 类”在这里,我创建了一个名为 healthM 的 Meter 类实例来激活”受到伤害”效果。
<script> // Meter instance var healthM = new Meter(25, healthHelper);
function update () => { healthM.setValue(health); window.requestAnimationFrame(update); }
function healthHelper () => { // "Took Damage" effect up next }
// . . .
</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){ // The lifetime of the effect should be iterated in the draw function. this.lifetime = 200; this.speed = speed; this.direction = direction; this.color = color;
this.draw = () => { // Set fill from parameter ctx.fillStyle = `hsl(${this.color}, 100%, 50%)`; // Check direction if (this.direction == "up"){ ctx.fillRect(0, this.lifetime, width, 30); } else { ctx.fillRect(0, height - this.lifetime, width, 30); } // As the lifetime decreases by 5 each update, the rectangle moves accordingly. this.lifetime -= speed; } }function update() { // Background color ctx.fillStyle = "black"; ctx.fillRect(0, 0, 320, 200);
// Draw all effects in array and remove them if they are done. 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){ // Make sure to correctly set the color and direction paremeters. effects.push(new healthEffect(1, "down", 5)); healthPrev = healthM.value; }}以下是实际效果的显示方式:

得益于函数的可编辑设计,我们也可以轻松创建治疗效果!只需在回调函数中添加一个额外的条件。
function healthHelper () => { if (healthM.decreased && healthM.value != healthPrev){ // Damage effect effects.push(new healthEffect(1, "down", 5)); healthPrev = healthM.value; } if (healthM.increased && healthM.value != healthPrev){ // Healing effect effects.push(new healthEffect(120, "up", 5)); healthPrev = healthM.value; }}
这个效果的简单性是 LightScript 开发者的最大优势。不必创建每个需要数百行代码的自定义设计效果,而是构建小型可重用的效果作为特定触发器的构建块。接下来的三个效果是基于我们刚创建的小效果的变体。随意尝试,看看您能实现什么!



状态处理程序
Section titled “状态处理程序”我个人将状态处理程序保留给具有非常特定状态逻辑的大型复杂效果,特别是当您希望效果优先于所有其他效果运行时。请记住,状态处理程序像栈一样工作,只运行顶部效果;每个状态效果在完成时负责删除自身。此示例演示了一个根据您是否正在搜索而自适应的配对大厅动画。它还包括自己的小效果数组和辅助效果。这里使用的计量器是 inLobby 和 yellowPlayButton,它们仅在配对大厅中激活。yellowPlayButton 根据您是否正在搜索而改变颜色。
function lobbyAnimation(){ // Instance variables this.start = new Date().getTime(); this.elapsed = 0; this.radius = 0; this.searching = 1000; // Instance effects array this.effects = [];
// Process is the method that allows a state effect to remove itself this.Process = function () { // If the lobby menu and play button disappear, remove the effect if (inLobby.decreased && yellowPlayButton.decreased) { stateMgr.Pop(); // Global boolean to prevent duplicate lobby effects in the state handler lobbyAnim = false; } // If the play button is present, edit this.searching based on its color yellowPlayButton.value == 1 ? this.searching = 1000 : this.searching = 100; // Call the Draw function attached to the effect this.Draw(); };
this.Draw = function(){ // Iterate important variables this.elapsed = new Date().getTime() - this.start; this.radius = 50 + (Math.sin(this.elapsed/this.searching)*10);
// Fill the background to overwrite other effects ctx.fillStyle = 'black'; ctx.fillRect(0, 0, 320, 200);
// Draw the main arc ctx.beginPath(); ctx.strokeStyle = `hsl(120, 100%, 50%)`; ctx.lineWidth = 20; ctx.arc(160, 100, this.radius, 0, 2 * Math.PI); ctx.stroke(); // If the main arc is the right size, push helper effects into the helper array if(this.radius > 59 && this.elapsed % 2 == 0){ this.effects.push(new lobbyHelper(160, 100, 69)); } // Play and remove helper effects if necessary 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(){ // Set the global transparency value. 1 is opaque, 0 is invisible. 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(); // Set the global transparency back to 1 so your other effects are opaque ctx.globalAlpha = 1; this.radius++; }}// Declare state handler (found in Snippets)var stateMgr = new StateHandler();// Global boolean to prevent duplicate lobby animationsvar lobbyAnim = false;
function update(){ // Run the Process function to draw the animation stateMgr.Process();}function lobbyAnimationPlay(){ if(inLobby.value == 1 && yellowPlayButton.value == 1 && !lobbyAnim){ stateMgr.Push(new lobbyAnimation()); lobbyAnim = true; }};未搜索中:

搜索中:

这就结束了我们的回调教程,也是官方 SignalRGB 开发演练的结束。请关注我们演练的下一个也是最后一个补充——Lightscript 维护!