Lập trình game 2D trên HTML5, Phần 8: Phát hiện va chạm và các hình ảnh động của sprite

Phát hiện và phản ứng với sự va chạm; làm cho các sprite nổ tung

Trong loạt bài này, David Geary sẽ hướng dẫn bạn từng bước thực hiện trò chơi video HTML5 2D. Trong phần này, bạn sẽ học cách làm thế nào mà Snail Bait thực hiện được va chạm và nổ tung.

David Geary, Tác giả và diễn giả, Clarity Training, Inc.

Ảnh của David GearyDavid Geary, tác giả của quyển sách Core HTML5 Canvas, cũng là đồng sáng lập của Nhóm người dùng HTML5 Denver và là tác giả của 8 cuốn sách Java, bao gồm cả những cuốn sách bán chạy nhất về Swing và JavaServer Faces. David là một diễn giả thường xuyên tại các hội nghị, bao gồm JavaOne, Devoxx, Loop Strange, NDC và OSCON và ông đã ba lần đạt danh hiệu JavaOne Rock Star (diễn giả hàng đầu tại hội nghị JavaOne). Ông đã viết loạt bài Lập trình game 2D trên HTML5 2D, JSF 2 fu, và GWT fu cho developerWorks.



12 09 2013

Phát hiện va chạm và hình ảnh động của sprite là các yếu tố chủ yếu của các trò chơi video. Trò chơi Snail Bait mà bạn đang thực hiện trong loạt bài này, cũng không ngoại lệ. Hình 1 thể hiện nhân vật bị nổ tung sau khi va chạm với con ong ở góc trên bên trái.

Hình 1. Phát hiện va chạm khi hành động
Hình chụp từ sự va chạm giữa nhân vật và con ong và sau đó là việc nổ trong Snail Bait

Trong bài viết này, bạn sẽ học làm thế nào để:

  • Phát hiện va chạm
  • Sử dụng bối cảnh của khung nền ảnh (Canvas) HTML5 cho việc phát hiện va chạm.
  • Hiện thực phát hiện va chạm như là các hành động của sprite
  • Xử lí các va chạm
  • Hiện thực các hình ảnh động của sprite, như là việc nổ.

Quá trình phát hiện va chạm

Phát hiện va chạm là một quá trình gồm 4 bước, mỗi bước thực sự phát hiện việc va chạm:

  1. Lặp qua các sprite của trò chơi
  2. Loại ra các sprite không liên quan đến việc phát hiện va chạm
  3. Nhận diện sự va chạm giữa các sprite liên quan
  4. Xử lí va chạm

Việc phát hiện va chạm có thể tốn nhiều chi phí để tính toán, vì vậy cần tránh làm việc đó đối với các sprite mà không có khả năng xảy ra va chạm. Ví dụ, nhân vật của Snail Bait chạy xuyên qua các sprite khác khi chúng đang nổ tung. Bởi vì chỉ cần một ít thời gian để kiểm tra xem liệu một sprite có đang nổ hay không hơn là phát hiện va chạm, Snail Bait loại trừ các sprite đang nổ khỏi sự phát hiện va chạm.

Hãy bắt đầu với cái nhìn tổng quát về các kỹ thuật phát hiện va chạm.

Các kỹ thuật phát hiện va chạm

Chúng ta có thể phát hiện được sự va chạm giữa các sprite bằng nhiều cách khác nhau. Có 3 kĩ thuật phổ biến, sắp xếp tăng dần theo mức độ tinh tế và phức tạp:

  • Vùng bao quanh (khối bao quanh trong game 3D)
  • Ray casting
  • Định lý tách trục

Phát hiện va chạm với kỹ thuật dò vùng bao quanh bằng cách kiểm tra điểm cắt nhau của các hình tròn hoặc các hình đa giác. Ví dụ như trong Hình 2, hình tròn nhỏ là vùng bao quanh của một sprite (quả bóng nhỏ) và hình tròn lớn là vùng bao quanh của cái xô. Khi cả hai vùng bao quanh có điểm chung thì quả bóng sẽ rơi vào trong xô.

Hình 2. Vùng bao quanh: Sự va chạm giữa hai hình tròn
Minh họa các nguyên tắc cơ bản phát hiện va chạm giữa các vòng tròn

Phát hiện va chạm giữa hai vòng tròn là kỹ thuật đơn giản nhất trong các kỹ thuật phát hiện sự va chạm. Nếu khoảng cách giữa hai trọng tâm hình tròn nhỏ hơn tổng bán kính của hai hình tròn đó thì hai hình tròn sẽ có điểm chung và hai sprite sẽ va chạm nhau.

