Tạo Sprite Sheet

Thả vào một sheet có sprite nằm rải rác ở bất kỳ đâu — từng sprite được dò riêng, xếp vào cùng một khung chứa, rồi đóng gói thành sheet đều đặn, file atlas JSON chỉ cách một cú bấm.

Thả một sprite sheet, hoặc một bộ ảnh khung hình, hoặc bấm để chọn file

PNGWEBPGIFBMPJPEG

Thả nhiều ảnh cùng lúc thì mỗi ảnh là một khung hình, xếp theo tên file: walk1, walk2, walk10.

Công Cụ Này Làm Gì

Thả vào một sheet có sprite nằm rải rác ở bất kỳ đâu. Từng sprite được dò riêng, cả tập hợp được sắp theo thứ tự đọc, mỗi sprite được xếp vào cùng một khung chứa đồng nhất, và kết quả tải về là một file PNG đã đóng gói, còn file atlas JSON gọi tên nó thì chỉ cách một cú bấm. Vẫn là bộ công cụ mà trang cắt sheet dùng, chỉ khác là mở sẵn ở phần xuất sprite sheet thay vì phần xuất ZIP.

Cách Tìm Các Sprite Nằm Rải Rác

Việc dò tìm làm việc trên độ đục của sheet, mỗi điểm ảnh một byte, và lan dần ra từ mọi điểm ảnh có nội dung mà nó chưa nhận. Lân cận được xét đủ tám hướng, nên hai điểm ảnh chỉ chạm nhau ở góc vẫn thuộc cùng một sprite — nếu chỉ xét bốn hướng vuông góc thì một nét chéo của pixel art sẽ bị xé thành một dãy khung rời rạc.

Có ba con số quyết định kết quả. Con số đầu tiên quyết định việc dò tìm được phép nhìn thấy những gì; hai con số còn lại là bộ lọc trên những gì nó tìm được, và chúng chạy theo đúng thứ tự này:

Điều khiểnChức năngGiá trị khởi đầu
Ngưỡng alphaMức đục để một điểm ảnh được tính là nội dung1
Diện tích tối thiểuLoại khung có chiều rộng nhân chiều cao nhỏ hơn giá trị này4
Khoảng cách gộpNối lại các khung gần nhau hơn giá trị này trên cả hai trục2

Bộ lọc diện tích chạy trước bước gộp là có chủ ý. Một chấm lạc đằng nào cũng sẽ bị bỏ đi thì không được phép kéo giãn khung của sprite bên cạnh trên đường bị loại.

Việc một sheet mở ra ở chế độ cắt nào luôn được quyết định theo cùng một quy tắc. Lưới đo được từ chính các khe hở của sheet thắng tuyệt đối. Nếu không có lưới, mà tìm được nhiều hơn một sprite với chiều rộng hoặc chiều cao chênh nhau quá 10 phần trăm so với sprite lớn nhất, thì các sprite được coi là những vật thể riêng biệt và mỗi cái được giữ riêng. Nếu cả hai điều trên đều không xảy ra, bộ cắt theo lưới vẫn được giữ với các con số do bạn nhập. Dòng trạng thái nói rõ điều gì đã xảy ra và vì sao, và một nút bấm là đổi được.

Sheet Không Có Vùng Trong Suốt

Ảnh JPEG không có kênh alpha, nên phần trông như khoảng trống quanh sprite lại là những điểm ảnh đục thật — và cả hai bộ dò đều sẽ chỉ thấy một khối khổng lồ phủ kín ảnh. Vì vậy công cụ đọc màu chiếm ưu thế ở viền ngoài của sheet, loại màu đó ra trước khi chạy bước dò tìm, và nói cho bạn biết. Dung sai khởi đầu là 16: một điểm ảnh lệch không quá 16 đơn vị RGB so với màu nền sẽ được coi là nền, đủ rộng cho phần nhiễu mà nén JPEG để lại quanh mép hồng cánh sen và đủ hẹp để không đụng tới màu sprite rực rỡ. Cả màu nền lẫn dung sai đều sửa được, và dung sai chạy tới 128.

