ذخیره اطلاعات بازی به صورت حرفه ای با ScriptableObject
در این اموزش ما یاد میگیریم که چگونه اطلاعات بازی رو درون یک فایل ذخیره کنیم که هم برای استفاده مجدد ما بهینه باشه یعنی در صورت اضافه کردن متغییر جدید نیازی به تغییر و یا کد نویسی مجدد نداشته باشه و در هرکجای بازی فقط با یک فراخوانی متد قابل اجرا شدن باشه پس در این اموزش با ما باشید

اهمیت این مقاله
ذخیره اطلاعات در بازی سازی امری بسیار مهم بوده و از اهمیت بسیاری برخوردار است زیرا درامد تیم بازی سازی کاملا به این امر وابسته است , به عنوان مثال شما مدت یک سال از وقت و انرژی خود را صرف ساخت یک بازی پرطرفدار میکنید و طبیعتا نیاز به درامد ان دارید ولی به راحتی اطلاعات ذخیره شده شما لو میرود و نمی توانید از بازی خود کسب درامد کنید , معمولا اطلاعاتی که در بازی ذخیره میشوند معمولا شامل پول , طلا , انواع سلاح هستند , چند سال پیش درامد از بازی فقط شامل فروختن بازی میشد ولی طی این چندین سال این فرایند به صورت اتومات منسوخ شده و پرداخت درون برنامه ای جایگذین ان شده به این صورت که کاربر به بازی دسترسی کامل دارد ولی برای پیشرفت نیاز به خرید پول و … با استفاده از قابلیت پرداخت درون برنامه دارد , در مقاله ضد هک کردن بازی در مورد روش های هک شدن بازی و همچنین نحوه جلوگیری از ان را اموزش داده ام و از انجایی که موضوع ان مقاله نحوه ضد هک کردن و امنیت داده بود زیاد وارد بحث ذخیره اطلاعات در یونیتی نشدم با اینحال در این مقاله یاد میگیریم که چگونه بدون نیاز به پکیج خودمان یک سیستم برای ذخیره اطلاعات به صورت ابجکت داشته باشیم بریم برای اموزش
همچنین مقاله ضدهک کردن بازی رو هم میتوانید از این لینک مطالعه نمایید
ScriptableObject
اسکریپتیبل ابجکت یک ابزار بسیار قدرتمند و یک جایگذین مناسب برای SQL در یونیتی است , دوستانی که نمیدونن اس کیو ال چی هست یه توضیح کوتاه بدم که در واقع بر پایه ان میشود دیتا بیسی ساخت که مقادیر مختلفی رو در اون ذخیره کرد و از انجایی که یونیتی به صورت کامل ان را ساپورت نمیکند ( البته با استفاده از پکیج میشه ولی بنده تست نکردم یعنی نیازی بهش نداشتم ) در موقع ساخت دیتابیس شاید کمبود ان را احساس کنید ولی زمانی که کار با ScriptableObject ها رو یاد بگیرید کلا ان را فراموش خواهید کرد چون در ScriptableObject ما میتوانیم هر نوع داده ای را ذخیره کنیم و مدیریت ان هم بسیار اسان بوده است که در ادامه اموزش خواهیم دید.
ScriptableObject ها و بهینه سازی منابع
ایا استفاده از ScriptableObject باعث کند شدن بازی میشود ؟ جواب خیر نه تنها باعث افک فریم ریت بازی نمی شود بلکه موجب افزایش سرعت بازی میشود : زمانی که شما یک ارایه از نوع رشته را در کد خود استفاده میکنید موقع اجرای بازی تمامی رشته ها در حافظه موقت دوایس لود میشوند و باعث اشغال فضای ان میشوند که با استفاده از ScriptableObject ما تمام ارایه هایی که زیاد کاربرد ندارند و بیشتر برای ذخیره داده از ان ها استفاده میکنیم را درون یک ScriptableObject تعریف میکنیم و از انها به صورت یک است استفاده میکنیم و فقط زمانی که به ان نیاز داریم ان را لود میکنیم , این روش برای بازی های موبایل بسیار توصیه میشود
ScriptableObject یا PlayerPrefs
اگر فقط یک داده را ذخیره میکنید مانند مقدار پول یا الماس و یا … قطعا PlayerPrefs به صورت Obscured بهترین گزینه است برای درک بهتر Obscured ها این مقاله رو بخونید , اما اگر شما تعداد بسیار زیادی متغییر برای ذخیره سازی و بازیابی دارید بهترین روش استفاده از ScriptableObject است .
ساخت یک ScriptableObject ساده
قالب کلی یک ScriptableObject به شکل زیر است ابتدا یک کد جدید ایجاد کنید و سپس محتوای ان را به شکل زیر جایگذین کنید

