Code Monkey home page Code Monkey logo

bakingsheet's Introduction

Nuget GitHub release (latest by date) openupm GitHub Discord

BakingSheet 🍞

Easy datasheet management for C# and Unity. Supports Excel, Google Sheet, JSON and CSV format. It has been used for several mobile games that released on Google Play and AppStore.

Table of Contents

Concept

Throughout all stage of game development, you'll need to deal with various data. Characters, stats, stages, currencies and so on! If you're using Unity, scriptable object and inspector is not good enough for mass edition and lacks powerful features like functions or fill up. With BakingSheet your designers can use existing spreadsheet editor, while you, the programmer, can directly use C# object without messy parsing logics or code generations.

Let's say your team is making a RPG game. Your game has 100 characters and 10 stats for each character. If your team use Unity's scriptable object, designers will have to spend lots of time adding and editing from Unity inspector. And after setup what if you need mass edit, like to double ATK stat of all characters? Will you go through all characters with Unity inspector, or make Editor script for every time mass edit is required? With BakingSheet, designers can work easily with spreadsheet functions and fill ups without programmer help!

Concept

BakingSheet's core concept is controlling datasheet schema from C# code, make things flexible while supporting multiple sources like Excel files or Google sheets. You can think it as datasheet version of ORM. Also, you won't have to include source Excel files or parsing libraries for production builds. BakingSheet supports JSON serialization by default, you can ship your build with JSON or your custom format.

BakingSheet's basic workflow is like this:

  1. Programmers make C# schema that represents Datasheet. (They can provide sample Excel files or Google Sheet with headers.)
  2. Designers fill up the Datasheet, using any powerful functions and features of spreadsheet.
  3. Edit-time script converts Datasheet to JSON (or any custom format) with your C# schema and validates data.
  4. Runtime script reads from JSON (or any custom format) with your C# schema.
  5. Your business logic directly uses C# instance of your schema.
  6. Profit!

Don't trust me that it's better than using ScriptableObject? You might change your mind if you see how famous SuperCell ships their games with CSV, like Clash Royale or Brawl Stars. Though of course, their games aren't made with Unity, still a very good example to show how you can utilize spreadsheet!

Sample1 Sample2

Features

Install

For C# projects or server, download with NuGet.

For Unity projects, add git package from Package Manager.

https://github.com/cathei/BakingSheet.git?path=UnityProject/Packages/com.cathei.bakingsheet#v4.1.2

Or install it via OpenUPM.

openupm add com.cathei.bakingsheet

Sample .unitypackage is available in releases. (Main package should be installed first.)

If you are planning to use StreamingAssets folder on Android, install BetterStreamingAssets as well.

Need help?

Before you start, we want to mention that if you have problem or need help, you can always ask directly on Discord Channel!

Contribution

We appreciate any contribution. Please create issue for bugs or feature requests. Any contribution to feature, test case, or documentation through pull requests are welcome! Any blog posts, articles, shares about this project will be greatful!

First Step

BakingSheet manages datasheet schema as C# code. Sheet class represents a table and SheetRow class represents a record. Below is example content of file Consumables page in MySheets.xlsx. Also, any column starts with $ will be considered as comment and ignored.

Plain Sample

Markdown version
Id Name Price $Comment
LVUP_001 Warrior's Shield 10000 Warrior Lv up material
LVUP_002 Mage's Staff 10000 Mage Lv up material
LVUP_003 Assassin's Dagger 10000 Assassin Lv up material
POTION_001 Health Potion 30 Heal 20 Hp
POTION_002 Mana Potion 50 Heal 20 Mp

Code below is corresponding BakingSheet class.

public class ConsumableSheet : Sheet<ConsumableSheet.Row>
{
    public class Row : SheetRow
    {
        // use name of matching column
        public string Name { get; private set; }
        public int Price { get; private set; }
    }
}

