วันอาทิตย์ที่ 11 ตุลาคม พ.ศ. 2558

Tip1 : Basic javascript animation PART2

สำหรับตอนที่2 นี้เราก็จะมาดูการประยุกต์ใช้งานจริงด้วยคำสั่งต่างๆนะครับ โดยจากตอนที่แล้ว
ผมได้ทำการอธิบายโค้ดที่เป็นตัวหลักในการควบคุมอนิเมชั่นไปแล้ว วันนี้จะมาอธิบายพวก
คำสั่งที่ใช้งานบ่อยๆ สำหรับใช้ในจาวาสคริปอนิเมชั่นกันนะครับ 


1.คำสั่งที่ใช้ควบคุม javascript ที่จำเป็น

var t = setInterval(framerate,500);  เป็นการสั่งให้เรียกฟังค์ชั่น (ในที่นี้ชื่อ framerate) ทุกๆ 500ms ไปตลอด
จะหยุดได้ต้องใช้ clearInterval(t); และนี้คือเหตุผลที่ต้องมี var t มารับค่า เพราะจะได้นำมาใช้หยุดคำสั่งได้
note : คำสั่งจะเริ่มทำงานครั้งแรกเมื่อผ่านไป 500ms นับจากคำสั่งถูกรัน ไม่ใช่เริ่มทำงานทันที

คำสั่งต่อมาที่ใช้บ่อยเช่นกันคือ setTimeout(framerate,500); อันนี้ต่างกันคือเรียกใช้เพียงครั้งเดียว เริ่มทำงานหลัง 500ms 


สองคำสั่งนี้จำเป็นมากในการควบคุมเวลาที่ทำให้อนิเมชั่นเกิดการเปลี่ยนแปลงตามเวลา
สำหรับ setInterval นั้นนอกจากจะใส่เวลาเป็นตัวเลขเป็นเวลาที่แน่นอนคงที่แล้ว
ยังกำหนดเป็นแบบไม่คงที่ได้ เช่น นับเร็วขึ้นเรื่อยๆ ดูรายละเอียดได้ที่เว็บนี้เลย
http://javascript.info/tutorial/animation



2.คำสั่งเบสิกสุดๆ คือการเปลี่ยนค่า css ของแต่ล่ะ element  

เป็นการเปลี่ยนค่าวัตถุ เช่น ขนาด ตำแหน่ง ซ่อน แสดง ขอบ สี  โดยเมื่อออกแบบลำดับดีๆ
ก็จะทำให้เกิดการเปลี่ยนแปลงของวัตถุเป็นอนิเมชั่นที่สวยงามได้ โดยใน js เบื้องต้นจะเป็น
document.getElementById("id").style. แล้วตามด้วยคำสั่ง style ต่างๆ เช่น


