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"  
Để chung file ảnh nền kage.bkg và file sprite kage2.spr vào cùng thư mục với NESASM, chạy script và xác nhận kết quả bằng giả lập.

0 bình luận :

Post a Comment

 
Top