Quản lý bản đồ văn bản trong một mảng 2D sẽ được vẽ trên HTML5 Canvas


46

Vì vậy, tôi đang tạo một game nhập vai HTML5 chỉ để giải trí. Bản đồ có <canvas>chiều rộng (chiều rộng 512px, chiều cao 352px | 16 ô trên, 11 ô từ trên xuống dưới). Tôi muốn biết nếu có một cách hiệu quả hơn để vẽ <canvas>.

Đây là cách tôi có nó ngay bây giờ.

Làm thế nào gạch được tải và vẽ trên bản đồ

Bản đồ đang được vẽ bằng gạch (32x32) bằng cách sử dụng Image()mảnh. Các tập tin hình ảnh được tải thông qua một forvòng lặp đơn giản và đưa vào một mảng được gọi tiles[]là SƠN khi sử dụng drawImage().

Đầu tiên, chúng tôi tải gạch ...

nhập mô tả hình ảnh ở đây

và đây là cách nó được thực hiện:

// SET UP THE & DRAW THE MAP TILES
tiles = [];
var loadedImagesCount = 0;
for (x = 0; x <= NUM_OF_TILES; x++) {
  var imageObj = new Image(); // new instance for each image
  imageObj.src = "js/tiles/t" + x + ".png";
  imageObj.onload = function () {
    console.log("Added tile ... " + loadedImagesCount);
    loadedImagesCount++;
    if (loadedImagesCount == NUM_OF_TILES) {
      // Onces all tiles are loaded ...
      // We paint the map
      for (y = 0; y <= 15; y++) {
        for (x = 0; x <= 10; x++) {
          theX = x * 32;
          theY = y * 32;
          context.drawImage(tiles[5], theY, theX, 32, 32);
        }
      }
    }
  };
  tiles.push(imageObj);
}

Đương nhiên, khi người chơi bắt đầu trò chơi, nó sẽ tải bản đồ mà họ rời đi. Nhưng ở đây, nó là một bản đồ toàn cỏ.

Ngay bây giờ, các bản đồ sử dụng mảng 2D. Đây là một bản đồ ví dụ.

[[4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 1, 1, 1, 1], 
[1, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 1], 
[13, 13, 13, 13, 1, 1, 1, 1, 13, 13, 13, 13, 13, 13, 13, 1], 
[13, 13, 13, 13, 1, 13, 13, 1, 13, 13, 13, 13, 13, 13, 13, 1], 
[13, 13, 13, 13, 1, 13, 13, 1, 13, 13, 13, 13, 13, 13, 13, 1], 
[13, 13, 13, 13, 1, 13, 13, 1, 13, 13, 13, 13, 13, 13, 13, 1], 
[13, 13, 13, 13, 1, 1, 1, 1, 13, 13, 13, 13, 13, 13, 13, 1], 
[13, 13, 13, 13, 13, 13, 13, 1, 13, 13, 13, 13, 13, 13, 13, 1], 
[13, 13, 13, 13, 13, 11, 11, 11, 13, 13, 13, 13, 13, 13, 13, 1], 
[13, 13, 13, 1, 1, 1, 1, 1, 1, 1, 13, 13, 13, 13, 13, 1], 
[1, 1, 1, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 1, 1, 1]];

Tôi nhận được các bản đồ khác nhau bằng cách sử dụng một ifcấu trúc đơn giản . Khi mảng 2d ở trên là return, số tương ứng trong mỗi mảng sẽ được vẽ theo Image()lưu trữ bên trong tile[]. Sau đó drawImage()sẽ xảy ra và vẽ theo xyvà lần nó bằng cách 32vẽ trên x-ytọa độ chính xác .

Làm thế nào nhiều chuyển đổi bản đồ xảy ra

Với trò chơi của tôi, bản đồ có năm điều để theo dõi: currentID, leftID, rightID, upID, và bottomID.

  • currentID: ID hiện tại của bản đồ bạn đang ở.
  • leftID: ID nào currentIDsẽ tải khi bạn thoát bên trái bản đồ hiện tại.
  • rightID: ID nào currentIDsẽ tải khi bạn thoát ở bên phải bản đồ hiện tại.
  • downID: ID nào currentIDsẽ tải khi bạn thoát ở dưới cùng của bản đồ hiện tại.
  • upID: ID nào currentIDsẽ tải khi bạn thoát trên đỉnh bản đồ hiện tại.

