JavaScript animation / game loop (ნაწილი 1)

იმ დღეს ერთ-ერთ ვებ აპლიკაციაში არც თუ ისე მარტივი ანიმაციის გაკეთება მომინდა და ბოლოს ჯავასკრიპტამდე მივედი. თუმცა რამდენიმე გზის გამოკვლევა დამჭირდა და გადავწყვიტე ამ თემაზე პოსტი კარგი იქნებოდა. იქნებ ვინმეს უკეთესი იდეა ჰქონდეს…

ჩემი პირველი ნაბიჯი CSS3-ის გამოცდა იყო. მაგის ანიმაციების მოდული ძალიან მომწონს და ვიფიქრე იქნებ როგორმე მის ფარგლებში ჩავმჯდარიყავი, მაგრამ როცა ანიმირებულ ელემენტებს შორის კოლიზიების დადგენა დამჭირდა, გავიჭედე 🙂

კოლიზიის შესახებ ინფორმაციას ბრაუზერი ვერ მაძლევს css ანიმაციის დროს (და არც მაქვს მაგის მოთხოვნის უფლება). შესაბამისად, როგორღაც მოძრავი ობიექტების ადგილსამყოფელის გაკონტროლება მჭირდებოდა. ეს ნიშნავს რომ რაღაც ინტერვალით ვამოწმო თითოეული მათგანის კოორდინატი და თანაკვეთა შევამოწმო, ამასთან ინტერვალებში არ გადავახტე კოლიზიის მომენტს.

როგორც აღმოჩნდა, მოძრავი ობიექტის კოორდინატებიც არ არის ზედაპირზე. თუ ვინმეს დაგჭირდებათ, ვებკიტში არის ერთი ადგილი საიდანაც მათი აღება შეიძლება – ეს არის ტრანსფორმაციის მატრიცა, რომელიც ახალი მდგომარეობის გამოსათვლელად გამოიყენება. ეს მატრიცა შეიცავს მიმდინარე კოორდინატების ინფორმაციასაც. მაგალითად:

var animatedElement = document.getElementById("animatedElement");
var transform = window.getComputedStyle(animatedElement).webkitTransform;
var curTransform = new WebKitCSSMatrix(transform);
console.log(animatedElement.offsetLeft + curTransform.m41);
console.log(animatedElement.offsetTop + curTransform.m42);

ამიტომ გადავწყვიტე ჩემს შემთხვევაში მხოლოდ CSS3-ის იმედზე ვერ ვიქნებოდი და ისევ ჯავასკრიპტს მოვკიდე ხელი. იდეაში საჭიროა შემდეგი: უნდა გავაკეთო კადრების ციკლი. დროის პატარა ინტერვალებში უნდა მოხდეს ელემენტების მდგომარეობის / მდებარეობის განახლება და შესაბამისად გადახატვა. კოლიზიების შემოწმებაც მანდ შეიძლება – მდგომარეობის გენერაციის დროს.

წინათ ჯავასკრიპტის ანიმაცია რომ გაგეკეთებინათ, ერთმანეთში ჩადგმული და გადაბმული setTimeout ან setInteval მეთოდებით მოგიწევდათ ანიმაციის ციკლის შეკვრა. მაგალითად, ასე:

function draw(){
    console.log('timestamp ' + +new Date());
    // update positions and redraw frame
}
setInterval(draw, 10);

ყოველ 10 მილიწამში მოხდება draw მეთოდის გამოძახება და მაგ მეთოდში იქნებოდა ანიმაციის ახალი მდგომარეობის გენერაცია.

ზოგი ახლაც იყენებს ამ გზას მარტივი ამოცანების გადასაჭრელად, თუმცა გასათვალისწინებელია მათი პრობლემები:

1. არაზუსტი დრო. დაყოვნება, რომელსაც ამ მეთოდებს (setTimeout ან setInteval) მილიწამების სახით გადავცემთ არგუმენტად, მხოლოდ იმას ნიშნავს, რომ ჩვენი ფუნქციები ამ დროის შემდეგ ბრაუზერის UI thread queue-ში ჩაემატებიან და თავის რიგს დაელოდებიან შესასრულებლად. შესაბამისად თუ UI ნაკადი სხვა საქმითაა დაკავებული, ჩვენი ანიმაციის ფრაგმენტი ზუსტად საჭირო დროს ვერ შესრულდება.

2. Browser timer resolution. მისი ქართული შესატყვისი ვერ მოვიფიქრე – ეს არის დრო, რამდენ ხანში ერთხელაც ახლდება საათი. მაგალითად, წინათ ბრაუზერები OS-ის სისტემურ დროს უყურებდნენ, რომელსაც ეს რეზოლუცია გაცილებით დიდი ჰქონდა (მაგალითად 10-15 მილიწამი), შესაბამისად რამდენიმე მილიწამის მითითების შემთხვევაში, შედეგის განსაზღვრა ვერ ხდებოდა. დღეს მთლად ასე აღარ არის, მაგრამ ამაზე ლაპარაკი შორს წამიყვანს.

მოკლედ რომ ვთქვათ, არ იყო გარანტია თუ როდის გადახატავდა ბრაუზერი ეკრანს.
შემდეგ მოზილას ერთ-ერთი თანამშრომელი მივიდა ახალ იდეამდე და Firefox-ში ჩაემატა მეთოდი mozRequestAnimationFrame, რომელიც შემდეგ სხვა ბრაუზერებმაც გადაიღეს და სპეციფიკაციაში requestAnimationFrame სახელით შევიდა.

ამ მეთოდს მხოლოდ ერთი პარამეტრი აქვს – თქვენი ფუნქცია, რომელსაც ბრაუზერი ეკრანის გადახატვამდე გამოიძახებს. ხოლო თქვენს ქოლბექს გამოძახების დროს გადმოეცემა მაღალი სიზუსტის ტაიმსტემპი – დრო როცა requestAnimationFrame-მა ქოლბექების გამოძახება დაიწყო მიმდინარე კადრისთვის.

მაგალითად:

function step(timestamp) {
    console.log(timestamp);
    requestAnimationFrame(step);
}
requestAnimationFrame(step);

requestAnimationFrame-ს გამოძახებით მხოლოდ მომდევნო გადახატვაზე გამოიძახება ქოლბექი. რადგან ჩვენ ანიმაციის ციკლი გვჭირდება, მოგვიწევს რომ ქოლბექში შიგნით ხელახლა მივებათ ბრაუზერის მოვლენას.

ეს მაღალი სიზუსტის ტაიმსტემპი არის DOMHighResTimeStamp ობიექტი და განსხვავდება ჩვეულებრივი Date.now() ფუნქციის დასაბრუნებელი მნიშვნელობისგან. თუ მაგ ფორმატის მიმდინარე ტაიმსტემპი გჭირდებათ, მისი მიღება შეიძლება performance.now() მეთოდით.

One thought on “JavaScript animation / game loop (ნაწილი 1)

კომენტარის დატოვება

თქვენი ელფოსტის მისამართი გამოქვეყნებული არ იყო. აუცილებელი ველები მონიშნულია *