Skip to content
This repository was archived by the owner on Aug 15, 2024. It is now read-only.

Commit 8674ef0

Browse files
committed
doc: update doc
1 parent 8168b0e commit 8674ef0

File tree

1 file changed

+87
-17
lines changed

1 file changed

+87
-17
lines changed

Packages/com.nuclearband.sodatabase/Documentation/Documentation.ru.md

Lines changed: 87 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@
55
Для хранения таких данных существует много способов - кто-то хранит их в таблицах, в xml или json файлах, которые редактируют собственными инструментами. Unity предоставляет свой способ - Scriptable Objects (SO), которые мне нравится тем, что для их визуального представления не нужно писать свой редактор, легко делать ссылки на ассеты игры и друг на друга, а с появлением Addressables эти данные можно легко и удобно хранить вне игры и обновлять отдельно.
66

77
В этой статье я хотел бы рассказать о своей библиотеке SODatabase, с помощью которой можно удобно создавать, редактировать и использовать в игре (редактировать и сериализовать) scriptable objects.
8-
8+
<cut />
99
### Создание и редактирование SO
1010

11-
Создание и редактирование SOшек я веду в отдельном окне, которое чем-то похоже на окно проекта с инспектором - слева находится дерево папок (папка, в которой находятся все SOшки - группа в addressables), а справа - инспектор выделенной SOшки.
11+
Создание и редактирование SOшек я веду в отдельном окне, которое чем-то похоже на окна проекта с инспектором - слева находится дерево папок (папка, в которой находятся все SOшки - группа в addressables), а справа - инспектор выделенной SOшки.
1212