Sự phát hiện va chạm giữa hai vùng bao quanh thì rất đơn giản, nhưng có thể sai nếu như hai vùng bao đó quá nhỏ hoặc vật thể di chuyển quá nhanh. Trong trường hợp khác, các sprite có thể đi lướt qua nhau trong một khung hình khác, do đó không kiểm tra được va chạm.

Một kỹ thuật đáng tin cậy hơn cho những vật thể nhỏ và di chuyển nhanh là ray casting, được minh họa trong Hình 3. Ray casting phát hiện sự va chạm qua hai vector vận tốc của các sprite. Trong mỗi khung hình của Hình 3, vector vận tốc của quả bóng là đường chéo màu xanh và vector vận tốc của cái xô là đường ngang màu đỏ (cái xô di chuyển theo chiều ngang). Quả bóng sẽ rớt trong xô khi quả bóng nằm dưới giao điểm và giao điểm nằm trên cái thùng, ta có thể xem trong Hình 3.

Hình 3. Ray casting
Minh họa các nguyên tắc cơ bản trong việc phát hiện va chạm của dò tia

Phát hiện va chạm tiên nghiệm (priori) hoặc hậu nghiệm (posteriori)

Ta có thể phát hiện sự va chạm trước khi nó được xảy ra (tiên nghiệm) hoặc sau khi sự va chạm xảy ra (hậu nghiệm). Nếu bạn phát hiện được có sự va chạm trước khi nó xảy ra, bạn phải dự đoán xem đó là sprite nào. Nếu bạn phát hiện sự va chạm sau khi nó xảy ra, bạn cần phải tách các sprite đã va chạm. Các phương pháp đó thì không hẳn cái nào sẽ tốt hơn hoặc đơn giản hơn cái nào.

Ray casting hoạt động tốt với những hình đơn giản trong một vài trường hợp — như là quả bóng rơi vào trong xô ở Hình 2— thật đơn giản để xác định hai vật va chạm nhau dựa vào điểm giao của hai vector vận tốc.

Đối với những trường hợp phức tạp hơn, như sự va chạm giữa hai hình đa giác với kích cỡ và hình dạng tùy ý thì Định lý tách trục là một trong những kỹ thuật đáng tin cậy — và phức tạp nhất —. Định lý tách trục dựa trên cách thức chiếu sáng lên hai đa giác từ nhiều góc độ khác nhau của toán học, như trong Hình 4. Nếu bóng của các hình đa giác cho ta một khoảng trống nghĩa là các hình đa giác chưa va chạm với nhau.

Hình 4. Định lý tách trục
Minh họa các nguyên tắc cơ bản trong việc phát hiện va chạm của Định lí tách trục

Trong phạm vi bài viết, chúng ta sẽ không bàn thêm về phương pháp Ray casting hoặc Định lý tách trục. Bạn có thể tìm hiểu sâu hơn trong quyển Core HTML5 Canvas (NXB. Prentice Hall, 2012). (Vui lòng tham khảo tại phần Tài nguyên.)


Phát hiện sự va chạm của Snail Bait

Trong Snail Bait, các đối tượng tương đối lớn và di chuyển chậm nên ta dùng phương pháp tính toán sự va chạm bằng vùng bao quanh. Những vùng bao quanh đó có thể xem ở Hình 5.

Hình 5. Vùng bao quanh để phát hiện va chạm của Snail Bait
Hình chụp từ Snail Bait với vùng bao quanh hình chữ nhật được thêm vào mỗi sprite

Snail Bait hiện thực các sprite với những hành động như là chạy, nhảy và nổ — xem bài viết "Hiện thực các hành vi của sprite" (developerWorks, January 2013). Và cũng tương tự như vậy cho việc phát hiện sự va chạm. Ta hiện thực trong Snail Bait các hành động của nhân vật như: chạy, nhảy và va chạm với các sprite khác. Liệt kê 1 hiện thực nhân vật với ba hành động.

Liệt kê 1. Các hành động của nhân vật
Sprite = function () {
   ...
   this.runner = new Sprite('runner',           // type
                            this.runnerArtist,  // artist
                            [ this.runBehavior, // behaviors
                              this.jumpBehavior,
                              this.collideBehavior
                            ]); 
};

Liệt kê 2 là đoạn mã nguồn của collideBehavior (hành vi va chạm) của nhân vật

Liệt kê 2. Hành vi va chạm của nhân vật
var SnailBait =  function () {
   ...

   // Runner's collide behavior...............................................

   this.collideBehavior = {
      execute: function (sprite, time, fps, context) {  // sprite is the runner
         var otherSprite;

         for (var i=0; i < snailBait.sprites.length; ++i) { 
            otherSprite = snailBait.sprites[i];

            if (this.isCandidateForCollision(sprite, otherSprite)) {
               if (this.didCollide(sprite, otherSprite, context)) { 
                  this.processCollision(sprite, otherSprite);
               }
            }
         }
      },
      ...
      
   };
};

