Khảo sát về sprite engine
Lời nói đầu
Khi nói về trò chơi điện tử (video game) là ta nói về 3 yếu tố cấu thành nên nó: hình ảnh, âm thanh và sự tương tác giữa người chơi với CPU qua các nút bấm. Dù là hệ máy chơi game nào đi nữa, từ thời nguyên sơ cho đến những thế hệ sau này cũng đều có chung cấu trúc đó. Nói về hình ảnh thì ta có hai yếu tố chủ đạo là bối cảnh (background) và sprite. Đây là những thuật ngữ mà người chơi game ngày nay không còn xa lạ, nhưng ít ai hiểu rõ về nó.
Bài viết này giới thiệu những nét tổng quan về sprite và khảo sát engine điều khiển sprite ở một số game thương mại nổi tiếng trong thời Super Famicom (SFC/SNES).
Khái quát về sprite
Mọi hình ảnh được hiển thị trên màn hình trong một trò chơi điện tử đều được phân loại thành 2 thành phần: bối cảnh (background) hoặc sprite. Trong đó bối cảnh là những thành phần đồ họa cố định trên màn hình, không có khả năng di chuyển bằng cách thay đổi tọa độ của chúng, chẳng hạn như nhà cửa/cây cối/vật thể trên màn hình. Định nghĩa này có thể thay đổi ít nhiều ở những thế hệ máy chơi game sau này, nhưng điều này luôn đúng đối với hệ máy Famicom (FC/NES) và Super Famicom (SFC/SNES). Một số game thương mại dùng lớp bối cảnh để diễn đạt vật thể di chuyển, nhưng đó là những xảo thuật đặc biệt, không phải đặc tính vốn có của bối cảnh.
Trong khi đó, sprite là thành phần đồ họa có thể di chuyển (thay đổi vị trí) dễ dàng trên màn hình thông qua thông số điều khiển tọa độ của nó. Do vậy, sprite thường được dùng để diễn tả nhân vật trong game. Chẳng hạn, Mario và lũ quái phe địch đều là các sprite.
Sprite của một số game thương mại trên thị trường
Máy SFC có khả năng hiển thị tối đa 128 sprite trên màn hình. Mỗi sprite có độ sâu màu 4bpp, tức có khả năng hiển thị 16 màu cho mỗi pallette. Phần sprite có thể sử dụng 8 pallette nên tổng số màu mà toàn bộ sprite trên màn hình có thể sử dụng là 128 màu.
Tên gọi chính thức của sprite trong các tài liệu chính thức về SFC do Nintendō phát hành là OAM (Object Attribute Memory). Đó là một vùng memory trong CPU chuyên điều khiển sprite. OAM có kích thước 0x0220 byte, gồm 2 table. Table chính nằm ở vị trí $00 ~ $0200, còn table phụ nằm ở vị trí $0200 ~ $0220 trong vùng CPU này.
Table chính của sprite có kích thước 0x0200 byte (128 sprite x 4 byte), chuyên điều khiển tọa độ X, Y và thuộc tính của mỗi sprite, được xác định qua 4 byte với cấu trúc như sau:
Tọa độ X - Tọa độ Y - ID của hình ảnh trong Vram - Thuộc tính
Trong đó:
Byte 1 là tọa độ X: xác định vị trí mà sprite xuất hiện trên trục hoành, có giá trị từ 0 ~ 255 (do màn hình hiển thị của máy SFC có bề ngang tối đa là 256 pixel), tăng dần từ trái qua phải. Đó là trong trường hợp bit 0 của table thứ 2 được tắt/đóng/reset/clear. Chi tiết được trình bày trong phần OAM table thứ 2.
Byte 2 là tọa độ Y: xác định vị trí mà sprite xuất hiện trên trục tung, có giá trị từ 0 ~ 224 (đối với hệ NTSC, do ở TV hệ NTSC thì từ scanline thứ 224 trở đi là vùng Vblank, tức màn hình sẽ tắt để máy cập nhật hình ảnh, và để đường scanline trở về vị trí tọa độ gốc. Con số này là 240 đối với máy hệ PAL), tăng dần từ trên xuống dưới.
Byte 3 là ID của hình ảnh: số thứ tự của tile (mảng) đồ họa trong Vram cấu thành nên sprite. Sprite có thể dùng 512 ID (từ 0x0000 tới 0x01FF) trong Vram. Tuy nhiên, byte 3 chỉ có thể chỉ đến 256 ID đầu tiên trong Vram, phần còn lại được thiết lập ở byte 4 như bên dưới.
Byte 4 là thuộc tính của sprite, có cấu trúc: vhoopppc
Trong đó bit cực tả (v) khi được bật/mở/set sẽ lật ngược hình ảnh của sprite theo chiều dọc (lật quanh trục hoành), còn khi nó tắt/đóng/reset/clear thì hình ảnh không bị lật ngược theo chiều dọc. Bit 6 (h) khi được bật/mở/set sẽ lật ngược hình ảnh của sprite theo chiều ngang (lật quanh trục tung). Bit 5, bit 4 (oo) là 2 bit thiết lập mức độ ưu tiên của sprite: mức ưu tiên cao nhất được hiển thị ở lớp trên cùng của màn hình, đè lên trên sprite có mức ưu tiên thấp hơn. Bit 3, 2, 1 (ppp) thiết lập số ID của pallette màu cho sprite đó. Có thể lựa chọn 1 trong 8 pallette cho sprite với các thiết lập như sau.
000: pallette #0
001: pallette #1
010: pallette #2
011: pallette #3
100: pallette #4
101: pallette #5
110: pallette #6
111: pallette #8
Bit 0 (c) kích hoạt địa chỉ ID của hình ảnh sprite trong Vram (0x0100 ~ 0x01FF) khi được bật/mở/set.
Giả dụ trong OAM table chính có 4 byte như sau: $10 - $28 - $04 - $2C thì ta hiểu là sprite đó xuất hiện ở hoành độ 16 (0x10), tung độ 40 (0x28), dùng mảng (tile) hình ảnh thứ #4 trong Vram, dùng pallette thứ 6 và có mức độ ưu tiên 2, không bị lật ngược hình ảnh (0x2C).
Dưới đây là một ví dụ về sprite trong game Rockman do Capcom phát triển cho SFC năm 1995.
Ta có thể thấy nhân vật Rockman được cấu thành từ nhiều sprite như: sprite phần đầu, sprote phần thân, các sprite phần tay và những chi tiết khác. Trong đó 4 byte của sprite phần đầu được định nghĩa như phần khoanh đen: $5B - $9F - $00 -$62.
Qua 3 byte đầu tiên thì ta thấy phần đầu của Rockman có hoành độ 91, tung độ 159 (dĩ nhiên là tọa độ này có thể thay đổi theo vị trí của nhân vật), phần hình ảnh của sprite sử dụng mảng (tile) đồ họa #00 trong Vram. Byte cuối cùng có giá trị 0x62, tức %0110 0010, cho thấy sprite này sử dụng hình ảnh trong khoảng 0x00 ~ 0xFF (do bit 0 đang tắt/đóng/reset/clear), sử dụng pallette màu #01 (%010), có độ ưu tiên 2 (%10) và hình ảnh bị lật ngược theo chiều ngang (bit 6 được bật/mở/set).
Table thứ 2 của OAM nằm liền sau table thứ nhất, gồm 0x20 byte (128 sprite x 2 bit), từ địa chỉ $0200 tới $0220, có cấu trúc 2 bit cho mỗi sprite. Trong đó: bit 0 ở table này là bit thứ 9 điều khiển tọa độ X của sprite. Nếu nó được bật/mở/set thì tọa độ X của sprite sẽ nằm ở ngoài vùng hiển thị của màn hình (vùng hiển thị: 0~255). Nói cách khác, tọa độ X của sprite gồm 9 bit nên nó có thể ở bất kỳ vị trí nào trong khoảng -256 (0x01FF) tới 255 (0xFF). Mục đích của bit thứ 9 này là để ẩn sprite ở tọa độ ngoài vùng hiển thị khi không cần thiết.
Bit 1 của OAM thứ 2 điều khiển kích thước mảng (tile) đồ họa của sprite. Kích thước mảng là nhỏ nếu bit này được tắt/đóng/reset/clear; kích thước mảng là lớn nếu bit này được bật/mở/set. Kích thước của mảng còn phụ thuộc vào bit 5~bit 7 của register $2101. Nếu tất cả các bit 5~7 của register này được tắt/đóng/reset/clear thì kích thước sprite nhỏ là mảng (tile) 8x8 pixel, còn kích thước sprite lớn là 16x16 pixel. Tương quan kích thước sprite với các bit của Register này như sau.
%000 Sprite lớn: 16x16, sprite nhỏ: 8x8
%001 Sprite lớn: 32x32, sprite nhỏ: 8x8
%010 Sprite lớn: 64x64, sprite nhỏ: 8x8
%011 Sprite lớn: 32x32, sprite nhỏ: 16x16
%100 Sprite lớn: 64x64, sprite nhỏ: 16x16
%101 Sprite lớn: 64x64, sprite nhỏ: 32x32
Giả dụ trạng thái của register $2101 là %011XXXXX và bit 1 của OAM table thứ 2 được bật/mở/set thì sprite đó sử dụng tile hình ảnh có kích thước 32x32 pixel. Thông thường, hầu hết game thương mại trên thị trường đều dùng kích thước sprite lớn là 16x16 và kích thước nhỏ là 8x8; rất hiếm gặp game nào dùng cỡ sprite khác.
* Tại sao người ta lại dùng nhiều sprite để diễn tả một nhân vật mà không dùng chỉ một sprite duy nhất để đơn giản hơn? Lý do của việc này là một nhân vật với kích thước lớn sẽ tốn rất nhiều chỗ chứa hình ảnh trong Vram nếu như chỉ dùng một sprite duy nhất. Trong khi đó, Vram của SFC chỉ có dung lượng 64KB nên các hãng thường cắt nhỏ sprite thành nhiều phần nhằm tiết kiệm không gian lưu trữ hình ảnh trong Vram.
Ta chỉ có thể can thiệp vào OAM trong thời kỳ màn hình tắt, tức 1 trong 3 thời điểm: force blank, v-blank và h-blank. Thông thường, người ta không can thiệp trực tiếp vào OAM, thay vào đó là tạo một bản sao của OAM trong Wram, thao túng trên vùng Wram này để rồi cuối cùng chuyển toàn bộ nội dung của bản sao này vào OAM bằng hình thức DMA ở 1 trong 3 thời điểm trên. Ở nhiều game thương mại, thường thấy người ta định dạng OAM table vào đầu mỗi vòng lặp bằng cách ghi tọa độ Y của tất cả các Sprite trong table thành tọa độ ngoài vùng hiển thị của màn hình (thường là Y = 240), sau đó chỉ ghi tọa độ, ID và thuộc tính của Sprite cần thiết trong một routine xử lý sau đó.
Khảo sát sprite engine ở một số game thương mại
Trên đây là những nét khái quát về sprite của máy SFC. Để hoạt dụng những tính năng của nó thì hầu như mỗi game thương mại lại có những engine khác nhau, chủ yếu là để thao túng các giá trị trong bản sao của OAM trong Wram.
1. Đệ tứ đại chiến siêu Robot (第4次スーパーロボット大戦)
Game được Winky Soft và Banpresto đồng phát triển, Banpresto phát hành cho máy SFC vào năm 1995. Toàn bộ engine điều khiển của game này như bên dưới.
Phân tích
Routine xử lý sprite được đặt ở routine $80DF81 vào cuối kỳ NMI, sau khi đã DMA dữ liệu đồ họa cần thiết. Routine này kết thúc bằng cách thiết lập các thông số để DMA 0x220 byte bản sao OAM từ địa chỉ $000500 vào Vram vào đầu kỳ NMI sau. Set bit bất kỳ của $03BB để kích hoạt DMA trong kỳ NMI.
C0/E0D5: E220 SEP #$20
C0/E0D7: C210 REP #$10
C0/E0D9: A900 LDA #$00
C0/E0DB: 8D0043 STA $4300
C0/E0DE: A904 LDA #$04
C0/E0E0: 8D0143 STA $4301
C0/E0E3: A20005 LDX #$0500
C0/E0E6: 8E0243 STX $4302
C0/E0E9: 9C0443 STZ $4304
C0/E0EC: A22002 LDX #$0220
C0/E0EF: 8E0543 STX $4305
C0/E0F2: A901 LDA #$01
C0/E0F4: 0CBB03 TSB $03BB
C0/E0F7: 6B RTL
Có 2 phân đoạn sử dụng sprite trong game này, đó là sprite ở map và sprite ở những phần còn lại (như sprite trong trận đấu, sprite ở màn hình tựa đề,....). Sprite ở map là các icon Robot, còn sprite ở trận đấu là các nhân vật Robot trong trận đấu.
Sprite ở map và sprite trong trận đấu
Sprite ở hai phân đoạn này được quản lý theo hai hướng khác nhau. Vào đầu routine xử lý, CPU sẽ kiểm tra nó đang xử lý phân đoạn nào bằng cách kiểm tra $80. Nếu bit 15 của địa chỉ này được bật/mở/set thì nó sẽ xử lý các sprite trong table như là sprite trên map, còn bit này được tắt/đóng/reset/clear thì nó sẽ xử lý như sprite trong trận đấu và những phân đoạn khác. Mỗi "vật thể" trên map chỉ gồm 1 sprite có kích thước 16x16 pixel trong khi "vật thể" trong trận đấu gồm nhiều sprite hợp thành. Nếu bit 3 của $80 được tắt/đóng/reset/clear thì bỏ qua routine xử lý sprite, tức sprite ở frame đó không được cập nhật, đồng nghĩa với việc hình ảnh nhân vật được giữ nguyên từ frame trước đó, màn hình sẽ không hiển thị sự chuyển động hay thay đổi của sprite.
C0/DF81: C230 REP #$30
C0/DF83: 2480 BIT $80
C0/DF85: 101C BPL $DFA3 //bit 15 của $80 được tắt thì nhảy đến đoạn xử lý sprite trong trận đấu
C0/DF87: AD880E LDA $0E88
C0/DF8A: 85C5 STA $C5
C0/DF8C: AD8C0E LDA $0E8C //cuộn BG2 theo chiều ngang
C0/DF8F: 8D8E03 STA $038E
C0/DF92: 8D9203 STA $0392
C0/DF95: AD8A0E LDA $0E8A
C0/DF98: 85C7 STA $C7
C0/DF9A: AD8E0E LDA $0E8E //cuộn BG2 theo chiều dọc
C0/DF9D: 8D9003 STA $0390
C0/DFA0: 8D9403 STA $0394
C0/DFA3: A580 LDA $80
C0/DFA5: 890800 BIT #$0008
C0/DFA8: F0D6 BEQ $DF80 //bit 3 của $80 được tắt thì thoát routuine
C0/DFAA: 9C0007 STZ $0700 //định dạng OAM table #2
C0/DFAD: 9C0207 STZ $0702
C0/DFB0: 9C0407 STZ $0704
C0/DFB3: 9C0607 STZ $0706
C0/DFB6: 9C0807 STZ $0708
C0/DFB9: 9C0A07 STZ $070A
C0/DFBC: 9C0C07 STZ $070C
C0/DFBF: 9C0E07 STZ $070E
C0/DFC2: 9C1007 STZ $0710
C0/DFC5: 9C1207 STZ $0712
C0/DFC8: 9C1407 STZ $0714
C0/DFCB: 9C1607 STZ $0716
C0/DFCE: 9C1807 STZ $0718
C0/DFD1: 9C1A07 STZ $071A
C0/DFD4: 9C1C07 STZ $071C
C0/DFD7: 9C1E07 STZ $071E
Có thể dùng từ "vật thể" để diễn tả mọi sprite xuất hiện trong game này. Các "vật thể" có thể là Robot, vũ khí, viên đạn,... Mỗi vật thể gồm một (ở map) hoặc nhiều sprite (trong trận đấu) hợp thành. Chẳng hạn ở cảnh dưới đây gồm 2 vật thể: Robot đĩa bay bên địch và viên đạn. Vật thể đầu tiên là viên đạn, gồm 6 sprite cỡ nhỏ (8x8 pixel) hợp thành. Vật thể thứ hai là Robot đĩa bay, gồm 4 sprite cỡ lớn (16x16 pixel) và 2 sprite cỡ nhỏ hợp thành. Sprite cỡ lớn được đánh dấu bằng khung màu trắng, sprite cỡ nhỏ được đánh dấu bằng khung màu đỏ.
Do khung cảnh này sử dụng 12 sprite cho 2 vật thể, mỗi sprite chiếm 4 byte nên có cả thảy 48 byte (0x30) trong phần buffer của OAM được dùng để mô tả cảnh này. Phần buffer của OAM bắt đầu tại $0500, và 2 vật thể này dùng hết 0x30 byte nên bắt đầu từ địa chỉ $0530 trở đi, ta thấy tọa độ Y của tất cả các sprite còn lại đều có giá trị 0xF0, tức pixel thứ 240 từ trên đỉnh màn hình tính xuống. Nói cách khác, 128-12 = 116 sprite còn lại không được sử dụng đến, được giấu ở tọa độ Y = 240, tức nằm ngoài vùng hiển thị của màn hình. Do vậy, mặc dù máy SFC luôn hiển thị 128 sprite trong mỗi frame mà CPU xử lý routine này, nhưng ta không nhìn thấy gì khác ngoài các sprite cấu thành nên vật thể được phép xuất hiện trong phân cảnh đó.
Địa chỉ $0500 là nơi bắt đầu phần buffer của OAM table #1, còn OAM table #2 bắt đầu tại $0700 đến $071F. Như vậy, $0500 ~ $071F là đủ điều khiển OAM table #1 và table #2, nhưng game này còn sử dụng đến một số table phụ khác từ $0720 trở đi để tính toán các thành phần trong table. Game này cho phép xuất hiện 0x30 (48) vật thể trên màn hình, mỗi vật thể chiếm 2 byte trong các table phụ nên mỗi table phụ chiếm 0x60 byte. Dưới đây là một vài table phụ.
$0720 ~ $077F: table thuộc tính 1 của vật thể
$0780 ~ $07DF: table thuộc tính 2 của vật thể
$07E0 ~ $083F: table tọa độ X của vật thể
$0840 ~ $089F: table tọa độ Y của vật thể
$08A0 ~ $08FF: table chứa ID của vật thể phụ như vũ khí trên tay nhân vật, sprite khói lửa,...
$0900 ~ $095F: table chứa pointer dẫn đến dữ liệu đồ họa của cái bóng dưới chân nhân vật
$0960 ~ $09BF: table chứa thông tin về tốc độ di chuyển, thời gian tồn tại của vật thể, thời gian tồn tại của hiệu ứng trên màn hình
$0B40 ~ $0B9E: table địa chỉ Vram của sprite thể hiện vật thể
Table phụ đầu tiên là table thuộc tính 1 của vật thể. Đây là checkpoint đầu tiên để kiểm tra xem vật thể có được hiển thị trên màn hình hay không, vật thể dùng pallette màu nào, có mức độ ưu tiên như thế nào. Table phụ thứ hai định nghĩa nên những thuộc tính khác của vật thể: có lật ngược sprite hay không, xác định dữ liệu đồ họa của sprite trong Rom. Hai table phụ tiếp theo chứa thông tin về tọa độ nơi vật thể xuất hiện. Table $0B40 chứa thông tin về địa chỉ Vram của sprite cấu thành nên vật thể.
C0/DFEB: A20000 LDX #$0000
C0/DFEE: A03000 LDY #$0030 //xử lý 0x30 vật thể
C0/DFF1: BD2007 LDA $0720,X //table thuộc tính 1
C0/DFF4: 2930C0 AND #$C030
C0/DFF7: C930C0 CMP #$C030 //kiểm tra bit 4, bit 5 set
C0/DFFA: D008 BNE $E004
C0/DFFC: 20F8E0 JSR $E0F8 //xử lý sprite
C0/DFFF: 9003 BCC $E004
C0/E001: 4CCAE0 JMP $E0CA //kết thúc routine
Routine xử lý từng sprite ở $80E0F8, nhưng chỉ khi bit 4 và bit 5 của 2 byte thuộc tính 1 được set thì sprite mới được xử lý. Tại routine này, những thông tin như tọa độ X và Y, pallette màu, độ ưu tiên của sprite lần lượt được ghi vào OAM table.
C0/E0F8: 865E STX $5E
C0/E0FA: 8446 STY $46
C0/E0FC: BD2007 LDA $0720,X //đọc table thuộc tính 1
C0/E0FF: 855C STA $5C
C0/E101: 29003E AND #$3E00 //chỉ giữ lại thông tin pallette màu và độ ưu tiên
C0/E104: 855A STA $5A
C0/E106: BD8007 LDA $0780,X //đọc table thuộc tính 2
C0/E109: 853A STA $3A
C0/E10B: 2900C0 AND #$C000 //chỉ giữ lại thông tin lật sprite (bit 15, 14)
C0/E10E: 8552 STA $52
C0/E110: BDE007 LDA $07E0,X //đọc thông tin tọa độ X
C0/E113: 853C STA $3C
C0/E115: BD4008 LDA $0840,X //đọc thông tin tọa độ Y
C0/E118: 853E STA $3E
C0/E11A: BD400B LDA $0B40,X //offset ID của sprite
C0/E11D: 29FF01 AND #$01FF
C0/E120: 8550 STA $50
C0/E122: 245B BIT $5B //kiểm tra bit 6 của 2 byte thuộc tính 1
C0/E124: 501F BVC $E145 //nếu bit 6 được bật thì xử lý thành phần phụ như vũ khí của nhân vật
C0/E126: BDA008 LDA $08A0,X //đọc pointer đến thành phần phụ
C0/E129: 297E00 AND #$007E
C0/E12C: AA TAX
C0/E12D: BDE007 LDA $07E0,X //đọc tọa độ X của thành phần phụ
C0/E130: 18 CLC
C0/E131: 653C ADC $3C
C0/E133: 853C STA $3C
C0/E135: BD4008 LDA $0840,X //đọc tọa độ Y của thành phần phụ
C0/E138: 18 CLC
C0/E139: 653E ADC $3E
C0/E13B: 853E STA $3E
C0/E13D: BD2007 LDA $0720,X
C0/E140: 294000 AND #$0040
C0/E143: D0E1 BNE $E126
C0/E145: A55C LDA $5C
C0/E147: 290700 AND #$0007
C0/E14A: 0A ASL A
C0/E14B: AA TAX
C0/E14C: 7C4FE1 JMP ($E14F,X)
dw $E15F, $E174, $E228D, $E28D
dw $E30E, $E30E, $E30E, $E30E
C0/E15F: 245B BIT $5B
C0/E161: 103B BPL $E19E //nếu bit 7 của 2 byte trong table thuộc tính 1 được set thì xử lý như sprite trên map
C0/E163: A53C LDA $3C //xử lý tọa độ sprite trên map
C0/E165: 38 SEC
C0/E166: E5C5 SBC $C5
C0/E168: 853C STA $3C
C0/E16A: A53E LDA $3E
C0/E16C: 18 CLC
C0/E16D: E5C7 SBC $C7
C0/E16F: 853E STA $3E
C0/E171: 4C9EE1 JMP $E19E
C0/E174: A53C LDA $3C //tọa độ sprite = tọa độ sprite ÷ 8
C0/E176: 4A LSR A
C0/E177: 4A LSR A
C0/E178: 4A LSR A
C0/E179: 243C BIT $3C
C0/E17B: 1003 BPL $E180
C0/E17D: 0900E0 ORA #$E000
C0/E180: 245B BIT $5B
C0/E182: 1003 BPL $E187
C0/E184: 38 SEC
C0/E185: E5C5 SBC $C5
C0/E187: 853C STA $3C
C0/E189: A53E LDA $3E
C0/E18B: 4A LSR A
C0/E18C: 4A LSR A
C0/E18D: 4A LSR A
C0/E18E: 243E BIT $3E
C0/E190: 1003 BPL $E195
C0/E192: 0900E0 ORA #$E000
C0/E195: 245B BIT $5B
C0/E197: 1003 BPL $E19C
C0/E199: 18 CLC
C0/E19A: E5C7 SBC $C7
C0/E19C: 853E STA $3E
C0/E19E: A53B LDA $3B
C0/E1A0: 293F00 AND #$003F //trong số thông tin ở table thuộc tính 2, giữ phần thông tin địa chỉ graphic của sprite
C0/E1A3: 8530 STA $30
C0/E1A5: 0A ASL A
C0/E1A6: 6530 ADC $30
C0/E1A8: AA TAX //khối graphic
C0/E1A9: A53A LDA $3A
C0/E1AB: 29FF00 AND #$00FF
C0/E1AE: 0A ASL A
C0/E1AF: A8 TAY //graphic ID trong khối graphic
C0/E1B0: BF3000C4 LDA $C40030,X //địa chỉ graphic trong Rom
C0/E1B4: 8530 STA $30
C0/E1B6: BF3100C4 LDA $C40031,X
C0/E1BA: 8531 STA $31
C0/E1BC: B730 LDA [$30],Y
C0/E1BE: 8530 STA $30
C0/E1C0: A00000 LDY #$0000
C0/E1C3: B730 LDA [$30],Y //đọc số lượng sprite trong khối vật thể
C0/E1C5: 29FF00 AND #$00FF
C0/E1C8: F06A BEQ $E234
C0/E1CA: 8538 STA $38 //ghi số lượng sprite
C0/E1CC: C8 INY
C0/E1CD: B730 LDA [$30],Y //đọc kích thước, ID sprite
C0/E1CF: 8558 STA $58
C0/E1D1: 290010 AND #$1000
C0/E1D4: D003 BNE $E1D9
C0/E1D6: A90008 LDA #$0800
C0/E1D9: EB XBA
C0/E1DA: 8554 STA $54 //ghi kích thước sprite
C0/E1DC: C8 INY
C0/E1DD: B730 LDA [$30],Y //đọc tọa độ tương đối Y của sprite
C0/E1DF: 2900FF AND #$FF00
C0/E1E2: 1003 BPL $E1E7
C0/E1E4: 09FF00 ORA #$00FF //nếu Y là số âm
C0/E1E7: EB XBA
C0/E1E8: 18 CLC
C0/E1E9: 2452 BIT $52 //kiểm tra sprite có thuộc tính lật ngược theo chiều dọc không
C0/E1EB: 1007 BPL $E1F4
C0/E1ED: 49FFFF EOR #$FFFF //lật đối xứng theo trục hoành
C0/E1F0: 38 SEC
C0/E1F1: E554 SBC $54 //trừ kích thước 1 tile
C0/E1F3: 38 SEC
C0/E1F4: 653E ADC $3E //cộng tọa độ Y tương đối với tọa độ Y tuyệt đối
C0/E1F6: C8 INY
C0/E1F7: C9F1FF CMP #$FFF1
C0/E1FA: B005 BCS $E201
C0/E1FC: C9F000 CMP #$00F0
C0/E1FF: B02D BCS $E22E
C0/E201: 29FF00 AND #$00FF
C0/E204: EB XBA
C0/E205: 8556 STA $56 //ghi tọa độ Y của sprite
C0/E207: B730 LDA [$30],Y
C0/E209: 2900FF AND #$FF00
C0/E20C: 1003 BPL $E211
C0/E20E: 09FF00 ORA #$00FF //tọa độ Y âm
C0/E211: EB XBA
C0/E212: 18 CLC
C0/E213: 2452 BIT $52
C0/E215: 5007 BVC $E21E //kiểm tra sprite có thuộc tính lật ngược theo chiều dọc không
C0/E217: 49FFFF EOR #$FFFF
C0/E21A: 38 SEC
C0/E21B: E554 SBC $54 //trừ kích thước 1 tile
C0/E21D: 38 SEC
C0/E21E: 653C ADC $3C //cộng tọa độ X
C0/E220: 8534 STA $34
C0/E222: A64E LDX $4E
C0/E224: C9F1FF CMP #$FFF1
C0/E227: B011 BCS $E23A //tọa độ X là số âm
C0/E229: C90001 CMP #$0100
C0/E22C: 9014 BCC $E242
C0/E22E: C8 INY
C0/E22F: C8 INY
C0/E230: C638 DEC $38 //lặp đến hết số lượng sprite trong khối vật thể
C0/E232: D099 BNE $E1CD
C0/E234: A65E LDX $5E
C0/E236: A446 LDY $46
C0/E238: 18 CLC
C0/E239: 60 RTS
C0/E23A: BD0007 LDA $0700,X //2 bit ở table #2
C0/E23D: 054A ORA $4A
C0/E23F: 9D0007 STA $0700,X
C0/E242: 064A ASL $4A
C0/E244: A554 LDA $54
C0/E246: C91000 CMP #$0010
C0/E249: D008 BNE $E253
C0/E24B: BD0007 LDA $0700,X //sprite lớn
C0/E24E: 054A ORA $4A
C0/E250: 9D0007 STA $0700,X
C0/E253: 064A ASL $4A
C0/E255: 9006 BCC $E25D
C0/E257: 264A ROL $4A
C0/E259: E64E INC $4E
C0/E25B: E64E INC $4E
C0/E25D: A64C LDX $4C //thứ tự sprite trong table
C0/E25F: A534 LDA $34 //tọa độ X tuyệt đối
C0/E261: 29FF00 AND #$00FF //bỏ bit thừa
C0/E264: 0556 ORA $56 //tọa độ Y tuyệt đối
C0/E266: 9D0005 STA $0500,X
C0/E269: A558 LDA $58 //ID sprite
C0/E26B: 18 CLC
C0/E26C: 6550 ADC $50 //offset ID của sprite
C0/E26E: 4552 EOR $52 //thuộc tính của sprite: lật ngang lật dọc
C0/E270: 29FFCF AND #$CFFF //loại bỏ bit thừa
C0/E273: 18 CLC
C0/E274: 655A ADC $5A //pallette màu, độ ưu tiên
C0/E276: 9D0205 STA $0502,X
C0/E279: 8A TXA
C0/E27A: 18 CLC
C0/E27B: 690400 ADC #$0004
C0/E27E: 854C STA $4C
C0/E280: C90002 CMP #$0200
C0/E283: D0A9 BNE $E22E
C0/E285: A65E LDX $5E
C0/E287: A446 LDY $46
C0/E289: 6448 STZ $48
C0/E28B: 38 SEC
C0/E28C: 60 RTS
Dữ liệu của khối vật thể trong Rom gồm các byte sau:
Byte 1: số lượng sprite cấu thành nên vật thể đó
4 byte tiếp theo gồm: số ID của tile đồ họa trong Vram, cỡ sprite (00: nhỏ/01: lớn), tọa độ Y, tọa độ X
Với byte chỉ định cỡ sprite, nếu set bit 0 thì có thể chỉ đến sprite trong phạm vi 0x100 tới 0x1FF, tuy nhiên cách dùng này khá bất tiện trong việc chỉ định tile đồ họa của sprite, nên thường không set bit này, mà dùng kết hợp với 2 byte ở table $0B40 để chỉ định tile đồ họa trong phạm vi trên.
Cách dùng
Bật/tắt hiển thị sprite trong trận đấu: ghi giá trị 16 bit vào buffer table ở $0720 với bit 4, 5 được set ($XX30).
Hiển thị sprite trên map: ghi giá trị 16 bit vào buffer table ở $0720 với bit 4, 5, 7 được set ($XXB0).
Hiển thị sprite vũ khí, hiệu ứng trong trận đấu: ghi giá trị 16 bit vào buffer table ở $0720 với bit 4, 5, 6 được set ($XX70).
Chỉ định ID đồ họa của sprite: ghi giá trị 16 bit vào buffer table ở $0780: với hi-byte là ID của nhóm graphic, low-byte là số ID của sprite trong nhóm đó.
Lật ngược sprite theo chiều dọc: ghi giá trị 16 bit vào buffer table ở $0780 với bit 15 được set.
Lật ngược sprite theo chiều ngang: ghi giá trị 16 bit vào buffer table ở $0780 với bit 14 được set.
Tọa độ X của sprite: ghi giá trị 16 bit vào buffer table ở $07E0.
Tọa độ Y của sprite: ghi giá trị 16 bit vào buffer table ở $840.
Chỉ định ID đồ họa của sprite trong phạm vi 0x00 ~ 0x01FF: ghi giá trị 16 bit vào buffer table ở $0B40.