#ゲーム内容
作成ROM
ファミコンROM作ってみた
上記リンクにあるゲームのコードになります。
コード全体のイメージとしては下記の記事をご参照ください。
ファミコンROM作ってみた:開発編(コード設計)
コード内で利用している共通ライブラリについては下記の記事をご参照ください。
ファミコンROM作ってみた:開発編(共通関数ライブラリ)
#global.c,h
プロダクト用にglobal.c,global.hを用意して、プロダクト専用の関数ライブラリを用意しました。
基本的にスコアといったゲーム的なデータや、パッド情報、スプライト情報といった各ファイルを横断するデータの管理と機能提供を行っています。
##src/global.h
#ifndef __GLOBAL__H__
#define __GLOBAL__H__
#include <fcsub.h>
#include "pattern.h"
//タイル番号からスプライト番号への変換マクロ
#define SPRITE_NO(x) (x-256)
//各スプライトタイプ定義
#define TYPE_CHAR 0
#define TYPE_ENEMY 1
#define TYPE_BULLET 2
#define TYPE_ITEM 3
#define TYPE_UNKNOWN 255
//パレットテーブルで先頭にしたい番号
#define PALETTE_TOP 8
//スプライトID特殊定義
#define SPRITE_DISABLE_NO 64
#define SPRITE_NEXT_NO 100
//最大位置
#define OUTSIDE_MAX (255)
//ゲームスコア用構造体
typedef struct gameScore {
signed short score;
unsigned char pddiing_num;
unsigned char gameover;
}gameScore;
//スプライト処理用関数ポインタ
typedef signed char (*sp_func)(signed char spr_no, unsigned char x, unsigned char y);
//スプライトテーブル
typedef struct SpriteTable {
signed char id;
unsigned char tileno;
signed short param1;
signed short pos_xy;
sp_func func;
}SpriteTable;
//スプライトテーブル初期化
void init_spr_idtbl();
//スプライトタイプ取得
unsigned char getSpriteType(unsigned char tile_no);
//空スプライトID取得
char GetDisableSprIdTbl(char n);
//スプライト追加
char AddSpriteSet4(unsigned short tile_no, unsigned char x, unsigned char y, char add_attr);
char AddSpriteSet1(unsigned short tile_no, unsigned char x, unsigned char y, char add_attr);
//スプライト削除
void SetRemoveSprite4(char spr_id);
void SetRemoveSprite1(char spr_id);
//グローバル変数のextern定義
extern gameScore g_gameScore;
extern SpriteTable g_spr_idtbl[SPRITE_NUM_MAX];
extern unsigned char g_pre_pad1;
extern unsigned char g_now_pad1;
#endif
##src/global.c
#include "global.h"
//ゲームスコア
gameScore g_gameScore;
//スプライトテーブル
SpriteTable g_spr_idtbl[SPRITE_NUM_MAX];
//パッド情報
//(各Vsyncループ内で1度だけ個別更新して、スプライト内の処理コードではこの変数を利用します)
unsigned char g_pre_pad1;
unsigned char g_now_pad1;
//スプライトテーブル初期化
void init_spr_idtbl()
{
char i;
for (i = 0; i < SPRITE_NUM_MAX; i++) {
g_spr_idtbl[i].id = SPRITE_DISABLE_NO;
}
}
//空スプライトID取得
char GetDisableSprIdTbl(char n)
{
char i, j, cnt;
for (i = 0; i <= SPRITE_NUM_MAX - n; i++) {
if (i % 4 != 0) {
continue;
}
cnt = 0;
for (j = 0; j < n; j++) {
if (g_spr_idtbl[i + j].id == SPRITE_DISABLE_NO) {
cnt++;
}
}
if (cnt >= n) {
return i;
}
}
return SPRITE_DISABLE_NO;
}
//スプライト追加
char AddSpriteSet4(unsigned short tile_no, unsigned char x, unsigned char y, char add_attr) {
char add_spr_no = GetDisableSprIdTbl(4);
if (add_spr_no != SPRITE_DISABLE_NO) {
g_spr_idtbl[add_spr_no].id = sp_append_set4(add_spr_no, x, y, SPRITE_NO(tile_no), add_attr | (pattern_pal_tbl[tile_no] & 3)); //
g_spr_idtbl[add_spr_no].tileno = SPRITE_NO(tile_no);
g_spr_idtbl[add_spr_no + 1].id = g_spr_idtbl[add_spr_no].id;
g_spr_idtbl[add_spr_no + 2].id = g_spr_idtbl[add_spr_no].id;
g_spr_idtbl[add_spr_no + 3].id = g_spr_idtbl[add_spr_no].id;
return g_spr_idtbl[add_spr_no].id;
}
return SPRITE_DISABLE_NO;
}
char AddSpriteSet1(unsigned short tile_no, unsigned char x, unsigned char y, char add_attr) {
char add_spr_no = GetDisableSprIdTbl(1);
if (add_spr_no != SPRITE_DISABLE_NO) {
g_spr_idtbl[add_spr_no].id = sp_append_set1(add_spr_no, x, y, SPRITE_NO(tile_no), add_attr | (pattern_pal_tbl[tile_no] & 3)); //
g_spr_idtbl[add_spr_no].tileno = SPRITE_NO(tile_no);
return g_spr_idtbl[add_spr_no].id;
}
return SPRITE_DISABLE_NO;
}
//スプライト削除
void SetRemoveSprite4(char spr_id)
{
char i;
sp_remove4(spr_id);
for (i = 0; i <= SPRITE_NUM_MAX; i++) {
if (g_spr_idtbl[i].id == spr_id) {
g_spr_idtbl[i].id = SPRITE_DISABLE_NO;
}
}
}
void SetRemoveSprite1(char spr_id)
{
char i;
sp_remove1(spr_id);
for (i = 0; i <= SPRITE_NUM_MAX; i++) {
if (g_spr_idtbl[i].id == spr_id) {
g_spr_idtbl[i].id = SPRITE_DISABLE_NO;
}
}
}
//スプライトタイプ取得
unsigned char getSpriteType(unsigned char tile_no) {
//数字が大きい順から
if (tile_no >= SPRITE_NO(SPRITE_PUDDING_TOP)) {
return TYPE_ITEM;
}
if (tile_no >= SPRITE_NO(SPRITE_ITEM_TOP)) {
return TYPE_ITEM;
}
if (tile_no >= SPRITE_NO(SPRITE_BULLET_TOP)) {
return TYPE_BULLET;
}
if (tile_no >= SPRITE_NO(SPRITE_ENEMY_TOP)) {
return TYPE_ENEMY;
}
if (tile_no >= SPRITE_NO(SPRITE_CHAR_TOP)) {
return TYPE_CHAR;
}
return TYPE_UNKNOWN;
}
##補足
char GetDisableSprIdTbl(char n)
取得したいスプライト数を入れて連続した空きスプライトを取得するコードですが、現状4の倍数でしか返していません。
ですので、最大スプライト数は16枚のままの状態です
char AddSpriteSet4(unsigned short tile_no, unsigned char x, unsigned char y, char add_attr)
char AddSpriteSet1(unsigned short tile_no, unsigned char x, unsigned char y, char add_attr)
void SetRemoveSprite4(char spr_id)
void SetRemoveSprite1(char spr_id)
ゲーム制御コードからスプライトを利用したり開放したりする場合は全て上記の関数を介して処理しています。
g_spr_idtbl[i].idに利用中かどうかの情報があり、使用していない場合はSPRITE_DISABLE_NOが入っています。
#参考・出典
日経BP発行「日経ソフトウエア」2021年3月号の特集記事「ファミコンで動くゲームを作ろう 第3部 オリジナルのゲームを完成させる」(著者:松原拓也)
※上記記事に掲載されたコードや内容を参考にしました。掲載にあたっては、著者および編集部の承諾をいただきました。
#関連記事一覧
ファミコンROM作ってみた
ファミコンROM作ってみた:開発編(画像コンバーター)
ファミコンROM作ってみた:開発編(環境)
ファミコンROM作ってみた:開発編(ビルド)
ファミコンROM作ってみた:開発編(コード設計)
ファミコンROM作ってみた:開発編(共通関数ライブラリ)
ファミコンROM作ってみた:開発編(プロダクト用関数ライブラリ)
ファミコンROM作ってみた:開発編(mainとフローの処理コード)
ファミコンROM作ってみた:開発編(キャラクター制御コード)