Bởi vì đối tượng collideBehavior là một hành vi của sprite, Snail Bait gọi phương thức execute() của nó cho mỗi khung hình hoạt họa. Và bởi vì đối tượng collideBehavior liên quan với nhân vật nên Snail Bait chuyển cho phương thức execute(). Vui lòng xem phần Các nguyên tắc cơ bản của hành vi trong bài "Hiện thực các hành vi của sprite" để có nhiều thông tin hơn về các hành vi của sprite.

Đối tượng collideBehavior có phương thức execute() đóng gói bốn bước phát hiện sự va chạm đã được liệt kê bên trên. Ba bước cuối cùng đã được hiện thực trong các phương thức của collideBehavior:

  • isCandidateForCollision(sprite, otherSprite)
  • didCollide(sprite, otherSprite, context)
  • processCollision(sprite, otherSprite)

Việc hiện thực mỗi bước của các phương thức trên sẽ được bàn luận trong phần tiếp theo của bài viết.


Lựa chọn các ứng viên cho việc phát hiện va chạm

Một sprite được xem là có thể va chạm với nhân vật khi:

  • Sprite không phải là nhân vật
  • Cả sprite và nhân vật đều có thể nhìn thấy
  • Cả sprite và nhân vật đều chưa bị nổ tung

Phương thức isCandidateForCollision() của đối tượng collideBehavior, được thể hiện trong Liệt kê 3, hiện thực lý luận này.

Liệt kê 3. Chọn ứng cử viên cho việc phát hiện va chạm: isCandidateForCollision()
var SnailBait =  function () {
   ...

   isCandidateForCollision: function (sprite, otherSprite) {
      return sprite !== otherSprite &&                       // not same
             sprite.visible && otherSprite.visible &&        // visible
             !sprite.exploding && !otherSprite.exploding;    // not exploding
   }, 
   ...
};

Tiếp theo, ta hãy xem làm thế nào để phát hiện sự va chạm giữa các sprite được chọn.


Phát hiện va chạm giữa nhân vật và các sprite khác

Phương thức didCollide() của đối tượng collideBehavior, xác định xem nhân vật có va chạm với các sprite khác hay không, được thể hiện trong Liệt kê 4.

