Fork me on GitHub

Hướng dẫn tạo Platformer

Trong hướng dẫn này, chúng ta sẽ tạo trò chơi platformer đơn giản. Hướng dẫn này sẽ chủ yếu tập trung vào tạo các yếu tố cơ bản của một trò chơi hoạt động bằng cách dùng công cụ thiết kế màn chơi Tiled.

Giới thiệu

Để làm theo hướng dẫn này trơn tru, bạn cần những thứ sau đây:

  • The Tiled Map Editor, được cài đặt và chạy (0.9.0 hoặc mới hơn)
  • melonJS boilerplate, chúng ta sẽ dùng nó như dự án mẫu mặc định cho hướng dẫn của chúng ta.
  • tập tin dữ liệu hướng dẫn, được giải nén vào (ở trên) thư mục dữ liệu mẫu, và gồm có những thứ sau đây :
    • một bộ chia ô màn chơi
    • hai ảnh nền cho các lớp song song
    • một số bảng sprite cơ bản
    • một số âm thanh sfx và bài nhạc
    • một ảnh nền màn hình tiêu đề
  • Thư viện melonJS, được sao chép vào dưới thư mục /lib (xin đảm bảo tải về cả bản rút gọn và bản thô, bởi vì có thể cần các bản mới hơn cho mục đích sửa lỗi)
  • Tài liệu melonJS để biết thêm thông tin chi tiết

Kiểm thử/gỡ lỗi:
Nếu bạn chỉ muốn dùng hệ thống tập tin, vấn đề là bạn sẽ gặp lỗi bảo mật "cross-origin request". Với Chrome, bạn cần dùng thông số "--disable-web-security" hoặc tốt hơn là "--allow-file-access-from-files" khi gọi trình duyệt. Việc này phải được hoàn tất để kiểm thử bất kỳ nội dung cục bộ, hoặc trình duyệt sẽ phàn nàn khi cố gắng nạp các tài nguyên thông qua XHR. Dù phương thức này không được khuyến khích, bởi vì miễn là bạn bật tùy chọn, bạn đang thêm các lỗ hổng bảo mật vào môi trường của bạn

Một tùy chọn thứ hai và dễ dàng hơn là dùng một máy chủ web cục bộ, ví dụ cụ thể ở trong README melonJS boilerplate, bằng cách dùng công cụ grunt serve, và nó sẽ cho phép bạn kiểm thử trò chơi trong trình duyệt của bạn bằng cách dùng url http://localhost:8000.

Tác giả bổ sung:

Hãy thoải mái chỉnh sửa bất cứ thứ gì bạn muốn. Chúng tôi cũng giả sử ở đây, bạn đã quen thuộc với Tiled; nếu bạn cần thêm sự hỗ trợ với công cụ này, bạn có thể ghé qua trang chủ Tiled và wiki để được trợ giúp nhiều hơn.

Phần 1: Tạo một màn chơi bằng Tiled

Đầu tiên chúng ta hãy mở Tiled và tạo một bản đồ mới : đối với hướng dẫn này chúng ta sẽ chọn kích cỡ (canvas) 640x480, và bởi vì chúng ta có 32x32 ô, chúng ta phải chỉ định ít nhất 20 và 15 cho kích cỡ bản đồ. Trong ví dụ, tôi sẽ định nghĩa một màn chơi 40x15, để chúng ta có thể chơi với cảnh nền di chuyển cuộn tới sau này.

Step 1 of creating a new map

Ngoài ra, vì melonJS chỉ hỗ trợ các bản đồ chia ô không nén, xin đảm bảo bạn cài đặt chính xác. Chúng tôi khuyến khích dùng mã hóa Base64, bởi vì nó cho tập tin nhỏ hơn, nhưng điều này là tùy bạn

Sau đó chúng ta hãy thêm bộ ô bằng cách dùng Map/New Tileset. Xin đảm bảo bạn thiết lập khoảng cách và lề bộ ô thành 0 trong Tiled.

Adding a tileset

Để tạo cảnh đẹp, chúng ta sẽ tạo hai lớp - một lớp cảnh nền, và một lớp tiền cảnh, Hãy thoải mái sử dụng sự tưởng tượng của bạn và làm những gì bạn muốn. Tôi đặt tên chúng theo logic là "background" và "foreground", nhưng bạn có thể đặt bất cứ tên gì bạn muốn.

Đây là màn chơi của tôi khi tôi hoàn thành nó:

Tiled level design

Cuối cùng, hãy định nghĩa một màu ảnh nền cho màn chơi của chúng ta, bằng cách dùng công cụ chọn màu (Map/Map Properties), và chọn bất cứ màu bạn thích.

Setting a background color in Tiled

Để hoàn tất, hãy lưu bản đồ mới của chúng ta là "area01" dưới thư mục "/data/map/" (tác vụ Grunt dành cho xây dựng tập tin resources.js sẽ chỉ kiểm tra vị trí cụ thể này cho các bản đồ), và chúng ta đã xong bước đầu tiên!

Phần 2: Nạp màn chơi của chúng ta

Đầu tiên, và sau khi giải nén các tài nguyên hướng dẫn vào cấu trúc thư mục boilerplate, bạn sẽ có cấu trúc như thế này:

data/
  bgm/
    dst-inertexponent.mp3
    dst-inertexponent.ogg
  img/
    font/
     32x32_font.png
    gui/
     title_screen.png
    map/
     area01_level_tiles.png
     license.txt
    sprite/
     gripe_run_right.png
     spinning_coin_gold.png
     wheelie_right.png
    area01_bkg0.png
    area01_bkg1.png
   map/
   sfx/
    cling.mp3
    cling.ogg
    jump.mp3
    jump.ogg
    stomp.mp3
    stomp.ogg
js/
  game.js
  resources.js
  entities/
    HUD.js
    entities.JS
  screens/
    play.js
    title.js
index.html
index.css

boilerplate cung cung cấp một bộ code mặc định, nhưng đầu tiên hãy xem qua khung xương js/game.js của chúng ta::

/* game namespace */
var game = {
  /**
   * an object where to store game global data
   */
  data : {
    score : 0
  },

  // Run on page load.
  onload : function () {
    // Initialize the video.
    if (!me.video.init(640, 480, {wrapper : "screen", scale : "auto", scaleMethod : "flex-width"})) {
      alert("Your browser does not support HTML5 canvas.");
      return;
    }

    // add "#debug" to the URL to enable the debug Panel
    if (me.game.HASH.debug === true) {
      window.onReady(function () {
        me.plugin.register.defer(this, me.debug.Panel, "debug", me.input.KEY.V);
      });
    }

    // Initialize the audio.
    me.audio.init("mp3,ogg");

    // Set a callback to run when loading is complete.
    me.loader.onload = this.loaded.bind(this);

    // Load the resources.
    me.loader.preload(game.resources);

    // Initialize melonJS and display a loading screen.
    me.state.change(me.state.LOADING);
  },

  // Run on game resources loaded.
  loaded : function () {
    me.state.set(me.state.MENU, new game.TitleScreen());
    me.state.set(me.state.PLAY, new game.PlayScreen());

    // add our player entity in the entity pool
    me.pool.register("mainPlayer", game.PlayerEntity);

    // Start the game.
    me.state.change(me.state.PLAY);
  }
};