using UnityEngine; public class DB : ScriptableObject { }
حالا کلاس ما از نوع ScriptableObject است متد های update و start معمولا در ScriptableObject بی مصرف هستند زیرا این متدها فراخوانی نمیشوند و علت ان هم این است که ما اسکریپت رو بر روی یک گیم ابجکت داخل صحنه اعمال نمیکنیم و از ان فقط به عنوان محلی برای ذخیره دیتاها استفاده میکنیم. کد بالا قالب یک ScriptableObject را نشان میدهد ولی با این حال کامل نیست ما نیاز داریم تا از روی ScriptableObject در قسمت پروژه ابجکت ساخته بشه و برای همین امر ما کد به ان اضافه میکنیم تا از طریق منوها قابل کنترل باشه
using UnityEngine; [CreateAssetMenu(fileName = "db", menuName = "My Game/New DB", order = 0)] public class DB : ScriptableObject { }
با استفاده از CreateAssetMenu ما یک منو در منوهای پرژه مون ایجاد کردیم که حالا اونو تست میکنیم

مشاهده میکنید که با کلیک بر روی منوی بالا یک فایل برای ما ساخته شد که فعلا برای ما کاربردی نداره و یا صرفا فقط یک فایله حالا برای اینکه کمی با کاربرد ان اشنا بشویم چند متغییر به ان اضافه میکنیم
ng UnityEngine; [CreateAssetMenu(fileName = "db", menuName = "My Game/New DB", order = 0)] public class DB : ScriptableObject { public int num; public float tm; public bool On; public new string name; }
حالا کد رو ذخیره میکنیم تا نتیجه رو ببینیم

میبینید که متغییرها به دیتابیس ما اضافه شده اند شما هرمتغییری را میتوانید به این شکل اضافه کنید و همچنین بعد از دوبلکیت کردن دیتا هم میبینید که داده های هر فایل با دیگری فرق داشته و شما میتوانید از این ویژگی نهایت استفاده رو بکنید, حال بریم برای اشنایی با کاربرد دیتابیسمون
using UnityEngine; public class controll : MonoBehaviour { [SerializeField] DB db; private void Start() { Debug.Log(db.name); Debug.Log(db.num); } }
میبینیم که با ایجاد یک متغییر از نوع DB که همان کد اسکریپتیبل ابجکت ما هست میتوانیم به تمام متغییرهای db دسترسی داشته باشیم حالا برای استفاده از متغییرهامون نیاز داریم تا با استفاده از درگ دروپ یونیتی فایل رو به اسکریپتمون معرفی کنیم مثل تصویر زیر

