Callbacks
Esta seção aborda a coleta de dados, a criação de funções de efeito e o uso da classe Meter para acionar essas funções. Não haverá ênfase em explicar animações ou os detalhes específicos dos nossos manipuladores de efeito, então se precisar se atualizar, recomendo conferir nossas seções de Tutoriais ou Referência da API.
O processo para acionar uma animação de forma eficaz do início ao fim é:
- Use o método Meter.setValue(value) dentro da função update para inserir dados brutos do medidor em uma instância da sua classe Meter.
- Se o Meter se julgar estável, ativará a função de callback passada como seu segundo parâmetro.
- Essa função de callback contém lógica condicional para avaliar o estado do Meter. Se o estado for verdadeiro, seu efeito deve ser adicionado ao array de efeitos (effects.push(new yourEffect())) ou ao manipulador de estado (steMgr.Push(new yourEffect())).
- Cada execução da função update avalia o array completo de efeitos e o elemento mais recente no manipulador de estado. Nesta etapa, os efeitos são desenhados com base nas variáveis de estado.
- Após serem desenhados, cada efeito contribui para verificar seu tempo de vida. Efeitos no array de efeitos simplesmente iteram uma variável de tempo de vida definida na instância de sua classe. Quando o tempo de vida é 0 ou menos, o efeito é facilmente removido com um splice durante a avaliação do array de efeitos. Efeitos no manipulador de estado podem se remover quando necessário porque o manipulador de estado funciona como uma pilha, e a lógica condicional para esses efeitos tende a ser mais complexa.
O medidor
Seção intitulada “O medidor”Este medidor está procurando uma barra de vida típica e retornará a porcentagem de cor correspondente como um número entre 0 e 1. O intervalo HSL é bastante amplo neste caso para contabilizar um gradiente na coloração da barra. Isso introduz um problema — conforme a barra de vida diminui, ela revela um fundo transparente, que pode incluir elementos verdes do ambiente do jogo que passam na verificação de cor e distorcem o nosso medidor.
<head> <meta meter="health" tags="example" x= ".05" y=".9" width=".189" h="70-140" s="40-100" l="40-100" type="linear"/></head>O Meter
Seção intitulada “O Meter”Aqui, criei uma instância da nossa classe Meter chamada healthM para ativar um efeito de “sofreu dano”.
<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>Dei um requisito de estabilidade bem alto (25) para contabilizar o problema de instabilidade acima. A função de callback associada só será ativada se o valor do medidor for estável por 25 atualizações, o que excluirá a maioria dos dados que não sejam a barra de vida real, mesmo que o jogador esteja parado. A única desvantagem disso é a crescente latência do efeito, que sempre será o fator limitante nesse cenário. Para essa barra de vida específica, podemos contornar isso de pelo menos duas maneiras:
- Quando atingido, parte da barra verde é instantaneamente convertida para vermelho antes de diminuir para o novo nível de vida verde. Se adicionarmos um medidor healthRed buscando vermelho no mesmo lugar que o medidor de vida, podemos combinar seus valores para um acionamento mais rápido. Um condicional if ( healthM.decreased && healthRed.increased ) deve fornecer boa precisão neste caso.
- Também podemos combinar as leituras de vários medidores lineares com as mesmas configurações de cor. Atualmente tenho um medidor linear configurado no meio da barra de vida. Se eu configurar mais dois, um levemente acima e outro abaixo do original, mas ainda dentro da barra de vida, a proximidade dos três valores de medidor pode ser usada como uma verificação de estabilidade adicional. Os três medidores teriam que ler Meter.decreased e estar dentro de um pequeno intervalo dos valores uns dos outros para acionar o efeito.
O Callback
Seção intitulada “O Callback”Esta função de callback só é executada depois que o medidor verificou que todos os elementos em seu array de valores são iguais. Isso garante que os valores do medidor sejam baseados em dados estáveis. Dentro do callback, usarei uma instrução condicional para determinar se o efeito deve ser reproduzido.
let healthPrev = 0;
function healthHelper () => { if (healthM.decreased && healthM.value != healthPrev){ effects.push(new healthEffect()); healthPrev = healthM.value; }}A instrução condicional requer duas condições verdadeiras: uma diminuição estável no medidor e uma incompatibilidade entre o valor atual do medidor e um valor previamente registrado. Adicionei essa segunda condição para evitar um bug. Se verificássemos apenas uma diminuição, o efeito se repetiria indefinidamente. Como Meter.decreased é definido uma vez que o medidor se estabiliza, seu valor permanece o mesmo enquanto o medidor não muda. Dessa forma, o efeito só será reproduzido se houver uma diminuição diferente do último valor estável.
O Efeito
Seção intitulada “O Efeito”Vou criar dois efeitos: um para o array de efeitos e outro para o manipulador de estado.
Array de Efeitos
Seção intitulada “Array de Efeitos”Este primeiro bloco de código define uma função de efeito destinada ao array de efeitos. Ela foi projetada para ser rápida e leve, permitindo que múltiplos efeitos menores sejam executados simultaneamente. Para reduzir a carga do sistema, também tornei o efeito fácil de personalizar por meio de parâmetros para reutilização.
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; }}Veja como deve aparecer em ação:

Graças ao design editável da função, podemos criar um efeito de cura também! Basta adicionar um condicional adicional à função de callback.
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; }}
A simplicidade deste efeito é sua maior vantagem para um desenvolvedor de LightScript. Em vez de criar efeitos de designer personalizados, cada um exigindo centenas de linhas de código, crie efeitos pequenos e reutilizáveis que sirvam como blocos de construção para gatilhos específicos. Os próximos três efeitos são variações baseadas no pequeno efeito que acabamos de criar. Sinta-se à vontade para experimentar e ver o que você consegue alcançar!



Manipulador de Estado
Seção intitulada “Manipulador de Estado”Pessoalmente, reservo o manipulador de estado para efeitos grandes e complexos com lógica de estado muito específica, especialmente quando você quer que o efeito tenha prioridade sobre todos os outros. Tenha em mente que o manipulador de estado funciona como uma pilha e apenas executa o efeito do topo; cada efeito de estado é responsável por se remover quando terminar. Este exemplo demonstra uma animação de lobby de matchmaking que se adapta com base em se você está buscando uma partida ou não. Ele também inclui seu próprio pequeno array de efeitos com um efeito auxiliar. Os medidores usados aqui são inLobby e yellowPlayButton, que só ativam no lobby de matchmaking. O yellowPlayButton muda de cor dependendo de se você está buscando.
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; }};Sem Buscar:

Buscando:

E com isso concluímos nosso tutorial de callbacks, bem como o walkthrough oficial de desenvolvimento do SignalRGB. Fique atento à próxima e última adição ao nosso walkthrough, Manutenção de Lightscripts!