Bước này rất đơn giản. Khi trang được tải, hàm onload() được gọi, màn hình và âm thanh được khởi tạo, và tất cả tài nguyên trò chơi bắt đầu tải. Chúng ta cũng định nghĩa một lệnh callback được gọi khi mọi thứ sẵn sàng để dùng. Trong callback, chúng ta định nghĩa một trạng thái mới sẽ được dùng cho các đối tượng trong trò chơi, cùng với một đối tượng PlayScreen sẽ được chúng ta dùng để quản lý các sự kiện trò chơi (reset,...).

Thay đổi duy nhất chúng ta sẽ làm trong mẫu dự án mặc định là độ phân giải video đã cho đối với hàm `me.video.init()`, đối với hướng dẫn này chúng ta sẽ tạo một kích cỡ (canvas) 640x480. Đồng thời chúng ta sẽ thay đổi scaleMethod thành "flex-width", vì nó phù hợp hơn với một trò chơi platformer (xem tài liệu `me.video.init` để biết thêm thông tin về những chế độ căn chỉnh sẵn có).

Boilerplate tự động xay dựng danh sách tài nguyên và đưa nó vào ứng dụng của bạn là game.resources (build/js/resources.js) khi dùng tác vụ grunt serve.

THÔNG BÁO: Nếu bạn đang không dùng boilerplate, bạn sẽ phải quản lý resources.js thủ công (nó tốn thời gian, và dễ bị lỗi). Nếu quản lý resources.js thủ công, bạn có thể xem một ví dụ trên git repo.

Cũng lưu ý là mặc dù ở đây chúng ta dùng trực tiếp tập tin tmx, cho việc phat triển chúng tôi khuyến khích bạn dùng định dạng json (nó cũng có thể được xuất trực tiếp từ Tiled), khi nó cho một kích thước tập tin nhỏ hơn, cho phép tải màn chơi nhanh hơn và đề phòng bất kỳ sự cố máy chủ với định dạng .tmx.

Cuối cùng, hãy mở tập tin js/screens/play.js file và trong hàm onResetEvent() (được gọi là một thay đổi trạng thái), chúng ta yêu cầu level director hiển thị màn chơi đã được nạp trước đó, bằng cách thêm một lệnh gọi hàm loadLevel và tên màn chơi mặc định của chúng ta :

game.PlayScreen = me.ScreenObject.extend({
  /**
   * action to perform on state change
   */
  onResetEvent : function () {
    // load a level
    me.levelDirector.loadLevel("area01");

    // reset the score
    game.data.score = 0;

    // add our HUD to the game world
    this.HUD = new game.HUD.Container();
    me.game.world.addChild(this.HUD);
  },

  /**
   * action to perform when leaving this screen (state change)
   */
  onDestroyEvent : function () {
    // remove the HUD from the game world
    me.game.world.removeChild(this.HUD);
  }
});

Đã xong rồi! Nếu bạn làm mọi thứ chính xác, và hãy mở index.html của bạn (Nhớ rằng nếu bạn không dùng một máy chủ web, bạn sẽ cần cho phép trình duyệt của bạn truy cập các tập tin cục bộ, xin tham khảo "Kiểm thử/gỡ lỗi" ở đầu hướng dẫn nếu cần thiết).

Chạy thử

(bấm lên ảnh để xem nó chạy trên trình duyệt của bạn), bạn sẽ thấy như thế này

Step 2 results

Vâng, không gì đẹp hơn thế, nhưng đây chỉ là khởi đầu!

Trong trường hợp bạn không để ý, bởi vì chúng ta đã thiết lập màn hình 640x480 trong ứng dụng, chúng ta chỉ thấy một phần bản đồ (chính xác là một nửa), điều đó là bình thường. melonJS tự động tạo một viewport (cổng xem) tương ứng, và chúng ta sẽ có thể điều hướng thông qua bản đồ trong bước kế tiếp, khi chúng ta sẽ thêm một "người chơi chính"

Phần 3: Thêm một người chơi chính

Ở bước này chúng ta sẽ tạo một đối tượng mới bằng cách mở rộng me.Entity mặc định, để tạo người chơi. Chúng ta sẽ dùng bảng sprite đơn giản (gripe_run_right.png) được cấp để tạo chuyển động cho nhân vật, và định nghĩa chuyển động đi và đứng yên cơ bản. Tất nhiên là bạn có thể định nghĩa các chuyển động phức tạp hơn cho cùng thực thể (nhảy, ngồi, khi bị thương, v.v...) nhưng hãy dừng ở mức cơ bản đã.

Gripe run right

Giờ là lúc để tạo thực thể, hãy mở tập tin ví dụ `js/entities/entities.js`, và chúng ta hãy hoàn tất nó để khớp với đoạn mã sau: :

/**
 * a player entity
 */
game.PlayerEntity = me.Entity.extend({
  /**
   * constructor
   */
  init : function (x, y, settings) {
    // call the constructor
    this._super(me.Entity, 'init', [x, y, settings]);

    // set the default horizontal & vertical speed (accel vector)
    this.body.setVelocity(3, 15);

    // set the display to follow our position on both axis
    me.game.viewport.follow(this.pos, me.game.viewport.AXIS.BOTH);

    // ensure the player is updated even when outside of the viewport
    this.alwaysUpdate = true;

    // define a basic walking animation (using all frames)
    this.renderable.addAnimation("walk",  [0, 1, 2, 3, 4, 5, 6, 7]);

    // define a standing animation (using the first frame)
    this.renderable.addAnimation("stand",  [0]);

    // set the standing animation as default
    this.renderable.setCurrentAnimation("stand");
  },

  /*
   * update the player pos
   */
  update : function (dt) {
    if (me.input.isKeyPressed('left')) {
      // flip the sprite on horizontal axis
      this.renderable.flipX(true);

      // update the entity velocity
      this.body.vel.x -= this.body.accel.x * me.timer.tick;

      // change to the walking animation
      if (!this.renderable.isCurrentAnimation("walk")) {
        this.renderable.setCurrentAnimation("walk");
      }
    }
    else if (me.input.isKeyPressed('right')) {
      // unflip the sprite
      this.renderable.flipX(false);

      // update the entity velocity
      this.body.vel.x += this.body.accel.x * me.timer.tick;

      // change to the walking animation
      if (!this.renderable.isCurrentAnimation("walk")) {
        this.renderable.setCurrentAnimation("walk");
      }
    }
    else {
      this.body.vel.x = 0;

      // change to the standing animation
      this.renderable.setCurrentAnimation("stand");
    }

    if (me.input.isKeyPressed('jump')) {
      // make sure we are not already jumping or falling
      if (!this.body.jumping && !this.body.falling) {
        // set current vel to the maximum defined value
        // gravity will then do the rest
        this.body.vel.y = -this.body.maxVel.y * me.timer.tick;

        // set the jumping flag
        this.body.jumping = true;
      }
    }

    // apply physics to the body (this moves the entity)
    this.body.update(dt);

    // handle collisions against other shapes
    me.collision.check(this);

    // return true if we moved or if the renderable was updated
    return (this._super(me.Entity, 'update', [dt]) || this.body.vel.x !== 0 || this.body.vel.y !== 0);
  },

  /**
   * colision handler
   * (called when colliding with other objects)
   */
  onCollision : function (response, other) {
    // Make all other objects solid
    return true;
  }
});

