Обратные вызовы
В этом разделе рассматриваются сбор данных, создание функций эффектов и использование класса Meter для запуска этих функций. Здесь не будет подробных объяснений анимаций или специфики обработчиков эффектов — при необходимости обратитесь к разделам Обучение или Справочник API.
Процесс эффективного запуска анимации от начала до конца выглядит следующим образом:
- Используйте метод Meter.setValue(value) внутри функции обновления для передачи необработанных данных счётчика в экземпляр класса Meter.
- Если Meter оценивает себя как стабильный, он активирует функцию обратного вызова, переданную вторым параметром.
- Эта функция обратного вызова содержит условную логику для оценки состояния Meter. Если состояние истинно, ваш эффект должен быть добавлен в массив эффектов (effects.push(new yourEffect())) или в обработчик состояний (steMgr.Push(new yourEffect())).
- При каждом запуске функции обновления оценивается весь массив эффектов и последний элемент в обработчике состояний. На этом этапе эффекты отрисовываются на основе переменных состояния.
- После отрисовки каждый эффект участвует в проверке своего жизненного срока. Эффекты в массиве эффектов просто итерируют переменную жизненного срока, заданную для экземпляра их класса. Когда жизненный срок равен 0 или меньше, эффект легко удаляется с помощью splice при оценке массива эффектов. Эффекты в обработчике состояний могут удалять себя самостоятельно, поскольку обработчик состояний функционирует как стек, и условная логика для таких эффектов, как правило, более сложная.
Счётчик (meter)
Заголовок раздела «Счётчик (meter)»Этот счётчик отслеживает типичную полосу здоровья и возвращает процент совпадающих пикселей в виде числа от 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
Заголовок раздела «Класс Meter»Здесь создаётся экземпляр класса Meter с именем healthM для активации эффекта «получен урон».
<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. Вместо создания эксклюзивных дизайнерских эффектов, каждый из которых требует сотен строк кода, создайте небольшие многократно используемые эффекты, служащие строительными блоками для конкретных триггеров. Следующие три эффекта — это вариации на основе только что созданного небольшого эффекта. Экспериментируйте и смотрите, чего вы можете достичь!



Обработчик состояний
Заголовок раздела «Обработчик состояний»Обработчик состояний лучше всего использовать для больших, сложных эффектов с очень специфической логикой состояния, особенно когда нужно, чтобы эффект имел приоритет над всеми остальными. Помните, что обработчик состояний работает как стек и выполняет только верхний эффект; каждый эффект состояния сам отвечает за своё удаление. Этот пример демонстрирует анимацию лобби матчмейкинга, адаптирующуюся в зависимости от того, идёт ли поиск матча. Он также включает собственный небольшой массив эффектов со вспомогательным эффектом. Счётчики 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!