ข้ามไปยังเนื้อหา

Callbacks

ส่วนนี้ครอบคลุมการรวบรวมข้อมูล การสร้างฟังก์ชันเอฟเฟกต์ และการใช้คลาส Meter เพื่อเรียกใช้ฟังก์ชันเหล่านั้น เราจะไม่เน้นอธิบายเรื่องแอนิเมชันหรือรายละเอียดของตัวจัดการเอฟเฟกต์ ดังนั้นหากคุณต้องการทบทวน แนะนำให้ดูส่วน บทช่วยสอน หรือ เอกสารอ้างอิง API ของเรา

ขั้นตอนในการเรียกใช้แอนิเมชันอย่างมีประสิทธิภาพตั้งแต่ต้นจนจบมีดังนี้:

  1. ใช้เมธอด Meter.setValue(value) ภายในฟังก์ชัน update เพื่อใส่ข้อมูลมิเตอร์ดิบลงในอินสแตนซ์ของคลาส Meter ของคุณ
  2. หาก Meter ประเมินว่าตัวเองมีความเสถียร ก็จะเรียกใช้ฟังก์ชัน callback ที่ส่งเข้ามาเป็นพารามิเตอร์ตัวที่สอง
  3. ฟังก์ชัน callback นี้มีตรรกะเงื่อนไขเพื่อประเมินสถานะของ Meter หากสถานะเป็นจริง (truthy) ควรเพิ่มเอฟเฟกต์ของคุณลงในอาร์เรย์ effects (effects.push(new yourEffect())) หรือตัวจัดการสถานะ (steMgr.Push(new yourEffect()))
  4. การทำงานของฟังก์ชัน update แต่ละครั้งจะประเมินอาร์เรย์ effects ทั้งหมดและสมาชิกล่าสุดในตัวจัดการสถานะ ในขั้นนี้ เอฟเฟกต์จะถูกวาดตามตัวแปรสถานะ
  5. หลังจากวาดแล้ว แต่ละเอฟเฟกต์จะมีส่วนในการตรวจสอบอายุการทำงานของตัวเอง เอฟเฟกต์ในอาร์เรย์ effects เพียงลดค่าตัวแปรอายุการทำงานที่ผูกกับอินสแตนซ์ของคลาส เมื่ออายุการทำงานเป็น 0 หรือน้อยกว่า ก็สามารถลบเอฟเฟกต์ออกได้ง่าย ๆ ด้วย splice ระหว่างการประเมินอาร์เรย์ effects ส่วนเอฟเฟกต์ในตัวจัดการสถานะสามารถลบตัวเองออกได้เมื่อจำเป็น เนื่องจากตัวจัดการสถานะทำงานเป็นสแตก และตรรกะเงื่อนไขของเอฟเฟกต์เหล่านี้มักซับซ้อนกว่า

มิเตอร์นี้มองหาแถบพลังชีวิตทั่วไป และจะคืนค่าเปอร์เซ็นต์ของสีที่ตรงกันเป็นตัวเลขระหว่าง 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 ชื่อ 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) เพื่อรับมือกับปัญหาความไม่เสถียรข้างต้น ฟังก์ชัน callback ที่ผูกไว้จะถูกเรียกใช้ก็ต่อเมื่อค่ามิเตอร์คงที่ติดต่อกัน 25 ครั้งของการอัปเดต ซึ่งจะกรองข้อมูลส่วนใหญ่ที่ไม่ใช่แถบพลังชีวิตจริงออกไป แม้ผู้เล่นจะอยู่นิ่งก็ตาม ข้อเสียเพียงอย่างเดียวคือความหน่วงของเอฟเฟกต์ที่เพิ่มขึ้น ซึ่งจะเป็นปัจจัยจำกัดในสถานการณ์นี้เสมอ สำหรับแถบพลังชีวิตนี้โดยเฉพาะ เราสามารถแก้ไขได้อย่างน้อยสองวิธี:

  • เมื่อถูกโจมตี ส่วนหนึ่งของแถบสีเขียวจะเปลี่ยนเป็นสีแดงทันทีก่อนจะหดลงสู่ระดับพลังชีวิตสีเขียวใหม่ หากเราเพิ่มมิเตอร์ healthRed ที่มองหาสีแดงในตำแหน่งเดียวกับมิเตอร์พลังชีวิต เราสามารถรวมค่าทั้งสองเพื่อให้เรียกใช้ได้เร็วขึ้น เงื่อนไข if ( healthM.decreased && healthRed.increased ) น่าจะให้ความแม่นยำที่ดีในกรณีนี้
  • เรายังสามารถรวมค่าที่อ่านได้จากมิเตอร์แบบ linear หลายตัวที่มีการตั้งค่าสีเดียวกันได้ ขณะนี้เรามีมิเตอร์แบบ linear หนึ่งตัวตั้งไว้ตรงกลางแถบพลังชีวิต หากตั้งเพิ่มอีกสองตัว ตัวหนึ่งอยู่เหนือและอีกตัวอยู่ใต้ตัวเดิมเล็กน้อยแต่ยังอยู่ภายในแถบพลังชีวิต ความใกล้เคียงกันของค่ามิเตอร์ทั้งสามสามารถใช้เป็นการตรวจสอบความเสถียรเพิ่มเติมได้ มิเตอร์ทั้งสามตัวต้องอ่านค่า Meter.decreased และมีค่าอยู่ในช่วงใกล้เคียงกันจึงจะเรียกใช้เอฟเฟกต์

