C言語ターミナルタイピングRPG「クムドール・クロニクル」v1.0.1|道中マップとバトル画面の遊びやすさを改善 | UNIX Cafe

* 当サイトでは、コンテンツの一部に広告を掲載しています。

System Note $ cat /proc/ai-disclosure

本記事の構成および論理分析にはAI(人工知能)を使用しています。情報の正確性は、システム管理者(UNIXユーザー)による手動検証済みです。

C言語ターミナルタイピングRPG「クムドール・クロニクル」v1.0.1|道中マップとバトル画面の遊びやすさを改善 | UNIX Cafe

C言語製のターミナルタイピングRPG「クムドール・クロニクル」v1.0.1を公開しました。

今回の更新で主に直したのは、遊んでいる時に気になる小さな違和感です。

マップとバトルの切りかえ時に、マップ画面が上下に揺れる。マップで連続して押したキーが、そのままバトルの入力欄に入る。K のマークはあるのに、使い道がよく分からない。

こういう細かい部分をコツコツ直していくと、ターミナル上の文字だけのゲームでも、かなり「ゲームをしている」感がでてきます。

GitHubでは kumdor-chronicle という名前で公開しています。

git clone https://github.com/k1117n-cmyk/kumdor-chronicle.git
cd kumdor-chronicle
make
make run
目次

今回の改善テーマ

v1.0.1では、主に次の点を改善しました。

  • マップ画面を見やすくする
  • マップとバトルの入力を分ける
  • 探索要素に意味を持たせる
  • ステージ間の物語を短く見せる
  • バトル開始表示を短くする

新しい機能を大きく足すというより、遊んでいる途中のひっかかりを減らす更新です。

道中マップの表示を整える

「クムドール・クロニクル」では、各ステージの戦闘前に11行x21列の道中マップを歩きます。

以前の表示では、HPや所持金は見えるものの、道具やキーの状態が分かりにくくなっていました。そこで、マップ下部の表示を1行にまとめました。

HP: 9/10  所持金: 44  道具: 小さな水晶  キー: 宝箱

また、マップ上の記号も色付きで表示するようにしました。プレイヤー、回復、罠、鍵、宝箱、道具、落とし物、マーケットが見分けやすくなります。

S=スタート  G=ゴール  M=マーケット  ^>v< = あなた  !=手がかり  +=回復  x=罠  K=鍵  C=宝箱  I=道具  $=落とし物

ターミナルの色表示が不要な場合は、従来通り NO_COLOR=1 で色なし表示にできます。

画面の揺れを減らす

プレイ中に気になったのが、マップの開始位置が操作ごとに上下へ揺れることでした。

操作メッセージがない時、1行だけ出る時、2行出る時があり、その下にマップが続くため、ノートパソコンの画面では目がちらつきます。

そこで、道中画面の上部を固定しました。

【道中】アズドフ村の墜落跡
討伐数: 7/10  / ゴールまで同じ地図を進む
ログ1行目
ログ2行目
ログ3行目
       +-------------------------------------------+

操作メッセージが1行しかない時も、残りは空行で埋めます。これにより、マップの開始位置が毎回同じになり、画面の揺れがかなり減りました。

文字だけの画面では、こうした縦位置の安定が思った以上に効果があります。

K と宝箱 C

以前の K は、キーを入手できるイベントとして置いていました。しかし、そのキーをどう使うのかの設定が弱く、プレイヤーから見るといつ使えるのかが分かりにくい状態でした。

そこで、K を「宝箱の鍵」とし、マップ上に C の宝箱を追加しました。

  • K: 宝箱の鍵
  • C: 宝箱
  • 鍵があれば宝箱を開けられる
  • 宝箱を開けるとクム銀貨を入手
  • HPが減っている時は、追加で少し回復

小さな変更ですが、マップ上で K を見つけた時に「あとで宝箱を開けられる」という目的が生まれます。

イベント配置をランダムにする

もうひとつ気になったのは、KM などのイベントが毎回同じ場所にあることでした。

地図の形が同じでも、イベントの場所が変わるだけで探索の印象は変わります。そこで、ステージ開始時にイベントを通行可能なマスへランダム配置するようにしました。

ランダム配置するのは、主に次のようなイベントです。

  • !: 手がかり
  • +: 回復
  • x: 罠
  • K: 鍵
  • C: 宝箱
  • I: 道具
  • $: 落とし物
  • M: マーケット

ただし、壁 #、スタート位置、ゴール位置には置かないようにしています。地図の形はそのままに、イベントの位置だけを変える仕組みです。

そのステージ中は同じ配置を維持します。戦闘のたびに全部が動くと探索の記憶が使えなくなるためです。

マップ入力とバトル入力を分ける

今回、ターミナルアプリらしい問題も直しました。

道中マップでは、f で前進します。ターミナルで遊ぶ時はEnterなしで反応するため、つい f を押しっぱなしにしてしまいます。

ところが、マップからバトルへ移る瞬間に余った f が標準入力に残っていると、そのままバトルの解答欄に入ってしまいます。

課題を入力してEnter(:helpヘルプ / :keys対応表): fffffffff