Liệt kê 4. Kiểm tra va chạm: didCollide()
var SnailBait =  function () {
   ...

   didCollide: function (sprite,      // runner
                          otherSprite, // candidate for collision
                          context) {   // for context.isPointInPath()
      var left = sprite.left + sprite.offset,
          right = sprite.left + sprite.offset + sprite.width,
          top = sprite.top,
          bottom = sprite.top + sprite.height,
          centerX = left + sprite.width/2,
          centerY = sprite.top + sprite.height/2;

      // All the preceding variables -- left, right, etc. -- pertain to the runner sprite.

      if (otherSprite.type !== 'snail bomb') {
         return this.didRunnerCollideWithOtherSprite(left, top, right, bottom,
                                                         centerX, centerY,
                                                         otherSprite, context);
      }
      else {
         return this.didSnailBombCollideWithRunner(left, top, right, bottom,
                                                       otherSprite, context);
      }
   },

Tùy thuộc vào nhận dạng sprite, phương thức didCollide() tính vùng bao quanh và tâm của nhân vật, lần lượt thỏa mãn một trong hai phương thức.

Nếu sprite đó không phải là bom, phương thức didCollide() sẽ gọi didRunnerCollideWithOtherSprite(), được thể hiện trong Liệt kê 5:.

Liệt kê 5. Phương thức didRunnerCollideWithOtherSprite()
didRunnerCollideWithOtherSprite: function (left, top, right, bottom,
                                              centerX, centerY,
                                              otherSprite, context) {
   // Determine if either of the runner's four corners or its
   // center lies within the other sprite's bounding box. 

   context.beginPath();
   context.rect(otherSprite.left - otherSprite.offset, otherSprite.top,
                otherSprite.width, otherSprite.height);
         
   return context.isPointInPath(left,    top)     ||
          context.isPointInPath(right,   top)     ||

          context.isPointInPath(centerX, centerY) ||

          context.isPointInPath(left,    bottom)  ||
          context.isPointInPath(right,   bottom);
},

Cho trước vùng bao quanh của nhân vật và tọa độ tâm của nó, didRunnerCollideWithOtherSprite() kiểm tra xem một trong những góc của vùng bao quanh hoặc tâm nằm trên vùng của sprite khác.

Ngữ cảnh khung hình thực hiện nhiều việc hơn chỉ là đồ họa

Phương thức isPointInPath() của ngữ cảnh khung hình nhận dạng xem một điểm có thuộc cạnh hay không. Snail Bait dùng phương thức này để kiểm tra một điểm có nằm trong một hình chữ nhật hay không. Nhưng isPointInPath() sẽ có ích khi hình không theo quy tắc. Xác định xem một điểm nằm trên một hình đều rất khó để tính toán bằng tay.

Xác định xem liệu một điểm có nằm trên một hình chữ nhật hay không đòi hỏi rất nhiều phép tính toán học; tuy nhiên, ngữ cảnh 2D của phần tử canvas HTML5 đã đơn giản hóa việc này với phương thức isPointInPath(), phương thức này trả về giá trị true nếu một điểm nằm trong đường đi hiện thời của ngữ cảnh khung hình.

Phương thức didRunnerCollideWithOtherSprite() tạo một viền chữ nhật thể hiện vùng bao quanh của sprite với lời gọi tới beginPath()rect(). Phương thức didRunnerCollideWithOtherSprite() sau đó gọi isPointInPath() để xác định xem một trong năm điểm của nhân vật có nằm trên vùng bao quanh của sprite khác hay không

Phương thức didRunnerCollideWithOtherSprite() nhận diện chính xác va chạm giữa nhân vật với các sprite khác ngoại trừ quả bom, được thể hiện trong Hình 6.

Hình 6. Nhân vật và quả bom
Hình chụp từ Snail Bait thể hiện nhân vật va chạm với bom

Phương thức trên sẽ không tính toán chính xác cho bom vì nó diễn ra đủ nhỏ để có thể đi qua nhân vật mà không đụng vào bất kì góc nào của vùng bao quanh nhân vật hoặc tâm của nhân vật chạm đến bom. Vì tỉ lệ không cân xứng giữa kích thước nhân vật và kích thước bom, phương thức didCollide() trong Liệt kê 4 gọi didSnailBombCollideWithRunner(), được thể hiện trong Liệt kê 6, khi sprite là quả bom.

Liệt kê 6. Phương thức didSnailBombCollideWithRunner()
didSnailBombCollideWithRunner: function (left, top, right, bottom,
                                             snailBomb, context) {
   // Determine if the center of the snail bomb lies within
   // the runner's bounding box  

   context.beginPath();
   context.rect(left, top, right - left, bottom - top); // runner's bounding box

   return context.isPointInPath(
                 snailBomb.left - snailBomb.offset + snailBomb.width/2,
                 snailBomb.top + snailBomb.height/2);
},

Phương thức didSnailBombCollideWithRunner() ngược lại với didRunnerCollideWithOtherSprite(): didRunnerCollideWithOtherSprite() kiểm tra xem một điểm trên nhân vật có thuộc một sprite khác hay không, trong khi didSnailBombCollideWithRunner() kiểm tra xem điểm giữa của bom có thuộc nhân vật hay không.

Bạn có thể biết làm thế nào để hiện thực phát hiện va chạm với vùng bao quanh, tuy nhiên phương thức này có thể chính xác và chạy tốt hơn. Trong phần tiếp theo, ta sẽ điều chỉnh Snail Bait phát hiện va chạm bằng cách chỉnh sửa vùng bao quanh của nhân vật và chia nhỏ không gian trò chơi.


Tinh chỉnh vùng bao quanh

Như bạn có thể thấy ở Hình 5, có một vùng nhận diện va chạm bao xung quanh sprite. Gần các góc sẽ thấy những khoảng trống nhất định, tuy nhiên chúng trong suốt. Trong trường hợp này là của sprite nhân vật, như trong Hình 7. Những vùng trống này có thể dẫn đến việc nhận diện va chạm không đúng, điều này thể hiện rõ ràng nhất khi 2 vùng trống tiếp xúc với nhau mà lại xảy ra va chạm.

Hình 7. Vùng bao quanh ban đầu của nhân vật
Hình chụp từ Snail Bait thể hiện vùng bao quanh ban đầu của nhân vật

Một cách để tránh việc nhận diện sai đó là giảm kích thước của vùng bao quanh, làm cho những vùng trống bị thu hẹp hoặc biến mất, như trong Hình 8.

Hình 8. Vùng bao quanh nhân vật đã được tinh chỉnh
Hình chụp từ Snail Bait thể hiện vùng bao quanh đã được tinh chỉnh

Snail Bait giảm kích cỡ của vùng bao quanh nhân vật bằng cách chỉnh sửa phương thức didCollide(), được thể hiện trong Liệt kê 7.

Liệt kê 7. Chỉnh sửa vùng bao quanh của nhân vật
var SnailBait =  function () {
...

didCollide: function (sprite,      // runner
                       otherSprite, // candidate for collision
                       context) {   // for context.isPointInPath()
      var MARGIN_TOP = 10,
          MARGIN_LEFT = 10,
          MARGIN_RIGHT = 10,
          MARGIN_BOTTOM = 0,
          left = sprite.left + sprite.offset + MARGIN_LEFT,
          right = sprite.left + sprite.offset + sprite.width - MARGIN_RIGHT,
          top = sprite.top + MARGIN_TOP,
          bottom = sprite.top + sprite.height - MARGIN_BOTTOM,
          centerX = left + sprite.width/2,
          centerY = sprite.top + sprite.height/2;
       ...
   },
   ...
};

Giảm kích thước vùng bao quanh sẽ giúp cho việc nhận diện va chạm được tốt hơn. Phần tiếp theo, sẽ chỉ cho chúng ta biết cách làm thế nào để thực hiện việc nhận diện tốt hơn.


Phân vùng không gian

Thông tin thêm về phân vùng không gian

Cách phân vùng không gian trong Snail Bait có thể nói là cơ bản và đơn giản nhất. Ở mức độ phức tạp hơn là dùng kỹ thuật cây bát phânphân vùng nhị phân, những kỹ thuật này sẽ giúp cho việc nhận diện tốt hơn vì các ô sẽ được chia nhỏ đến mức có thể. Đọc thêm ở Tài nguyên để có nhiều thông tin chi tiết hơn.

Phân vùng không gian nghĩa là chia nhỏ không gian trong trò chơi thành những ô nhỏ, qua đó chỉ những sprite nằm trong cùng một ô thì mới có khả năng xảy ra va chạm. Bằng cách loại trừ sự nhận diện va chạm cho các sprite ở các ô khác nhau, phân vùng không gian sẽ tăng hiệu suất. Snail Bait sau khi áp dụng phân vùng không gian, được thể hiện trong Hình 9.

Hình 9. Phân vùng không gian trong Snail Bait
Hình chụp minh họa phân vùng không gian trong Snail Bait. Chỉ các sprite trong phần bên trái (chiếm một phần mười màn hình có thể va chạm với nhân vật. Các sprite trong phần bên phải lớn hơn không thể va chạm với nhân vật)

Ở Liệt kê 8 cho thấy, Snail Bait bỏ qua không xét va chạm đối với tất cả những sprite nằm ở khu vực bên tay phải trong Hình 9 điều này giúp làm giảm một lượng tính toán đáng kể cho quá trình nhận diện.

Liệt kê 8. Chọn lọc những sprite cần kiểm tra
this.isCandidateForCollision: function (sprite, otherSprite) {
   return sprite !== otherSprite &&
          sprite.visible && otherSprite.visible &&
          !sprite.exploding && !otherSprite.exploding &&
          otherSprite.left - otherSprite.offset < sprite.left + sprite.width;


},

Chúng ta đã được biết về hai kỹ thuật nhận diện va chạm một cách hiệu quả. Phần tiếp theo, hãy xem Snail Bait xử lý va chạm như thế nào.


Xử lý va chạm

Một khi va chạm đã được nhận diện thì bạn cần phải biết mình nên làm gì với nó. Phương thức processCollision() xử lý va chạm giữa nhân vật và các sprite khác như ở Liệt kê 9.

Liệt kê 9. Xử lý va chạm: processCollision()
var SnailBait =  function () {
   processCollision: function (sprite, otherSprite) {
      if ('coin'  === otherSprite.type    ||  // bad guys
          'sapphire' === otherSprite.type ||
          'ruby' === otherSprite.type     ||
          'button' === otherSprite.type   ||
          'snail bomb' === otherSprite.type) {
         otherSprite.visible = false;
      }

      if ('bat' === otherSprite.type   ||  // good guys
          'bee' === otherSprite.type   ||
          'snail' === otherSprite.type ||
          'snail bomb' === otherSprite.type) {
         snailBait.explode(sprite);
      }

      if (sprite.jumping && 'platform' === otherSprite.type) {
         this.processPlatformCollisionDuringJump(sprite, otherSprite);
      }
   },
   ...
};

Khi nhân vật va chạm với "phe tốt" như — đồng tiền, sa-phia, hồng ngọc, nút bấm — hoặc với bom, Snail Bait làm những sprite này mất đi bằng cách thiết lập thuộc tính visible về giá trị false.

Khi nhân vật va chạm với "phe xấu" — dơi, ong, ốc và bom —processCollision() làm nổ tung nhân vật bằng phương thức explode(). Tại thời điểm này, phương thức explode() chỉ đơn giản in ra màn hình từ BOOM. Trong bài viết kế tiếp, tôi sẽ nói về phiên bản hoàn chỉnh của phương thức explode().

Cuối cùng, phương thức processPlatformCollisionDuringJump(), được thể hiện trong Liệt kê 10, xử lý va chạm với bậc thềm trong lúc nhân vật đang nhảy.

Liệt kê 10. processPlatformCollisionDuringJump()
processPlatformCollisionDuringJump: function (sprite, platform) {
      var isDescending = sprite.descendAnimationTimer.isRunning();
   
      sprite.stopJumping();
   
      if (isDescending) { // Collided with platform while descending
         // land on platform

         sprite.track = platform.track;
         sprite.top = snailBait.calculatePlatformTop(sprite.track) - sprite.height;
      }
      else { // Collided with platform while ascending
         sprite.fall();
      }
   }
};

