
STEP04:ui層の実装(タイトル・エンディング・ステータス表示)
STEP04では、画面に表示する処理をまとめる ui層 を作ります。
ゲーム本体(探索・戦闘など)の処理から「表示の細かい手順」を追い出して、コードを読みやすく・直しやすくするのが目的です。
このSTEPで実装するものは次の3つです。
- タイトル画面(開始/遊び方/終了)
- エンディング表示(クリア演出・メッセージ)
- ステータス表示(パーティのHP/MPなどの一覧表示)
前STEPまでの土台も活用します。
- platform層:画面クリア、色、効果音
- input層:安全な数値入力、メニュー選択
ui層の役割
| 層 | 主な責務 | 例 |
|---|---|---|
| input | 入力を安全に受け取る | choose_menu() |
| platform | OS差分(画面クリア、色、音) | clear_screen(), platform_set_color() |
| ui | 見た目・表示の統一 | タイトル、枠、ステータス表 |
ui層は「ゲームのルール」を持ちません。
「どう表示するか」だけに集中させます。
今回追加するファイル
| ファイル | 内容 |
|---|---|
| ui.h | ui層の公開関数(宣言) |
| ui.c | タイトル・エンディング・ステータス表示の実装 |
ui.h(公開API)
ここでは、後続STEPでも使いやすい形にしておきます。
- タイトルの戻り値は enum(どれを選んだか)
- ステータス表示は「UI側専用の軽い構造体」を使う(後でcharacters層ができても繋げやすい)
/* ui.h */
#ifndef UI_H
#define UI_H
#include "common.h"
/* タイトルメニューの選択結果 */
typedef enum UiTitleChoice {
UI_TITLE_START = 1,
UI_TITLE_HOWTO = 2,
UI_TITLE_QUIT = 3
} UiTitleChoice;
/* 表示用ステータス(UIに必要な最低限だけ) */
typedef struct UiStatusRow {
const char *name;
int level;
int hp, hp_max;
int mp, mp_max;
} UiStatusRow;
/* 画面表示の基本部品 */
void ui_wait_enter(const char *prompt);
void ui_print_header(const char *title);
/* タイトル/エンディング */
UiTitleChoice ui_title_screen(void);
void ui_show_howto(void);
void ui_show_ending(int is_clear);
/* ステータス表示 */
void ui_show_party_status(const UiStatusRow *rows, int count, int gold);
#endifui.c(実装)
ポイントは「見た目を固定化する小さな部品」を用意しておくことです。
- 画面上部の見出し(ヘッダ)
- Enter待ち
- ステータス表の描画
ここでは platform層の関数名を次のように仮定します(あなたのSTEP02の名前に合わせて読み替えてOKです)。
- clear_screen():画面クリア
- sfx_beep(kind):ビープ(kindは0=決定など)
- platform_set_color_xxx / platform_reset_color:色(もし用意済みなら使う)
色関数がまだ無い・名前が違う場合でも、色部分はコメントアウトしても動きます。
/* ui.c */
#include "ui.h"
#include "input.h"
/* STEP02のplatform層関数(名前はあなたの実装に合わせて調整) */
void clear_screen(void);
void sfx_beep(int kind);
/* 色がある場合だけ使う想定(無いなら削除/コメントアウトでOK) */
void platform_set_color_title(void);
void platform_set_color_accent(void);
void platform_reset_color(void);
static void print_line(int width) {
for (int i = 0; i < width; i++) putchar('=');
putchar('\n');
}
void ui_wait_enter(const char *prompt) {
char buf[8];
if (prompt && prompt[0]) printf("%s", prompt);
(void)read_line(buf, (int)sizeof(buf));
}
void ui_print_header(const char *title) {
clear_screen();
/* platform_set_color_title(); */
print_line(60);
printf("%s\n", title);
print_line(60);
/* platform_reset_color(); */
putchar('\n');
}
UiTitleChoice ui_title_screen(void) {
for (;;) {
ui_print_header("BATTLE RPG");
/* platform_set_color_accent(); */
printf("1) Start\n");
printf("2) How to play\n");
printf("3) Quit\n");
/* platform_reset_color(); */
putchar('\n');
int sel = choose_menu("Select (1-3): ", 1, 3);
sfx_beep(0);
return (UiTitleChoice)sel;
}
}
void ui_show_howto(void) {
ui_print_header("HOW TO PLAY");
printf("Move on the map with numeric keypad.\n");
printf(" 8: Up 2: Down 4: Left 6: Right\n");
putchar('\n');
printf("On your journey, you may find:\n");
printf(" - Enemies (battle)\n");
printf(" - Treasure chests (items)\n");
printf(" - Inn (full recovery)\n");
printf(" - Church (revive)\n");
putchar('\n');
ui_wait_enter("Press Enter to return...");
sfx_beep(0);
}
void ui_show_ending(int is_clear) {
if (is_clear) {
ui_print_header("THE END");
/* platform_set_color_title(); */
printf("Congratulations!\n");
printf("You defeated the final enemy and saved the world.\n");
/* platform_reset_color(); */
putchar('\n');
sfx_beep(3); /* 勝利 */
} else {
ui_print_header("GAME OVER");
printf("Your party was defeated...\n");
putchar('\n');
sfx_beep(4); /* 警告など */
}
ui_wait_enter("Press Enter to quit...");
}
void ui_show_party_status(const UiStatusRow *rows, int count, int gold) {
/* 画面のどこで呼ぶかはゲーム側次第なので、ここでは「表示だけ」 */
printf("[Party Status]\n");
print_line(60);
printf("%-12s %5s %12s %12s\n", "Name", "Lv", "HP", "MP");
for (int i = 0; i < count; i++) {
const UiStatusRow *r = &rows[i];
char hpbuf[32];
char mpbuf[32];
snprintf(hpbuf, sizeof(hpbuf), "%d/%d", r->hp, r->hp_max);
snprintf(mpbuf, sizeof(mpbuf), "%d/%d", r->mp, r->mp_max);
printf("%-12s %5d %12s %12s\n", r->name, r->level, hpbuf, mpbuf);
}
print_line(60);
printf("Gold: %d\n", gold);
putchar('\n');
}main.c 側の最小動作例(UIの動作確認)
STEP04の時点では、探索や戦闘がまだ無くてもOKです。
「タイトル → 遊び方 → 戻る」「開始を選んだらダミーでステータス表示」「終了」だけで土台確認できます。
/* main.c(STEP04の動作確認用の例) */
#include "ui.h"
int main(void) {
for (;;) {
UiTitleChoice c = ui_title_screen();
if (c == UI_TITLE_QUIT) {
ui_show_ending(0);
break;
}
if (c == UI_TITLE_HOWTO) {
ui_show_howto();
continue;
}
/* START を選んだ場合:今はダミー表示 */
ui_print_header("ADVENTURE (DUMMY)");
UiStatusRow party[3] = {
{"Hero", 1, 30, 30, 10, 10},
{"Mage", 1, 18, 18, 25, 25},
{"Warrior", 1, 40, 40, 5, 5}
};
ui_show_party_status(party, 3, 120);
ui_wait_enter("Press Enter to return to title...");
}
return 0;
}このSTEPの狙い(後の拡張がラクになる)
ui層にまとめておくと、後でこういう変更が簡単になります。
| 変更したいこと | ui層があると |
|---|---|
| 表示を豪華にしたい | ui.cだけ直せばよい |
| ステータス項目を増やす | UiStatusRowと表示を更新 |
| 画面の統一感を出したい | ui_print_headerを全画面で使う |
| 色や効果音の演出を増やす | platform呼び出しをui側に追加 |
次のSTEPへのつながり(STEP05予告)
次のSTEP05では、探索に必要な「マップ表示」や「カーソル移動」など、表示+入力の組み合わせが増えていきます。
STEP04の ui層があると、探索画面でも
- ui_print_header(...)
- ui_show_party_status(...)
- choose_menu / 安全入力
という形で、処理の見通しがかなり良くなります。
