セーブ・ロード機能を使う

セーブスロットごとに距離を保存し、またスロットに対応した読み込みを行います。

2022-06-30
公開

コード

res_image.res

  1. TILESET mark "mark.png" NONE ALL

main.c

  1. #include <genesis.h>
  2. #include "res_image.h"
  3.  
  4. static void JoyInit();
  5. static void JoyEvent(u16 joy, u16 changed, u16 state);
  6. static void saveRecorrido(u8 state, u16 recorrido);
  7. static u16 loadRecorrido(u8 state);
  8.  
  9. enum E_LinePosition {
  10. RECORRIDO,
  11. SAVE_SLOT,
  12. LOAD_SLOT,
  13. };
  14.  
  15. typedef struct {
  16. u16 current; // 押下しているキー
  17. u16 trigger; // 押し始めたキー
  18. u16 off; // 離したキー
  19. } S_JoyState;
  20. S_JoyState JoyState;
  21.  
  22. int main(u16 hard) {
  23. JoyInit();
  24.  
  25. // 各部テキストの描画
  26. u16 posX = 13;
  27. u16 posY = 11;
  28. VDP_drawText("< 0 >", posX, posY - 1);
  29. VDP_drawText("SAVE < 0 >", posX, posY + 1);
  30. VDP_drawText("LOAD < 0 >", posX, posY + 3);
  31. VDP_drawText(":SELECT A:RUN", posX, posY + 6);
  32.  
  33. // マーク類の描画
  34. VDP_loadTileSet(&mark, TILE_USERINDEX, DMA);
  35. // NOTE: TILE_ATTR_FULL(PAL0, FALSE, FALSE, FALSE, index) == index
  36. VDP_fillTileMapRect(BG_A, TILE_USERINDEX, posX, posY, 11, 1); // 下線
  37. VDP_setTileMapXY(BG_A, TILE_USERINDEX + 1, posX - 2, posY + 6); // 上矢印
  38. VDP_setTileMapXY(BG_A, TILE_USERINDEX + 2, posX - 1, posY + 6); // 下矢印
  39.  
  40. // 各部位の変数
  41. char blankLine[] = " ";
  42. u8 lineNum = 0;
  43. u16 recorrido = 0;
  44. u8 saveSlot = 0;
  45. u8 loadSlot = 0;
  46.  
  47. // メインループ
  48. u16 counter = 0;
  49. while (TRUE) {
  50. SYS_doVBlankProcess();
  51.  
  52. // 動作のウェイト
  53. if (counter++ & 3) {
  54. continue;
  55. }
  56.  
  57. // 現在のライン位置
  58. u8 currentLinePos = lineNum % 3;
  59.  
  60. // キー操作による各処理
  61. if (JoyState.trigger & BUTTON_UP) {
  62. lineNum--;
  63. }
  64. if (JoyState.trigger & BUTTON_DOWN) {
  65. lineNum++;
  66. }
  67. if (JoyState.trigger & BUTTON_LEFT) {
  68. switch (currentLinePos) {
  69. case RECORRIDO:
  70. recorrido--;
  71. break;
  72. case SAVE_SLOT:
  73. saveSlot--;
  74. break;
  75. case LOAD_SLOT:
  76. loadSlot--;
  77. break;
  78. }
  79. }
  80. if (JoyState.trigger & BUTTON_RIGHT) {
  81. switch (currentLinePos) {
  82. case RECORRIDO:
  83. recorrido++;
  84. break;
  85. case SAVE_SLOT:
  86. saveSlot++;
  87. break;
  88. case LOAD_SLOT:
  89. loadSlot++;
  90. break;
  91. }
  92. }
  93. if (JoyState.trigger & BUTTON_A) {
  94. switch (currentLinePos) {
  95. case SAVE_SLOT:
  96. saveRecorrido(saveSlot & 3, recorrido);
  97. break;
  98. case LOAD_SLOT:
  99. recorrido = loadRecorrido(loadSlot & 3);
  100. break;
  101. }
  102. }
  103.  
  104. // 描画刷新
  105. // 下線
  106. VDP_drawText(blankLine, posX, posY + currentLinePos*2);
  107. VDP_fillTileMapRect(BG_A, TILE_USERINDEX, posX, posY + (lineNum%3)*2, 11, 1);
  108. // 距離
  109. char strRecorrido[6];
  110. sprintf(strRecorrido, "%5d", recorrido);
  111. VDP_drawText(strRecorrido, posX + 3, posY - 1);
  112. // セーブスロット
  113. char strSlot[2];
  114. intToStr(saveSlot & 3, strSlot, 1);
  115. VDP_drawText(strSlot, posX + 8, posY + 1);
  116. // ロードスロット
  117. intToStr(loadSlot & 3, strSlot, 1);
  118. VDP_drawText(strSlot, posX + 8, posY + 3);
  119. }
  120.  
  121. return 0;
  122. }
  123.  
  124. /**
  125. * コントローラの初期化
  126. */
  127. static void JoyInit() {
  128. memset(&JoyState, 0, sizeof(S_JoyState));
  129.  
  130. JOY_setEventHandler(JoyEvent);
  131. }
  132.  
  133. /**
  134. * 1Pコントローラの状態を設定する
  135. * @param joy 変更のあったコントローラ
  136. * @param changed 変更のあったボタン
  137. * @param state 押されているボタン
  138. */
  139. static void JoyEvent(u16 joy, u16 changed, u16 state) {
  140. if (joy != JOY_1) {
  141. return;
  142. }
  143.  
  144. // 押し始めたボタン
  145. JoyState.trigger = state & changed;
  146. // 離したボタン
  147. JoyState.off = changed & ~state;
  148. // 押しているボタン
  149. JoyState.current = state;
  150. }
  151.  
  152. /**
  153. * 距離を保存する
  154. * @param slot セーブスロット
  155. * @param recorrido 距離
  156. */
  157. static void saveRecorrido(u8 slot, u16 recorrido) {
  158. // SRAMを読み書き可能にする
  159. SRAM_enable();
  160.  
  161. // 距離をSRAMに書き込む
  162. // NOTE: SRAM_writeXXの第1引数のoffsetは1増えるごとにSRAMを8bit進む
  163. // つまりSRAM_writeWordの次に書き込むならoffsetを2、
  164. // SRAM_writeLongの次に書き込むならoffsetを4進めないと、
  165. // 前に保存した値の一部を上書きしてしまう
  166. // 例: wordサイズのデータを保存した後、offsetを1だけ進めて
  167. // 次のwordサイズのデータを保存した場合
  168. // |------> offset: +1
  169. // |- 保存したかった場所 -|
  170. // xxxxxxxx yyyyyyyy yyyyyyyy 00000000
  171. // |-- 保存した場所 --|
  172. // 前の値(xxxxxxxx xxxxxxxx)の後半を上書きしてしまっている
  173. SRAM_writeWord(slot * 2, recorrido);
  174.  
  175. // SRAMを読み込み専用にする
  176. SRAM_enableRO();
  177. }
  178.  
  179. /**
  180. * 保存された距離を読み込んで返す
  181. * @param slot セーブスロット
  182. * @return u16 距離
  183. */
  184. static u16 loadRecorrido(u8 slot) {
  185. // NOTE: SRAM_readXXの引数offsetは1増えるごとにSRAMを8bit進む
  186. // wordサイズで保存された値の次の値を読み込むならoffsetを2、
  187. // longサイズで保存された値の次の値を読み込むならoffsetを4進めないと、
  188. // 前の値の一部を読み込んでしまう
  189. // 例: wordサイズのデータを読み込んだ後、offsetを1だけ進めて
  190. // 次のwordサイズのデータを読み込んだ場合
  191. // |------> offset: +1
  192. // |- 読み込みたい値 -|
  193. // xxxxxxxx xxxxxxxx yyyyyyyy yyyyyyyy
  194. // |--- 読み込む値 ---|
  195. // 前の値の後半+欲しい値の前半を読み込んでしまっている
  196. return SRAM_readWord(slot * 2);
  197. }

実行結果

上記コードをSGDK1.70でコンパイルし
Gens v2.14 Souvenirで実行したスクリーンショット

解説

コード内にも書いてありますが、SRAM_readXXやSRAM_writeXXの第1引数offsetは1増減することで、保存もしくは読み込むデータのサイズ(byte, word, long)に関わらずSRAM領域を8bit移動します。
保存するデータのbit長とその範囲を間違えると、既存の保存データを壊したり、おかしなデータを読み込むことになるため注意が必要です。

なお、offsetの最大値は511のようです。つまり512 * 8bit = 4096bitのデータを保存できます。
範囲を超えた場合、offsetが0の位置に戻り、値を上書きしていきます。

ダウンロード