میبینیم که هرفایلی رو که نیاز داریم رو میتونیم در کد controll استفاده کنیم البته حواسم نبود و r رو برای کنترول ننوشتم ^_^ حالا فعلا اونو بیخیال , اگه از بازی تست بگیریم میبینیم که مقادیر خواسته شده رو برای ما پرینت میکنه و از طریق پنجره کنسول قابل مشاده است , یکی از ویژگی های اسکریپتیبل ابجکت نگه داشتن تغییرات انجام شده است به این گونه که وقتی درون محیط یونیتی ما یسری تغییرات رو بر روی متغییرهای دیتابیسمون اعمال میکنیم بعد از پاز کردن پلی پروژه هم تغییرات درون فایل ثبت شده میمونن , البته باید اشاره کنم که این تغغیرات فقط و فقط درون یونیتی ثبت میشن و وقتی از بازی خروجی بگیریم در موقع استارت شدن بازی همان مقادیری که اخرین بار در محیط ادیتور بازی تغییر یافته اند لود خواهند شد و تغییراتی که در موقع اجرای بازی انجام شده ذخیره نمی شوند , البته لازم به ذکره که موقع اجرا در یک دیوایس تا موقعی که بازی در حال اجرا است تمامی دیتا ها قابل دسترسی هستند یعنی اگر مقدار پیشفرض برای int a عدد 0 باشد و در صحنه یک برابر 11 باشد تا موقعی که بازی استاپ نشده در صحنه دو هم قابل دسترس است و مقدار ان برابر 11 خواهد بود و با خروج از بازی در هنگام لود شدن مقدار ان 0 خواهد بود , حال ما میخواهیم که تمامی محتوای فایل db رو در یک فایل ذخیره کنیم و موقع شروع بازی اخرین مقادیر ثبت شده رو لود کنه برای این کار از دو متد میسازیم
محتوای کلاس contoll رو به شکل زیر تغییر میدهیم دو متد میسازیم یکی برای ذخیره و یکی برای لود کردن مقادیر
using System; using System.IO; using System.Runtime.Serialization.Formatters.Binary; using System.Text; using UnityEngine; public class controll : MonoBehaviour { [SerializeField] DB db; [SerializeField] bool canLoad; private void Start() { if (canLoad) { Load(); } else { Save(); } Debug.Log(db.name); Debug.Log(db.num); } public void Save() { if (Directory.Exists(Application.persistentDataPath + "/save") == false) { Directory.CreateDirectory(Application.persistentDataPath + "/save"); } BinaryFormatter bf = new BinaryFormatter(); FileStream file = File.Create(Application.persistentDataPath + "/save/game.sav"); var Json = JsonUtility.ToJson(db); byte[] jsonToEncode = Encoding.UTF8.GetBytes(Json); string encodedJson = Convert.ToBase64String(jsonToEncode); bf.Serialize(file, encodedJson); file.Close(); Debug.Log("save"); } public void Load() { BinaryFormatter bf = new BinaryFormatter(); if (Directory.Exists(Application.persistentDataPath + "/save") == true) { if (File.Exists(Application.persistentDataPath + "/save/game.sav") == true) { FileStream file = File.Open(Application.persistentDataPath + "/save/game.sav", FileMode.Open); byte[] decodedBytes = Convert.FromBase64String((string)bf.Deserialize(file)); string decodedText = Encoding.UTF8.GetString(decodedBytes); JsonUtility.FromJsonOverwrite(decodedText, db); file.Close(); Debug.Log("Load"); } } } }
در این کد ما یک بولین ساختیم که در موقع شروع بازی بتوانیم کدمون رو تست کنیم پس میریم برای بررسی خط به خط کدها
در سطر 27 از طریق کلاس Directory ما چک میکنیم که ایا پوشه ای به نام save وجود دارد و اگر موجود نبود یکی ساخته بشود
در سطر 31 ما یک BinaryFormatter ایجاد میکنیم تا به وسیله ان بتوانیم دیتاها مون رو وارد و یا خارج کنیم (ادیت فایل db با رشته ها )
در سطر 32 با استفاده از کلاس FileStream یک فایل در داخل پوشه save ایجاد میکنیم با نام game.sav
در سطر 33 ما یک متغییر از نوع جیسون تعریف میکنیم و تنظیمات db ( همان مقادیر متغییرها ) رو داخل ان میریزیم
- نکته در این حالت مقادیر به صورت جیسون هستند اما اگر قبلا با Json یا xml کار کرده باشید متوجه خواهید شد که این نوع فایل ها از نوع txt بوده و به راحتی قابل ویرایش با یک ویرایشگر تکست هستند پس برای امن نگه داشتن مقادیر ذخیره شده ما انها را رمزنگاری میکنیم البته اگر اطلاعات ذخیره شده مهم نبودند میتوانید داده ها را رمزنگاری نکنید
در سطر 35 و 36 ما محتوای متغییر Json رو رمزنگاری میکنیم
در سطر 38 با استفاده از BinaryFormatter مقادیر کدگزاری شده رو درون فایل game.sav ذخیره میکنیم
در سطر 39 هم فایل رو میبندیم تا ذخیره بشه
محل ذخیره فایل هم در ویندوز در مسیر C:\Users\Administrator\AppData\LocalLow\yourPackageName میباشد
و در اندروید هم احتمالا در مسیر data یا android/data/packagename باید باشه
حالا میریم برای توضیح سطر به سطر متد load
سطر 46 تا 48 چک میکنه که ایا فایل و پوشه موجوده یا نه
سطر 50 حالت فایل رو به open تغییر میده تا بتونیم اون رو بخونیم
سطر 52 و 53 برای دیکد کردن فایل رمزنگاری شده اس
سط 56 و 57 مقادیر رو جایگزین کرده و فایل رو میبنده
شکل کلی فایل سیو در صورتی که رمزنگاری نشه به این شکله
ÿÿÿÿ -{"num":444,"tm":33.0,"On":true,"name":"test"}
همون طور که میبینید کاملا قابل خواندن و ویرایش هست و بعد از رمزنگاری به صورت زیر درمی اید
ÿÿÿÿ <eyJudW0iOjQ0NCwidG0iOjMzLjAsIk9uIjp0cnVlLCJuYW1lIjoidGVzdCJ9
که عمل خواندن و ویرایش آن را تقریبا غیرممکن میکند

