0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Unityで「ノンフィールドRPG」を最短で形にする手順メモ

Posted at

「ノンフィールドRPG 作り方」で迷子になって、戦闘ループ設計で足が止まった。

ちょうど『Unity 初心者向けノンフィールドRPGの作り方(スマホ対応)』講座を見つけて道筋が見えたので、実際にわたしの環境で“まず遊べる最小構成”を通した記録を置いておく。

再現手順→ハマり→比較の順で、テンプレじゃなく「こうしたら動いた」視点。

最初に“捨てる”を決める(ノンフィールドとは何か)

フィールド移動や広域マップを思い切って捨て、「前進→遭遇→コマンド戦闘→結果処理」を核心に。

迷走しないために、最初に境界線を紙に書いた。

  • 残す:ターン制コマンド、HP/食料などのリソース、周回強化(メタ進行)
  • 捨てる:自由移動、複雑な装備DB、重量インベントリ
  • 後回し:状態異常・属性・難易度分岐・隊列・スキルツリー

ここが定まると、UIとデータ構造がスッと決まる。

環境準備

プロジェクトは「3D コア」で作成し、2DパッケージとTextMeshProを追加。
Portrait前提で最初から解像度を固定。

  • Build ProfilesでAndroid/iOSにSwitch→Gameビューに「1080×1920 Portrait」を追加
  • TMPの日本語フォントアセットを生成(Atlas 4096、Custom Charactersに日本語表)
  • Assets/{Scenes, Scripts, Data, Images, Fonts, Prefabs, Materials, Audios} を先に切っておく

画像はTexture Type=Sprite(2D and UI)、PPUは統一(わたしは480)。
PPUがバラつくとボタン寸法が端末で崩れる。

まずは“遊べる最小ループ”から:タイトル→強化→ダンジョン→バトル→結果

最初は演出ゼロでOK。
MonoBehaviour 1本で通す。
キーボード(A/G/H/E)でも、UIボタンからでも動くようにした。

// GameLoop.cs
// 最小ノンフィールドRPGのゲームループ(タイトル→強化→ダンジョン→バトル→結果→…)
// ・A:Attack / G:Guard / H:Heal / E:Escape
// ・UIボタンからは BattleUI などで EnqueueCommand("Attack") のように呼ぶ
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;
[SerializeField] int hpMax = 20;

// ------ バトル補助 ------
int healCooldown = 0;                   // 0で使用可
BattleCommand? pendingCommand = null;   // UIからの入力キュー

// デモ用の敵(ScriptableObject導入前の仮)
int enemyHp = 10;
int enemyDef = 2;

void Update()
{
    switch (state)
    {
        case GameState.Title:
            if (Input.anyKeyDown) state = GameState.PowerUp;
            break;

        case GameState.PowerUp:
            // 本来はメタポイントで初期HP/食料を増やすなど
            state = GameState.Dungeon;
            break;

        case GameState.Dungeon:
            if (DoAdvance())
            {
                SpawnEnemy();
                state = GameState.Battle;
            }
            break;

        case GameState.Battle:
            var cmd = GetCommand();
            DoBattleTurn(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) ResetGame();
            break;
    }
}

// ---- UIから呼ぶ入口(Button OnClickで "Attack" など渡す)----
public void EnqueueCommand(string command)
{
    if (System.Enum.TryParse(command, out BattleCommand cmd))
    {
        pendingCommand = cmd;
    }
}

