【Unity】外部からテキストファイルを読み込む

導入

txtファイルやcsvファイルの中身を読み込んで使うとき、下のようなコードを書きます。

using System.IO;
using System.Text;
using UnityEngine;

public class FileInfo : MonoBehaviour
{
    private void Start()
    {
        // 使用例:↓下のように書けば C:\text.txt の中身が出力されます。
        string text = ContentOfTxtFile(@"C:\test.txt");
        Debug.Log(text);
    }

    /// <summary>
    /// テキストファイルの中身を返却
    /// </summary>
    /// <param name="iPath">テキストファイルの場所(フルパス)</param>
    /// <returns>テキストの中身(string) or null</returns>
    public static string ContentOfTxtFile(string iPath)
    {
        //Textファイルが存在するなら読み取って、無いならnullを返却
        if (File.Exists(iPath)) return ReadText(iPath);

        Debug.LogError("Txt file not found");
        return null;
    }

    #region ReadText
    private static string ReadText(string iPath)
    {
        // テキスト読み込み処理
        using var fs = new FileStream(iPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
        using var reader = new StreamReader(fs, Encoding.UTF8);
        string textContent = reader.ReadToEnd();
        return textContent;
    }
    #endregion
}

そしてCドライブにtest.txtを配置(図1)。FileInfo.csを適当なGameObjectにアタッチ。するとプログラム実行時にテキストファイルの内容がコンソールに出力されます。

図1.ファイルテスト

Cドライブ直下でテキストを編集すると、権限の関係上、ファイルに変更が反映されない場合がある様子です。

また図2のように文字化けしてしまった場合は、文字フォーマットをUTF-8に変更して保存し、再度実行してみましょう。

図2.文字化け

すると、図3のようにテキストファイルの中身が出力されるはずです。

図3.文字化けの解消

ここまで来たら一先ずテキストファイルの中身の読込は終了です。

さて、出来ればビルドしたexeファイルの近くにフォルダを置きたいですよね。テキストはまとめておいて、それら全部を読み出したい場合だってあるはずです。そんなときに使える拡張クラスを作りました。


応用

導入編のスクリプトのメソッドをどこからでも使えるようクラス自体をstaticに変更し、メソッドを追加します。

下記のコードで出来ることは

・パスの返却
 → exeファイル(エディタの場合はAssets, Library…)があるところを返す
・ファイルが存在するか確認
・テキストファイルの中身を返却
・指定階層にあるファイルのフルパス/ファイル名を返却

など、エクスプローラのファイル読取り操作を大体網羅しました。

using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using UnityEngine;

public static class FileInfo
{
    /// <summary>
    /// Exeファイルと同階層のフルパスを返す
    /// </summary>
    public static string AppPath
    {
        get
        {
            var result = Application.dataPath;
            result += Application.platform switch
            {
                // MacOSでビルドした場合のパス → .appファイルの同階層のパス
                // Windowsでビルドした場合のパス → .exeファイルの同階層のパス
                // それ以外の場合 → Assets/Resourcesフォルダのパス
                RuntimePlatform.OSXPlayer => "/../../",
                RuntimePlatform.WindowsPlayer => "/../",
                RuntimePlatform.WindowsEditor => "/../",
                _ => "/Resources/"
            };
            return result;
        }
    }

    /// <summary>
    /// 存在するファイルかどうか調べ、結果を返却
    /// </summary>
    /// <param name="path"></param>
    /// <param name="ifCountSubDirectory"></param>
    /// <returns></returns>
    public static bool isExistFile(this string path, bool ifCountSubDirectory = false)
    {
        return File.Exists(path);
    }


    /// <summary>
    /// テキストファイルの中身を返却
    /// </summary>
    /// <param name="iPath">テキストファイルの場所(フルパス)</param>
    /// <returns>テキストの中身(string) or null</returns>
    public static string ContentOfTxtFile(string iPath)
    {
        //Textファイルが存在するなら読み取って、無いならnullを返却
        if (File.Exists(iPath)) return ReadText(iPath);

        Debug.LogError("Txt file not found");
        return null;
    }

    #region ReadText
    private static string ReadText(string iPath)
    {
        // テキスト読み込み処理
        using var fs = new FileStream(iPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
        using var reader = new StreamReader(fs, Encoding.UTF8);
        string textContent = reader.ReadToEnd();
        return textContent;
    }
    #endregion


    /// <summary>
    /// 指定階層にあるファイルのフルパスを返却
    /// </summary>
    /// <param name="dir">パス</param>
    /// <param name="ifCountSubDirectory">フォルダ(ファイルではない)を対象に含むかどうか</param>
    /// <returns>ファイルの名前(フルパス、string[])</returns>
    public static string[] FilePaths(this string dir, bool ifCountSubDirectory = false)
    {
        return Directory.GetFiles(dir, "*", ifCountSubDirectory ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly);
    }

    /// <summary>
    /// 指定階層にあるファイルのファイル名を返却
    /// </summary>
    /// <param name="dir">パス</param>
    /// <param name="ifCountSubDirectory">フォルダ(ファイルではない)を対象に含むかどうか</param>
    /// <returns>ファイルの名前(拡張子なし、ファイル名のみ)</returns>
    public static IEnumerable<string> FileNames(this string dir, bool isWithExtension = false, bool ifCountSubDirectory = false)
    {
        return isWithExtension == false ? FilePaths(dir, ifCountSubDirectory).Select(Path.GetFileNameWithoutExtension) : FilePaths(dir, ifCountSubDirectory).Select(Path.GetFileName);
    }
}

下記は、上で改造したスクリプトの使い方の例です。

using System.Linq;
using UnityEngine;

public class FileInfoTest : MonoBehaviour
{

    private void Start()
    {
        // 使用例: プロジェクトは C:\Projects\FileInfoTest にあるものとすると下記の出力が得られます。

        // 1. ファイルが存在するかどうかを確認します → ファイルは存在します/しません
        Debug.Log((FileInfo.AppPath + "test.txt").isExistFile() ? "ファイルは存在します" : "ファイルは存在しません");

        // 2. 指定階層の一番上のファイルを返却します → C:\Projects\FileInfoTest\.vsconfig, .vsconfig
        Debug.Log(FileInfo.AppPath.FilePaths(ifCountSubDirectory: true)[0]);
        Debug.Log(FileInfo.AppPath.FileNames(isWithExtension: true, ifCountSubDirectory: true).ToList()[0]);

        // 3. ファイルの中身を確認します → [テキストファイルの中身]
        string text = FileInfo.ContentOfTxtFile(FileInfo.AppPath + "test.txt");
        Debug.Log(text);
    }
}

仮にプロジェクトが 図4 に示すように “E:\UnityProjectStudioRis\Archived Projects\2022\FileInfoTest” という階層にある場合、

図4.プロジェクトのファイル構成

図5のように出力が得られます。

図5.得られた出力

このときエディタは図6のようになっていて、2つのスクリプトともProjectフォルダにはありますが、アタッチされているのはMonoBehaviourを継承した後者だけです。

図6.エディタ構成

FileInfoクラスはプロジェクト内どこからでも使うことができます。

エラーや感想・質問は、コメント欄に書いて頂けると幸いです。


さらに応用

なお、読者のあなたがGUI厨で、どうしてもクリックポチポチでファイルを読み込みたい場合は、Standalone File Browserを使えば目的の動作を実現できます。リンク先に使い方も書いてあります。

作成者: rarafy

2013年くらいからUnityを触っているかもしれません。 特に書くこともありませんが、趣味は部屋の掃除です。

コメントする

メールアドレスが公開されることはありません。 が付いている欄は必須項目です