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 đó:

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.



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:

 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