آرمان
آرمان
خواندن ۱۲ دقیقه·۵ سال پیش

لرزش دوربین در unity3d

خوب دوستان کسانی که نمی دانند که unity3d چی هست باید بگویم یک موتور بازی سازی به زبان سی شارپ هست که می توانید انواع بازی برای انواع دستگاهها و کنسولها بسازید و برای کارهای تکنیکی هم استفاده می شود. اگر اطلاعات بیشتری می خواهید سرچ کنید.

یاد خواهید گرفت که یک اسکریپت را برای شبیه سازی لرزش دوربین بنویسید. شما برای کنترل فرکانس و بزرگی اثر لرزش از Perlin noise استفاده خواهید کرد.

لرزش دوربین می تواند ابزاری قدرتمند برای برقراری ارتباط ضربه یا موج شوک به بازیکنان باشد.در این مقاله ، تکنیک های گام به گام برای اجرای یک اثر لرزش پویا کنترل شده توسط Perlin noise ، بر اساس این GDC talk توسط Squirrel Eiserloh ارائه خواهد شد. در حالی که هدف از این مقاله اجرای لرزش دوربین است ، می توان از همان تکنیک ها برای اعمال حرکات لرزش بر روی هر نوع شی یا عنصر UI استفاده کرد.

پروژه تکمیل شده در پایان مقاله ارائه شده است. توجه داشته باشید که همچنین حاوی مقدار زیادی از comment در فایلهای C# برای کمک به درک است.

پیش نیازها

برای تکمیل این آموزش ، به دانش لازم در زمینه موتور Unity و C# نیاز دارید.

Download starter project .zip

شروع

پروژه استارت ارائه شده در بالا را دانلود کنید ، آن را در ویرایشگر Unity باز کنید و صحنه Main را باز کنید.اگر روی شیء CameraContainer را کلیک کرده و دوربین را انتخاب کنید ، اسکریپت ShakeableTransform را که به آن متصل است ، خواهید دید.این اسکریپت اثر لرزش دوربین را کنترل می کند. آن را در ویرایشگر کد مورد نظر خود باز کنید.

1. حرکت تصادفی

از آنجا که دوربین ما در فضای سه بعدی وجود دارد ، ما شش محور در دسترس داریم که می توانیم آن را تکان دهیم: سه translational (حرکت) و سه angular/زاویه ای (چرخش).ما با چند حرکت تصادفی ساده در محور X شروع خواهیم کرد. موارد زیر را به متد Update از ShakeableTransform اضافه کنید.

transform.localPosition = new Vector3(Random.Range(-1, 1), 0, 0) * 0.3f;

ما یک مقدار تصادفی بین -0.2 و 0.2 را به موقعیت X دوربین اختصاص می دهیم و دو بعد دیگر را در 0 می گذاریم.


این در واقع نتیجه نسبتاً موثری داشته است. برای دیدن خروجی کامل ، محورهای Y و Z را اضافه خواهیم کرد. خط قبلی کد را با موارد زیر جایگزین کنید.

transform.localPosition = new Vector3(Random.Range(-1, 1), Random.Range(-1, 1), Random.Range(-1, 1)) * 0.3f;

هر سه محور در کنار هم یک لرزش دوربین قانع کننده ایجاد می کنند.برای برخی از برنامه ها ، این ممکن است یک راه حل به اندازه کافی رضایت بخش باشد (لرزش زاویه ای را می توان به همان روش اضافه کرد).با این حال ، این روش دارای کاستی هایی است: یک موقعیت کاملاً تصادفی کاملاً جدید ایجاد می شود و بلافاصله به هر فریم پرش می شود. هیچ دخالتی بین موقعیت جدید و قدیمی وجود ندارد.

افزایش ضریب مقیاس از 0.2f به عدد بیشتر ، مانند 1 یا 2 ، این موضوع را آشکارتر می کند و می تواند حرکتی بسیار جدا از هم ایجاد کند و کنترل سرعت لرزش دوربین را غیرممکن می کند.

تنظیم ضریب مقیاس در 2 اجازه می دهد تا پرش هایی بین موقعیت دوربین در هر فریم ایجاد شود و جلوه ای بسیار ناسازگار ایجاد کند.
تنظیم ضریب مقیاس در 2 اجازه می دهد تا پرش هایی بین موقعیت دوربین در هر فریم ایجاد شود و جلوه ای بسیار ناسازگار ایجاد کند.