Tôi nghĩ đoạn mã ở trên khá dễ hiểu. Về cơ bản, chúng ta mở rộng Entity, thiết lập tốc độ người chơi mặc định, tối ưu camera, kiểm tra nếu một số phím được nhấn và quản lý hành động người chơi của chúng ta (bằng cách thiết lập tốc độ người chơi, và sau đó gọi hàm update Cơ thể thực thể). Ngoài ra, bạn cũng có thể để ý tôi đang kiểm tra vận tốc cuối cùng (this.body.vel.x và this.body.vel.y) của đối tượng của tôi, cho phép tôi biết nếu đối tượng của tôi thật sự di chuyển, và điều khiển nếu tôi muốn chạy chuyển động sprite hay không.

Sau đó, mặc dù game.PlayerEntity mặc định đã được khai báo trong boilerplate, chúng ta đã chỉnh sửa "main" của chúng ta để thật sự khai báo thực thể mới trong pool đối tượng (được dùng bởi cơ chế để khởi tạo đối tượng), và cuối cùng là gắn phím mà chúng ta sẽ dùng cho chuyển động của người chơi. Vì vậy hàm loaded() của chúng ta sẽ trở thành:

/**
 * callback when everything is loaded
 */
loaded : function () {
  // set the "Play/Ingame" Screen Object
  me.state.set(me.state.PLAY, new game.PlayScreen());

  // register our player entity in the object pool
  me.pool.register("mainPlayer", game.PlayerEntity);

  // enable the keyboard
  me.input.bindKey(me.input.KEY.LEFT,  "left");
  me.input.bindKey(me.input.KEY.RIGHT, "right");
  me.input.bindKey(me.input.KEY.X,     "jump", true);

  // start the game
  me.state.change(me.state.PLAY);
}

Và bây giờ chúng ta có thể thêm thực thể vào màn chơi! Hãy trở lại Tiled, thêm một Lớp Đối tượng mới, và cuối cùng là một Thực thể. Để tạo một Thực thể mới hãy dùng Công cụ "Insert Rectangle" để thêm một hình chữ nhật vào lớp đối tuowngj, sau đó bạn có thể bấm chuột phải lên đối tượng và thêm các thuộc tính bên dưới.

Đặt tên nó (trong trường hợp không quan trọng) mainPlayer (hoặc dùng cùng tên mà bạn dùng khi đăng ký Đối tượng vào Kho Đối tượng), và thêm hai thuộc tính vào Đối tượng:

  • image : với giá trị gripe_run_right (tên của tài nguyên)
  • framewidth : với giá trị 64 là kích cỡ của một sprite đơn trong bảng sprite
  • frameheight : chúng ta không định nghĩa giá trị này ở đây bởi vì chúng ta dùng một bảng sprit dòng đơn, và bởi vì trong trường hợp này cơ chế sẽ lấy chiều cao thực của ảnh đặt cho giá trị của nó.

Hai thông số này sẽ được chuyển đi như thông số (cài đặt đối tượng ở trên bằng cách dùng constructor) khi đối tượng sẽ được tạo. Bây giờ bạn có thể hoặc là chỉ định các trường này ở đây trong Tiled, hoặc trực tiếp trong đoạn mã của bạn (khi xử lý nhiều đối tượng, có thể dễ hơn là chỉ cần chỉ định tên trong TIled, và quản lý phần còn lại trong constructor trực tiếp).

Lưu ý: Bạn cũng có thể thêm bao nhiêu thuộc tính tùy thích, chúng ta có trong cài đặt đối tượng được chuyển đến constructor của bạn

Adding an entity

Khi đối tượng được tạo định vị thực thể của bạn trong màn chơi, và trong ví dụ bên dưới xin đảm bảo bạn cũng thay đổi kích thước chữ nhật đối tượng trong Tiled để khớp với kích thước sprite thực tế của bạn

positioning an entity

Định nghĩa lớp va chạm

Chúng ta gần như đã hoàn tất! Bước cuối cùng là định nghĩa lớp va chạm. Đơn giản chúng ta chỉ cần tạo một lớp đối tượng mới tên là "collision" và thêm một số hình dạng cơ bản vào nó. Vậy là xong!

Bây giờ hãy thêm một Lớp Nhóm Đối tượng. Tên lớp này PHẢI chứa từ khóa "collision" cho cơ chế nhận diện nó như là một lớp đối tượng va chạm.

Khi lớp được thêm vào, hãy chọn nó, và "vẽ" bản đồ va chạm màn chơi của bạn bằng cách thêm bất kỳ hình dạng bằng thanh công cụ đối tượng.

object tool bar

Xin lưu ý là melonJS thực hiện phát hiện va chạm bằng cách sử dụng thuật toán Separating Axis Theorem. Tất cả đa giác được dùng cho va chạm cần phải lồi với tất cả các đỉnh được định nghĩa với xoay theo chiều kim đồng hồ. Đa giác lồi khi tất cả các phân đoạn đường nối hai điểm bên trong không xuyên qua bất kỳ cạnh nào của đa giác (có nghĩa là mọi góc đều nhỏ hơn 180 độ), như các hình dưới đây:

Vòng quay của đa giác là theo chiều kim đồng hồ nếu các đỉnh (điểm) của nó được khai báo quay về bên phải (Lưu ý thứ hai: ảnh ở trên là quay NGƯỢC CHIỀU KIM ĐỒNG HỒ)

