Bài 10: hiển thị nhiều sprite
Ta đã biết, sprite là những hình ảnh 8x8 pixel phía trước phông nền. Nhân vật trong game, chẳng hạn như Mario, được cấu thành từ nhiều sprite và cả khối nhân vật được gọi là meta-sprite.
Trong bài 6, ta đã biết cách hiển thị sprite qua các Register $2003 và $2004, nhưng khi số lượng sprite tăng lên thì việc chuyển từng sprite vào RAM sẽ rất phiền. Tuy nhiên, Famicom cho phép chuẩn bị sẵn dữ liệu các sprite và đồng thời chuyển hết chúng vào RAM. Phương pháp đó gọi là sprite DMA (Direct Memory Access).
Trong bài 6, ta cũng đã biết 1 dữ liệu sprite trong RAM được cấu thành từ 4 byte: tọa độ Y, tile Index, thuộc tính của sprite, tọa độ X. Máy Famicom có thể hiển thị tối đa 64 sprite, tức sẽ cần 4x64 = 256 ($100) byte. Do vậy, để chuyển sprite RAM bằng DMA thì cần phải bảo đảm được $100 RAM trống. Vậy làm cách nào để bảo đảm được $100 byte trống này?
Trong những bài đầu đã biết, ta có thể tự do sử dụng từ $0000~$07FF trong RAM. Trong số đó thì $0000~$00FF là Zero page, nên sẽ không động đến. Còn $0100~$01FF là khu vực của Stack. Vì vậy ta sẽ dùng khu vực từ $0200 trở đi làm vùng đệm cho sprite.
Sprite RAM Register
Đầu tiên cần bảo đảm không gian trống trong RAM để chứa sprite. Trong ví dụ dưới đây, ta chọn bắt đầu từ $0300.
.bank 0
.org $0300 ; bắt đầu từ $0300, bố trí dữ liệu sprite DMA
Sprite1_Y: .db 0 ; sprite #1, tọa độ Y
Sprite1_T: .db 0 ; sprite #1, số ID
Sprite1_S: .db 0 ; sprite #1, thuộc tính
Sprite1_X: .db 0 ; sprite #1, tọa độ X
Sprite2_Y: .db 0 ; sprite #2, tọa độ Y
Sprite2_T: .db 0 ; sprite #2, số ID
Sprite2_S: .db 0 ; sprite #2, thuộc tính
Sprite2_X: .db 0 ; sprite #2, tọa độ X
.org $8000 ; bắt đầu từ $8000
Start:
; phần chính của chương trình bắt đầu từ đây
Và ta sẽ cập nhật dữ liệu sprite dự định chuyển DMA. Sau đó ta sẽ ghi địa chỉ RAM muốn chuyển vào Register $4014 khi muốn cập nhật sprite.
; vẽ sprite (lợi dụng DMA)
LDA #$03 ; dữ liệu sprite ở địa chỉ $300
STA $4014 ; chứa nội dung trong A vào trong Register chuyển DMA
Trong ví dụ ở bài trước, ta đã viế chương trình di chuyển sprite. Bài này sử dụng lại ví dụ đó nhưng sửa lại phần hiển thị sprite lợi dụng DMA để thể hiện 2 sprite dính liền nhau.
; Ví dụ về sprite DMA
; INES header
.inesprg 1 ; - chọn bank 1
.ineschr 1 ; -chọn bank graphic 1
.inesmir 0 ; - đối xứng gương 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 đến Stary khi reset và khi khởi động
.dw 0 ; phát sinh do ngắt phần cứng và ngắt phần mềm
.bank 0 ; bank 0
.org $0300 ; bắt đầu từ $0300, bố trí sprite DMA
Sprite1_Y: .db 0 ; sprite#1 tọa độ Y
Sprite1_T: .db 0 ; sprite#1 number
Sprite1_S: .db 0 ; sprite#1 thuộc tính
Sprite1_X: .db 0 ; sprite#1 tọa độ X
Sprite2_Y: .db 0 ; sprite#2 tọa độ Y
Sprite2_T: .db 0 ; sprite#2 number
Sprite2_S: .db 0 ; sprite#2 thuộc tính
Sprite2_X: .db 0 ; sprite#2 tọa độ X
.org $8000 ; bắt đầu từ $8000, phần chính của chương trình
Start:
lda $2002 ; khi phát sinh VBlank thì bit 7 của $2002 là 1
bpl Start ; khi bit7 là 0 thì nhảy tới Start, đợi lặp
; Định dạng Register điều khiển PPU
lda #001000
sta $2000
lda #000110 ; tắt BG và sprite khi đang định dạng
sta $2001
ldx #$00 ; xóa nội dung của X Register
; chỉ định địa chỉ $3F00 tải palette vào Register $2006 của VRAM
lda #$3F ; bảo $2006 chỉ định
sta $2006 ; $2007 để bắt đầu
lda #$00 ; tại $3F00 (pallete).
sta $2006
loadPal:
lda tilepal, x ;
sta $2007 ; đọc giá trị vào $2007
inx ; tăng X lên 1
cpx #32 ; so sánh X với 32
bne loadPal ; không bằng thì nhảy tới loadPal
; định dạng tọa độ của sprite #1
lda X_Pos_Init
sta Sprite1_X
lda Y_Pos_Init
sta Sprite1_Y
; định dạng tọa độ của sprite #2
lda X_Pos_Init
adc #7 ; lệch sang phải 7 dot
sta Sprite2_X
lda Y_Pos_Init
sta Sprite2_Y
; xoay ngược sprite theo chiều dọc
lda #%01000000
sta Sprite2_S
; định dạng Register 2 điều khiển PPU
lda #011110 ; bật BG và sprite
sta $2001
mainLoop: ; vòng lặp cính
lda $2002 ; khi VBlank phát sinh thì bit 7 của $2002 là 1
bpl mainLoop ; ki bit7=0 thì nhảy tới mainLoop
; vẽ sprite (lợi dụng DMA)
LDA #$03 ; dữ liệu sprite ở địa chỉ $300
STA $4014 ; chứa nội dung trong A vào trong Register chuyển DMA
; chuẩn bị I/O Register
lda #$01
sta $4016
lda #$00
sta $4016
; kiểm tra nhấn tay cầm
lda $4016 ; bỏ qua nút A
lda $4016 ; bỏ qua nút B
lda $4016 ; bỏ qua nútSelect
lda $4016 ; bỏ qua nútStart
lda $4016 ; nút lên
and #1 ; AND #1
bne UPKEYdown ; nếu khác 0 thì nhảy tới UPKeydown
lda $4016 ; nút xuống
and #1 ; AND #1
bne DOWNKEYdown ; nếu khác 0 thì nhảy tới DOWNKeydown
lda $4016 ; nút trái
and #1 ; AND #1
bne LEFTKEYdown ; nếu khác 0 thì nhảy tới LEFTKeydown
lda $4016 ; nút phải
and #1 ; AND #1
bne RIGHTKEYdown ; nếu khác 0 thì nhảy tới RIGHTKeydown
jmp NOTHINGdown ; không nhấn gì thì nhảy tới NOTHINGdown
UPKEYdown:
dec Sprite1_Y ; giảm tọa độ Y 1 pixel
jmp NOTHINGdown
DOWNKEYdown:
inc Sprite1_Y ; tăng tọa độ Y 1 pixel
jmp NOTHINGdown
LEFTKEYdown:
dec Sprite1_X ; giảm tọa độ X 1 pixel
jmp NOTHINGdown
RIGHTKEYdown:
inc Sprite1_X ; tăng tọa độ Y 1 pixel
NOTHINGdown:
; cập nhật tọa độ sprite 2
lda Sprite1_X
adc #8 ; lệch 8 dot về bên phải
sta Sprite2_X
lda Sprite1_Y
sta Sprite2_Y
jmp mainLoop ; trở lại mainLoop
; dữ liệu ban đầu
X_Pos_Init .db 20 ; tọa độ X ban đầu
Y_Pos_Init .db 40 ; tọa độ Y ban đầu
tilepal: .incbin "giko2.pal" ; include palette
.bank 2 ; bank 2
.org $0000 ; bắt đầu từ $0000
.incbin "kage.bkg"
.incbin "kage2.spr"
0 bình luận :
Post a Comment