برای برطرف کردن این مسئله ، برای جلوگیری از جهش های بزرگ ، باید مقادیر تصادفی که حفظ تداوم/maintain continuity را محاسبه می کند- از این رو که مقادیر قبلی بر روی بعدی تأثیر می گذارد - محاسبه کنیم.ما با استفاده از Perlin noise به این هدف می رسیم.

2. حرکت Perlin noise

هدف ما تولید یک سری مقادیر است که بصورت تصادفی به نظر می رسد اما استمرار را حفظ می کند ، به جای یک مقدار کاملاً تصادفی برای هر فریم.Perlin noise برای این کار ایده آل است.Unity تابعی را برای تولید Perlin noise از ورودی دو بعدی در Mathf.PerlinNoise پیاده سازی می کند.

به عنوان ورودی یک Vector2 میگیرد ، Mathf.PerlinNoise مقدار شبه تصادفی را در دامنه 0 ... 1 برمی گرداند.نکته مهم اینجاست که مقادیر ورودی مشابه منجر به خروجی مشابه می شوند. این مزیت اصلی Perlin noise به Random.Range است ، جایی که مقادیر بازگشتی را نمی توان با یک ورودی کنترل کرد.

مقایسه Random.Range (سمت چپ) و Mathf.PerlinNoise (راست). توجه داشته باشید که اگرچه Perlin noise بسیار نرم تر است ، اما هنوز هم مجموعه ای بینظم از مقادیر ایجاد می کند.
مقایسه Random.Range (سمت چپ) و Mathf.PerlinNoise (راست). توجه داشته باشید که اگرچه Perlin noise بسیار نرم تر است ، اما هنوز هم مجموعه ای بینظم از مقادیر ایجاد می کند.


چرا می گوییم مقادیر Perlin noise شبه تصادفی است؟ این یعنی چی؟

اگرچه مقادیر برگشت داده شد توسط Perlin noise تصادفی به نظر می رسند ، به این دلیل که آنها الگوهای قابل تشخیصی ندارند ، function کاملاً قطعی است. به این شبه تصادفی/pseudorandomness گفته می شود.

توجه داشته باشید که Random.Range نیز در واقعیت ، شبه تصادفی نیز هست.در حقیقت ، در خارج از استفاده سخت افزار تخصصی ، تمام توالی های عدد تصادفی ایجاد شده توسط توابع رایانه می توانند در صورت ارائه تابع در همان حالت اولیه تکرار شوند.Unity به شما امکان می دهد وضعیت تولید کننده شماره تصادفی آن را با پراپرتی Random.state کنترل کنید.

ما یک بار دیگر لرزش را به محور X اضافه خواهیم کرد ، اما این بار با استفاده از Perlin noise. خط کد موجود را با موارد زیر جایگزین کنید.

transform.localPosition = new Vector3(Mathf.PerlinNoise(0, Time.time) * 2 - 1, 0, 0) * 0.5f;

اگرچه Mathf.PerlinNoise یک Vector2 را به عنوان ورودی در نظر می گیرد ، ما مقدار X را 0 ثابت نگه می داریم. پیمایش از طریق noise در یک محور واحد با استفاده از Time.time ورودی کافی است. توجه داشته باشید که Mathf.PerlinNoise مقداری را در دامنه 0 ... 1 باز می گرداند. ما قبل از استفاده از مقدار ، آن را به محدوده -1 ... 1 تبدیل می کنیم.

این حرکت نرم و صاف تری ایجاد کرده است ، اما با سرعت قابل توجهی کندتر. ما یک فیلد جدید را برای کنترل سرعت لرزش اضافه خواهیم کرد.

// Add as a new field. [SerializeField] float frequency = 25; … // Modify the existing code in Update(). transform.localPosition = new Vector3(Mathf.PerlinNoise(0, Time.time * frequency) * 2 - 1, 0, 0) * 0.5f;

مقادیر بزرگتر frequency باعث لرزش سریعتر خواهد شد. مانند گذشته ، دو محور دیگر را نیز در آن اضافه خواهیم کرد.

// Replace the previous line setting transform.localPosition. transform.localPosition = new Vector3(Mathf.PerlinNoise(0, Time.time * frequency) * 2 - 1, 0, 0) * 0.5f; transform.localPosition = new Vector3( Mathf.PerlinNoise(0, Time.time * frequency) * 2 - 1, Mathf.PerlinNoise(0, Time.time * frequency) * 2 - 1, Mathf.PerlinNoise(0, Time.time * frequency) * 2 - 1 ) * 0.5f;

