
Làm thế nào để xoay góc nhìn của một bản đồ isometric 2D nhưng cây, mặt đất, bờ biển và các sprite vẫn giữ nguyên hình dạng pixel art? Video DEV LOG dài khoảng 1 phút 22 giây cho thấy một giải pháp đáng chú ý: không xoay trực tiếp toàn bộ TileMap như một tấm ảnh, mà lưu từng ô bản đồ bằng tọa độ logic, sau đó tính lại vị trí hiển thị của từng tile theo góc xoay của camera.
Nhờ cách tiếp cận này, bản đồ tạo cảm giác giống một mặt phẳng 3D đang được quan sát từ nhiều hướng, dù toàn bộ hình ảnh vẫn được kết xuất bằng sprite 2D. Hệ thống trong video còn sử dụng cấu trúc TileMap nhiều lớp dựa trên MultiMesh, hướng tới khả năng hiển thị một bản đồ rất lớn với số lượng cây, ô đất, bờ cát và mặt nước dày đặc.
Lưu ý kiểm chứng: video trình bày ý tưởng, kết quả và nguyên lý vận hành nhưng không công khai mã nguồn đầy đủ. Phần mã và cấu trúc triển khai bên dưới là bản hướng dẫn kỹ thuật được suy dựng từ cơ chế thể hiện trong video, dành cho Godot và các engine 2D có mô hình tương tự.
Kết quả kỹ thuật mà video đang giải quyết

Yêu cầu của hệ thống gồm bốn phần:
Hiển thị một bản đồ pixel art isometric có kích thước rất lớn.
Cho phép góc nhìn quay quanh một tiêu điểm giống camera orbit trong không gian 3D.
Không xoay hoặc làm biến dạng trực tiếp texture của từng tile.
Giữ chi phí kết xuất đủ thấp bằng cách gom nhiều phần tử giống nhau vào MultiMesh.
Nếu chỉ đặt toàn bộ bản đồ vào một Node2D rồi xoay node cha, hình vuông isometric, thân cây, sóng nước và pixel sẽ bị xoay theo. Kết quả có thể méo, mờ và mất chất pixel art. Video tránh vấn đề này bằng cách chỉ thay đổi vị trí của từng phần tử, trong khi sprite vẫn đứng thẳng và giữ đúng hướng thiết kế ban đầu.
Timeline phân tích toàn bộ video

Thời gian | Nội dung thể hiện | Ý nghĩa kỹ thuật |
|---|---|---|
00:00–00:08 | Giới thiệu bản đồ pixel art isometric ở góc nhìn gần | Xác định mục tiêu: xoay góc nhìn 2D đẳng cự |
00:08–00:16 | Thu camera ra xa để thấy toàn cảnh bản đồ | Cho thấy quy mô lớn, nhiều lớp địa hình và số lượng instance dày đặc |
00:16–00:28 | Camera di chuyển và quay quanh một tiêu điểm | Mô phỏng orbit camera, tạo cảm giác mặt phẳng có chiều sâu |
00:28–00:44 | Phóng gần vào bản đồ sau khi đổi góc | Kiểm tra đường bờ, cây, ô đất và thứ tự che khuất |
00:44–00:58 | Giải thích tile không xoay, không biến dạng, chỉ đổi vị trí | Đây là phép chiếu lại tọa độ, không phải xoay texture |
00:58–01:08 | Mỗi tile được đưa vào TileMap tự xây theo tọa độ logic | Tách dữ liệu gameplay khỏi vị trí hiển thị trên màn hình |
01:08–01:16 | Khi góc camera thay đổi, vị trí tile được tính lại theo thời gian thực | Cập nhật transform của instance để tạo hiệu ứng xoay |
01:16–01:22 | Hiển thị kết quả ổn định và kết thúc | Xác nhận giải pháp hoạt động trên bản đồ lớn |
Nguyên lý quan trọng: xoay tọa độ, không xoay sprite