これはゲームとしては、かなりまずいです。プレイヤーがバトル開始時にfのはみ出しに気がつかないと、ちゃんと入力しているつもりでもミスタイプになります。

対策として、マップからバトルへ移る直前に端末入力を破棄する処理を入れました。

一時的に端末を非カノニカル、非エコーに切り替え、残っている入力を1文字ずつ捨てます。さらに、短い無入力状態が続くまで待つことで、キーリピートや連打の残りも拾って捨てます。

これで、マップ操作とバトル入力の境目がかなりきれいになりました。

バトル開始表示を短くする

以前のバトル開始表示は、演出としては分かりやすいものの、縦に長くなっていました。

*************** BATTLE START ***************
右手の番人が立ちはだかった!
敵特性: 標準 - 特別な追加効果はない
********************************************

さらに敵のセリフや殺気の表示が続くため、マップから最初の課題までの距離が長くなります。

v1.0.1では、これを短くまとめました。

[BATTLE] 右手の番人 / 特性: 標準
[敵] 「鐘は鳴らぬ。鳴らすのは、おまえの右手だ」
[殺気] 右手の番人が鐘楼の影を揺らし、セミコロンを混ぜた構えに変えた。
[状態] あなた Lv:1 EXP:5/10 HP:6/10 状態:通常 | 右手の番人 遭遇戦
[課題] j;kj ;ljk

情報は残しつつ、枠線や長い説明を減らしています。ターミナルの縦幅は限られているため、課題入力に近い情報を優先しました。

プロローグスキップとステージ遷移

最初のメニューには、プロローグをスキップする項目も追加しました。

0: ゲームの遊び方
1: ビッグハンド訓練場で練習する
2: クムドールへ向かう
3: プロローグをスキップして向かう
4: 旅の途中から始める
q: 終了

初回はプロローグを読んでほしい一方で、繰り返し遊ぶ時にはすぐに本編へ入りたくなります。そこで、通常開始とスキップ開始を分けました。

また、ステージ2以降へ進む時には、次のステージの短い物語をタイプライター表示するようにしました。プロローグと同じく、ターミナル上では一文字ずつ表示されます。

長い説明を毎回入れるのではなく、1行から2行程度で次の場所へつなぐ演出です。

v1.0.1の変更一覧

v1.0.1の主な変更は次の通りです。

  • 最初のメニューに「プロローグをスキップして向かう」を追加
  • ステージ遷移時に次ステージの短い物語をタイプライター表示
  • 道中イベントをステージ開始時にランダム配置
  • 宝箱 C と宝箱の鍵 K を追加
  • マップ下部にHP、所持金、道具、キーを1行表示
  • マップ上のイベント記号とプレイヤー記号を色付き表示
  • 道中画面のヘッダとログ行数を固定
  • マップからバトルへ移る時の残入力を破棄
  • バトル開始表示を短縮

おわりに

今回は「大きな機能を足す」よりも、「遊んで気になった違和感を直す」変更でした。

ターミナルゲームは文字だけでできていますが、表示位置、色、空行、入力バッファの扱いで、遊び心地はかなり変わります。

特に、画面の揺れと入力の混入は、ゲーム内容とは別のところで集中力を削ってしまいます。そこを整えるだけでも、プレイヤーが見るべきものに集中しやすくなります。

次は、道具や地形効果をもう少しゲーム性のある形へつなげたいと考えています。たとえば、水場ではアクアラングが役立つ、危険地帯では耐熱ブーツが効く、といった形です。

文字だけのターミナルRPGでも、まだまだ遊べる余地はありそうです。

押しっぱなしのキーが、バトル入力にはみ出す問題を直す

C言語製のターミナルタイピングRPG「クムドール・クロニクル」で、マップ画面からバトル画面へ戻る時の入力処理をもう一度見直しました。

今回の修正は、機能追加というより操作感の修正です。

道中マップでは f で前進しますが、ターミナルではEnterなしで動くため、プレイヤーは自然に f を連打したり、押しっぱなしにしたりします。

ところが、その直後に敵と遭遇してバトルへ移ると、余った fspacej が標準入力に残り、バトルの解答欄へ入ってしまうことがありました。

課題を入力してEnter(:helpヘルプ / :keys対応表): f

プレイヤーとしては、まだ課題を見て入力を始めたつもりがないのに、解答欄にはすでに文字が入っている。タイピングゲームとしては、これはかなり気持ち悪い状態です。

前回入れていた対策

前回の時点で、マップからバトルへ移る直前には入力を捨てる処理を入れていました。

具体的には、一時的に端末を非カノニカル、非エコーに切り替え、標準入力に残っている文字を1文字ずつ読み捨てます。さらに、短い無入力状態が続くまで待ち、キーリピートや連打の残りも捨てます。

この処理により、マップ操作で積まれた f の多くは消せるようになりましたが、何度かプレイしてみると、すっきりと解決していないことがわかりました。

入力を捨て終わってから、バトル画面が表示され、解答プロンプトが出て、fgets() が標準入力を読むまでには、わずかな隙間があります。

その間に指が space に当たったり、fj が押し込まれたままになっていると、キーリピートが遅れて届きます。