Ngoài ra nếu bạn cần các hình dạng phức tạp để chỉ định các thông số của môi trường, thì chúng tôi khuyến nghị bạn dùng các phân đoạn đường riêng biệt. Các đường cũng có thể được dùng cho ví dụ khi định nghĩa platform hoặc các yếu tố tường, nơi bạn chỉ cần chỉ ra bên nào của đối tượng có thể va chạm.

Chạy thử

Hãy lưu mọi thứ, và giờ nếu bạn mở lại index.html, bạn sẽ thấy kết quả như thế này: (bấm lên hình để xem nó chạy trong trình duyệt của bạn)

Step 3 Results

Bạn cũng sẽ để ý thấy màn hình tự động di chuyển theo người chơi của chúng ta, cuốn môi trường bối cảnh theo.

Một điều cuối cùng - khi tạo một đối tượng, một hình dạng va chạm măc định sẽ được tự động tạo ra để quản lý va chạm giữa các đối tượng, dựa trên kích cỡ đối tượng bạn định nghĩa trong Tiled. Vì các mục đích gỡ lỗi, bạn có thể bật bảng điều khiển gỡ lỗi bằng cách thêm #debug vào URL trong thanh URL của trình duyệt.

Nếu bạn tải lại trò chơi, và bật "hitbox" bạn sẽ thấy như thế này:

Enabling the debug panel

Hộp va chạm có thể điều chỉnh từ Tiled bằng cách thay đổi kích cỡ đối tượng và khớp với ví dụ ở trên. (Hình dạng va chạm cũng có thể được điều chỉnh thủ công bằng truy cập vào thuộc tính hình dạng toàn thân thực thể).

Lưu ý: Khi dùng Bảng điều khiển gỡ lỗi, viền sprite được vẽ bằng màu xanh lá cây, hình dạng va chạm đã định nghĩa là màu đỏ, và nếu bạn dùng cái gì khác/hơn một hình va chạm chữ nhật, bạn cũng sẽ thấy một hộp màu cam tương ứng với hình chữ nhật nhỏ nhất có chứa tất cả hình dạng va chạm đã định nghĩa (và cũng được gọi là hộp giới hạn thân thực thể).

Phần 4: Thêm một cảnh nền di chuyển cuộn được

Bước này rất dễ. Chúng ta thậm chí không cần thêm một dòng code, bởi vì mọi thứ sẽ được thực hiện thông qua Tiles.

Đầu tiên, hãy xóa màu cảnh nền mà chúng ta đã thêm trước đó ở cuối Phần 1. (để làm điều đó, bạn sẽ cần sửa văn bản tập tin TMX và xóa thuộc tính `backgroundcolor`). Bởi vì cảnh nền sẽ được lấp đầy với các lớp cuộn được của chúng ta, chúng ta không cần màn hình có một màu cụ thể (sau này nó sẽ tiết kiệm một số frame quý giá).

Sau đó chúng ta sẽ dùng hai bối cảnh sau:

/data/img/area01_bkg0.png cho lớp bối cảnh thứ nhất

Parallax background 1

/data/img/area01_bkg1.png cho lớp bối cảnh thứ hai

Parallax background 2

Mở Tiled, và thêm hai Lớp Ảnh mới, đặt tên chúng tùy thích và đảm bảo điều chỉnh chính xác thứ tự lớp (thứ tự hiển thị là từ dưới lên trên)

Layering parallax layers

Bây giờ nhấn chuột phải vào các lớp để định nghĩa các thuộc tính và chúng ta đặt thuộc tính như sau:

  • Bấm nút browse và chọn hình area01_bkg0 cho lớp thứ nhất (Parallax_layer1 trên hình)
  • Làm lại lần nữa cho lớp thứ hai (Parallax_layer2)
Configuring Image Layer properties

Và cuối cùng là thêm một thuộc tính ratio để chỉ định tốc độ di chuyển cuộn của mỗi lớp : chúng tôi sẽ chỉ định giá trị 0.25 cho lớp đầu tiên (Parallax_layer1 trên hình) và giá trị 0.35 cho lớp thứ hai (xin lưu ý là tỷ lệ càng nhỏ, thì tốc độ di chuyển cuộn càng chậm).

Lưu ý là hành vi mặc định đối với Lớp Ảnh là tự động repeated (lặp lại) trên cả trục x và y, đó chính xác là những gì chúng ta muốn ở đây khi tạo hiệu ứng song song.

Chạy thử

"Et voila!". Nếu giờ bạn mở index.html của bạn, bạn sẽ thấy:

Step 4 results

Hãy chơi thử với người chơi của bạn, và tận hưởng góc nhìn :)

Phần 5: Thêm một số đối tượng và kẻ thù cơ bản

Trong phần này chúng ta sẽ thêm một đồng xu có thể thu thập được (mà chúng ta sẽ dùng sau để thêm vào điểm số), bằng cách dùng bảng sprite spinning_coin_gold.png:

Spinning gold coin

Và một kẻ thù cơ bản, bằng cách dùng bảng sprite wheelie_right.png:

Wheelie right sprite

Đồng xu chính nó rất dễ tạo; chúng ta chỉ cần mở rộng me.CollectableEntity. Thật ra, chúng ta có thể dùng nó trực tiếp trong Tiled (mà không cần tạo CoinEntity ở đây), nhưng bởi vì chúng ta sẽ thêm một số điểm và âm thanh sfx sau này khi xu được thu thập, nên hãy làm nó trực tiếp theo cách này.

/**
 * a Coin entity
 */
game.CoinEntity = me.CollectableEntity.extend({
  // extending the init function is not mandatory
  // unless you need to add some extra initialization
  init: function (x, y, settings) {
    // call the parent constructor
    this._super(me.CollectableEntity, 'init', [x, y , settings]);

  },

  // this function is called by the engine, when
  // an object is touched by something (here collected)
  onCollision : function (response, other) {
    // do something when collected

    // make sure it cannot be collected "again"
    this.body.setCollisionMask(me.collision.types.NO_OBJECT);

    // remove it
    me.game.world.removeChild(this);

    return false
  }
});

Nhân tiện, để chắc chắn cho bạn rõ là cả hai cách đều làm được việc này, chúng ta sẽ định nghĩa trực tiếp các thuộc tính đối tượng Xu trong Tiled, vì thế chúng ta không cần thêm gì khác trong hàm khởi tạo lúc này:

Spinning gold coin

Còn kẻ thù của chúng ta, sẽ dài hơn một chút :

/**
 * an enemy Entity
 */
