[Статья]Тэги и взаимодействие между объектами. ч2

Научился сам? Помоги начинающему.

[Статья]Тэги и взаимодействие между объектами. ч2

Сообщение ilkalawson 03 апр 2015, 20:17

Статья: Тэги и взаимодействие между объектами.

Предыдущая часть

В прошлой части я показал уже всем знакомые приемы использования тэгов для взаимодействия между объектами.
А в этой части я расскажу какие приемы, разработчики знакомые с ООП, используют чтобы упростить задачу взаимодействия объектов друг с другом.

Давайте представим три машины, как в случае трех объектов взаимодействия из прошлой части.
Все три машины имеют разный дизайн, может даже имеют разное кол во колес или дверей, в общем внешне машины очень не похожи друг на друга.
Но у всех есть кое что что их всех объединяет - это двигатель. Когда мы видим машину мы сразу понимаем что у нее есть двигатель и не важно какая это машина, какой у нее дизайн и т д. Мы всегда знаем чтобы взаимодействовать с двигателем машины не нужно знать ее название, или марку, или сколько у нее колес, мы можем на прямую сразу работать с двигателем, т к это объединяющий элемент всех машин. Возвращаясь к примеру трех объектов взаимодействия - они все разные, обладают разными свойствами, у них разные имена управляющих скриптов, разные тэги и тд, но у них есть кое что что их объединяет - с каждым из них может взаимодействовать игрок.

Сразу скажу что у каждого имеется свой опыт подхода к ООП, я лишь расскажу о том что вижу чаще всего.

Когда программист замечает сходные свойства двух или более объектов, ему сразу на ум приходит решение объединить эти объекты, каким то более базовым объектом, от которого уже дальше будут наследоваться остальные объекты - один из базовых принципов ООП.
Так вот и в нашем случае - с каждым из объектов можно взаимодействовать, и следовательно эти объекты можно объединить каким-то более базовым объектом с которым можно будет взаимодействовать и от которого будут наследоваться все три объекта.

Для решения этой задачи можно было бы наследовать все три объекта от одного базового класса - к примеру под названием DynamicObject который был бы унаследован от MonoBehaviour.
Синтаксис:
Используется csharp
public class DynamicObject : MonoBehaviour {
 
}

И который бы имел метод под названием HitObject.
Синтаксис:
Используется csharp
public class DynamicObject : MonoBehaviour {
 
 public void HitObject(Vector2 hitDirection, float damage) {

 }

}

Этот метод принимал бы два параметра:
hitDirection - вектор удара
damage - значение нанесенного урона
А дальше мы бы просто унаследовали все три объекта взаимодействия(Стену, ящик и врага) от этого класса.
Синтаксис:
Используется csharp
public class Enemy : DynamicObject {
 
 public float health;

}

public class Box : DynamicObject {
 
 public Rigidbody body = null;

 private void Start() {
  this.body = this.GetComponent<Rigidbody>() ?? this.gameObject.AddComponent<Rigidbody>();
 }

}

public class Wall : DynamicObject {

 public WallType wallType = WallType.Glass;
 public int prochnost = 100;

 public enum WallType {
  Brick,
  Metal,
  Glass
 }

}

Теперь все три объекта, наследуясь от класса DynamicObject, будут иметь один общий метод HitObject.

Но здесь возникает сложность, метод HitObject находится в классе DynamicObject, а не в классах объектов что наследуются от него, как же тогда игрок будет взаимодействовать именно с этими тремя объектами, а не с их базовым классом!?
В этом случае есть пару выходов из ситуации.
1) Сделать метод HitObject виртуальным, чтобы другие наследующиеся классы могли его(метод) перегрузить и каждый объект по отдельности имел бы свою реализацию метода HitObject. Выглядело бы это примерно так.
Синтаксис:
Используется csharp
public class DynamicObject : MonoBehaviour {
 
 public virtual void HitObject(Vector2 hitDirection, float damage) {

 }

}

И теперь каждый объект взаимодействия может перегрузить данный метод чтобы по своему его реализовать.
Синтаксис:
Используется csharp
public class Enemy : DynamicObject {
 
 public float health;