Khi nhân vật xảy ra va chạm với một bậc thềm khi đang nhảy, nếu nhân vật đang ở trạng thái nhảy xuống thì sẽ đáp ngay trên bậc thềm đó. Còn nếu trong trường hợp nhân vật nhảy lên và lại đụng ngay bậc thềm ở phía trên đầu, kết quả là nhân vật sẽ rơi xuống. Tại thời điểm này, phương thức fall() của nhân vật được cài đặt như trong Liệt kê 11.

Liệt kê 11. Phương thức tạm thời cho hành động rơi xuống
var SnailBait =  function () {

   ...


   this.runner.fall = function () {
      snailBait.runner.track = 1;
      snailBait.runner.top = snailBait.calculatePlatformTop(snailBait.runner.track) -
                             snailBait.runner.height;
   };
   ...
};

Phương thức fall() sẽ đặt nhân vật ở bậc thềm thấp nhất. Ở bài viết tiếp theo, bạn sẽ biết cách làm thế nào để mô phỏng hành động rơi xuống giống với thực tế.


Theo dõi hiệu suất phát hiện va chạm

90% nhàn rỗi?

Mục trên cùng của bảng trong Hình 10 nghĩa là Snail Bait chỉ chờ đợi để làm điều gì đó trong 90% thời gian. Snail Bait có hiệu suất đáng ghi nhận bởi vì trắc đồ trong Hình 10 được thực hiện trong trình duyệt Chrome (phiên bản 26) — giống như các trình duyệt hiện đại khác — hỗ trợ tăng tốc phần cứng cho phần tử canvas. Các hãng trình duyệt thông thường hiện thực tăng tốc cho phần tử canvas chuyển các lời gọi từ Canvas API thành WebGL, để bạn có sự tiện lợi của Canvas API với hiệu suất của WebGL.

