対応バージョン:Unity 6.3 LTS(6000.3系)/WebGL出力 この記事はシリーズ全11回の第7回です。第6回では19本の会話データをDialogueDataに入力し、台本の矛盾を修正しました。今回はゲームの見た目を作る工程——立ち絵・背景の取り込みとタイトル画面の実装に進みます。 【PR】本記事にはアフィリエイトリンクを含みます。
目次
- はじめに
- 1. 会話データ・話題カード・キャラクターデータをCSVから一括インポートする仕組みを作る
- 2. 画像素材のフォルダ構成とインポート
- 3. ゲーム画面への配置
- 4. 会話に合わせて立ち絵・背景を切り替える
- 5. タイトル画面シーンを作る
- 6. 動作確認チェックリスト
- まとめ
- あわせて読みたいおすすめ書籍【PR】
- この記事を作成するにあたって参考にしたサイト
はじめに
前回までで、DialogueDataを19本、TopicDataを15個、CharacterDataを4つ、すべてInspectorへ手入力しました。この量を毎回手入力するのは非効率で、入力ミスも起きやすくなります。
そこで今回はまず、CSVファイルからこれらのScriptableObjectを一括生成するエディタ拡張を作り、入力の手間を減らします。そのうえで、AIで生成したキャラクター立ち絵と背景画像をUnityに取り込み、会話シーンに表示する実装を行います。
この記事でやることは以下の5つです。
- CSVから会話データ・話題カード・キャラクターデータを一括インポートする仕組みを作る
- 立ち絵・背景・UI素材をUnityにインポートする
- ゲーム画面に立ち絵と背景を配置する
- 会話の進行に合わせて立ち絵・背景を切り替える仕組みを追加する
- タイトル画面シーンを作る
1. 会話データ・話題カード・キャラクターデータをCSVから一括インポートする仕組みを作る
なぜCSVインポートが必要か
DialogueData・TopicData・CharacterDataはすべてScriptableObjectで、これまではUnityのInspectorに直接入力していました。しかし数が増えると(今回は合計38個)、Inspectorでの手入力は時間がかかるうえ、コピー&ペーストのミスにも気づきにくくなります。
CSVファイルであれば表計算ソフトでまとめて編集でき、セリフの誤字や設定漏れも一覧で見直せます。今回は「CSVを読み込んでScriptableObjectを自動生成するエディタ拡張」を2本作ります。エディタ拡張とは、Unityエディタ自体に独自のツールやウィンドウを追加する仕組みのことです。
CSV用のフォルダを準備する
🔧 詳しい手順
- ProjectウィンドウのAssetsフォルダで右クリック→「Create」→「Folder」で「Data/CSV」フォルダを作成する
- Data/CSVフォルダ内に「Topics」フォルダと「Characters」フォルダを作成する
Assets/Data/CSV/
├── (DialogueData用のCSVファイルを直接置く)
├── Topics/ // TopicData用のCSVファイル
└── Characters/ // CharacterData用のCSVファイル
DialogueDataをCSVから読み込むエディタ拡張を作る
Scripts/フォルダ以下のEditorフォルダ(Editorという名前のフォルダに置いたスクリプトはビルドに含まれず、エディタ上でのみ動作します)に「DialogueDataImporter」という名前でMonoBehaviour Scriptを作成し、以下の内容に書き換えます。
using UnityEngine;
using UnityEditor;
using System.IO;
using System.Collections.Generic;
public class DialogueDataImporter : EditorWindow
{
private string csvPath = "Assets/Data/CSV/";
private string outputPath = "Assets/Data/Dialogues/";
[MenuItem("MysteryADV/DialogueData CSVインポート")]
public static void ShowWindow()
{
GetWindow<DialogueDataImporter>("DialogueData インポート");
}
void OnGUI()
{
GUILayout.Label("DialogueData CSVインポーター", EditorStyles.boldLabel);
GUILayout.Space(10);
GUILayout.Label("CSVフォルダ(Assets以下)");
csvPath = GUILayout.TextField(csvPath);
GUILayout.Label("出力フォルダ(Assets以下)");
outputPath = GUILayout.TextField(outputPath);
GUILayout.Space(10);
if (GUILayout.Button("CSVフォルダ内の全CSVをインポート"))
{
ImportAll();
}
}
void ImportAll()
{
if (!Directory.Exists(csvPath))
{
EditorUtility.DisplayDialog("エラー", "CSVフォルダが見つかりません:" + csvPath, "OK");
return;
}
if (!Directory.Exists(outputPath))
Directory.CreateDirectory(outputPath);
string[] csvFiles = Directory.GetFiles(csvPath, "*.csv");
if (csvFiles.Length == 0)
{
EditorUtility.DisplayDialog("エラー", "CSVファイルが見つかりません", "OK");
return;
}
int successCount = 0;
foreach (string filePath in csvFiles)
{
if (ImportCSV(filePath))
successCount++;
}
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
EditorUtility.DisplayDialog("完了", successCount + "件のDialogueDataを生成しました", "OK");
}
bool ImportCSV(string filePath)
{
string[] lines = File.ReadAllLines(filePath, System.Text.Encoding.UTF8);
if (lines.Length < 2)
{
Debug.LogWarning("空のCSVをスキップ: " + filePath);
return false;
}
// ヘッダー行をスキップ、2行目以降を読む
// フォーマット: sceneTitle,setFlagOnComplete,speakerName,dialogueText,isMonologue
string sceneTitle = "";
string setFlag = "";
List<DialogueLine> lineList = new List<DialogueLine>();
for (int i = 1; i < lines.Length; i++)
{
string line = lines[i].Trim();
if (string.IsNullOrEmpty(line)) continue;
string[] cols = SplitCSVLine(line);
if (cols.Length < 5)
{
Debug.LogWarning("列数が不足している行をスキップ(" + (i + 1) + "行目): " + line);
continue;
}
// 最初のデータ行からsceneTitle・setFlagを取得
if (sceneTitle == "")
{
sceneTitle = cols[0].Trim();
setFlag = cols[1].Trim();
}
DialogueLine dl = new DialogueLine();
dl.speakerName = cols[2].Trim();
dl.dialogueText = cols[3].Trim();
dl.isMonologue = cols[4].Trim().ToLower() == "true";
lineList.Add(dl);
}
if (lineList.Count == 0 || sceneTitle == "")
{
Debug.LogWarning("有効なデータがありません: " + filePath);
return false;
}
// ScriptableObject生成
DialogueData data = ScriptableObject.CreateInstance<DialogueData>();
data.name = sceneTitle;
data.sceneTitle = sceneTitle;
data.setFlagOnComplete = setFlag;
data.lines = lineList.ToArray();
string assetPath = outputPath + sceneTitle + ".asset";
if (AssetDatabase.LoadAssetAtPath<DialogueData>(assetPath) != null)
AssetDatabase.DeleteAsset(assetPath);
AssetDatabase.CreateAsset(data, assetPath);
Debug.Log("新規作成: " + assetPath);
return true;
}
// カンマ区切りCSVのパース(ダブルクォート対応)
string[] SplitCSVLine(string line)
{
List<string> result = new List<string>();
bool inQuotes = false;
System.Text.StringBuilder current = new System.Text.StringBuilder();
for (int i = 0; i < line.Length; i++)
{
char c = line[i];
if (c == '"')
{
if (inQuotes && i + 1 < line.Length && line[i + 1] == '"')
{
// エスケープされたダブルクォート("")
current.Append('"');
i++;
}
else
{
inQuotes = !inQuotes;
}
}
else if (c == ',' && !inQuotes)
{
result.Add(current.ToString());
current.Clear();
}
else
{
current.Append(c);
}
}
result.Add(current.ToString());
return result.ToArray();
}
}
SplitCSVLine()はカンマ区切りのCSVを1行ずつ分割する処理です。セリフの中にカンマが含まれる場合(「はい、そうです」など)は、CSV側でそのセル全体をダブルクォートで囲むことで正しく分割されます。
CSVフォーマット:sceneTitle,setFlagOnComplete,speakerName,dialogueText,isMonologue
以下は沙耶との「あの夜のこと」(Ch2_Saya_Night)の例です。
sceneTitle,setFlagOnComplete,speakerName,dialogueText,isMonologue
Ch2_Saya_Night,talked_saya_night,凛,昨夜、月凪に伺いました。少しお話を聞かせてもらえますか。,false
Ch2_Saya_Night,,沙耶,……何のお話かしら。,false
Ch2_Saya_Night,,凛,(本心を見せない目。何かを隠している。),true
sceneTitleとsetFlagOnCompleteはコード上、1行目のデータ行しか読まれません。2行目以降は空欄のままで構いません。
🔧 詳しい手順(使い方)
- Assets/Data/CSV/フォルダに、DialogueDataの数だけCSVファイルを用意する(ファイル名は何でもよい。アセット名はCSV内のsceneTitle列から決まる)
- Unityメニューの「MysteryADV」→「DialogueData CSVインポート」を開く
- CSVフォルダ・出力フォルダのパスを確認し、「CSVフォルダ内の全CSVをインポート」ボタンを押す
- 「〇件のDialogueDataを生成しました」と表示されれば成功