 public override void HitObject(Vector2 hitDirection, float damage) {
 
 }

}

public class Box : DynamicObject {
 
 public Rigidbody body = null;

 private void Start() {
  this.body = this.GetComponent<Rigidbody>() ?? this.gameObject.AddComponent<Rigidbody>();
 }
 
 public override void HitObject(Vector2 hitDirection, float damage) {
 
 }

}

public class Wall : DynamicObject {

 public WallType wallType = WallType.Glass;
 public int prochnost = 100;

 public override void HitObject(Vector2 hitDirection, float damage) {
 
 }

 public enum WallType {
  Brick,
  Metal,
  Glass
 }

}

Теперь каждый объект имеет свою реализацию метода HitObject.
Но есть также еще более правильный подход к решению этой проблемы.

2) Объявить базовый класс DynamicObject абстрактным и сделать метод HitObject абстрактным, чтобы каждый объект также мог реализовать его по своему усмотрению, но при этому у базового класса отпала бы необходимость иметь у себя точно такую же реализацию этого же метода.
Выглядело бы это так.
Синтаксис:
Используется csharp
public abstract class DynamicObject : MonoBehaviour {
 
 public abstract void HitObject(Vector2 hitDirection, float damage);

}

Как вы видите абстрактные методы(и др поля) не имеют конкретной реализации, им как бы все равно что будет происходить и как будет реализован абстрактный метод - главное что он имеет этот метод и все.
В объектах взаимодействия ни каких изменений не последует, т к они также будут перегружать абстрактный метод из базового абстрактного класса.

Но и этот способ немного но далек от истины!
Дело в том что ВЫ как программист должны оценить не только на сколько объекты похожи друг на друга, но также и объем работ связанны с их использованием. В нашем случае игрок просто бьет объекты и больше ни чего с ними не делает, а для того чтобы это произошло нам пришлось создать целый, хоть и не большой, новый класс с одним лишь методом. Поэтому должен возникнуть вопрос: а зачем вообще мне что то откуда то наследовать, если есть решение еще более проще - интерфейсы!? Так как в языке C Sharp нет множественного наследования и где любой класс может наследоваться только от ОДНОГО любого другого класса, появилось другое решение - интерфейсы. Любой класс или структура может наследоваться от сколь угодно интерфейсов. Подробно о них(интерфейсах) можно почитать на мсдн

В нашем случае интерфейс будет выполнять практически ту же работу что и базовый абстрактный класс DynamicObject за исключением лишь того, что нам больше не нужен будет этот класс как объединяющее звено, им будет выступать интерфейс, а также не нужно будет перегружать метод HitObject в каждом объекте взаимодействия.

И так поэтому сначала сотрем класс DynamicObject, т к он нам больше не нужен и создадим простой интерфейс с одним методом HitObject.
Синтаксис:
Используется csharp
public interface IHittable {
 void HitObject(Vector2 hitDirection, float damage);
}

Как и в абстрактном классе, методы в интерфейсах не имеют реализации - как и абстрактные методы.

И теперь вернем всем трем объектам базовый класс MonoBehaviour и дополнительно унаследуем его от интерфейса IHittable.
Синтаксис:
Используется csharp
public class Enemy : MonoBehaviour, IHittable {
 
 public float health;

 public void HitObject(Vector2 hitDirection, float damage) {
 
 }

}

public class Box : MonoBehaviour, IHittable {
 
 public Rigidbody body = null;

 private void Start() {
  this.body = this.GetComponent<Rigidbody>() ?? this.gameObject.AddComponent<Rigidbody>();
 }
 
 public void HitObject(Vector2 hitDirection, float damage) {
 
 }

}

public class Wall : MonoBehaviour, IHittable {

 public WallType wallType = WallType.Glass;
 public int prochnost = 100;

 public void HitObject(Vector2 hitDirection, float damage) {
 
 }

 public enum WallType {
  Brick,
  Metal,
  Glass
 }

}

Теперь когда мы проделали эту работу мы получили опять же разные объекты с разными свойствами, но всех их объединяет один интерфейс, как и двигатель объединяет все машины из примера с машинами.
Теперь мы можем написать свою реализацию метода HitObject в каждом объекте взаимодействия, также как это мы делали при использовании тэгов.