از آنجا که مقادیر برگشتی توسط Mathf.PerlinNoise قطعی هستند ، اجرای عملکرد با ورودیهای یکسان برای هر محور باعث شده است که هر سه محور دارای یک مقدار لرزش یکسان باشند.تنظیم frequency به مقدار 1 باعث می شود که دوربین به جای تکان دادن به طور تصادفی در یک مسیر مورب ثابت حرکت کند. ما این مسئله را با افزایش مقدار X ورودی خود برطرف می کنیم.

transform.localPosition = new Vector3( Mathf.PerlinNoise(0, Time.time * frequency) * 2 - 1, Mathf.PerlinNoise(1, Time.time * frequency) * 2 - 1, Mathf.PerlinNoise(2, Time.time * frequency) * 2 - 1 ) * 0.5f;

این مسئله مشکل را برطرف کرده است ، اما یک مشکل احتمالی در آینده ایجاد کرده است: اگر چندین مورد ShakeableTransform در صحنه وجود داشته باشد ، همه آنها با همان حرکت یکسان می لرزند.با محاسبه یک seed تصادفی در هر نمونه می توان از این امر جلوگیری کرد تا ورودی را جبران کند.

// Add as a new field and method. private float seed; private void Awake() { seed = Random.value; } … // Modify the existing code in Update(). transform.localPosition = new Vector3( Mathf.PerlinNoise(seed, Time.time * frequency) * 2 - 1, Mathf.PerlinNoise(seed + 1, Time.time * frequency) * 2 - 1, Mathf.PerlinNoise(seed + 2, Time.time * frequency) * 2 - 1 ) * 0.5f;

در حال حاضر ما در حال مقیاس پذیری کل اثر لرزش با یک ثابت 0.5 هستیم.این باید در یک فیلد ذخیره شود و در معرض inspector قرار گیرد.در حالی که می توانستیم از یک float استفاده کنیم - مانند گذشته - اگر به جای آن از یک Vector3 استفاده کنیم ، می توانیم هر محور را به صورت جداگانه مقیاس پذیر کنیم.

// Add as a new field. [SerializeField] Vector3 maximumTranslationShake = Vector3.one * 0.5f; … transform.localPosition = new Vector3( maximumTranslationShake.x * (Mathf.PerlinNoise(seed, Time.time * frequency) * 2 - 1), maximumTranslationShake.y * (Mathf.PerlinNoise(seed + 1, Time.time * frequency) * 2 - 1), maximumTranslationShake.z * (Mathf.PerlinNoise(seed + 2, Time.time * frequency) * 2 - 1) ) * 0.5f;

تاکنون ، ما فقط لرزش translational را پیاده سازی کرده ایم. قبل از ادامه ، ما با استفاده از همان تکنیک ها در لرزش angular/زاویه ای اضافه خواهیم کرد.

// Add as a new field. [SerializeField] Vector3 maximumAngularShake = Vector3.one * 2; … // Add below the code assigning transform.position. transform.localRotation = Quaternion.Euler(new Vector3( maximumAngularShake.x * (Mathf.PerlinNoise(seed + 3, Time.time * frequency) * 2 - 1), maximumAngularShake.y * (Mathf.PerlinNoise(seed + 4, Time.time * frequency) * 2 - 1), maximumAngularShake.z * (Mathf.PerlinNoise(seed + 5, Time.time * frequency) * 2 - 1) ));

لرزش با حرکت و چرخش همیشه لازم نیست.برای 3D ، اغلب لرزش translational نیز نامطلوب است ، زیرا حرکت می تواند باعث شود دوربین از طریق اشیاء مجاور بچرخد.

3. لرزیدن از trauma

در حال حاضر ، دوربین بدون هیچ گونه ورودی خارجی به طور مداوم می لرزد.اگرچه این ممکن است برای یک زمین لرزه یا صحنه مشابه مناسب باشد ،غالباً دلپذیر است که دوربین از نزدیکی اثرات و تأثیرات نزدیک تکان بخورد.برای تحقق این امر ، میزان لرزش دوربین را با مقداری به نام trauma کنترل خواهیم کرد.

تروما/trauma یک مقدار float در محدوده 0 ... 1 خواهد بود که در قدرت لرزش ما ضرب خواهد شد.همچنین ، trauma به طور مداوم رو به کاهش خواهد بود ،این امکان را به دوربین می دهد که هنگام وقوع از اثرات بازیابی شود.