You can see there are two classes, ConsumableSheet and ConsumableSheet.Row. Each represents a page of sheet and a single row. ConsumableSheet is surrounding Row class (It is not forced but recommended convention). Important part is they will inherit from Sheet<TRow> and SheetRow.

Id column is mandatory, so it is already defined in base SheetRow class. Id is string by default, but you can change type. See this section to use non-string type for Id.

To represent collection of sheets, a document, let's create SheetContainer class inherits from SheetContainerBase.

public class SheetContainer : SheetContainerBase
{
    public SheetContainer(Microsoft.Extensions.Logging.ILogger logger) : base(logger) {}

    // property name matches with corresponding sheet name
    // for .xlsx or google sheet, **property name matches with the name of sheet tab in workbook**
    // for .csv or .json, **property name matches with the name of file**
    public ConsumableSheet Consumables { get; private set; }

    // add other sheets as you extend your project
    public CharacterSheet Characters { get; private set; }
}

You can add as many sheets you want as properties of your SheetContainer. This class is designed to be "fat", means single SheetContainer should contain all your sheets unless there is specific reason to partition your sheets. For example when you want to deploy some Sheet only exclusive to server program, you might want to partition ServerSheetContainer and ClientSheetContainer.

Supported Column Type

  • string
  • Numeric primitive types (int, long, float, double, and so on)
  • bool ("TRUE" or "FALSE")
  • Custom enum types
  • DateTime and TimeSpan
  • Cross-sheet reference (Sheet<>.Reference)
  • Nullable for any other supported value type (for example int?)
  • List<> and Dictionary<,>
  • Custom struct and class as nested column
  • Custom type converted with ValueConverter

Note
When using JsonConverter, enum is serialized as string by default so you won't have issue when reordering them.

Converters

Converters are simple implementation import/export records from datasheet sources. These come as separated library, as it's user's decision to select datasheet source. User can have converting process, to convert datasheet to other format ahead of time and not include heavy converters in production applications.

BakingSheet supports four basic converters. They're included in Unity package as well.

Package Name Format Supports Import Supports Export
BakingSheet.Converters.Excel Microsoft Excel O X
BakingSheet.Converters.Google Google Sheet O X
BakingSheet.Converters.Csv Comma-Separated Values (CSV) O O
BakingSheet.Converters.Json JSON O O
ScriptableObject Converter (Unity only) ScriptableObject O O (Read-only)

Below code shows how to convert .xlsx files from Excel/Files/Path directory.

// any ILogger will work, there is built-in UnityLogger
var logger = new UnityLogger();

// pass logger to receive logs
var sheetContainer = new SheetContainer(logger);

// create excel converter from path
var excelConverter = new ExcelSheetConverter("Excel/Files/Path");

// bake sheets from excel converter
await sheetContainer.Bake(excelConverter);

For Google Sheet, first create your service account through Google API Console. Then add it to your sheet with Viewer permission. Use Google credential for that service account to create converter. For detailed information about how to create service account and link to your sheet, see How to import from Google Sheet.

// replace with your Google sheet identifier
// https://developers.google.com/sheets/api/guides/concepts
string googleSheetId = "1iWMZVI4FgtGbig4EgPIun_BRbzp4ulqRIzINZQl-AFI";

// service account credential than can read the sheet you're converting
// this starts with { "type": "service_account", "project_id": ...
string googleCredential = File.ReadAllText("Some/Path/Credential.json");

var googleConverter = new GoogleSheetConverter(googleSheetId, googleCredential);

// bake sheets from google converter
await sheetContainer.Bake(googleConverter);

Save and Load Converted Datasheet

Below code shows how to load sheet from Excel and save as JSON. This typically happens through Unity Editor script or any pre-build time script.

// create excel converter from path
var excelConverter = new ExcelSheetConverter("Excel/Files/Path");

// create json converter from path
var jsonConverter = new JsonSheetConverter("Json/Files/Path");

// convert from excel
await sheetContainer.Bake(excelConverter);

// save as json
await sheetContainer.Store(jsonConverter);

Then, for runtime you can load your data from JSON.