Начнем с объекта Enemy. Как вы помните когда игрок бьет врага то у него просто отнимается уровень здоровья, поэтому повторим эти же действия только уже не в методе OnTriggerEnter класса Player, а в методе HitObject класса Enemy.
Синтаксис:
Используется csharp
public class Enemy : MonoBehaviour, IHittable {
 
 public float health;

 public void HitObject(Vector2 hitDirection, float damage) {
  this.health -= damage;
 }

}

Переходим к объекту Box. В этом случае заставляем объект отпрыгнуть от игрока только уже в методе HitObject.
Синтаксис:
Используется csharp
public class Box : MonoBehaviour, IHittable {
 
 public Rigidbody body = null;

 private void Start() {
  this.body = this.GetComponent<Rigidbody>() ?? this.gameObject.AddComponent<Rigidbody>();
 }
 
 public void HitObject(Vector2 hitDirection, float damage) {
  Vector2 force = hitDirection * damage;
  this.body.AddForce(force);
 }

}

И закончим объектом Wall.
Синтаксис:
Используется csharp
public class Wall : MonoBehaviour, IHittable {

 public WallType wallType = WallType.Glass;
 public int prochnost = 100;

 public void HitObject(Vector2 hitDirection, float damage) {
  if (this.wallType == WallType.Glass) this.prochnost -= damage;
  else if (this.wallType == WallType.Brick) this.prochnost -= damage / 2f;
  else if (this.wallType == WallType.Metall) this.prochnost -= damage / 3f;
 }

 public enum WallType {
  Brick,
  Metal,
  Glass
 }

}

Готово. Теперь все три объекта готовы к взаимодействию - переходим к методу OnTriggerEnter класса Player и очистим его.
Синтаксис:
Используется csharp
public class Player : MonoBehaviour {
 
 public float damage;
 public Vector2 direction;

 private void OnTriggerEnter(Collider collider) {

 }

}

Теперь для взаимодействия на объект нужно просто найти нужный интерфейс и вызвать метод HitObject. Вот как это выглядит.
Синтаксис:
Используется csharp
public class Player : MonoBehaviour {
 
 public float damage;
 public Vector2 direction;

 private void OnTriggerEnter(Collider collider) {
  IHittable hitObject = collider.gameObject.GetComponent<MonoBihaviour>() as IHittable;
 }

}

Как вы видите нельзя напрямую взять компонент объекта как интерфейс, но можно его преобразовать с помощью оператора as.
Скрытый текст:
В версиях unity от 5.х можно передавать в метод GetComponent любой тип Т:
IHittable hitObject = collider.gameObject.GetComponent<IHittable>();

И когда мы получили ссылку на интерфейс объекта, сначала мы должны точно проверить это, мы можем вызвать метод HitObject объекта взаимодействия.
Синтаксис:
Используется csharp
public class Player : MonoBehaviour {
 
 public float damage;
 public Vector2 direction;

 private void OnTriggerEnter(Collider collider) {
  IHittable hitObject = collider.gameObject.GetComponent<MonoBihaviour>() as IHittable;
  if (hitObject != null) hitObject.HitObject(this.direction, this.damage);
 }

}

И передать в него необходимые параметры.

Ну вот и готово - как и обещал две строки, вместо 17ти, и без использования тэгов.

Этот пример показывает как можно и как даже нужно устанавливать связь между объектами, чаще всего.
Ведь тэги просто заносят объекты в категории без указания, что это за объекты и без какой либо еще дополнительной информации, как допустим вы дали бы слепому человеку в одну руку яблоко, а в другую кокос и задали бы этим объектам тэги "Фрукт", и когда вы скажите слепому человеку что фрукт съедобный, он попробует съесть яблоко и у него это получиться, а вот когда он попробует укусить кокос он поймет что скорей всего больше ни чего не сможет кусать.
Да, тэги просты в использовании и в unity они часто используются в случаях когда теряются любые другие ссылки на объекты, или когда их просто нет(ссылок). Также в отличие от тэгов, с помощью наследования можно искать объекты на сцене через метод FindObjectsOfType, конечно только если выбранный тип прямо или косвенно наследуется от UnityEngine.Object. За тэгами нужно следить, т к это простые строки. Когда здесь или там изменил их или когда написал имя тэга с ошибкой - получается что все - код не будет выполняться.

