کاربرد لِرپ در یونیتی

قبل از شروع، طبق روندی که تا الان داشتیم فایل‌های این پروژه رو به گیت هاب آپلود کردم پس می‌تونید اون رو از این لینک دانلود کنید یا با نرم‌افزار گیت هاب دسکتاپ بازش کنید که بهتره چون آپدیت‌های بعدی رو هم می‌تونید راحت بگیرید. اسم فایل هم LerpExamples ﻪ که در پوشه‌ی Scenes قرار داره.

اول لِرپ چیه؟ خیلی ساده اگه بگم با لِرپ میشه نوعی حرکت یا انیمیشن ایجاد کرد. البته این انیمیشن حالت خطی داره چون موضوع داخل صحنه قراره از نقطه‌ی اول به نقطه‌ی دوم حرکت کنه یا رنگی که الان داره به رنگ دوم تغییر کنه و برای اجرای این تغییر جدا از نقطه‌های شروع و پایان باید مشخص کنیم که این کار در چه زمانی انجام بشه. در کل نحوه‌ی نوشتن لِرپ شبیه به نمونه‌ی زیرِ. جای اون ایکس‌ها هم چیزای دیگه‌ای قرار می‌گیره که در ادامه توضیح می‌دم.

xxxx.Lerp(startingValue, EndingValue, time);

در اصل کلمه‌ی لِرپ که کوتاه شده‌ی کلمه Linear interpolation ﻪ یه عملکرد یا تابع ریاضیه که از طریق اون میشه نقاط جدیدی رو در بین دو نقطه مشخص شده ایجاد کرد. مثلا اگه شما دو عدد داشته باشید و بخواید عدد بین اونها رو بدونید کافیه که عدد اول رو در ½ ضرب کنید و با عدد دوم که اون هم در ½ ضرب شده جمع کنید. این می‌تونه ادامه داشته باشه و در نهایت تعداد زیادی نقاط جدید بین اون دو عدد اولیه مشخص کرد. به دلیل همین ماهیت ریاضی گونه‌ای که لِرپ داره اون رو داخل کلاسی از یونیتی قرار دادن به اسم Mathf که کوتاه شده‌ی کلمه‌ی Math Function ﻪ. این رو هم اشاره کنم که در کلاس Mathf  مجموعه‌ای از توابع ریاضی رو جمع کردن که به طور معمول در توسعه‌ی بازی‌ها استفاده می‌شن مثل توابع لگاریتمی، مثلثاتی (سینوس، کسینوس، تانژانت و غیره)، اینترپولیشنی که در پارسی بهش درون‌یابی میگن (انواع لِرپ و غیره)، ریشه‌های دوم اعداد، توان‌ها و غیره.

با توجه به توضیح بالا الان باید به جای اون ایکس‌ها اسم کلاسی که تابع لِرپ داخلش تعریف شده رو قرار بدیم که البته غیر از کلاس Mathf کلاس‌های دیگه‌ای مثل Vector3 و Color هم مِتد لِرپ رو داخل خودشون تعریف کردن که کاربردشون رو بعدن توضیح می‌دم.

Mathf.Lerp(startingValue, EndingValue, time);

یه نکته‌ی خیلی مهم متغیر سوم یا همون زمانه که همیشه بین صفره و یک ﻪ یعنی وقتی به یک برسه لِرپ متوقف میشه پس برای اینکه مثلا کاری چهار ثانیه طول بکشه باید زمان طی شده از ابتدای اجرا رو داشته باشیم تا با تقسیم اون بر عدد چهار اعداد زیر یک رو ایجاد کنیم تا زمانی که از تقسیم این دو بر هم به یک برسیم.

در مثال پایین قراره که تغییر اعداد بین صفر تا ده در چهار ثانیه نشون داده بشه. پس ابتدا متغیرهای نقطه‌ی شروع، نقطه‌ی پایان و زمانی که در اون تغییر اتفاق می‌افته رو تعریف می‌کنم یعنی خط‌های ۷ و ۸ و ۹

برای اینکه بتونم زمان طی شده رو داشته باشیم متغیر خط ۱۱ رو تعریف می‌کنیم. خط ۱۲ نتیجه‌ی اجرای لِرپ رو ذخیره می‌کنه. اما خط ۱۴ چون فکر کردم بهتره که نمایش تغییر اعداد در زمان اجرا به صورت اعداد صحیح باشه این متغیر رو تعریف کردم تا اعداد اعشاری که در متغیر خط ۱۲ ذخیره میشه رو به اعداد صحیح تبدیل کنه. این رو بدونید که لِرپ فقط اعداد اعشاری رو قبول می‌کنه و خودش هم یک تابع اعشاریه.