// create json converter from path
var jsonConverter = new JsonSheetConverter("Json/Files/Path");

// load from json
await sheetContainer.Bake(jsonConverter);

You can extend JsonSheetConverter to customize serialization process. For example encrypting data or prettifying JSON.

Note
For AOT platforms (iOS, Android), read about AOT Code Stripping.

Note
If you are using StreamingAssets on Android, also see Reading From StreamingAssets.

Accessing Row

Now you have SheetContainer loaded from your data, accessing to the row is fairly simple. Below code shows how to access specific ConsumableSheet.Row.

// same as sheetContainer.Consumables.Find("LVUP_003");
// returns null if no row found
var row = sheetContainer.Consumables["LVUP_003"];

// print "Assassin's dagger"
logger.LogInformation(row.Name);

Sheet<T> is KeyedCollection, you can loop through it and order is guaranteed to be as same as your spreadsheet. Plus of course you can use all benefits of IEnumerable<T>.

// loop through all rows and print their names
foreach (var row in sheetContainer.Consumables)
    logger.LogInformation(row.Name);

// loop through consumable ids that price over 5000
foreach (var consumableId in sheetContainer.Consumables.Where(row => row.Price > 5000).Select(row => row.Id))
    logger.LogInformation(consumableId);

Using List Column

List columns are used for simple array.

List Sample

Flat header
Id Name Monsters:1 Monsters:2 Monsters:3 Loots:1 Loots:2
DUNGEON001 Easy Field MONSTER001 POTION_001 LVUP_001
DUNGEON002 Expert Zone MONSTER001 MONSTER002 POTION_002 LVUP_002
DUNGEON003 Dragon’s Nest MONSTER003 MONSTER004 MONSTER005 LVUP_003
Split header
Id Name Monsters Loots
1 2 3 1 2
DUNGEON001 Easy Field MONSTER001 POTION_001 LVUP_001
DUNGEON002 Expert Zone MONSTER001 MONSTER002 POTION_002 LVUP_002
DUNGEON003 Dragon’s Nest MONSTER003 MONSTER004 MONSTER005 LVUP_003
public class DungeonSheet : Sheet<DungeonSheet.Row>
{
    public class Row : SheetRow
    {
        public string Name { get; private set; }

        // you can use any supported type as list
        // to know more about sheet reference types, see cross-sheet reference section
        public List<MonsterSheet.Reference> Monsters { get; private set; }
        public List<ConsumableSheet.Reference> Loots { get; private set; }
    }
}

Use it as simple as just including a column has type implmenting IList<T>. Since spreadsheet is designer's area, index on sheet is 1-based. So be aware when you access it from code.

Also you can pick between flat-header style(Monsters:1) and split-header style(Monsters, 1) as the example shows. There is no problem to mix-and-match or nest them.

Using Dictionary Column

Dictionary columns are used when key-based access of value is needed.

Dictionary Sample

Flat header
Id Name Texts:Greeting Texts:Purchasing Texts:Leaving
NPC001 Fat Baker Morning traveler! Thank you! Come again!
NPC002 Blacksmith G'day! Good choice. Take care.
NPC003 Potion Master What do you want? Take it already. Don't come again.
Split header
Id Name Texts
Greeting Purchasing Leaving
NPC001 Fat Baker Morning traveler! Thank you! Come again!
NPC002 Blacksmith G'day! Good choice. Take care.
NPC003 Potion Master What do you want? Take it already. Don't come again.
public enum Situation
{
    Greeting,
    Purchasing,
    Leaving
}

public class NpcSheet : Sheet<NpcSheet.Row>
{
    public class Row : SheetRow
    {
        public string Name { get; private set; }

        public Dictionary<Situation, string> Texts { get; private set; }
    }
}

Use it as simple as just including a column has type implmenting IDictionary<TKey, TValue>.

Using Nested Type Column

Nested type columns are used for complex structure.

Nested Type Sample