Вот о чем я хотел рассказать в этой статье. И надеюсь у меня получилось донести смысл чем же отличается взаимодействие с помощью тэгов и прямым доступом к объекту.

Автор: этот хрен.
Последний раз редактировалось ilkalawson 10 апр 2015, 13:50, всего редактировалось 2 раз(а).
ilkalawson
UNIверсал
 
Сообщения: 412
Зарегистрирован: 19 янв 2015, 20:38
Skype: lawsonunity

Re: [Статья]Тэги и взаимодействие между объектами. ч2

Сообщение Serge 09 апр 2015, 09:15

Статья очень полезная.
Мне ужасно стыдно, но все же никак не соображу, где же нужно объявить интерфейс?
Аватара пользователя
Serge
UNIверсал
 
Сообщения: 476
Зарегистрирован: 20 мар 2009, 15:53
Откуда: Сибирь г. Омск
  • Сайт
  • ICQ

Re: [Статья]Тэги и взаимодействие между объектами. ч2

Сообщение Paul Siberdt 09 апр 2015, 10:42

...где же нужно объявить интерфейс?

Где угодно, как энумы или немонобеховые классы. Можно завести файлик с интерфейсами или прописать в какомнить менеджере.

Респект, статья, похоже, вполне доступная для понимания.
Аватара пользователя
Paul Siberdt
Адепт
 
Сообщения: 5317
Зарегистрирован: 20 июн 2009, 21:24
Откуда: Moscow, Russia
Skype: siberdt
  • Сайт

Re: [Статья]Тэги и взаимодействие между объектами. ч2

Сообщение Serge 09 апр 2015, 11:10

Попробовать сейчас не могу, и потому как говорится на пальцах.
В случае класса Enemy все ясно – это скрип Enemy.cs
В случае интерфейса IHittable – это что?
И лежать, как я понимаю должно все в одной папке?
К сожалению пока не пользовался в проектах не «энумами», не «немонобеховыми классами».
О, сколько нам открытий чудных Готовят просвещенья дух И опыт, сын ошибок трудных, И гений, парадоксов друг, И случай, бог изобретатель.
Аватара пользователя
Serge
UNIверсал
 
Сообщения: 476
Зарегистрирован: 20 мар 2009, 15:53
Откуда: Сибирь г. Омск
  • Сайт
  • ICQ

Re: [Статья]Тэги и взаимодействие между объектами. ч2

Сообщение Paul Siberdt 09 апр 2015, 11:15

Если совсем тупо - то вот так :) :

Код: Выделить всё
public interface IHittable {
   void HitObject(Vector2 hitDirection, float damage);
}

public class MyMegaClass : MonoBehaviour, IHittable {
    public void HitObject(Vector2 hitDirection, float damage)
    {
    }
}


То есть, где угодно :) .. естесственно, в разумных пределах. Можно завести папку с интерфейсами и хранить каждый в своем файлике. Можно создать библиотеку интерфейсов всего и вся, можно компоновать их вместе с зависимыми классами, можно встроить в менеджер объектов и так далее.
Аватара пользователя
Paul Siberdt
Адепт
 
Сообщения: 5317
Зарегистрирован: 20 июн 2009, 21:24
Откуда: Moscow, Russia
Skype: siberdt
  • Сайт

Re: [Статья]Тэги и взаимодействие между объектами. ч2

Сообщение waruiyume 09 апр 2015, 11:23

Не знаю как сейчас, раньше были какие то глюки, при геткомпоненте интерфейса который лежит в файле с другим именем.
Аватара пользователя
waruiyume
Адепт
 
Сообщения: 6143
Зарегистрирован: 30 окт 2010, 05:03
Откуда: Ростов на Дону

Re: [Статья]Тэги и взаимодействие между объектами. ч2

Сообщение Paul Siberdt 09 апр 2015, 11:27

Эээ.. странно. У меня гет компонент в InputController-е, сам интерфейс - в отдельном файлике с абстрактным InteractiveObject, а на объектах - свои наследованные приблуды. Все всегда в разных файлах было.
Аватара пользователя
Paul Siberdt
Адепт
 