// Add as a new field. [SerializeField] float recoverySpeed = 1.5f; … // We set trauma to 1 to trigger an impact when the scene is run, // for debug purposes. This will later be changed to initialize trauma at 0. private float trauma = 1; … // Modify the existing code in Update(). transform.localPosition = new Vector3( maximumTranslationShake.x * (Mathf.PerlinNoise(seed, Time.time * frequency) * 2 - 1), maximumTranslationShake.y * (Mathf.PerlinNoise(seed + 1, Time.time * frequency) * 2 - 1), maximumTranslationShake.z * (Mathf.PerlinNoise(seed + 2, Time.time * frequency) * 2 - 1) ) * trauma; transform.localRotation = Quaternion.Euler(new Vector3( maximumAngularShake.x * (Mathf.PerlinNoise(seed + 3, Time.time * frequency) * 2 - 1), maximumAngularShake.y * (Mathf.PerlinNoise(seed + 4, Time.time * frequency) * 2 - 1), maximumAngularShake.z * (Mathf.PerlinNoise(seed + 5, Time.time * frequency) * 2 - 1) ) * trauma); trauma = Mathf.Clamp01(trauma - recoverySpeed * Time.deltaTime);

اکنون صحنه را اجرا کنید،خواهید دید که شدت لرزش با گذشت زمان از بین می رود. هرچه یک recoverySpeed بزرگتر باشد ، اثر کوتاهتر دوام خواهد داشت.

وقتی صحنه به دلیل بارگیری در یک ویرایشگر اجرا می شود ، Unity چندین فریم را رد می کند. برای جلوگیری از این ، قبل از فشار دادن Play ، مکث/Pause را فشار دهید. با این کار صحنه به طور کامل بارگذاری می شود ، در این زمان می توانید برای اجرای صحنه Unpause کنید.

لرزش دوربین در حال حاضر بسیار ناگهانی به پایان می رسد. ما می توانیم با بالا بردن trauma به یک توان^ ، یک لرزش نرم تر ایجاد کنیم.

// Add as a new field. [SerializeField] float traumaExponent = 2; … // Add to the top of Update(). float shake = Mathf.Pow(trauma, traumaExponent); // Modify the existing code. transform.localPosition = new Vector3( maximumTranslationShake.x * (Mathf.PerlinNoise(seed, Time.time * frequency) * 2 - 1), maximumTranslationShake.y * (Mathf.PerlinNoise(seed + 1, Time.time * frequency) * 2 - 1), maximumTranslationShake.z * (Mathf.PerlinNoise(seed + 2, Time.time * frequency) * 2 - 1) ) * shake; transform.localRotation = Quaternion.Euler(new Vector3( maximumAngularShake.x * (Mathf.PerlinNoise(seed + 3, Time.time * frequency) * 2 - 1), maximumAngularShake.y * (Mathf.PerlinNoise(seed + 4, Time.time * frequency) * 2 - 1), maximumAngularShake.z * (Mathf.PerlinNoise(seed + 5, Time.time * frequency) * 2 - 1) ) * shake);

این تأثیر در حال حاضر با شروع اولیه trauma به 1 هدایت می شود.برای اینکه اشیاء موجود در جهان بتوانند اثرات خود را برسانند ، ما می توانیم با اضافه کردن یک متد عمومی trauma را اضافه کنیم.کد زیر را به کلاس ShakeableTransform اضافه کنید ...

// Modify trauma to initialize to 0. private float trauma = 0; … // Add at the bottom of the class, just before the closing curly brace. public void InduceStress(float stress) { trauma = Mathf.Clamp01(trauma + stress);

... و کد زیر را به اسکریپت Explosion اضافه کنید.

// Add at the end of Start(). target.InduceStress(1);

سرانجام ، در صحنه/scene ، هدف بازی ExplosionNear را فعال/active کنید. این یک اثر ذره/particle انفجاری است که باعث تاخیر کوتاه پس از اجرای صحنه می شود.

https://giant.gfycat.com/RegularDishonestArachnid.webm

این بسیار مؤثر است؛ با این حال ، فاصله بین انفجار و دوربین فاصله نمی گیرد. اشیاء ExplosionMiddle و ExplosionFar را فعال کنید و صحنه را اجرا کنید. هر سه ضربه به همان اندازه از trauma ایجاد می کنند - ما هنگام اعمال ضربه ، کد خود را به جای فاصله ضریب تغییر می دهیم.

// Add as a new field. [SerializeField] float range = 45; … // Add just below the GetComponent line in Start(). float distance = Vector3.Distance(transform.position, target.transform.position); float distance01 = Mathf.Clamp01(distance / range); target.InduceStress(1 - distance01);

