رابط للمشروعفي هذه المقالة ، أريد أن
أوضح كيف يمكنك استخدام
SharedEvents للتحكم في شخصية الشخص الثالث الذي يقدم مجموعة قياسية من الأصول. كتبت عن
SharedEvents في المقالات السابقة (
هذا وذاك ).
مرحبا بكم في القط!
أول ما تحتاج إليه هو تنفيذ مشروع مع تطبيق SharedState / SharedEvents وإضافة مجموعة قياسية من الأصول

أنا خلقت مشهد صغير وبسيط جدا من النماذج الجاهزة

وخبز الملاحة السطح مع الإعدادات القياسية

بعد ذلك ، تحتاج إلى إضافة
ThirdPersonCharacter الجاهزة إلى هذا المشهد

ثم يمكنك البدء والتأكد من أن كل شيء يعمل خارج الصندوق. ثم يمكنك المتابعة لتكوين استخدام
البنية الأساسية المشتركة لـ SharedState / SharedEvents التي تم إنشاؤها مسبقًا. للقيام بذلك ، قم بإزالة المكون
ThirdPersonUserController من كائن الحرف.

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

تحتاج الآن إلى إنشاء وحدة تحكم بسيطة تتحكم في الشخصية
مع الماوس
AgentMouseController
using UnityEngine; using UnityEngine.AI; using UnityStandardAssets.Characters.ThirdPerson; public class AgentMouseController : MonoBehaviour { public NavMeshAgent agent; public ThirdPersonCharacter character; public Camera cam; void Start() {
وأضفها إلى كائن الشخصية ، واعطها روابط إلى الكاميرا ، ووحدة التحكم في الشخصية والوكيل. كل شيء متاح من المرحلة.

وهذا كل شيء. هذا يكفي للتحكم في الشخصية عن طريق إخبار الوكيل بمكان التحرك ، باستخدام الماوس (النقر بزر الماوس الأيسر).
يمكنك البدء والتأكد من أن كل شيء يعمل

SharedEvents التكامل
الآن بعد أن أصبح المشهد الأساسي جاهزًا ، يمكنك المتابعة لدمج التحكم في الأحرف من خلال
SharedEvents . للقيام بذلك ، سوف تحتاج إلى إنشاء العديد من المكونات. أولها هو المكون الذي سيكون مسؤولاً عن تلقي الإشارة من الماوس وإخطار جميع المكونات التي تتعقب موضع النقر بالماوس على المشهد ، وسوف يهتمون فقط بإحداثيات النقر.
سيتم استدعاء المكون ، على سبيل المثال ،
MouseHandlerComponent
using UnityEngine; public class MouseHandlerComponent : SharedStateComponent { public Camera cam; #region MonoBehaviour protected override void OnSharedStateChanged(SharedStateChangedEventData newState) { } protected override void OnStart() { if (cam == null) throw new MissingReferenceException(" "); } protected override void OnUpdate() {
يحتاج هذا المكون إلى فصل لإرسال البيانات في الإعلامات. بالنسبة لهذه الفئات التي ستحتوي على بيانات للإعلامات فقط ، يمكنك إنشاء ملف واحد
وتسميته DefinedEventsData
وأضف فئة واحدة إليه ، لإرسال موضع النقرة بالماوس
using UnityEngine; public class PointOnGroundEventData : EventData { public Vector3 Point { get; set; } }
الشيء التالي الذي يجب فعله هو إضافة مكون سيكون
غلافًا أو أداة
تزيين ، كما تريد ، لمكون
NavMeshAgent . نظرًا لأنني لن أغير مكونات (الطرف الثالث) الحالية ، فسوف أستخدم أدوات الديكور للتكامل مع
SharedState / SharedEvents .

سيتلقى هذا المكون إشعارات حول نقرات الماوس في نقاط معينة على الساحة وإخبار الوكيل بمكان التحرك. وأيضًا مراقبة وضع موضع الوكيل في كل إطار وإنشاء إشعار حول التغيير.
يعتمد هذا المكون على مكون
NavMeshAgent. using UnityEngine; using UnityEngine.AI; [RequireComponent(typeof(NavMeshAgent))] public class AgentWrapperComponent : SharedStateComponent { private NavMeshAgent agent; #region Monobehaviour protected override void OnSharedStateChanged(SharedStateChangedEventData newState) { } protected override void OnStart() {
لإرسال البيانات ، يحتاج هذا المكون إلى فصل يحتاج إلى إضافته إلى ملف
DefinedEventsData. public class AgentMoveEventData : EventData { public Vector3 DesiredVelocity { get; set; } }
هذا هو بالفعل ما يكفي للشخصية للتحرك. لكنه سيفعل ذلك بدون رسوم متحركة ، لأننا لا نستخدم
ThirdPersonCharater حتى الآن.
وله ، تمامًا مثل
NavMeshAgent ، تحتاج إلى إنشاء ديكور CharacterWrapperComponent

سيستمع المكون إلى الإخطارات المتعلقة بتغيير موضع الوكيل ، ونقل الحرف في الاتجاه الذي تم استلامه من الإشعار (الحدث).
using UnityEngine; using UnityStandardAssets.Characters.ThirdPerson; [RequireComponent(typeof(ThirdPersonCharacter))] public class CharacterWrapperComponent : SharedStateComponent { private ThirdPersonCharacter character; #region Monobehaviour protected override void OnSharedStateChanged(SharedStateChangedEventData newState) { } protected override void OnStart() { character = GetComponent<ThirdPersonCharacter>(); Events.Subscribe<AgentMoveEventData>("agentmoved", OnAgentMove); } protected override void OnUpdate() { } #endregion private void OnAgentMove(AgentMoveEventData eventData) { // character.Move(eventData.DesiredVelocity, false, false); } }
وهذا كل شيء. يبقى لإضافة هذه المكونات إلى كائن اللعبة للشخصية. تحتاج إلى إنشاء نسخة من القائمة ، قم بإزالة المكون
AgentMouseControl القديم

وأضف
MouseHandlerComponent جديدًا و
AgentWrapperComponent و
CharacterWrapperComponent .
في
MouseHandlerComponent ، تحتاج إلى نقل الكاميرا من المشهد الذي سيتم حساب موضع النقرة منه.


يمكنك البدء والتأكد من أن كل شيء يعمل.
حدث ذلك بمساعدة
SharedEvents للتحكم في الشخصية دون وجود اتصال مباشر بين المكونات ، كما في المثال الأول. سيسمح ذلك بتكوين أكثر مرونة للتركيبات المختلفة للمكونات وتخصيص التفاعل بينها.
سلوك غير متزامن لـ SharedEvents
تعتمد طريقة تنفيذ آلية الإشعار الآن على الإرسال المتزامن للإشارة ومعالجتها. أي أنه كلما زاد عدد المستمعين ، كلما طالت مدة معالجته. من أجل الابتعاد عن هذا ، تحتاج إلى تنفيذ معالجة الإخطار غير المتزامن. أول شيء فعله هو إضافة إصدار غير متزامن من أسلوب
النشر
أنت الآن بحاجة إلى تغيير طريقة
OnUpdate المجردة في الفئة الأساسية لـ
SharedStateComponent إلى غير متزامن بحيث تقوم بإرجاع المهام التي تم
بدءها داخل تطبيق هذه الطريقة وإعادة تسميتها إلى
OnUpdateAsync protected abstract Task[] OnUpdateAsync();
ستحتاج أيضًا إلى آلية تتحكم في إكمال المهام من الإطار السابق ، قبل الحالي
private Task[] _previosFrameTasks = null;
يلزم
تحديث طريقة
التحديث في الفئة الأساسية على أنها غير
متزامنة والتحقق المسبق من تنفيذ المهام السابقة
async void Update() { await CompletePreviousTasks();
بعد هذه التغييرات في الفئة الأساسية ، يمكنك المتابعة لتغيير تطبيق الأسلوب
OnUpdate القديم إلى
OnUpdateAsync الجديد. المكون الأول حيث سيتم ذلك هو
AgentWrapperComponent . الآن هذه الطريقة تتوقع عودة النتيجة. ستكون هذه النتيجة مجموعة من المهام. صفيف لأنه في الطريقة يمكن إطلاق العديد بالتوازي وسوف نقوم بمعالجتها في مجموعة.
protected override Task[] OnUpdateAsync() {
المرشح التالي لإجراء تغييرات على أسلوب
OnUpdate هو
MouseHandlerController . هنا المبدأ هو نفسه
protected override Task[] OnUpdateAsync() {
في جميع التطبيقات الأخرى حيث كانت هذه الطريقة فارغة ، يكفي استبدالها بـ
protected override Task[] OnUpdateAsync() { return null; }
هذا كل شيء. يمكنك الآن البدء ، وإذا كانت المكونات التي تعالج الإشعارات بشكل غير متزامن لا تصل إلى تلك المكونات التي يجب معالجتها في السلسلة الرئيسية ، مثل Transform ، على سبيل المثال ، كل شيء سوف يعمل. وإلا ، فسوف نحصل على أخطاء في وحدة التحكم تبلغنا بأننا نصل إلى هذه المكونات وليس من سلسلة الرسائل الرئيسية

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

سيكون هذا المكون منفردًا وله طريقة مجردة عامة واحدة والتي ستنفذ التعليمات البرمجية في سلسلة الرسائل الرئيسية. مبدأ المرسل بسيط للغاية. سننقل إليه المندوبين الذين سيتم إعدامهم في الخيط الرئيسي ، وسنضعهم في قائمة الانتظار. وفي كل إطار ، إذا كان هناك شيء في قائمة الانتظار ، فقم بتنفيذه في السلسلة الرئيسية. سيضيف هذا المكون نفسه إلى المشهد بنسخة واحدة ، وأنا أحب هذا النهج البسيط والفعال.
using System; using System.Collections; using System.Collections.Concurrent; using UnityEngine; public class Dispatcher : MonoBehaviour { private static Dispatcher _instance; private volatile bool _queued = false; private ConcurrentQueue<Action> _queue = new ConcurrentQueue<Action>(); private static readonly object _sync_ = new object();
الشيء التالي الذي يجب القيام به هو تطبيق المرسل. هناك 2 أماكن للقيام بذلك. الأول هو صاحب الشخصية ، وهناك نسأله عن الاتجاه. في مكون
CharacterWrapperComponent private void OnAgentMove(AgentMoveEventData eventData) { Dispatcher.RunOnMainThread(() => character.Move(eventData.DesiredVelocity, false, false)); }
2 هو الديكور وكيل ، هناك نشير إلى موقف الوكيل. في مكون
AgentWrapperComponent private void OnPointToGroundGot(PointOnGroundEventData eventData) {
الآن لن تكون هناك أخطاء ، الكود سوف يعمل بشكل صحيح. يمكنك البدء ورؤية هذا.
قليلا من إعادة البناء
بعد أن أصبح كل شيء جاهزًا ويعمل كل شيء ، يمكنك مشط الكود قليلاً وجعله أكثر ملاءمةً وبساطة. هذا سوف يتطلب بعض التغييرات.
من أجل عدم إنشاء مجموعة من المهام ووضع المهمة الوحيدة فيها يدويًا ، يمكنك إنشاء طريقة ملحق. لجميع طرق التمديد ، يمكنك استخدام نفس الملف للإرسال إلى الإخطارات وكذلك لجميع الفئات. ستكون موجودة في مجلد
النظام وتسمى
الامتدادات
في الداخل ، سننشئ طريقة بسيطة عامة للتمديد ستلتف أي مثيل في صفيف
public static class Extensions {
التغيير التالي يخفي الاستخدام المباشر للمرسل في المكونات. بدلاً من ذلك ، قم بإنشاء طريقة في
SharedStateComponent الفئة الأساسية واستخدم المرسل من هناك.
protected void PerformInMainThread(Action action) { Dispatcher.RunOnMainThread(action); }
والآن تحتاج إلى تطبيق هذه التغييرات في عدة أماكن. أولاً ، قم بتغيير الأساليب التي نقوم بها يدويًا بإنشاء صفيف من المهام ووضع مثيل واحد فيها
في مكون
AgentWrapperComponent protected override Task[] OnUpdateAsync() {
وفي المكون
MouseHandlerComponent protected override Task[] OnUpdateAsync() {
الآن نتخلص من الاستخدام المباشر للمرسل في المكونات وبدلاً من ذلك نسمي الأسلوب
PerformInMainThread في الفئة الأساسية.
الأول في
AgentWrapperComponent private void OnPointToGroundGot(PointOnGroundEventData eventData) {
وفي مكون
CharacterWrapperComponent private void OnAgentMove(AgentMoveEventData eventData) { PerformInMainThread(() => character.Move(eventData.DesiredVelocity, false, false)); }
هذا كل شيء. يبقى تشغيل اللعبة والتأكد من عدم حدوث أي شيء أثناء إعادة التجهيز وأن كل شيء يعمل بشكل صحيح.