برنامج تعليمي: التلاعب بالنماذج

في هذا البرنامج التعليمي، تتعلم كيفية:

  • أضف حدودًا مرئية ومعالجة حول النماذج التي تم عرضها عن بُعد
  • تحريك، وتدوير، وقياس
  • Raycast مع الاستفسارات المكانية
  • أضف رسومًا متحركة بسيطة للكائنات المعروضة عن بُعد

المتطلبات الأساسية

الاستعلام عن حدود الكائن البعيد وتطبيقها على الحدود المحلية

للتفاعل مع الكائنات البعيدة، نحتاج إلى تمثيل محلي للتفاعل معه أولاً. تعد حدود الكائنات مفيدة في المعالجة السريعة للكائن البعيد. يمكن الاستعلام عن الحدود البعيدة من ARR، باستخدام الكيان المحلي كمرجع. يتم الاستعلام عن الحدود بعد تحميل النموذج في جلسة العمل البعيدة.

يتم تحديد حدود النموذج من خلال المربع الذي يحتوي على النموذج بالكامل - تمامًا مثل BoxCollider الخاص بـ Unity، والذي يحتوي على مركز وحجم محددَين لمحاور x، وy، وz. في الواقع، سنستخدم BoxCollider من Unity لتمثيل حدود النموذج البعيد.

  1. قم بإنشاء برنامج نصي جديد في نفس الدليل مثل RemoteRenderedModel وقم بتسميته RemoteBounds.

  2. استبدل محتويات البرنامج النصي بالرمز التالي:

    // Copyright (c) Microsoft Corporation. All rights reserved.
    // Licensed under the MIT License. See LICENSE in the project root for license information.
    
    using Microsoft.Azure.RemoteRendering;
    using Microsoft.Azure.RemoteRendering.Unity;
    using System;
    using UnityEngine;
    
    [RequireComponent(typeof(BaseRemoteRenderedModel))]
    public class RemoteBounds : BaseRemoteBounds
    {
        //Remote bounds works with a specific remotely rendered model
        private BaseRemoteRenderedModel targetModel = null;
    
        private RemoteBoundsState currentBoundsState = RemoteBoundsState.NotReady;
    
        public override RemoteBoundsState CurrentBoundsState
        {
            get => currentBoundsState;
            protected set
            {
                if (currentBoundsState != value)
                {
                    currentBoundsState = value;
                    BoundsStateChange?.Invoke(value);
                }
            }
        }
    
        public override event Action<RemoteBoundsState> BoundsStateChange;
    
        public void Awake()
        {
            BoundsStateChange += HandleUnityEvents;
            targetModel = GetComponent<BaseRemoteRenderedModel>();
    
            targetModel.ModelStateChange += TargetModel_OnModelStateChange;
            TargetModel_OnModelStateChange(targetModel.CurrentModelState);
        }
    
        private void TargetModel_OnModelStateChange(ModelState state)
        {
            switch (state)
            {
                case ModelState.Loaded:
                    QueryBounds();
                    break;
                default:
                    BoundsBoxCollider.enabled = false;
                    CurrentBoundsState = RemoteBoundsState.NotReady;
                    break;
            }
        }
    
        // Create an async query using the model entity
        async private void QueryBounds()
        {
            //Implement me
        }
    }
    

    ملاحظة

    إذا رأيت خطأً في Visual Studio يدعي أن الميزة "X" غير متوفرة في C# 6. فالرجاء استخدام إصدار اللغة 7.0 أو أعلى، يمكن تجاهل هذا الخطأ بأمان. هذا مرتبط بحل الوحدة وتوليد المشروع.

    يجب إضافة هذا البرنامج النصي إلى نفس GameObject مثل البرنامج النصي الذي ينفذ BaseRemoteRenderedModel. في هذه الحالة، هذا يعني RemoteRenderedModel. على غرار البرامج النصية السابقة، تتعامل هذه التعليمة البرمجية الأولية مع جميع تغييرات الحالة والأحداث والبيانات المتعلقة الحدود البعيدة.

    لا توجد سوى طريقة واحدة متبقية للتنفيذ: QueryBounds. يجلب QueryBounds الحدود بشكل غير متزامن، ويأخذ نتيجة الاستعلام ويطبقها على BoxCollider المحلي.

    طريقة QueryBounds واضحة ومباشرة: قم بإرسال استعلام إلى جلسة العرض عن بُعد وانتظر النتيجة.

  3. استبدل طريقة QueryBounds بالطريقة المكتملة التالية:

    // Create a query using the model entity
    async private void QueryBounds()
    {
        var remoteBounds = targetModel.ModelEntity.QueryLocalBoundsAsync();
        CurrentBoundsState = RemoteBoundsState.Updating;
        await remoteBounds;
    
        if (remoteBounds.IsCompleted)
        {
            var newBounds = remoteBounds.Result.toUnity();
            BoundsBoxCollider.center = newBounds.center;
            BoundsBoxCollider.size = newBounds.size;
            BoundsBoxCollider.enabled = true;
            CurrentBoundsState = RemoteBoundsState.Ready;
        }
        else
        {
            CurrentBoundsState = RemoteBoundsState.Error;
        }
    }
    

    نتحقق من نتيجة الاستعلام لمعرفة ما إذا كانت ناجحة. إذا كانت الإجابة بنعم، فقم بتحويل الحدود التي تم إرجاعها وتطبيقها بتنسيق يمكن لـ BoxCollider قبوله.