using System;
using TMPro;
using UnityEngine;

public class lerp1stExample : MonoBehaviour
{
    float startingValue = 0f;
    float EndingValue = 10f;
    float changingDuration = 4f;

    float timePast;
    float lerpValue;

    int toDisplayOnScreen;
    public TMP_Text myNumbersOnScreen;

    void Update()
    {
        lerpValue = Mathf.Lerp(startingValue, EndingValue, timePast / changingDuration);
        timePast += Time.deltaTime;

        toDisplayOnScreen = (int)lerpValue;
        myNumbersOnScreen.text = toDisplayOnScreen.ToString() + "  Changing numbers";
    }
}

در خط ۱۵ برای اینکه بتونیم به کامپوننت text در کلاس TextMeshPro دسترسی داشته باشیم که متن داخلش رو تغییر بدیم این متغیر رو باید تعریف کنیم نوعش هم public ﻪ که اجازه میده متنی که در یونیتی ساختیم رو بهش وصل کنیم مثل تصویر زیر:

connect TMP

در مورد Time.deltaTime در خط ۲۰ رو هم اینجا بخونید.

اما برای حرکت دادن یه مکعب که در یه نقطه‌ای در فضای سه‌بعدی بر اساس سه مقدار xyz قرار گرفته به جای کلاس Mathf از کلاس Vector3 و متد لِرپ اون استفاده می‌کنیم. چون Vector3 سه مقدار می‌گیره برای هر چیزی که سه مقداریه مناسبه مثل چرخش و اندازه.

using UnityEngine;

public class lerp2ndExample : MonoBehaviour
{
    Vector3 startingPosition;
    Vector3 EndingPosition = new Vector3(2f, 0.1f, 0.2f);
    float changingDuration = 4f;
    float timePast;

    void Start()
    {
        startingPosition = transform.position;
    }
    void Update()
    {
        transform.position = Vector3.Lerp (startingPosition, EndingPosition, timePast / changingDuration);
        timePast += Time.deltaTime;  
    }
}

در مثال بالا حرکت مکعب حالتی کاملن خطی داره ولی اگه خط ۱۶ رو با خط زیر جایگزین کنیم حرکت ابتدا کند و بعد شتاب می‌گیره و در انتها هم دوباره کند میشه ولی زمان تغییری نمی‌کنه.

transform.position = Vector3.Lerp (startingPosition, EndingPosition, Mathf.SmoothStep (0, 1, timePast / changingDuration));

یه اسم دیگه‌ی متد Smoothstep که در خط بالا اضافه شده در نرم‌افزارهای انیمیشن ease in / ease out ﻪ که معمولن در قسمت کنترل انیمیشن می‌تونید ازش استفاده کنید. در نمودار زیر تفاوت پارامتر t یا همون زمان رو در دو حالت خطی و منحنی نوع Smoothstep می‌تونید مقایسه کند. همون طور که اول این نوشته هم توضیح دادم متغیر زمان بین صفر و یک ﻪ به همین دلیل نمودارها رو بین صفر و یک تنظیم کردم. اما چرا دو خط بالاتر بهش گفتم پارامتر t چون معمولن به صورت f(t) = t نوشته میشه که اینجا t پارامتر فانکشن f محسوب میشه به این معنی که ما اینجا یه function داریم که t رو به عنوان پارامتر می‌گیره و یه مقدار اعشاری رو بر می‌گردونه. در حالت خطی یا Linear فانکشن ما دقیقن f(t) = t یعنی هر مقداری به t بدیم همون رو بر می‌گردونه ولی در حالت Smoothstep فانکشن ما به صورت f(t) = 3t^2 – 2t^3 عمل می‌کنه. اگه خواستید بیشتر در مورد Smoothstep بدونید اینجا کلیک کنید.

Linear vs Smoothstep

در یونیتی غیر از مِتد Linear که به صورت پیشفرض برای متغیر زمان در لِرپ اجرا میشه و مِتد Smoothstep که بسیار پر کاربرده، مِتدها یا فانکشن‌های دیگه‌ای هم هستن که داخل کلاس Mathf یا Vector3 تعریف نشدن مثل Elastic Out یا Parabola و یا Bounce Out که در نمودارهای زیر می‌بینید.

