ファミコンROM作ってみた:開発編(共通関数ライブラリ）

#ゲーム内容
作成ROM
[ファミコンROM作ってみた](https://qiita.com/tmori/items/a130fb0364dc5f757d67)

上記リンクにあるゲームのコードになります。

コード全体のイメージとしては下記の記事をご参照ください。
[ファミコンROM作ってみた:開発編(コード設計）](https://qiita.com/tmori/items/d49fd2b261fcab86f0a4)

#ライブラリの利用について
###参考・出典
日経BP発行「日経ソフトウエア」2021年1月号の特集記事「ファミコンで動くゲームを作ろう 第2部 ライブラリの自作と実験プログラム」（著者：松原拓也氏）

※上記記事に掲載されたコードや内容を参考にしました。掲載にあたっては、著者および編集部の承諾をいただきました。

###fcsub.h,fcsub.c
ROMの作成にあたっては上記記事に掲載されたfcsub.h,fcsub.cを元に改変したものを利用しています。


##lib/fsub.h　※改変部分のみ記載

```
#ifndef __FCSUB__H__
#define __FCSUB__H__

～
元コードの#define 定義
～
↓追加改変分

#define SPRITE_NUM_MAX 64

#define PATTERN_MAX 512
#define BG_TILE_PTN_MAX 255
#define SPRITE_TILE_PTN_MAX 255

#define PALETTE_MAX 8
#define PALETTE_BG_MAX 4
#define PALETTE_SPRITE_MAX 4


typedef char nBool;
#define nTRUE 1
#define nFALSE 0

～
元コードの関数定義
～
↓追加改変分

//カナありBG文字列表示（X座標０～31、Y座標０～29、文字列ポインタ、かなフラグ）
void bg_printstr_kana(char x, char y, char *str, char kana_flg);

//制御文字除外用文字列数取得
unsigned char n_strlen(char* str);

//10進数５桁変数（格納先ポインタ、数値０～65535）
void num_to_str_z(char *str, unsigned short num);
void num_to_str_n(char *str, unsigned short num);

//スプライト 追加（X座標、Y座標、タイル０～255、アトリビュート０～255）
char sp_append4(unsigned char x, unsigned char y, unsigned char tile, char attr);
char sp_append1(unsigned char x, unsigned char y, unsigned char tile, char attr);
char sp_append_set4(char id_top,unsigned char x, unsigned char y, unsigned char tile, char attr);
char sp_append_set1(char id, unsigned char x, unsigned char y, unsigned char tile, char attr);

//スプライト 消去（スプライト番号０～１５）
void sp_remove4(char id_top);
void sp_remove1(char id);

//スプライト4枚 表示変更（スプライト番号０～15、タイル０～255、アトリビュート０～255）
void sp_char4(char id_top,unsigned char tile, unsigned char attr);
void sp_char1(char id, unsigned char tile, unsigned char attr);

//スプライト4枚 アニメ処理（スプライト番号０～15）
void sp_anime4(char id_top, unsigned char tile);
void sp_anime1(char id, unsigned char tile);

//スプライト4枚 移動（スプライト番号０～15、X座標０～255、Y座標（０～255）
void sp_ofs4(char id_top, unsigned char x, unsigned char y);
void sp_ofs1(char id_top, unsigned char x, unsigned char y);



#endif
```


##改変箇所
###＜用語説明＞
*スプライトID　*
　spr_idとしている部分が多いですが、実際に動かせるスプライト64枚それぞれのIDを指しています。

*タイル番号*
　パターンとして定義してあるBG,SPRITEの512パターンの番号です。
　スプライト番号とすると上記のスプライトIDと混乱する可能性があったので、タイル番号と呼称しています。

###＜関数リファレンス（追加変更部分のみ）＞
###かな文字、カナ文字を含めた表示
**void bg_printstr_kana(char x, char y, char *str, char kana_flg);**

**引数**
　char x　表示x座標
　char y　表示y座標
　char kana_flg  0だと全部”カナ”表示、1だと全部”かな”表示

**戻り値**
　なし

**説明**
　BGスプライトで表示する英数、カナ、かなをBGに配置します。
　なお、”</”、”/>”内に記載したｶﾅ文字はひらがなに変更します。

###かな文字、カナ文字を含めた文字数取得
**unsigned char n_strlen(char* str);**

**引数**
　char* str 文字列のポインタ

**戻り値**
　文字数

**説明**
　制御用の”</”、”/>”文字を除いた文字数を返します。

###指定した数値を文字列に格納する変数（最大5桁）
**void num_to_str_z(char *str, unsigned short num);**
**void num_to_str_n(char *str, unsigned short num);**

**引数**
　char* str           格納する文字列バッファ
　unsigned short num  文字列に変換する数値

**戻り値**
　なし

**説明**
　_zの関数の方は5桁分、0埋めされます。
　_nの関数の方は0埋めなしで文字列になります。

###スプライト系関数（関数名の後ろの4,1について）
**char sp_append?(unsigned char x, unsigned char y, unsigned char tile, char attr);**
**char sp_append_set?(char id_top,unsigned char x, unsigned char y, unsigned char tile, char attr);**
**void sp_remove?(char id_top);**
**void sp_char?(char id_top,unsigned char tile, unsigned char attr);**
**void sp_anime?(char id_top, unsigned char tile);**
**void sp_ofs?(char id_top, unsigned char x, unsigned char y);**

※上記の関数は関数の?部分に4ないし、1が入っています。

**説明**
　それぞれ、4，1は利用するスプライト枚数のセットです。
　もともとのライブラリでは4枚セットのスプライト処理関数しか用意されていませんでしたので、最大16枚しか制御できなかったのですが、1枚でも制御できるように関数を追加して関数名を変更してあります。

##lib/fsub.c(追加変更部分のみ）

```
void bg_printstr_kana(char x, char y, char *str, char kana_flg)
{
	ppu_address(0x2000 + x + ((unsigned short)(y * 32)));
	while (*str != 0) {
		unsigned char c = (unsigned char)str[0];
		if (str[0] == '<' && str[1] == '/'){
			kana_flg = 1;
			str += 2;
			continue;
		}
		if (str[0] == '/' && str[1] == '>'){
			kana_flg = 0;
			str += 2;
			continue;
		}

		if (c< 0x80) {
			PPUDATA = c;
		}
		else if(c >= 0xA0 && c <= 0xDF) {
			PPUDATA = c - 0xA0 + 0x80 + (kana_flg * 64);
		}
		str++;
	}
}
unsigned char n_strlen(char* str) {
	unsigned char strcnt = 0;
	unsigned char cnt = 0;
	while (str[cnt] != 0) {
		if (str[cnt + 0] == '<' && str[cnt + 1] == '/') {
			cnt += 2;
			continue;
		}
		if (str[cnt + 0] == '/' && str[cnt + 1] == '>') {
			cnt += 2;
			continue;
		}
		cnt++;
		strcnt++;
	}
	return strcnt;
}



//10進数n桁変数（格納先ポインタ、数値０～65535）
void num_to_str_n(char *str, unsigned short num)
{
	char cnt=0;
	if (num >= 10000) {
		str[cnt++]= (num / 10000)%10 + '0';
	}
	if (num >= 1000) {
		str[cnt++] = (num / 1000) % 10 + '0';
	}
	if (num >= 100) {
		str[cnt++] = (num / 100) % 10 + '0';
	}
	if (num >= 10) {
		str[cnt++] = (num / 10) % 10 + '0';
	}
	str[cnt++] = (num) % 10 + '0';
	str[cnt] = 0;
}
//スプライト4枚 追加（X座標、Y座標、タイル０～255、アトリビュート０～255）
char sp_append4(unsigned char x, unsigned char y, unsigned char tile, char attr)
{
	char i;
	for (i = 0; i < SPRITE_NUM_MAX-3; i++) {
		if (sp_gety(i) == OUTSIDE && sp_gety(i + 1) == OUTSIDE && sp_gety(i + 2) == OUTSIDE && sp_gety(i + 3) == OUTSIDE) {
			sp_ofs4(i, x, y);
			sp_char4(i, tile, attr);
			break;

		}
	}
	return (i);
}

//スプライト1枚 追加（X座標、Y座標、タイル０～255、アトリビュート０～255）
char sp_append1(unsigned char x, unsigned char y, unsigned char tile, char attr)
{
	char i;
	for (i = 0; i < SPRITE_NUM_MAX; i++) {
		if (sp_gety(i) == OUTSIDE) {
			sp_ofs1(i, x, y);
			sp_char1(i, tile, attr);
			break;

		}
	}
	return (i);
}

char sp_append_set4(char id_top, unsigned char x, unsigned char y, unsigned char tile, char attr) {
	sp_ofs4(id_top, x, y);
	sp_char4(id_top, tile, attr);
	return id_top;
}
char sp_append_set1(char id, unsigned char x, unsigned char y, unsigned char tile, char attr) {
	sp_ofs1(id, x, y);
	sp_char1(id, tile, attr);
	return id;
}

```
##引っかかった点
###各レジスタへの値の代入処理
例えば、bgスクロール処理の下記コードは最初ORが抜けてるのかな？とか思ったのですが、記事を見る限りレジスタが連続してX、Yの数値を期待しているらしく、仕様通りでした。

```
	PPUSCROLL = (x & 0xff);
	PPUSCROLL = y;
```
他にもPPUDATAに連続して入力していたりしている個所もあります。そのあたりの詳しくはNESの仕様に沿う形になるのかなと思われます。

#関連記事一覧
[ファミコンROM作ってみた](https://qiita.com/tmori/items/a130fb0364dc5f757d67)
[ファミコンROM作ってみた:開発編(画像コンバーター）](https://qiita.com/tmori/items/e952a9d5e150e60af393)
[ファミコンROM作ってみた:開発編(環境）](https://qiita.com/tmori/items/14e250c52d362a403cbe)
[ファミコンROM作ってみた:開発編(ビルド）](https://qiita.com/tmori/items/ab1f163667878a7c9bf5)
[ファミコンROM作ってみた:開発編(コード設計）](https://qiita.com/tmori/items/d49fd2b261fcab86f0a4)
[ファミコンROM作ってみた:開発編(共通関数ライブラリ）](https://qiita.com/tmori/items/bc0cebefe64959337428)
[ファミコンROM作ってみた:開発編(プロダクト用関数ライブラリ）](https://qiita.com/tmori/items/ad667c6dd8cd2c27f80b)
[ファミコンROM作ってみた:開発編(mainとフローの処理コード）](https://qiita.com/tmori/items/ed9f4d85fb7fcc99ccae)
[ファミコンROM作ってみた:開発編(キャラクター制御コード）](https://qiita.com/tmori/items/ddf96075f05d0a1f710d)