1"use strict";(globalThis.webpackChunksoft_kitty_docs=globalThis.webpackChunksoft_kitty_docs||[]).push([[5505],{7822(e,n,i){i.r(n),i.d(n,{assets:()=>o,contentTitle:()=>l,default:()=>h,frontMatter:()=>r,metadata:()=>a,toc:()=>c});const a=JSON.parse('{"id":"core/SaveLoad","title":"Save & Load System","description":"The [EntityManagerObject] provides a flexible and lightweight save/load system based on JSON serialization. It is designed to handle large numbers of [Entities] and custom gameplay data in a simple and extensible way.","source":"@site/docs/core/SaveLoad.md","sourceDirName":"core","slug":"/core/SaveLoad","permalink":"/docs/core/SaveLoad","draft":false,"unlisted":false,"tags":[],"version":"current","frontMatter":{"title":"Save & Load System"},"sidebar":"docs","previous":{"title":"SoftKitty.CustomData","permalink":"/docs/core/CustomData"},"next":{"title":"Overview","permalink":"/docs/master-combat-core/overview"}}');var s=i(4848),t=i(8453);const r={title:"Save & Load System"},l=void 0,o={},c=[{value:"Basic Save Architecture",id:"basic-save-architecture",level:3},{value:"Creating Custom Save Data",id:"creating-custom-save-data",level:3},{value:"Saving Data (Multiple Save Slots)",id:"saving-data-multiple-save-slots",level:3},{value:"Loading Data",id:"loading-data",level:3},{value:"Managing Save Slots",id:"managing-save-slots",level:3},{value:"EntityManagerObject API Reference",id:"entitymanagerobject-api-reference",level:3},{value:"Saving & Loading",id:"saving--loading",level:4},{value:"Full Save System",id:"full-save-system",level:4},{value:"Single Entity Operations",id:"single-entity-operations",level:4}
1,{value:"Performance Optimization (Dirty Flag)",id:"performance-optimization-dirty-flag",level:3}];function d(e){const n={a:"a",code:"code",em:"em",h3:"h3",h4:"h4",hr:"hr",li:"li",ol:"ol",p:"p",pre:"pre",strong:"strong",ul:"ul",...(0,t.R)(),...e.components};return(0,s.jsxs)(s.Fragment,{children:[(0,s.jsxs)(n.p,{children:["The ",(0,s.jsx)(n.a,{href:"/docs/core/entities/EntityManagerObject",children:"EntityManagerObject"})," provides a flexible and lightweight save/load system based on ",(0,s.jsx)(n.code,{children:"JSON"})," serialization. It is designed to handle large numbers of ",(0,s.jsx)(n.a,{href:"/docs/core/entities/Entity",children:"Entities"})," and custom gameplay data in a simple and extensible way."]}),"\n",(0,s.jsx)(n.p,{children:"This page explains how to implement:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"Multiple save slots"}),"\n",(0,s.jsx)(n.li,{children:"Custom game save data"}),"\n",(0,s.jsx)(n.li,{children:"Entity Manager API usage"}),"\n",(0,s.jsx)(n.li,{children:"Persistent save slot selection"}),"\n",(0,s.jsx)(n.li,{children:"Dirty flag optimization"}),"\n"]}),"\n",(0,s.jsx)(n.hr,{}),"\n",(0,s.jsx)(n.h3,{id:"basic-save-architecture",children:"Basic Save Architecture"}),"\n",(0,s.jsx)(n.p,{children:"The save system is split into two parts:"}),"\n",(0,s.jsxs)(n.ol,{children:["\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.strong,{children:"Entity Manager Data"}),"\r\nHandled automatically by the system, including:"]}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:["Entity ",(0,s.jsx)(n.a,{href:"/docs/core/attributes/Attribute",children:"Attribute"}),"s"]}),"\n",(0,s.jsxs)(n.li,{children:["Entity ",(0,s.jsx)(n.a,{href:"/docs/core/CustomData",children:"CustomData"})]}),"\n",(0,s.jsx)(n.li,{children:"Inventory Data/Character Customization Data/etc"}),"\n"]}),"\n"]}),"\n",(0,s.jsxs)(n.li,{children:["\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.strong,{children:"Extra Game Data (User-defined)"}),"\r\nUsed for anything outside entity data, such as:"]}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"Player progression"}),"\n",(0,s.jsx)(n.li,{children:"Quest state"}),"\n",(0,s.jsx)(n.li,{children:"World flags"}),"\n",(0,s.jsx)(n.li,{children:"Settings"}),"\n",(0,s.jsx)(n.li,{children:"Save slot metadata\r\nYou define this using a serializable class."}),"\n"]}),"\n"]}),"\n"]}),"\n",(0,s.jsx)(n.hr,{}),"\n",(0,s.jsx)(n.h3,{id:"creating-custom-save-data",children:"Creating Custom Save Data"}),"\n",(0,s.jsxs)(n.p,{children:["To store additional game-specific information, create a ",(0,s.jsx)(n.code,{children:"Serializable"})," class:"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-csharp",children:"[System.Serializable]\r\npublic class GameSaveData\r\n{\r\n public int playerLevel;\r\n public float playTime;\r\n public string currentScene;\r\n public Vector3 playerPosition;\r\n}\n"})}),"\n",(0,s.jsxs)(n.p,{children:["This data will be stored alongside ",(0,s.jsx)(n.a,{href:"/docs/core/entities/Entity",children:"Entity"})," data as ",(0,s.jsx)(n.code,{children:"JSON"}),"."]}),"\n",(0,s.jsx)(n.hr,{}),"\n",(0,s.jsx)(n.h3,{id:"saving-data-multiple-save-slots",children:"Saving Data (Multiple Save Slots)"}),"\n",(0,s.jsx)(n.p,{children:"You can support multiple save slots by using a save index:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-csharp",children:'public void Save(GameSaveData _data, int _saveIndex)\r\n{\r\n string _extraString = JsonUtility.ToJson(_data);\r\n\r\n GameManager.EntityManagerData.Save(\r\n GameManager.GetFullSavePath("UserSave" + _saveIndex.ToString() + ".sav"),\r\n _extraString\r\n );\r\n}\n'})}),"\n",(0,s.jsx)(n.p,{children:"What happens here:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"GameSaveData"})," \u2192 converted to ",(0,s.jsx)(n.code,{children:"JSON"})]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.a,{href:"/docs/core/entities/EntityManagerObject",children:"EntityManagerObject"})," data is saved automatically"]}),"\n",(0,s.jsx)(n.li,{children:"Both are stored in the same save file"}),"\n"]}),"\n",(0,s.jsx)(n.hr,{}),"\n",(0,s.jsx)(n.h3,{id:"loading-data",children:"Loading Data"}),"\n",(0,s.jsx)(n.p,{children:"To load a save slot:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-csharp",children:'public void Load(int _saveIndex)\r\n{\r\n string _extraString = GameManager.Entity
1ManagerData.Load(\r\n GameManager.GetFullSavePath("UserSave" + _saveIndex.ToString() + ".sav")\r\n );\r\n\r\n GameSaveData _data =\r\n (GameSaveData)JsonUtility.FromJson(_extraString, typeof(GameSaveData));\r\n\r\n // Apply loaded data here\r\n}\n'})}),"\n",(0,s.jsx)(n.p,{children:"After loading:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.a,{href:"/docs/core/entities/EntityManagerObject",children:"EntityManagerObject"})," system restores all ",(0,s.jsx)(n.a,{href:"/docs/core/entities/Entity",children:"Entities"})," automatically"]}),"\n",(0,s.jsx)(n.li,{children:"You restore your custom game state manually"}),"\n"]}),"\n",(0,s.jsx)(n.hr,{}),"\n",(0,s.jsx)(n.h3,{id:"managing-save-slots",children:"Managing Save Slots"}),"\n",(0,s.jsx)(n.p,{children:"A common approach is to store the last selected save slot:"}),"\n",(0,s.jsx)(n.p,{children:"Example workflow:"}),"\n",(0,s.jsxs)(n.ol,{children:["\n",(0,s.jsx)(n.li,{children:"Player selects Save Slot (0, 1, 2...)"}),"\n",(0,s.jsx)(n.li,{children:"Store selected index in a separate config file or PlayerPrefs"}),"\n",(0,s.jsxs)(n.li,{children:["On game start:","\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"Load last used save index"}),"\n",(0,s.jsx)(n.li,{children:"Call Load(saveIndex)"}),"\n"]}),"\n"]}),"\n"]}),"\n",(0,s.jsxs)(n.p,{children:["This allows seamless ",(0,s.jsx)(n.em,{children:(0,s.jsx)(n.strong,{children:"Continue Game"})})," functionality."]}),"\n",(0,s.jsx)(n.hr,{}),"\n",(0,s.jsxs)(n.h3,{id:"entitymanagerobject-api-reference",children:[(0,s.jsx)(n.a,{href:"/docs/core/entities/EntityManagerObject",children:"EntityManagerObject"})," API Reference"]}),"\n",(0,s.jsx)(n.h4,{id:"saving--loading",children:"Saving & Loading"}),"\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.code,{children:"public void ToJson(string _extraInfo)"}),"\r\n",(0,s.jsx)(n.em,{children:"Converts all entities and extra info into a JSON string."})]}),"\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.code,{children:"public string FromJson(string _json)"}),"\r\n",(0,s.jsx)(n.em,{children:"Loads entity data from JSON and returns stored extra info."})]}),"\n",(0,s.jsx)(n.h4,{id:"full-save-system",children:"Full Save System"}),"\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.code,{children:"public void Save(string _path, string _extraInfo)"}),"\r\n",(0,s.jsx)(n.em,{children:"Saves all entities + extra data to a file."})]}),"\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.code,{children:"public string Load(string _path)"}),"\r\n",(0,s.jsx)(n.em,{children:"Loads all entities + extra data from a file."})]}),"\n",(0,s.jsx)(n.h4,{id:"single-entity-operations",children:"Single Entity Operations"}),"\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.code,{children:"public string SingleEntityToJson(string _uid)"}),"\r\n",(0,s.jsx)(n.em,{children:"Returns JSON for a single entity."})]}),"\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.code,{children:"public void SaveSingleEntity(string _uid, string _path)"}),"\r\n",(0,s.jsx)(n.em,{children:"Saves a single entity to a file."})]}),"\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.code,{children:"public void LoadSingleEntity(string _uid, string _path)"}),"\r\n",(0,s.jsx)(n.em,{children:"Loads a single entity from a file."})]}),"\n",(0,s.jsx)(n.hr,{}),"\n",(0,s.jsx)(n.h3,{id:"performance-optimization-dirty-flag",children:"Performance Optimization (Dirty Flag)"}),"\n",(0,s.jsxs)(n.p,{children:["To improve performance in large projects, the ",(0,s.jsx)(n.a,{href:"/docs/core/entities/Entity",children:"Entity"})," class includes a dirty flag:"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-csharp",children:"public bool isDirty;\n"})}),"\n",(0,s.jsx)(n.p,{children:"How it works:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"isDirty = true"})," \u2192 ",(0,s.jsx)(n.a,{href:"/docs/core/entities/Entity",children:"Entity"})," has been modified"]}),"\n",(0,s.jsxs)(n.li,{children:["Your save logic can ignore unchanged ",(0,s.jsx)(n.a,{href:"/docs/core/entities/Entity",children:"Entities"})]}),"\n",(0,s.jsx)(n.li,{children:"Improves performance for large worlds"}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"This reduces unnecessary serialization work in large projects."}),"\n",(0,s.jsx)(n.hr,{})]})}function h(e={}){const{wrapper:n}={...(0,t.R)(),...e.components};return n?(0,s.jsx)(n,{...e,children:(0,s.jsx)(d,{...e})}):d(e)}},8453(e,n,i){i.d(n,{R:()=>r,x:()=>l});var a=i(6540);const s={},t=a.createContext(s);function r(e){const n=a.useContext(t);return a.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function l(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(s):e.components||s:r(e.components),a.createElement(t.Provider,{value:n},e.children)}}}]);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.