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

Commit 8009410

Browse files
committed
Merge remote-tracking branch 'origin/master'
2 parents af8d865 + 772c389 commit 8009410

File tree

5 files changed

+144
-12
lines changed

5 files changed

+144
-12
lines changed

Packages/com.nuclearband.sodatabase/CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
## [1.14.1](https://github.com/NuclearBand/UnityScriptableObjectDatabase/compare/v1.14.0...v1.14.1) (2020-09-27)
2+
3+
4+
### Bug Fixes
5+
6+
* JsonConvert.PopulateObject for lists ([54ad94c](https://github.com/NuclearBand/UnityScriptableObjectDatabase/commit/54ad94ca70dde05214a1547c8480aea63c3c212f))
7+
18
# [1.14.0](https://github.com/NuclearBand/UnityScriptableObjectDatabase/compare/v1.13.1...v1.14.0) (2020-09-22)
29

310

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
### Introduction
2+
3+
Each game has data that game-designers work with. In RPG there is a database of items, in match-3 - the cost in the crystals of tools from the store, in action - hit points, for which medical kit heals.
4+
5+
There are many ways to store such data - someone stores it in tables, in XML or JSON files that edit with their own tools. Unity provides its own way - Scriptable Objects (SO), which I like because you don't have to write your own editor to visualize them, it's easy to make links to the game's assets and to each other, and with Addressables this data can be easily and conveniently stored off-game and updated separately.
6+
7+
In this article I would like to talk about my SODatabase library, with which you can conveniently create, edit and use in the game (edit and serialize) scriptable objects.
8+
<cut />
9+
### Create and edit SO
10+
11+
I create and edit SOs in a separate window similar to the project windows with an inspector - on the left there is a folder tree (the folder where all SOs are located - the group in addressables), and on the right there is a selected SO inspector.
12+
13+
![Interface](https://habrastorage.org/webt/g-/at/3_/g-at3_ewbaje3clfmpwy2fdv9fw.png).
14+
15+
To draw such a WindowEditor, I use the library [Odin Inspector](https://odininspector.com/). In addition, I use serialization for SO from this library - it extends the standard Unity serialization, allowing to store polymorphic classes, deep nesting, references to classes.
16+
17+
![Create SO](https://habrastorage.org/webt/kw/8z/6k/kw8z6kpamvb8eq2k5mkissvo4m8.png)
18+
19+
Creation of new SO is done by pressing the button in this window - there you need to select the type of the desired model, and it is created in the folder. In order for the SO type to appear in this window as an option, SO must be inherited from DataNode, which has only one additional field to ScriptableObject.
20+
```csharp
21+
public string FullPath { get; }
22+
```
23+
This is the path to a given SO, by which it can be accessed at runtime
24+
25+
### Access to SO in the game
26+
27+
Usually, you need to either get some specific model, for example, SO with a list of settings of some window, or a set of models from a folder - for example, a list of items, where the model of each item represents a separate SO.
28+
For this purpose, SODatabase has two main methods that return either the entire list of models from the desired folder or a specific model from a folder with a certain name.
29+
30+
```csharp
31+
public static T GetModel<T>(string path) where T : DataNode
32+
33+
public static List<T> GetModels<T>(string path, bool includeSubFolders = false) where T : DataNode
34+
```
35+
36+
Once at the beginning of the game, before requesting the SODatabase models, you need to initialize to update and download data from Addressables.
37+
38+
### Load and save
39+
40+
One of the disadvantages of ScriptableObject in comparison with storing data with serialization in its own format is that it is not possible to change data in SO and save it at runtime. That is, in fact, ScriptableObject is designed to store static data. But game state needs to be loaded and saved, and I implement this through the same SO from the database.
41+
42+
Perhaps this is not an idiomatic way to combine the database of static models of the game with the loading and saving of dynamic data, but in my experience there has never been a case when it would create some inconvenience, but there are some tangible advantages. For example, with the help of the same inspectors, you can watch the game data in the editor and change them. You can conveniently load player saves, look at their contents and edit them in the editor without using any external utilities or your own editors to render XML or other formats.
43+
44+
I achieve this by serializing dynamic fields in ScriptableObject with JSON.
45+
46+
The *DataNode class *- the parent class of all SO stored in *SODatabase* is marked as
47+
```csharp
48+
[JsonObject(MemberSerialization.OptIn, IsReference = true)].
49+
```
50+
and all its *JsonProperty* are serialized into a save.txt file when you save the game. Accordingly, during the initialization of *SODatabase*, besides the request for addressables change data, *JsonConvert.PopulateObject* for each dynamic model from *SODatabase* is executed using data from this file.
51+
52+
For this to work smoothly, I serialize the SO references (which can be dynamic fields marked as JsonProperty) into a path line and then deserialize them back into the SO references at boot. There is a limitation - data on game assets cannot be dynamic. But it's not a fundamental constraint, I just haven't had a case when such dynamic data would be required yet, so I didn't implement a special serialization for such data.
53+
54+
### Examples
55+
56+
In a class-starter game initialization and data upload
57+
```csharp
58+
async void Awake()
59+
{
60+
SODatabase.InitAsync(null, null);
61+
await SODatabase.LoadAsync();
62+
}
63+
```
64+
and saving the state when you leave
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+
```
77+
78+
In RPG to store information about the player I directly create *PlayerSO*, in which only dynamic fields - the name, the number of explosions of the player, crystals, etc.. It's also a good practice in my opinion to create a static line with the path by which I store the model in SODatabase, so that I can access it at runtime.
79+
```csharp
80+
public class PlayerSO : DataNode
81+
{
82+
public static string Path => "PlayerInfo/Player";
83+
84+
[JsonProperty]
85+
public string Title = string.empty;
86+
87+
[JsonProperty]
88+
public int Experience;
89+
}
90+
```
91+
92+
Similarly, for the player inventory I create *PlayerInventorySO*, where I store a list of links to the player's items (each item is a link to a static SO from the 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+
There are half static, half dynamic data - for example, quests. This may not be the best approach, but I store dynamic information on progress in this quest right in *QuestSO* models with static information about quests (name, description, goals, etc.). Thus, a game-designer in one inspector sees all the information about the current state of the quest and its description.
105+
106+
```csharp
107+
public class QuestNode : DataNode
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+
In general, it's better to make the fields with JsonProperty private so that SO does not serialize them.
129+
The access to this data looks like this
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+
```

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

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -132,13 +132,3 @@ var playerSO = SODatabase.GetModel<PlayerSO>(PlayerSO.Path);
132132
var playerInventorySO = SODatabase.GetModel<PlayerInventorySO>(PlayerInventorySO.Path);
133133
var questNodes = SODatabase.GetModels<QuestNode>(QuestNode.Path, true);
134134
```
135-
136-
### Текущее состояние библиотеки
137-
138-
В продакшене несколько лет использовался прообраз этой библиотеки - в ней аналогичное окно-проводник для создания/редактирования моделей, которые содержали статичные и динамические данные, но все эти модели не использовали SO, а были целиком в json. Из-за этого для каждой модели приходилось писать свой эдитор вручную, ссылки моделей друг на друга и игровые ассеты(спрайты и т.д.) делались довольно неудобными способами. Переход на SO совершён в прошлом году, и пока всего одна игра с SODatabase ушла в релиз, но в ней не использовались Addressables.
139-
140-
На addressables я перешёл совсем недавно для использования в текущем проекте (на разработку которой [я ищу в команду второго программиста](https://gamedev.ru/projects/forum/?id=254030) в партнёры). В данный момент идёт активное допиливание этой библиотеки под нужды этой игры.
141-
142-
Библиотека лежит в открытом доступе на github. Написана с использованием Nullable из c# 8, соответственно требует Unity 2020.1.4 в качестве минимальной версиии.
143-
144-
[https://github.com/NuclearBand/UnityScriptableObjectDatabase](https://github.com/NuclearBand/UnityScriptableObjectDatabase)

Packages/com.nuclearband.sodatabase/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "com.nuclearband.sodatabase",
33
"displayName": "Scriptable Objeсt database",
4-
"version": "1.14.0",
4+
"version": "1.14.1",
55
"dependencies": {
66
"com.unity.addressables": "1.7.5",
77
"com.unity.nuget.newtonsoft-json": "2.0.0"

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,12 @@ Unity 2020.1.4 or later (C# 8)
77
## Installation
88
You can install via git url by adding this entry in your **manifest.json**
99
```
10-
"com.nuclearband.sodatabase": "https://github.com/Tr0sT/UnityScriptableObjectDatabase.git#upm"
10+
"com.nuclearband.sodatabase": "https://github.com/NuclearBand/UnityScriptableObjectDatabase.git#upm"
1111
```
1212
The library depends on [Odin Inspector](https://odininspector.com/)
1313

1414
## Documentation
15+
- [Documentation](https://github.com/NuclearBand/UnityScriptableObjectDatabase/blob/master/Packages/com.nuclearband.sodatabase/Documentation/Documentation.en.md)
1516
- [Документация](https://github.com/NuclearBand/UnityScriptableObjectDatabase/blob/master/Packages/com.nuclearband.sodatabase/Documentation/Documentation.ru.md)
1617

1718
## Contributing

0 commit comments

Comments
 (0)