الآن، عند إضافة البرنامج النصي RemoteBounds إلى نفس عنصر اللعبة مثل RemoteRenderedModel، تتم إضافة BoxCollider إذا لزم الأمر وعندما يصل النموذج إلى حالته، سيتم الاستعلام عن الحدود تلقائيا وتطبيقها Loaded على BoxCollider.

  1. باستخدام TestModel GameObject الذي تم إنشاؤه مسبقًا، أضف المكون RemoteBounds.

  2. تأكد من إضافة البرنامج النصي.

    إضافة مكون RemoteBounds

  3. شغّل التطبيق مرة أخرى. بعد وقت قصير من تحميل النموذج، سترى حدود الكائن البعيد. سترى شيئًا مثل القيم أدناه:

    لقطة شاشة تعرض مثال حدود الكائن البعيد.

الآن لدينا BoxCollider محلي تم تكوينه بحدود دقيقة على كائن Unity. تسمح الحدود بالتخيل والتفاعل باستخدام نفس الإستراتيجيات التي نستخدمها لكائن يتم عرضه محليًا. على سبيل المثال، البرامج النصية التي تغير التحويل، والفيزياء، والمزيد.

تحريك، وتدوير، وقياس

يعمل تحريك الكائنات المعروضة عن بُعد وتدويرها وقياسها بنفس طريقة عمل أي كائن Unity آخر. يقوم RemoteRenderingCoordinator، بطريقته LateUpdate، بالاتصال بـ Update في الجلسة النشطة حاليًا. جزء مما يفعله Update هو مزامنة تحويلات كيان النموذج المحلي مع نظيراتها البعيدة. لتحريك نموذج تم عرضه عن بُعد أو تدويره أو قياسه، ما عليك سوى تحريك أو تدوير أو قياس تحويل كائن GameObject الذي يمثل نموذجًا بعيدًا. هنا، سنقوم بتعديل تحويل كائن GameObject الأصلي الذي يحتوي على البرنامج النصي RemoteRenderedModel المرفق به.

يستخدم هذا البرنامج التعليمي MRTK للتفاعل مع الكائن. يقع معظم تنفيذ MRTK المحدد لتحريك كائن وتدويره وقياسه خارج نطاق هذا البرنامج التعليمي. هناك وحدة تحكم في عرض النموذج تأتي مُعَدَّة مسبقًا داخلAppMenu، في قائمةModel Tools.

  1. تأكد من وجود TestModel GameObject الذي تم إنشاؤه مسبقًا في المشهد.
  2. تأكد من وجود مبنى AppMenu الجاهز في المشهد.
  3. اضغط على زر تشغيل Unity لتشغيل المشهد وافتح قائمة Model Tools داخل AppMenu. عرض وحدة التحكم

