ファミコンプログラミングへの挑戦 ～思い出のゲーム機で動く自作ゲームを作ろう～

第1章: ファミコンの仕組みを理解しよう
1-1 ファミコンとNESの違いとは？
1-2 ファミコンを動かす心臓部「6502」とは
1-3 映像を支えるPPUと音を奏でるAPU
第2章: プログラムの基本 ～レジスタと命令セット～
2-1 A、X、Yレジスタを使いこなす基礎
2-2 計算を実現する加算命令と減算命令
2-3 データを操作する比較・シフト・スタック命令
第3章: 実践! 簡単なプログラムから始めよう
3-1 「Hello, ファミコン」: 最初の動作確認プログラム
3-2 サブルーチンとIOレジスタの活用
3-3 ブロック崩しもどきの制作で学ぶ基本
第4章: スクロールSTGの基礎を作る
4-1 自機の移動と入力処理を実装しよう
4-2 弾の発射と敵キャラの出現をプログラムする
4-3 背景スクロールでダイナミックな動きを演出
第5章: ゲームの完成と次の挑戦
5-1 当たり判定と爆発アニメーションの実装
5-2 マップエディタで作るオリジナルステージ
5-3 あなたのゲームを友達と楽しむ方法

以下は3-3のサンプルプログラムです。

PadState=$07
MX	= $08
MY	= $09
ReflectCount=$0A
DivI	= $0B
DivJ	= $0C
Pad_Stat	=	$0D
Pad_Old		=	$0F
Pad_DownOneTime	=	$11

	; INESヘッダー
	.inesprg 1 ;   - プログラムにいくつのバンクを使うか。今は１つ。
	.ineschr 1 ;   - グラフィックデータにいくつのバンクを使うか。今は１つ。
	.inesmir 2 ;   - 水平ミラーリング
	.inesmap 0 ;   - マッパー。0番にする。

	.bank 1      ; バンク１
	.org $FFFA   ; $FFFAから開始

	.dw MainLoop        ; VBlank割り込み
	.dw Start    ; リセット割り込み。起動時とリセットでStartに飛ぶ
	.dw IRQ        ; ハードウェア割り込みとソフトウェア割り込みによって発生

	.bank 0			 ; バンク０
	.org $0000

	.org $0200	 ; $0000から開始

BALL_Y:		.db 0
BALL_T:		.db 0
BALL_S:		.db 0
BALL_X:		.db 0

PADDLE1_Y:	.db 0
PADDLE1_T:	.db 0
PADDLE1_S:	.db 0
PADDLE1_X:	.db 0

PADDLE2_Y:	.db 0
PADDLE2_T:	.db 0
PADDLE2_S:	.db 0
PADDLE2_X:	.db 0

PADDLE3_Y:	.db 0
PADDLE3_T:	.db 0
PADDLE3_S:	.db 0
PADDLE3_X:	.db 0

Sprite_Y:	.db 0
Sprite_T:	.db 0
Sprite_S:	.db 0
Sprite_X:	.db 0

	.org $8000	 ; $8000から開始
	
Start:
	sei			; 割り込み不許可
	cld			; デシマルモードフラグクリア
	ldx #$ff
	txs			; スタックポインタ初期化 
	
	lda $2002  ; VBlankが発生すると、$2002の7ビット目が1になる
	bpl Start  ; bit7が0の間は、Startラベルの位置に飛んでループして待つ
	
	lda #0
	ldy #0
	sty <$00
	lda #7
	sta <$01
	ldy #$FF
.MemReset
	lda #0
	sta [$00],y
	dey
	bne .MemReset
	dec <$01
	bpl .MemReset
	
	lda #0
	sta <$00
	sta <$01
	
	; PPUコントロールレジスタ初期化
	lda #%00001000 
	sta $2000
	lda #%00000110		; 初期化中はスプライトとBGを表示OFFにする
	sta $2001

	ldx #$00    ; Xレジスタクリア

	; VRAMアドレスレジスタの$2006に、パレットのロード先のアドレス$3F00を指定する。
	lda #$3F    ; have $2006 tell
	sta $2006   ; $2007 to start
	lda #$00    ; at $3F00 (pallete).
	sta $2006

loadPal:			; ラベルは、「ラベル名＋:」の形式で記述
	lda tilepal, x ; Aに(ourpal + x)番地のパレットをロードする

	sta $2007 ; $2007にパレットの値を読み込む

	inx ; Xレジスタに値を1加算している

	cpx #32 ; Xを32(10進数。BGとスプライトのパレットの総数)と比較して同じかどうか比較している	
	bne loadPal ;	上が等しくない場合は、loadpalラベルの位置にジャンプする
	; Xが32ならパレットロード終了

	jsr BGInit
	jsr OBJInit
	
	jsr SetUpPaddleAndBall
	
	
	
	lda #$20
	sta $2006
	lda #$80
	sta $2006
	
	ldx #-1
