Ga naar inhoud

Callbacks

In dit gedeelte wordt behandeld hoe u data verzamelt, effectfuncties bouwt en de Meter-klasse gebruikt om deze functies te activeren. Er wordt geen nadruk gelegd op het uitleggen van animaties of de specifics van onze effect-handlers; als u dat wilt opfrissen, bekijk dan onze secties Tutorials of API-referentie.

Het proces voor het effectief activeren van een animatie van begin tot eind is:

  1. Gebruik de methode Meter.setValue(value) in de update-functie om ruwe meterdata in te voegen in een instantie van uw Meter-klasse.
  2. Als de Meter zichzelf stabiel acht, activeert hij de callbackfunctie die als tweede parameter is meegegeven.
  3. Deze callbackfunctie bevat conditiologica om de toestand van de Meter te evalueren. Als de toestand waar is, moet uw effect worden toegevoegd aan de effecten-array (effects.push(new yourEffect())) of aan de toestandshandler (steMgr.Push(new yourEffect())).
  4. Elke uitvoering van de update-functie evalueert de volledige effecten-array en het laatste element in de toestandshandler. In deze fase worden de effecten getekend op basis van toestandsvariabelen.
  5. Na het tekenen draagt elk effect bij aan het controleren van zijn levensduur. Effecten in de effecten-array itereren eenvoudigweg een levensduurvariabele die is ingesteld op de instantie van hun klasse. Wanneer de levensduur 0 of minder is, wordt het effect eenvoudig verwijderd met een splice tijdens de evaluatie van de effecten-array. Effecten in de toestandshandler kunnen zichzelf verwijderen wanneer nodig, omdat de toestandshandler functioneert als een stapel, en de conditiologica voor deze effecten tends complexer te zijn.

Deze meter zoekt naar een typische gezondheidsbar en retourneert het percentage overeenkomende kleur als een getal tussen 0 en 1. Het HSL-bereik is in dit geval vrij breed om rekening te houden met een verloop in de kleur van de bar. Dit introduceert een probleem - naarmate de gezondheidsbar krimpt, onthult het een transparante achtergrond, die groene elementen uit de speelomgeving kan bevatten die de kleurcontrole doorstaan en onze meter verstoren.

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

Hier heb ik een instantie van onze Meter-klasse gemaakt, genaamd healthM, om een “schade ontvangen”-effect te activeren.

<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>

Ik heb een vrij hoge stabiliteitsvereiste (25) gegeven om rekening te houden met het bovenstaande instabiliteitsprobleem. De bijgevoegde callbackfunctie wordt alleen geactiveerd als de meterwaarde stabiel is gedurende 25 updates, waardoor het meeste data dat niet de eigenlijke gezondheidsbar is wordt uitgesloten, zelfs als de speler stil staat. Het enige nadeel hiervan is de toenemende latentie van het effect, wat altijd de beperkende factor in dit scenario zal zijn. Voor deze specifieke gezondheidsbar kunnen we dit op zijn minst op een paar manieren tegengaan:

  • Wanneer geraakt, wordt een deel van de groene bar direct omgezet naar rood voordat het krimpt naar het nieuwe niveau van groene gezondheid. Als we een meter healthRed toevoegen die rood zoekt op dezelfde plek als de gezondheidsmeter, kunnen we hun waarden combineren voor snellere activering. Een conditie if ( healthM.decreased && healthRed.increased ) zou in dit geval een goede nauwkeurigheid moeten bieden.
  • We kunnen ook de metingen van meerdere lineaire meters met dezelfde kleurinstellingen combineren. Ik heb momenteel één lineaire meter ingesteld in het midden van de gezondheidsbar. Als ik er twee meer instelde, een iets boven en onder het origineel maar nog steeds binnen de gezondheidsbar, kan de nabijheid van de drie meterwaarden worden gebruikt als een extra stabiliteitsscontrole. Alle drie meters zouden Meter.decreased moeten lezen en binnen een klein bereik van elkaars waarden moeten liggen om het effect te activeren.

Deze callbackfunctie wordt alleen uitgevoerd nadat de meter heeft geverifieerd dat alle elementen in zijn waardenarray gelijk zijn. Dit zorgt ervoor dat de meterwaarden zijn gebaseerd op stabiele data. Binnen de callback gebruik ik een conditie om te bepalen of het effect moet worden afgespeeld.

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

De conditie vereist twee ware voorwaarden: een stabiele afname in de meter en een mismatch tussen de huidige meterwaarde en een eerder vastgelegde waarde. Ik heb deze tweede voorwaarde toegevoegd om een bug te vermijden. Als we alleen op een afname zouden controleren, zou het effect herhaaldelijk worden afgespeeld. Omdat Meter.decreased wordt ingesteld zodra de meter stabiliseert, blijft de waarde ervan hetzelfde zolang de meter niet verandert. Op deze manier speelt het effect alleen als er een afname is die verschilt van de laatste stabiele waarde.

Ik maak twee effecten voor u: een voor de effecten-array en een voor de toestandshandler.

Dit eerste codeblok definieert een effectfunctie bedoeld voor de effecten-array. Het is ontworpen om snel en lichtgewicht te zijn om meerdere kleine effecten gelijktijdig te laten uitvoeren. Om de systeembelasting te verminderen, heb ik het effect ook eenvoudig aanpasbaar gemaakt via parameters voor hergebruik.

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;
}
}

Zo ziet het er in actie uit:

Dankzij het aanpasbare ontwerp van de functie kunnen we ook eenvoudig een genezingseffect maken! Voeg gewoon een extra conditie toe aan de callbackfunctie.

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;
}
}

De eenvoud van dit effect is het grootste voordeel voor een LightScript-ontwikkelaar. In plaats van aangepaste designer-effecten te maken, elk met honderden regels code, bouwt u kleine, herbruikbare effecten die dienen als bouwstenen voor specifieke triggers. De volgende drie effecten zijn variaties op basis van het kleine effect dat we zojuist hebben gemaakt. Experimenteer gerust en kijk wat u kunt bereiken!

Ik reserveer de toestandshandler persoonlijk voor grote, complexe effecten met zeer specifieke toestandslogica, vooral wanneer u wilt dat het effect prioriteit heeft boven alle andere. Houd er rekening mee dat de toestandshandler werkt als een stapel en alleen het bovenste effect uitvoert; elk toestandseffect is verantwoordelijk voor het verwijderen van zichzelf wanneer het klaar is. Dit voorbeeld demonstreert een matchmaking-lobby-animatie die zich aanpast op basis van of u aan het zoeken bent of niet. Het bevat ook zijn eigen kleine effecten-array met een hulpeffect. De hier gebruikte meters zijn inLobby en yellowPlayButton, die alleen activeren in de matchmaking-lobby. De yellowPlayButton verandert van kleur afhankelijk van of u aan het zoeken bent.

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 animations
var 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;
}
};

Niet aan het zoeken:

Aan het zoeken:

En daarmee sluiten we onze callbacks-tutorial af, evenals de officiële SignalRGB-dev-walkthrough. Houd de volgende en laatste toevoeging aan onze walkthrough in de gaten: Lightscript-onderhoud!