يحتوي AppMenu على القائمة الفرعية Model Tools التي تنفذ وحدة التحكم في العرض للربط مع النموذج. عندما يحتوي GameObject على مكون RemoteBounds، ستضيف وحدة التحكم في العرض مكون BoundingBox، وهو مكون MRTK الذي يعرض مربعًا محيطًا حول كائن باستخدام BoxCollider. ObjectManipulator، وهو المسؤول عن تفاعلات اليد. ستسمح لنا هذه البرامج النصية مجتمعة بتحريك النموذج الذي تم عرضه عن بُعد وتدويره وتوسيع نطاقه.

  1. حرك الماوس إلى لوحة اللعبة وانقر بداخلها لتركيزها.

  2. باستخدام محاكاة يد MRTK ، اضغط مع الاستمرار على مفتاح Shift الأيسر.

  3. قم بتوجيه يد المحاكاة بحيث يشير شعاع اليد إلى نموذج الاختبار.

    شعاع اليد المدببة

  4. اضغط مع الاستمرار فوق اليسار واسحب النموذج لتحريكه.

يجب أن ترى المحتوى الذي تم عرضه عن بُعد يتحرك جنبًا إلى جنب مع المربع المحيط. قد تلاحظ بعض التأخير بين المربع المحيط والمحتوى البعيد. سيعتمد هذا التأخير على زمن انتقال الإنترنت وعرض النطاق الترددي.

استعلامات راي كاست والاستعلامات المكانية للنماذج البعيدة

يعتبر مصادم الصندوق حول النماذج مناسبًا للتفاعل مع النموذج بأكمله، ولكنه غير مفصل بما يكفي للتفاعل مع الأجزاء الفردية من النموذج. لحل هذه المشكلة، يمكننا استخدام البث بالأشعة عن بُعد . إرسال الأشعة عن بُعد عبارة عن واجهة برمجة تطبيقات API يتم توفيرها بواسطة Azure Remote Rendering لإلقاء أشعة في المشهد البعيد وإرجاع نتائج النتائج محليًا. يمكن استخدام هذه التقنية لاختيار الكيانات التابعة لنموذج كبير أو الحصول على معلومات نتيجة النتائج مثل الموضع، والسطح الطبيعي، والمسافة.

يحتوي نموذج الاختبار على عدد من الكيانات الفرعية التي يمكن الاستعلام عنها واختيارها. في الوقت الحالي، سيؤدي التحديد إلى إخراج اسم الكيان المحدد إلى وحدة التحكم في الوحدة. تحقق من فصل المواد والإضاءة والتأثيرات لإبراز الكيان المحدد.