.mesloop
	inx
	lda MessageTbl,x
	sta $2007
	cmp #0
	bne .mesloop
	
	
	; PPUコントロールレジスタ2初期化
	lda #%00011110 ;画面ON
	sta $2001
	
	lda #$00
	sta $2005
	sta $2005
	
	lda #0
	sta <ReflectCount
	
	lda #1
	sta <MX
	lda #2
	sta <MY
	
	lda #$88	;VBlank割り込み許可
	sta $2000
	
infinityLoop:					; メインループ
	lda $2002  ; VBlankが発生すると、$2002の7ビット目が1になります。
	
	jmp infinityLoop	;V_Blank待ち
	
	rti

Paddle_Y:	.db (240-240/4)

SetUpPaddleAndBall:
	lda Paddle_Y
	sta PADDLE1_Y
	sta PADDLE2_Y
	sta PADDLE3_Y
	ldx PADDLE1_T
	inx
	stx PADDLE2_T
	inx
	stx PADDLE3_T
	inx
	stx BALL_T
	lda #(255-8)
	lsr a
	sta BALL_X
	lda #(240-8)
	lsr a
	sta BALL_Y
	
	lda #1
	sta BALL_S
	
	rts

MainLoop:
	
	
	lda #2
	sta $4014
	
	jsr Pad_Check
	
	
	jsr ReflectBall
	jsr MovePaddle
	jsr ReflectPaddle
	jsr WriteReflectNumber
	
	rti			;割り込みから戻る



P_A			.equ	%10000000
P_B			.equ	%01000000
P_SELECT	.equ	%00100000
P_START		.equ	%00010000
P_UP		.equ	%00001000
P_DOWN		.equ	%00000100
P_LEFT		.equ	%00000010
P_RIGHT		.equ	%00000001

;-------------------------------------
wari:
	;input A/X carry:A result:Y
;-------------------------------------
	ldy #0
	
	cmp #0
	jmp .brk
	
	sta DivJ
	stx DivI
.loop
	lda DivJ
	sec
	sbc DivI
	sta DivJ
	iny
	cmp DivI
	bcs .loop
	;y:result
.brk
	rts
;-------------------------------------
WriteReflectNumber:
;-------------------------------------
	lda #0
	jsr ShowScreen
	
	lda #$20
	sta $2006
	lda #32
	lsr a
	lsr a
	lsr a
	sta $2006
	lda <ReflectCount
	ldx #100
	jsr wari
	tya
	ldx #10
	jsr wari
	clc
	adc #$30
	sta $2007
	
	ldx #0
	ldy #0
	jsr SetScroll
	
	
	lda #$20
	sta $2006
	lda #40
	lsr a
	lsr a
	lsr a
	sta $2006
	lda <ReflectCount
	ldx #10
	jsr wari
	tya
	ldx #10
	jsr wari
	clc
	adc #$30
	sta $2007
	
	ldx #0
	ldy #0
	jsr SetScroll
	
	lda #$20
	sta $2006
	lda #48
	lsr a
	lsr a
	lsr a
	sta $2006
	lda <ReflectCount
	ldx #10
	jsr wari
	clc
	adc #$30
	sta $2007
	
	ldx #0
	ldy #0
	jsr SetScroll
	lda #1
	jsr ShowScreen
	rts
;-------------------------------------
ReflectPaddle:
;-------------------------------------
	lda PADDLE1_X
	clc
	adc #24
	cmp BALL_X
	bcc .noJadge
	
	lda BALL_X
	clc
	adc #8
	cmp PADDLE1_X
	bcc .noJadge
	
	lda BALL_Y
	clc
	adc #8
	cmp PADDLE1_Y
	bcc .noJadge
	
	lda PADDLE1_Y
	clc
	adc #0
	cmp BALL_Y
	bcc .noJadge
	
	lda <MY
	eor #$FF
	clc
	adc #1
	sta <MY
	inc ReflectCount
	;my<0
.noJadge
	rts
;-------------------------------------
ReflectBall:
;-------------------------------------
	lda BALL_X
	clc
	adc <MX
	sta BALL_X
	cmp #0
	bcs .skipX1Ref
	lda <MX
	eor #$FF
	clc
	adc #1
	sta <MX
.skipX1Ref
	lda BALL_X
	cmp #(255-10)
	bcc .skipX2Ref
	lda <MX
	eor #$FF
	clc
	adc #1
	sta <MX
.skipX2Ref
	
	lda BALL_Y
	clc
	adc <MY
	sta BALL_Y
	cmp #0
	bcs .skipY1Ref
	lda <MY
	eor #$FF
	clc
	adc #1
	sta <MY