Сообщения: 5317
Зарегистрирован: 20 июн 2009, 21:24
Откуда: Moscow, Russia
Skype: siberdt
  • Сайт

Re: [Статья]Тэги и взаимодействие между объектами. ч2

Сообщение waruiyume 09 апр 2015, 12:17

Вы меня не поняли. Проблемы были если не совпадали имя интерфейса и имя файла в котором тот находится.
Аватара пользователя
waruiyume
Адепт
 
Сообщения: 6143
Зарегистрирован: 30 окт 2010, 05:03
Откуда: Ростов на Дону

Re: [Статья]Тэги и взаимодействие между объектами. ч2

Сообщение Paul Siberdt 09 апр 2015, 12:27

А у меня имя файла не совпадает с именем интерфейса. Имя файла, скорее, будет совпадать с именем клаасса.

Как вариант, файлик FwInteractiveObjectBase.cs
Код: Выделить всё
using UnityEngine;

public abstract class FwInteractiveObjectBase : MonoBehaviour
{
    [System.NonSerialized]
    public Transform t;
    [System.NonSerialized]
    public Collider c;
.....
}

public interface IFwInteractiveObject
{
    void OnHover();
    void OnUnhover();
    void OnPress();
    void OnRelease();
.....
}
Аватара пользователя
Paul Siberdt
Адепт
 
Сообщения: 5317
Зарегистрирован: 20 июн 2009, 21:24
Откуда: Moscow, Russia
Skype: siberdt
  • Сайт

Re: [Статья]Тэги и взаимодействие между объектами. ч2

Сообщение Strannik 09 апр 2015, 14:33

ilkalawson, спасибо, что пишете статьи! Далеко не каждый разработчик удосужится поделиться своими знаниями с другими, потратив время на написание статьи. Честь и хвала вам!
Вот только одним текстом как-то не удобно просматривать, как классы взаимодействуют друг с другом. Может вам попробовать Uml диаграммы вставлять?

Думаю, в статье упрощенно многое, для простого понимания. Иначе бы кода было бы больше.
Тоже поделюсь своими мыслями как можно в более сложном проекте делать, с моей точки зрения. Возможно, многое будет не по теме. Да и в небольшом проекте, многое будет лишним.

Я не понял, про это ли выше говорят или нет. В общем, в unity5 можно сразу получать компонент по интерфейсу (я каких-нибудь проблем не заметил):
gameObject.GetComponent<IHittable>();

if (this.wallType == WallType.Glass) this.prochnost -= damage;
else if (this.wallType == WallType.Brick) this.prochnost -= damage / 2f;
else if (this.wallType == WallType.Metall) this.prochnost -= damage / 3f;
Тут можно или в перечислении сразу задать значения или в его атрибутах (предпочтительней), тогда будет без проверки типа, например: prochnost -= damage / (int)this.wallType.

Когда программист замечает сходные свойства двух или более объектов, ему сразу на ум приходит решение объединить эти объекты, каким то более базовым объектом, от которого уже дальше будут наследоваться остальные объекты
А разработчику игр должно приходить на ум выделить из них отдельный компонент :)

Enemy.cs – не стоит разделять классы на то, кому объект принадлежит. Тут лучше тег, слой, или лучше отдельное поле (например, номер игрока) завести и по нему проверять какому игроку принадлежит объект.

damage, health, prochnost – я обычно подобное в отдельный компонент(ы) выделяю – Stats.
Также, можно не классы Enemy, Box, Wall с интерфейсом IHittable создавать, а компонент выделить - HitsHandler с тем же интерфейсом или абстрактным классом.
Strannik
UNIт
 
Сообщения: 93
Зарегистрирован: 26 апр 2012, 22:30
Откуда: Омск

Re: [Статья]Тэги и взаимодействие между объектами. ч2

Сообщение ilkalawson 09 апр 2015, 16:53

damage, health, prochnost – я обычно подобное в отдельный компонент(ы) выделяю – Stats.
Также, можно не классы Enemy, Box, Wall с интерфейсом IHittable создавать, а компонент выделить - HitsHandler с тем же интерфейсом или абстрактным классом.