أولاً، لنقم بإنشاء غلاف ثابت حول استعلامات إرسال الأشعة البعيدة. سيقبل هذا البرنامج النصي موضعًا واتجاهًا في مساحة الوحدة، ويحوله إلى أنواع البيانات المقبولة من قِبل جهاز إرسال الأشعة عن بُعد، ويعيد النتائج. سيستفيد البرنامج النصي منRayCastQueryAsyncAPI.

  1. قم بإنشاء برنامج نصي جديد يسمى RemoteRayCaster واستبدل محتوياته بالتعليمة البرمجية التالية:

    // Copyright (c) Microsoft Corporation. All rights reserved.
    // Licensed under the MIT License. See LICENSE in the project root for license information.
    
    using Microsoft.Azure.RemoteRendering;
    using Microsoft.Azure.RemoteRendering.Unity;
    using System.Linq;
    using System.Threading.Tasks;
    using UnityEngine;
    
    /// <summary>
    /// Wraps the Azure Remote Rendering RayCast queries to easily send requests using Unity data types
    /// </summary>
    public class RemoteRayCaster
    {
        public static double maxDistance = 30.0;
    
        public static async Task<RayCastHit[]> RemoteRayCast(Vector3 origin, Vector3 dir, HitCollectionPolicy hitPolicy = HitCollectionPolicy.ClosestHit)
        {
            if(RemoteRenderingCoordinator.instance.CurrentCoordinatorState == RemoteRenderingCoordinator.RemoteRenderingState.RuntimeConnected)
            {
                var rayCast = new RayCast(origin.toRemotePos(), dir.toRemoteDir(), maxDistance, hitPolicy);
                var result = await RemoteRenderingCoordinator.CurrentSession.Connection.RayCastQueryAsync(rayCast);
                return result.Hits;
            }
            else
            {
                return new RayCastHit[0];
            }
        }
    
        public static async Task<Entity[]> RemoteRayCastEntities(Vector3 origin, Vector3 dir, HitCollectionPolicy hitPolicy = HitCollectionPolicy.ClosestHit)
        {
            var hits = await RemoteRayCast(origin, dir, hitPolicy);
            return hits.Select(hit => hit.HitEntity).Where(entity => entity != null).ToArray();
        }
    }
    

    ملاحظة

    تحتوي الوحدة على فئة تسمى RaycastHit، ويحتوي Azure Remote Rendering على فئة تسمى RayCastHit. يعد الحرف الكبير C فرقًا مهمًا لتجنب أخطاء الترجمة.

    يوفر RemoteRayCaster نقطة وصول مشتركة لإرسال الأشعة عن بُعد إلى الجلسة الحالية. لنكون أكثر تحديدًا، سنقوم بتطبيق معالج مؤشر MRTK بعد ذلك. سينفذ البرنامج النصي واجهة IMixedRealityPointerHandler التي ستخبر MRTK أننا نريد أن يستمع هذا البرنامج النصي لأحداث مؤشر الواقع المختلط .

  2. قم بإنشاء برنامج نصي جديد يسمى RemoteRayCastPointerHandler واستبدل الرمز بالتعليمة البرمجية التالية:

    // Copyright (c) Microsoft Corporation. All rights reserved.
    // Licensed under the MIT License. See LICENSE in the project root for license information.
    
    using Microsoft.Azure.RemoteRendering;
    using Microsoft.MixedReality.Toolkit.Input;
    using System;
    using System.Linq;
    using System.Threading.Tasks;
    using UnityEngine;
    
    public class RemoteRayCastPointerHandler : BaseRemoteRayCastPointerHandler, IMixedRealityPointerHandler
    {
        public UnityRemoteEntityEvent OnRemoteEntityClicked = new UnityRemoteEntityEvent();
    
        public override event Action<Entity> RemoteEntityClicked;
    
        public void Awake()
        {
            // Forward events to Unity events
            RemoteEntityClicked += (entity) => OnRemoteEntityClicked?.Invoke(entity);
        }
    
        public async void OnPointerClicked(MixedRealityPointerEventData eventData)
        {
            if (RemoteEntityClicked != null) //Ensure someone is listening before we do the work
            {
                var firstHit = await PointerDataToRemoteRayCast(eventData.Pointer);
                if (firstHit.success)
                    RemoteEntityClicked.Invoke(firstHit.hit.HitEntity);
            }
        }
    
        public void OnPointerDown(MixedRealityPointerEventData eventData) { }
    
        public void OnPointerDragged(MixedRealityPointerEventData eventData) { }
    
        public void OnPointerUp(MixedRealityPointerEventData eventData) { }
    
        private async Task<(bool success, RayCastHit hit)> PointerDataToRemoteRayCast(IMixedRealityPointer pointer, HitCollectionPolicy hitPolicy = HitCollectionPolicy.ClosestHit)
        {
            RayCastHit hit;
            var result = pointer.Result;
            if (result != null)
            {
                var endPoint = result.Details.Point;
                var direction = pointer.Rays[pointer.Result.RayStepIndex].Direction;
                Debug.DrawRay(endPoint, direction, Color.green, 0);
                hit = (await RemoteRayCaster.RemoteRayCast(endPoint, direction, hitPolicy)).FirstOrDefault();
            }
            else
            {
                hit = new RayCastHit();
            }
            return (hit.HitEntity != null, hit);
        }
    }
    