.skipY1Ref
	lda BALL_Y
	cmp #(240-8)
	bcc .skipY2Ref
	lda <MY
	eor #$FF
	clc
	adc #1
	sta <MY
.skipY2Ref
	
	rts
;-------------------------------------
MovePaddle:	
;-------------------------------------
	lda Pad_Stat
	and #P_LEFT
	beq .skipLeft
	dec PADDLE1_X
	dec PADDLE1_X
.skipLeft
	lda Pad_Stat
	and #P_RIGHT
	beq .skipRight
	inc PADDLE1_X
	inc PADDLE1_X
.skipRight
	
	lda PADDLE1_X
	clc
	adc #8
	sta PADDLE2_X
	clc
	adc #8
	sta PADDLE3_X
	
	
	rts
;-------------------------------------
;	パッド入力
;-------------------------------------
Pad_Check:

	ldx	#1	
	stx	$4016
	dex
	stx	$4016
	
	
	lda	<Pad_Stat
	sta	<Pad_Old

	lda	#1		
	sta	<Pad_Stat

	ldx #8
.loop			
	lda	$4016
	and #1
	lsr	a
	rol	<Pad_Stat
	dex
	bne	.loop

	lda	<Pad_Stat
	ora	<Pad_Old
	sec
	sbc	<Pad_Old
	sta	<Pad_DownOneTime

	rts
;-------------------------------------
OBJInit:
;-------------------------------------
	lda #0
	sta <$00
	lda #$2
	sta <$01
	ldy #$FF
	
.loop	
	lda #0
	sta $200,y
	
	dey
	bne .loop
	
	lda #0
	sta <$00
	sta <$01
	
	rts
;-------------------------------------
BGPalInit:
;-------------------------------------
	lda #$23
	sta $2006
	lda #$C0
	sta $2006
	
	ldx #64
	
.loopset
	lda #0
	sta $2007
	dex
	bne .loopset
	
	
	lda #$2B
	sta $2006
	lda #$C0
	sta $2006
	
	ldx #64
	
.loopset2
	lda #0
	sta $2007
	dex
	bne .loopset2
	
	
	rts
;-------------------------------------
BGInit:
;-------------------------------------
	lda #32	;32x32=1024
	sta <$00
	ldy #32
	sta <$01
	
	lda #$20	;$2000
	sta $2006
	lda #$00
	sta $2006
.loop
	ldx #0
	stx $2007	;VRAM Write Zero
	
	dec <$00
	bne .loop
	lda #32
	sta <$00
	dec <$01
	bne .loop
	
	lda #32		;32x32=1024
	sta <$00
	ldy #32
	sta <$01
	
	lda #$28	;2800
	sta $2006
	lda #$00
	sta $2006
.loop2
	ldx #0
	stx $2007	;VRAM Write Zero
	
	dec <$00
	bne .loop2
	lda #32
	sta <$00
	dec <$01
	bne .loop2	
	
	lda #0
	sta <$00	;Memory Reset 0
	sta <$01
	
	rts
;-------------------------------------
SetBGArea:
;-------------------------------------
; A:00->2000 A:02->2800
;-------------------------------------
	ora #$08
	sta $2000
	rts
;-------------------------------------
SetBGArea2:	;A:00->2000 02:2800(forNMI)
;-------------------------------------
	asl a
	ora #$88
	sta $2000
	rts
;-------------------------------------

;-------------------------------------
ShowScreen:
;-------------------------------------
; A:00->hide 01->show
;-------------------------------------
	tax
	lda ShowHideTbl,x
	sta $2001
	rts
;-------------------------------------
SetScroll:
;-------------------------------------
; X: XDirScrollAmount Y:YDirScrollAmount
;-------------------------------------
;-------------------------------------
	lda $2002
	lda $2000
	stx $2005
	sty $2005
	rts
;-------------------------------------
IRQ:
	
	rti
	
MessageTbl:
	.db "BLOCK BREAK Let's Enjoy!!"
	.db 0	;番兵(データの末尾（まつび）に-1や0や999などの
	;特殊な値を置いてデータの終了をプログラム内のループに教える)
	
ShowHideTbl:
	.db $00,$1e


	;すぐ下もただのバイナリデータなので覚えなくていいです。
tilepal: ;"blockbreak.dat"
		 .db $0F ,$05 ,$25 ,$35 ,$0F ,$30 ,$26, $05 ,$0F ,$20 ,$10 ,$00 ,$0F ,$13, $23, $33 
		 .db $2D ,$01 ,$2C ,$3C ,$0F ,$05 ,$25, $35 ,$0A ,$05 ,$26 ,$30 ,$22 ,$16 ,$27, $18
	.bank 1
	
	
	.bank 2       ; バンク２
	.org $0000    ; $0000から開始

	;以下はただのバイナリデータなので特に覚える必要は全くないです。
	
	.incbin "blockbreak.chr"