⚠️ 注意:証拠の設定はCSVに含まれない このCSVフォーマットには
grantEvidenceOnComplete(証拠付与)の列がありません。動画などの証拠を入手する会話(Ch3_Matsumoto_Videoなど)は、CSVインポート後にInspectorでGrant Evidence On Completeを個別に設定する必要があります。
TopicData・CharacterDataをCSVから読み込むエディタ拡張を作る
同じくEditorフォルダに「TopicCharacterImporter2」という名前でMonoBehaviour Scriptを作成し、以下の内容に書き換えます。TopicDataとCharacterDataは参照関係にあるため、1つのウィンドウに2つのインポート機能をまとめています。
using UnityEngine;
using UnityEditor;
using System.IO;
using System.Collections.Generic;
public class TopicCharacterImporter2 : EditorWindow
{
private string topicCSVPath = "Assets/Data/CSV/Topics/";
private string topicOutputPath = "Assets/Resources/Topics/";
private string charCSVPath = "Assets/Data/CSV/Characters/";
private string charOutputPath = "Assets/Data/Characters/";
[MenuItem("MysteryADV/TopicData・CharacterData CSVインポート2")]
public static void ShowWindow()
{
GetWindow<TopicCharacterImporter2>("Topic・Character インポート2");
}
void OnGUI()
{
GUILayout.Label("TopicData・CharacterData CSVインポーター", EditorStyles.boldLabel);
GUILayout.Space(10);
GUILayout.Label("--- TopicData ---", EditorStyles.boldLabel);
GUILayout.Label("CSVフォルダ");
topicCSVPath = GUILayout.TextField(topicCSVPath);
GUILayout.Label("出力フォルダ");
topicOutputPath = GUILayout.TextField(topicOutputPath);
GUILayout.Space(10);
GUILayout.Label("--- CharacterData ---", EditorStyles.boldLabel);
GUILayout.Label("CSVフォルダ");
charCSVPath = GUILayout.TextField(charCSVPath);
GUILayout.Label("出力フォルダ");
charOutputPath = GUILayout.TextField(charOutputPath);
GUILayout.Space(10);
if (GUILayout.Button("TopicDataをインポート"))
ImportAllTopics();
GUILayout.Space(5);
if (GUILayout.Button("CharacterDataをインポート"))
ImportAllCharacters();
}
// =====================
// TopicData インポート
// =====================
void ImportAllTopics()
{
if (!Directory.Exists(topicCSVPath))
{
EditorUtility.DisplayDialog("エラー", "CSVフォルダが見つかりません:" + topicCSVPath, "OK");
return;
}
if (!Directory.Exists(topicOutputPath))
Directory.CreateDirectory(topicOutputPath);
string[] csvFiles = Directory.GetFiles(topicCSVPath, "*.csv");
if (csvFiles.Length == 0)
{
EditorUtility.DisplayDialog("エラー", "CSVファイルが見つかりません", "OK");
return;
}
int count = 0;
foreach (string filePath in csvFiles)
{
if (ImportTopicCSV(filePath)) count++;
}
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
EditorUtility.DisplayDialog("完了", count + "件のTopicDataを生成しました", "OK");
}
bool ImportTopicCSV(string filePath)
{
string[] lines = File.ReadAllLines(filePath, System.Text.Encoding.UTF8);
if (lines.Length < 2) return false;
string[] cols = SplitCSVLine(lines[1].Trim());
if (cols.Length < 6) return false;
string topicID = cols[0].Trim();
string topicTitle = cols[1].Trim();
string dialoguePath = cols[2].Trim();
bool isUnlocked = cols[3].Trim().ToLower() == "true";
string unlockFlagID = cols[4].Trim();
string setFlagOnComplete = cols[5].Trim();
DialogueData dialogue = null;
if (!string.IsNullOrEmpty(dialoguePath))
{
string[] guids = AssetDatabase.FindAssets(dialoguePath + " t:DialogueData");
if (guids.Length > 0)
dialogue = AssetDatabase.LoadAssetAtPath<DialogueData>(AssetDatabase.GUIDToAssetPath(guids[0]));
if (dialogue == null)
Debug.LogWarning("DialogueDataが見つかりません: " + dialoguePath);
}
string fileName = Path.GetFileNameWithoutExtension(filePath);
string assetPath = topicOutputPath + fileName + ".asset";
// 既存があれば削除
if (AssetDatabase.LoadAssetAtPath<TopicData>(assetPath) != null)
AssetDatabase.DeleteAsset(assetPath);
TopicData data = ScriptableObject.CreateInstance<TopicData>();
data.name = fileName;
data.topicID = topicID;
data.topicTitle = topicTitle;
data.dialogue = dialogue;
data.unlockFlagID = unlockFlagID;
data.setFlagOnComplete = setFlagOnComplete;
AssetDatabase.CreateAsset(data, assetPath);
Debug.Log("作成: " + assetPath);
return true;
}
// =====================
// CharacterData インポート
// =====================
void ImportAllCharacters()
{
if (!Directory.Exists(charCSVPath))
{
EditorUtility.DisplayDialog("エラー", "CSVフォルダが見つかりません:" + charCSVPath, "OK");
return;
}
if (!Directory.Exists(charOutputPath))
Directory.CreateDirectory(charOutputPath);
string[] csvFiles = Directory.GetFiles(charCSVPath, "*.csv");
if (csvFiles.Length == 0)
{
EditorUtility.DisplayDialog("エラー", "CSVファイルが見つかりません", "OK");
return;
}
int count = 0;
foreach (string filePath in csvFiles)
{
if (ImportCharacterCSV(filePath)) count++;
}
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
EditorUtility.DisplayDialog("完了", count + "件のCharacterDataを生成しました", "OK");
}
bool ImportCharacterCSV(string filePath)
{
string[] lines = File.ReadAllLines(filePath, System.Text.Encoding.UTF8);
if (lines.Length < 2) return false;
string characterID = "";
string characterName = "";
List<TopicData> topics = new List<TopicData>();
for (int i = 1; i < lines.Length; i++)
{
string line = lines[i].Trim();
if (string.IsNullOrEmpty(line)) continue;
string[] cols = SplitCSVLine(line);
if (cols.Length < 3) continue;
if (characterID == "")
{
characterID = cols[0].Trim();
characterName = cols[1].Trim();
}
string topicName = cols[2].Trim();
string[] guids = AssetDatabase.FindAssets(topicName + " t:TopicData");
if (guids.Length > 0)
{
TopicData topic = AssetDatabase.LoadAssetAtPath<TopicData>(AssetDatabase.GUIDToAssetPath(guids[0]));
if (topic != null) topics.Add(topic);
else Debug.LogWarning("TopicDataが見つかりません: " + topicName);
}
else
{
Debug.LogWarning("TopicDataが見つかりません: " + topicName);
}
}
if (characterID == "") return false;
string fileName = Path.GetFileNameWithoutExtension(filePath);
string assetPath = charOutputPath + fileName + ".asset";
// 既存があれば削除
if (AssetDatabase.LoadAssetAtPath<CharacterData>(assetPath) != null)
AssetDatabase.DeleteAsset(assetPath);
CharacterData data = ScriptableObject.CreateInstance<CharacterData>();
data.name = fileName;
data.characterID = characterID;
data.characterName = characterName;
data.topics = topics.ToArray();
AssetDatabase.CreateAsset(data, assetPath);
Debug.Log("作成: " + assetPath);
return true;
}
string[] SplitCSVLine(string line)
{
List<string> result = new List<string>();
bool inQuotes = false;
System.Text.StringBuilder current = new System.Text.StringBuilder();
for (int i = 0; i < line.Length; i++)
{
char c = line[i];
if (c == '"')
{
if (inQuotes && i + 1 < line.Length && line[i + 1] == '"')
{ current.Append('"'); i++; }
else inQuotes = !inQuotes;
}
else if (c == ',' && !inQuotes)
{ result.Add(current.ToString()); current.Clear(); }
else
current.Append(c);
}
result.Add(current.ToString());
return result.ToArray();
}
}
TopicDataのCSVフォーマット:topicID,topicTitle,dialoguePath,isUnlocked,unlockFlagID,setFlagOnComplete(1ファイルにつきデータ行は1行だけ)
topicID,topicTitle,dialoguePath,isUnlocked,unlockFlagID,setFlagOnComplete
saya_night,あの夜のこと,Ch2_Saya_Night,true,,talked_saya_night
このCSVファイルはTopic_Saya_Night.csvのように保存します。生成されるTopicDataのアセット名は、CSVの中身ではなくCSVファイル名から決まります(コード内のPath.GetFileNameWithoutExtension(filePath)の部分)。
CharacterDataのCSVフォーマット:characterID,characterName,topicName(キャラクターが持つ話題の数だけ行を繰り返す)
characterID,characterName,topicName
saya,桐島沙耶,Topic_Saya_Night
saya,桐島沙耶,Topic_Saya_Tanaka
saya,桐島沙耶,Topic_Saya_Leave
saya,桐島沙耶,Topic_Saya_Past
topicName列(3列目)はAssetDatabase.FindAssets(topicName + " t:TopicData")でTopicDataアセットを名前検索するために使われます。先ほど作ったTopicDataのCSVファイル名と、この列の値を完全に一致させてください。一致しないと「TopicDataが見つかりません」という警告が出て、その話題はキャラクターに紐づきません。
🔧 詳しい手順(使い方・実行順序に注意)
- Assets/Data/CSV/Topics/フォルダに、TopicDataの数だけCSVファイルを用意する
- Assets/Data/CSV/Characters/フォルダに、CharacterDataの数だけCSVファイルを用意する
- Unityメニューの「MysteryADV」→「TopicData・CharacterData CSVインポート2」を開く
- 先に「TopicDataをインポート」ボタンを押す
- 完了したら「CharacterDataをインポート」ボタンを押す
④⑤の順番は必ず守ってください。 CharacterDataのインポートはTopicDataを名前で検索する仕組みのため、TopicDataが先に存在していないと、すべての話題が空のまま登録されてしまいます。同様に、TopicDataのインポートもDialogueDataを名前検索するため、DialogueDataを先にインポートしておく必要があります。
実行順序:DialogueDataインポート → TopicDataインポート → CharacterDataインポート
⚠️ ハマったポイント
isUnlocked列がTopicDataに反映されない。ImportTopicCSV()内ではbool isUnlocked = cols[3]...という変数にCSVの値を読み込んでいますが、生成するTopicDataオブジェクトに代入する行がありません。そのためCSVでfalseを指定しても、TopicDataのIs UnlockedはScriptableObjectの初期値であるtrueのままになります。最初から話題が全部見えてしまい、しばらく原因に気づけませんでした。反映させるには、data.unlockFlagID = unlockFlagID;の下にdata.isUnlocked = isUnlocked;を1行追加するか、インポート後にInspectorで手動チェックを外す必要があります- 再インポートすると既存アセットは削除され、上書きされる。 インポーター側で
AssetDatabase.DeleteAsset()のあとにAssetDatabase.CreateAsset()を実行しているため、Inspectorで直接手を加えた内容があっても再インポートで消えます。修正はCSV側に反映してから再インポートしてください- CharacterDataを先にインポートすると全滅する。 実行順序を間違えてCharacterDataを先にインポートすると、
TopicDataが見つかりませんという警告がコンソールに大量に出て、すべてのキャラクターがtopicsが空の状態で生成されます。この場合はTopicDataを先にインポートしてから、CharacterDataだけ再インポートすれば直ります
2. 画像素材のフォルダ構成とインポート
フォルダを作る
まずUnityのProjectウィンドウで素材用のフォルダを作成します。
Assets/
├── Sprites/
│ ├── Characters/ ← キャラクター立ち絵
│ ├── Backgrounds/ ← 背景画像
│ └── UI/ ← ボタンなどのUI素材
インポート設定が重要な理由
画像をUnityに取り込むだけでは正しく表示されません。Texture Typeなどのインポート設定を用途に合わせて変更する必要があります。
設定を間違えると、立ち絵の透過が効かなかったり、背景がぼやけたりします。
立ち絵のインポート設定
キャラクター立ち絵(背景透過PNG)を選択してInspectorで以下を設定し、Applyを押します。
| 項目 | 値 | 理由 |
|---|---|---|
| Texture Type | Sprite (2D and UI) | UIとして使用するため |
| Alpha Is Transparency | ✅ ON | 透過PNGを正しく表示するため |
| Compression | None | 品質劣化を防ぐため |
| Max Size | 2048 | 高解像度立ち絵に対応するため |
特にAlpha Is Transparencyは忘れやすい設定です。これをONにしないと、透過部分が黒や白で塗りつぶされてしまいます。

