Перейти к содержимому

Фрагменты кода

Этот раздел содержит три полезных фрагмента кода для LightScript: счётчики, обработчики состояний и обработчики эффектов.

Как продолжение предыдущего раздела, класс Meter предназначен для отслеживания значимых изменений в нестабильных данных счётчиков. Думайте о нём как о педали тормоза для вашего кода — он обеспечивает стабильность даже в сложных зонах. Каждый счётчик хранит настраиваемый массив значений, собранных во время каждого запуска функции обновления. Когда все значения в массиве одинаковы (то есть данные «стабильны»), счётчик обновляет своё значение и запускает связанную функцию обратного вызова для активации эффекта.

МетодОписание
Meter(size, callback)Конструктор принимает size и callback.\n\n\n\nsize — количество раз, которое одинаковое значение должно присутствовать, чтобы значение счётчика считалось достаточно «стабильным» для обработки. Большее значение даёт большую точность, меньшее — меньшую задержку.\n\n\ncallback — функция, вызываемая при стабилизации и изменении значения счётчика.
Meter.setValue(value)Вызывайте setValue(value) каждый кадр или каждый раз, когда доступны новые необработанные данные для этого счётчика.

Процесс сбора данных экрана должен в целом соответствовать следующей структуре:

  1. Значение engine.vision.meter обновляется на основе новых данных экрана.
  2. Внутри функции обновления вызывается Meter.setValue(engine.vision.meter) для записи нового значения.
  3. Meter проверяет свой внутренний массив — если все значения идентичны, он обновляет своё состояние и запускает назначенную функцию обратного вызова.
  4. Функция обратного вызова затем оценивает конкретные условия с использованием переменных Meter и выполняет код, если условия выполнены.

ВАЖНО:

Не пропускайте использование обратных вызовов. Поскольку функция обновления выполняется непрерывно, она должна оставаться максимально эффективной. Её основная роль — обновлять значения Meter. Избегайте цепочек условных операторов внутри update для проверки счётчиков и запуска эффектов — это приводит к ненужным проверкам при каждом цикле. Вместо этого определяйте эффекты в отдельных функциях и передавайте их в Meter как обратные вызовы.

При стабилизации Meter предоставляет несколько значений своей функции обратного вызова:

ПеременныеОписание
Meter.valueТекущее значение счётчика.
Meter.increasedБулево значение, указывающее, увеличилось ли значение при этом обновлении
Meter.decreasedБулево значение, указывающее, уменьшилось ли значение при этом обновлении
Meter.diffАбсолютное значение изменения, которое претерпел счётчик при этом обновлении.

Вот базовая настройка с одним счётчиком, включая код класса Meter:

<head>
<title>Meter Example</title>
<meta description="Step-by-step metering" />
<meta meter="health" tags="vlc,fortnite" x= ".05" y=".9" width=".189" h="70-140" s="40-100" l="40-100" type="linear" />
</head>
<body style="margin: 0; padding: 0; background: #000;">
<canvas id="exCanvas" width="320" height="200"></canvas>
</body>
<script>
var c = document.getElementById("exCanvas");
var ctx = c.getContext("2d");
// Initialize meter
var healthMeter = new Meter(5, healthEffect);
function update(){
// Update Meter values
healthMeter.setValue(engine.vision.health);
window.requestAnimationFrame(update);
}
function healthEffect(){
if(healthMeter.increased){
// Trigger effect
} else if (healthMeter.value == 0){
// Trigger effect
} else if (healthMeter.diff > .3){
// Trigger effect
}
}
function Meter(size, callback) {
this.size = size;
this.value = 0;
this.diff = 0;
this.increased = false;
this.decreased = false;
var values = [];
this.setValue = function (updatedValue) {
// Add and shift.
values.push(updatedValue);
if (values.length > this.size) {
values.shift();
}
// Exit early if we don't have a long-term match.
for (var i = 0; i < values.length - 1; i++) {
if (values[i] !== values[i + 1]) return;
}
// We got here, so we've got a matching value collection. Set variables and execute callback.
if (this.value !== values[0]) {
this.diff = Math.abs(this.value - values[0]);
this.increased = this.value < values[0];
this.decreased = this.value > values[0];
this.value = values[0];
callback();
}
};
}
window.requestAnimationFrame(update);
</script>
<head>
<title>Meter Example</title>
<meta description="Step-by-step metering" />
<meta meter="health" tags="vlc,fortnite" x= ".05" y=".9" width=".189" h="70-140" s="40-100" l="40-100" type="linear" />
</head>
<body style="margin: 0; padding: 0; background: #000;">
<canvas id="exCanvas" width="320" height="200"></canvas>
</body>
<script>
var c = document.getElementById("exCanvas");
var ctx = c.getContext("2d");
// Initialize meter
var healthMeter = new Meter(5, healthEffect);
function update(){
// Update Meter values
healthMeter.setValue(engine.vision.health);
window.requestAnimationFrame(update);
}
function healthEffect(){
if(healthMeter.increased){
// Trigger effect
} else if (healthMeter.value == 0){
// Trigger effect
} else if (healthMeter.diff > .3){
// Trigger effect
}
}
function Meter(size, callback) {
this.size = size;
this.value = 0;
this.diff = 0;
this.increased = false;
this.decreased = false;
var values = [];
this.setValue = function (updatedValue) {
// Add and shift.
values.push(updatedValue);
if (values.length > this.size) {
values.shift();
}
// Exit early if we don't have a long-term match.
for (var i = 0; i < values.length - 1; i++) {
if (values[i] !== values[i + 1]) return;
}
// We got here, so we've got a matching value collection. Set variables and execute callback.
if (this.value !== values[0]) {
this.diff = Math.abs(this.value - values[0]);
this.increased = this.value < values[0];
this.decreased = this.value > values[0];
this.value = values[0];
callback();
}
};
}
window.requestAnimationFrame(update);
</script>
function Meter(size, callback) {
this.size = size;
this.value = 0;
this.diff = 0;
this.increased = false;
this.decreased = false;
var values = [];
this.setValue = function (updatedValue) {
// Add and shift.
values.push(updatedValue);
if (values.length > this.size) {
values.shift();
}
// Exit early if we don't have a long-term match.
for (var i = 0; i < values.length - 1; i++) {
if (values[i] !== values[i + 1]) return;
}
// We got here, so we've got a matching value collection. Set variables and execute callback.
if (this.value !== values[0]) {
this.diff = Math.abs(this.value - values[0]);
this.increased = this.value < values[0];
this.decreased = this.value > values[0];
this.value = values[0];
callback();
}
};
}