Một cái gì đó để lưu ý: Nếu một trong hai leftID, rightID, upID, hoặc bottomIDlà KHÔNG cụ thể, điều đó có nghĩa họ là một 0. Điều đó có nghĩa là họ không thể rời khỏi phía đó của bản đồ. Nó chỉ đơn thuần là một cuộc phong tỏa vô hình.

Vì vậy, một khi một người thoát ra một bên của bản đồ, tùy thuộc vào nơi họ thoát ra ... ví dụ nếu họ thoát ra ở phía dưới, bottomIDsố lượng maptải sẽ được vẽ và do đó sẽ được vẽ trên bản đồ.

Đây là một .GIF mang tính đại diện để giúp bạn hình dung rõ hơn:

nhập mô tả hình ảnh ở đây

Như bạn có thể thấy, sớm hay muộn, với nhiều bản đồ tôi sẽ xử lý nhiều ID. Và điều đó có thể có thể có một chút bối rối và bận rộn.

Ưu điểm rõ ràng là nó tải 176 gạch cùng một lúc, làm mới một khung vẽ nhỏ 512x352 và xử lý một bản đồ tại một thời điểm. Mặt trái là các id MAP, khi xử lý nhiều bản đồ, đôi khi có thể gây nhầm lẫn.

Câu hỏi của tôi

  • Đây có phải là một cách hiệu quả để lưu trữ bản đồ (được sử dụng cho các ô), hoặc có cách nào tốt hơn để xử lý bản đồ?

Tôi đã suy nghĩ dọc theo dòng của một bản đồ khổng lồ. Kích thước bản đồ là lớn và tất cả chỉ là một mảng 2D. Chế độ xem, tuy nhiên, vẫn là 512x352 pixel.

Đây là một .gif khác tôi đã thực hiện (cho câu hỏi này) để giúp trực quan hóa:

nhập mô tả hình ảnh ở đây

Xin lỗi nếu bạn không thể hiểu tiếng Anh của tôi. Hãy hỏi bất cứ điều gì bạn gặp khó khăn để hiểu. Hy vọng, tôi đã làm cho nó rõ ràng. Cảm ơn.


16
Nỗ lực đặt vào câu hỏi này với đồ họa và tất cả đều xứng đáng được nâng cấp. :)
Tapio

Câu trả lời:


5

Chỉnh sửa: Chỉ thấy rằng câu trả lời của tôi dựa trên mã của bạn nhưng thực sự không trả lời câu hỏi của bạn. Tôi giữ câu trả lời cũ trong trường hợp bạn có thể sử dụng thông tin đó.

Chỉnh sửa 2: Tôi đã sửa 2 vấn đề với mã gốc: - Bổ sung +1 trong tính toán kết thúc x và y là do nhầm lẫn bên trong dấu ngoặc, nhưng nó cần được thêm vào sau khi chia. - Tôi quên xác nhận giá trị x và y.

Bạn chỉ cần có bản đồ hiện tại trong bộ nhớ và tải các bản đồ xung quanh. Hãy tưởng tượng một thế giới bao gồm một mảng 2d gồm các cấp đơn có kích thước 5x5. Người chơi bắt đầu trong trường 1. Vì giới hạn trên cùng và bên trái của cấp độ nằm ở rìa thế giới, họ không cần phải tải. Vì vậy, trong trường hợp đó, cấp 1/1 đang hoạt động và cấp 1/2 và 2/1 được tải ... Nếu bây giờ người chơi di chuyển sang phải, tất cả các cấp (ngoài cấp độ bạn di chuyển đến) sẽ không được tải và môi trường xung quanh mới nạp vào. Có nghĩa là cấp 2/1 hiện đang hoạt động, 1/1, 2/2 và 3/1 hiện đang được tải.

Tôi hy vọng điều này cung cấp cho bạn một ý tưởng làm thế nào nó có thể được thực hiện. Nhưng phương pháp này sẽ không hoạt động tốt, khi mọi cấp độ phải được mô phỏng trong trò chơi. Nhưng nếu bạn có thể đóng băng các mức không sử dụng thì nó sẽ hoạt động tốt.

Câu trả lời cũ:

Những gì tôi làm khi hiển thị mức (cũng dựa trên ô) là để tính toán các mục từ mảng gạch giao với khung nhìn và sau đó tôi chỉ hiển thị các ô đó. Như thế bạn có thể có bản đồ lớn nhưng chỉ cần hiển thị phần trên màn hình.

//Mock values
//camera position x
//viewport.x = 233f;
//camera position y
//viewport.y = 100f;
//viewport.w = 640
//viewport.h = 480
//levelWidth = 10
//levelHeight = 15
//tilesize = 32;

//startX defines the starting index for the x axis.
// 7 = 233 / 32
int startX = viewport.x / tilesize;

//startY defines the starting index
// 3 = 100 / 32
int startY = viewport.y / tilesize;

//End index y
// 28 = (233 + 640) / 32 + 1
int endX = ((viewport.x + viewport.w) / tilesize) + 1;

//End index y
// 19 = (100 + 480) / 32 + 1
int endX = ((viewport.x + viewport.w) / tilesize) + 1;

//Validation
if(startX < 0) startX = 0;
if(startY < 0) startY = 0;
//endX is set to 9 here
if(endX >= levelWidth) endX = levelWidth - 1;
//endX is set to 14 here
if(endY >= levelHeight) endY = levelHeight - 1;

for(int y = startY; y < yEnd; y++)
    for(int x = startX; x < xEnd; x++)
          [...]

Lưu ý: Mã ở trên không được kiểm tra nhưng cần đưa ra ý tưởng phải làm gì.

Sau một ví dụ cơ bản cho một đại diện khung nhìn:

public class Viewport{
    public float x;
    public float y;
    public float w;
    public float h;
}

Một đại diện javascript sẽ trông như thế nào

<script type="text/javascript">
//Declaration
//Constructor
var Viewport = function(xVal, yVal, wVal, hVal){
    this.x = xVal;
    this.y = yVal;
    this.w = wVal;
    this.h = hVal;
}
//Prototypes
Viewport.prototype.x = null;
Viewport.prototype.y = null;
Viewport.prototype.w = null;
Viewport.prototype.h = null;
Viewport.prototype.toString = function(){
    return ["Position: (", this.x, "/" + this.y + "), Size: (", this.w, "/", this.h, ")"].join("");
}

//Usage
var viewport = new Viewport(23, 111, 640, 480);
//Alerts "Position: (23/111), Size: (640/480)
alert(viewport.toString());
</script>

Nếu bạn lấy ví dụ mã của tôi, bạn đơn giản phải thay thế khai báo int và float bằng var. Đồng thời đảm bảo rằng bạn sử dụng Math.floor cho các tính toán đó, được gán cho các giá trị int, để có cùng hành vi.

Một điều tôi sẽ xem xét (mặc dù tôi không chắc điều này có quan trọng trong javascript không) là đặt tất cả các ô (hoặc càng nhiều càng tốt) vào một kết cấu lớn thay vì sử dụng nhiều tệp đơn lẻ.

Đây là một liên kết giải thích làm thế nào để làm điều đó: http://thiscouldbebetter.wordpress.com/2012/02/25/slicing-an-image-into-tiles-in-javascript/


Chỉ cần làm rõ ... viewport.x sẽ được xác định là "531" và viewport.y sẽ là "352" và brickize = "32". Và như vậy..?
kiểm tra

Nếu bạn có nghĩa là vị trí của máy ảnh là 531 thì có. Tôi đã thêm một số ý kiến ​​cho mã ở trên.
tom van xanh

Tôi đang sử dụng JavaScript ... Tôi là Java gần giống với điều này.
kiểm tra

Vâng, bạn chỉ cần tạo một lớp khung nhìn dựa trên js (hoặc bạn chỉ có thể tạo 4 biến x, y, w và h). Ngoài ra, bạn phải đảm bảo sàn (Math.floor) các phép tính đó, được gán cho các giá trị int. Tôi đã thêm một ví dụ mã, lớp viewport sẽ trông như thế nào. Chỉ cần chắc chắn rằng bạn đặt khai báo trước khi sử dụng.
tom van xanh

Chỉ cần nói rằng bạn đặt 640, 480.. nhưng nó có thể làm việc với 512, 352phải không?
kiểm tra

3