背景画像のインポート設定
背景画像も同様にSprite (2D and UI)に設定しますが、1点だけ異なります。
| 項目 | 値 |
|---|---|
| Texture Type | Sprite (2D and UI) |
| Generate Mip Maps | ☐ OFF |
Generate Mip MapsはOFFにします。背景は常に一定のサイズで表示されるため、距離に応じて解像度を変えるミップマップは不要です。ONのままだとメモリを無駄に使います。
3. ゲーム画面への配置
Canvasの設定——画面サイズ対応の鍵
Canvas(UI全体の親)の設定は、ゲームの見た目に大きく影響します。
HierarchyのCanvasを選択してInspectorで以下を設定します。
| 項目 | 値 |
|---|---|
| Render Mode | Screen Space – Overlay |
| UI Scale Mode | Scale With Screen Size |
| Reference Resolution | X: 1920 / Y: 1080 |
| Screen Match Mode | Match Width Or Height |
| Match | 0.5 |
Scale With Screen Sizeが重要です。 これを設定することで、ブラウザやウィンドウのサイズが変わっても自動的にUIがスケールされます。WebGL出力では特に重要な設定です。

Hierarchyの並び順——重なりの仕組み
CanvasではHierarchyの下にあるオブジェクトが手前に表示されます。以下の順番になるよう配置してください。
Canvas
├── BackgroundImage ← 一番奥(背景)
├── CharacterImage ← 立ち絵
├── DialoguePanel ← テキストボックス
├── TopicPanel ← 話題カード
├── NotificationPanel ← 通知
└── EndingPanel ← エンディング

