An open API service indexing awesome lists of open source software.

https://github.com/ishix-g/cmsunivortex

Data can be retrieved from CMS and imported into ScriptableObject. CMSからデータを取得し、ScriptableObjectにインポート
https://github.com/ishix-g/cmsunivortex

addressables cockpit-cms spreadsheets unity unity3d

Last synced: 3 months ago
JSON representation

Data can be retrieved from CMS and imported into ScriptableObject. CMSからデータを取得し、ScriptableObjectにインポート

Awesome Lists containing this project

README

          

[日本語のRead me](docs/README_jp.md)

![Unity](https://img.shields.io/badge/Unity-2021.3%2B-black)
![Cockpit](https://img.shields.io/badge/Cockpit-v2-black)
![Google Drive API](https://img.shields.io/badge/GoogleAPI-Drive%20v3%201.68.0.3568-blue)
![Google Sheet API](https://img.shields.io/badge/GoogleAPI-Sheet%20v4%201.68.0.3574-blue)

# CMSuniVortex
It's a plugin that allows you to easily load CMS data into `ScriptableObject`.

![Logo](docs/assets/top.png)

## Table of Contents

Details

- [Why use this plugin?](#why-use-this-plugin)
- [Ease of Input](#ease-of-input)
- [High-performance Data](#high-performance-data)
- [Supported CMS](#supported-cms)
- [Methods of Reference Supported](#methods-of-reference-supported)
- [Unity Version](#unity-version)
- [Getting started](#getting-started)
- [Install via git URL](#install-via-git-url)
- [Quick Start](#quick-start)
- [Generate CuvImporter](#generate-cuvimporter)
- [Generating the code](#generating-the-code)
- [Entering Necessary Information in CuvImporter](#entering-necessary-information-in-cuvimporter)
- [Cockpit Client](#cockpit-client)
- [Cockpit CMS Test](#cockpit-cms-test)
- [Login Information](#login-information)
- [Notes for Cockpit Test Server](#notes-for-cockpit-test-server)
- [Starting the Import](#starting-the-import)
- [Specifying the Output](#specifying-the-output)
- [Retrieval and Display of Data](#retrieval-and-display-of-data)
- [Setup for Cockpit](#setup-for-cockpit)
- [Roles of Each Class](#roles-of-each-class)
- [Why Do I Want to Make This Plugin?](#why-do-i-want-to-make-this-plugin)
- [1. Addressable](#1-addressable)
- [Advantages](#advantages)
- [Concerns](#concerns)
- [2. WebView](#2-webview)
- [Merits](#merits)
- [Concerns](#concerns-1)
- [3. Json](#3-json)
- [Merits](#merits-1)
- [Concerns](#concerns-2)
- [What We Learned From the Test Results](#what-we-learned-from-the-test-results)
- [iOS : iPhone SE2 17.5.1](#ios--iphone-se2-1751)
- [Android : Galaxy S10 Android11](#android--galaxy-s10-android11)
- [Plans for the Future](#plans-for-the-future)

## Why use this plugin?

This plugin is built on the concept of being **"Easy to input and delivering top-notch performance"**.

### Ease of Input

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.

### High-performance Data

`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.

## Supported CMS

- [Cockpit](docs/IntegrationWithCockpit.md)
- [Google Sheets](docs/IntegrationWithGoogleSheet.md)

## Methods of Reference Supported

You can specify how to refer to the data you have output.

- Direct reference
- Referenced via [Addressables](https://docs.unity3d.com/Packages/com.unity.addressables@1.19/manual/index.html)

## Unity Version
Unity 2021.3.x or higher

## Getting started

### Install via git URL
Please add the URL to "Window > Package Manager > Add package from git URL...".

URL: `https://github.com/IShix-g/CMSuniVortex.git?path=Packages/CMSuniVortex`

![Package Manager](docs/assets/package_manager.png)

## Quick Start

### Generate CuvImporter

Right click on the Project and select "Create > CMSuniVortex > create CuvImporter" to create a `CuvImporter`.

![create](docs/assets/create.png)

### Generating the code

Click the "Script Generator" button on the generated `CuvImporter`.

open generator

Enter the necessary information to generate the code. In this case, we generate the code for Cockpit.

create classes

| | 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/Models/ |

### Entering Necessary Information in CuvImporter

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](https://docs.unity3d.com/Packages/com.unity.addressables@1.19/manual/index.html), 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|

select client

#### Cockpit Client

| | 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 |

### Cockpit CMS Test
Actual tests using Cockpit CMS are possible. Please use the following.

| | value |
|------------|----------------------------------------------|
| Base Url | [https://devx.myonick.biz/cockpit/](https://devx.myonick.biz/cockpit/)|
| Api Key | API-a92fac21986ac045e143f07c27c60e09f19ae856 |
| Model Name | Model |

#### Login Information

Although the permission is read-only, you can actually log in and view the admin page.

| | value |
|-----|------------------------------------------------------------------------|
| URL | [https://devx.myonick.biz/cockpit/](https://devx.myonick.biz/cockpit/) |
| ID | guest |
| PW | guest |

#### Notes for Cockpit Test Server

- 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.

### Starting the Import

After input, click Import, and the data is generated in the specified directory.

start import

### Specifying the Output

Decide how to refer to the imported data. This time, we select `CatDetailsCockpitCuvOutput` for direct reference.

start import

After selection, click on Output to generate it.

start import

### Retrieval and Display of Data

Data can be retrieved using `GetList()` from the generated `CatDetailsCockpitCuvReference`. If you use the prepared `CuvComponent`, you can retrieve it as follows.

start import

The instance of Reference and the Key set on the inspector are passed, so you use `TryGetByKey` to retrieve it.

```csharp
using UnityEngine;
using UnityEngine.UI;
using CMSuniVortex.Compornents;

public sealed class TestText : CuvComponent
{
[SerializeField] Text _text;

protected override void OnChangeLanguage(CatDetailsCockpitCuvReference reference, string key)
{
if (reference.GetList().TryGetByKey(key, out var model))
{
_text.text = model.Text;
}
}
}
```

※ `CuvAsyncComponent` is used for Addressables.

## Setup for Cockpit

For details on how to set up, please see [here](docs/IntegrationWithCockpit.md).

## Roles of Each Class

You can check representative classes that constitute the plugin [here](docs/RelationshipsBetweenClasses.md).

## Why Do I Want to Make This Plugin?

What prompted me to develop this plugin was performance testing. There are roughly three methods to download data and display it.

### 1. Addressable

#### Advantages

Good performance with no need for deserialization or data conversion by using `ScriptableObject` or `Sprite`

#### Concerns

Since it needs to be exported by Unity, a programmer is required. Or a significant conversion system needs to be established.

Test Code

```csharp

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 _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");
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;
}
```

### 2. WebView

Webview from [Cross Platform Essential Kit](https://assetstore.unity.com/packages/tools/integration/cross-platform-native-plugins-essential-kit-mobile-ios-android-140111)

#### Merits

- Can be used for both WEB pages and applications.
- The layout can be free even after release.

#### Concerns

- Concerned about the amount of memory used.

Test Code

```csharp

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ページ
```html


Test



img{
max-width: 100%;
}




Image

$.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");
}
});

```

データを取得するAPI
```php
Id = 2222;
$model->Title = '猫 ねこ';
$model->Contents = '猫は、古代のミアキスと言う豹のような大きな動物が起源と言われています。 今から4000~5000年前にエジプトから発生し、住み良い環境を求め分化して中東に行きました。';
$model->Image = 'https://xxx.xxxx.com/webview/cat.jpg';
echo json_encode( $model );
```

### 3. Json

Display by converting JSON obtained from the server using [UnityWebRequest](https://docs.unity3d.com/ja/2021.3/ScriptReference/Networking.UnityWebRequest.html).

#### Merits
- Can be used for WEB and apps.
- Apart from initialization, it seems lighter than WebView.

#### Concerns
- 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

```csharp

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 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(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;
}
}
}
```

### What We Learned From the Test Results

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.

#### iOS : iPhone SE2 17.5.1

| | GC Alloc | Time | Size |
|---|:--|:--|---|
| Addressables | 3.2KB | 0.24ms | 1.1MB |
| WebView | 22.9KB | 0.52ms | 2MB |
| Json | 15KB | 3.75ms | 2.3MB |

#### Android : Galaxy S10 Android11

| | GC Alloc | Time | Size |
|---|:--|:--|---|
| Addressables | 3.1KB | 0.24ms | 9MB |
| WebView | 31.8KB | 0.56ms | 70MB |
| Json | 4.3KB | 1.18ms | 9.7MB |

## Plans for the Future

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.