Thứ Tự Đọc, Không Phải Thứ Tự Quét

Việc dò tìm trả sprite về theo thứ tự bắt gặp điểm ảnh đầu tiên của chúng khi quét sheet từ trên xuống. Đó không phải thứ tự mà mắt người đọc một sprite sheet. Một sprite thấp mà mép trên tình cờ nằm cao hơn mép trên của sprite cao bên cạnh sẽ ra trước nếu chỉ sắp xếp theo toạ độ dọc, dù rõ ràng cả hai cùng nằm trên một hàng.

Nên thay vào đó các sprite được gom thành những dải ngang. Một sprite gia nhập dải hiện tại khi khoảng dọc của nó chồng lên khoảng dọc của dải ít nhất một nửa cái ngắn hơn trong hai khoảng, và khoảng của dải nở ra khi có sprite gia nhập — chính điều này cho phép một dải bám theo hàng sprite trôi dần xuống dưới. Chỉ sau đó từng dải mới được sắp từ trái sang phải.

Một Khung Chứa Cho Mọi Sprite

Mọi sprite đều được xếp vào cùng một khung chứa, và đó là thứ biến sheet đã đóng gói thành một lưới mà game engine đánh chỉ số được. Chế độ tự động lấy sprite lớn nhất trong số đang chọn theo từng trục một cách độc lập — một sprite rộng và một sprite cao cùng lúc sẽ cho ra khung đủ chỗ cho cả hai — cộng thêm phần đệm ở cả hai phía, rồi làm tròn lên số chẵn kế tiếp, hoặc lên luỹ thừa của hai kế tiếp nếu bạn bật tuỳ chọn đó. Chế độ thủ công nhận chiều rộng và chiều cao bạn gõ, hoặc một trong các mức dựng sẵn: 32, 64, 128, 256 và 512.

Phần đệm ăn vào cả bốn cạnh của khung, còn điểm neo quyết định sprite nằm ở đâu trong khoảng còn lại. Mặc định là giữa cạnh dưới, để bàn chân đứng yên: neo một khung nhảy cao hơn vào chính giữa sẽ đẩy bàn chân nhân vật lên so với những khung thấp hơn bên cạnh, và khi hoạt ảnh chạy thì cả nhân vật trông như trượt lên trượt xuống. Cả chín điểm neo đều chỉ cách một cú bấm.

Sprite quá lớn so với khung thì được báo chứ không bị âm thầm thay đổi kích thước. Mặc định là nêu tên các khung không vừa trong một nhãn đỏ, liệt kê rõ sáu khung đầu và đếm số còn lại, rồi vẫn đặt từng khung đó ở nguyên kích thước thật — từ một vị trí lệch âm bên trong ô của nó, nên trên sheet đã đóng gói sprite sẽ tràn sang ô bên cạnh hoặc ra ngoài mép. Đó chính là chủ ý của chế độ cảnh báo: không có gì bị thay đổi kích thước sau lưng bạn, và nhãn đỏ chỉ đích danh những khung cần xử lý. Tăng kích thước khung chứa, bỏ chọn khung đó, hoặc chuyển sang chế độ thu nhỏ đều giải quyết được; chế độ thu nhỏ giảm những sprite đó theo cùng một tỉ lệ trên cả hai trục để giữ nguyên tỉ lệ hình. Mọi vị trí mà công cụ tính ra đều là số nguyên điểm ảnh, nên hình chữ nhật được vẽ vào file PNG và hình chữ nhật được ghi vào file JSON là cùng một hình chữ nhật — kể cả khi hình chữ nhật đó nằm ra ngoài ô của chính nó.

Sheet Đã Đóng Gói Và File Atlas Của Nó

Mặc định sheet xếp các khung thành một lưới gần vuông, ngoài ra còn có kiểu một dải ngang duy nhất, hoặc theo số cột do bạn chọn. Lề được áp cho cả bốn cạnh của sheet; khoảng cách chỉ được chèn giữa các ô liền kề, không bao giờ thừa ra sau ô cuối cùng.