背景が一番上(奥)、テキストボックスや話題カードが一番下(手前)になっていれば正解です。
CharacterImageの配置
立ち絵を表示するImageオブジェクトを追加します。Canvasを右クリック → UI → Image → 名前をCharacterImageに変更します。
| 項目 | 値 |
|---|---|
| Anchor | Bottom Center |
| Pivot X / Y | 0.5 / 0 |
| Preserve Aspect | ✅ ON |
| Raycast Target | ☐ OFF |
Preserve AspectをONにすることで、立ち絵の縦横比が崩れずに表示されます。Raycast TargetはOFFにしてください。ONのままだと、後でキャラクタークリックを実装したときに背景のRaycastが立ち絵に遮られてしまいます。

4. 会話に合わせて立ち絵・背景を切り替える
セリフごとに画像を差し替える仕組み
ADVゲームでは、セリフが進むたびに立ち絵の表情や背景が変わります。この仕組みを実装するために、DialogueLine.csに立ち絵と背景のフィールドを追加します。
public Sprite characterSprite; // このセリフで表示する立ち絵(nullなら前の状態を維持)
public Sprite backgroundSprite; // このセリフで切り替える背景(nullなら変更なし)
nullのままにしておけば変化しません。切り替えたいセリフだけに設定すればよいので、全セリフに設定する必要はありません。
DialogueManagerで切り替える
ShowLine()に立ち絵と背景の切り替え処理を追加します。考え方は以下の通りです。
セリフを表示するたびにShowLine()が呼ばれる
↓
backgroundSpriteが設定されていれば背景を切り替え
↓
characterSpriteが設定されていれば立ち絵を切り替えて表示
↓
characterSpriteがnullなら立ち絵を非表示(または前の状態を維持)
DialogueDataへの設定
各DialogueDataのInspectorを開き、背景が切り替わる最初のセリフ行のBackground Spriteに対応する背景画像をドラッグ&ドロップします。
背景の使い分けは以下を参考にしてください。
| 会話データ | 背景 | 場所 |
|---|---|---|
| Ch1_Opening | bg_ferry_deck | フェリー甲板 |
| Ch1_Lounge | bg_lounge | ラウンジ「月凪」 |
| Ch1_Morning | bg_inn_corridor | 民宿・廊下 |
| Ch2_Matsumoto_* | bg_inn_corridor | 民宿・廊下 |
| Ch2_Saya_* | bg_lounge | ラウンジ「月凪」 |
| Ch2_Tanaka_* | bg_police_station | 駐在所 |
| Ch2_Isobe_* | bg_harbor_edge | 港の端 |
5. タイトル画面シーンを作る
なぜタイトル画面を別シーンにするか
ゲーム起動時の最初の画面をタイトル画面にするには、Unityのシーン管理を使います。Build ProfilesのScene List(シーン一覧)の0番目のシーンが起動時に表示されるため、TitleSceneを0番に設定します。
シーンの作成
File → New Scene → Basic (Built-in) → Createでシーンを新規作成し、TitleSceneという名前でAssets/Scenes/に保存します。
タイトル画面の構成
タイトル画面は最低限以下の要素があればOKです。
- 背景・ロゴを含むタイトル画像(全画面Image)
- スタートボタン(透明なButtonを重ねる)
スタートボタンはImageコンポーネントのAlphaを0にして透明にし、タイトル画像上のボタン部分に重ねます。テキストや枠線はタイトル画像に描き込んでおく方がデザインの自由度が高くなります。
シーン遷移スクリプト
スタートボタンをクリックしたときにゲームシーンへ遷移するスクリプトを作ります。
using UnityEngine.SceneManagement;
public class TitleSceneController : MonoBehaviour
{
public void OnStartButtonClicked()
{
SceneManager.LoadScene("SampleScene");
}
}
このスクリプトをCanvasにアタッチし、スタートボタンのOnClick()にOnStartButtonClickedを登録します。
Build ProfilesへのTitleScene追加
File → Build Profilesを開いて、TitleSceneとSampleSceneが両方登録されているか確認します。TitleSceneを一番上(インデックス0)に配置してください。

