Thiết kế toàn bộ bản đồ so với thiết kế mảng gạch


10

Tôi đang làm việc trên một game nhập vai 2D, sẽ có các bản đồ ngục tối / thị trấn thông thường (được tạo trước).

Tôi đang sử dụng gạch, sau đó tôi sẽ kết hợp để tạo ra các bản đồ. Kế hoạch ban đầu của tôi là lắp ráp các ô bằng Photoshop, hoặc một số chương trình đồ họa khác, để có một bức tranh lớn hơn mà sau đó tôi có thể sử dụng làm bản đồ.

Tuy nhiên, tôi đã đọc ở một số nơi mọi người nói về cách họ sử dụng mảng để xây dựng bản đồ của họ trong công cụ (vì vậy bạn đưa một mảng gạch x cho công cụ của mình và nó lắp ráp chúng thành bản đồ). Tôi có thể hiểu nó đã được thực hiện như thế nào, nhưng có vẻ phức tạp hơn rất nhiều khi thực hiện và tôi không thể nhìn thấy những hình ảnh rõ ràng.

Phương pháp phổ biến nhất là gì, và ưu điểm / nhược điểm của mỗi phương pháp là gì?


1
Vấn đề chính là bộ nhớ. Một kết cấu lấp đầy màn hình 1080p một lần là khoảng 60mb. Vì vậy, một hình ảnh sử dụng một int trên pixel, một lát sử dụng một int per (pixel / (brickize * brickize)). Vì vậy, nếu các ô có kích thước 32,32, bạn có thể biểu thị bản đồ lớn gấp 1024 lần bằng cách sử dụng các ô so với pixel. Cũng lần kết cấu trao đổi sẽ làm tổn thương đẩy nhiều bộ nhớ xung quanh vv ...
ClassicThunder

Câu trả lời:


18

Trước hết, hãy để tôi nói rằng các game nhập vai 2D gần gũi và thân thuộc với tôi và làm việc với các công cụ MORPG DX7 VB6 cũ (đừng cười, đó là 8 năm trước, bây giờ :-)) là điều đầu tiên khiến tôi quan tâm đến việc phát triển trò chơi . Gần đây, tôi bắt đầu chuyển đổi một trò chơi mà tôi đã làm việc trên một trong những công cụ đó để sử dụng XNA.

Điều đó nói rằng, đề nghị của tôi là bạn sử dụng cấu trúc dựa trên gạch với phân lớp cho bản đồ của bạn. Với bất kỳ API đồ họa nào bạn sử dụng, bạn sẽ có giới hạn về kích thước họa tiết bạn có thể tải. Chưa kể các giới hạn bộ nhớ kết cấu card đồ họa. Vì vậy, xem xét điều này, nếu bạn muốn tối đa hóa kích thước bản đồ của mình trong khi không chỉ giảm thiểu số lượng và kích thước họa tiết bạn tải vào bộ nhớ mà còn giảm kích thước tài sản của bạn trên ổ cứng của người dùng VÀ thời gian tải, bạn chắc chắn sẽ muốn đi với gạch.