Flat header
Id Name Texts:Greeting Texts:Purchasing Texts:Leaving
NPC001 Fat Baker Morning traveler! Thank you! Come again!
NPC002 Blacksmith G'day! Good choice. Take care.
NPC003 Potion Master What do you want? Take it already. Don't come again.
Split header
Id Name Texts
Greeting Purchasing Leaving
NPC001 Fat Baker Morning traveler! Thank you! Come again!
NPC002 Blacksmith G'day! Good choice. Take care.
NPC003 Potion Master What do you want? Take it already. Don't come again.
public struct SituationText
{
    public string Greeting { get; private set; }
    public string Purchasing { get; private set; }
    public string Leaving { get; private set; }
}

public class NpcSheet : Sheet<NpcSheet.Row>
{
    public class Row : SheetRow
    {
        public string Name { get; private set; }

        public SituationText Texts { get; private set; }
    }
}

As you see, content of the datasheet is just same as when using Dictionary column. The data type of column determines how BakingSheet reads the column.

Using Row Array

Row arrays are used for 2-dimentional structure. Below is example content of file Heroes.xlsx.

Row Array Sample

Markdown version
Id Name Strength Inteligence Vitality StatMultiplier RequiredExp RequiredMaterial
HERO001 Warrior 100 80 140 1 0
1.2 10
1.4 20
1.6 40
2 100 LVUP_001
HERO002 Mage 60 160 80 1 0
1.2 10
1.4 20
1.6 40
2 100 LVUP_002
HERO003 Assassin 140 100 80 1 0
1.2 10
1.4 20
1.6 40
2 100 LVUP_003

Rows without Id is considered as part of previous row. You can merge the non-array cells to make it visually intuitive. Below corresponding code shows how to define row arrays.

public class HeroSheet : Sheet<HeroSheet.Row>
{
    public class Row : SheetRowArray<Elem>
    {
        public string Name { get; private set; }

        public int Strength { get; private set; }
        public int Inteligence { get; private set; }
        public int Vitality { get; private set; }

        public Elem GetLevel(int level)
        {
            // Level 1 would be index 0
            return this[level - 1];
        }

        // Max level would be count of elements
        public int MaxLevel => Count;
    }

    public class Elem : SheetRowElem
    {
        public float StatMultiplier { get; private set; }
        public int RequiredExp { get; private set; }
        public string RequiredMaterial { get; private set; }
    }
}

SheetRowArray<TElem> implements IEnumerable<TElem>, indexer this[int] and Count property.

Note
It is worth mention you can use VerticalList<T> to cover the case you want to vertically extend your List<T> without pairing them as Elem. Though we recommend to split the sheet in that case if possible.

Using Cross-Sheet Reference

Below code shows how to replace string RequiredMaterial to ConsumableSheet.Reference RequiredMaterial to add extra reliablity. Sheet<TKey, TRow>.Reference type is serialized as TKey, and verifies that row with same id exists in the sheet.

public class HeroSheet : Sheet<HeroSheet.Row>
{
    public class Row : SheetRowArray<Elem>
    {
        // ...
    }

    public class Elem : SheetRowElem
    {
        public float StatMultiplier { get; private set; }
        public int RequiredExp { get; private set; }
        public ConsumableSheet.Reference RequiredMaterial { get; private set; }
    }
}
public class SheetContainer : SheetContainerBase
{
    // ...

    // use name of each matching sheet name from source
    public HeroSheet Heroes { get; private set; }
    public ConsumableSheet Consumables { get; private set; }
}

Both ConsumableSheet and HeroSheet must be the properties on same SheetContainer class to reference each other's row.

Now, not only error message will pop up when RequiredMaterial doesn't exist in SheetContainer.Consumables, you can access ConsumableSheet.Row directly through it.

var heroRow = sheetContainer.Heroes["HERO001"];

// LVUP_001 from Consumables sheet
var consumableRow = heroRow.GetLevel(5).RequiredMaterial.Ref;