Hãy hình dung mỗi tile có một tọa độ logic (x, y, z). Tọa độ này mô tả vị trí thật trong dữ liệu bản đồ, không phụ thuộc camera đang nhìn từ hướng nào.
Khi góc nhìn thay đổi, hệ thống thực hiện ba bước:
Lấy vị trí logic của tile so với tiêu điểm quay.
Xoay cặp tọa độ mặt phẳng
(x, y)bằng góc camera.Chiếu tọa độ đã xoay sang hệ isometric để nhận vị trí màn hình mới.
Texture của tile không bị xoay. Chỉ transform vị trí của instance được cập nhật. Vì vậy cây vẫn mọc thẳng, mặt đứng của khối đất vẫn giữ hướng pixel được vẽ sẵn và texture không bị nội suy xoay gây nhòe.
Công thức chuyển tọa độ logic sang vị trí hiển thị
Giả sử tâm quay của camera là (focus_x, focus_y), góc quay là angle. Trước tiên đưa tile về hệ tọa độ quanh tiêu điểm:
dx = tile_x - focus_x
dy = tile_y - focus_yXoay tọa độ trên mặt phẳng:
rotated_x = dx * cos(angle) - dy * sin(angle)
rotated_y = dx * sin(angle) + dy * cos(angle)Sau đó chiếu sang isometric:
screen_x = (rotated_x - rotated_y) * tile_width * 0.5
screen_y = (rotated_x + rotated_y) * tile_height * 0.5
screen_y -= tile_z * elevation_stepCuối cùng cộng vị trí tâm hiển thị của camera:
screen_position = camera_focus_screen + Vector2(screen_x, screen_y)Đây là mô hình toán học cơ bản. Trong dự án thật, bạn còn phải thêm offset của sprite, chiều cao địa hình, vị trí chân của cây, layer bờ biển và các sai số cố ý để giảm cảm giác lặp.
Bước 1: Thiết kế bản đồ bằng tọa độ logic

Không nên dùng vị trí pixel trên màn hình làm nguồn dữ liệu chính. Hãy lưu bản đồ dưới dạng lưới logic:
class_name LogicalTile
var grid_position: Vector3i
var tile_type: int
var layer: int
var variant: int
var elevation: int
var instance_index: intMỗi ô cần biết ít nhất:
Tọa độ lưới
x, y.Cao độ hoặc tầng
z.Loại tile: nước, cát, cỏ, vách đất, cây hoặc vật thể.
Biến thể hình ảnh đang dùng.
Nhóm MultiMesh và chỉ số instance tương ứng.
Khi camera quay, dữ liệu logic không đổi. Hệ thống gameplay, tìm đường, va chạm và lưu game vẫn đọc cùng một tọa độ. Chỉ lớp trình bày được chiếu lại.
Bước 2: Chia bản đồ thành nhiều lớp TileMap
Video cho thấy bản đồ có nhiều lớp hình ảnh: nước, nền cát, mặt cỏ, cạnh cao độ và cây. Không nên nhét tất cả vào một MultiMesh duy nhất nếu chúng dùng texture, vật liệu hoặc quy tắc sắp xếp khác nhau.
Một cấu trúc hợp lý:
Layer 0 – Water: mặt nước và hoạt ảnh sóng.
Layer 1 – Ground: cát, cỏ và nền địa hình.
Layer 2 – Elevation: cạnh vách, bờ cao và khối nổi.
Layer 3 – Decoration: đá nhỏ, bụi cây, dấu vết mặt đất.
Layer 4 – Objects: cây, công trình và vật thể có chiều cao.
Mỗi layer có thể chứa nhiều MultiMesh, thường được nhóm theo loại texture hoặc shader. Ví dụ cây cùng atlas có thể dùng chung một MultiMesh; mặt nước dùng MultiMesh khác vì cần shader chuyển động.
Bước 3: Tạo MultiMesh cho số lượng tile lớn
Trong Godot 4, bạn có thể dùng MultiMeshInstance2D cùng MultiMesh ở chế độ transform 2D. Ý tưởng là một lệnh vẽ có thể hiển thị nhiều instance dùng chung mesh và material.
var multimesh := MultiMesh.new()
multimesh.transform_format = MultiMesh.TRANSFORM_2D
multimesh.use_colors = true
multimesh.use_custom_data = true
multimesh.instance_count = tile_count
$GroundMultiMesh.multimesh = multimeshMỗi instance nhận một transform vị trí:
var transform := Transform2D.IDENTITY
transform.origin = projected_position
multimesh.set_instance_transform_2d(instance_index, transform)Nếu dùng atlas, có hai phương án:
Tạo một MultiMesh riêng cho từng loại tile hoặc từng vùng texture.
Dùng custom data của instance và shader để chọn vùng UV trong atlas.
Phương án thứ hai giảm số MultiMesh nhưng shader phức tạp hơn. Với người mới, nhóm theo tile type thường dễ kiểm soát và dễ gỡ lỗi hơn.
Bước 4: Đặt tiêu điểm quay của camera

