この記事は「ノンフィールドRPG 作り方」を、感覚論ではなく“再現できる手順”で固めた検証ログです。
目的は3つ。
「ノンフィールドとは」を動く最小単位に落とす PC/スマホで破綻しないUIを、追加コード最少で確認する 同じ要件をRPGツクールで作る場合の判断材料を作る(定性比較)
結論の要約: ・コアは「前進→遭遇→コマンド→結果」の1ループで十分検証できる。
・スマホは SafeArea と 物理サイズ(約48dp)を先に押さえると最短で安定する。
・「名作」らしさはリッチさよりテンポの管理(1タップ→反応まで<300ms)1 。
以下、環境・手順・コード・比較・不具合修正の順で記録します。
検証の前提と評価軸(「ノンフィールドとは」を実装観点に翻訳)
本稿ではノンフィールドRPGを「歩行マップを持たず、単一の“進行入力”で出現/戦闘/リソース消費が進むRPG」と定義します。
評価軸は以下。
・再現性:同手順で誰がやっても“遊べる”まで到達できるか ・最小性:スクリプト1〜2本、UI4ボタンでコア体験が成立するか ・スマホ適合:SafeArea/解像度/タップ性で致命的崩れがないか ・拡張余地:数値やスキルを後から外出しできる見通しがあるか
除外するもの(今回は検証対象外): ・広いメニュー階層、複雑な装備/属性、ストーリー演出。
・オンライン要素。
必要になったら後段に差し替える前提です。
再現手順:Unityで“最小でも遊べる”ところまで(PC→スマホ)
環境:Unity(3D/2D混在でOK)。
テンプレートは3Dコア。
新規プロジェクトから開始。
手順A:シーン/空オブジェクト 空のシーンを開く → GameRoot(空のGameObject)を作成。
下記「GameLoop.cs」を GameRoot にアタッチ。
Canvas(Screen Space-Overlay)を作る。
Panelの上にButtonを4つ(Attack/Guard/Heal/Escape)。
各Buttonの OnClick に GameRoot をドラッグ → 関数 GameLoop.EnqueueCommand(string) を選択 → 引数に "Attack" など文字列を設定。
手順B:ログ表示(任意) ・TextMeshProのUI Textを1つ置き、GameLoop の OnGUI() で GUILayout.Label(...) か、TMPへ簡易表示(今回は省略可)。
手順C:スマホ基本対応 Canvas Scaler → “Scale With Screen Size”(基準 1080×1920、Match 0.5)。
ボタンサイズは最小48dp相当(だいたい縦横100〜120px目安)。
パネル親に「SafeAreaApplier.cs」をアタッチ(後述コード)。
Android/iOSビルドでノッチやホームバーに重ならないことを確認。
これでPC(A/G/H/Eキー)・スマホ(4ボタン)両方で、前進→遭遇→戦闘→結果→再開が回ります。
コアループ実装:1クラスで検証に十分な挙動をまとめる
実装の意図: ・1ファイルで「前進」「遭遇」「戦闘」「回復CD」「逃走」を成立させる。
・“経過”は Tick() に集約し、リソース消費の二重更新を防止。
・テスト容易性のため、入力はキーボードとUIの両方から受ける。
// GameLoop.cs
using UnityEngine;
public class GameLoop : MonoBehaviour
{
public enum GameState { Title, PowerUp, Dungeon, Battle, Result, GameOver }
public enum BattleCommand { Attack, Guard, Heal, Escape }
[SerializeField] GameState state = GameState.Title;
[SerializeField] int floor = 1;
[SerializeField] int food = 20;
[SerializeField] int hp = 20, hpMax = 20;
int healCooldown = 0;
BattleCommand? pending; // UIの一回分入力を溜める
// 仮の敵(検証用)
int enemyHp = 10, enemyDef = 2;
void Update()
{
switch (state)
{
case GameState.Title:
if (Input.anyKeyDown) state = GameState.PowerUp;
break;
case GameState.PowerUp:
// メタ強化は後回し。今回は即ダンジョンへ。
state = GameState.Dungeon;
break;
case GameState.Dungeon:
if (Advance()) // 遭遇したら戦闘へ
{
SpawnEnemy();
state = GameState.Battle;
}
break;
case GameState.Battle:
var cmd = ReadCommand();
DoTurn(cmd);
if (hp <= 0 || food <= 0) state = GameState.GameOver;
else if (enemyHp <= 0) state = GameState.Result;
break;
case GameState.Result:
floor++;
state = GameState.Dungeon;
break;
case GameState.GameOver:
if (Input.anyKeyDown) ResetAll();
break;
}
}
// --- UIボタンから "Attack" 等を渡す ---
public void EnqueueCommand(string command)
{
if (System.Enum.TryParse(command, out BattleCommand cmd))
pending = cmd;
}
BattleCommand ReadCommand()
{
if (pending.HasValue) { var c = pending.Value; pending = null; return c; }
if (Input.GetKeyDown(KeyCode.A)) return BattleCommand.Attack;
if (Input.GetKeyDown(KeyCode.G)) return BattleCommand.Guard;
if (Input.GetKeyDown(KeyCode.H)) return BattleCommand.Heal;
if (Input.GetKeyDown(KeyCode.E)) return BattleCommand.Escape;
return BattleCommand.Attack; // 入力なし時は既定で進行
}
bool Advance()
{
if (food > 0) food--; // 前進は食料を消費
return Random.value < 0.4f; // 40%で遭遇(検証用)
}
void SpawnEnemy()
{
enemyHp = 10 + floor; // フロアで微増
enemyDef = 2;
}
void DoTurn(BattleCommand cmd)
{
switch (cmd)
{
case BattleCommand.Attack:
enemyHp -= CalcDamage(5, enemyDef);
Tick(consumesFood: true);
break;
case BattleCommand.Guard:
// 今回は効果薄めでも可。先にループを回す。
Tick(consumesFood: true);
break;
case BattleCommand.Heal:
if (healCooldown == 0)
{
hp = Mathf.Min(hp + 8, hpMax);
healCooldown = 3; // 3ターン再使用不可
// 回復はターン/食料を消費しない
}
break;
case BattleCommand.Escape:
state = GameState.Dungeon;
break;
}
if (healCooldown > 0 && cmd != BattleCommand.Heal)
healCooldown--;
}
int CalcDamage(int atk, int def)
{
var baseDmg = Mathf.Max(1, atk - def);
return baseDmg + Random.Range(0, 2); // 0〜1の軽い揺らぎ
}
void Tick(bool consumesFood)
{
if (consumesFood && food > 0) food--;
// 敵の行動・DoT等を追加するならここに集約
}
void ResetAll()
{
floor = 1; food = 20; hp = hpMax = 20;
healCooldown = 0; enemyHp = 10; enemyDef = 2;
state = GameState.Title;
}
}
ポイント: ・“回復は食料を消費しない/クールダウン有り”は、操作テンポの検証に有効(ボタン連打で破綻しがちな箇所の炙り出し)。
・遭遇率やダメージは「感じ」を掴むために固定/近似でOK。
定数は後でSOやJSONに外出しできます。
スマホ適合の検証:SafeAreaとタップ性だけ先に固める
初回ビルドで最も崩れやすいのが、ノッチ/ナビバー衝突とボタンの押下性です。
以下の2点だけ先に入れると失敗が激減。
SafeArea適用(親RectTransformのアンカーを書き換え) 48dp相当の最小ターゲット(指先9mm)+十分なPadding
// SafeAreaApplier.cs
using UnityEngine;
[RequireComponent(typeof(RectTransform))]
public class SafeAreaApplier : MonoBehaviour
{
void Start()
{
var rt = GetComponent<RectTransform>();
var safe = Screen.safeArea;
var min = safe.position;
var max = safe.position + safe.size;
min.x /= Screen.width; min.y /= Screen.height;
max.x /= Screen.width; max.y /= Screen.height;
rt.anchorMin = min;
rt.anchorMax = max;
}
}
検証方法(再現手順): ・解像度を 1080×1920(縦)に固定 → 端末上でノッチ右/左/上下のパターンを2機種以上で確認。
・ボタンの最小辺を100px前後に設定 → 右利き/左利きで親指タップの“外し”が出ないかをカウント(10回×2手)。
・期待結果:外し率5%未満、UIがノッチ/バーに重ならない。
UnityとRPGツクール、同要件の比較検証(定性・最小表)
同じ「前進→遭遇→コマンド→結果」を両者で作る前提で、初期ハードルと伸び代を比較。
あくまで今回の要件・私の再現に基づく所感です。
観点 Unity ツクール 最初の一歩 C#1ファイル+UI配線で自由。
雛形から空気抵抗が低い 既存の戦闘/DBが強い。
イベント駆動が早い UI自由度/スマホ SafeArea/Canvasでコントロールしやすい 既定UIが安心。
高度カスタムはプラグインに依存 ループの可視化 Update/Stateで一本化しやすい マップ/イベントの抽象化が必要になる 拡張(3D/2D混在) 背景3D×敵2Dなどの混在が自然 2D中心でスムーズ。
3Dは別アプローチ
判断の指針: ・“名作のテンポを踏襲して短編を早く出す”ならツクールの既成レイヤーが便利。
・“スマホの押し心地/3D背景を詰めたい”ならUnityが手早い。
どちらを選んでも、ノンフィールドrpg スマホの体験核心は「反応速度と手順の少なさ」。
ここを検証の主語にすると迷いづらいです。
不具合の再現と修正ログ(最小ケース→原因→対処)
ログ1:回復が無限に撃てる ・再現:Heal連打 → クールダウン不発。
・原因:CD更新が入力直後にだけ行われ、他コマンド経過で減らしていない。
・対処:healCooldown > 0 && cmd != Heal の時にデクリメント(上掲コード)。
・確認:連続Heal不可、3アクション後に再解放。
ログ2:食料の二重消費 ・再現:前進で1消費、Attackでもう1消費 → 期待通り。
ただしGuardでの消費が場当たり的に増減。
・原因:各所でfood--していた。
・対処:Tick(consumesFood: bool) に集約。
・確認:前進時1、戦闘1アクションで1(Attack/Guardのみ)に統一。
ログ3:スマホでボタンがノッチに潜る ・再現:上下セーフエリア端末で、最下ボタンがホームバーに重なる。
・原因:親コンテナのアンカーを固定比率にしていた。
・対処:SafeAreaApplier を親へ適用。
・確認:全端でUIが安全領域内に収まる。
ログ4:テンポが重く感じる ・再現:Attack→結果表示まで体感>300ms。
・原因:Update内で余計な処理(ログ構築/Find)をしていた。
・対処:ログ構築を必要時のみ、Find禁止(SerializeField参照)。
・確認:体感改善。
まとめ:まず“1ループ”を完走させる。次はデータ外出しとスキルへ
今回の検証で見えた作り方:
・「ノンフィールドとは」の芯は、前進とリソース消費、単純な戦闘の往復。
ここを1クラス/4ボタンで回す。
・スマホは SafeArea と 48dp だけ先に。
デザインは後からで間に合う。
・“経過処理の集約(Tick)”がバグと計測を減らす。
・拡張は ScriptableObject 等で“後から”外出しで十分(敵・スキル・成長)。
次の検証計画: ダメージ式/遭遇率/回復量をSO化し、難易度プロファイルを差し替え実験。
Healと同じCD機構をスキルに横展開し、強スキルの再使用間隔でテンポ調整。
メタ進行(例:到達階層に応じた初期食料+1)を最小実装。
“名作”の空気は、派手さより“流速の気持ちよさ”。
ここを継続的に測るとブレません。
必要になったとき、教材や素材はピンポイントで補えば十分でした。
【Unity6対応】Unity 初心者向けノンフィールドRPGの作り方講座【全14回】 【スマホ化対応】が参考になったのでよかったらみて。
— 脚注 —
タップ→反応までの主観閾値は約100〜300ms。
演出を待たせる場合は“自発操作直後”に即時フィードバック(SE/小さな点滅)を返し、重い処理は後段非同期/短フェードに逃がすのが有効。