Theo như triển khai, tôi đã đi sâu vào chi tiết về cách tôi xử lý nó trong một vài câu hỏi ở đây trên GameDev.SE và trên blog của tôi (cả hai được liên kết bên dưới), và đó không chính xác là những gì bạn đang hỏi nên tôi sẽ chỉ đi vào những điều cơ bản ở đây Tôi cũng sẽ lưu ý các tính năng của gạch giúp chúng có lợi khi tải một số hình ảnh được kết xuất sẵn lớn. Nếu bất cứ điều gì không rõ ràng, cho tôi biết.

  1. Điều đầu tiên bạn cần làm là tạo một lát gạch. Đây chỉ là một hình ảnh lớn chứa tất cả các ô của bạn được căn chỉnh trong một lưới. Điều này (và có thể thêm một tùy thuộc vào số lượng gạch) sẽ là điều duy nhất bạn cần tải. Chỉ cần 1 hình ảnh! Bạn có thể tải 1 trên mỗi bản đồ hoặc một bản đồ với mọi ô trong trò chơi; bất cứ tổ chức nào làm việc cho bạn.
  2. Tiếp theo, bạn cần hiểu làm thế nào bạn có thể lấy "tờ" đó và dịch từng ô thành một số. Điều này khá đơn giản với một số phép toán đơn giản. Lưu ý rằng phép chia ở đây là phép chia số nguyên , vì vậy các vị trí thập phân được bỏ (hoặc làm tròn xuống, nếu bạn thích). Làm thế nào để chuyển đổi các tế bào để tọa độ và trở lại.
  3. OK, bây giờ bạn đã chia ô gạch thành một chuỗi các ô (số), bạn có thể lấy các số đó và cắm chúng vào bất kỳ vật chứa nào bạn thích. Để đơn giản, bạn chỉ có thể sử dụng một mảng 2D.

    int[,] mapTiles = new int[100,100]; //Map is 100x100 tiles without much overhead
  4. Tiếp theo, bạn muốn vẽ chúng. Một trong những cách bạn có thể làm cho việc này hiệu quả hơn rất nhiều (tùy thuộc vào kích thước bản đồ) là chỉ tính các ô mà máy ảnh hiện đang xem và lặp qua các ô đó. Bạn có thể thực hiện việc này bằng cách tìm nạp tọa độ mảng ô bản đồ của các góc trên cùng bên trái ( tl) và dưới cùng bên phải ( br) của máy ảnh . Sau đó lặp từ tl.X to br.Xvà trong một vòng lặp lồng nhau, từ tl.Y to br.Yđể vẽ chúng. Mã ví dụ dưới đây:

    for (int x = tl.X; x <= br.X;x++) {
        for (int y = tl.Y; y <= br.Y;y++) {
            //Assuming tileset setup from image
            Vector2 tilesetCoordinate = new Vector2((mapTiles[x,y] % 8) * 32,(mapTiles[x,y] / 8) * 32);
            //Draw 32x32 tile using tilesetCoordinate as the source x,y
        }
    }
  5. Giải độc đắc! Đó là những điều cơ bản của động cơ gạch. Bạn có thể thấy rằng thật dễ dàng để có một bản đồ 1000x1000 mà không có quá nhiều chi phí. Ngoài ra, nếu bạn có ít hơn 255 ô, bạn có thể sử dụng một mảng byte cắt giảm bộ nhớ xuống 3 byte cho mỗi ô. Nếu một byte quá nhỏ, một ushort có thể sẽ đủ cho nhu cầu của bạn.

Lưu ý: Tôi đã bỏ qua khái niệm tọa độ thế giới (đó là vị trí máy ảnh của bạn sẽ dựa vào đâu) vì tôi nghĩ, nằm ngoài phạm vi của câu trả lời này. Bạn có thể đọc về điều đó ở đây trên GameDev.SE.

Tài nguyên Động cơ Ngói của tôi
Lưu ý: Tất cả những tài nguyên này được nhắm mục tiêu tại XNA, nhưng nó áp dụng khá nhiều cho mọi thứ - bạn chỉ cần thay đổi các lệnh gọi rút thăm.

  • Câu trả lời của tôi cho câu hỏi này phác thảo cách tôi xử lý các ô bản đồ và xếp lớp trong trò chơi của mình. (Xem liên kết thứ ba.)
  • Câu trả lời của tôi cho câu hỏi này giải thích cách tôi lưu trữ dữ liệu ở định dạng nhị phân.
  • Đây là bài đăng blog đầu tiên (tốt, kỹ thuật thứ hai, nhưng "kỹ thuật") đầu tiên về sự phát triển của trò chơi mà tôi đang làm việc. Toàn bộ loạt bài chứa thông tin về những thứ như đổ bóng pixel, ánh sáng, hành vi gạch, chuyển động và tất cả những thứ thú vị đó. Tôi thực sự đã cập nhật bài đăng để bao gồm nội dung câu trả lời của tôi cho liên kết đầu tiên tôi đã đăng ở trên, vì vậy bạn có thể muốn đọc nó thay vào đó (tôi có thể đã thêm những thứ). Nếu bạn có bất kỳ câu hỏi nào, bạn có thể gửi bình luận ở đó hoặc ở đây và tôi rất sẵn lòng giúp đỡ.