File atlas xuất theo định dạng TexturePacker, ở dạng hash lấy tên file làm khoá hoặc dạng array có tên file trong từng mục. Mỗi mục ghi rõ vị trí các điểm ảnh của sprite trên sheet đã đóng gói, vị trí chính các điểm ảnh đó bên trong khung chứa mà nó khai báo, kích thước của khung chứa ấy, và sprite có bị cắt gọn hay lệch bên trong khung hay không — ba thông tin sau là cách một engine khôi phục đúng vị trí ban đầu của sprite sau khi cắt gọn, và ghi sai chúng thì mãi tận trong game bạn mới phát hiện ra. Phần metadata gọi đúng tên file PNG mà bản xuất sheet ghi ra, nên hai file luôn mô tả lẫn nhau.

Nút tải cả gói gộp sheet, atlas và GIF vào chung một file ZIP dùng cùng một tên gốc. Bản xuất ZIP từng khung hình vẫn còn đó, cách hai thẻ về bên trái, và trên trang này nó mở sẵn với mọi khung hình đã được xếp vào khung chứa chung — nên các file PNG của nó đều ra đúng kích thước khung chứa, mỗi sprite nằm đúng chỗ mà nó nằm trong ô tương ứng của sheet đã đóng gói. Một nút chuyển ngay trên thẻ đó sẽ cho ra file PNG theo đúng kích thước của chính từng khung hình, như lúc nó được cắt ra — thiết lập đó lấy đúng ô khung hình, nên khi bật cắt bỏ viền trong suốt thì nó vẫn xuất nguyên phần chưa cắt.

Câu hỏi thường gặp