Phát hiện va chạm có thể dễ dàng trở thành trở ngại về hiệu suất, đặc biệt là các thuật toán phát hiện va chạm thiên về toán học như là Định lí tách trục. Bài viết này chỉ cho bạn vài kỹ thuật mà bạn có thể dùng để cải thiện hiệu suất, như là tinh chỉnh lại vùng bao quanh và phân vùng không gian. Nhưng có một ý tưởng hay đó là liên tục theo dõi hiệu suất của trò chơi để mà có thể nắm bắt và sửa chữa các vấn đề về hiệu suất ngay khi chúng xuất hiện.

Tất cả các trình duyệt hiện đại kết hợp với môi trường phát triển phức tạp, như Chrome, Safari, Firefox, Internet Explorer, và Opera, tất cả để cho bạn cấu hình mã khi nó chạy. Hình 10 chỉ ra trình chẩn đoán phân tích của Chrome, trong đó mô tả lượng thời gian, so với tổng số mà bạn dành cho các phương thức riêng lẻ.

Hình 10. Hiệu suất phát hiện va chạm
Trình chẩn đoán phân tích của Chrome hiển thị phần trăm thời gian chạy trong mỗi phương thức mà Snail Bait sử dụng

Bạn có thể thấy trong Hình 10 đó là phương thức didCollide() của Snail Bait chỉ cần 0.05% thời gian của trò chơi. (Cột Self, giá trị didCollide() là 0.01%, chỉ thể hiện thời gian bỏ ra trực tiếp trong một phương thức, không bao gồm thời gian trong các phương thức mà nó gọi.)

Khi nhân vật va chạm với phe xấu, nhân vật nổ. Phần kế chúng ta sẽ biết làm thế nào để hiện thực việc nổ.