6. 動作確認チェックリスト
- [ ] CSVインポーターでDialogueData・TopicData・CharacterDataが正しく生成される
- [ ] タイトル画面が表示される
- [ ] スタートボタンをクリックするとSampleSceneに遷移する
- [ ] 立ち絵が透過付きで正しく表示される
- [ ] 背景が正しく表示される
- [ ] NEXTボタンを押すと背景・立ち絵が切り替わる
- [ ] Consoleにエラーが出ていない
まとめ
今回はCSV一括インポートの仕組みを作り、Inspectorへの手入力から解放されました。あわせてゲームの見た目も大きく改善されました。
CSVインポーターは「読み込む→ScriptableObjectを作る→既存を上書きする」という単純な仕組みですが、TopicData→CharacterDataのように他のアセットを名前で参照するインポート順序には注意が必要です。
画像のインポート設定は地味な作業ですが、Alpha Is TransparencyやRaycast Targetなど、設定を間違えると後で思わぬトラブルの原因になります。最初にしっかり設定しておきましょう。
次回は、オープニング会話から話題カードが表示されるまでの流れと、フラグ連動で話題が自動解放される仕組みの実装を解説します。
あわせて読みたいおすすめ書籍【PR】
今回実装した画像インポート・Canvas設定・立ち絵の切り替えについてさらに学びたい方には、以下の書籍もおすすめです。本セクションにはアフィリエイトリンクを含みます。
- 『Unityの教科書 Unity 6完全対応版』(北村愛実 著/SBクリエイティブ) ── Sprite設定・Canvas Scalerなど、UI周りの基礎を体系的に学べるUnity 6対応の定番入門書
- 『楽しく学ぶ Unity「2Dゲーム」作りのきほん』(森巧尚 著/マイナビ出版) ── 2D特化の入門書。スプライトの扱い方やUIレイアウトを丁寧に解説
- 『作って学ぶゲームプログラミング Unity まるっと入門 Unity6完全対応』(トライタム 著/吉谷幹人 監修) ── 画像素材の管理やシーン構成など、今回の実装に直結する内容を含む
- 『ゲームシナリオの書き方 第2版』(佐々木智広 著/SBクリエイティブ) ── 立ち絵や背景の演出をシナリオ側からどう活かすかの考え方を学べる一冊
- 『作って学べる Unity本格入門 Unity6対応版』(賀好昭仁 著/技術評論社) ── 今回のシリーズを読み終えてさらにステップアップしたい人向けの中級者向け書籍
※このセクションのリンクはアフィリエイトリンクであり、商品が購入された場合、サイト運営者に紹介料が支払われることがあります【PR】。
この記事を作成するにあたって参考にしたサイト
- Unity – Scripting API: AssetDatabase.CreateAsset(Unity公式ドキュメント)
- Unity – Manual: Programming with the Asset Database(Unity公式ドキュメント)
- Unity – Manual: Sprite (2D and UI) Import Settings reference(Unity公式ドキュメント)
- Canvas Scaler | Unity UI(Unity公式ドキュメント)
- 前回の実践記事「【ADV編⑥】19本の会話データを流し込む——DialogueDataの実践的な作り方」(本サイト内)


コメント