// print "Warrior's Shield"
logger.LogInformation(consumableRow.Name);

Using Non-String Column as Id

Any type can be used value can be also used as Id. This is possible as passing type argument to generic class SheetRow<TKey> and Sheet<TKey, TRow>. Below is example content of file Contstants.xlsx.

Const Sample

Markdown version
Id Value
ServerAddress https://github.com/cathei/BakingSheet
InitialGold 1000
CriticalChance 0.1

Below code shows how to use enumeration type as Id.

public enum GameConstant
{
    ServerAddress,
    InitialGold,
    CriticalChance,
}

public class ConstantSheet : Sheet<GameConstant, ConstantSheet.Row>
{
    public class Row : SheetRow<GameConstant>
    {
        public string Value { get; private set; }
    }
}

Using Post Load Hook

You can override PostLoad method of Sheet, SheetRow or SheetRowElem to execute post load process.

Below code shows how to convert loaded sheet value dynamically.

public class ConstantSheet : Sheet<GameConstant, ConstantSheet.Row>
{
    public class Row : SheetRow<GameConstant>
    {
        public string Value { get; private set; }

        private int valueInt;
        public int ValueInt => valueInt;

        private float valueFloat;
        public float ValueFloat => valueFloat;

        public override void PostLoad(SheetConvertingContext context)
        {
            base.PostLoad(context);

            int.TryParse(Value, out valueInt);
            float.TryParse(Value, out valueFloat);
        }
    }

    public string GetString(GameConstant key)
    {
        return Find(key).Value;
    }

    public int GetInt(GameConstant key)
    {
        return Find(key).ValueInt;
    }

    public float GetFloat(GameConstant key)
    {
        return Find(key).ValueFloat;
    }
}

Note
Properties without setter are not serialized. Alternatively you can use [NonSerialized] attribute.

Using AssetPostProcessor to Automate Converting

For Excel and CSV, you could set up AssetPostProcessor to automate converting process. Recommended practice is keeping both source .xlsx and .csv files alongside with destination .json files in your version control system. For Google Sheet, it is instead recommended to use custom MenuItem to convert into destination .json files that keeped in your version control.

The below is example source code that triggers when any .xlsx is changed, convert Excel sheet under Assets/Excel into .json under Assets/StreamingAssets/Json. You can customize this logic with your desired source and destination folder.

public class ExcelPostprocessor : AssetPostprocessor
{
    static async void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths)
    {
        // automatically run postprocessor if any excel file is imported
        string excelAsset = importedAssets.FirstOrDefault(x => x.EndsWith(".xlsx"));

        if (excelAsset != null)
        {
            // excel path as "Assets/Excel"
            var excelPath = Path.Combine(Application.dataPath, "Excel");

            // json path as "Assets/StreamingAssets/Json"
            var jsonPath = Path.Combine(Application.streamingAssetsPath, "Json");

            var logger = new UnityLogger();
            var sheetContainer = new SheetContainer(logger);

            // create excel converter from path
            var excelConverter = new ExcelSheetConverter(excelPath);

            // bake sheets from excel converter
            await sheetContainer.Bake(excelConverter);

            // create json converter to path
            var jsonConverter = new JsonSheetConverter(jsonPath);

            // save datasheet to streaming assets
            await sheetContainer.Store(jsonConverter);

            AssetDatabase.Refresh();

            Debug.Log("Excel sheet converted.");
        }
    }
}

About AOT Code Stripping (Unity)

If you are working on AOT (IL2CPP) environment, you would have option called Managed Stripping Level in your Player Settings. Since BakingSheet uses reflection, if you set stripping level Medium or High, the stripper might remove the code piece that required. Especially some property setters.

You can prevent this by either using Low stripping level, or adding own link.xml to preserve your sheet classes. The below is simplest example of link.xml. If you want to know more about it, see Unity's Documentation.

<?xml version="1.0" encoding="utf-8" ?>
<linker>
  <!--
    Replace `MyCompany.MyGame.Sheet` to your assembly to prevent Unity code stripping
  -->
  <assembly fullname="MyCompany.MyGame.Sheet" preserve="all"/>