Обработчик состояний — это стек эффектов, специально предназначенный для обработки эффектов, имеющих приоритет над всеми остальными. Если вам нужно, чтобы одна большая доминирующая анимация выполнялась без прерывания до завершения, поместите её в обработчик состояний.

МетодОписание
StateHandler()Конструктор не требует аргументов.
StateHandler.push(new State())Можно поместить новое состояние в стек с помощью push — обработка начнётся немедленно.
StateHandler.pop()Извлекает текущее состояние из стека и начинает обработку предыдущего состояния.

Любой объект, помещённый в обработчик состояний, обязан реализовывать Process(). Функция Process определяет жизненный срок эффекта в обработчике.

Ниже показан базовый обработчик состояний вместе с функцией Process, которая должна выполняться внутри функций эффектов:

var stateHdlr = new StateHandler();
function StateHandler() {
var stack = [];
var state = null;
// Set current state to the top item in the stack
var updateState = function () {
if (stack.length > 0) {
state = stack[stack.length - 1];
} else {
state = null;
}
};
// Allows dev to add effect to state handler
this.Push = function (newState) {
stack.push(newState);
updateState();
};
// Allows dev to remove effect from handler
this.Pop = function () {
stack.pop();
updateState();
};
// Call the Process function of the current state (effect)
this.Process = function () {
if (state != null) {
state.Process();
}
};
}
function update(){
stateHdlr.Process();
}
this.start = new Date.now()
this.elapsed = 0;
this.duration = 1000;
this.Process = function () {
// Evaluate time since effect start
this.elapsed = new Date.now() - this.start;
// If the effect has reached its lifespan, remove it from the state handler
if (this.elapsed > this.duration) {
stateMgr.Pop();
}
// If the effect is still running, call the effect draw function
this.Draw();
};

Обработчики эффектов отлично подходят для хранения и одновременного рендеринга небольших лёгких эффектов. Каждый эффект в обработчике оценивается при каждом вызове update и удаляется по достижении конца своего жизненного срока.

Обработчик эффектов — это просто массив для хранения эффектов. Это всё.

// Declare effects array
let effects = [];
// (Not shown) Evaluate Meter, callback function pushes effect into effects array
effects.push(new SpecialEffect());
// Iterate through the array, animating a frame for each effect
for (let i = 0; i < effects.length; i++) {
effects[i].draw();
// Remove effect if the lifetime has ended
if (effects[i].lifetime <= 0) {
effects.splice(i, 1);
}
}

Обработчик состояний предназначен специально для приоритетных эффектов, что означает необходимость давать ему приоритет при выполнении. В функции update обрабатывайте обработчик эффектов СНАЧАЛА, а затем обработчик состояний. Это отрисует эффект состояния поверх элементов обработчика эффектов.

Вот так:

function update(){
// Evaluate effects handler
for (let i = 0; i < effects.length; i++) {
effects[i].draw();
if (effects[i].lifetime <= 0) {
effects.splice(i, 1);
}
}
// THEN evaluate state handler
stateMgr.Process();
}

Сохранение USB-данных — простой процесс, но драйверы, используемые этими программами, известны конфликтами с определёнными системами, иногда отключая USB-порты до удаления программного обеспечения. Распространённое решение — отключить «Безопасную загрузку» в настройках BIOS/UEFI. Перед этим настоятельно рекомендуется иметь точку восстановления системы на флэш-накопителе или возможность удалённого доступа к компьютеру без ввода данных при загрузке. Это важно в случае, если отключение безопасной загрузки не решит проблему и вы не сможете получить доступ к системе для удаления программы. В таком случае может потребоваться обновление Windows для удаления всех установленных программ при сохранении личных файлов. Вы делаете это на свой страх и риск. Если вы не чувствуете уверенности — запросите ваше устройство здесь. Не используйте ноутбук для этого процесса.

После установки процесс прост и перехватит все данные, проходящие через USB-устройства. Не вводите конфиденциальную информацию во время захвата, если планируете делиться данными, так как нажатия клавиш включаются в запись. Две сторонние программы: Usblyzer(бесплатная версия) и Wireshark (бесплатно). Обе работают, но при выборе Wireshark убедитесь, что выбран USBPcap.

Ознакомьтесь со следующей страницей — Обратные вызовы — для более подробной информации о создании обратных вызовов счётчиков и функций эффектов.