document.getElementById("id").style.marginTop = "50%";
document.getElementById("id").style.border = "10px solid yellow";
document.getElementById("id".style.backgroundColor = "red";
document.getElementById("id").style.top = "30%";
document.getElementById("id").style.opacity = "0.9";

ถ้าของ Jquery ก็จะสั้นกว่า ตามนี้ http://www.w3schools.com/jquery/jquery_css.asp
ซึ่งคำสั่งเพิ่มเติมสามารถหาได้จาก google เลยครับ

ตัวอย่างโค้ดทำให้การ์ด (html element) เลื่อนขึ้นและโชว์ขอบ และเลื่อนลงซ่อนขอบ
function cardSlideUp(card) {
  document.getElementById(card).style.marginTop = "40" + "%";
  document.getElementById(card).style.border = "10px solid yellow";
  document.getElementById("frame").style.backgroundColor = "red";
  setTimeout(function(){ document.getElementById('raid').style.width = "98%"; },20);
  setTimeout(function(){ document.getElementById('raid').style.width = "100%"; },200);
}

function cardSlideDown(card) {
  document.getElementById(card).style.marginTop = "50" + "%";
  document.getElementById(card).style.border = "0px solid yellow";
  document.getElementById("frame").style.backgroundColor = "black";
}


เวลาเรียกใช้งานก็จะประมาณนี้

function onClick() { //คำสั่งนี้ถูกเรียกได้หลายวิธี สามารถดูได้ที่ tip part1 บทความที่แล้ว
  var scene = 0

  function framerate() {   
      scene++  


      switch(scene) {
      case 1:
          cardSlideUp("card"); //500ms แรก เรียกการ์ดขยับขึ้น เป็นการโจมตี โดยต้องมี html element ภาพการ์ดที่มี id = "card" ด้วยในการเรียก
          break;
      case 2:
          cardSlideDown("card"); //500ms ต่อมา โจมตีเสร็จขยับลง
          break;


      if (scene == 2) 
        clearInterval(t); //หยุดการทำงานของ setInterval
   }

    var t = setInterval(framerate,500); //ตั้งเวลาไว้ 500ms หรือครึ่งวินาทีต่อการเปลี่ยนแปลง 1 ครั้ง
}



3.คำสั่งจำพวก Canvas ใช้ร่วมกับ sprite sheet
ถ้าต้องการอนิเมชั่นที่ลื่นไหล สวยงาม สมจริง เช่นท่าเตะของตัวละคร effect สกิลโจมตีต่างๆ
ก็จำเป็นที่จะต้องใช้ sprite sheet ร่วมกับคำสั่ง canvas ล่ะครับ

sprite sheet ก็คือภาพๆนึง ที่รวมการเคลื่อนไหวของตัวละครหรือวัตถุต่างๆ ในทุกๆเฟรมไว้
นั้นเอง เฟรมๆนึงก็จะใช้รูปภาพนึง ดังนั้น sprite image จึงมีภาพเล็กๆหลายภาพ อยู่ในตำแหน่ง
ที่แน่นอน เพราะเวลานำไปใช้งานเราจะทำการดึงภาพเล็กๆนั้นแหละไปทีล่ะภาพ โดยจะเปลี่ยน
ตำแหน่งในการดึงทุกครั้ง เพื่อให้ได้ภาพเคลื่อนไหวที่สวยงามต่อเนื่อง




ตัวอย่างภาพ sprite sheet ของ effect ระเบิด 

(http://stackoverflow.com/questions/28201187/working-with-sprite-sheet-animations-in-android)


คราวนี้จะมาอธิบายพร้อม code นะครับ
ขั้นแรก เราต้องมี html element ให้มันแสดงผล canvas ก่อนนะครับ ง่ายๆเลยก็ตัวอย่างเช่น
< canvas id="show" width="640px" height="400px" >< /canvas >
ต่อมาก็เป็นรูปภาพ sprite sheet ซึ่งให้โหลดมาเป็น html img แล้วทำการซ่อนภาพ ก่อนปิดด้วยวงเล็บปีกกา เช่น
< img id="skill" src="sprite.png" style="display:none" />
สำหรับการโหลดภาพเข้ามาใช้ในงานนั้น เนื่องจาก javascript นั้นทำงานแบบไม่รอใคร ไม่รอคำสั่งบรรทัดบนประมวลผลเสร็จ
ก็ไปรันคำสั่งถัดไปแล้ว (async) ก็จะทำให้บางทีนั้นภาพอนิเมชั่นไม่ขึ้น เพราะไม่สามารถหาภาพเจอเนื่องจากโหลดไม่ทัน
วิธีแก้นั้นก็คือให้ทำเป็น img html แล้วโหลดมาไว้ก่อน พร้อมกับซ่อนไว้ ดังตัวอย่างข้างบน หรือถ้าจะใช้ javascript โหลดมาตรงๆล่ะก็ อาจจะต้องมีการหน่วงเวลาด้วยคำสั่ง setTimeout หรือจะใช้ window.onload = function () { ... } ในการรอให้โหลดเสร็จก่อนค่อยรันก็ได้


จากนั้นก็สร้าง javascript สำหรับเรนเดอร์ ดังนี้ 

function animation() {
  var mainAnimationFrame = 0; //เฟรมเริ่มต้นเป็น 0 เหมือนเดิม
 
   var c = document.getElementById("show"); //ต้องนำเข้า canvas html element ที่เราสร้างไว้ในขั้นแรกมาเป็นตัวแปรก่อน
    var ctx = c.getContext("2d"); //จากตัวแปร canvas นั้น ก็ใช้คำสั่ง getContext("2d") เพื่อเรียกส่วนการวาดของ canvas ขึ้นมา เก็บไว้ที่ตัวแปร ctx

    var skill_img = document.getElementById('skill'); //นำภาพ sprite sheet เข้ามาเก็บ


    //ต่อมากำหนดตำแหน่งจุดเริ่มต้นภาพที่จะให้ตัดรูปไปแสดงผลในแต่ล่ะเฟรม โดยสร้างเป็นอาเรย์ javascript
    var skill_list = [{'y': '5', 'x': '5'} ,
                    {'y': '5', 'x': '655'} ,
                    {'y': '5', 'x': '1305'} ,
                    {'y': '5', 'x': '1955'}];

    function run() {
       var id = mainAnimationFrame;
        ctx.clearRect(0, 0, 640, 400); //ลบภาพเก่าเฟรมที่แล้ว ถ้าไม่ใส่ภาพจะวาดทับกันไปเรื่อยๆ
        ctx.drawImage(
          skill_img, //ภาพ sprite ที่โหลดมาไว้ข้างบน
          skill_list[id].x, //จุดเริ่มต้นที่วาดภาพในแนวแกน x โดยดูจากอาเรย์ skill_list ตำแหน่งของอาเรย์ตามเฟรมภาพปัจจุบัน
          skill_list[id].y, 
//จุดเริ่มต้นที่วาดภาพในแนวแกน y โดยดูจากอาเรย์ skill_list ตำแหน่งของอาเรย์ตามเฟรมภาพปัจจุบัน        
          640,  //ขนาดรูปต้นฉบับที่จะ crop มาแสดงผล นับจากจุดเริ่มต้นแกน x
          400, 
//ขนาดรูปต้นฉบับที่จะ crop มาแสดงผล นับจากจุดเริ่มต้นแกน y         
          0, 0, //ตำแหน่ง x,y เริ่มต้นที่จะวาดใน html canvas element
          640, //ขนาดรูปแนวแกน x ที่จะวาดลงไปใน canvas
          400 //ขนาดรูปแนวแกน y ที่จะวาดลงไปใน canvas
          );
        mainAnimationFrame += 1; //เพิ่มเฟรมไปเรื่อยๆทีล่ะ 1

        if(mainAnimationFrame == 3) //ถ้าหมด sprite ที่จะแสดงผลแล้ว ในที่นี้มี 4 รูป เริ่มจาก 0 1 2 3 ดังนั้นเมื่อถึง 3 ก็คือรูปสุดท้าย จบแล้ว ให้ทำคำสั่งข้างล่าง
        {
            ctx.clearRect(0, 0, 640, 400); //ลบภาพเก่าที่วาดไว้
            clearInterval(r); //จบการแสดงผลโดยจบคำสั่ง setInterval ข้างล่าง
        }
    }

   var r = setInterval(run, 100); //ทำงานทุกๆ 100ms
    
}


ดูคำอธิบายโค้ดเพิ่มได้ที่
http://www.w3schools.com/tags/canvas_drawimage.asp    


จากการแสดงผลอนิเมชั่นด้วย canvas ที่ยกตัวอย่างให้เห็นนั้น เป็นการแสดงผล
อนิเมชั่นจาก sprite image ที่โหลดเข้ามา โดยตัดภาพแต่ล่ะส่วนของ sprite image 

(ตามตำแหน่ง x,y ที่เซตใน array skill_list) ไปแสดงผลที่ canvas element ทุกๆ 100ms 
ก็เลยจะได้ภาพที่ค่อนข้างลื่นไหล จะเห็นได้ว่าการทำงานนั้นคล้ายกับตัวอย่างโค้ดใน
PART1 อยู่มาก หลักๆคือให้รู้ว่าทุกๆครั้งที่เฟรมนับไปเรื่อยๆนั้นจะต้องทำอะไร อย่างไรบ้าง
จากตัวอย่าง canvas ในที่นี้คือ เฟรมแรกตัดภาพส่วนที่1 ไปแสดง เฟรมต่อไปก็ตัดภาพส่วนที่2 ไปแสดง 
ทำแบบนี้ไปเรื่อยๆจนหมดเฟรม ตรงส่วนนี้เราสามารถออกแบบโปรแกรมให้ทำงานตามที่เราต้องการได้

สำหรับการประยุกต์ใช้ตัวอย่างนึงคือ การใช้โค้ดใน part1 นั้น มาเรียกใช้
โค้ดในตัวอย่าง canvas นี้ เช่น 


function onClick() {
  var scene = 0

  function framerate() {   
      scene++  


      switch(scene) {
      case 1:
          cardSlideUp("card"); //500ms แรก เรียกการ์ดขยับขึ้น เป็นการโจมตี โดยต้องมี html element ภาพการ์ดที่มี id = "card" ด้วยในการเรียก          

          animation(); //สั่งให้วาดอนิเมชั่นด้วย canvas ที่เราได้ออกแบบไว้หลังการ์ดขยับโจมตี
          break;
      case 2:
          cardSlideDown("card"); //500ms ต่อมา โจมตีเสร็จขยับลง
          break;


      if (scene == 2) 
        clearInterval(t); //หยุดการทำงานของ setInterval
   }

    var t = setInterval(framerate,500); //ตั้งเวลาไว้ 500ms หรือครึ่งวินาทีต่อการเปลี่ยนแปลง 1 ครั้ง
}

 

ไม่มีความคิดเห็น:

แสดงความคิดเห็น