</linker>

Optional Script Defining Symbols (Unity)

There is few optional symbols that can be defined for runtime usage. By default only JSON and ScriptableObject converters will be included to the build.

Symbol Effect
BAKINGSHEET_RUNTIME_GOOGLECONVERTER Include Google Converter to your build.
See also: Google Sheet Converter
BAKINGSHEET_RUNTIME_CSVCONVERTER Include CSV Converter to your build.
BAKINGSHEET_EXTERNAL_LOGGING_DLL Use external Microsoft.Extensions.Logging.Abstractions.dll. Useful if you already have same dll in your project for different dependency.

bakingsheet's People

Contributors

cathei avatar omega-ult avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

bakingsheet's Issues

Transpose Sheet Row and Column on import

I have a large list of settings (with variations for different builds) I'm importing from a Google Sheet but at the moment I don't see an easy way of transposing the data at the BakingSheet end so that the 'rows' of data are laid out horizontally and the 'columns' are on the left hand side, vertically.

I think a property in the Sheet definition would be required ie bool importTransposed = true

And then maybe just adding a toggle in the GetCell to reverse the SheetConverter - thing is that currently doesn't have access to the Sheet definition. Also the record/row count would be wrong as the last empty column should be used rather than the row.

Anyway - a feature suggestion as it came to mind really.

Currently possible to transpose in the Google Sheet using reference formula cells in the Google Sheet, hide the Import Sheet and only work on the referenced sheet - so it's not vital or anything. Or manually before import etc.

Support C# 9 Record Type

This will be interesting and well match with concept of BakingSheet

However there will be some complication like compatibility with Unity, version check, IsExternalInit issue, etc.

Consideration about link.xml

AOT code stripping could affect reflection. BakingSheet has been deployed link.xml with .unitypackage.

But this needs to be tested again since we moved to embedded package. Also, Unity does not support link.xml from pacakge.

At least I can say that when user trying to ship a mobile build with Google converter, they'll need link.xml.

Unity documentation

Can't make it work in WebGL builds

Hello! I tried to use this in the WebGL target with and without the BetterStreamingAssets package and I can't make it work.

I think it is probably because StreamingAssets in web need to be loaded using UnityWebRequest but I don't understand how to use that with the included Converters.

Is there a converter that I can just feed the string containing my csv and it just works? 🤔


Some more info: I don't see any network request to download the assets, The error I am getting is
Failed to find sheet: Constants when trying to run the line Debug.Log(Sheet.Constants.Count);

Add support for nested vertical list in other vertical list or dictionary

It is common in game developement to have nested vertical list to ease the workflow of game designers.

For example, an seasonal event CSV can have multiple packages, each package contains multiple rewards, and each reward can be a single item or multiple items. Splitting this kind of data into multiple CSV sheets would make it hard for the designer to track and compare their balancing between those data packages.

I wonder if it requires too much work to support nested vertical lists?

It's hard to promote the use of BakingSheet in my company. Currently I'm the only one use BakingSheet. But atm the game is in alpha stage and doesn't have complex data types. But every single product of my company will eventually need this kind of data at some point in the game's lifetime.

RawSheetImporter's ImportRow should wrap page.GetCell in the try block

I was implementing my own RawSheetImporter and ran into an issue where only about half of the pages in the sheet were being loaded. It turns out that GetCell() was throwing but because it's wrapped in a Task called from an Async MenuItem, nothing actually caught the exception.

I think the GetCell call in ImportRow should be part of the same try/catch that propertyMap.SetValue() is.

Make SheetContainerBase overridable, extendable

I'm working on a custom Editor tooling that depends on BakingSheet for intermediate steps. But I've found myself in need of overidding SheetContainerBase to support IProgress<float> in the async Bake and Store methods.

Could you make at least Bake and Store methods virtual to enable this use case?