در تصویر بالا ابتدا یه سری مقادیر به متغییرهای db میدهیم و تیک canload رو برمیداریم تا موقع اجرا بازی داده های ما ذخیره شوند و سپس یکبار بازی را اجرا میکنیم تا دیتا رو ذخیره کنه و بعد بازی رو استاپ میکنیم و مقادیر db رو تغییر میدهیم و همچنین اینبار تیک canload رو میزنیم تا موقع اجرای بازی مقادیر رو برای ما لود کنه و با لود شدن بازی میبینیم که مقادیری که قبلا ذخیره کرده ایم همه لود شدن
سخن پایانی
این روش برای بازی هایی که مقادیر زیادی رو ذخیره میکنند بسیار مناسب است و برای بازی هایی که فقط چند متغییر برای ذخیره دارند بهتره از همان روش قدیمی استفاده بشه همچنین امیدوارم که این مقاله براتون مفید واقع بشه موفق و پیروز باشید
عالییی بود من مشکل ذخیره سازی داشتم و این عالی بود عالییییییی
خیلی خیلی عالی واقعا دستتون درد نکنه خیلی مفید و عالی بود
ممنون – امیدوارم سایر مطالب سایت هم براتون مفید بوده باشه
واقعا عالی و مفید و پرکاربرد
مرسی از این آموزش 20 تتون
ممنون – خوشحالم که برای شما مفید بوده
سلام و عرض ادب
جسارتا بنده توی بخش سیو و لود هیچی متوجه نشدم
الان محتوا و متغیر های فایل تغیر می کنن یا یه فایل براشون ساخته میشه؟
سلام دوست عزیز , بطور خلاصه در این حالت ابتدا فایل اسکریپتیبل ابجکت به رشته تبدیل و داخل یک فایل با هر فرمتی که بخوایین ذخیره میشه و بعد اون فایل رو روی حافظه دیوایستون میسازه ,
موقع لود هم عملیات عکس اون اجرا میشه , کمی تمرین کنید راحت درک میکنید.
موفق باشید
سلام و عرض ادب
توی سیستم سکه از اونجا که هر سکه ممکنه گرفته بشه یا نشه، می خوام برای هر کدوم متغیر bool بذارم. این کار خیلی وقت گیره به نظرتون چه کار کنم از این روش استفاده کنم یا نه؟
سلام کمی بیشتر توضیح بدید که میخواهید چیکار کنید ؟
سلام و عرض ادب
سکه ها در جاهای مختلفی وجود دارن و اصلا ترتیب خاصی برای گرفتنشون وجود نداره. مثلا اگه سه تا سکه ای و بی و سی باشه، خب کاربر ممکنه یکی یا دوتارو بگیره و بقیه رو یادش بره خب بقیه هم یه جوری باید برای مرحله بعد ذخیره بشن خب. اصولا پلیر پرفبز برای این مواقع اصلا عاقلانه نیست. چون ممکنه 100 تا سکه یا شایدم بیشتر نو یه مرحله بازی باشه. چطور اینو پیاده کنم؟
با تشکر فراوان از شما!
عزیز اگر نوع سکه هات یکی هست یعنی مثلا تو یه مرحله سه تا سکه ( مثلا طلایی ) مخفی کردی میخوای کاربر اینا رو پیدا کنه برای این مورد سیستم عادی جمع اوری سکه جوابگو هست ولی اگر نوع سکه هات خاص هستند و میخوای دقیقا ذخیره کنی که کاربر کدوم سکه رو از کجا برداشته و کدوم ها رو یادش رفته بهتره از لیست و اسکریپتیبل ابجکت استفاده کنی براش.
و موقع ذخیره کردن اطلاعات با روش بالا میتونی ذخیره کنی , همچنین از پلیرفبز هم در این حالت میتونی استفاده کنی – میتونی یه حلقه برای ذخیره کردن و لود کردن داده هم بنویسی بیشتر بسته به سلیقه شما داره .
خب ایم احتمالا توی حافظه کش باید باشه.
اگه کاربر کش برنامه رو پاک کرد چی؟
چطور از این اقدام جلوگیری کنیم؟
عزیز این اموزش برای اینکه رو حافظه گوشی ذخیره کنید فایلهاتون رو و اصلا به کش ربطی نداره.
سلام و عرض ادب
این سیستم ذخیره سازی نیاز به دسترسی حافظه در فایل مانیفست نداره؟
یا مثلا در صورت آپدیت بازی مقادیر این روشی و پلیر پرفبز نمیپرن؟
سپاس فراوان از شما!
سلام وخسته نباشید
مبحث مرتبط با IO و system.file واقعا گسترده هست مخصوصا روی اندروید , روی ویندوز اگر انتی ویروس گیر نده ( معمولا گیر نمیده ) شما میتونید هرجا که خواستید فایل ذخیره کنید البته بغیر از قسمت فایل های سیستمی اما روی اندروید این داستان کمی متفاوته ,
من سعی میکنم تو چند خط توضیح بدم :
اول اینکه از نظر بیس همه نرم افزارها به حافظه دسترسی دارند ولی سیستم عامل دسترسی اینا رو کنترل میکنه به این صورت که مثلا وقتی شما میخوایین یه فایل مثلا فایل obb دیتا رو دانلود و کپی کنید سیستم به صورت اتوماتیک این اجازه رو به شما میده تا به پوشه obb/packagename دسترسی داشته باشید بدون اینکه نیاز به گرفتن دسترسی داشته باشید , البته تو بعضی از گوشی موقع پاک کردن بازی پوشه مربوطه هم پاک میشه ( باز یه بحث جداست ) معمولا اگر دایرکشن خاصی رو وارد نکنید مثلا “/” هیچ محدودیتی ندارید میتونید فایل رو با هر فرمتی که خواستید ذخیره کنید و یا فرابخونید .
برای همینه که گاها در مسیر روت حافظه گوشی فایل هایی با فرمت های غیر متداول مشاهده میکنید.
نه جسارتا شما منظورم رو متوجه نشدید
اگه دقت کرده باشید هنگام بروزرسانی یک بازی یا برنامه، بعد دانلود و قبل نصب یه پیامی میاد با این مضمون : “آیا می خواهید بروزرسانی این برنامه کاربردی را نصب کنید؟ داده های شما از بین نخواهد رفت.” یا یه همچین چیزی با یکم تغیر
خب وقتی که یه آپدیت منتشر کنیم و کاربر نصبش کنه، اگه ببینه که همه ذخیره هاش و خریدارش صفر شده، کلا اعصابش نابود میشه. (شما فکر کنید که متغیر های ذخیره شونده بازی مثلا متغیر های عدد صحيح و اعشاری با آپدیت صفر بشن، رشته ای ها نال بشن و دوحالتی ها فالس؛ این یه کابوس خیلی خیلی خیلی وحشتناکه!!!!)
منم میخوام بدونم سیستم پلیر پرفبز و این سیستم که اینجا گفتید، همچین امکانی که داده های ذخیره شونده با بروزرسانی نپرن رو داره یا نه؟
سپاس فراوان از شما!
سلام عزیز – به احتمال خیلی زیاد بله این قابلیت رو داره . ( خودم تست نکردم و فقط با توجه به کارکردنش اینو میگم ).
اما برای اپدیت ها معمولا یا داده ها انلاین سیو میشن و یا در فروشگاه خریدهای یکبار مصرف تعریف میشن مثلا وقتی کاربر یک ماشین رو میخره و بعد حتی اگر بازی رو هم پاک کنه با نصب دوباره بازی و ورود به اکانت بازار کالایی که قبلا خریده سر جاشه و انواع روش های دیگه.
راستی جیسون و اون یکی متغیره دقیقا چی هستن؟
جیسون بر پاییه .txt هست ( البته اطلاع دقیقی ندارم ) و تا جایی که بنده اطلاع دارم فقط رشته رو میشه در اون ذخیره کرد.
سلام.خسته نباشید
من یه مشکلی دارم که با هیچ روشی نمیتونم حلش کنم
وقتی داخل scene 0 ، کد رو میزنم ، دکمه ی ورود به سین ۱ فعال میشه و دیگه داخل سین ۰ نمیتونم همون کد رو بزنم ( تا اینجا مشکلی نیست )
ولی میخوام وقتی از سین ۱ برمیگردم به سین ۰ ، دیگه همه چی به اول برنـــگرده و در واقع اگه دوباره همون کد رو زدم ، اجازه ورود به سین ۱ رو نده.
هدف از این کار ، ساخت کد یکبار مصرف و ذخیره اونه تا برای بار دوم که از سین ۱ به ۰ برمیگردم ، فعال نـــشه.
لطفاً اگه میتونید راهنمایی کنید ، یک دنیا ممنونتم
سلام – سوالتون رو به اینجا انتقال دادم روی لینک کلیک کنید
https://rosedev.ir/Q/%d8%b0%d8%ae%db%8c%d8%b1%d9%87-%d8%af%db%8c%d8%aa%d8%a7%db%8c-%d8%b3%db%8c%d9%86-%d9%87%d8%a7
من با یونیتی یه اپ حسابداری مثل هلو میسازم
برای ثبت و ذخیره جنس و مشتری باید از چه سیستم ذخیره ای استفاده کنم
شاید تو برنامه بیش از هزار جنس ثبت میشه بدر گروه های مختلف ذخیره باید طوری باشه که بتونم توی همون گروه سرچش کنم
وقت بخیر – بهتره از دیتابیس ها استفاده کنید – اسکرپتیبل ابجت ها برای ذخیره سازی تعداد بالا جوابگو نیستند – میتونید از mongo یا sql استفاده کنید .
به اینصورت که کاربر همراه با نصب نرم افزار نوشته شده سرویس دیتابیس رو هم روی کامپیوترش نصب میکنه.
برای نوشتن نرم افزار حسابداری میتوانید از خود c# استفاده کنید – موقع نصب vs تیک windows form ( اگه درست نوشته باشم ) رو بزنید و سپس داخل vs یک پروژه جدید بسازید .
ممنونم
راستی یادم رفت بگم اپ من برای اندروید هست ،همین سیستم ذخیره که فرمودید خوبه؟
شما خودتون آموزشی در مورد mongo یا sql دارید؟؟
خواهش میکنم .
ببینید اینکه دیتابیس رو در حافظه گوشی بتونید بنویسید رو یا نه رو من خودم تست نکردم و نمیدونم با یونیتی قابل انجام هست یا خیر , چون یونیتی بیشتر روی ساخت بازی تمرکز داره تا ساخت نرم افزار اما در حالت کلی استفاده از دیتابیس داخل گوشی رو توصیه نمیکنم , بهتره یه سرور بنویسید تا اطلاعات کاربرها رو تگهداری کنه همچنین تا جایی که بنده اطلاع دارم noSQL روی گوشی اجرا نمیشه و فقط میتونید از sqlLite استفاده کنید .
در مورد اموزش هم باید خدمتتون عرض کنم که فعلا زمان کافی برای ساخت اموزش رو ندارم .
سوالی داشتید در بخش سوال و جوب بپرسید تا بتونم پاسخ بدم . با تشکر
لینک صفحه سوال و جواب : https://rosedev.ir/newq