ฟังก์ชัน callback นี้จะทำงานหลังจากมิเตอร์ยืนยันแล้วว่าสมาชิกทั้งหมดในอาร์เรย์ค่าของมันเท่ากัน ซึ่งช่วยให้มั่นใจว่าค่ามิเตอร์มาจากข้อมูลที่เสถียร ภายใน callback เราจะใช้คำสั่งเงื่อนไขเพื่อตัดสินว่าควรเล่นเอฟเฟกต์หรือไม่

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

คำสั่งเงื่อนไขนี้ต้องการเงื่อนไขที่เป็นจริงสองข้อ ได้แก่ การลดลงของมิเตอร์อย่างเสถียร และค่ามิเตอร์ปัจจุบันไม่ตรงกับค่าที่บันทึกไว้ก่อนหน้า เราเพิ่มเงื่อนไขที่สองนี้เพื่อหลีกเลี่ยงบั๊ก หากเราตรวจสอบเพียงการลดลง เอฟเฟกต์จะเล่นซ้ำไปเรื่อย ๆ เนื่องจาก Meter.decreased ถูกตั้งค่าเมื่อมิเตอร์เสถียรแล้ว ค่าของมันจะคงเดิมตราบใดที่มิเตอร์ไม่เปลี่ยนแปลง ด้วยวิธีนี้ เอฟเฟกต์จะเล่นก็ต่อเมื่อมีการลดลงที่ต่างจากค่าเสถียรครั้งล่าสุดเท่านั้น

เราจะสร้างเอฟเฟกต์ให้คุณสองแบบ: แบบหนึ่งสำหรับอาร์เรย์ effects และอีกแบบสำหรับตัวจัดการสถานะ

โค้ดบล็อกแรกนี้กำหนดฟังก์ชันเอฟเฟกต์สำหรับอาร์เรย์ effects ซึ่งออกแบบมาให้รวดเร็วและเบา เพื่อให้เอฟเฟกต์ย่อยหลายตัวทำงานพร้อมกันได้ เพื่อลดภาระของระบบ เรายังทำให้เอฟเฟกต์ปรับแต่งได้ง่ายผ่านพารามิเตอร์เพื่อนำกลับมาใช้ซ้ำได้

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

เมื่อทำงานจริงควรมีลักษณะดังนี้:

ด้วยการออกแบบฟังก์ชันที่ปรับแก้ได้ เราจึงสร้างเอฟเฟกต์ฟื้นฟูพลังชีวิตได้ง่าย ๆ เช่นกัน! เพียงเพิ่มเงื่อนไขอีกข้อในฟังก์ชัน 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;
}
}

ความเรียบง่ายของเอฟเฟกต์นี้คือข้อได้เปรียบที่ใหญ่ที่สุดสำหรับนักพัฒนา LightScript แทนที่จะสร้างเอฟเฟกต์ที่ออกแบบเฉพาะซึ่งแต่ละตัวต้องใช้โค้ดหลายร้อยบรรทัด ให้สร้างเอฟเฟกต์ขนาดเล็กที่นำกลับมาใช้ซ้ำได้ เพื่อเป็นส่วนประกอบพื้นฐานสำหรับทริกเกอร์เฉพาะต่าง ๆ เอฟเฟกต์สามแบบถัดไปเป็นรูปแบบที่ดัดแปลงจากเอฟเฟกต์เล็ก ๆ ที่เราเพิ่งสร้าง ลองทดลองดูได้ตามสบายว่าคุณจะสร้างอะไรได้บ้าง!

โดยส่วนตัวแล้ว เราสงวนตัวจัดการสถานะไว้สำหรับเอฟเฟกต์ขนาดใหญ่และซับซ้อนที่มีตรรกะสถานะเฉพาะเจาะจงมาก โดยเฉพาะเมื่อคุณต้องการให้เอฟเฟกต์นั้นมีลำดับความสำคัญเหนือเอฟเฟกต์อื่นทั้งหมด โปรดจำไว้ว่าตัวจัดการสถานะทำงานเหมือนสแตกและจะเรียกใช้เฉพาะเอฟเฟกต์ที่อยู่บนสุดเท่านั้น เอฟเฟกต์สถานะแต่ละตัวมีหน้าที่ลบตัวเองออกเมื่อทำงานเสร็จ ตัวอย่างนี้แสดงแอนิเมชันล็อบบี้จับคู่ผู้เล่น (matchmaking) ที่ปรับเปลี่ยนตามว่าคุณกำลังค้นหาอยู่หรือไม่ และยังมีอาร์เรย์ effects ขนาดเล็กของตัวเองพร้อมเอฟเฟกต์ตัวช่วยอีกด้วย มิเตอร์ที่ใช้ในที่นี้คือ 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 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;
}
};

ไม่ได้ค้นหา:

กำลังค้นหา:

และนี่คือบทสรุปของบทช่วยสอน callbacks รวมถึงคู่มือการพัฒนาอย่างเป็นทางการของ SignalRGB โปรดติดตามส่วนถัดไปและส่วนสุดท้ายของคู่มือของเรา การดูแลรักษา Lightscript!