ذخیره اطلاعات بازی به صورت حرفه ای با 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 رو میزنیم تا موقع اجرای بازی مقادیر رو برای ما لود کنه و با لود شدن بازی میبینیم که مقادیری که قبلا ذخیره کرده ایم همه لود شدن

سخن پایانی

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

اخرین مقاله ها

پیشنهادی

Unity Assets

سخنی از بزرگان

Random Text Plus Image

3 thoughts on “ذخیره اطلاعات بازی به صورت حرفه ای با ScriptableObject

  1. عالییی بود من مشکل ذخیره سازی داشتم و این عالی بود عالییییییی

  2. خیلی خیلی عالی واقعا دستتون درد نکنه خیلی مفید و عالی بود

پاسخ دهید

آدرس ایمیل شما منتشر نخواهد شد.قسمتهای مورد نیاز علامت گذاری شده اند *

تلگرام