すると、せっかく直前に入力を捨てても、バトル側の通常入力がその文字を拾ってしまいます。

今回の方針

今回の修正では、マップ側の入力破棄だけに頼らず、バトル側にも一回限りの入力ガードを追加しました。

流れは次のようになります。

マップで敵と遭遇
  
マップ側で残入力を捨てる
  
次のバトル入力だけガード対象にする
  
バトル開始表示
  
課題を表示
  
解答プロンプト直前で、もう一度「静かになるまで」入力を捨てる
  
ここから解答入力を受け付ける

ポイントは、バトルの課題を表示したあと、解答欄を出す直前にもう一度待つことです。

これにより、画面遷移中に遅れて届いたキーも、バトルの解答欄へ入る前に捨てられます。

一回限りのガードにする

バトルのすべての入力前で待つと、通常のテンポが悪くなります。

必要なのは、マップからバトルへ移った直後の最初の入力だけです。そこで、src/game.c からバトルへ移るところでフラグを立てるようにしました。

discard_pending_terminal_input();
guard_next_battle_input_until_quiet();
start_stage_bgm(stage + 1);

guard_next_battle_input_until_quiet() は、次の player_turn() だけをガード対象にします。

バトル側では、課題を表示したあと、入力時間の計測を始める前に待機処理を呼びます。

wait_for_guarded_battle_input();
input_started_at = monotonic_seconds();
if (!read_input(input, ...)) {
    ...
}

この位置に置くことで、時間制限つきの課題でも、待機時間を制限時間に含めないようにしています。

静かになるまで待つ

実際の待機処理では、端末を一時的に非カノニカル、非エコーにします。

raw_terminal = old_terminal;
raw_terminal.c_lflag &= (tcflag_t)~(ICANON | ECHO);
raw_terminal.c_cc[VMIN] = 0;
raw_terminal.c_cc[VTIME] = 0;

その状態で select() を使い、短い間隔で標準入力を見ます。

入力があれば1文字読み捨て、静かな状態のカウントをリセットします。入力がなければ静かな状態として数えます。

今回の設定では、50ミリ秒の無入力を8回、つまり約400ミリ秒続けて確認します。

#define BATTLE_INPUT_QUIET_CHECKS 8
#define BATTLE_INPUT_QUIET_USEC 50000

キーリピートや連打の残りが届いている間は、静かな状態になりません。文字を捨て続け、入力が止まってから解答欄へ進みます。

ただし、キーが押しっぱなしのままだといつまでも待つ可能性があります。そこで最大待機時間も入れています。

#define BATTLE_INPUT_GUARD_MAX_SECONDS 8.0

8秒たっても入力が止まらない場合は、そこでガードを終えます。無限に待機するよりも、プレイヤーが状況を確認できることを優先しました。

非TTYでは何もしない

このゲームはターミナルで遊ぶ想定ですが、テストやスクリプト実行では標準入力がパイプになることがあります。

そのため、今回のガードは isatty(STDIN_FILENO) が真の時だけ動きます。

if (isatty(STDIN_FILENO)) {
    ...
}

パイプ入力では待機も端末モード変更も行わないため、自動チェックの入力列には影響しません。

実際に、修正後もビルドと既存チェックは通っています。

make
sh scripts/check_save.sh

小さな待ち時間で操作感を守る

今回の修正で入る待ち時間は、通常なら約400ミリ秒(0.4秒)です。

数字だけ見ると少し待っているように見えますが、入るのはマップからバトルへ移った直後の最初の解答欄だけです。通常のバトル中の連続入力には入りません。

タイピングゲームでは、プレイヤーが意図して打った文字と、画面遷移中に残った文字を分けることが重要です。

今回の変更で、マップ操作の勢いがそのまま解答欄へはみ出す場面をかなり減らせました。

コミットは次のものです。

67f5705 Guard battle input after map transitions

修正したファイルは次の3つです。

  • include/battle.h
  • src/battle.c
  • src/game.c

ターミナルのゲームは、文字だけで動いているように見えて、実際には端末モード、標準入力、キーリピート、画面遷移のタイミングがプレイ感に強く出ます。

大きな変更ではありませんが、こういう細かい入力の境目を整えることで、タイピングRPGとしての感触はかなり安定します。

GitHub Repository

GitHub
kumdor-trial
C言語製ターミナルタイピングRPG「クムドール・クロニクル」
github.com/k1117n-cmyk/kumdor-chroniclel

C言語の次にUnityやUnreal Engineを学ぶ選択肢

用途ごとに選ぶ C言語のおすすめ本

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

のいのアバター のい UNIX Cafe マスター

Macintosh Color Classicから始まった旅は、長いWindows時代を経て、Windows10のサポート終了をきっかけにUNIXの世界へ戻ってきました。UNIX Cafeでは、UNIX・Linux・そしてMacな世界を、むずかしい言葉を使わず、物語のように書いています。プログラミングは、アイデアをコンピューターに伝えるための言葉です。簡単な単語と文法を覚えれば、誰でもコマンドを使えます。ぜひ一度、やさしいプログラミングの世界をのぞいてみてください。

目次