Tài nguyên động cơ ngói khác

  • Hướng dẫn về công cụ gạch từ trang web này đã cho tôi cơ sở mà tôi đã sử dụng để tạo bản đồ của mình.
  • Tôi chưa thực sự xem các video hướng dẫn này vì tôi không có thời gian, nhưng chúng có thể hữu ích. :) Tuy nhiên, chúng có thể bị lỗi thời, nếu bạn đang sử dụng XNA.
  • Đây trang web có một số hướng dẫn hơn (tôi nghĩ) được dựa trên các đoạn video trên. Nó có thể là giá trị kiểm tra.

Đợi đã, đó là thông tin nhiều gấp đôi so với tôi đã hỏi và khá nhiều câu trả lời cho tất cả các câu hỏi tôi không hỏi, thật tuyệt, cảm ơn :)
Cristol.GdM

@Mikalichov - Vui mừng tôi có thể giúp! :)
Richard Marskell - Drackir

Thông thường với mảng 2D bạn muốn sử dụng thứ nguyên đầu tiên là Y của bạn và tiếp theo là X. Trong bộ nhớ, một mảng sẽ được tuần tự 0,0-i sau đó là 1,0-i. Nếu bạn thực hiện các vòng lặp lồng nhau với X đầu tiên, bạn thực sự đang nhảy qua lại trong bộ nhớ thay vì đi theo một con đường đọc tiếp theo. Luôn luôn cho (y) sau đó cho (x).
Brett W

@BrettW - Một điểm hợp lệ. Cảm ơn. Nó khiến tôi tự hỏi làm thế nào các mảng 2D được lưu trữ trong .Net (Thứ tự chính hàng. Đã học được một cái gì đó mới ngày hôm nay! :-)). Tuy nhiên, sau khi cập nhật mã của tôi, tôi nhận ra rằng nó giống hệt như những gì bạn mô tả, chỉ với các biến được chuyển đổi. Sự khác biệt là theo thứ tự gạch được vẽ. Mã ví dụ hiện tại của tôi vẽ từ trên xuống dưới, từ trái sang phải trong khi những gì bạn mô tả sẽ vẽ từ trái sang phải, từ trên xuống dưới. Vì vậy, để đơn giản, tôi quyết định hoàn nguyên nó trở lại ban đầu. :-)
Richard Marskell - Drackir

6

Cả hai hệ thống dựa trên gạch và một mô hình tĩnh / hệ thống kết cấu có thể được sử dụng để đại diện cho một thế giới và mỗi hệ thống có những thế mạnh khác nhau. Cho dù một trong những tốt hơn so với những người khác nắm rõ cách bạn sử dụng các mảnh, và những gì hoạt động tốt nhất cho kỹ năng và nhu cầu của bạn.

Điều đó đang được nói rằng cả hai hệ thống có thể được sử dụng riêng biệt, hoặc cũng có thể cùng nhau.

Một hệ thống dựa trên gạch tiêu chuẩn có các điểm mạnh sau:

  • Mảng 2D đơn giản của gạch
  • Mỗi viên gạch có một vật liệu duy nhất
    • Vật liệu này có thể là một kết cấu đơn, nhiều hoặc pha trộn từ gạch xung quanh
    • Có thể sử dụng lại kết cấu cho tất cả các loại gạch đó, giảm việc tạo và làm lại tài sản
  • Mỗi ô có thể được thông qua hoặc không (phát hiện va chạm)
  • Dễ dàng kết xuất và xác định các phần của bản đồ
    • Vị trí nhân vật và vị trí bản đồ là tọa độ tuyệt đối
    • Đơn giản để lặp qua các ô có liên quan cho vùng màn hình

Nhược điểm của gạch là bạn phải tạo một hệ thống để xây dựng dữ liệu dựa trên ô này. Bạn có thể tạo một hình ảnh sử dụng từng pixel làm hình xếp, do đó tạo mảng 2D của bạn từ một kết cấu. Bạn cũng có thể tạo một định dạng độc quyền. Sử dụng bitflags bạn có thể lưu trữ một số lượng lớn dữ liệu trên mỗi ô trong một không gian khá nhỏ.