Các hình ảnh động của Sprite

Hình 11 minh họa, từ trên xuống hình ảnh động của việc nổ tung mà Snail Bait hiển thị khi nhân vật va chạm với phe xấu, ví dụ như con ong.

Hình 11. Nhân vật nổ tung sau khi va chạm
Hình chụp từ Snail Bait của nhân vật nổ sau khi va chạm với con ong

Snail Bait hiện thực hình ảnh động của sprite, như trong Hình 11, với bộ tạo hình ảnh động cho sprite. Một bộ tạo hình ảnh động cho sprite di chuyển ô mà một đối tượng vẽ sprite vẽ trong một khoảng thời gian được chỉ định. Ví dụ, bộ tạo hình ảnh thể hiện sự nổ của sprite thay đổi các ô trong hình ảnh động của nhân vật được thể hiện trong Hình 12 trong khoảng thời gian là 500 ms.

Hình 12. Các ô mô tả việc nổ tung từ spritesheet của Snail Bait
Các ô của chuỗi spritesheet trong việc nổ được thay đổi bởi bộ tạo hình ảnh động của sprite cho việc nổ

Hàm khởi tạo của đối tượng tạo hình ảnh động của sprite được thể hiện trong Liệt kê 12.

Liệt kê 12. Hàm khởi tạo của bộ tạo hình ảnh động cho Sprite
// Sprite Animators...........................................................

var SpriteAnimator = function (cells, duration, callback) {
   this.cells = cells;
   this.duration = duration || 1000;
   this.callback = callback;
};

Hàm khởi tạo của SpriteAnimator cần 3 đối số. Đối số thứ nhất là một mảng các vùng bao quanh trong spritesheet của Snail Bait; chúng là các ô hình ảnh động tạm thời và bắt buộc. Đối số thứ hai và ba thì tùy chọn. Cái thứ hai là thời gian cho hình ảnh động, cái thứ ba là hàm gọi lại để khởi chạy bộ tạo hình ảnh động cho sprite khi hết thời gian.

Các phương thức của SpriteAnimator được định nghĩa trong nguyên mẫu của đối tượng, được thể hiện trong Liệt kê 13.

Liệt kê 13. Các phương thức của đối tượng tạo hình ảnh động cho sprite
SpriteAnimator.prototype = {
   start: function (sprite, reappear) {
      var originalCells = sprite.artist.cells,
          originalIndex = sprite.artist.cellIndex,
          self = this;

      sprite.artist.cells = this.cells;
      sprite.artist.cellIndex = 0;
      
      setTimeout(function() {
         sprite.artist.cells = originalCells;
         sprite.artist.cellIndex = originalIndex;

         sprite.visible = reappear;

         if (self.callback) {
            self.callback(sprite, self);
         }
      }, self.duration); 
   },
};

Phương thức start() của SpriteAnimator bắt đầu bằng cách lưu các ô chứa hình ảnh gốc và chỉ mục trỏ tới ô hiện thời, và tương ứng, thay thế chúng với các ô tạm thời và số 0. Sau đó, sau khi thời gian hoạt họa kết thúc, phương thức start() trở lại với ô chứa hình ảnh và chỉ mục như ban đầu.

Liệt kê 14 chỉ ra làm cách nào mà Snail Bait sử dụng bộ tạo hình ảnh động để làm cho nhân vật nổ.

Liệt kê 14. Khởi tạo bộ tạo hình ảnh động thể hiện việc nổ
var SnailBait =  function () {
   this.canvas = document.getElementById('game-canvas'),
   this.context = this.canvas.getContext('2d'),
   ...

   this.RUN_ANIMATION_RATE = 17,     // frames/second
   this.EXPLOSION_CELLS_HEIGHT = 62, // pixels
   this.EXPLOSION_DURATION = 500,    // milliseconds

   this.explosionCells = [
      { left: 1,   top: 48, width: 50, height: this.EXPLOSION_CELLS_HEIGHT },
      { left: 60,  top: 48, width: 68, height: this.EXPLOSION_CELLS_HEIGHT },
      { left: 143, top: 48, width: 68, height: this.EXPLOSION_CELLS_HEIGHT },
      { left: 230, top: 48, width: 68, height: this.EXPLOSION_CELLS_HEIGHT },
      { left: 305, top: 48, width: 68, height: this.EXPLOSION_CELLS_HEIGHT },
      { left: 389, top: 48, width: 68, height: this.EXPLOSION_CELLS_HEIGHT },
      { left: 470, top: 48, width: 68, height: this.EXPLOSION_CELLS_HEIGHT }
   ],
   ...

   this.explosionAnimator = new SpriteAnimator(
      this.explosionCells,          // Animation cells
      this.EXPLOSION_DURATION,      // Duration of the explosion

      function (sprite, animator) { // Callback after animation
         sprite.exploding = false; 

         if (sprite.jumping) {
            sprite.stopJumping();
         }
         else if (sprite.falling) {
            sprite.stopFalling();
         }

         sprite.visible = true;
         sprite.track = 1;
         sprite.top = snailBait.calculatePlatformTop(sprite.track) - sprite.height;
         sprite.artist.cellIndex = 0;
         sprite.runAnimationRate = snailBait.RUN_ANIMATION_RATE;
      }
   );
};