1313
![Интерфейс](https://habrastorage.org/webt/g-/at/3_/g-at3_ewbaje3clfmpwy2fdv9fw.png)
1414

@@ -24,7 +24,7 @@ public string FullPath { get; }
2424

2525
### Доступ к SO в игре
2626

27-
В игре обычно нужно либо получить какую-то конкретную модель, например, SO со списком настроек какого-либо окна, либо набор моделей из папки - например, список айтемов, где модель каждого айтема представляет собой отдельный SO.
27+
В рантайме обычно нужно либо получить какую-то конкретную модель, например, SO со списком настроек какого-либо окна, либо набор моделей из папки - например, список айтемов, где модель каждого айтема представляет собой отдельный SO.
2828
Для этого в static классе SODatabase есть два основных метода, которые возвращают либо весь список моделей из нужной папки, либо конкретную модель из папки с определённым именем.
2929

3030
```csharp
@@ -33,8 +33,7 @@ public static T GetModel<T>(string path) where T : DataNode
3333
public static List<T> GetModels<T>(string path, bool includeSubFolders = false) where T : DataNode
3434
```
3535

36-
37-
Уточню, что один раз в начале игры перед запросом моделей SODatabase нужно проинициализировать, чтобы обновились данные из Addressables.
36+
Уточню, что один раз в начале игры перед запросом моделей SODatabase нужно проинициализировать, чтобы обновились и прогрузились данные из Addressables.
3837

3938
### Загрузка и сохранение
4039

@@ -44,31 +43,102 @@ public static List<T> GetModels<T>(string path, bool includeSubFolders = false)
4443

4544
Я достигаю этого, сериализуя динамические поля в ScriptableObject с помощью JSON.
4645

47-
Класс DataNode - родительский класс всех SO, хранящихся в SODatabase, помечен как
46+
Класс *DataNode *- родительский класс всех SO, хранящихся в *SODatabase*, помечен как
4847
```csharp
4948
[JsonObject(MemberSerialization.OptIn, IsReference = true)]
5049
```
51-
и все его *JsonProperty* сериализуются в файл save.txt при сохранении игры. Соответственно при инициализации SODatabase кроме запроса данных об изменении addressables происходит *JsonConvert.PopulateObject* для каждой динамической модели из SODatabase, используя данные из этого файла.
50+
и все его *JsonProperty* сериализуются в файл save.txt при сохранении игры. Соответственно при инициализации *SODatabase* кроме запроса данных об изменении addressables происходит *JsonConvert.PopulateObject* для каждой динамической модели из *SODatabase*, используя данные из этого файла.
5251

53-
Для того, чтобы это работало гладко, я сериализую ссылки на SO (которые могут являтся динамическими полями, помеченными как JSONProperty) в строку-путь, и потом десериализую обратно в ссылки на SO при загрузке. Есть ограничение - данные на игровые ассеты динамическими быть не могут. Но это не фундаментальное ограничение, просто у меня ещё не было случая, когда такие динамические данные потребовались бы, поэтому я не реализовывал специальную сериализацию для таких данных.
52+
Для того, чтобы это работало гладко, я сериализую ссылки на SO (которые могут являтся динамическими полями, помеченными как JsonProperty) в строку-путь, и потом десериализую обратно в ссылки на SO при загрузке. Есть ограничение - данные на игровые ассеты динамическими быть не могут. Но это не фундаментальное ограничение, просто у меня ещё не было случая, когда такие динамические данные потребовались бы, поэтому я не реализовывал специальную сериализацию для таких данных.
5453

5554
### Примеры
56-
В рпг для хранения информации об игроке я прямо создаю *PlayerSO*, в котором одни только динамические поля - имя, количество экспы игрока, кристаллов и так далее. Точно также для инвентаря игрока я создаю *PlayerInventorySO*, где храню список ссылок на айтемы игрока (каждый айтем представляет собой ссылку на статичный SO из SODatabase).
57-
58-
Бывают наполовину статические, наполовину динамические данные - например, квесты. Возможно, это не лучший подход, но я прямо в моделях *QuestSO *со статической информацией о квестах (название, описание, цели и т.д.) храню динамическую информацию по прогрессу в этом квесте. Таким образом гейм-дизайнер в одном инспекторе видит всю инфу о текущем состоянии квеста и его описание.
59-
60-
61-
62-
63-
64-
6555

56+
В классе-стартере игры инициализация и загрузка данных
57+
```csharp
58+
async void Awake()
59+
{
60+
await SODatabase.InitAsync(null, null);
61+
await SODatabase.LoadAsync();
62+
}
63+
```
64+
и сохранение стейта при выходе
65+
```csharp
66+
private void OnApplicationPause(bool pauseStatus)
67+
{
68+
if (pauseStatus)
69+
SODatabase.Save();
70+
}
71+
72+
private void OnApplicationQuit()
73+
{
74+
SODatabase.Save();
75+
}
76+
```
6677

78+
В рпг для хранения информации об игроке я прямо создаю *PlayerSO*, в котором одни только динамические поля - имя, количество экспы игрока, кристаллов и так далее. Также хорошей практикой по-моему является создание статической строки с путём, по которому я сохраняю данную модель в SODatabase, чтобы потом обращаться к ней в рантайме.
79+
```csharp
80+
public class PlayerSO : DataNode
81+
{
82+
public static string Path => "PlayerInfo/Player";
6783

84+
[JsonProperty]
85+
public string Title = string.Empty;
6886

87+
[JsonProperty]
88+
public int Experience;
89+
}
90+
```
91+
92+
Точно также для инвентаря игрока я создаю *PlayerInventorySO*, где храню список ссылок на айтемы игрока (каждый айтем представляет собой ссылку на статичный SO из SODatabase).
93+
94+
```csharp
95+
public class PlayerInventorySO : DataNode
96+
{
97+
public static string Path => "PlayerInfo/PlayerInventory";
98+
99+
[JsonProperty]
100+
public List<ItemSO> Items = new List<ItemSO>();
101+
}
102+
```
103+
104+
Бывают наполовину статические, наполовину динамические данные - например, квесты. Возможно, это не лучший подход, но я прямо в моделях *QuestSO* со статической информацией о квестах (название, описание, цели и т.д.) храню динамическую информацию по прогрессу в этом квесте. Таким образом гейм-дизайнер в одном инспекторе видит всю инфу о текущем состоянии квеста и его описание.
69105

106+
```csharp
107+
public class QuestNode : BaseNode
108+
{
109+
public static string Path = "QuestNodes";
110+
111+
//Editor
112+
public virtual string Title { get; } = string.Empty;
113+
114+
public virtual string Description { get; } = string.Empty;
115+
116+
public int TargetCount;
117+
118+
//Runtime
119+
[JsonProperty]
120+
private bool finished;
121+
public bool Finished
122+
{
123+
get => finished;
124+
set => finished = value;
125+
}
126+
}
127+
```
128+
Вообще, лучще поля с JsonProperty делать приватными, чтобы SO их не сериализовал.
129+
Доступ к этим данным выглядит следующим образом
130+
```csharp
131+
var playerSO = SODatabase.GetModel<PlayerSO>(PlayerSO.Path);
132+
var playerInventorySO = SODatabase.GetModel<PlayerInventorySO>(PlayerInventorySO.Path);
133+
var questNodes = SODatabase.GetModels<QuestNode>(QuestNode.Path, true);
134+
```
70135

136+
### Текущее состояние библиотеки
71137

138+
В продакшене несколько лет использовался прообраз этой библиотеки - в ней аналогичное окно-проводник для создания/редактирования моделей, которые содержали статичные и динамические данные, но все эти модели не использовали SO, а были целиком в json. Из-за этого для каждой модели приходилось писать свой эдитор вручную, ссылки моделей друг на друга и игровые ассеты(спрайты и т.д.) делались довольно неудобными способами. Переход на SO совершён в прошлом году, и пока всего одна игра с SODatabase ушла в релиз, но в ней не использовались Addressables.
72139

140+
На addressables я перешёл совсем недавно для использования в текущем проекте (на разработку которой [я ищу в команду второго программиста](https://gamedev.ru/projects/forum/?id=254030) в партнёры). В данный момент идёт активное допиливание этой библиотеки под нужды этой игры.
73141

142+
Библиотека лежит в открытом доступе на github. Написана с использованием Nullable из c# 8, соответственно требует Unity 2020.1.4 в качестве минимальной версиии.
74143

144+
[https://github.com/NuclearBand/UnityScriptableObjectDatabase](https://github.com/NuclearBand/UnityScriptableObjectDatabase)

0 commit comments

Comments
 (0)