One possible solution for better extending is extracting an ISheetContainer interface out of SheetContainerBase, and making SheetConvertingContext use this interface instead of the abstract class.

With either solutions, people can freely provide their own custom implementation of SheetContainerBase without the need of modifying your source code.

Add optional Script Defining Symbols for Google and CSV converter

Giving option to define a symbol to include Google and CSV converter on runtime.

BakingSheet has distributed as embedded package because this reason.
As intended modification can be done with script defining symbol instead, BakingSheet will be distributed with git or OpenUPM only.

Suggestions for clarifying setup instructions

Hi thanks for the asset, got it working after a (lot) of confusion. Main issue for me was the terminology... I was connecting to Google Sheets so 'Sheets' was used everywhere... it was not working for a long time until I finally spotted this:

"You can see there are two classes, ItemSheet and ItemSheet.Row. Each represents a page of sheet and a single row. ItemSheet is surrounding Row class (It is not forced but recommended convention). Important part is they will inherit from Sheet and SheetRow."

So in Google Sheets, the document is considered a Sheet, and then you can add Sheets to the Sheet.... !

The Page vs Sheet issue got me for a long time and it wasn't too clear even looking at the code.
Highlighting this comment would also help.

// property name matches with corresponding sheet name
// for .xlsx or google sheet, **it is name of the sheet tab in the workbook**
// for .csv or .json, it is name of the file
public ItemSheet Items { get; private set; }

Another issue that got me was the sample Items... so when I referenced the container.Items it looked a lot like a List item .... not a SheetPage.Rows. Finally figured it was container.SheetPage.Row.

So maybe changing the example from Items to anything else like Spells or Animals etc would make it clearer as to the abstract class language and the model language.

com.unity.nuget.newtonsoft-json 3.0.2/Newtonsoft.Json 13.0.1 causing dependency version mismatch when importing .unitypackage

Hello,

Thanks for developing this awesome package 😄 Unity installs the 3.0.2 version of their JSON package, which uses Newtonsoft.Json 13.0.1, even though this package depends on the 2.0 one. I imagine this is because we're using 2021 LTS, mainly for its new features and improved Android stability.

I am unable to rollback to the previous version and it seems this was an informed decision on Unity's side.. It seems you can manually install 2.0.0 from Package Manager > Add package by name, or edit Packages/manifest.json manually on Unity versions that don't have that option. However, this might be an issue for people who use Unity's Version Control package as it depends on Unity's Newtonsoft Json package. Unity isn't good at dealing with different versions of the same package coexisting.

I managed to get rid of the errors by upgrading all the Google.Apis.* packages to their latest version while using the 3.0.2 version. As for future releases, I can think of several solutions but I'm unsure which one would be the best course of action.

  1. Upgrade com.unity.nuget.newtonsoft-json dependency from 2.0 to 3.0
  2. Keep the old version but add a reminder to the readme that you have to install the 2.0.0 version if Unity installs 3.0+ manually. Also maybe explain that you need to download the latest versions of the DLLs yourself for 3.0 support.
  3. Release multiple versions of the .unitypackage, one supporting com.unity.nuget.newtonsoft-json 2.0 and the other 3.0

I feel like the first option makes the most sense, as the 2.0 docs says it's meant for internal development but the 3.0 docs don't, but I'd love to hear your opinion on it too.

Support partial sheet

Support partial sheet across multiple spreadsheet files. To be consistent, optional sheet subtitle convention Name.Subname may be supported as well. Subname is metadata and should be unique for same name if used.

Additional thoughts

  • Most Spreadsheet tools do not support page with same name in one file, subname can be a remedy.
  • The ordering can be preserved by sorting subnames. If no subname provided then order cannot be guaranteed.
  • Partial sheets can have different combination of columns as long as they are defined.
  • Row id in all partial sheets with same name must be unique.

Built-in Addressable Assets Verifier

We could use versionDefines of asmdef to make it only enabled when Addressable Assets package is included

Until this get included, user can define their own.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.