شما اینجا هستید: خانه » آموزش های پروژه محور » دوره ی آموزش بازی سازی دو بعدی در یونیتی » آموزش ساخت بازی دو بعدی (جلسه ی پنجم)

آموزش ساخت بازی دو بعدی (جلسه ی پنجم)

برای دیدن جزئیات تمام جلسه های این دوره اینجا کلیک کنید .

پیش از گذراندن این درس حتما دروس قبلی را مطالعه کنید . 

پیمایش بازی (Parallax scrolling)

ما قبلا بازی ساختیم با صحنه و پس زمینه ی ثابت که مطمئنا چنین حالتی خسته کننده خواهد بود بیایید کمی با پس زمینه ی کار کنیم و آنرا بهتر کنیم

چیزی که از ۱۵ سال پیش در تمام بازی های دوبعدی دیده میشود و پس زمینه ی بازی ها را به حالت متحرک یا نمایش پیمایشی در می آورد Parallax scrolling نام دارد 

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

اگر این کار به درستی انجام بگیرد یک عمق خیالی بسیار جالبی در بازی دو بعدی ایجاد خواهد کرد. 

 بیایید درون یونیتی این قابلیت را به بازی خودمان اضافه کنیم 

اضافه کردن پیمایش به بازی : 

اضافه کردن محور پیمایش نیازمند تفکر در مورد نحوه ی استفاده از این گزینه ی جدید میباشد 

همیشه بهتر هست قبل از عمل در مورد آن فکر کنید 🙂

ما چه کاری میخواهیم انجام دهیم ؟ 

  •  انتخاب اول : دوربین و کاراکتر حرکت بکنند و پس زمینه ثابت باشد 
  • انتخاب دوم : دوربین و کاراکتر ثابت باشد ولی مرحله بصورت تردمیل حرکت کند 

انتخاب اول ذهنیت خوبی نسبت به دوربین از نوع Perspective  ندارد چون که المان های پس زمینه عمق بالاتری دارند پس آنها که پست هستند احساس حرکت بسیار آرام را منتقل میکنند 

ولی در بازی دو بعدی استاندارد یونیتی ما از دوربین از نوع Orthographic  استفاده میکنیم که هیچ عمقی در کار نیست 

درباره ی دوربین باید بهتون بگم که خصوصیت Projection نوع دوربین را مشخص میکنه دو نوع دوربین داریم که دید مختلفی دارند 

یکی Prespective هست و همانطور که از اسمش پیداست عمق تصویر را نشان میدهد و برای بازی های سه بعدی مناسب هست و یک نوع Ortoghraphic داریم که هیچ عمقی ندارد و تمام اشیا را بدون در نظر گرفتن فاصله ی آنها از دوربین نمایش میدهد .

در نهایت برای اضافه کردن پیمایش به بازی خودمان باید از هر دو نوع بصورت مخلوطی استفاده کنیم که ما دو حالت اسکرول خواهیم داشت : 

  • کاراکتر در حال حرکت به سمت جلو هست به همراه دوربین
  • المان های پس زمینه با سرعت های مختلفی حرکت میکنند 

 

ریزش دشمن ها در بازی در یونیتی (Spawning )

اضافه کردن پیمایش به بازی عواقبی با خود دارد  مخصوصا در رابطه با دشمن های بازی . در حال حاضر دشمن های بازی با شروع بازی فقط حرکت میکنند و شلیک میکنند . ما میخواهیم آنها بدون آسیب دیدن منتظر بمانند تا ریزش شوند یا اصطلاحا spawn رخ بدهد. 

چطور میتوانیم دشمن را spawn کنیم ؟ 

این مورد قطعا به بازی شما بستگی دارد ،  شما میتوانید رخ دادهایی را برای  زمان spawn شدن دشمن ، موقعیت مکانی شروع آنها و دیگر موارد را به دلخواه خود انجام دهید .

اینجا کاری که ما انجام خواهیم داد : 

ما Poulpies  را بصورت مستقیم درون صحنه قرار میدهیم (بصورت مستقیم با درگ کردن prefab مربوط به آن) . بصورت پیشفرض آنها ثابت و بدون آسیب پذیری هستند تا زمانی که دوربین وارد آنها شود و آنها را فعال کند .

camera_use

بهترین ایده این هست که شما با استفاده از ادیتور یونیتی اقدام به تنظیم کاراکتر های دشمن خود بکنید 

plan ها:

ابتدا ، ما باید تعریف کنیم که plan ها اصلا چی هستند و آنها بصورت مداوم تکرار خواهند شد یا نه ؟ تکرار بکگراند باعث میشود که آن  بارها و بارها در طول مرحله پشت سر هم نمایش داده شود . 

برای مثال این مورد برای ساخت چیزی مثل یک آسمان بسیار پرکاربرد هست . 

ما چیزهایی که قرار هست داشته باشیم به شرح زیر هست :

تکرار شونده لایه
بله پس زمینه + آسمان
خیر  پس زمینه (ردیف اول plan ها)
خیر پس زمینه (ردیف دوم plan ها)
خیر 

روی صحنه + بازیکن اصلی بازی + شخصیت دشمن ها

plan های بازی در یونیتی

 

ما باید تعدادی لایه به پس زمینه اضافه بکنیم 

شروع به کدزنی ، ساخت صحنه ی پیمایشی در بازی یونیتی (کدنویسی)

خب ، ما گفتیم که چطوری افکت پیمایش را به صحنه ی بازی خودمان اضافه کنیم . 

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

یونینی بصورت پیشفرض درون پکیج استاندارد خود (standard packages ) . شما میتوانید از آنها استفاده کنید ، ولی ما میخواهیم صحنه ی پیمایشی را اینجا از صفر بسازیم تا شما دقیقا یاد بگیرید !

اسکریپت نمونه 

با قسمت ساده ای شروع خواهیم کرد : پیمایش پس زمینه بدون حلقه ی تکرار . 

MoveScript را بخاطر بیاورید  . پایه ی هر دو یکی هست یک متغیر و مسیر به مرور اضافه خواهیم کرد .

اسکریپت جدید درست کنید و نام آنرا ScrollingScript قرار دهید ، محتویات آن باید به شکل زیر باشد : 

using UnityEngine;

/// <summary>
/// Parallax scrolling script that should be assigned to a layer
/// </summary>
public class ScrollingScript : MonoBehaviour
{
  /// <summary>
  /// Scrolling speed
  /// </summary>
  public Vector2 speed = new Vector2(2, 2);

  /// <summary>
  /// Moving direction
  /// </summary>
  public Vector2 direction = new Vector2(-1, 0);

  /// <summary>
  /// Movement should be applied to camera
  /// </summary>
  public bool isLinkedToCamera = false;

  void Update()
  {
    // Movement
    Vector3 movement = new Vector3(
      speed.x * direction.x,
      speed.y * direction.y,
      ۰);

    movement *= Time.deltaTime;
    transform.Translate(movement);

    // Move the camera
    if (isLinkedToCamera)
    {
      Camera.main.transform.Translate(movement);
    }
  }
}

اسکریپت را به اشیای زیر با مواردی که در جدول زیر داده شده است اختصاص دهید : 

اسکریپت صحنه ی پیمایشی در یونیتی

 

برای قابل درک کردن نتیجه ی کار موارد زیر را انجام دهید : 

  •  یک Background دیگر بعد از دو تای قبلی اضافه کنید 
  • چندعدد platform کوچک به لایه ی Background elements اضافه کنید
  • platform به لایه ی Middleground اضافه کنید 
  • دشمن هایی را در سمت راست لایه ی Foreground اضافه کنید 

نتیحه به شکل زیر خواهد بود : 

صحنه ی پیمایشی در یونیتی

صحنه ی پیمایشی در یونیتی

بد نیست ! ولی ما میبینیم که دشمن ها حرکت کرده و شلیک میکنند زمانی که بیرون از دوربین هستند حتی قبل از این که آنها ریزش شوند .

علاوه بر این ، آنها زمانی که پلایر را رد میکنند بازیابی نمیشوند (زوم کنید و سمت چپ صحنه را ببینید . دشمن ها هنوز در حال حرکت هستند).

این مشکل را بعدا حل خواهیم کرد فعلا میخواهیم background  بی نهایت را مدیریت کنیم .

پس زمینه تکرار شونده ی بی نهایت 

به منظور ایجاد یک بکگراند تکرار شونده ی بی نهایت ، ما فقط باید به لایه ای زیرینی که در سمت چپ لایه ی بی نهایت قرار دارد نگاه بندازیم 

زمانی که این شی از لبه ی سمت چپ دوربین فراتر میرود ، ما آنرا به سمت راست لایه هدایت میکنیم 

آموزش پیمایش کاراکتر بازی دو بعدی در یونیتی

 

برای یک فیلد لایه به همراه تصویر ، توجه کنید که شما حداقل سایز برای پوشش کامل دوربین نیاز دارید ، بنابراین ما چیزی که فراتر از آن باشد را هرگز مشاهده نخواهیم کرد . اینجا سه قسمت آسمان هست ولی قراردادن آنها دلخواه هست 

براساس انعطاف پذیری و منابع سخت افزاری که بازی راطراحی میکنید تعادل را ایجاد کنید.

در این اینجا ، ایده ی ما اینست که تمام بچه های لایه را بگیریم و renderer آنها را بررسی کنیم 

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

اسکریپت افزونه ی rendrer سی شارپ 

اسکریپت جدید به نام RendererExtensions.cs بسازید و محتویات زیر را کپی کنید : 

using UnityEngine;

public static class RendererExtensions
{
  public static bool IsVisibleFrom(this Renderer renderer, Camera camera)
  {
    Plane[] planes = GeometryUtility.CalculateFrustumPlanes(camera);
    return GeometryUtility.TestPlanesAABB(planes, renderer.bounds);
  }
}

 

ما این متد را باید سمت چپ لایه ی بی نهایت فراخوانی بکنیم 

ScrollingScript کامل

اسکریپت ScrollingScript را در زیر مشاهده کنید (توصبحات در زیر داده شده است )
 

using System.Collections.Generic;
using System.Linq;
using UnityEngine;

/// <summary>
/// Parallax scrolling script that should be assigned to a layer
/// </summary>
public class ScrollingScript : MonoBehaviour
{
    /// <summary>
    /// Scrolling speed
    /// </summary>
    public Vector2 speed = new Vector2(10, 10);

    /// <summary>
    /// Moving direction
    /// </summary>
    public Vector2 direction = new Vector2(-1, 0);

    /// <summary>
    /// Movement should be applied to camera
    /// </summary>
    public bool isLinkedToCamera = false;

    /// <summary>
    /// ۱ - Background is infinite
    /// </summary>
    public bool isLooping = false;

    /// <summary>
    /// ۲ - List of children with a renderer.
    /// </summary>
    private List<SpriteRenderer> backgroundPart;

    // ۳ - Get all the children
    void Start()
    {
        // For infinite background only
        if (isLooping)
        {
            // Get all the children of the layer with a renderer
            backgroundPart = new List<SpriteRenderer>();

            for (int i = 0; i < transform.childCount; i++)
            {
                Transform child = transform.GetChild(i);
                SpriteRenderer r = child.GetComponent<SpriteRenderer>();

                // Add only the visible children
                if (r != null)
                {
                    backgroundPart.Add(r);
                }
            }

            // Sort by position.
            // Note: Get the children from left to right.
            // We would need to add a few conditions to handle
            // all the possible scrolling directions.
            backgroundPart = backgroundPart.OrderBy(
              t => t.transform.position.x
            ).ToList();
        }
    }

    void Update()
    {
        // Movement
        Vector3 movement = new Vector3(
          speed.x * direction.x,
          speed.y * direction.y,
          ۰);

        movement *= Time.deltaTime;
        transform.Translate(movement);

        // Move the camera
        if (isLinkedToCamera)
        {
            Camera.main.transform.Translate(movement);
        }

        // ۴ - Loop
        if (isLooping)
        {
            // Get the first object.
            // The list is ordered from left (x position) to right.
            SpriteRenderer firstChild = backgroundPart.FirstOrDefault();

            if (firstChild != null)
            {
                // Check if the child is already (partly) before the camera.
                // We test the position first because the IsVisibleFrom
                // method is a bit heavier to execute.
                if (firstChild.transform.position.x < Camera.main.transform.position.x)
                {
                    // If the child is already on the left of the camera,
                    // we test if it's completely outside and needs to be
                    // recycled.
                    if (firstChild.IsVisibleFrom(Camera.main) == false)
                    {
                        // Get the last child position.
                        SpriteRenderer lastChild = backgroundPart.LastOrDefault();

                        Vector3 lastPosition = lastChild.transform.position;
                        Vector3 lastSize = (lastChild.bounds.max - lastChild.bounds.min);

                        // Set the position of the recyled one to be AFTER
                        // the last child.
                        // Note: Only work for horizontal scrolling currently.
                        firstChild.transform.position = new Vector3(lastPosition.x + lastSize.x, firstChild.transform.position.y, firstChild.transform.position.z);

                        // Set the recycled child to the last position
                        // of the backgroundPart list.
                        backgroundPart.Remove(firstChild);
                        backgroundPart.Add(firstChild);
                    }
                }
            }
        }
    }
}

شماره های بالا به توضیحات زیر اشاره دارد 

۱ ما به یک متغیر public نیازز داریم تا looping را روشن کنیم و این از طریق inspector انجام گیرد 

۲ ما یک متغیر private نیاز داریم تا بچه های لایه را ذخیره کنیم منظور از بچه همان child هست 

۳ در متد Start() ما backgroundPart  را با لیست child هایی تنظیم کردیم  که rendrer دارد . به لطف LINQ ما انها را براساس پوزیشن x مرتب کردیم و آنها را در سمت چپ اولین عنصر ارایه ریختیم

۴ در متد Update() اگر isLooping روشن باشد (منظور true باشد ) اولین عنصری که در لیست backgroundPart  قرارگرفته است را فراخوانی کردیم ما تست کردیم اگر بطور کامل بیرون از دوربین باشد . اگر چنین اتفاقی رخ دهد ما موقعیت انرا تغییردادیم که بعد از اخرین child باشد در نهایت ما آنرا در آخر لیست backgroundPart قرار دادیم .

در واقع backgroundPart  دقیقا نماینده ی این هست که چه اتفاقی در صحنه بیوفتد 

بخاطر داشته باشید Is Looping را در اسکریپت ScrollingScript برای ۰ – Background در Inspector فعال کنید 

آموزش بازی سازی در یونیتی

بله بلاخره ما یک اسکریپت parallax scrolling پیاده سازی کردیم . 

بهبود اسکریپت بالا : 

بیایید اسکریپتی که نوشتیم را بروزرسانی کنیم 

دشمن نسخه ی ۲ بصورت spawn

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

ما میخواهیم EnemyScript را بروزرسانی کنیم پس : 

۱ movement ، کلایدر و auto fire را غیرفعال کنید 

۲ چک کنید که rendrer داخل دوربین باشد 

۳ آنرا فعال کنید 

۴ وقتی که game object از دوربین خارج شد آنرا نابود کن 

در کدهای زیر شماره ها برای توضیحات هست 

using UnityEngine;

/// <summary>
/// Enemy generic behavior
/// </summary>
public class EnemyScript : MonoBehaviour
{
    private bool hasSpawn;
    private MoveScript moveScript;
    private WeaponScript[] weapons;
    private Collider2D coliderComponent;
    private SpriteRenderer rendererComponent;

    void Awake()
    {
        // Retrieve the weapon only once
        weapons = GetComponentsInChildren<WeaponScript>();

        // Retrieve scripts to disable when not spawn
        moveScript = GetComponent<MoveScript>();

        coliderComponent = GetComponent<Collider2D>();

        rendererComponent = GetComponent<SpriteRenderer>();
    }

    // ۱ - Disable everything
    void Start()
    {
        hasSpawn = false;

        // Disable everything
        // -- collider
        coliderComponent.enabled = false;
        // -- Moving
        moveScript.enabled = false;
        // -- Shooting
        foreach (WeaponScript weapon in weapons)
        {
            weapon.enabled = false;
        }
    }

    void Update()
    {
        // ۲ - Check if the enemy has spawned.
        if (hasSpawn == false)
        {
            if (rendererComponent.IsVisibleFrom(Camera.main))
            {
                Spawn();
            }
        }
        else
        {
            // Auto-fire
            foreach (WeaponScript weapon in weapons)
            {
                if (weapon != null && weapon.enabled && weapon.CanAttack)
                {
                    weapon.Attack(true);
                }
            }

            // ۴ - Out of the camera ? Destroy the game object.
            if (rendererComponent.IsVisibleFrom(Camera.main) == false)
            {
                Destroy(gameObject);
            }
        }
    }

    // ۳ - Activate itself.
    private void Spawn()
    {
        hasSpawn = true;

        // Enable everything
        // -- Collider
        coliderComponent.enabled = true;
        // -- Moving
        moveScript.enabled = true;
        // -- Shooting
        foreach (WeaponScript weapon in weapons)
        {
            weapon.enabled = true;
        }
    }
}

بازی را اجرا کنید بله این یک باگ هست 

MoveScript را غیرفعال کنید ببنید پلایر هرگز به حرکت دشمن ها نمی رسد تصویر زیر را چک کنید 

camera_moving_along

به خاطر بسپارید : ما باید ScrollingScript را باید به این لایه اضافه کنیم برای اینکه پلایر همراه با دوربین حرکت بکند 

ولی یک راه حل ساده هم وجود دارد : ScrollingScript را از لایه Foreground به پلایر اختصاص دهید

چرا این کار را الان انجام دادیم ؟ 

تنها چیزی که در این لایه حرکت میکند خودش هست و اسکریپتی به یک شی خاصی هم اختصاص داده نشده هست 

دکمه ی play را بزنید و مشاهده میکنید که به درستی کار میکند 

۱ تا زمانی که دشمن ها spawn نشده اند غیر فعال هستند (تا زمانی که در محدوده ی دوربین قرار نگیرند )

۲  زمانی که انها از دوربین رد میشوند ناپدید میشوند 

آموزش ساخت دشمن بازی در یونیتی

نگه داشتن کاراکتر اصلی بازی (player) در چهارچوب دوربین بازی 

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

بازی را اجرا کنید و دکمه حرکت به چپ یا راست بزنید میبینید که کاراکتر بعد از مدتی از محدودذه ی دوربین خارج میشود 

برای حل این مشکل باید اسکریپت PlayerScript را باز کنید و کدهای زیر را به اخر متذد Update() اضافه کنید

void Update()
  {
    // ...

    // ۶ - Make sure we are not outside the camera bounds
    var dist = (transform.position - Camera.main.transform.position).z;

    var leftBorder = Camera.main.ViewportToWorldPoint(
      new Vector3(0, 0, dist)
    ).x;

    var rightBorder = Camera.main.ViewportToWorldPoint(
      new Vector3(1, 0, dist)
    ).x;

    var topBorder = Camera.main.ViewportToWorldPoint(
      new Vector3(0, 0, dist)
    ).y;

    var bottomBorder = Camera.main.ViewportToWorldPoint(
      new Vector3(0, 1, dist)
    ).y;

    transform.position = new Vector3(
      Mathf.Clamp(transform.position.x, leftBorder, rightBorder),
      Mathf.Clamp(transform.position.y, topBorder, bottomBorder),
      transform.position.z
    );

    // End of the update method
  }

کدهای بالا ساده هستند 

ما لبه های دوربین را پیدا میکنیم و مطمعن میشویم که کاراکتر درون دوربین باشد همین 

خسته نباشید 

اگر جاهایی از آموزش گنگ و مبهم بود عذرخواهی میکنم و سعی میکنم هرچه سریعتر اموزش ویدیویی رو بزارم تا مشکلات برطرف شه و در نهایت با به اشتراگ گذاری سایت با دوستان خود از ما حمایت کنید 

با احترام 

 

 

 

 

ما سایت سامنتا را با هدف انتقال دانش و تجربیات خود از بهترین متدهای آموزشی دنیا راه اندازی کردیم . محتویات این سایت از ابتدا براساس سلیقه ، نظرات و پیشنهادهای کاربران ساخته شده است . به امید روزی که سیستم آموزشی ایران متحول و از شیوه های نوین آموزشی استفاده کنند

http://Samenta.ir

پاسخ دهید

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