بخش هفتم آموزش متوسطه یونیتی (آموزش استفاده از Streaming assets)
فهرست مطالب
آخرین به روزرسانی در 21/10/2022
در بخش هفتم از مجموعه آموزش های دوره ی متوسطه یونیتی می باشیم.
در این بخش ما قصد آموزش استفاده از Streaming assets در یونیتی را داریم.
خب قائدتا در دوره های قبلی ما به صورت خلاصه اشاره هایی به این موضوع کردیم و در بخش قصد ارائه ی توضیحات تخصصی تری را داریم.
در واقع در 6 بخش قبلی ما برای هر پروژه ای که داشتیم از asset های آماده تا درصد زیادی استفاده می کردیم ؛ اما در اینجا معنی assets کمی متفاوت است و به منظور اختصاصی سازی بازی برای کاربران می باشد.
تمرکز ما در بخش 7 از آموزش یونتی استفاده از Streaming assets می باشد که با آن در ادامه آشنا خواهیم شد.
پیش نیاز این آموزش تسلط به مبحث اسکریپت نویسی در سی شارپ می باشد.
پیش نیاز دوم این دوره ی آموزشی تسلط و درک مفاهیم اولیه ی زبان برنامه نویسی C# می باشد.
مقدمه
یکی از مهم ترین مسائلی که می تواند باعث افزایش جذابیت یک بازی شود ، سفارشی سازی توسط کاربر می باشد.
این سفارشی سازی یعنی دادن کنترل و اختیار به کاربر برای ایجاد تغییرات بصری و گرافیکی دلخواه در بازی.
به عنوان مثال در بسیاری از بازی های موفق در سبک های مختلف شما شاهد این خواهید بود که کاربران می توانند کارکترها خود ، تم ها و حتی زمین های بازی را سفارشی سازی کنند که این موارد جزوه آپشن های جذابی از یک بازی به شمار می آید.
در این بخش از آموزش یونیتی، از دایرکتوری Streaming assets در Unity استفاده میکنید تا به کاربران خود اجازه دهید رابط کاربری، موسیقی متن، مدل پلی شدن بازی را سفارشی کنند و حتی لول هایی برای خود ایجاد کنند.
شروع کار
پروژه استارتر را دانلود کنید و پروژه TankArena_Starter را از حالت فشرده خارج کرده و در Unity باز کنید.
پوشه دیگری به نام TankArenaAllAssets با تعدادی از asset های نمونه موجود است که می توانید بعداً در این آموزش استفاده کنید.
بازی را پلی کنید ؛ شاهد یک تانک کوچک در یک میدان هستید.
هدف شما در این بازی این است که به سمت یک برج درخشان نور بروید.
اما چالشی که در این بازی در راه رسیدن به هدف دارید این است که تعدادی از موانع وجود دارد که باید از آنها اجتناب کنید یا آنها را نابود کنید تا به هدف خود برسید.
بازی در حالت عادی دارای کمی meh است.
بنابراین درهمین ابتدای کار میتوانید برخی از asset های درخواستی را اضافه کنید تا به بازیکن اجازه دهید بازی را سفارشی کند و سطوح هیجانانگیزی ایجاد کند.
لودینگ منابع در زمان اجرا
راههای مختلفی برای بالا آمدن منابع به Unity در زمان اجرا وجود دارد، و هر روش در توسعه بازی جایگاه خود را دارد که عبارتند از :
- asset bundles
- resource folders
- streaming assets
بیایید به ترتیب با هر یک بیشتر آشنا شویم.
Asset Bundles
Asset Bundles به شما این امکان را میدهند که محتوا را در خارج از بیلد Unity به برنامه خود اضافه کنید.
به طور کلی، شما این فایل ها را روی یک وب سرور remote میزبانی می کنید تا کاربران به صورت پویا به آن دسترسی داشته باشند.
حالا سوال اینجاست که چرا از Asset Bundles استفاده کنیم؟
هنگام توسعه بازیهای چند پلتفرمی، ممکن است لازم باشد بیش از یک متریال یا مدل ایجاد کنید.
Asset Bundles به شما امکان میدهند Asset های مناسب را بر اساس هر پلتفرم ارائه دهید، در حالی که اندازه نصب اولیه بازی را به حداقل میرسانید.
Asset Bundles میتوانند شامل هر چیزی از Asset های فردی گرفته تا صحنههای کامل باشند، که همچنین آنها را برای ارائه محتوای قابل دانلود (DLC) برای بازی شما ایدهآل میکند.
Resource Folders
برخلاف Asset Bundles ، Resource Folders به عنوان بخشی از بازی در Unity Player قرار میگیرند.
می توانید این کار را با افزودن Asset های مورد نیاز به پوشه ای به نام Resources در فهرست Asset های خود انجام دهید.
Resource Folders برای بارگذاری Asset ها به صورت ریل تایم (در زمان اجرا) مفید هستند که معمولاً بخشی از صحنه یا مرتبط با GameObject نیستند.
به عنوان مثال یک آبجکت یا یک event را در نظر بگیرید که ممکن است در هر تایمی نخواهید به صورت صد در صد بارگذاری شود.
Streaming Assets
مانند Resource Folders، دایرکتوری Streaming Assets را می توان با ایجاد مستقیم پوشه ای به نام StreamingAssets در فهرست Asset های پروژه شما ایجاد کرد.
برخلاف Resource Folders ، این دایرکتوری دست نخورده باقی می ماند و در پلیر Unity قابل دسترسی است.
این یک نقطه دسترسی منحصر به فرد برای کاربران ایجاد می کند تا فایل های خود را به بازی اضافه کنند.
افزودن Streaming Assets
در پنجره assets بر روی Create کلیک کنید و یک پوشه جدید به پروژه خود اضافه کنید.
نام پوشه StreamingAssets را تغییر دهید.
اکنون روی File\Build Settings کلیک کنید و مطمئن شوید که Target Platform به درستی به پلتفرمی که روی آن کار می کنید اختصاص داده شده است.
برای ساخت پلیر روی Build کلیک کنید.
به جایی که از پلیر Unity خروجی می دهید بروید.
در پی سی، به accompanying folder با عنوان <SaveName>_Data نگاه کنید.
هر فایل یا پوشهای که در آن رها کنید برای بازی شما قابل دسترسی خواهد بود.
به راحتی می توانید فایل ها را مستقیماً در پوشه StreamingAssets در نمای پروژه خود رها کنید تا بتوانید به سرعت آنرا در ادیتور آزمایش کنید.
اضافه کردن اولین تصویر
برای شروع کار در آموزش استفاده از Streaming assets در اینجا کاری که می خواهیم انجام دهیم به اینصورت است که یک تصویر جایگذین برای تانک قرار دهیم.
که کاربر در بازی به جای استفاده از تصویر پروفایل تانک بتواند از تصویر دیگری نیز به انتخاب خود استفاده کند.
حال عکس مورد نظر خود را به فهرست StreamingAssets که در Project Window ایجاد کرده اید بکشید.
به عنوان مثال ما تصویر یک گربه ی بامزه را اضافه کردیم.
حال به دلیل اینکه نام باید حاوی یک کلمه کلیدی خاص باشد تا بازی بداند با تصویر چه کند ؛ باید نام Asset تصویر خود را تغییر دهید.
نام Asset تصویر خود را در قالب “player1 .png” تغییر دهید.
به عنوان مثال، ما نام عکس خود را player1 Captain Patch.png گذاشتیم.س
اکثر فایل هایی که اضافه می کنید فرمت مشابهی خواهند داشت.
آنها یک تگ برای کمک به شناساییشان در بازی خواهند داشت و متن بعدی به شما امکان می دهد پارامترهای اضافی را برای بازی اضافه کنید.
بارگذاری اولین Resource
در Project Window ، به Assets\Scripts بروید و روی Game Manager Script دوبار کلیک کنید تا در IDE مورد نظر شما باز شود.
تحت class declaration ، این متغیرهای عمومی را برای ارجاع به آبجکت های بازی UI که سفارشی می کنید اضافه کنید:
public Image playerAvatar;
public Text playerName;
قبل از اینکه کدی را برای یافتن و دسترسی به فایلها در فهرست streaming assets خود اضافه کنید، باید موارد زیر را به بالای اسکریپت GameManager خود اضافه کنید:
using System.IO;
این قابلیت پشتیبانی از فایل ها و دایرکتوری ها را اضافه می کند و امکان خواندن و نوشتن فایل ها را فراهم می کند.
موارد زیر را به () Start در زیر playerTank = GameObject.FindGameObjectWithTag(“Player”) اضافه کنید:
DirectoryInfo directoryInfo = new DirectoryInfo(Application.streamingAssetsPath);
print("Streaming Assets Path: " + Application.streamingAssetsPath);
FileInfo[] allFiles = directoryInfo.GetFiles("*.*");
فهرست streaming assets بسته به پلتفرم در مکان های مختلفی قرار دارد.
با این حال، Application.streamingAssetsPath همیشه مسیر صحیح را برمی گرداند.
statement print به شما نشان می دهد که ببینید streamingAssetsPath شما به کجا اشاره می کند.
خط آخر آرایه ای را ایجاد می کند که شامل تمام فایل های فهرست streaming assets است.
در حین انجام این آموزش برای مدیریت فایل های یافت شده، یک سری conditional ایجاد خواهید کرد.
برای ارسال تصویر پلیر برای پردازش، به کدی که به تازگی اضافه کرده اید، کد زیر را نیز اضافه کنید:
foreach (FileInfo file in allFiles)
{
if (file.Name.Contains("player1"))
{
StartCoroutine("LoadPlayerUI", file);
}
}
حلقه foreach در میان فایل ها تکرار می شود، و conditional بررسی می کند که آیا نام فایل حاوی کلمه کلیدی “player1” است یا خیر.
پس از یافتن، فایل به یک () Coroutine LoadPlayerUI ارسال می شود که در ادامه اضافه می کنید:
IEnumerator LoadPlayerUI(FileInfo playerFile)
{
//1
if (playerFile.Name.Contains("meta"))
{
yield break;
}
//2
else
{
string playerFileWithoutExtension = Path.GetFileNameWithoutExtension(playerFile.ToString());
string[] playerNameData = playerFileWithoutExtension.Split(" "[0]);
//3
string tempPlayerName = "";
int i = 0;
foreach (string stringFromFileName in playerNameData)
{
if (i != 0)
{
tempPlayerName = tempPlayerName + stringFromFileName + " ";
}
i++;
}
//4
string wwwPlayerFilePath = "file://" + playerFile.FullName.ToString();
WWW www = new WWW(wwwPlayerFilePath);
yield return www;
//5
playerAvatar.sprite = Sprite.Create(www.texture, new Rect(0, 0, www.texture.width, www.texture.height), new Vector2(0.5f, 0.5f));
playerName.text = tempPlayerName;
}
}
اگرچه بارگذاری این تصویر بسیار سریع اتفاق می افتد، اما به زودی فایل های بیشتری را بارگیری و دستکاری خواهید کرد.
یک Coroutine از رشته اصلی خارج می شود و حلقه بازی را قطع نمی کند.
روی کد:
1- conditional در شروع تابع بررسی می کند که نام فایل حاوی متا نباشد.
در این صورت، فایل احتمالاً فایل پشتیبان تولید شده Unity است و تابع خارج می شود.
این قطعه در مراحل بعدی پردازش فایل تکرار خواهد شد.
2- نام فایل بدون پسوند ذخیره می شود و متعاقباً به یک آرایه رشته ای که حاوی کلمات جداگانه است تقسیم می شود.
3- یک رشته خالی برای نام بازیکن ایجاد می شود.
حلقه foreach از طریق آرایه playerNameData تکرار می شود.
هر آیتم در آرایه به جز مورد اول (player1) به رشته playerName متصل می شود.
4- مسیر کامل فایل برای بارگذاری یک WWW Object مورد نیاز است.
yield return تضمین می کند که اجرای تابع تا زمانی که فایل بارگیری شود به تأخیر می افتد.
5- یک sprite با متریال بارگذاری شده ایجاد می شود و روی playerAvatar اعمال می شود.
playerName با استفاده از tempPlayerName که شما ساخته اید پر می شود.
قبل از اینکه دکمه play را فشار دهید تا نتیجه کار خود را ببینید، به یاد داشته باشید که playerAvatar و playerName را به Game Object های مربوطه خود در Unity متصل کنید.
در یونیتی ادیتور Game Manager را از Hierarchy Window انتخاب کنید تا متغیرهای عمومی در inspector نمایش داده شوند.
Alt را نگه دارید و بر روی disclosure triangle در کنار UICanvas در Hierarchy Window کلیک کنید.
این باید UICanvas و همه فرزندان فرزندانش را گسترش دهد.
تحت GameUIPanel PlayerAvatar و PlayerName را به متغیرهای عمومی با نام یکسان خود در Game Manager در Inspector بکشید:
دکمه پلی را فشار دهید و نماد و تگنام جدید خود را بررسی کنید.
افزودن موسیقی اختصاصی
فایل صوتی خود را به پوشه Project Window StreamingAssets بکشید و نام متن فایل موسیقی را تغییر دهید.
توجه : انواع مختلفی از فایل های صوتی وجود دارد.
اگر مشکل در اجرا دارید ، فایل را به .ogg تبدیل کنید زیرا یک فرمت خوب در Unity است که به راحتی پشتیبانی می شود.
یک متغیر public جدید به GameManager اضافه کنید:
public AudioSource musicPlayer;
با انتخاب GameManager در Hierarchy Window ، این متغیر جدید را با کشیدن کل Main Camera روی متغیر در Inspector به AudioSource در Main Camera صحنه متصل کنید.
Main Camera فرزند مدل Player’s Tank شماست، بنابراین مطیعانه شما را دنبال می کند.
به یاد داشته باشید، می توانید از کادر جستجوی Hierarchy برای یافتن سریع هر آبجکت بازی در صحنه استفاده کنید.
به تابع () GameManager Start برگردید و یک conditional جدید در زیر دیگری اضافه کنید تا فایل موسیقی متن را به یک Coroutine جدید منتقل کنید:
else if (file.Name.Contains("soundtrack"))
{
StartCoroutine("LoadBackgroundMusic", file);
}
تحت LoadPlayerUI Coroutine، یک Coroutine جدید با عنوان LoadBackgroundMusic اضافه کنید.
IEnumerator LoadBackgroundMusic (FileInfo musicFile)
{
if (musicFile.Name.Contains("meta"))
{
yield break;
}
else
{
string musicFilePath = musicFile.FullName.ToString();
string url = string.Format("file://{0}", musicFilePath);
WWW www = new WWW(url);
yield return www;
musicPlayer.clip = www.GetAudioClip(false, false);
musicPlayer.Play();
}
}
این کد باید بسیار آشنا به نظر برسد.
بارگذاری یک فایل صوتی بسیار شبیه به بارگذاری یک متریال است.
شما از URL برای بارگیری فایل استفاده می کنید و سپس صدا را به property clip MusicPlayer اعمال می کنید.
در نهایت، () Play را در MusicPlayer فراخوانی میکنید تا صدای متن آهنگ به سرعت پخش شود.
کار تمام شد روی play کلیک کنید.
سفارشی سازی مدل بازیکن
اکنون نوبت به آن رسیده است که بتوانید مدل تانک را سفارشی کنید.
در ادامه با دو رویکرد مختلف برای سفارشی کردن مدل مخزن آشنا خواهید شد.
- اولی از نمونه های رنگی ساده استفاده می کند تا به کاربر اجازه دهد رنگ های مورد علاقه خود را روی تانک اعمال کند.
- دومی یک پوسته ی جدید کامل برای تانک خواهد بود ، شبیه به مدهای پوسته Minecraft.
تصویر playercolor را در پوشه منابع TankArenaAllAssets که با دانلود پروژه استارتر ارائه شده است، پیدا کنید.
همانطور که قبلا انجام داده اید، فایل را به پوشه Project Window StreamingAssets بکشید.
متغیرهای جدید زیر را به Game Manager اضافه کنید، همگی تحت تگ Header Tank Customisation :
[Header("Tank Customisation")]
public Texture2D tankTexture;
public Texture2D tankTreads;
public Renderer tankRenderer;
private Texture2D newTankTexture;
private Vector3 defaultTankPrimary = new Vector3(580, 722, 467);
private Vector3 defaultTankSecondary = new Vector3(718, 149, 0);
Game Manager باید به تکسچرهای مدلهای تانک و رندرها اشاره کند تا بتوان تغییراتی ایجاد کرد و مدل را دوباره مونتاژ کرد.
علاوه بر این، مقادیر رنگ اصلی سبز نظامی و قرمز را به عنوان اعداد صحیح در Vector3 برای دستورات شرطی بعدی ذخیره می کنید.
شما از Vector3 بر خلاف Color استفاده می کنید، زیرا مقایسه یک رنگ با رنگ دیگر بسیار غیر قابل اعتماد است.
دوباره به () Start بروید و یک شرط دیگر اضافه کنید:
else if (file.Name.Contains("playercolor"))
{
StartCoroutine("LoadPlayerColor", file);
}
در () Coroutine LoadBackgroundMusic موارد زیر را اضافه کنید.
IEnumerator LoadPlayerColor(FileInfo colorFile)
{
//1
if (colorFile.Name.Contains("meta"))
{
yield break;
}
else
{
string wwwColorPath = "file://" + colorFile.FullName.ToString();
WWW www = new WWW(wwwColorPath);
yield return www;
Texture2D playerColorTexture = www.texture;
//2
Color primaryColor = playerColorTexture.GetPixel(5, 5);
Color secondaryColor = playerColorTexture.GetPixel(15, 5);
//3
Color[] currentPixelColors = tankTexture.GetPixels();
Color[] newPixelColors = new Color[currentPixelColors.Length];
//4
float percentageDifferenceAllowed = 0.05f;
int i = 0;
foreach (Color color in currentPixelColors)
{
Vector3 colorToTest = new Vector3((Mathf.RoundToInt(color.r * 1000)), (Mathf.RoundToInt(color.g * 1000)), (Mathf.RoundToInt(color.b * 1000)));
if ((colorToTest - defaultTankPrimary).sqrMagnitude <= (colorToTest * percentageDifferenceAllowed).sqrMagnitude)
{
newPixelColors.SetValue(primaryColor, i);
}
else if ((colorToTest - defaultTankSecondary).sqrMagnitude <= (colorToTest * percentageDifferenceAllowed).sqrMagnitude)
{
newPixelColors.SetValue(secondaryColor, i);
}
else
{
newPixelColors.SetValue(color, i);
}
i++;
}
//5
newTankTexture = new Texture2D(tankTexture.width, tankTexture.height);
newTankTexture.SetPixels(newPixelColors);
newTankTexture.Apply();
//6
ApplyTextureToTank(tankRenderer, newTankTexture);
}
}
1- در خط اول یک meta check خوب قرار دارد.
2- داده های رنگ یک پیکسل را در سمت چپ و سمت راست نمونه رنگ در این دو متغیر ذخیره می کنید.
3- سپس دو آرایه رنگی ایجاد می کنید.
اولین، CurrentPixelColors شامل تمام اطلاعات رنگ از متریال پیش فرض تانک است.
دوم، newPixelColors با اطلاعات رنگی یکسان پر می شود. (اما تنها زمانی که طرح رنگ سفارشی اعمال شود.
به همین دلیل است که می توانید آن را با اندازه اولین آرایه نمونه برداری کنید.)
4- حلقه foreach هر پیکسل را از CurrentPixelColors می گیرد و آن را آزمایش می کند.
اگر رنگ با پیشفرض TankPrimary که کدنویسی کردهاید مطابقت داشته باشد، مقدار PrimeColor جدید به جای خود در آرایه newPixelColor ذخیره میشود.
اگر رنگ با defaultTankSecondary مطابقت داشت، secondaryColor جدید را ذخیره کنید.
اگر رنگ با هیچ کدام مطابقت ندارد، به سادگی همان رنگ را ذخیره کنید.
5- هنگامی که آرایه newPixelColors پر شد، یک texture2D جدید ایجاد میکنید و برای ذخیره تمام تغییرات پیکسل، () Apply را فرا میخوانید.
متد زیر را زیر متدی که ایجاد کردید اضافه کنید:
public void ApplyTextureToTank(Renderer tankRenderer, Texture2D textureToApply)
{
Renderer[] childRenderers = tankRenderer.gameObject.GetComponentsInChildren();
foreach (Renderer renderer in childRenderers)
{
renderer.material.mainTexture = textureToApply;
}
tankRenderer.materials[1].mainTexture = textureToApply;
tankRenderer.materials[0].mainTexture = tankTreads;
}
() ApplyTextureToTank دو آرگومان می گیرد:
Renderer و Texture2D جدیدی که می خواهید اعمال کنید.
شما از GetComponentsInChildren برای واکشی همه رندرهای مدل مخزن و اعمال متریال اصلاح شده جدید استفاده می کنید.
GetComponentsInChildren، مؤلفه درخواستی را در GameObject والد واکشی می کند.
در این مدل خاص، آج های تانک متریال خاص خود را دارند.
شما باید این قسمت از سفارشی سازی تانک را در متد public خود قرار دهید زیرا بعداً به عملکردی مشابه با این نیاز خواهید داشت.
مرحله آخر اتصال متغیرهای public جدید Game Manager در Inspector است.
اطمینان حاصل کنید که Game Manager در Hierarchy Window انتخاب شده است.
در Project Window ، به فهرست Assets\Tank Model نگاه کنید.
دو فایل پیشفرض تکسچر مورد استفاده برای مدل مخزن را پیدا خواهید کرد.
LowPolyTank را به Tank Texture و NewThreads را به متغیر Tank Treads بکشید.
در پنجره Hierarchy ،
Player\Tank را به Tank Renderer بکشید.
روی play کلیک کنید و تانک جدید خود را بررسی کنید:
راه دوم برای سفارشی سازی پوسته تانک
در این بخش از آموزش استفاده از Streaming assets قصد داریم راه دیگر برای سفارشی کردن تانک ها ارائه دهیم.
به کاربر اجازه دهید پوسته های جدیدی را به streaming assets اضافه کند و به آنها اجازه دهید آنها را به صورت ریل تایم انتخاب و اعمال کنند.
یک ScrollView و یک Skin Object به صورت دیفالت و پیش ساخته در منوی Pause وجود دارد که می توانید برای رابط کاربری استفاده کنید.
هر پوسته توسط یک تانک نمایش داده می شود و یک دکمه به شما امکان می دهد آن را اضافه کنید.
برخی از تکسچرهای “skin” تانک در پوشه TankArenaAllAssets گنجانده شده است که همراه با دانلود پروژه استارتر دریافت کردید.
اکنون آنها را در پوشه Project Window StreamingAssets قرار دهید.
به اسکریپت Game Manager برگردید تا این ویژگی جدید کار کند.
پوسته ها و نام آنها در لیست ها ذخیره می شود.
بنابراین، Generic namespace را در بالای اسکریپت Game Manager اضافه کنید.
using System.Collections.Generic;
متغیرهای زیر را زیر سایر متغیرهای سفارشی سازی تانک خود اضافه کنید.
//1
public List tankSkins;
public List tankSkinNames;
//2
public GameObject skinContainer;
public GameObject skinObject;
//3
private bool skinMenuAssembled = false;
1- هنگامی که پوسته ای پیدا می شود، بافت را به لیست tankSkins و نام آن را به لیست tankSkinNames اضافه می کند.
2-برای نمونهسازی یک Skin Object در ScrollView، هم به prefab برای نمونهسازی و هم به container که والد آن خواهد بود، نیاز دارید.
3-در نهایت یک Boolean برای تعیین اینکه آیا شما قبلاً لیست پوسته را در ScrollView پردازش و مونتاژ کرده اید یا خیر استفاده می شود.
این برای اطمینان از تکرار نشدن غیر ضروری این فرآیند بین راهاندازی مجدد سطح استفاده میشود.
مانند قبل، یک شرط دیگر را به تابع start اضافه کنید:
else if (file.Name.Contains("skin"))
{
StartCoroutine("LoadSkin", file);
}
یک coroutine جدید برای پردازش فایل های skin ایجاد کنید:
IEnumerator LoadSkin(FileInfo skinFile)
{
if (skinFile.Name.Contains("meta"))
{
yield break;
}
else
{
//1
string skinFileWithoutExtension = Path.GetFileNameWithoutExtension(skinFile.ToString());
string[] skinData = skinFileWithoutExtension.Split(" "[0]);
string skinName = skinData[0];
//2
string wwwSkinPath = "file://" + skinFile.FullName.ToString();
WWW www = new WWW(wwwSkinPath);
yield return www;
Texture2D newTankSkin = www.texture;
tankSkins.Add(newTankSkin);
tankSkinNames.Add(skinName);
}
}
1- نام skin اولین کلمه در filename است.
2- ممکن است چندین فایل skin برای پردازش وجود داشته باشد.
در این مرحله، تکسچرها و نام ها به سادگی به لیست ها اضافه می شوند.
برای این آموزش، شما فرض می کنید که وقتی صفحه loading تمام می شود ، همه streaming assets پردازش شده اند.
موارد زیر را به انتهای () RemoveLoadingScreen:
if (!skinMenuAssembled)
{
StartCoroutine("AssembleSkinMenu");
}
یک Coroutine جدید ایجاد کنید و کد زیر را اضافه کنید :
IEnumerator AssembleSkinMenu()
{
skinMenuAssembled = true;
int i = 0;
//1
foreach (Texture2D skinTexture in tankSkins)
{
GameObject currentSkinObject = Instantiate(skinObject, new Vector3(0, 0, 0), Quaternion.identity, skinContainer.transform);
//2
currentSkinObject.transform.localPosition = new Vector3(100 + (200 * i),-80,0);
//3
SkinManager currentSkinManager = currentSkinObject.GetComponent();
currentSkinManager.ConfigureSkin(tankSkinNames[i], i);
ApplyTextureToTank(currentSkinManager.tankRenderer, tankSkins[i]);
i++;
}
yield return null;
}
1- حلقه foreach از طریق tankSkins تکرار می شود.
برای هر آیتم، یک Skin Object را نمونهسازی میکنید و آن را به آبجکت content در ScrollView اضافه میکنید.
2- موقعیت Skin Object در Scrollview بسته به فهرست جابجا می شود.
این اطمینان حاصل می کند که تمام skin ها به طور منظم در نمای space وجود دارند.
3- شما اسکریپت SkinManager را در Skin Object میآورید و نام Skin و فهرست آن را در لیست ارسال میکنید.
برای اعمال Skin سفارشی به تانک Skin Object مجدداً از () ApplyTextureToTank استفاده می کنید.
به Scripts folder در Project Window بروید و روی اسکریپت SkinManager دوبار کلیک کنید تا آن را در IDE خود باز کنید.
()ConfigureSkin فهرستی را که در یک متغیر خصوصی ارسال شده ذخیره می کند و تگ دکمه با استفاده از نام Skin سفارشی می شود.
هنگامی که بازیکن دکمه اعمال Skin را فشار می دهد، () ApplySkinTapped فهرست ذخیره شده را به ()ApplySkin در GameManager می فرستد.
با افزودن کد زیر، ()ApplySkin را در پایین اسکریپت GameManager به پایان برسانید:
ApplyTextureToTank(tankRenderer, tankSkins[indexOfSkin]);
PlayerUI.SetActive(true);
pauseMenuCamera.SetActive(false);
isPaused = false;
Time.timeScale = 1.0f;
این تکسچر مربوطه را از لیست استخراج می کند و آن را در تانک بازیکنان اعمال می کند.
همچنین منوی pause را حذف کرده و بازی را از سر می گیرید.
وقت آن است که همه اینها را به هم وصل کنیم.
روی Game Manager در Hierarchy ضربه بزنید تا اسکریپت Game Manager در Inspector ظاهر شود.
Skin Object ، prefab را از پوشه Project Window Prefabs به متغیر عمومی skinObject در Inspector بکشید.
محتوا را در قسمت جستجوی Hierarchy تایپ کنید تا آبجکت محتوای ScrollView را بدون از دست دادن Game Manager در inspector پیدا کنید (یا روی نماد قفل در بالا سمت راست در نمای inspector ضربه بزنید). در نهایت، آبجکت content را به متغیر skinContainer بکشید.
روی دکمه play ضربه بزنید و Escape را فشار دهید تا بازی متوقف شود.
روی یک دکمه ضربه بزنید و پوسته جدیدی را برای تانک خود انتخاب کنید:
طراحی سطح
بسیار خوب، وقت آن است که در این مرحله از آموزش استفاده از Streaming assets به طراحی و ایجاد سطح سفارشی خودمان برای بازی برویم.
ابتدا بیایید نگاهی به آناتومی یک میدان بیندازیم تا بتوانیم دیدی از نحوه ساخت آن پیدا کنیم.
در Hierarchy View ، روی Default Arena بازی دوبار ضربه بزنید تا آن را انتخاب کنید و در Scene View نمایش دهید.
Arena از صدها کاشی ساخته شده است و هر نوع کاشی یک Prefab از پوشه Prefab در Project View است.
شما از این Prefab ها برای مونتاژ یک سطح در هر ترکیب یا جایگشتی که می توانید تصور کنید استفاده خواهید کرد.
می توانید این سطوح سفارشی را در پوشه دانلود استارتر TankArenaAllAssets پیدا کنید.
به فایل ها نگاهی بیندازید، با استفاده از هر image editor می توان سطح نسبتاً پیچیده ای ساخت…
اکنون شما سطح سفارشی خود را ایجاد کرده و کدی را برای بارگیری هر یک از این سطوح می نویسید.
image editor مورد نظر خود را باز کنید و یک document/canvas جدید 100 پیکسل در 100 پیکسل مربع ایجاد کنید.
برای ایجاد آبجکت ها با استفاده از طرح رنگ زیر از یک pencil tool سخت 1 پیکسلی یا ابزار line استفاده کنید.
از cursor tool برای بدست آوردن مختصات x و y از جایی که می خواهید بازیکن شروع کند و کاشی هدف باید کجا باشد استفاده کنید.
پس از اتمام استفاده از naming scheme زیر، فایل را به صورت png ذخیره کنید.
<x مختصات شروع بازیکن>
<y مختصات شروع بازیکن>
<x مختصات هدف>
<y مختصات هدف>
<نام سطح شما>.png
هنگامی که از طراحی خود راضی بودید، فایل را به پوشه Project Window StreamingAssets اضافه کنید.
به Game Manager برگردید.
بالای class declaration ، کد زیر را اضافه کنید:
[System.Serializable]
public class Arena
{
public string arenaFilePath;
public int levelNumber;
public string levelName;
public float startX;
public float startZ;
public float targetX;
public float targetZ;
}
این قطعه کد یک کلاس Arena ایجاد می کند که شامل تمام متغیرهای لازم برای قرار دادن داده های استخراج شده از یک فایل arena است.
ویژگی Serializable به این کلاس اجازه می دهد تا در inspector نمایش داده شود.
یک لیست جدید به GameManager اضافه کنید که تمام نمونه های کلاس Arena را که ایجاد می کنید نگه می دارد:
[Header("Arena")]
public List arenaList = new List();
متغیرهای public اضافی زیر را به GameManager در arenaList اضافه کنید:
public Texture2D arenaTexture;
[Header("Arena Prefabs")]
public GameObject floorPrefab;
public GameObject weakFloorPrefab;
public GameObject wallPrefab;
public GameObject weakWallPrefab;
public GameObject mineTilePrefab;
[Header("Arena Objects")]
public GameObject defaultArena;
public GameObject arenaTiles;
public GameObject target;
[Space]
این متغیرها همه بلوکهای سازنده سطح را تشکیل میدهند و به عنوان مرجعی برای بازیکن و آبجکت هدف عمل میکنند تا بتوانید موقعیت آنها را سفارشی کنید.
ما همچنین به defaultArena اشاره میکنیم تا بتوانیم آن را حذف کنیم، و arenaTiles نیز بهمنظور اینکه container برای کاشیهای نمونه جدید داشته باشیم ، است.
همانطور که قبلاً انجام دادید، یک دستور شرطی جدید به تابع start اضافه کنید:
else if (file.Name.Contains("Arena"))
{
StartCoroutine("LoadArena", file);
}
یک coroutine جدید به نام ()LoadArena می سازیم :
IEnumerator LoadArena (FileInfo arenaFile)
{
if (arenaFile.Name.Contains(".meta"))
{
yield break;
}
else
{
//1
Arena arenaInstance = new Arena();
string arenaFileWithoutExtension = Path.GetFileNameWithoutExtension(arenaFile.ToString());
string[] arenaDataArray = arenaFileWithoutExtension.Split(" "[0]);
arenaInstance.startX = int.Parse(arenaDataArray[1]);
arenaInstance.startZ = int.Parse(arenaDataArray[2]);
arenaInstance.targetX = int.Parse(arenaDataArray[3]);
arenaInstance.targetZ = int.Parse(arenaDataArray[4]);
//2
string levelName = "";
if (arenaDataArray.Length <= 5)
{
if (arenaList.Count != 0)
{
levelName = "Level " + (arenaList.Count + 1);
}
else
{
levelName = "Level 1";
}
}
else
{
int i = 0;
foreach (string stringFromDataArray in arenaDataArray)
{
if (i > 4)
{
levelName = levelName + stringFromDataArray + " ";
}
i++;
}
}
arenaInstance.levelName = levelName;
//3
arenaInstance.arenaFilePath = "file://" + arenaFile.FullName.ToString();
//4
arenaList.Add(arenaInstance);
}
}
1- در اینجا شما یک نمونه جدید از Arena ایجاد می کنید.
برنامه ای که قبلا انجام داده بود، نام فایل تقسیم شده و برای استفاده از کلاس می شود.
2- برای نام arena ، تعداد موارد موجود در نام فایل تقسیم شده را آزمایش کنید.
اگر کمتر از 6 باشد، هیچ نامی وجود ندارد و یک نام پیشفرض بر اساس تعداد سطوحی که قبلاً بارگذاری شدهاند میشود.
3- مسیر فایل با نمونه arena ذخیره می شود تا سطح فقط در صورت نیاز بارگیری شود.
arenaInstance -4 در فهرست GameManagers arenas ذخیره میشود.
به ()Start برگردید، برای لود سطح اول (در صورت وجود) پس از ارسال همه فایل ها به قسمت های اصلی خود، درست بعد از حلقه foreach، موارد زیر را اضافه کنید:
if (arenaList.Count != 0 )
{
//1
Destroy(defaultArena);
StartCoroutine("LoadLevel", arenaList[0]);
}
این Coroutine نهایی را برای لود یک arena اضافه کنید:
IEnumerator LoadLevel(Arena arenaToLoad)
{
arenaName = arenaToLoad.levelName;
//2
loadingScreen.SetActive(true);
gameOverScreen.SetActive(false);
winScreen.SetActive(false);
//3
foreach (Transform child in arenaTiles.transform)
{
GameObject.Destroy(child.gameObject);
}
//4
WWW www = new WWW(arenaToLoad.arenaFilePath);
yield return www;
arenaTexture = www.texture;
Color[] arenaData = arenaTexture.GetPixels();
//5
int x = 0;
foreach (Color color in arenaData)
{
int xPosition = ((x + 1) % 100);
if (xPosition == 0)
{
xPosition = 100;
}
int zPosition = (x / 100) + 1;
//6
if (color.a < 0.1f)
{
GameObject.Instantiate(floorPrefab, new Vector3(xPosition / 1.0f, 0.0f, zPosition / 1.0f), Quaternion.Euler(90, 0, 0), arenaTiles.transform);
}
else
{
if (color.r > 0.9f && color.g > 0.9f && color.b < 0.1f)
{
}
else if (color.r > 0.9f && color.g < 0.1f && color.b < 0.1f)
{
GameObject.Instantiate(mineTilePrefab, new Vector3(xPosition / 1.0f, 0.0f, zPosition / 1.0f), Quaternion.identity, arenaTiles.transform);
}
else if (color.r < 0.1f && color.g > 0.9f && color.b < 0.1f)
{
GameObject.Instantiate(weakWallPrefab, new Vector3(xPosition / 1.0f, 0.0f, zPosition / 1.0f), Quaternion.identity, arenaTiles.transform);
}
else if (color.r < 0.1f && color.g < 0.1f && color.b > 0.9f)
{
GameObject.Instantiate(weakFloorPrefab, new Vector3(xPosition / 1.0f, 0.0f, zPosition / 1.0f), Quaternion.identity, arenaTiles.transform);
}
else
{
GameObject.Instantiate(wallPrefab, new Vector3(xPosition / 1.0f, 0.0f, zPosition / 1.0f), Quaternion.identity, arenaTiles.transform);
}
}
x++;
}
//7
StartCoroutine("RemoveLoadingScreen");
Time.timeScale = 1.0f;
//8
playerTank.transform.position = new Vector3(arenaToLoad.startX / 1.0f, 1.0f, (100 - arenaToLoad.startZ) / 1.0f);
playerTank.transform.localRotation = Quaternion.Euler(0.0f, 0.0f, 0.0f);
target.transform.position = new Vector3(arenaToLoad.targetX / 1.0f, 0.6f, (100 - arenaToLoad.targetZ) / 1.0f);
}
1- نابود کردن arena پیش فرض
2- از آنجایی که این متد می تواند هنگام تکمیل یک سطح نیز فراخوانی شود، صفحه لودینگ را مجدداً اعمال می کنید و صفحه برد یا باخت بازی را حذف می کنید.
3- کاشی های Arena موجود در صحنه را بردارید.
4- شما تکسچر را از مسیر فایل ذخیره شده لود می کنید و getPixels تمام داده های پیکسل را ضبط می کند.
این فرآیند تصویر 2 بعدی 100 100x پیکسل را به فهرستی 1 بعدی از مقادیر رنگ کاهش می دهد.
5- روی فهرست دادههای پیکسلی تکرار کنید. شما از مقدار شاخص برای تعیین مکان پیکسل در فضای دوبعدی استفاده می کنید.
سپس کاشی صحیح را می توان در موقعیت صحیح در صحنه نمونه برداری کرد.
6- مقدار رنگ پیکسل تعیین می کند که کدام کاشی را باید نمونه برداری کنید. از آنجایی که برخی از ویرایشگرهای تصویر ممکن است پیکسل های مجاور را از بین ببرند، شما یک حاشیه خطا در نظر می گیرید. شرطی ها مقدار رنگ پیکسل را بررسی می کنند و کاشی قابل اجرا را در موقعیت صحیح قرار می دهند.
7-هنگامی که تمام داده های پیکسل را پردازش کردید، ()RemoveLoadingScreen را فراخوانی کنید تا پس از یک ثانیه صفحه نمایش داده شود و گیم پلی از سر گرفته شود.
8- تانک بازیکن و کاشی هدف را در موقعیت های مربوطه خود همانطور که در نمونه Arena آنها ثبت شده است حرکت دهید.
تابع خالی StartNextLevel را پیدا کنید و کد زیر را اضافه کنید:
//1
if (arenaList.Count >= currentLevel + 1)
{
currentLevel++;
StartCoroutine("LoadLevel", arenaList[currentLevel - 1]);
}
else
{
SceneManager.LoadScene("MainScene");
}
//2
Rigidbody playerRB = playerTank.GetComponent();
playerRB.isKinematic = true;
playerRB.isKinematic = false;
1- پس از تکمیل یک سطح، بررسی کنید که آیا سطح دیگری وجود دارد یا خیر.
اگر چنین است، آن را به () LoadLevel ارسال کنید. در غیر این صورت، کل صحنه را دوباره بارگذاری کنید تا در سطح اول دوباره شروع شود.
2- ممکن است هنگام راه اندازی مجدد یا انتقال بین سطوح، ورودی باقی مانده به تانک اعمال شود.
Rigidbody بازیکن را از kinematic به non-kinematic تغییر دهید تا آن را به صفر برسانید.
اکنون که ()StartNextLevel کمی مشخص شده است، “Next” را در نوار جستجوی پنجره Hierachy در Unity تایپ کنید.
این جستجو باید به یک Game Object به نام Next Level Button برسد.
برای انتخاب آن کلیک کنید، و در inspector تیک interactable در زیر مؤلفه Button را بزنید.
اکنون باید چند اصلاح کد انجام دهید تا با این واقعیت مطابقت داشته باشید که اکنون می توانید چندین سطح (و نه فقط سطح starter ) داشته باشید.
جایگزین ;SceneManager.LoadScene (“MainScene”) در ()RestartLevel با موارد زیر :
if (arenaList.Count != 0)
{
StartCoroutine("LoadLevel", arenaList[currentLevel - 1]);
}
else
{
SceneManager.LoadScene("MainScene");
}
این کد تضمین میکند که به جای لود game scene با سطح starter پیشفرض در level restart ، به جای آن، () Coroutine LoadLevel فراخوانی میشود که arena پیشفرض را از بین میبرد و آن را با content سطح سفارشی که از streaming assets بارگذاری شده است جایگزین میکند.
همچنین جایگزین ;timerText.text = arenaName + ” ” + formattedTime در () UpdateTimerUI با خط زیر نیز هست :
timerText.text = arenaList[currentLevel-1].levelName + " " + formattedTime;
این بیت کد تضمین می کند که تگ متنی نام سطح در رابط کاربری بازی با نام سطح سفارشی به روز می شود.
قبل از اینکه هیجان زده شوید و Play را فشار دهید، فراموش نکنید که prefab outlets را در inspector وصل کنید.
پوشه Prefabs را در Project Window و Game Manager را در Hierarchy Window انتخاب کنید.
این باید هر چیزی را که نیاز دارید در معرض دید قرار دهد.
در Inspector، متغیرهای کاشی های Arena Prefab را در عنوان Arena Prefabs پیدا خواهید کرد.
هر یک از اینها یک Prefab با نام یکسان در پوشه Prefabs در Project Window دارند.
هر کدام را از پوشه Prefab به متغیر مربوطه خود بکشید.
در مرحله بعد، به سربرگ Arena Objects در Inspector نگاهی بیندازید.
این سه متغیر GameObject در Hierarchy Window یافت می شوند.
Target ، Default Arena و ArenaTiles را از پنجره Hierarchy به متغیرهای مربوطه خود در Inspector بکشید.
روی play کلیک کنید و طرح های خود را ببینید.
افزودن Assets های سفارشی به یک بیلد واقعی
برای تکمیل آموزش استفاده از Streaming assets ، تعدادی asset های سفارشی را به یک بیلد واقعی اضافه خواهید کرد.
تمام asset های سفارشی را از پوشه Project Window StreamingAssets حذف کنید و روی File\Build Settings کلیک کنید.
اطمینان حاصل کنید که پلتفرم هدف به درستی به پلتفرمی که روی آن کار می کنید اختصاص داده شده است و روی Build and Run کلیک کنید.
شما به نقطه اول برگشتید! با این حال، این کد هنوز برای کنترل هر گونه سفارشی سازی که می خواهید اضافه کنید، وجود دارد.
به پوشه StreamingAssets در Player Build بروید.
در رایانه شخصی، به پوشه accompanying با نام _Data نگاهی بیندازید.
هر یک یا همه asset های سفارشی را که از دانلود پروژه starter استفاده کردهاید (در زیر پوشه TankArenaAllAssets) رها کنید.
پلیر را دوباره راه اندازی کنید.
سفارشی سازی ها باید به درستی اعمال شوند و سطوح سفارشی بارگیری شوند.
مهرشاد شادان مهر
مدرس سئو ، طراح سایت ، انیماتور
قهرمان زندگی شما در چند سال آینده ی شما می باشد