Lưu trữ bản đồ dưới dạng các ô rất hữu ích cho phép bạn sử dụng lại tài sản và thiết kế lại bản đồ. Thực tế mặc dù nó không thực sự mang lại nhiều lợi ích cho người chơi. Thêm vào đó, sau đó bạn sẽ vẽ lại mỗi ô riêng lẻ mỗi lần. Đây là những gì tôi sẽ làm:

Lưu trữ toàn bộ bản đồ như một mảng lớn. Không có điểm nào có bản đồ và các bản con, cũng như các bản đồ phụ có khả năng (ví dụ: nếu bạn đang vào các tòa nhà). Dù sao bạn cũng đang tải tất cả và điều này giúp mọi thứ tốt đẹp và đơn giản.

Kết xuất bản đồ tất cả cùng một lúc. Có một khung vẽ ngoài màn hình mà bạn kết xuất bản đồ và sau đó sử dụng nó trong trò chơi của bạn. Trang này chỉ cho bạn cách thực hiện với chức năng javascript đơn giản:

var renderToCanvas = function (width, height, renderFunction) {
  var buffer = document.createElement('canvas');
  buffer.width = width;
  buffer.height = height;
  renderFunction(buffer.getContext('2d'));
  return buffer;
};

Điều này là giả sử bản đồ của bạn sẽ không thay đổi nhiều. Nếu bạn muốn kết xuất các bản đồ con tại chỗ (ví dụ: bên trong tòa nhà), bạn chỉ cần kết xuất nó thành một khung vẽ và sau đó vẽ nó lên trên. Đây là một ví dụ về cách bạn có thể sử dụng điều này để hiển thị bản đồ của mình:

var map = renderToCanvas( map_width*32, map_height*32, renderMap );

var MapSizeX = 512;
var MapSizeY = 352;

var draw = function(canvas, mapCentreX, mapCentreY) {
  var context = canvas.getContext('2d');

  var minX = playerX - MapSizeX/2;
  var minY = playerY - MapSizeY/2;

  context.drawImage(map,minX,minY,MapSizeX,MapSizeY,0,0,MapSizeX,MapSizeY);
}

Điều này sẽ vẽ một phần nhỏ của bản đồ tập trung xung quanh mapCentreX, mapCentreY. Điều này sẽ cung cấp cho bạn cuộn trơn tru trên toàn bộ bản đồ, cũng như chuyển động của ô phụ - bạn có thể lưu trữ vị trí người chơi dưới dạng 1/32 của ô, để bạn có thể di chuyển trơn tru trên bản đồ (Rõ ràng nếu bạn muốn di chuyển từng ô như một sự lựa chọn theo phong cách, bạn chỉ có thể tăng theo từng phần 32).

Bạn vẫn có thể lập bản đồ nếu muốn, hoặc nếu thế giới của bạn rộng lớn và không phù hợp với bộ nhớ trên các hệ thống mục tiêu của bạn (hãy nhớ rằng ngay cả với 1 byte cho mỗi pixel, bản đồ 512x352 là 176 KB) nhưng trước -chuyển đổi bản đồ của bạn như thế này, bạn sẽ thấy hiệu suất tăng lớn trong hầu hết các trình duyệt.

Điều này mang lại cho bạn là sự linh hoạt của việc sử dụng lại các ô trên toàn thế giới mà không phải đau đầu khi chỉnh sửa một hình ảnh lớn, nhưng cũng cho phép bạn chạy nó nhanh chóng và dễ dàng và chỉ xem xét một 'bản đồ' (hoặc nhiều 'bản đồ' như bạn muốn) .


2

Trong tất cả các triển khai tôi đã thấy các bản đồ ô vuông thường được tạo thành một hình ảnh lớn và sau đó "chunk" thành các phần nhỏ hơn.

https://gamedev.stackexchange.com/a/25035/10055

Những khối này thường có sức mạnh bằng 2, vì vậy chúng phù hợp với bộ nhớ một cách tối ưu.


1

Một cách dễ dàng để theo dõi ID sẽ chỉ đơn giản là sử dụng một mảng 2d khác với chúng: bằng cách duy trì x, y trên "mảng meta" đó, bạn có thể dễ dàng tra cứu các ID khác.

Điều đó đang được nói, đây là Google tham gia trò chơi canvas được lát gạch 2D (thực ra là nhiều người chơi HTML5, nhưng công cụ xếp gạch cũng được bảo hiểm) từ I / O 2012 gần đây của họ: http://www.youtube.com/watch?v=Prkyd5n0P7k Nó cũng có sẵn mã nguồn.