يتم استدعاء طريقة RemoteRayCastPointerHandlerOnPointerClicked بواسطة MRTK عندما "ينقر" المؤشر على مصادم، مثل مصادم الصندوق. بعد ذلك، يتم استدعاء PointerDataToRemoteRayCast لتحويل نتيجة المؤشر إلى نقطة واتجاه. ثم يتم استخدام هذه النقطة والاتجاه لإلقاء شعاع بعيد في الجلسة البعيدة.

تم تحديث الحدود

يعد إرسال طلبات إرسال الأشعة عند النقر إستراتيجية فعالة للاستعلام عن الكائنات البعيدة. ومع ذلك، فهي ليست تجربة مستخدم مثالية؛ لأن المؤشر يصطدم بمصادم الصندوق وليس النموذج نفسه.

يمكنك أيضًا إنشاء مؤشر MRTK جديد يلقي أشعته في الجلسة البعيدة بشكل متكرر. على الرغم من أن هذا نهج أكثر تعقيدًا، فإن تجربة المستخدم ستكون أفضل. هذه الإستراتيجية خارج نطاق هذا البرنامج التعليمي، ولكن يمكن رؤية مثال على هذا الأسلوب في تطبيق العرض الموجود في مستودع عينات ARR .

عند اكتمال عملية إرسال الشعاع بنجاح في RemoteRayCastPointerHandler، يتم إصدار النتيجة Entity من حدث الوحدة OnRemoteEntityClicked. للرد على هذا الحدث، سننشئ نصًا برمجيًا مساعدًا يقبل Entity وينفذ إجراءً عليه. لنبدأ بجعل البرنامج النصي يطبع اسم Entity في سجل التصحيح.

  1. قم بإنشاء برنامج نصي جديد باسم RemoteEntityHelper واستبدل محتوياته بما يلي:

    // Copyright (c) Microsoft Corporation. All rights reserved.
    // Licensed under the MIT License. See LICENSE in the project root for license information.
    
    using Microsoft.Azure.RemoteRendering;
    using Microsoft.Azure.RemoteRendering.Unity;
    using UnityEngine;
    
    public class RemoteEntityHelper : MonoBehaviour
    {
        public void EntityToDebugLog(Entity entity)
        {
            Debug.Log(entity.Name);
        }
    }
    
  2. في TestModel GameObject الذي تم إنشاؤه مسبقًا، أضف كلاً من المكون RemoteRayCastPointerHandler والمكون RemoteEntityHelper.

  3. قم بتعيين طريقة EntityToDebugLog للحدث OnRemoteEntityClicked. عندما يتطابق نوع إخراج الحدث ونوع إدخال الأسلوب، يمكننا استخدام ربط الحدث الديناميكي في Unity، والذي سيمرر تلقائيًا قيمة الحدث إلى الطريقة.

    1. إنشاء حقل رد اتصال جديد إضافة رد اتصال
    2. اسحب مكون Remote Entity Helper إلى حقل Object، للإشارة إلى كائن GameObject Assign الأصل
    3. EntityToDebugLog تعيين كرد اتصال تعيين رد الاتصال
  4. اضغط على play في Unity Editor لبدء المشهد، والاتصال بجلسة عمل بعيدة وتحميل نموذج الاختبار.

  5. باستخدام محاكاة يد MRTK، اضغط مع الاستمرار على مفتاح Shift الأيسر.

  6. قم بتوجيه يد المحاكاة بحيث يشير شعاع اليد إلى نموذج الاختبار.

  7. نقرة طويلة لمحاكاة الضغط الجوي، وتنفيذ الحدث OnPointerClicked.

  8. راقب وحدة التحكم في الوحدة لرسالة سجل باسم الكيان الفرعي المحدد. على سبيل المثال: مثال الكيان التابع

مزامنة الرسم البياني للكائن البعيد في التسلسل الهرمي للوحدة