game.EnemyEntity = me.Entity.extend({
  init: function (x, y, settings) {
    // define this here instead of tiled
    settings.image = "wheelie_right";

    // save the area size defined in Tiled
    var width = settings.width;
    var height = settings.height;

    // adjust the size setting information to match the sprite size
    // so that the entity object is created with the right size
    settings.framewidth = settings.width = 64;
    settings.frameheight = settings.height = 64;

    // redefine the default shape (used to define path) with a shape matching the renderable
    settings.shapes[0] = new me.Rect(0, 0, settings.framewidth, settings.frameheight);

    // call the parent constructor
    this._super(me.Entity, 'init', [x, y , settings]);

    // set start/end position based on the initial area size
    x = this.pos.x;
    this.startX = x;
    this.endX   = x + width - settings.framewidth
    this.pos.x  = x + width - settings.framewidth;

    // to remember which side we were walking
    this.walkLeft = false;

    // walking & jumping speed
    this.body.setVelocity(4, 6);

  },

  /**
   * update the enemy pos
   */
  update : function (dt) {

    if (this.alive) {
      if (this.walkLeft && this.pos.x <= this.startX) {
        this.walkLeft = false;
      }
      else if (!this.walkLeft && this.pos.x >= this.endX) {
        this.walkLeft = true;
      }

      // make it walk
      this.renderable.flipX(this.walkLeft);
      this.body.vel.x += (this.walkLeft) ? -this.body.accel.x * me.timer.tick : this.body.accel.x * me.timer.tick;
    }
    else {
      this.body.vel.x = 0;
    }

    // update the body movement
    this.body.update(dt);

    // handle collisions against other shapes
    me.collision.check(this);

    // return true if we moved or if the renderable was updated
    return (this._super(me.Entity, 'update', [dt]) || this.body.vel.x !== 0 || this.body.vel.y !== 0);
  },

  /**
   * colision handler
   * (called when colliding with other objects)
   */
  onCollision : function (response, other) {
    if (response.b.body.collisionType !== me.collision.types.WORLD_SHAPE) {
      // res.y >0 means touched by something on the bottom
      // which mean at top position for this one
      if (this.alive && (response.overlapV.y > 0) && response.a.body.falling) {
        this.renderable.flicker(750);
      }
      return false;
    }
    // Make all other objects solid
    return true;
  }
});

Như bạn có thể thấy ở đây, tôi chỉ định các thuộc tính settings.image và settings.framewidth trong hàm khởi tạo trực tiếp, nghĩa là trong Tiled, tôi sẽ không phải thêm các thuộc tính này vào Đối tượng của tôi (Một lần nữa, tùy ý bạn quyết định cách dùng nó).

Ngoài ra, tôi đang dùng thuộc tính width được cho bởi Tiled để chỉ định đường mà kẻ thù sẽ chạy. Cuối cùng, trong phương thức onCollision, tôi làm cho kẻ thù nhấp nháy nếu có gì đó nhảy lên đầu của nó

Lưu ý rằng một thành phần đồ họa được Thực thể Đối tượng (hoặc một sprite đơn của một chuyển động) có thể truy cập được thông qua thuộc tính `renderable` của Thực thể, nó sẽ giải thích tại sao chúng ta làm như sau: `this.renderable.flicker(750);`

Sau đó một lần nữa, chúng ta thêm một số đối tượng mới này vào Kho Đối tượng

// register our object entities in the object pool
me.pool.register("mainPlayer", game.PlayerEntity);
me.pool.register("CoinEntity", game.CoinEntity);
me.pool.register("EnemyEntity", game.EnemyEntity);

Và chúng ta đã sẵn sàng để hoàn tất màn chơi của chúng ta trong Tiled. Hãy tạo một lớp đối tượng mới, và dùng công cụ Chèn Đối tượng để thêm xu và kẻ thù vào nơi bạn muốn. Bấm chuột phải lên từng đối tượng và đảm bảo đặt tên của chúng là CoinEntity hoặc EnemyEntity.

Step 5

Trước khi thử nghiệm, chúng ta cần chỉnh sửa người chơi để kiểm tra va chạm với các thực thể khác. Để làm điều này, chúng ta cần thêm một lệnh gọi đến hàm me.collision.check(this) trong đoạn mã mainPlayer, hãy xem bên dưới:

/**
 * update the player pos
 */
update : function (dt) {

  if (me.input.isKeyPressed('left')) {
    // flip the sprite on horizontal axis
    this.renderable.flipX(true);

    // update the entity velocity
    this.body.vel.x -= this.body.accel.x * me.timer.tick;

    // change to the walking animation
    if (!this.renderable.isCurrentAnimation("walk")) {
      this.renderable.setCurrentAnimation("walk");
    }
  }
  else if (me.input.isKeyPressed('right')) {
    // unflip the sprite
    this.renderable.flipX(false);

    // update the entity velocity
    this.body.vel.x += this.body.accel.x * me.timer.tick;

    // change to the walking animation
    if (!this.renderable.isCurrentAnimation("walk")) {
      this.renderable.setCurrentAnimation("walk");
    }
  }
  else {
    this.body.vel.x = 0;

    // change to the standing animation
    this.renderable.setCurrentAnimation("stand");
  }

  if (me.input.isKeyPressed('jump')) {
    if (!this.body.jumping && !this.body.falling) {
      // set current vel to the maximum defined value
      // gravity will then do the rest
      this.body.vel.y = -this.body.maxVel.y * me.timer.tick;

      // set the jumping flag
      this.body.jumping = true;
    }
  }

  // apply physics to the body (this moves the entity)
  this.body.update(dt);

  // handle collisions against other shapes
  me.collision.check(this);

  // return true if we moved or if the renderable was updated
  return (this._super(me.Entity, 'update', [dt]) || this.body.vel.x !== 0 || this.body.vel.y !== 0);
},

Cuối cùng nhưng không kém phần quan trọng, vì chúng ta đã thêm một số platform trong màn chơi, hãy chỉnh sửa bộ xử lý onCollision để thâm một hành vi tùy chỉnh cho kiểu "WORLD_SHAPE" và mô phỏng một yếu tố "platform", như nhìn thấy bên dưới.

Xin lưu ý là các hình dạng va chạm đặc biệt mà chúng ta muốn phản ứng như "platforms" ở đây được định nghĩa theo cài đặt thuộc tính kiểu của chúng thành "platform" trong Tiled (Đừng ngại sử dụng bất cứ cái gì bạn cần, miễn là bạn dùng cùng giá trị trên tất cả).

/**
 * colision handler
 */