فاصله بین موقعیت انفجار و دوربین گرفته شده. سپس با حداکثر شعاع ضربه/impact تقسیم می شود تا یک مقدار در محدوده 0 ... 1 بدست آید.این مقدار معکوس شده/inverted است و باعث می شود که distance01 در هنگام انفجار به طور مستقیم در نزدیکی دوربین حداکثر شود و در صورت دور شدن از آن به حداقل برسد.

https://thumbs.gfycat.com/CourteousDimpledFrigatebird-mobile.mp4

میزان فشار اعمال شده اکنون با فاصله انفجار از دوربین تعدیل می شود. میزان لرزش با تنظیمات فعلی کمی زیاد می شود. انفجار نهایی به سختی احساس می شود.

این امر باعث انحراف نرمی می شود ، جایی که انفجارهای دور باعث لرزش کمتری می شوند ، اما همچنین باعث شده است که trauma به طور قابل توجهی کمتر به دوربین اعمال شود.. ما این را با به روزرسانی برخی از فیلد های ShakeableTransform برطرف می کنیم.

حداکثر لرزش Maximum Translation Shake به (1 ، 1 ، 1) تنظیم خواهد شد ، حداکثر لرزش زاویه ای Maximum Angular Shake به (15, 15, 10) و Trauma Exponent به 1 ، ناپدید شدن نمایی در حال حاضر به اندازه نیاز نیست.(این تنظیمات مورد استفاده در انیمیشن در بالای این مقاله است.)

قبل از پیچیدن ، ما فیلدی را برای کنترل حداکثر فشار ممکن ناشی از انفجار اضافه خواهیم کرد و distance01 را به یک معادله ساده اضافه می کنیم تا مقداری از انحراف را اضافه کنیم ، مشابه کاری که با trauma در ShakeableTransform انجام دادیم.

// Add as a new field. [SerializeField] float maximumStress = 0.6f; … // Add immediately below the line declaring distance01. float stress = (1 - Mathf.Pow(distance01, 2)) * maximumStress; target.InduceStress(stress);

یک بار دیگر ، ما از بتوان^ رساندن برای کنترل افت فشار استفاده می کنیم و distance01 را به توان 2 می رسانیم.توجه داشته باشید که این عمل را قبل از معکوس کردن مقدار انجام می دهیم.این یک منحنی انحراف محدب(convex falloff curve) را ایجاد می کند که از انفجارهای اطراف استفاده می کند و باعث ایجاد فشار بیشتر از آنچه که با یک منحنی خطی می شوند می شود. بتوان رساندن پس از معکوس نتیجه مخالف می دهد.از طرف دیگر ، می توانستیم از یک تابع نرم کننده(smoothing function) متفاوت مانند Mathf.SmoothStep استفاده کرده و یا به صورت دستی منحنی انحراف را با استفاده از کلاس AnimationCurve ایجاد کنیم.

مقایسه انحراف trauma بین معکوس پس از گرفتن توان (1 - Mathf.Pow (distance، 2) ، سبز)؛ معکوس قبل از گرفتن توان (Mathf.Pow (1 - distance، 2) ، آبی)؛ و Mathf.Smoothstep (قرمز).
مقایسه انحراف trauma بین معکوس پس از گرفتن توان (1 - Mathf.Pow (distance، 2) ، سبز)؛ معکوس قبل از گرفتن توان (Mathf.Pow (1 - distance، 2) ، آبی)؛ و Mathf.Smoothstep (قرمز).


نتیجه

جلوه های لرزش - چه به صورت تصادفی و چه از طریق noise شبه تصادفی - می توانند بیش از اثرات دوربین استفاده شوند.صدای کم فرکانس پایین (lower frequency noise) می تواند یک دوربین دستی را شبیه سازی کند.عناصر UI می توانند از لرزش نیز بهره مند شوند. به متن گفتگو اضافه/added to dialogue text شوند ، حرکات تند و تیز می تواند احساس ترس یا وحشت ایجاد کند ، در حالی که هر بار که صدمه وارد شود ، می تواند نشانگرهای سلامتی/health indicators can shake بازیکن را لرزاند.

View source GitHub repository

منبع

unity3dc
یک برنامه نویس که هرآنچه را که یاد میگیرد در دفترچه یادداشت ویرگولیش یادداشت میکرد(!) حتی یک خط ! تا درصورت نیاز به آن رجوع کند...
شاید از این پست‌ها خوشتان بیاید