Проста система подій у Unity

image

Що це таке, і для чого це потрібно?

Рано чи пізно будь-який проект Unity розростається великою кількістю скриптів і стає важко тримати в голові, який скрипт з яким пов'язаний. З такою проблемою зіткнувся і я. Через деякий час вийшов на публікацію «Методи організації взаємодії між скриптами в Unity3D „. Мене відразу зацікавив третій підхід “Світовий ефір» (настійно рекомендую почитати, відмінно показано, навіщо це потрібно). Він ідеально мені підходить, але в тій статті вказано складний варіант і я, не володіючи великими знаннями програмування, так і не зміг зрозуміти його. В коментарях помітив згадки вбудована в мову системи подій. Погугливши знайшов статті про події в C#. У цьому пості я хочу розповісти, як подружити unity і систему подій C#, щоб уберегти форуми і unity answers від схожих питань.

Суть

Нехай у нас є гра — шутер. При натисканні на кнопку прицілювання гравець переходить на крок, включається камера прицілу, виконується сотня інших дій. А при натисканні кнопки стрільби — створення кулі, прорахунок польоту і т. д. Все це виллється у величезну кашу з купою зв'язків і програмувати це все стане дуже важко (див. картинку першої статті).
Інший важливий момент — для різних платформ управління буде сильно відрізнятися і доведеться правити купу скриптів, що містять в собі введення.

Події пропонують куди більш елегантний і зручний підхід, див. зображення вище. Схема матиме такий вигляд:

image

InputAggregator слухає enter і як тільки гравець натиснув клавішу/тапнул, говорить в ефір: «Гравець натиснув кнопку Х!». Про це одразу дізнаються скрипти, підписані на це подія (а точніше методи в цих скриптах) і виконуються зазначені методи. Причому ці методи можуть викликатися різними подіями, наприклад, метод «Померти» викликається як при отриманні критичного шкоди, так і, наприклад, при виходу за межі рівня.

Зверніть увагу, що нам не важливо, яку клавішу натиснув гравець, важливо лише те, що сталося подія. Тобто для підгонки управління для іншої платформи нам не треба лізти в 10 скриптів і всі їх правити, достатньо буде модифікувати InputAggregator.

Навіщо потрібен EventController — дивіться нижче.

Як це реалізувати?

Забудемо про шутер і кулі, давайте поставимо собі просте завдання:

Є дві сфери:

image

Ми хочемо при натисканні кнопки Space зрушити ліву сферу вгору, а нижній — вниз. І якщо якась з них іде надто далеко, повернути їх назад.

Але для початку зробимо тільки першу частину, без подолання кордонів:

image

Насамперед створимо скрипти сфер, які містять методи телепорт і повісимо їх на сфери:

public class Sphere1T : MonoBehaviour 
{
public void TeleportUp()
{
transform.Translate(Vector3.up);
}
}


Аналогічно для іншої сфери.

Створимо скрипт EventController і повісимо його куди-небудь (я повісив на камеру).
У ньому створимо делегат MethodContainer():

public delegate void MethodContainer();

Якщо дуже грубо, делегати — це покажчики на методи або контейнери методів. Для подій делегат використовується як тип і з ним самим нічого робити не треба, тому особливо вникати в механіку делегатів необов'язково (але знання зайвими не бувають). Важливо тільки знати, що до делегату підходять тільки методи, відповідні його сигнатурі. У нашому випадку це методи, не повертають значень (void) і не мають параметрів (). Відповідно й методи, що викликаються нашими подіями, повинні мати такий вигляд.

Ну і створимо в ньому лінки на наші сфери, в редакторі не забуваємо вказати, який лінк до чого відноситься (ви повинні вже вміти це робити, якщо читаєте цю статтю), вони нам знадобляться.

public Sphere1T s1t;
public Sphere2T s2t;


Тепер створимо скрипт InputAggregator. Створимо в ньому подія типу MethodContainer, що викликає телепорт сфер:

public static event EventController.MethodContainer OnTeleportEvent;

Тобто наш делегат ми вказуємо в якості типу. Зверніть увагу, що створювати лінк на EventController не потрібно.
У методі Update() викличемо наша подія:

void Update()
{
if (Input.GetKeyDown("space")) OnTeleportEvent();
}