onCollision : function (response, other) {
  switch (response.b.body.collisionType) {
    case me.collision.types.WORLD_SHAPE:
      // Simulate a platform object
      if (other.type === "platform") {
        if (this.body.falling &&
          !me.input.isKeyPressed('down') &&

          // Shortest overlap would move the player upward
          (response.overlapV.y > 0) &&

          // The velocity is reasonably fast enough to have penetrated to the overlap depth
          (~~this.body.vel.y >= ~~response.overlapV.y)
        ) {
          // Disable collision on the x axis
          response.overlapV.x = 0;

          // Repond to the platform (it is solid)
          return true;
        }

        // Do not respond to the platform (pass through)
        return false;
      }
      break;

    case me.collision.types.ENEMY_OBJECT:
      if ((response.overlapV.y>0) && !this.body.jumping) {
        // bounce (force jump)
        this.body.falling = false;
        this.body.vel.y = -this.body.maxVel.y * me.timer.tick;

        // set the jumping flag
        this.body.jumping = true;
      }
      else {
        // let's flicker in case we touched an enemy
        this.renderable.flicker(750);
      }

      // Fall through

    default:
      // Do not respond to other objects (e.g. coins)
      return false;
  }

  // Make the object solid
  return true;
}

Chạy thử

Và đây là những gì bạn nên có (lưu ý rằng Tôi đã hoàn màn chơi một chút, thêm các nền tảng,...):

Step 5 results

Thử thu thập các đồng xu, tránh kẻ thù hoặc nhảy lên nó!

Phần 6: Thêm một số thông tin HUD cơ bản

Đến lúc hiển thị một số điểm khi thu thập các đồng xu đó.

Chúng ta sẽ dùng một font bitmap để hiển thị điểm số! Để thuận tiện chúng ta đang cung cấp cả thông tin bitmap và dữ liệu được yêu câu,f nhưng việc tạo các tập tin cần thiết bởi chính bạn rất đơn giản, chỉ cần làm theo hướng dẫn nhỏ ở đây

Trong thư mục `data\fnt` bạn sẽ tìm thấy hai tập tin : một .PNG (kết cấu thực sự ) và một .FNT (tập tin định nghĩa font), và ví dụ font chúng tôi cung cấp có tên là "PressStart2P", chúng ta chỉ cần thêm dòng sau vào danh sách tập tin có sẵn để nạp trước chúng:

// game font
{ name: "PressStart2P", type:"image", src: "data/fnt/PressStart2P.png" },
{ name: "PressStart2P", type:"binary", src: "data/fnt/PressStart2P.fnt"},

cẩn thận, vì kiểu tập tin .FNT cần được đặt về nhị phân.

boilerplate chúng ta đã dùng trước đây đã chứa một khung xương HUD mà chúng ta sẽ dùng như một cơ sở cho trò chơi của chúng ta. Khung xương khá đơn giản và bao gồm:

  • một đối tượng được gọi là game.HUD.Container, thừa kế từ me.Container
  • một đối tượng điểm số cơ bản được gọi là game.HUD.ScoreItem, thừa kế từ me.Renderable

HUD container về cơ bản chỉ là một container đối tượng, được định nghĩa là persistent (để nó có thể tồn tại qua các thay đổi màn chơi), được hiển thị trên tất cả đối tượng khác (thuộc tính z đặt về Infinity - vô hạn), và chúng ta cũng làm cho nó không va chạm để nó được bỏ qua trong kiểm tra va chạm.

Đối tượng Điểm số được định nghĩa là floating (để khi chúng ta thêm nó vào container HUD thì chúng ta dùng tọa độ màn hình), và giowf lưu bộ nhớ tạm giá trị điểm số (được định nghĩa dưới game.data).

/**
 * a HUD container and child items
 */
game.HUD = game.HUD || {};

game.HUD.Container = me.Container.extend({
  init: function () {
    // call the constructor
    this._super(me.Container, 'init');

    // persistent across level change
    this.isPersistent = true;

    // make sure we use screen coordinates
    this.floating = true;

    // give a name
    this.name = "HUD";

    // add our child score object
    this.addChild(new game.HUD.ScoreItem(-10, -10));
  }
});

/**
 * a basic HUD item to display score
 */
game.HUD.ScoreItem = me.Renderable.extend({
  /**
   * constructor
   */
  init : function (x, y) {
      // call the parent constructor
      // (size does not matter here)
      this._super(me.Renderable, 'init', [x, y, 10, 10]);

      // local copy of the global score
      this.score = -1;
  },

  /**
   * update function
   */
  update : function (dt) {
    // we don't do anything fancy here, so just
    // return true if the score has been updated
    if (this.score !== game.data.score) {
      this.score = game.data.score;
      return true;
    }
    return false;
  },

  /**
   * draw the score
   */
  draw : function (renderer) {
    // draw it baby !
  }
});

Giờ hãy hiển thị điểm số hiện tại của chúng ta! Để thực hiện chúng ta sẽ đơn giản chỉ cần hoàn tất đối tượng ScoreItem được cho, bằng cách tạo thuộc tính một font cục bộ (bằng cách dùng font bitmap trước đó), và vẽ điểm số đơn giản bằng cách dùng font bitmap :

/**
 * a basic HUD item to display score
 */
game.HUD.ScoreItem = me.Renderable.extend( {
  /**
   * constructor
   */
  init : function (x, y) {
    // call the parent constructor
    // (size does not matter here)
    this._super(me.Renderable, 'init', [x, y, 10, 10]);

    // create the font object
    this.font = new me.BitmapFont(me.loader.getBinary('PressStart2P'), me.loader.getImage('PressStart2P'));

    // font alignment to right, bottom
    this.font.textAlign = "right";
    this.font.textBaseline = "bottom";

    // local copy of the global score
    this.score = -1;
  },

  /**
   * update function
   */
  update : function (dt) {
    // we don't draw anything fancy here, so just
    // return true if the score has been updated
    if (this.score !== game.data.score) {
      this.score = game.data.score;
      return true;
    }
    return false;
  },

  /**
   * draw the score
   */
  draw : function (renderer) {
        // this.pos.x, this.pos.y are the relative position from the screen right bottom
		this.font.draw (renderer, game.data.score, me.game.viewport.width + this.pos.x, me.game.viewport.height + this.pos.y);
  }
});

HUD đã được thêm vào và bị xóa khi chúng ta bắt đầu trò chơi, vì thế không có gì để làm ở đây. Cũng lưu ý rằng chúng ta đang thêm HUD vào thế giới trò chơi sau khi tải màn chơi, như đối tượng me.Container được tự động thiết lập giá trị z theo mặc định (thông qua tính năng autoDepth), điều này sẽ đảm bảo HUD hiển thị chính xác trên cùng của tất cả.

