Bài 6: tạo sprite


Sprite là những hình ảnh tách biệt so với ảnh nền (BG). Khu vực của sprite trong VRAM chiếm 4KB, và có thể đăng ký 256 sprite với kích thước 8x8 điểm ảnh. Sprite là thành phần cấu thành nên các vật thể, nhân vật trong game. Chẳng hạn, ảnh nhân vật Mario được tạo thành từ nhiều sprite 8x8. Tuy đăng ký được tới 256 sprite nhưng bộ nhớ PPU chỉ hiển thị đồng thời được 64 sprite.
Thực chất, từng đơn vị 8x8 điểm ảnh này được gọi là "tile", gồm 4 màu. Phần dữ liệu sprite này gọi chung là CHR. Khi máy NES tải CHR vào PPU thì nó sẽ tải chúng vào 8 bank, mỗi bank $2000 byte, bao gồm 512 tile.


[​IMG]

Sprite trong game Jetman.​

[​IMG]

Sprite trong game Kunio.​

Có nhiều phần mềm cho phép tạo ảnh sprite cho NES và cả SNES, ở đây khuyên dùng YY-CHR. Tải YY-CHR tại đây. Mở một game NES bất kỳ bằng YY-CHR, ở góc trái bên dưới, chọn 2BPP NES và kéo thanh trượt xuống phía dưới để thấy sprite. Có thể copy từ những game có sẵn, hoặc tạo mới bằng YY-CHR. Sau khi tạo sprite, nên lưu với định dạng .chr để dễ phân biệt.


Có thể tải file này, giải nén và được các file: kage.pal (palette), kage.spr (sprite), kage.bkg (BG) và kage.asm (code chương trình). Có thể mở và chỉnh sửa những file này bằng các phần mềm đã biết.

[​IMG]

Ta có thể chỉ định 4 màu từ palette đối với mỗi sprite. Trong trường hợp bên dưới, nếu chọn palette 0 cho sprite (palette của sprite ở bên dưới) thì màu ảnh nền là màu xám, mắt màu đen, miệng màu đỏ và mặt màu trắng. Nếu chọn palette số 1 thì có thể sử dụng các màu 4-5-6-7 và mặt có màu xanh lá. Hãy ghi nhớ quy tắc chỉ định màu này (cụm 4 màu) vì nó còn được dùng cho cả BG.

[​IMG]

Bố trí đồ họa vào bank 2

Trong bài 4, ở phần định dạng PPU, ta đã thiết lập Pattern table cho BG bắt đầu từ $0000 và Pattern table cho sprite bắt đầu từ $1000 qua Register điều khiển PPU ở địa chỉ $2000. $1000 hệ số thập lục tương đương với 4096 byte, tức nó chứa được 4KB dữ liệu. Ta cần phải trình bày BG --> sprite theo trật tự như dưới đây.

.bank 2
.org $0000
.incbin "kage.bkg" ; include file BG
.incbin "kage.spr" ; include file sprite

Ghi vào sprite Ram

Để ghi dữ liệu sprite vào sprite RAM thì đầu tiên cần chỉ định địa chỉ Pattern table (dài 8bit) của sprite cần ghi vào sprite RAM Register $2003.

LDA #$00 ; tải giá trị $00 (địa chỉ sprite RAM 8 bit) vào A
STA $2003 ; chứa địa chỉ sprite RAm trong A vào $2003


Để chỉ định thông tin sprite, ta sử dụng sprite RAM Register $2004. Để vẽ sprite lên màn hình thì cần chỉ định 4 thông tin lần lượt như dưới đây.

+ Byte đầu tiên: tọa độ Y
+ Byte thứ 2: số hiệu Tile Index
+ Byte thứ 3: bit flag 8 bit chỉ định thuộc tính của sprite.
* Bit 7: xoay ngược vuông góc (1 là xoay ngược)
* Bit 6: xoay ngược theo chiều ngang (1 là xoay ngược)
* Bit 5: trật tự ưu tiên BG (0: phía trước, 1: đằng sau)
* Bit 0-1: 2 bit đầu của palette
* Các bit khác: 0
+ Byte thứ 4: tọa độ X

2 bit đầu của palette tức là, trong số palette của BG từ 0~15, nếu dùng palette 0~3 thì đó là 00 trong 0 (00), nếu dùng palette 4~7 thì là 01 trong 4 (%0100), nếu dùng palette 8~11 thì đó là 10 trong 8 (%1000), nếu dùng palette 12~15 thì chỉ định 11 trong 12 (%1100). Ở đây chọn palette 0. Nếu muốn hiển thị sprite số 0 ở tọa độ X=30, Y=40 thì viết như sau

LDA #40 ; load 40 vào A
STA $2004 ; chứa tọa độ Y vào Register
LDA #00 ; load giá trị 0 (sprite 0) vào A
STA $2004 ; chứa 0 vào Register, chỉ định sprite 0
STA $2004 ; lại chứa 0 vào vì ta không cần xoay ngược hay trật tự ưu tiên
LDA #30 ; load giá trị thập phân 30 vào A
STA $2004 ; chứa tọa độ X vào Register

Sprite 0 là sprite đầu tiên trong số pattern table, tức kho dữ liệu sprite.

[​IMG]

Ở đây nhắc lại, màn hình Famicom có độ phân giải 246x240 điểm ảnh.


VBlank

Trong bài trước, ta đã biết về khái niệm VBlank. Ở phần này sẽ đi sâu hơn.
Các loại TV ở Nhật, Mỹ đều chuẩn theo quy cách NTSC. Đường ngang chạy dọc màn hình TV trong 1/60 giây từ phía trên bên trái xuống phía dưới bên góc phải để vẽ nên hình ảnh. Khi nó chạy xuống góc phải bên dưới thì sẽ trở lại góc trái bên trên, và khoảng thời gian nó quay trở lại này được gọi là VBlank.

