こんにちは、コンテンツデザイン開発グループの竹田です。
Unreal Engine5でベータ版が実装された、Jsonが扱えるプラグイン「Json Blueprint Utilities」のテストの続きです。
前編はこちらをご覧ください。
ScrollBoxにJsonから取得した情報を入れる
BlueprintでのJsonの扱いも分かってきたので、次はUMGのScrollBoxと合わせた使い方を試します。
CSVからJsonに変換
まずはJsonファイルを用意します。
手頃なデータとして、気象庁の過去の気象データを使用してみます。
https://www.data.jma.go.jp/gmd/risk/obsdl/index.php
数ヵ月間の年月日、最高気温、最低気温のCSVをダウンロードしました。
データは少し調整

このCSVをJsonに変換します。
ExcelやCSVの扱いはPythonが得意そうですが、せっかくなので学習中のRustを使用します。

Rustは機械語に直接コンパイルされる言語なので案件によっては便利に感じる事も多く、 ちょっとしたツールの開発に利用する事もあります。
CSVの扱いには「csv」というCrate(機能のまとまり、ライブラリのようなイメージ)があるのでこちらを使用します。
変換ツール作成
Weather structを作成します。この構造体にCSVから読み込んだデータを入れてシリアライズします。
#[derive(Serialize, Debug)]
struct Weather {
date: String,
#[serde(rename(serialize = "maximumTemperature"))]
maximum_temperature: f32,
#[serde(rename(serialize = "minimumTemperature"))]
minimum_temperature: f32,
}
CSVファイルを読み込みます(必要に応じてCSVファイルをバッファに読み出しデコードしたものを使用します)。
let rdr = csv::Reader::from_reader(decoded_content.as_bytes());
forで取り出しWeatherに入れていきます。最後に東京の天気なので「tokyo」をキーとしたmapにしました。
fn create_map(mut rdr: Reader<&[u8]>) -> Result<BTreeMap<String, Vec<Weather>>, Box<dyn Error>> {
let mut weathers : Vec<Weather> = Vec::new();
for result in rdr.records() {
let record = result?;
//エラー処理など省略
let member = Weather {
date: record[0].into(),
maximum_temperature: record[1].into().parse().unwrap(),
minimum_temperature: record[2].into().parse().unwrap(),
};
weathers.push(member);
}
let mut map: BTreeMap<String, Vec<Weather>> = BTreeMap::new();
map.insert("tokyo".parse().unwrap(), weathers);
Ok(map)
}
Jsonのシリアライズはserde Crateを使用します。
let json_text = serde_json::to_string_pretty(&map)?;
途中を色々省略しましたが、このような形のJsonが作成できます。

Widget作成
ScrollBoxを配置します。

メインとなる上図のWidgetとは別に ScrollBoxの要素として入れるWidgetを新しく作成します。
WeatherElementWidgetという名前にしています。
年月日、最高気温、最低気温を入れるTextBoxを配置しています。

Structを作ります。jsonのキーと合わせます。

メイン側のWidgetでノードを繋いでいきます。

GetFieldで取得した値をForEachLoopで回し下記の作業を行います。
- WeatherElementWidgetを作成し、ScrollBoxの子に設定

- SetTextで、年月日、最高気温、最低気温のデータをセット

ScrollBoxでJsonから取得したデータを表示する事ができました。

Unityと比較
UnityでもScrollViewを使って同じような処理を行ってみます。
ScrollViewを配置します。

ScrollViewの要素を作りPrefabにします。

Jsonのデシリアライズに必要なデータクラスを作成します。
public class Weather
{
public string Date { get;}
public string MaximumTemperature { get;}
public string MinimumTemperature { get;}
public Weather(string date, string maximumTemperature, string minimumTemperature)
{
Date = date;
MaximumTemperature = maximumTemperature;
MinimumTemperature = minimumTemperature;
}
}
Weatherクラスの情報を要素内の各Textにセットするクラスを用意し、要素のトップにアタッチします。
public class WeatherElement : MonoBehaviour
{
[SerializeField] private Text _dateText;
[SerializeField] private Text _maxTempText;
[SerializeField] private Text _minTempText;
public void SetText(Weather weather)
{
_dateText.text = weather.Date;
_maxTempText.text = weather.MaximumTemperature;
_minTempText.text = weather.MinimumTemperature;
}
}

Jsonを読み取りWeatherクラスに流し込み、scrollViewに配置するまで。
using System.Collections.Generic;
using System.IO;
using Newtonsoft.Json;
using Scenes.Scripts;
using UnityEngine;
public class JsonTest : MonoBehaviour
{
[SerializeField] private GameObject _weatherElementPrefab;
[SerializeField] private Transform _contentsRootTr;//配置したい階層
//エラー処理などは省略
private void Start()
{
//StreamingAssetに置いたWeather.jsonをstringに
var jsonFile = Application.streamingAssetsPath + "/Weather.json";
var json = File.ReadAllText(jsonFile);
//デシリアライズ Weatherクラスに流し込み
var weatherTable = JsonConvert.DeserializeObject<Dictionary<string, Weather[]>>(json);
var weathers = weatherTable["tokyo"];
foreach (var weather in weathers)
{
//要素を作成しScrollViewのContentsを親として配置
var element = Instantiate(_weatherElementPrefab, Vector3.zero, Quaternion.identity, _contentsRootTr);
//Weatherの値をセット
element.GetComponent<WeatherElement>().SetText(weather);
}
}
}
Hierarchy上のGameObjectにアタッチします。

再生して確認します。

C#かBlueprintかという違いくらいでやる事はあまり変わらない印象です。
まとめ
以上、Jsonで取得した値をスクロールUIに入れるテストを作成しました。
実際の使用ではパフォーマンスを考慮してスクロール中見えていない要素は隠したり使いまわせるようにする事もあります。
Unityでは自作もいいですが、AssetStoreにはそういった機能に対応したAssetもあります。
Unreal EngineではListViewを使うとよさそうです。