1

Ý tưởng của bạn là tải toàn bộ bản đồ vào một mảng trước tiên, là một cách hiệu quả, nhưng sẽ dễ dàng có được JavaScript Tiêm, bất kỳ ai cũng có thể biết bản đồ và thực hiện cuộc phiêu lưu.

Tôi cũng đang làm việc trên một trò chơi RPG dựa trên web, cách tôi tải bản đồ của mình thông qua Ajax từ một trang PHP, tải bản đồ từ cơ sở dữ liệu, trong đó có chữ X, vị trí Z, vị trí Z và vlaue của gạch, vẽ nó, và cuối cùng người chơi di chuyển.

Tôi vẫn đang làm việc với nó để làm cho nó hiệu quả hơn, nhưng bản đồ tải nhanh đến mức đủ để giữ nó theo cách này cho đến bây giờ.


1
Tôi có thể xem bản demo không?
kiểm tra

1

Không , một mảng là cách lưu trữ bản đồ gạch hiệu quả nhất .

Có thể có một vài cấu trúc đòi hỏi ít bộ nhớ hơn nhưng chỉ với chi phí cao hơn nhiều cho việc tải và truy cập dữ liệu.


Tuy nhiên, điều cần xem xét là khi bạn sẽ tải bản đồ tiếp theo. Cho rằng các bản đồ không đặc biệt lớn, điều thực sự sẽ mất nhiều thời gian nhất là chờ máy chủ phân phối bản đồ. Việc tải thực tế sẽ chỉ mất vài mili giây. Nhưng sự chờ đợi này có thể được thực hiện không đồng bộ, nó không phải chặn dòng chảy của trò chơi. Vì vậy, điều tốt nhất là không tải dữ liệu khi cần mà trước đó. Người chơi ở trong một bản đồ, bây giờ tải tất cả các bản đồ liền kề. Người chơi di chuyển trong bản đồ tiếp theo, tải lại tất cả các bản đồ liền kề, v.v., v.v. Người chơi thậm chí sẽ không nhận thấy rằng trò chơi của bạn đang tải trong nền.

Bằng cách này, bạn cũng có thể tạo ra ảo ảnh về một thế giới ít biên giới hơn bằng cách vẽ cả các bản đồ liền kề, trong trường hợp đó, bạn cần phải tải không chỉ các bản đồ liền kề mà cả các bản đồ liền kề.


0

Tôi không chắc tại sao bạn nghĩ: Như bạn có thể thấy, sớm hay muộn, với nhiều bản đồ tôi sẽ xử lý nhiều ID. Và điều đó có thể có thể có một chút bối rối và bận rộn.

LeftID sẽ luôn là -1 củaID hiện tại, ID bên phải sẽ luôn là +1 của ID hiện tại.

UpID sẽ luôn là - (tổng chiều rộng bản đồ) của ID hiện tại và downID sẽ luôn là + (tổng chiều rộng bản đồ) của ID hiện tại ngoại trừ số 0 có nghĩa là bạn đã chạm vào cạnh.

Một bản đồ có thể được chia thành nhiều bản đồ và được lưu tuần tự với mapIDXXXX trong đó XXXX là id. Theo cách đó, không có gì phải nhầm lẫn. Tôi đã đến trang web của bạn và dường như, tôi có thể sai, rằng vấn đề là do trình chỉnh sửa bản đồ của bạn áp đặt giới hạn kích thước gây cản trở tự động hóa của bạn để phá vỡ một bản đồ lớn thành nhiều bản đồ nhỏ và hạn chế này là có lẽ hạn chế một giải pháp kỹ thuật.

Tôi đã viết một trình chỉnh sửa bản đồ Javascript cuộn theo mọi hướng, 1000 x 1000 (tôi không khuyến nghị kích thước đó) và nó vẫn di chuyển tốt như bản đồ 50 x 50 và đó là với 3 lớp bao gồm cuộn parallax. Nó lưu và đọc ở định dạng json. Tôi sẽ gửi cho bạn một bản sao nếu bạn nói rằng bạn không phiền một email khoảng 2meg. Nó có nghĩa là trên github nhưng tôi chưa có bất kỳ lát (hợp pháp) nào, đó là lý do tại sao tôi không bận tâm để đặt nó lên đó.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.