BattleCommand GetCommand()
{
    if (pendingCommand.HasValue)
    {
        var cmd = pendingCommand.Value;
        pendingCommand = null;
        return cmd;
    }
    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 DoAdvance()
{
    if (food > 0) food--;
    return Random.value < 0.4f; // 40%で遭遇
}

void SpawnEnemy()
{
    enemyHp = 10 + floor; // ゆるく強くする
    enemyDef = 2;
}

void DoBattleTurn(BattleCommand cmd)
{
    switch (cmd)
    {
        case BattleCommand.Attack:
            enemyHp -= CalcDamage(5, enemyDef);
            TurnProgress(true);
            break;
        case BattleCommand.Guard:
            // 実ダメ処理は未実装でもよい(最小形)
            TurnProgress(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 or 1
}

void TurnProgress(bool consumesFood)
{
    if (consumesFood && food > 0) food--;
    // ここに敵行動/毒/バフ経過などを集約
}

void ResetGame()
{
    floor = 1;
    food = 20;
    hp = hpMax = 20;
    healCooldown = 0;
    enemyHp = 10;
    enemyDef = 2;
    state = GameState.Title;
}

}

Safe Area(ノッチ回避):親RectTransformに一括適用

縦画面はノッチ差がつらい。
まずはSafe Areaを親に当ててUIズレを防ぐ。

// SafeAreaApplier.cs
using UnityEngine;

[RequireComponent(typeof(RectTransform))]
public class SafeAreaApplier : MonoBehaviour
{
void Start()
{
var rt = GetComponent<RectTransform>();
var area = Screen.safeArea;

    Vector2 min = area.position;
    Vector2 max = area.position + area.size;

    min.x /= Screen.width;
    min.y /= Screen.height;
    max.x /= Screen.width;
    max.y /= Screen.height;

    rt.anchorMin = min;
    rt.anchorMax = max;
}

}

データ駆動の導入

次の段階で、敵やスキルをScriptableObject化。
最小版が動いたあとで入れ替えると破綻しにくい。

// EnemyData.cs
using UnityEngine;

[CreateAssetMenu(menuName = "NonField/Enemy")]
public class EnemyData : ScriptableObject
{
public string enemyName;
public int hpMax;
public int atk;
public int def;
public Sprite artwork;
public SkillData[] skills;
}
// SkillData.cs
using UnityEngine;

[CreateAssetMenu(menuName = "NonField/Skill")]
public class SkillData : ScriptableObject
{
public string skillName;
public int power; // 与ダメ係数
public int cooldown; // 再使用までのターン
public int guardCut; // ガード時の軽減貫通など
}

SOにすると、再生中に値を触って手触りを詰められる。
配列の複製時に参照が連動することがあるので、想定外に動いたら新規SOを作り直すのが早かった。

UIの実務メモ

  • Canvas Scaler:Scale With Screen Size、Reference=1080×1920、Match=0.5
  • 重要UIはAnchorsで四隅固定を避け、LayoutGroupで伸縮を任せる
  • ボタンは48dp相当(物理約9mm)を目安に。TMPはアウトライン細め

RPGツクール系とUnityの比較)

  • ツクール:短編を高速で出す、戦闘DB編集が強い、最初の1本が圧倒的に速い
  • Unity:スマホ最適UI/3D背景×2D立ち絵/アセット連携/状態機械が素直
  • 結論:短距離走はツクール、長距離&スマホUI自由度はUnity、で住み分けるのがラク

ハマり→対策ログ

  • TMPが豆腐:日本語フォントアセット未生成。Atlas4096/Custom Charactersに日本語表
  • UIが端末でズレる:PPU統一+LayoutGroup+SafeAreaを親に適用
  • 回復クールダウンが働かない:減算タイミングを「ターン進行」に集約
  • 逃走後の食料が不正:食料減算をTurnProgressに一元化して分岐を減らす
  • BGM切替でプチノイズ:Stop→Playでなく、Volumeフェード or Mixer Snapshot

まとめ

ノンフィールドRPGは“捨てる決断”で難易度が一気に下がる。

今回は、状態遷移→最小戦闘→SafeArea→データ駆動の順に固めて、まず“遊べる原型”を1日で通した。
そこから演出やスキルツリーを足していけば良い。

ジャンル選びで迷っているなら、最小ループを先に通してから判断するのが結局いちばん早かった。
必要になったら、ゲーム制作をまとめて学べる講座群もあるので、詰まった箇所だけピンポイントで参照するのがおすすめ。
ちなみに今回参考にしたのはこちらの
【Unity6対応】Unity 初心者向けノンフィールドRPGの作り方講座【全14回】 【スマホ化対応
本1冊分の料金で完走できるのがうれしい。
ゲームを完成させたいなどの人にもおすすめ。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?