[​IMG]

Nếu cập nhật VRAM trong khi đang vẽ hình ảnh lên màn hình sau khi VBlank kết thúc thì hình ảnh sẽ bị vỡ. Vì vậy đầu tiên cần đợi phát sinh VBlank xong mới tiến hành xử lý vẽ hình ảnh trong thời gian VBlank. Và nhất định cần phải xử lý xong tất cả mọi thứ cho đến khi bắt đầu VBlank tiếp theo. Nói cách khác, Famicom (NES) có thể xử lý 60 vòng lặp trong 1 giây.

Start:
LDA $2002 ; khi VBlank phát sinh thì bit 7 của $2002 sẽ là 1
BPL Start ; trong khi bit 7 = 0 thì nhảy đến label Start, đợi vòng lặp

BPL là mệnh lệnh phân nhánh sang địa chỉ được chỉ định nếu N flag (Negative flag) của Status Register là 0. Vì trong N flag đã được set bit 7 của A Register nên có thể đợi bằng LDA và BPL.

Hoàn tất chương trình hiển thị sprite


; Ví dụ chương trình hiển thị sprit

; INES header
.inesprg 1 ; - chọn bank nào trong program. Giờ chọn 1
.ineschr 1 ; - chọn bank nào trong dữ liệu đồ họa. Giờ chọn 1
.inesmir 0 ; - Đối xứng gương theo chiều ngang
.inesmap 0 ; -Mapper 0

.bank 1 ; bank 1
.org $FFFA ; bắt đầu từ $FFFA

.dw 0 ; ngắt VBlank
.dw Start ; ngắt reset. Nhảy tới label Start khi khởi động và reset
.dw 0 ; phát sinh khi ngắt phần cứng và ngắt phần mềm

.bank 0 ; bank 0
.org $8000 ; $bắt đầu từ 8000

; Code chương trình bắt đầu từ đây

Start:
lda $2002 ; Khi VBlank phát sinh, bit 7 của $2002 thành 1
bpl Start ; trong khi bit 7 =0, nhảy tới Start

; Định dạng Register quản lý PPU
lda #001000
sta $2000
lda #000110 ; tắt hiển thị sprite và BG trong khi định dạng
sta $2001

ldx #$00 ; cho X Register về 0

; Chỉ định địa chỉ load palette $3F00 trong Register địa chỉ $2006 trong VRAM
lda #$3F
sta $2006
lda #$00
sta $2006

loadPal: ; label
lda tilepal, x ; load palette của địa chỉ (pal + X) vào A

sta $2007 ; ghi giá trị palette vào $2007

inx ; gia tăng X 1 đơn vị

cpx #32 ; so sánh X với 32 (tổng palette của BG và sprite)X
bne loadPal ; nếu không bằng thì nhảy sang loadpal
; nếu X=32 thì kết thúc load palette

; vẽ sprite
lda #$00 ; load $00 (địa chỉ sprite RAM 8 bit) vào A
sta $2003 ; chứa địa chỉ sprite RAM trong A

lda #50 ; load 50 vào A
sta $2004 ; chứa tọa độ Y vào Register
lda #00 ; load sprite 0 vào A
sta $2004 ; chứa 0, chỉ định sprite 0
sta $2004 ; lại chứa $00 vì không cần ưu tiên hay xoay ngược
lda #20 ; load 20 vào A
sta $2004 ; chứa tọa độ X vào Register

; Định dạng Register 2 điều khiển PPU
lda #011110 ; bật hiển thị BG và sprite
sta $2001

infinityLoop:
jmp infinityLoop ; lặp vô hạn

tilepal: .incbin "kage.pal" ; include file palette

.bank 2 ; bank 2
.org $0000 ; bắt đầu từ $0000

.incbin "kage.bkg" ; include file ảnh nền BG
.incbin "kage.spr" ; include file ảnh sprite

Đến đây tôi đã giải thích xong mọi thứ cần thiết để tạo một chương trình hiển thị sprite. Tải file cung cấp ở đầu bài, cho kage.pal, kage.spr, kage.bkg và kage.asm vào chung thư mục chứa NESASM, viết file lệnh .bat như lần trước

NESASM kage.asm
@Pause

Chạy file .bat vừa tạo, nếu thành công thì sẽ xuất hiện màn hình như dưới đây.

[​IMG]

Trong chương trình trên, có thể thay
tilepal: .incbin "kage.pal" bằng đoạn code dưới đây nếu bạn không muốn mất công tạo file palette bằng phần mềm chuyên dụng.

tilepal:
.db $0F, $31, $32, $33, $0F, $35, $36, $37, $0F, $39, $3A, $3B, $0F, $3D, $3E, $0F cho BG
.db $0F, $1C, $15, $14, $0F, $02, $38, $3C, $0F, $1C, $15, $14, $0F, $02, $38, $3C cho sprite

Thay đổi các giá trị màu để cho ra màu sắc khác nhau.
Sau khi chạy NESASM, file kage.nes được tạo ra, chạy file này bằng giả lập thì ta thấy sprite đã hiển thị trên màn hình. Hãy thử thay đổi code trong file kage.asm và quan sát thay đổi để hiểu rõ hơn nội dung bài này.

[​IMG]

1 bình luận :

  1. Cảm ơn bạn đã chia sẻ bài viết rất hay và chi tiết
    ..........................
    Huyền Sport
    Võ Thuật
    bong88 l bong88

    ReplyDelete

 
Top