Video cho thấy bản đồ quay quanh một điểm nằm gần trung tâm vùng quan sát. Tiêu điểm này có thể là nhân vật, con trỏ chuột, tâm màn hình hoặc một tọa độ do người chơi chọn.
var focus_grid := Vector2(128.0, 128.0)
var camera_angle := 0.0Khi người chơi kéo chuột hoặc nhấn phím xoay:
func rotate_view(delta_angle: float) -> void:
camera_angle += delta_angle
rebuild_visible_transforms()Không gọi phép tính lại toàn bộ bản đồ ở mọi frame nếu camera không thay đổi. Chỉ cập nhật khi góc, tiêu điểm, zoom hoặc tập hợp tile đang hiển thị thay đổi.
Bước 5: Tính lại vị trí của từng tile theo góc camera

Đây là phần cốt lõi được giải thích ở cuối video. Một hàm minh họa:
func project_tile(tile: LogicalTile) -> Vector2:
var delta := Vector2(
tile.grid_position.x - focus_grid.x,
tile.grid_position.y - focus_grid.y
)
var c := cos(camera_angle)
var s := sin(camera_angle)
var rotated := Vector2(
delta.x * c - delta.y * s,
delta.x * s + delta.y * c
)
var screen_x := (rotated.x - rotated.y) * TILE_WIDTH * 0.5
var screen_y := (rotated.x + rotated.y) * TILE_HEIGHT * 0.5
screen_y -= tile.elevation * ELEVATION_STEP
return viewport_center + Vector2(screen_x, screen_y)Khi cập nhật MultiMesh:
func update_tile_instance(tile: LogicalTile) -> void:
var t := Transform2D.IDENTITY
t.origin = project_tile(tile)
layer_multimeshes[tile.layer].set_instance_transform_2d(
tile.instance_index,
t
)Lưu ý rằng transform ở đây không chứa rotation của sprite. Đó chính là điểm khác biệt giữa phương pháp này và việc xoay node cha.
Bước 6: Sắp xếp chiều sâu sau khi đổi góc

Chỉ đổi vị trí vẫn chưa đủ. Khi góc nhìn thay đổi, thứ tự trước – sau giữa cây, bờ đất và vật thể cũng thay đổi. Nếu sắp xếp sai, cây ở xa có thể đè lên cây ở gần hoặc bờ đất phía sau chồng lên mặt nước phía trước.
Giá trị sắp xếp có thể dựa trên tọa độ chiếu:
sort_key = projected_y + layer_bias + elevation_biasĐối với sprite cao như cây, hãy dùng vị trí chân cây làm điểm sắp xếp, không dùng tâm texture. Một cây cao 96 pixel nhưng gốc nằm ở đáy phải được xếp theo tọa độ gốc.
MultiMesh không linh hoạt như từng Sprite2D độc lập trong việc thay đổi z-index cho mỗi instance. Vì vậy, bạn có thể:
Chia đối tượng thành nhiều dải độ sâu.
Tạo nhiều MultiMesh theo chunk hoặc theo khoảng
screen_y.Với nhân vật và vật thể tương tác quan trọng, dùng Sprite2D riêng thay vì MultiMesh.
Bước 7: Xử lý các hiện tượng méo phối cảnh

