Unityテキストアドベンチャー開発:技術資料
1. プロジェクト概要
このプロジェクトは、Unityでのテキストアドベンチャーゲームの実装を目的としています。以下の要素を含み、プレイヤーの選択に応じてストーリーが分岐する構造です:
- 背景画像:シーンに応じた背景画像を表示
- キャラクター情報:キャラクターの名前、表情、感情を表示
- サウンド情報:BGM、ボイス、効果音(SE)を再生
- アセットの動的ロード:Addressableを利用し、アセットを効率的に管理
2. データ構造とExcelでの管理
Excel(またはCSVファイル)を使って、各シーンの情報を管理します。以下のような構成でデータを管理します。
Excelの構造例
id | background | speaker | character_name | character_emotion | text | choice1_text | choice1_nextId | choice2_text | choice2_nextId | bgm | voice | sound_effect |
---|---|---|---|---|---|---|---|---|---|---|---|---|
1 | forest | キャラクター1 | リサ | happy | 「こんにちは!森へようこそ!」 | 続ける | 2 | 帰る | 3 | forest_bgm | lisa_happy | se_birds |
2 | forest | キャラクター2 | ジョン | neutral | 「ここで何をしているんだ?」 | 話をする | 4 | 無視する | 3 | forest_bgm | john_neutral | se_step |
3 | town | – | – | – | あなたは町に戻った。 | – | – | – | – | town_bgm | – | – |
- id:各シーンのID
- background:背景画像の名前
- speaker:話すキャラクターの識別子
- character_name:キャラクターの名前
- character_emotion:キャラクターの感情(表情)
- text:会話テキスト
- choiceX_text、choiceX_nextId:選択肢と次の会話ノードID
- bgm:シーンで流すBGMの名前
- voice:キャラクターのボイスの名前
- sound_effect:効果音の名前
3. Addressableの設定
- Addressablesのインストール:
- Unityの「Window」→「Package Manager」から「Addressables」をインストール。
- アセットの登録:
- 背景、キャラクター画像、サウンド(BGM、ボイス、効果音)をアセットとしてAddressablesに登録。
- 登録したいアセットに「Addressable」にチェックを入れ、適切なラベルを付与します。
- ビルド:
- 「Window」→「Asset Management」→「Addressables」→「Groups」でAddressablesウィンドウを開き、「Build」→「New Build」→「Default Build Script」でビルド。
4. スクリプトの実装
DialogueLoader(CSVデータの読み込み)
DialogueLoader
スクリプトでCSVファイルを読み込み、各シーン情報をDictionaryとして管理します。
using System.Collections.Generic;
using System.IO;
using UnityEngine;
public class DialogueLoader : MonoBehaviour
{
public TextAsset csvFile;
private Dictionary<int, DialogueNode> dialogueData;
void Start()
{
LoadDialogueData();
}
void LoadDialogueData()
{
dialogueData = new Dictionary<int, DialogueNode>();
using (StringReader reader = new StringReader(csvFile.text))
{
bool isFirstLine = true;
while (reader.Peek() > -1)
{
string line = reader.ReadLine();
if (isFirstLine) { isFirstLine = false; continue; }
string[] columns = line.Split(',');
DialogueNode node = new DialogueNode
{
id = int.Parse(columns[0]),
background = columns[1],
speaker = columns[2],
characterName = columns[3],
characterEmotion = columns[4],
text = columns[5],
bgm = columns[10],
voice = columns[11],
soundEffect = columns[12],
choices = new List<DialogueNode.Choice>()
};
if (!string.IsNullOrEmpty(columns[6]))
{
node.choices.Add(new DialogueNode.Choice { text = columns[6], nextId = int.Parse(columns[7]) });
}
if (!string.IsNullOrEmpty(columns[8]))
{
node.choices.Add(new DialogueNode.Choice { text = columns[8], nextId = int.Parse(columns[9]) });
}
dialogueData[node.id] = node;
}
}
}
public DialogueNode GetNode(int id)
{
return dialogueData.ContainsKey(id) ? dialogueData[id] : null;
}
}
[System.Serializable]
public class DialogueNode
{
public int id;
public string background;
public string speaker;
public string characterName;
public string characterEmotion;
public string text;
public string bgm;
public string voice;
public string soundEffect;
public List<Choice> choices;
[System.Serializable]
public class Choice
{
public string text;
public int nextId;
}
}
DialogueManager(データを使って表示とサウンド再生)
DialogueManager
スクリプトで、Addressablesを利用してアセットをロードし、シーン情報に応じた表示やサウンド再生を行います。
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;
using System.Threading.Tasks;
public class DialogueManager : MonoBehaviour
{
public DialogueLoader dialogueLoader;
public Text dialogueText;
public Text characterNameText;
public Image background;
public Image characterImage;
public Button[] choiceButtons;
public AudioSource bgmSource;
public AudioSource voiceSource;
public AudioSource soundEffectSource;
private DialogueNode currentNode;
void Start()
{
SetDialogue(1);
}
public void SetDialogue(int id)
{
currentNode = dialogueLoader.GetNode(id);
if (currentNode == null) return;
dialogueText.text = currentNode.text;
characterNameText.text = currentNode.characterName;
// Addressableでアセットを非同期にロード
UpdateBackground(currentNode.background);
UpdateCharacter(currentNode.speaker, currentNode.characterEmotion);
PlayBGM(currentNode.bgm);
PlayVoice(currentNode.voice);
PlaySoundEffect(currentNode.soundEffect);
for (int i = 0; i < choiceButtons.Length; i++)
{
if (i < currentNode.choices.Count)
{
choiceButtons[i].gameObject.SetActive(true);
choiceButtons[i].GetComponentInChildren<Text>().text = currentNode.choices[i].text;
int nextId = currentNode.choices[i].nextId;
choiceButtons[i].onClick.RemoveAllListeners();
choiceButtons[i].onClick.AddListener(() => SetDialogue(nextId));
}
else
{
choiceButtons[i].gameObject.SetActive(false);
}
}
}
private async void UpdateBackground(string backgroundName)
{
if (!string.IsNullOrEmpty(backgroundName))
{
var handle = Addressables.LoadAssetAsync<Sprite>($"Backgrounds/{backgroundName}");
await handle.Task;
if (handle.Status == AsyncOperationStatus.Succeeded)
{
background.sprite = handle.Result;
}
}
}
private async void UpdateCharacter(string characterName, string emotion)
{
if (!string.IsNullOrEmpty(characterName))
{
characterImage.gameObject.SetActive(true);
var handle = Addressables.LoadAssetAsync<Sprite>($"Characters/{characterName}_{emotion}");
await handle.Task;
if (handle.Status == AsyncOperationStatus.Succeeded)
{
characterImage.sprite = handle.Result;
}
}
else
{
characterImage.gameObject.SetActive(false);
}
}
private async void PlayBGM(string bgmName)
{
if (!string.IsNullOrEmpty(bgmName))
{
var handle = Addressables.LoadAssetAsync<AudioClip>($"BGM/{bgmName}");
await handle.Task;
if (handle.Status == AsyncOperationStatus.Succeeded)
{
bgmSource.clip = handle.Result;
bgmSource.Play();
}
}
}
private async void PlayVoice(string voiceName)
{
if (!string.IsNullOrEmpty(voiceName))
{
var handle = Addressables.LoadAssetAsync<AudioClip>($"Voices/{voiceName}");
await handle.Task;
if (handle.Status == AsyncOperationStatus.Succeeded)
{
voiceSource.clip = handle.Result;
voiceSource.Play();
}
}
}
private async void PlaySoundEffect(string soundEffectName)
{
if (!string.IsNullOrEmpty(soundEffectName))
{
var handle = Addressables.LoadAssetAsync<AudioClip>($"Effects/{soundEffectName}");
await handle.Task;
if (handle.Status == AsyncOperationStatus.Succeeded)
{
soundEffectSource.PlayOneShot(handle.Result);
}
}
}
}
5. テストとデバッグ
- CSVファイルを更新し、シーンやサウンドの情報を調整します。
- Unityエディタでプレイして、背景やキャラクターの表情、サウンドが正しく再生されるか確認します。
6. まとめ
この資料では、Unityのテキストアドベンチャーゲームにおける、背景やキャラクター、サウンドをAddressablesで管理する方法を紹介しました。この構成により、柔軟なストーリー構築と効率的なリソース管理が可能です。
ディスカッション
コメント一覧
まだ、コメントがありません