Я привел лишь утрированный пример для, как вы сказали, большего понимания. К тому же я в начале второй части немного задел тему объединения всех трех объектов общим обычным\абстрактным классом, а потом объяснил почему все же выбрал интерфейс.
К тому же я предупредил что - дело каждого лично как и из чего будут состоять компоненты, я лишь показал как можно взаимодействовать с объектами на уровне ООП, все остальное является сугубо личным делом каждого.

Кстати, если это правда, что в unity 5 можно брать интерфейс как компонент - очень круто, хотя я не представляю как это выглядит, т к тип T должен быть представлен от Component.
1.png

или так
Синтаксис:
Используется csharp
IHittable hitObject = this.gameObject.GetComponent(typeof(IHittable).ToString()) as IHittable;

А ваш вариант:
Синтаксис:
Используется csharp
gameObject.GetComponent<IHittable>();

выдает ошибку, что логично.
попробовать Uml диаграммы вставлять?

Я уже говорил что было бы очень удобно как на Хабре писать статьи. К тому же здесь нет таких сложных зависимостей или глубокой иерархии, которую нужно было бы отслеживать.
У вас нет доступа для просмотра вложений в этом сообщении.
ilkalawson
UNIверсал
 
Сообщения: 412
Зарегистрирован: 19 янв 2015, 20:38
Skype: lawsonunity

Re: [Статья]Тэги и взаимодействие между объектами. ч2

Сообщение waruiyume 09 апр 2015, 18:09

Синтаксис:
Используется csharp
IHittable hitObject = this.gameObject.GetComponent<IHittable>();
Аватара пользователя
waruiyume
Адепт
 
Сообщения: 6143
Зарегистрирован: 30 окт 2010, 05:03
Откуда: Ростов на Дону

Re: [Статья]Тэги и взаимодействие между объектами. ч2

Сообщение Strannik 09 апр 2015, 18:57

ilkalawson писал(а):
damage, health, prochnost – я обычно подобное в отдельный компонент(ы) выделяю – Stats.
Также, можно не классы Enemy, Box, Wall с интерфейсом IHittable создавать, а компонент выделить - HitsHandler с тем же интерфейсом или абстрактным классом.

Я привел лишь утрированный пример для, как вы сказали, большего понимания. К тому же я в начале второй части немного задел тему объединения всех трех объектов общим обычным\абстрактным классом, а потом объяснил почему все же выбрал интерфейс.
К тому же я предупредил что - дело каждого лично как и из чего будут состоять компоненты, я лишь показал как можно взаимодействовать с объектами на уровне ООП, все остальное является сугубо личным делом каждого
Понятно. Мне просто тоже захотелось свой вариант показать :)

ilkalawson писал(а):
Кстати, если это правда, что в unity 5 можно брать интерфейс как компонент - очень круто, хотя я не представляю как это выглядит, т к тип T должен быть представлен от Component.

Ну, они убрали код "where T: Component", вот и работает.
Strannik
UNIт
 
Сообщения: 93
Зарегистрирован: 26 апр 2012, 22:30
Откуда: Омск

Re: [Статья]Тэги и взаимодействие между объектами. ч2

Сообщение ilkalawson 10 апр 2015, 13:46

Мне просто тоже захотелось свой вариант показать

Так попробуйте написать свой вариант взаимодействия. Это очень полезно будет иметь две точки зрения на одну тему - есть из чего выбирать.
ilkalawson
UNIверсал
 
Сообщения: 412
Зарегистрирован: 19 янв 2015, 20:38
Skype: lawsonunity

Re: [Статья]Тэги и взаимодействие между объектами. ч2

Сообщение Strannik 10 апр 2015, 14:34

Ну так вы уже привели удобный пример, на основании которого я вкратце привел свой вариант. Зачем кучу тем создавать про одно и тоже с разных точек зрения, когда можно в одной.
Да и лень мне объемные статьи писать со своими примерами :)
Strannik
UNIт
 
Сообщения: 93
Зарегистрирован: 26 апр 2012, 22:30
Откуда: Омск


Вернуться в Уроки

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и гости: 1