Sprite sheet của tôi có bị upload lên đâu không?
Không. Ảnh được giải mã ngay trong trình duyệt, được dò tìm, xếp lại rồi ghi ra file PNG và file JSON bằng JavaScript chạy trong chính tab đó. File không bao giờ đi qua mạng — đó cũng là lý do công cụ vẫn chạy được khi mất mạng, miễn là trang đã tải xong.
Sprite của tôi nằm rải rác chứ không xếp theo lưới. Công cụ có xử lý được không?
Trang này sinh ra để làm đúng việc đó. Khi không đo được lưới đều nào từ sheet, công cụ chuyển sang dò từng sprite một dựa trên độ đục của ảnh, xét đủ tám hướng lân cận — hai điểm ảnh chỉ chạm nhau ở góc vẫn thuộc cùng một sprite, nên một nét chéo của pixel art không bị cắt làm đôi. Cách dò này được chọn tự động khi tìm được nhiều hơn một sprite và chiều rộng hoặc chiều cao của chúng chênh nhau quá 10 phần trăm so với sprite lớn nhất; nếu mọi sprite đều bằng nhau thì công cụ giữ nguyên bộ cắt theo lưới, vì ở đó số cột và số hàng vẫn sửa được. Dù chọn kiểu nào, công cụ đều nói rõ đang dùng kiểu nào và vì sao, kèm một nút để đổi sang kiểu còn lại.
Ba thanh trượt dò sprite có tác dụng gì?
Ngưỡng alpha là mức đục để một điểm ảnh bắt đầu được tính là phần của sprite, chạy từ 1 đến 255 và khởi đầu ở 1 nghĩa là mọi điểm ảnh không trong suốt đều được tính. Diện tích tối thiểu loại bỏ những khung có chiều rộng nhân chiều cao nhỏ hơn giá trị này, chạy từ 1 đến 1024 điểm ảnh vuông và khởi đầu ở 4 để dọn các chấm lạc mà vẫn giữ được sprite vật phẩm nhỏ. Khoảng cách gộp nối lại hai khung khi phần trống giữa chúng nhỏ hơn giá trị này trên cả hai trục cùng lúc, chạy từ 0 đến 64 điểm ảnh và khởi đầu ở 2 — vừa đủ để kéo thanh kiếm hay tia lửa nằm cách thân sprite đúng một điểm ảnh về lại với chủ của nó. Các chấm lạc bị loại trước khi bất cứ thứ gì được gộp, nên một chấm lạc không bao giờ kéo giãn khung của sprite bên cạnh.
Sheet của tôi là ảnh JPEG nền hồng cánh sen. Vẫn tách được chứ?
Được, và công cụ tự đặt sẵn cho bạn. Sheet không có kênh trong suốt thì phần nhìn như khoảng trống quanh sprite thực chất là điểm ảnh đục thật, nên công cụ lấy màu chiếm ưu thế ở viền ngoài của sheet và coi mọi màu gần nó là vùng rỗng khi dò tìm sprite, với dung sai khởi đầu là 16. Dung sai này là bán kính trong không gian màu, tức một điểm ảnh lệch không quá 16 đơn vị so với màu nền sẽ được coi là nền — đủ rộng cho phần nhiễu mà nén JPEG để lại quanh mép hồng cánh sen, và đủ hẹp để không đụng tới màu sprite rực rỡ. Cả màu nền lẫn dung sai đều hiển thị và đều sửa được, thanh trượt dung sai chạy tới 128 cho những sheet nhiễu nhiều hơn.
Vì sao điểm neo mặc định là giữa cạnh dưới?
Để bàn chân nhân vật đứng yên trên mặt đất. Các khung trong một chu kỳ đi hay nhảy hiếm khi cao bằng nhau, và nếu neo khung cao hơn vào chính giữa khung chứa thì bàn chân nhân vật bị đẩy lên so với những khung thấp hơn bên cạnh — khi phát hoạt ảnh, cả nhân vật trông như trượt lên trượt xuống. Neo vào giữa cạnh dưới giữ nguyên đường chân đế. Cả chín điểm neo đều có sẵn cho những sprite treo từ trần hoặc xếp thẳng theo cạnh trái.
Sprite lớn hơn khung chứa thì sao?
Công cụ báo cho bạn biết chứ không âm thầm thay đổi kích thước — nhưng ở chế độ mặc định thì nó cũng không ép sprite vừa khung. Chế độ cảnh báo hiện một nhãn đỏ ngay dưới phần khung chứa, nêu tên các khung không vừa, liệt kê rõ sáu khung đầu và đếm số còn lại, đồng thời giữ nguyên kích thước thật của từng khung. Trên sheet đã đóng gói, điều đó có nghĩa là sprite được vẽ từ một vị trí lệch âm bên trong ô của nó, nên nó tràn sang ô bên cạnh hoặc ra ngoài mép file PNG; file atlas JSON ghi đúng hình chữ nhật tràn ra đó, nên hai file vẫn khớp với nhau. File ZIP theo khung chứa — mặc định của trang này — là kiểu xuất duy nhất làm mất hẳn phần tràn ra thay vì ghi lại nó, vì mỗi khung hình là một file riêng và file kết thúc ngay ở mép khung chứa; nút Kích thước gốc trên thẻ đó thì giữ trọn cả sprite. Nhãn đỏ là cảnh báo duy nhất, và nó có mặt để bạn xử lý: chế độ thu nhỏ sẽ giảm sprite đó theo cùng một tỉ lệ trên cả hai trục nên tỉ lệ hình không bị méo, còn sprite vốn đã vừa thì không bao giờ bị phóng to cho đầy khung. Tăng kích thước khung chứa cũng được, và bỏ chọn khung quá khổ cũng được, vì kích thước khung tự động chỉ đo trên những khung đang được chọn.
File atlas JSON chứa những gì, và engine nào đọc được?
Đó là định dạng TexturePacker, ở dạng hash lấy tên file làm khoá hoặc dạng array có trường tên file trong từng mục. Pixi đọc dạng hash, còn Phaser 3 đọc được cả hai. Mỗi mục ghi rõ vị trí các điểm ảnh của sprite trên sheet đã đóng gói, vị trí chính các điểm ảnh đó bên trong khung chứa mà nó khai báo, kích thước đầy đủ của khung chứa ấy, sprite có bị cắt gọn hay lệch bên trong khung hay không, và một cờ xoay luôn bằng false vì ở đây không có gì xoay sprite trước khi đóng gói. Phần metadata gọi đúng tên file PNG mà bản xuất sheet ghi ra, nên hai file luôn khớp với nhau.