game.PlayScreen = me.ScreenObject.extend({
  /**
   * action to perform on state change
   */
  onResetEvent : function () {
    // load a level
    me.levelDirector.loadLevel("area01");

    // reset the score
    game.data.score = 0;

    // add our HUD to the game world
    this.HUD = new game.HUD.Container();
    me.game.world.addChild(this.HUD);
  },

  /**
   * action to perform when leaving this screen (state change)
   */
  onDestroyEvent : function () {
    // remove the HUD from the game world
    me.game.world.removeChild(this.HUD);
  }
});

Bước cuối cùng của phần này là thay đổi thật sự điểm số khi thu thập một đồng xu ! Bây giờ hãy chỉnh sửa Đối tượng Đồng xu:

onCollision : function () {
  // do something when collected

  // give some score
  game.data.score += 250;

  // make sure it cannot be collected "again"
  this.body.setCollisionMask(me.collision.types.NO_OBJECT);

  // remove it
  me.game.world.removeChild(this);
}

Như bạn có thể thấy, trong hàm onCollision, chúng ta chỉ thay đổi thuộc tính game.data.score bằng cách thêm một số giá trị cho nó, sau đó chúng ta đảm bảo đối tượng không thể được thu thập lần nữa, và xóa đồng xu.

Chạy thử

Chúng ta giờ có thể kiểm tra kết quả, và chúng ta nên có điểm số hiển thị trong góc phải bên dưới của màn hình:

Step 6 results

Phần 7: Thêm một số âm thanh

Trong phần này chúng ta sẽ thâm một số âm thanh vào trò chơi của chúng ta:

  • một âm thanh khi thu thập một xu
  • một âm thanh khi nhảy
  • một âm thanh khi dậm lên kẻ thù
  • một cảnh nền (hoặc trong âm nhạc trò chơi)

Nếu chúng ta nhìn lại cách chúng ta khởi tạo âm thanh, bạn có thể thấy chúng ta chuyển thông số "mp3,ogg" vào hàm khởi tạo initialization function, để chỉ ra chúng ta sẽ cung cấp hai định dạng tập tin âm thanh, một là mp3, và một là ogg và melonJS sau đó sẽ dùng loại phù hợp dựa trên khả năng của trình duyệt.

// initialize the "audio"
me.audio.init("mp3,ogg");

Bây giờ chúng ta hãy chỉnh sửa trò chơi :

Thu thập một đồng xu

Trong đoạn mã CoinEntity, nơi chúng ta từng quản lý điểm kiếm được, chúng ta sẽ chỉ cần thêm một lệnh gọi mớiđến me.audio.play() và dùng tài nguyên âm thanh "cling". Vậy là xong!

onCollision : function () {
  // do something when collected

  // play a "coin collected" sound
  me.audio.play("cling");

  // give some score
  game.data.score += 250;

  // make sure it cannot be collected "again"
  this.body.setCollisionMask(me.collision.types.NO_OBJECT);

  // remove it
  me.game.world.removeChild(this);
}

Nhảy

Trong hàm update() của mainPlayer, chúng ta thêm một lệnh gọi đến me.audio.play() và dùng tài nguyên âm thanh "nhảy". Bạn cũng có thể lưu ý là tôi đã thêm một thử nghiệm trên giá trị trả về của doJump(). doJump có thể trả về sai trong trường hợp bạn không được phép nhảy (đã nhảy,...) và trong trường hợp không cần phát âm thanh sfx.

if (me.input.isKeyPressed('jump')) {
  if (!this.body.jumping && !this.body.falling) {
    // set current vel to the maximum defined value
    // gravity will then do the rest
    this.body.vel.y = -this.body.maxVel.y * me.timer.tick;

    // set the jumping flag
    this.body.jumping = true;

    // play some audio
    me.audio.play("jump");
  }
}

Dậm lên

Và cũng tương tự với hành động này, nhưng dùng tài nguyên "dậm", lần này trong hàm xử lý va chạm của mainPlayer:

/**
 * colision handler
 */
onCollision : function (response, other) {

      // ...

      case me.collision.types.ENEMY_OBJECT:
        if ((response.overlapV.y>0) && !this.body.jumping) {
          // bounce (force jump)
          this.body.falling = false;
          this.body.vel.y = -this.body.maxVel.y * me.timer.tick;

          // set the jumping flag
          this.body.jumping = true;

          // play some audio
          me.audio.play("stomp");
        }
        else {
          // let's flicker in case we touched an enemy
          this.renderable.flicker(750);
        }

        // Fall through

      default:
        // Do not respond to other objects (e.g. coins)
        return false;
    }

  // Make the object solid
  return true;
}

Nhạc khi chơi

Trong main, trong hàm onResetEvent(), chúng ta chỉ cần thêm một lệnh gọi đến hàm me.audio.playTrack(), chỉ rõ track âm thanh nào được dùng:

onResetEvent : function () {
  // play the audio track
  me.audio.playTrack("dst-inertexponent");

  // ...
},

Và chúng ta cũng cần chỉnh sửa hàm onDestroyEvent() để dừng track hiện tại khi thoát trò chơi:

onDestroyEvent : function () {

  // ...

  // stop the current audio track
  me.audio.stopTrack();
}

Vậy là xong! bấm vào đây để xem kết quả cuối cùng.

Phần 8: Thêm một màn chơi thứ hai

Bạn giờ chắc chắn đã biết cách tạo một màn chơi. Tuy nhiên, ở đây tôi sẽ chỉ cho bạn cách tạo một màn chơi khác.

Để làm việc này, melonJS có một Đối tượng tên là me.LevelEntity, chúng ta sẽ thêm nó vào lớp Thực thể trong Tiled và chỉ rõ những gì phải làm khi người chơi chính của chúng ta gặp nó:

Creating an object to go to next level

Giả sử màn chơi mới của chúng ta được gọi là "area02", chúng ta chỉ cần thêm một thuộc tính "to" với giá trị "area02". Vì thế khi người chơi của chúng ta chạm vào Đối tượng, cơ chế sẽ tự động tải màn chơi "area02".

Không bắt buộc nhưng chúng ta có thể yêu cầu cơ chế thêm một hiệu ứng fadeOut/fadeIn khi đổi màn chơi bằng cách thêm thuộc tính màu "fade" và "duration" (theo ms) (như trong hình)

Bấm vào đây để xem kết quả cuối cùng.

Phần 9: Thêm một màn hình tiêu đề

Để hoàn tất, chúng ta hãy thêm một màn hình tiêu đề cho trò chơi, bằng cách dùng các tập tin title_screen.png trong thư mục "/data/img/gui/" (và tất nhiên là thêm vào danh sách tài nguyên, như chúng ta đã làm trước đó với các ảnh khác):

Title screen

và ở trên cùng của màn hình chúng ta sẽ thêm vài thông điệp, và đợi người dùng nhập vào để bắt đầu trò chơi!