Все, тепер якщо ми натиснемо пробіл, то активується подія OnTeleportEvent. Залишилося тільки визначити, які методи він повинен викликати.

Повернемося в EventController і в методі Awake() (викликається при старті гри, схожий на Start()) підпишемо на наше подія методи TeleportUp() і TeleportDown(). Робиться це за допомогою операції инкремента, ми додаємо до події методи:

void Awake()
{
InputAggregator.OnTeleportEvent += s1t.TeleportUp; //Зверніть увагу, лінк на клас (скрипт), що містить подія, робити не потрібно!
InputAggregator.OnTeleportEvent += s2t.TeleportDown;
}

Готово! Тепер при натисканні пробілу викликається подія OnTeleportEvent, яке викликає два методу в скриптах на сферах.

Тепер спробуємо зробити обробку кордонів.

У скрипти обох сфер додамо подія виходу за кордону (по одному на кожну):

public static event EventController.MethodContainer OnAbroadLeft;

public static event EventController.MethodContainer OnAbroadRight;

У методах телепортів викликаємо подія:

if (transform.position.y > 3) OnAbroadLeft(); //Для правої < -3

І додамо методи телепорту тому (можна назвати одними іменами):

public void ResetPosit()
{
transform.position = new Vector3(-2, 0, 0); //Для правої сфери (2,0,0)
}


А в Awake() EventController'a підписуємося:

Sphere1T.OnAbroadLeft += s1t.ResetPosit;
Sphere2T.OnAbroadRight += s2t.ResetPosit;

Все, тепер наші сфери справно рухаються і не втікають далеко.

Ще раз, повний код всіх скриптів:

public class InputAggregator : MonoBehaviour 
{
public static event EventController.MethodContainer OnTeleportEvent;

void Update()
{
if (Input.GetKeyDown("space")) OnTeleportEvent();
}
}


public class EventController : MonoBehaviour
{
public delegate void MethodContainer();

public Sphere1T s1t;
public Sphere2T s2t;

void Awake()
{
InputAggregator.OnTeleportEvent += s1t.TeleportUp;
InputAggregator.OnTeleportEvent += s2t.TeleportDown;

Sphere1T.OnAbroadLeft += s1t.ResetPosit;
Sphere2T.OnAbroadRight += s2t.ResetPosit;
}


}


public class Sphere1T : MonoBehaviour 
{
public static event EventController.MethodContainer OnAbroadLeft;

public void TeleportUp()
{
transform.Translate(Vector3.up);

if (transform.position.y > 3) OnAbroadLeft();
}

public void ResetPosit()
{
transform.position = new Vector3(-2, 0, 0); 
}
}


public class Sphere2T : MonoBehaviour
{
public static event EventController.MethodContainer OnAbroadRight;

public void TeleportDown()
{
transform.Translate(Vector3.down);

if (transform.position.y < -3) OnAbroadRight();
}

public void ResetPosit()
{
transform.position = new Vector3(2, 0, 0); 
}
}


Висновок
Це був найпростіший приклад. Зрозуміло, для двох сфер вернути події абсолютно не потрібно, але коли у вас в проекті більше двадцяти скриптів, події приходять на допомогу. От мені, наприклад:
InputAggregator_script.OnTrajectoryCall_event += trajectoryScript.DrawTrajectory;
InputAggregator_script.OnTrajectoryCall_event += destinationGUIScript.DrawDestinationText;
InputAggregator_script.OnTrajectoryCall_event += playerMovScript.RestartMov;

InputAggregator_script.OnTrajectoryClean_event += trajectoryScript.CleanTrajectory;
InputAggregator_script.OnTrajectoryClean_event += destinationGUIScript.DisableDestinationText;

InputAggregator_script.OnTurnSwitch_event += WorldState_class.ChangeDate;
InputAggregator_script.OnTurnSwitch_event += playerMovScript.Mov;
InputAggregator_script.OnTurnSwitch_event += dateGUIScript.UpdateDateBar;

Це тільки початок, уявляєте, як було б складно робити подібне для десятків скриптів без подій? А тут все акуратно, видно, що на що підписана.

Сподіваюся, стаття була корисною, буду радий коментарям та зауважень. Велике спасибі авторам двох зазначених мною на початку статті, без них я сам не розібрався.

Удачі вам в ваших проектах!

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.