It's a plugin that allows you to easily load CMS data into ScriptableObject
.
Details
This plugin is built on the concept of being "Easy to input and delivering top-notch performance".
When it comes to input, we think of CMS. CMS is filled with knowledge on how to input data easily and without stress. Also, it gives the ease of being able to be updated from anywhere.
ScriptableObject
is a data format that is optimized for handling in Unity. It has excellent performance.
However, these two may seem unrelated at first glance. But it is CMSuniVortex that connects CMS and ScriptableObject
. This is not a mere plugin, but a solution born out of pursuing efficiency and performance.
You can specify how to refer to the data you have output.
- Direct reference
- Referenced via Addressables
Caution
2.X upgrade contains breaking changes. Please make sure to backup before upgrading.
Specifically, we separated classes that use Languages (which was mandatory in 1.X) from those that don't. As a result, previous Language settings have been cleared, so please reconfigure and re-import after setting them again.
Change Details
We made the previously mandatory Language selection optional.
Window > CMSuniVortex > open Script Generator

If you want to continue using Language as before, please select Use localization in the Script Generator and build. ※This will not affect existing classes.

The Key name required for data can now be modified.

For example, in GoogleSheet, you previously had to use "Key" as the column name for unique IDs, but this could be unclear in some cases. Now you can change it to match your sheet.