Đầu tiên chúng ta hãy khai báo một Đối tượng mới, mở rộng me.ScreenObject:

/**
 * A title screen
 */
game.TitleScreen = me.ScreenObject.extend({
  // reset function
  onResetEvent : function () {
    // ...
  },

  // destroy function
  onDestroyEvent : function () {
    // ...
  }
});

Bây giờ chúng ta muốn:

  • hiển thị hình ảnh cảnh nền ở trên
  • thêm một số nội dung vào giữa màn hình ("Nhấn enter để chơi")
  • đợi người dùng nhập vào (nhấn enter)

Thêm vào đó, tôi cũng muốn thêm một văn bản di chuyển nhỏ về hướng dẫn này.

game.TitleScreen = me.ScreenObject.extend({
  /**
   * action to perform on state change
   */
  onResetEvent : function () {
    // title screen
    var backgroundImage = new me.Sprite(0, 0, {
            image: me.loader.getImage('title_screen'),
        }
    );

    // position and scale to fit with the viewport size
    backgroundImage.anchorPoint.set(0, 0);
    backgroundImage.scale(me.game.viewport.width / backgroundImage.width, me.game.viewport.height / backgroundImage.height);

    // add to the world container
    me.game.world.addChild(backgroundImage, 1);

    // add a new renderable component with the scrolling text
    me.game.world.addChild(new (me.Renderable.extend ({
      // constructor
      init : function () {
        this._super(me.Renderable, 'init', [0, 0, me.game.viewport.width, me.game.viewport.height]);

        // font for the scrolling text
        this.font = new me.BitmapFont(me.loader.getBinary('PressStart2P'), me.loader.getImage('PressStart2P'));

        // a tween to animate the arrow
        this.scrollertween = new me.Tween(this).to({scrollerpos: -2200 }, 10000).onComplete(this.scrollover.bind(this)).start();

        this.scroller = "A SMALL STEP BY STEP TUTORIAL FOR GAME CREATION WITH MELONJS       ";
        this.scrollerpos = 600;
      },

      // some callback for the tween objects
      scrollover : function () {
        // reset to default value
        this.scrollerpos = 640;
        this.scrollertween.to({scrollerpos: -2200 }, 10000).onComplete(this.scrollover.bind(this)).start();
      },

      update : function (dt) {
        return true;
      },

      draw : function (renderer) {
        this.font.draw(renderer, "PRESS ENTER TO PLAY", 20, 240);
        this.font.draw(renderer, this.scroller, this.scrollerpos, 440);
      },
      onDestroyEvent : function () {
        //just in case
        this.scrollertween.stop();
      }
    })), 2);

    // change to play state on press Enter or click/tap
    me.input.bindKey(me.input.KEY.ENTER, "enter", true);
    me.input.bindPointer(me.input.pointer.LEFT, me.input.KEY.ENTER);
    this.handler = me.event.subscribe(me.event.KEYDOWN, function (action, keyCode, edge) {
      if (action === "enter") {
        // play something on tap / enter
        // this will unlock audio on mobile devices
        me.audio.play("cling");
        me.state.change(me.state.PLAY);
      }
    });
  },

  /**
   * action to perform when leaving this screen (state change)
   */
  onDestroyEvent : function () {
    me.input.unbindKey(me.input.KEY.ENTER);
    me.input.unbindPointer(me.input.pointer.LEFT);
    me.event.unsubscribe(this.handler);
  }
});

Chúng ta có gì ở đoạn mã trên?

  1. Trong hàm onResetEvent, chúng ta tạo hai thành phần renderable và thêm chúng vào thế giới trò chơi. Thành phần thứ nhất là một đối tượng Sprite cơ bản sẽ hiển thị ảnh nền tiêu đề, và thành phần thứ hai xử lý văn bản "nhấn ENTER" và một thanh cuộn dựa trên một đối tượng Tween. Lưu ý: Về font, nếu bạn kiểm tra cẩn thận tập tin tương ứng (32x32_font.png), bạn sẽ để ý nó chỉ chứa các ký tự viết hoa, vì vậy hãy đảm bảo chỉ dùng ký tự hoa trong văn bản của bạn.
  2. Chúng ta cũng đồng thời đăng ký sự kiện phím, hoặc sự kiện chuột/chạm để tự động chuyển sang trạng thái PLAY nếu được nhấn.
  3. Khi hủy, chúng ta bỏ kết nối nút và các sự kiện được trỏ đến.

Và tất nhiên điều rất cuối cùng là chỉ cho cơ chế chúng ta đã tạo một đối tượng mới và liên kết nó với trạng thái tương ứng (ở đây, MENU). Ngoài ra, dùng hàm chuyển tiếp me.state, tôi đang bảo cơ chế thêm một hiệu ứng làm mờ giữa các thay đổi trạng thái.

Cuối cùng, thay vì chuyển đổi sang trạng thái PLAY ở cuối hàm được tải, tôi hiện đang chuyển sang trạng thái MENU:

/*
 * callback when everything is loaded
 */
loaded : function () {
  // set the "Play/Ingame" Screen Object
  me.state.set(me.state.MENU, new game.TitleScreen());

  // set the "Play/Ingame" Screen Object
  me.state.set(me.state.PLAY, new game.PlayScreen());

  // set a global fading transition for the screen
  me.state.transition("fade", "#FFFFFF", 250);

  // register our player entity in the object pool
  me.pool.register("mainPlayer", game.PlayerEntity);
  me.pool.register("CoinEntity", game.CoinEntity);
  me.pool.register("EnemyEntity", game.EnemyEntity);

  // enable the keyboard
  me.input.bindKey(me.input.KEY.LEFT, "left");
  me.input.bindKey(me.input.KEY.RIGHT, "right");
  me.input.bindKey(me.input.KEY.X, "jump", true);

  // display the menu title
  me.state.change(me.state.MENU);
}

Chạy thử

Chúc mừng bạn! Bạn đã hoàn tất hướng dẫn này, giờ là lúc thử nghiệm, và bạn sẽ có một trò chơi như thế này:

Your completed game

Phần 10: Kết

Vâng, chúng tôi hy vọng bạn đã tận hưởng khoảng thời gian chúng ta cùng nhau làm quen melonJS, và giờ bạn có thể tự nghiên cứu tiếp. Đây là một phần quan trọng của lập trình và phát triển trò chơi.

Nếu bạn mắc kẹt ở bất kỳ thách thức hoặc phần nào của hướng dẫn, xin tìm kiếm theo vấn đề, hoặc hỏi chúng tôi tại diễn đàn @html5gamedevs

Đừng bao giờ quên hướng dẫn này là vì niềm vui, vì thế hãy cảm thấy vui khi làm nó!