Snail Bait khởi tạo bộ tạo hình ảnh động với các ô được hiển thị trong Hình 12. Thời gian là 500ms, và khi kết thúc, bộ tạo hình ảnh động của sprite khởi chạy hàm gọi lại của bộ khởi tạo hình ảnh động của việc phát nổ, làm cho nhân vật được đặt vào bậc thềm thấp nhất. Cuối cùng, trong bài viết kế tiếp, chúng tôi sẽ hiện thực lại hàm này với tính năng là mất một mạng và khởi chạy lại cấp độ hiện thời.

Liệt kê 15 thể hiện phương thức explode() (chưa được hấp dẫn lắm) của nhân vật.

Liệt kê 15. Phương thức explode() của Snail Bait
SnailBait.prototype = {
   ...

   explode: function (sprite, silent) {
      if (sprite.runAnimationRate === 0) {
         sprite.runAnimationRate = this.RUN_ANIMATION_RATE;
      }
               
      sprite.exploding = true;

      this.explosionAnimator.start(sprite, true);  // true means sprite reappears
   },
};

Khi nhân vật nhảy, nó không có hình động vì tỷ lệ hình động là 0. Phương thức explode() gán tỉ lệ hình động thành giá trị thông thường để nó tạo hình động thông qua các ô của việc nổ. Phương thức explode() gán thuộc tính exploding() của nhân vật thành true và khởi chạy bộ tạo hình ảnh cho việc nổ.


Bài tiếp theo

Trong bài viết kế tiếp của loạt bài, chúng ta sẽ thấy làm cách nào để hiện thực việc rơi như thật bằng cách kết hợp với trọng lực, và làm thế nào để thêm âm thanh và nhạc vào Snail Bait.


Tải về

Mô tảTênKích thước
Sample codewa-html5-game8-code.zip1.2MB

Tài nguyên

Học tập

Lấy sản phẩm và công nghệ

Thảo luận

Bình luận

developerWorks: Đăng nhập

Các trường được đánh dấu hoa thị là bắt buộc (*).


Bạn cần một ID của IBM?
Bạn quên định danh?


Bạn quên mật khẩu?
Đổi mật khẩu

Bằng việc nhấn Gửi, bạn đã đồng ý với các điều khoản sử dụng developerWorks Điều khoản sử dụng.

 


Ở lần bạn đăng nhập đầu tiên vào trang developerWorks, một hồ sơ cá nhân của bạn được tạo ra. Thông tin trong bản hồ sơ này (tên bạn, nước/vùng lãnh thổ, và tên cơ quan) sẽ được trưng ra cho mọi người và sẽ đi cùng các nội dung mà bạn đăng, trừ khi bạn chọn việc ẩn tên cơ quan của bạn. Bạn có thể cập nhật tài khoản trên trang IBM bất cứ khi nào.

Thông tin gửi đi được đảm bảo an toàn.

Chọn tên hiển thị của bạn



Lần đầu tiên bạn đăng nhập vào trang developerWorks, một bản trích ngang được tạo ra cho bạn, bạn cần phải chọn một tên để hiển thị. Tên hiển thị của bạn sẽ đi kèm theo các nội dung mà bạn đăng tải trên developerWorks.

Tên hiển thị cần có từ 3 đến 30 ký tự. Tên xuất hiện của bạn phải là duy nhất trên trang Cộng đồng developerWorks và vì lí do an ninh nó không phải là địa chỉ email của bạn.

Các trường được đánh dấu hoa thị là bắt buộc (*).

(Tên hiển thị cần có từ 3 đến 30 ký tự)

Bằng việc nhấn Gửi, bạn đã đồng ý với các điều khoản sử dụng developerWorks Điều khoản sử dụng.

 


Thông tin gửi đi được đảm bảo an toàn.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=70
Zone=Nguồn mở
ArticleID=944909
ArticleTitle=Lập trình game 2D trên HTML5, Phần 8: Phát hiện va chạm và các hình ảnh động của sprite
publish-date=09122013