Lý do chính khiến hầu hết mọi người làm gạch là vì nó cho phép họ tạo ra các tài sản nhỏ và sử dụng lại chúng. Điều này cho phép bạn tạo ra một hình ảnh lớn hơn nhiều sẽ mảnh nhỏ hơn. Nó giảm việc làm lại bởi vì bạn không thay đổi toàn bộ bản đồ thế giới để thực hiện một thay đổi nhỏ. Ví dụ nếu bạn muốn thay đổi màu của tất cả các loại cỏ. Trong một hình ảnh lớn, bạn sẽ phải sơn lại tất cả các loại cỏ. Trong một hệ thống gạch, bạn chỉ cần cập nhật (các) gạch cỏ.

Tất cả trong tất cả, bạn sẽ thấy mình có khả năng làm lại ít hơn rất nhiều với hệ thống dựa trên ô so với bản đồ đồ họa lớn. Bạn có thể sẽ sử dụng một hệ thống dựa trên gạch để va chạm ngay cả khi bạn không sử dụng nó cho bản đồ mặt đất của mình. Chỉ vì bạn sử dụng một ô bên trong không có nghĩa là bạn không thể sử dụng các mô hình để thể hiện các đối tượng của môi trường có thể sử dụng nhiều hơn 1 ô cho không gian của chúng.

Bạn sẽ cần cung cấp thêm thông tin cụ thể về môi trường / công cụ / ngôn ngữ của bạn để cung cấp các ví dụ về mã.


Cảm ơn! Tôi đang làm việc theo C #, và đang tìm kiếm một phiên bản tương tự (nhưng kém tài năng hơn) của Final Fantasy 6 (hoặc cơ bản nhất là game nhập vai Square SNES), vì vậy gạch lát sàn và gạch cấu trúc (ví dụ như nhà ở). Mối quan tâm lớn nhất của tôi là tôi không thấy cách xây dựng mảng 2D mà không mất hàng giờ để kiểm tra "có một góc cỏ ở đó, rồi ba đường thẳng, rồi một góc nhà, rồi ..", với vô số lỗi có thể xảy ra dọc theo đường.
Cristol.GdM

Có vẻ như Richard đã đề cập đến bạn về các đặc điểm của mã. Cá nhân tôi sẽ sử dụng một liệt kê của tiletypes và sử dụng bitflags để xác định giá trị của ô. Điều này cho phép bạn lưu trữ hơn 32 giá trị trong một số nguyên trung bình. Bạn cũng sẽ có thể lưu trữ nhiều cờ trên mỗi ô. Vì vậy, bạn có thể nói Grass = true, Wall = true, Collision = true, v.v. mà không có các biến riêng biệt. Sau đó, bạn chỉ cần gán "chủ đề" xác định biểu đồ cho đồ họa cho vùng bản đồ cụ thể đó.
Brett W

3

Vẽ bản đồ: Gạch dễ dàng trên động cơ, bởi vì sau đó bản đồ có thể được biểu diễn dưới dạng một mảng lớn các chỉ số gạch. Vẽ mã chỉ là một vòng lặp lồng nhau:

for i from min_x to max_x:
    for j from min_y to max_y:
        Draw(Tiles[i][j], getPosition(i,j))

Đối với bản đồ lớn, một hình ảnh bitmap khổng lồ cho toàn bộ bản đồ có thể chiếm nhiều dung lượng bộ nhớ và có thể lớn hơn những gì card đồ họa hỗ trợ cho một kích thước hình ảnh. Nhưng bạn sẽ không gặp vấn đề gì với các ô vì bạn chỉ phân bổ bộ nhớ đồ họa một lần cho mỗi ô (hoặc một tổng, tối ưu, nếu tất cả đều nằm trên một trang)

Cũng dễ dàng sử dụng lại thông tin ô vuông cho các va chạm. ví dụ: để lấy ô địa hình ở góc trên cùng bên trái của hình nhân vật:

let x,y = character.position
let i,j = getTileIndex(x,y) // <- getTileIndex is the opposite function of getPosition
let tile = Tiles[i][j]

Giả sử bạn cần kiểm tra va chạm với đá / cây, v.v. Bạn có thể lấy gạch ở vị trí của (vị trí ký tự + kích thước ký tự + hướng hiện tại) và kiểm tra xem nó có thể đi bộ hoặc không đi được.

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.