We renamed components used to get values from output data and added components for Localization.
- CuvList
- CuvModel
- CuvLocalize
- CuvAddressableList
- CuvAddressableModel
- CuvLanguages
- CuvLanguageDropDown
- CuvLanguageSwitcher
Please add the URL to "Window > Package Manager > Add package from git URL...".
https://github.com/IShix-g/CMSuniVortex.git?path=Packages/CMSuniVortex
Right click on the Project and select "Create > CMSuniVortex > create CuvImporter" to create a CuvImporter
.
Click the "Script Generator" button on the generated CuvImporter
.
Enter the necessary information to generate the code. In this case, we generate the code for Cockpit.
※ Classes that have been generated once will not be overwritten when generated again and will be ignored.
explanation | e.g. | |
---|---|---|
Full Class Name | Specify the class name. Namespace can also be specified. | namespace.ClassName |
Build Path | Specify the path of the directory to generate the code. | Assets/Scripts/ |
Use addressables | Should output code for using addressables? | |
Use localization | Should output code for localization? | |
Generate output | Should output code for output? |
After generating, return to CuvImporter and enter the necessary information. Specify the script generated earlier as the client. This time, we selected CatDetailsCockpitCuvClient
for direct reference. If using Addressables, select the AddressableClient
above it.
Naming rule for output Client: "Full class name specified when generating code" + "CMS name" + "Output name" + "CuvClient"
explanation | e.g. | |
---|---|---|
Build Path | Specify the directory where the data will be output. | Assets/Models/ |
Languages | Specify the language, even if not used, at least one needs to be selected. | English |
Client | Specify any client for direct reference or Addressables, etc.. | Test.ClassNameCockpitCuvClient |
Output | Decide how to refer to the data output by the client. | Test.ClassNameCockpitCuvOutput |
explanation | e.g. | |
---|---|---|
Base Url | URL where Cockpit is installed | https://xxx.xxx.com/cockpit/ |
Api Key | Api Key obtainable from the Cockpit admin page | English |
Model Name | Model name set on Cockpit's admin page | Model |
Actual tests using Cockpit CMS are possible. Please use the following.
value | |
---|---|
Base Url | https://devx.myonick.biz/cockpit/ |
Api Key | API-a92fac21986ac045e143f07c27c60e09f19ae856 |
Model Name | Model |
Although the permission is read-only, you can actually log in and view the admin page.
value | |
---|---|
URL | https://devx.myonick.biz/cockpit/ |
ID | guest |
PW | guest |
- Please use in moderation.
- Do not access too frequently.
- Do not perform consecutive imports.
- Although advertisements are displayed because I use a free rental server, I am not involved at all.
- Please note that we may stop without notice if we find inappropriate access.
After input, click Import, and the data is generated in the specified directory.
Decide how to refer to the imported data. This time, we select CatDetailsCockpitCuvOutput
for direct reference.
After selection, click on Output to generate it.
Data can be retrieved using GetList()
from the generated CatDetailsCockpitCuvReference
. If you use the prepared CuvLocalized
, you can retrieve it as follows.
The instance of Reference and the Key set on the inspector are passed, so you use TryGetByKey
to retrieve it.
CuvLocalizedTest.cs
using CMSuniVortex;
public abstract class CuvLocalizedTest : CuvLocalized<CatDetailsLocalizeCockpitCuvReference>
{
protected abstract void OnChangeLanguage(CatDetailsLocalize catDetails);
protected override void OnChangeLanguage(CatDetailsLocalizeCockpitCuvReference reference, string key)
{
if (reference.TryGetByKey(key, out var model))
{
OnChangeLanguage(model);
}
}
}
CuvLocalizedTextTest.cs
using UnityEngine;
using UnityEngine.UI;
[RequireComponent(typeof(Text))]
public sealed class CuvLocalizedTextTest : CuvLocalizedTest
{
[SerializeField] Text _text;
protected override void OnChangeLanguage(CatDetailsLocalize model)
{
_text.text = model.Text;
}
protected override void Reset()
{
base.Reset();
_text = GetComponent<Text>();
}
}
※ CuvAddressableLocalized
is used for Addressables.
For details on how to set up, please see here.
You can check representative classes that constitute the plugin here.
You can check a list of created CuvImporters.
Window > CMSuniVortex > open CuvImporter list
For information about localization, please refer to here.
What prompted me to develop this plugin was performance testing. There are roughly three methods to download data and display it.
Good performance with no need for deserialization or data conversion by using ScriptableObject
or Sprite
Since it needs to be exported by Unity, a programmer is required. Or a significant conversion system needs to be established.
Test Code
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;
using UnityEngine.Profiling;
public sealed class AddressableTest : MonoBehaviour
{
[SerializeField] Image _image;
[SerializeField] Text _text;
[SerializeField] Button _loadButton;
[SerializeField] Button _unloadButton;
AsyncOperationHandle<AddressableData> _handle;
void Start()
{
_loadButton.onClick.AddListener(OnLoadButtonClicked);
_unloadButton.onClick.AddListener(OnUnloadButtonClicked);
_loadButton.interactable = true;
_unloadButton.interactable = false;
}
void OnDestroy() => Unload();
async void OnLoadButtonClicked()
{
_loadButton.interactable = false;
_unloadButton.interactable = true;
Profiler.BeginSample("AddressableTestProfile1");
_handle = Addressables.LoadAssetAsync<AddressableData>("AddressableData");
Profiler.EndSample();
await _handle.Task;
Profiler.BeginSample("AddressableTestProfile2");
var obj = _handle.Result;
_image.sprite = obj.Image;
_text.text = obj.GetText();
Profiler.EndSample();
}
void OnUnloadButtonClicked()
{
Unload();
_loadButton.interactable = true;
_unloadButton.interactable = false;
}
void Unload()
{
if (_image != default)
{
_image.sprite = default;
}
if (_text != default)
{
_text.text = default;
}
if (_handle.IsValid())
{
Addressables.Release(_handle);
}
}
}
[CreateAssetMenu(fileName = "AddressableData", menuName = "ScriptableObject/AddressableData", order = 0)]
public sealed class AddressableData : ScriptableObject
{
public int ID;
public string Title;
public string Contents;
public Sprite Image;
public string GetText() => "ID:" + ID + "\nTitle:" + Title + "\nContents:" + Contents;
}
Webview from Cross Platform Essential Kit
- Can be used for both WEB pages and applications.
- The layout can be free even after release.
- Concerned about the amount of memory used.
Test Code
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.Profiling;
using VoxelBusters.CoreLibrary;
using VoxelBusters.EssentialKit;
public sealed class WebViewTest : MonoBehaviour
{
const string url = "https://xxx.xxxx.com/webview/";
[SerializeField] Button _openButton;
[SerializeField] Button _closeButton;
WebView _webView;
void Start()
{
_openButton.onClick.AddListener(ClickOpenButton);
_closeButton.onClick.AddListener(ClickCloseButton);
_openButton.interactable = true;
_closeButton.interactable = false;
}
void OnEnable()
{
WebView.OnShow += OnWebViewShow;
WebView.OnHide += OnWebViewHide;
}
void OnDisable()
{
WebView.OnShow -= OnWebViewShow;
WebView.OnHide -= OnWebViewHide;
}
void ClickOpenButton()
{
_openButton.interactable = false;
Profiler.BeginSample("WebViewTestProfile");
_webView = WebView.CreateInstance();
_webView.SetNormalizedFrame(new Rect(0.1f, 0.2f, 0.8f, 0.6f));
_webView.LoadURL(URLString.URLWithPath(url));
_webView.Show();
Profiler.EndSample();
}
void ClickCloseButton() => _webView.Hide();
void OnWebViewShow(WebView view) => _closeButton.interactable = true;
void OnWebViewHide(WebView view)
{
_openButton.interactable = true;
_closeButton.interactable = false;
}
}
Webページ
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>Test</title>
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<meta name="format-detection" content="telephone=no,email=no,address=no">
<style type="text/css">
img{
max-width: 100%;
}
</style>
</head>
<body>
<div id="myData">
<h2 id="title"></h2>
<p id="contents"></p>
<img id="image" src="" alt="Image">
</div>
<script src="https://code.jquery.com/jquery-1.12.4.min.js"></script>
<script>
$.ajax({
url: 'getModel.php',
dataType: 'json',
success: function(data) {
$('#title').text(data.Title);
$('#contents').text(data.Contents);
$('#image').attr('src', data.Image);
},
error: function (request, status, error) {
console.log("Error: Could not fetch data");
}
});
</script>
</body>
</html>
データを取得するAPI
<?php
class Model {
public $Id;
public $Title;
public $Contents;
public $Image;
}
mb_language("uni");
mb_internal_encoding("UTF-8");
header('Content-type: application/json');
$model = new Model();
$model->Id = 2222;
$model->Title = '猫 ねこ';
$model->Contents = '猫は、古代のミアキスと言う豹のような大きな動物が起源と言われています。 今から4000~5000年前にエジプトから発生し、住み良い環境を求め分化して中東に行きました。';
$model->Image = 'https://xxx.xxxx.com/webview/cat.jpg';
echo json_encode( $model );
Display by converting JSON obtained from the server using UnityWebRequest.
- Can be used for WEB and apps.
- Apart from initialization, it seems lighter than WebView.
- There is a concern about the initialization cost if there are many images (only one image in the test).
- If you don't cache data, you need to provide your own caching mechanism.
Test Code
using System;
using System.Collections;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.Networking;
using UnityEngine.Profiling;
public sealed class JsonTest : MonoBehaviour
{
const string apiUrl = "https://xxx.xxxx.com/webview/getModel.php";
[SerializeField] Image _image;
[SerializeField] Text _text;
[SerializeField] Button _loadButton;
[SerializeField] Button _unloadButton;
[Serializable]
sealed class Model
{
public int ID;
public string Title;
public string Contents;
public string Image;
public string GetText() => "ID:" + ID + "\nTitle:" + Title + "\nContents:" + Contents;
}
void Start()
{
_loadButton.onClick.AddListener(OnLoadButtonClicked);
_unloadButton.onClick.AddListener(OnUnloadButtonClicked);
_loadButton.interactable = true;
_unloadButton.interactable = false;
}
void OnDestroy() => Unload();
void OnLoadButtonClicked()
{
_loadButton.interactable = false;
_unloadButton.interactable = false;
StartCoroutine(LoadCo((model, sprite) =>
{
_text.text = model.GetText();
_image.sprite = sprite;
Profiler.EndSample();
_unloadButton.interactable = true;
}));
}
IEnumerator LoadCo(Action<Model, Sprite> onSuccess)
{
Profiler.BeginSample("JsonTestProfile1");
using var request = UnityWebRequest.Get(apiUrl);
Profiler.EndSample();
yield return request.SendWebRequest();
if (request.result == UnityWebRequest.Result.Success)
{
Profiler.BeginSample("JsonTestProfile2");
var model = JsonUtility.FromJson<Model>(request.downloadHandler.text);
using var imgRequest = UnityWebRequestTexture.GetTexture(model.Image);
Profiler.EndSample();
yield return imgRequest.SendWebRequest();
if (imgRequest.result == UnityWebRequest.Result.Success)
{
Profiler.BeginSample("JsonTestProfile3");
var texture = ((DownloadHandlerTexture)imgRequest.downloadHandler).texture;
var sprite = Sprite.Create(
texture,
new Rect(0, 0, texture.width, texture.height),
new Vector2(0.5f, 0.5f));
onSuccess?.Invoke(model, sprite);
}
else
{
Debug.LogError(imgRequest.error);
}
}
else
{
Debug.LogError(request.error);
}
}
void OnUnloadButtonClicked()
{
Unload();
_loadButton.interactable = true;
_unloadButton.interactable = false;
}
void Unload()
{
if (_image != default
&& _image.sprite != default)
{
var tex = _image.sprite.texture;
_image.sprite = null;
DestroyImmediate(tex);
Resources.UnloadUnusedAssets();
}
if (_text != default)
{
_text.text = default;
}
}
}
From these tests, we learned that:
- Addressable performs the best.
- WebView uses significant memory on Android. It might not be possible to fully release all memory.
- Json has a significant initialization cost when there are many images.
From these results, I wanted to use Addressable, which has the best performance, but also allows for easy updates from the CMS, so I developed this plugin.
GC Alloc | Time | Size | |
---|---|---|---|
Addressables | 3.2KB | 0.24ms | 1.1MB |
WebView | 22.9KB | 0.52ms | 2MB |
Json | 15KB | 3.75ms | 2.3MB |
GC Alloc | Time | Size | |
---|---|---|---|
Addressables | 3.1KB | 0.24ms | 9MB |
WebView | 31.8KB | 0.56ms | 70MB |
Json | 4.3KB | 1.18ms | 9.7MB |
Currently, the system only supports up to the generation of ScriptableObject
. However, we are planning to enhance it to handle the locally generated objects and to build Addressable to send to a server. We would also like to increase support for CMS. If you are interested, we appreciate your cooperation.