حتى هذه اللحظة، رأينا فقط كائن GameObject محليًّا واحدًا يمثل النموذج بأكمله. يعمل هذا بشكل جيد لتقديم النموذج بأكمله ومعالجته. ومع ذلك، إذا أردنا تطبيق التأثيرات أو التعامل مع كيانات فرعية معينة، فسنحتاج إلى إنشاء GameObjects محلية لتمثيل تلك الكيانات. أولاً، يمكننا الاستكشاف يدويًا في نموذج الاختبار.

  1. ابدأ المشهد وقم بتحميل نموذج الاختبار.
  2. قم بتوسيع العناصر الفرعية لـ TestModel GameObject في التسلسل الهرمي للوحدة وحدد TestModel_Entity GameObject.
  3. في المفتش، انقر فوق الزر إظهار الأطفال. إظهار الأطفال
  4. استمر في توسيع العناصر الفرعية في التسلسل الهرمي والنقر فوق إظهار الأطفال حتى يتم عرض قائمة كبيرة من العناصر الفرعية. جميع الأطفال

سيتم الآن ملء قائمة من عشرات الكيانات بالتسلسل الهرمي. سيؤدي تحديد أحدها إلى إظهار المكونَين Transform و RemoteEntitySyncObject في المفتش. بشكل افتراضي، لا تتم مزامنة كل كيان تلقائيًا مع كل إطار؛ لذلك لا تتم مزامنة التغييرات المحلية على Transform مع الخادم. يمكنك تحديد مزامنة كل إطار ثم نقل التحويل أو قياسه أو تدويره في عرض المشهد، لن ترى النموذج المعروض في عرض المشهد، شاهد عرض اللعبة لمشاهدة تحديث موضع النموذج ودورانه بصريًا.

يمكن إجراء نفس العملية برمجيًا وهي الخطوة الأولى في تعديل كيانات بعيدة محددة.

  1. قم بتعديل البرنامج النصي RemoteEntityHelper ليحتوي أيضًا على الطريقة التالية:

    public void MakeSyncedGameObject(Entity entity)
    {
        var entityGameObject = entity.GetOrCreateGameObject(UnityCreationMode.DoNotCreateUnityComponents);
        var sync = entityGameObject.GetComponent<RemoteEntitySyncObject>();
        sync.SyncEveryFrame = true;
    }
    
  2. أضف رد اتصال إضافي إلى حدث RemoteRayCastPointerHandlerOnRemoteEntityClicked، واضبطه على MakeSyncedGameObject. رد اتصال إضافي

  3. باستخدام محاكاة يد MRTK، اضغط مع الاستمرار على مفتاح Shift الأيسر.

  4. قم بتوجيه يد المحاكاة بحيث يشير شعاع اليد إلى نموذج الاختبار.

  5. نقرة طويلة لمحاكاة الضغط الجوي، وتنفيذ الحدث OnPointerClicked.

  6. تحقق من التسلسل الهرمي وقم بتوسيعه لرؤية كائن فرعي جديد يمثل الكيان الذي تم النقر فوقه. تمثيل GameObject

  7. بعد الاختبار، قم بإزالة رد الاتصال لـ MakeSyncedGameObject، حيث سنقوم بدمج هذا كجزء من التأثيرات الأخرى لاحقًا.

ملاحظة

تكون مزامنة كل إطار مطلوبة فقط عندما تحتاج إلى مزامنة بيانات التحويل. هناك بعض النفقات العامة لمزامنة التحويلات؛ لذا يجب استخدامها باعتدال.

يعد إنشاء مثيل محلي وجعله متزامنًا تلقائيًا هو الخطوة الأولى في معالجة الكيانات الفرعية. يمكن استخدام نفس الأساليب التي استخدمناها لمعالجة النموذج ككل في الكيانات الفرعية أيضًا. على سبيل المثال، بعد إنشاء مثيل محلي متزامن لكيان ما، يمكنك الاستعلام عن حدوده وإضافة معالجات معالجة للسماح بتحريكه بواسطة أشعة يد المستخدم.

الخطوات التالية

يمكنك الآن التعامل مع النماذج التي تم عرضها عن بُعد والتفاعل معها! في البرنامج التعليمي التالي، سنغطي تعديل المواد وتغيير الإضاءة وتطبيق التأثيرات على النماذج التي يتم عرضها عن بُعد.