Video thừa nhận rằng vì đây là 2D thuần túy, một số dấu hiệu phối cảnh giả sẽ khó tránh hoàn toàn. Khi vị trí tile thay đổi nhưng texture không đổi hướng, mắt người có thể nhận ra:
Hướng sóng nước không khớp góc nhìn mới.
Mặt đứng của bờ đất vẫn quay về hướng cũ.
Cây giữ nguyên ánh sáng dù bản đồ đã quay.
Một số đường chéo của tile không còn khớp hoàn hảo.
Trong video, chuyển động được thực hiện đủ nhanh nên sai lệch khó gây chú ý. Ngoài cách tăng tốc tween, có thể giảm hiện tượng bằng:
Chuẩn bị bốn biến thể tile cho bốn hướng chính.
Chỉ cho phép xoay theo bước 90 độ hoặc 45 độ.
Chuyển atlas ở giữa hiệu ứng khi người chơi ít nhận thấy nhất.
Dùng fade, motion blur nhẹ hoặc hiệu ứng che chuyển cảnh.
Thiết kế texture ít phụ thuộc hướng chiếu sáng.
Bước 8: Tối ưu cho bản đồ rất lớn
MultiMesh giảm draw call, nhưng không có nghĩa bạn nên cập nhật hàng triệu transform trong mỗi frame. Với bản đồ lớn, cần thêm các lớp tối ưu sau.
Chia bản đồ thành chunk
Chia lưới thành các khối như 32×32 hoặc 64×64 tile. Mỗi chunk sở hữu nhóm MultiMesh riêng. Chỉ các chunk nằm trong hoặc gần vùng camera mới được hiển thị và cập nhật.
Chỉ cập nhật khi góc thật sự thay đổi
Tính sin và cos một lần cho mỗi góc, không tính lại cho từng tile. Nếu xoay bằng tween, có thể cập nhật 30 lần mỗi giây thay vì theo toàn bộ tốc độ khung hình.
Tiền tính cho các góc cố định
Nếu trò chơi chỉ có bốn hướng, hãy tiền tính vị trí cho 0°, 90°, 180° và 270°. Khi xoay, chỉ nội suy giữa hai trạng thái hoặc đổi trạng thái sau hiệu ứng chuyển cảnh.
Tách dữ liệu tĩnh và dữ liệu động
Mặt đất, bờ biển và rừng tĩnh có thể nằm trong MultiMesh. Nhân vật, quái, vật phẩm và cây có thể bị chặt nên dùng node riêng hoặc pool riêng.
Không cập nhật collision theo hình ảnh mỗi frame
Collision, navigation và logic nên giữ trên lưới logic. Camera chỉ thay đổi cách người chơi nhìn thấy bản đồ, không làm thay đổi vị trí thật của vật thể trong gameplay.
Bước 9: Đồng bộ điều khiển chuột và chọn tile
Khi góc nhìn đã quay, việc chuyển từ tọa độ chuột về tile logic phải sử dụng phép biến đổi ngược.
Quy trình:
Lấy vị trí chuột so với tâm hiển thị.
Thực hiện phép chiếu isometric ngược để tìm tọa độ đã xoay.
Xoay ngược tọa độ với góc
-camera_angle.Cộng lại tiêu điểm logic.
Làm tròn hoặc kiểm tra polygon của tile.
Nếu chỉ đổi hình ảnh nhưng không đổi thuật toán chọn tile, con trỏ sẽ chọn sai ô sau khi camera quay.
Kiến trúc gợi ý cho một dự án Godot
WorldRoot
├── LogicalMapData
├── IsometricProjector
├── CameraController
├── ChunkManager
├── RenderLayers
│ ├── WaterMultiMeshes
│ ├── GroundMultiMeshes
│ ├── ElevationMultiMeshes
│ ├── DecorationMultiMeshes
│ └── ObjectMultiMeshes
├── DynamicEntities
│ ├── Player
│ ├── Enemies
│ └── Interactables
└── DebugOverlayTrách nhiệm nên được tách rõ:
LogicalMapData: giữ dữ liệu tile, cao độ, va chạm và loại địa hình.
IsometricProjector: chứa công thức xoay và chiếu tọa độ.
CameraController: xử lý góc, zoom, tiêu điểm và tween.
ChunkManager: nạp, ẩn và cập nhật vùng bản đồ.
RenderLayers: quản lý instance và vật liệu.
Quy trình triển khai thực tế từ bản mẫu đến bản đồ lớn
Tạo bản đồ thử nghiệm 16×16 tile với một lớp nền duy nhất.
Viết hàm chiếu tọa độ logic sang isometric.
Thêm góc camera và xác nhận tile chỉ đổi vị trí.
Thêm layer cao độ và kiểm tra vách đất.
Thêm cây, dùng vị trí chân để sắp xếp.
Chuyển tile tĩnh sang MultiMesh.
Chia bản đồ thành chunk và kiểm tra vùng nhìn.
Thêm phép chọn tile ngược từ chuột.
Đo draw call, thời gian cập nhật transform và bộ nhớ.
Cuối cùng mới mở rộng lên bản đồ hàng trăm nghìn tile.
Đừng bắt đầu ngay bằng bản đồ khổng lồ. Phần khó nhất không phải vẽ nhiều tile mà là bảo đảm phép chiếu, chiều sâu, chọn ô và collision vẫn thống nhất ở mọi góc.
Các lỗi thường gặp
Xoay Node2D cha làm toàn bộ pixel art bị nghiêng
Giải pháp: giữ rotation của sprite bằng 0 và tính lại origin của từng instance.
Cây và nhân vật che nhau sai sau khi quay
Giải pháp: sắp xếp theo vị trí chân sau khi chiếu; chia MultiMesh theo dải độ sâu hoặc chunk.
Camera xoay gây giật mạnh
Giải pháp: tiền tính sin/cos, chỉ cập nhật chunk nhìn thấy và tránh cấp phát object mới trong vòng lặp.
Nhấp chuột chọn sai tile
Giải pháp: áp dụng phép biến đổi ngược, bao gồm cả góc camera và tiêu điểm quay.
Hiệu ứng giả 3D lộ rõ khi xoay chậm
Giải pháp: dùng góc rời rạc, đổi tile variant theo hướng hoặc tăng tốc chuyển động như cách video minh họa.
Khi nào nên dùng 2D MultiMesh và khi nào nên chuyển sang 3D?
Nên dùng 2D MultiMesh | Nên cân nhắc 3D |
|---|---|
Pixel art cần giữ sprite sắc nét | Cần camera nghiêng, zoom và quay tự do |
Góc xoay có giới hạn hoặc chuyển nhanh | Vật thể phải có phối cảnh chính xác ở mọi góc |
Bản đồ có rất nhiều tile tĩnh giống nhau | Cần ánh sáng, bóng đổ và occlusion vật lý |
Gameplay vẫn vận hành trên lưới 2D | Địa hình có chiều cao và va chạm 3D phức tạp |
Muốn kiểm soát hoàn toàn phong cách pixel | Không muốn duy trì nhiều biến thể sprite theo hướng |
Giải pháp trong video đặc biệt phù hợp với game chiến thuật, mô phỏng, xây dựng, quản lý hoặc ARPG pixel art có bản đồ rộng nhưng góc nhìn vẫn được kiểm soát.
Checklist kiểm thử trước khi đưa vào game thật
Tile có giữ độ sắc nét ở mọi góc và mức zoom không?
Đường bờ biển có bị hở hoặc chồng cạnh sau khi quay không?
Thứ tự cây, nhân vật và vách đất có chính xác không?
Con trỏ chuột có chọn đúng tile ở mọi góc không?
Navigation và collision có còn dùng tọa độ logic không?
Thời gian cập nhật khi xoay toàn màn hình là bao nhiêu mili giây?
Số draw call trước và sau khi dùng MultiMesh giảm bao nhiêu?
Chunk ngoài màn hình có được ẩn hoặc giải phóng đúng không?
Hiệu ứng xoay có làm người chơi mất phương hướng không?
Học sâu hơn: tự xây dựng một game ARPG hoàn chỉnh bằng AI
Phép chiếu isometric, quản lý TileMap, tối ưu bản đồ và tổ chức code chỉ là một phần của quy trình phát triển game. Nếu bạn muốn đi tiếp từ bản thử nghiệm kỹ thuật đến một dự án có nhân vật, chiến đấu, quái vật, kỹ năng và vòng lặp gameplay hoàn chỉnh, hãy tham khảo Khóa học làm game ARPG thực chiến bằng AI trong 7 ngày.
Khóa học trình bày lộ trình phát triển game ARPG từ con số không, kết hợp AI trong thiết kế, lập trình và tích hợp các công cụ mã nguồn mở theo từng bước thực hành.
Kết luận
Điểm thông minh của giải pháp trong video nằm ở việc tách hoàn toàn tọa độ logic khỏi tọa độ hiển thị. Bản đồ không thật sự bị xoay như một vật thể 3D. Thay vào đó, mỗi tile được chiếu lại sang vị trí mới dựa trên góc camera, trong khi texture vẫn giữ nguyên.
MultiMesh giải quyết phần kết xuất số lượng lớn; TileMap tự xây quản lý cấu trúc logic; phép xoay và chiếu tọa độ tạo cảm giác camera orbit. Khi kết hợp thêm chunk, sắp xếp chiều sâu và phép chọn tile ngược, kỹ thuật này có thể trở thành nền tảng cho một thế giới pixel art rất rộng mà vẫn giữ được hiệu năng và phong cách hình ảnh nhất quán.
Nguồn hình minh họa: các khung hình được trích từ video DEV LOG do người dùng cung cấp. Bài viết phân tích cơ chế thể hiện trong video và bổ sung bản thiết kế triển khai phục vụ mục đích học tập.









Bình luận
0 bình luận
Đăng nhập để tham gia thảo luận cùng cộng đồng!
Đăng nhập ngayĐang tải bình luận...