Linear vs Smoothstep

خب حالا سوال پیش میاد که چطور مثلن یه مِتد مشابه نمودار Bounce Out بنویسم که با اون متغیر زمان در لِرپ رو کنترل کنم. جواب اینه که نیازی به نوشتنش ندارید چون قبلن یه نفر این کار رو انجام داده و شما فقط باید اون رو داخل کلاس خودتون اضافه کنید. در این صفحه‌ی گیت هاب شخصی به اسم سی.جی کیمبرلین ۳۲ مِتد مناسب استفاده در یونیتی رو به اشتراک گذاشته که کافیه مِتدی که می‌خواید رو از داخلش پیدا کنید و مثل نمونه‌ی زیر استفاده کنید:

using UnityEngine;

public class lerp3rdExample : MonoBehaviour
{
    Vector3 startingPosition;
    Vector3 EndingPosition = new Vector3(2f, -0.4f, 0.2f);
    float changingDuration = 4f;
    float timePast;

    void Start()
    {
        startingPosition = transform.position;
    }
    void Update()
    {
        transform.position = Vector3.Lerp(startingPosition, EndingPosition, EaseOutBounce (0, 1, timePast / changingDuration));
        timePast += Time.deltaTime;
    }

    public static float EaseOutBounce(float start, float end, float value)
    {
        value /= 1f;
        end -= start;

        if (value < 1 / 2.75f)
            return end * (7.5625f * value * value) + start;

        if (value < 2 / 2.75f)
        {
            value -= 1.5f / 2.75f;
            return end * (7.5625f * value * value + .75f) + start;
        }

        if (value < 2.5 / 2.75)
        {
            value -= 2.25f / 2.75f;
            return end * (7.5625f * (value) * value + .9375f) + start;
        }

        value -= 2.625f / 2.75f;

        return end * (7.5625f * value * value + .984375f) + start;
    }
}

اگه فایل پروژه رو دانلود کردید این سکریپت به مکعب Cube-3 در صحنه وصله.

این رو هم در نظر داشته باشید که شما محدود به این تعداد مِتد نیستید و می‌تونید با ترکیب دو یا چند تا از این مِتدها مِتدهای جدیدی بسازید و ازشون استفاده کنید. شاید اگه فرصت شد در نوشته‌ی جداگانه‌ای نحوه‌ی ترکیب مِتدها رو مخصوصن برای استفاده در یونیتی توضیح بدم.

حالا این سوال به ذهن میاد که آیا راه ساده‌تری وجود نداره که ما رو محدود به تعدادی مِتد نکنه و دست ما رو در کنترل متغیر زمان در لِرپ بازتر کنه. برای این کار در یونیتی کلاسی وجود داره به اسم AnimationCurve که امکان کنترل زمان رو از طریق شکلی که ما ایجاد می‌کنیم رو میده. البته این تمام کاری نیست که این کلاس انجام میده ولی فعلن این قسمتش برای ما کاربردیه. حال کافیه که مثل نمونه‌ی زیر متغیری به اسم curve یا هر اسم دلخواه دیگه‌ای به صورت public تعریف و در خط ۱۸ مِتد Evaluate رو برای کنترل زمان به لِرپ اضافه کنید. حالا اگه در یونیتی به تنظیمات سکریپت در قسمت Inspector دقت کنید می‌بینید که گزینه curve بهش اضافه شده و با کلیک روی اون پنجره‌ای باز میشه که بصورت گرافیکی امکان ایجاد انواع شکل به صورت خطی یا منحنی رو به شما میده.

using UnityEngine;

public class lerp4thExample : MonoBehaviour
{
    Vector3 startingPosition;
    Vector3 EndingPosition = new Vector3(2f, -1f, 0.2f);
    float changingDuration = 4f;
    float timePast;

    public AnimationCurve curve;

    void Start()
    {
        startingPosition = transform.position;
    }
    void Update()
    {
        transform.position = Vector3.Lerp(startingPosition, EndingPosition, curve.Evaluate(timePast / changingDuration));
        timePast += Time.deltaTime;
    }
}

اگه سوالی داشتید پایین بپرسید.

پاسخی بگذارید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *