Menu
CoddyTech
flag Ar iconالعربيةdown icon

البطل

جزء من قسم هندسة الألعاب في Unity في رحلة Unity على Coddy. الدرس 41 من 50.

يبني هذا الفصل لعبة Arena Survivor، وهي اللعبة الأخيرة في هذه الرحلة. تندفع موجات الأعداء إلى ساحة محاطة بالجدران، وتقاتلهم بسيفك: أعداء مشاة يتجهون نحوك، وكرات تنتظر ثم تندفع. تترك الموجة المهزومة تعزيزًا، وتعرض واجهة HUD صحتك والموجة، وعندما تنفد صحتك تنتهي الجولة ويُحفَظ أفضل أداء لك. تستخدم اللعبة أدوات هذا القسم حيثما كان ذلك مناسبًا: فئة Enemy مجردة لها نوعان، وواجهة IDamageable لكل ما يمكن أن يتلقى الضرر، والموجات كأصل ScriptableObject، والتقاطات عبر واجهة، وواجهة HUD لا تستمع إلا إلى أحداث C#، وحفظ بصيغة JSON. توجد السكربتات الاثنا عشر كلها في المشروع منذ البداية. لم يُكتب الكود بعد إلا لفئة Hero، ويضيف كل درس المزيد.

يضرب سيف البطل ما يقع أمامه، لذا يحتاج التأرجح إلى اختبار يحدد ما إذا كان الهدف «أمامي». تضرب Vector3.Dot(a, b) الأجزاء المتناظرة من متجهين ثم تجمعها: a.x * b.x + a.y * b.y + a.z * b.z. وبالنسبة إلى اتجاهين طول كل منهما 1، تكون النتيجة جيب تمام الزاوية بينهما: 1 عندما يشيران إلى الاتجاه نفسه، و0.866 عند 30 درجة، و0.707 عند 45، و0 عند الزوايا القائمة، و-1 عندما يشيران إلى اتجاهين متعاكسين. لذلك تصبح عبارة «ضمن 30 درجة من اتجاه نظري» هي «قيمة الضرب النقطي لا تقل عن 0.866»، من دون الحاجة إلى حساب زاوية. يطلق روبوت الحراسة هذا الإنذار عندما يكون اللاعب ضمن 10 وحدات وداخل مجال رؤيته البالغ 30 درجة:

public Transform player;
public float range = 10f;

void Update()
{
    Vector3 toPlayer = player.position - transform.position;
    toPlayer.y = 0f;
    bool near = toPlayer.magnitude <= range;
    bool inView = Vector3.Dot(transform.forward, toPlayer.normalized) >= 0.866f;
    if (near && inView)
    {
        Debug.Log("Alarm!");
    }
}

يجب أن يكون طول كلا الاتجاهين 1. يتحقق ذلك دائمًا في transform.forward، لكن طول toPlayer يساوي المسافة، لذا يُطبَّع أولًا؛ وإلا فإن اللاعب الذي يبعد ضعف المسافة سيعطي ضعف قيمة الضرب النقطي. تُضبط قيمة y فيه على 0 قبل ذلك، لذا يُحتسب اللاعب الموجود على ممر مرتفع بحسب موقعه على الخريطة، لا بحسب ارتفاعه. تحدد إشارة القيمة ما إذا كان الاتجاه أماميًا أم خلفيًا: فقيمة الضرب النقطي الأكبر من 0 تعني أي موضع في النصف الأمامي، والأقل من 0 تعني أي موضع خلفك.

challenge icon

التحدي

متوسط

ابدأ Arena Survivor بسيف البطل. كُتبت الحركة: يحرّك الإجراء Move البطل باستخدام SimpleMove ويديره لمواجهة الاتجاه الذي يتحرك فيه. تقف حوله خمس دمى تدريب تحمل الوسم Enemy. يضبط Inspector قيمة reach إلى (2) وقيمة swingCooldown إلى (0.5) في Hero، وكذلك الضربة التي تظهر أثناء التأرجح.

  • يؤدي الضغط على Space أو النقر بزر الفأرة الأيسر إلى التأرجح، مرة واحدة كل swingCooldown ثانية كحد أقصى: لا يفعل الضغط قبل انتهاء فترة الانتظار شيئًا. اقرأ المفاتيح باستخدام Keyboard.current وMouse.current، واستدعِ Swing().
  • في Swing()، بعد إظهار الضربة، دمّر كل كائن يحمل الوسم Enemy ويقع ضمن reach وأمام البطل. يعني «ضمن المدى» أي مصادم تجده Physics.OverlapSphere حول البطل بنصف قطر reach. ويعني «أمام» أن يكون الجداء النقطي بين اتجاه البطل الأمامي والاتجاه نحو الكائن على امتداد الأرض 0.5 على الأقل: حتى 60 درجة إلى أي من الجانبين.

اترك البرامج النصية الأخرى كما هي. اضغط تشغيل، وامشِ نحو الدمى وتأرجح، ثم اضغط تحقّق.

جرّب بنفسك

using System.Collections;
using UnityEngine;
using UnityEngine.InputSystem;

public class Hero : MonoBehaviour
{
    [SerializeField] private float speed = 5f;
    [SerializeField] private float reach = 2f;
    [SerializeField] private float swingCooldown = 0.5f;
    [SerializeField] private GameObject slash;

    private CharacterController controller;
    private InputAction moveAction;

    void Start()
    {
        controller = GetComponent<CharacterController>();
        moveAction = InputSystem.actions.FindAction("Move");
    }

    void Update()
    {
        Vector2 input = moveAction.ReadValue<Vector2>();
        Vector3 move = new Vector3(input.x, 0f, input.y);
        controller.SimpleMove(move * speed);
        if (move != Vector3.zero)
        {
            transform.rotation = Quaternion.LookRotation(move);
        }

        // الضغط على Space أو النقر بزر الفأرة الأيسر ينفّذ ضربة، بمعدل لا يتجاوز مرة واحدة كل swingCooldown ثانية
    }

    void Swing()
    {
        StartCoroutine(ShowSlash());
        // دمّر كل ما يحمل الوسم Enemy ويقع ضمن مدى الوصول وأمام البطل
    }

    IEnumerator ShowSlash()
    {
        slash.SetActive(true);
        yield return new WaitForSeconds(0.1f);
        slash.SetActive(false);
    }
}
quiz iconاختبر نفسك

يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.

جميع دروس هندسة الألعاب في Unity