Осваиваю делегаты.

Форум для самых маленьких, а так же тех, кому недосуг читать справку самостоятельно.

Осваиваю делегаты.

Сообщение EagleOwle 21 мар 2017, 11:33

Приветствую.
Подскажите пожалуйста.
Есть класс EventController.
В нем объявляются делегаты
Синтаксис:
Используется csharp
public delegate void ChangePlayerState(int value);

public class EventController : MonoBehaviour
{
    private static EventController _singleton;

    public static EventController Singleton
    {
        get
        {
            if (_singleton == null)
            {
                _singleton = GameObject.FindObjectOfType<EventController>();
            }

            return _singleton;
        }
    }

    public ChangePlayerState d_ChangePlayerState;
 


Далее в другом классе подписываемся на делегат.
EventController.Singleton.d_ChangePlayerState += ChangePlayerState;

Все хорошо работает. Вот только когда останавливаем игру, вылетают ошибки
NullReferenceException: Object reference not set to an instance of an object

Понятно, что уничтожается обьект EventController.

В классе EventController я добавил ещё делегат. public DelateEventController d_DestroyEventController;
И в методе OnDestroy() в классе EventController вызываю этот делегат.
Подписчики подписываются
EventController.Singleton.d_DestroyEventController += DestroyEventController;
void DestroyEventController()
{
EventController.Singleton.d_ChangePlayerState -= ChangePlayerState;
EventController.Singleton.d_DestroyEventController -= DestroyEventController;
EventController.Singleton.d_PushActionButton -= Action;
EventController.Singleton.d_GetBotFollow -= GetBotFallow;
}
Соответственно, до удаления этого обьекта все подписчики на делегат должны отписатся и не вызывать ошибки.
Но ошибки не исчезли. Что-то я не до конца понимаю...
EagleOwle
UNIверсал
 
Сообщения: 493
Зарегистрирован: 02 янв 2015, 16:27

Re: Осваиваю делегаты.

Сообщение samana 21 мар 2017, 18:44

Я тут немного запутался, и меня сбил вызов какого-то другого делегата в OnDestroy, но просто хочу поделиться некоторой информацией на эту тему:
- любой экземпляр класса, который "подписывается" на делегат, должен самостоятельно и отписаться от него, то-есть в вашем классе EventController не должно быть отписок.

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

В общем в классе EventController должен быть только делегат и всё. А остальные классы, которые подписываются, должны самостоятельно и отписываться. Делегату незачем знать о тех, кто к нему добавился, он просто вызывается когда это нужно.
Аватара пользователя
samana
Адепт
 
Сообщения: 4738
Зарегистрирован: 21 фев 2015, 13:00
Откуда: Днепропетровск

Re: Осваиваю делегаты.

Сообщение EagleOwle 21 мар 2017, 19:30

Так и есть. В классе eventController вызывается делегат. Подписок или отписок там нет. По вы0ову этого делегата у всех подписчиков срабатывает метод отписки.
EagleOwle
UNIверсал
 
Сообщения: 493
Зарегистрирован: 02 янв 2015, 16:27

Re: Осваиваю делегаты.

Сообщение ilkalawson 21 мар 2017, 20:25

Потому что если ваш объект на сцене не активен, то он не должен вызывать или принимать какие-то события (в вашем случае делегат).

Потому что если вы выключите объект то метод OnDestroy не вызовится.
ilkalawson
UNIверсал
 
Сообщения: 412
Зарегистрирован: 19 янв 2015, 20:38
Skype: lawsonunity

Re: Осваиваю делегаты.

Сообщение EagleOwle 21 мар 2017, 20:55

Я не отключаю объект. Я вызывают делегат в методе onDestroy. Делегат вызывает метод "отписки".
EagleOwle
UNIверсал
 
Сообщения: 493
Зарегистрирован: 02 янв 2015, 16:27

Re: Осваиваю делегаты.

Сообщение samana 21 мар 2017, 21:19

А вы можете показать полностью тот код, любого одно из классов, которые подписываются и отписываются (можно лишние методы, которые не имеют отношения к подписке не копировать). Потому что тут меня смущает код
Синтаксис:
Используется csharp
EventController.Singleton.d_ChangePlayerState += ChangePlayerState;

ведь ChangePlayerState это у вас имя делегата, а вы почему-то его используете. Да и вообще хочется увидеть чёткую картину происходящего.
Аватара пользователя
samana
Адепт
 
Сообщения: 4738
Зарегистрирован: 21 фев 2015, 13:00
Откуда: Днепропетровск

Re: Осваиваю делегаты.

Сообщение EagleOwle 22 мар 2017, 08:58

Разобрался. Вся беда в инициализации синглтона.
Получается, что при выключении игры со сцены удаляется объект EventController а уже потом происходит отписка от делегата. Хотя странно. В методе OnDestroy класса EventController происходит вызов делегата, который вызывает отписку у всех заинтересованных.
EagleOwle
UNIверсал
 
Сообщения: 493
Зарегистрирован: 02 янв 2015, 16:27

Re: Осваиваю делегаты.

Сообщение EagleOwle 23 мар 2017, 08:34

Собственно вот код
Синтаксис:
Используется csharp
using UnityEngine;

public delegate void ChangePlayerState(int value);
public delegate void DelateEventController();
public delegate void PushActionButton();
public delegate void GetBotFollow(Transform followTransform);
public delegate void SetPlayerLook(Vector3 point);
public delegate void MouseButton0();
public delegate void  DrawAimPosition(ModulBasys modul);

public class EventController : MonoBehaviour
{
    private static EventController _singleton;
    public static EventController Singleton
    {
        get
        {
            if (_singleton == null)
            {
                 _singleton =  GameObject.FindObjectOfType<EventController>();    
            }

            return _singleton;
        }
    }

    public ChangePlayerState d_ChangePlayerState;
    public DelateEventController d_DestroyEventController;
    public PushActionButton d_PushActionButton;
    public GetBotFollow d_GetBotFollow;
    public SetPlayerLook d_SetPlayerLook;
    public MouseButton0 d_MouseButton0;
    public DrawAimPosition d_DrawAimPosition;

    public void OnDelegate(int value)
    {
        if (d_ChangePlayerState != null)
        {
            d_ChangePlayerState(value);
        }
    }

    public void PushActionButton()
    {
        if (d_PushActionButton != null)
        {
            d_PushActionButton();
        }
    }

    private void OnDestroy()
    {
        if (d_DestroyEventController != null)
        {
            d_DestroyEventController();
        }
    }
}
 


Синтаксис:
Используется csharp
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public enum SM_BotState
{
    None,
    Destroy,
    Hide,
    Deploed,
    Off,
    On,
}

public class BotController : MonoBehaviour
{
    [HideInInspector]
    public SM_BotState botState;

    [Header("Состояние игрока")]
    [Tooltip("Измените здесь для смены состояния в ручну.")]
    [SerializeField] SM_BotState currentBotState;

    [Header("Использует / не использует энергию")]
    public UsedEnergy usedEnergy = UsedEnergy.Default;

    [Header("Модули")]
    public ModulBasys[] modulArray;

    [Header("Позиция, куда нужно спрятать робота")]
    public Vector3 hidePosition;

    [Tooltip("Этот трансформ")]
    private Transform thisTransform;

    [Tooltip("Расстояние до земли")]
    private float groundCheckDistance = 0.01f;
    [Tooltip("На земле или нет")]
    private bool isGrounded;
    [Tooltip("Текущий режим использования энергии")]
    private UsedEnergy currentUsedEnergy;

    private Animator animator;
    private CharacterController characterController;
    private bool thisBotFollow = false;

    private void OnDisable()
    {
        //Debug.Log("OnDisable() " + name);

        //Отписываемся от всех делегатов
        DestroyEventController();
    }

    private void OnDestroy()
    {
        //Debug.Log("OnDestroy() " + name);

        //Отписываемся от всех делегатов
        DestroyEventController();
    }

    private void OnEnable()
    {
        EventController.Singleton.d_DestroyEventController += DestroyEventController;
        EventController.Singleton.d_ChangePlayerState += ChangePlayerState;
        EventController.Singleton.d_GetBotFollow += GetBotFallow;

        thisTransform = GetComponent<Transform>();
        characterController = GetComponent<CharacterController>();
        animator = GetComponent<Animator>();
        FindAllModul();

        botState = SM_BotState.Off;
    }

    private void Update()
    {
        //Данная часть предназначена для мониторинга изменения состояния BotState в инспектре
        if (botState != currentBotState)
        {
            ChangeState((int)currentBotState);
        }

        if (botState != SM_BotState.Hide)
        {
            CheckGroundStatus();
        }

        ChangeUsedEnergy();
    }

    //Данный метод присваивается делегату d_ChangePlayerState и вызывается всегда, когда плаер меняет состояние PlayerState
    private void ChangePlayerState(int value)
    {
        //Debug.Log("ChangePlayerState " + PlayerController.playerState.ToString());
        if (PlayerController.playerState == PlayerState.ControlBot && PlayerController.currentFollowTransform == thisTransform)
        {
            ChangeState(5);//On
        }
        else
        {
            ChangeState(4);//Off
        }
    }

    public void ChangeState(int value)
    {
        botState = (SM_BotState)value;

        if (botState == SM_BotState.Hide)
        {
            CheckHideState();
        }

        if (botState == SM_BotState.Destroy)
        {
            DisableAllModul();
            Dead();
        }

        if (botState == SM_BotState.Off)
        {
            animator.SetBool("Action", false);
            gameObject.layer = 0;
            DisableAllModul();
        }

        if (botState == SM_BotState.On)
        {
            animator.SetBool("Action", true);
            gameObject.layer = 8;
            EnableAllModul();
        }

        currentBotState = botState;
    }

    void Dead()
    {
        animator.SetInteger("Dead", 1);
    }

    public void HideBot()//Метод вызыввается из анимации смерти робота
    {
        thisTransform.position = hidePosition;
        ChangeState(2);//Hide
    }

    void CheckHideState()
    {
        //animator.SetInteger("Dead", 0);
    }

    void ClearControl()
    {
        ChangeState(4);//Off
    }

    void CheckGroundStatus()
    {
        RaycastHit hitInfo;
#if UNITY_EDITOR
        Debug.DrawLine(thisTransform.position + (Vector3.up * 0.1f), thisTransform.position + (Vector3.up * 0.1f) + (Vector3.down * groundCheckDistance), Color.red);
#endif

        if (Physics.Raycast(thisTransform.position + (Vector3.up * 0.1f), Vector3.down, out hitInfo, groundCheckDistance))
        {
            isGrounded = true;
        }
        else
        {
            isGrounded = false;
            characterController.Move(Physics.gravity * Time.deltaTime * 1);
        }
    }

    void FindAllModul()
    {
        modulArray = GetComponentsInChildren<ModulBasys>();
    }

    void DisableAllModul()
    {
        foreach (ModulBasys modul in modulArray)
        {
            modul.BotByControl(false);
        }
    }

    void EnableAllModul()
    {
        foreach (ModulBasys modul in modulArray)
        {
            modul.botController = this;
            modul.BotByControl(true);
        }
    }

    void ChangeUsedEnergy()
    {
        if (currentUsedEnergy != usedEnergy)
        {
            botState = SM_BotState.Off;
            currentUsedEnergy = usedEnergy;
        }
    }

    public void Action()
    {
        Debug.Log("Action " + name);
        ChangeState(5);//On
    }

    public void GetBotFallow(Transform followTransform)
    {
        //Debug.Log(followTransform.name);

        if (followTransform == thisTransform)
        {
            thisBotFollow = true;
            EventController.Singleton.d_PushActionButton += Action;
        }
        else
        {
            thisBotFollow = false;
            EventController.Singleton.d_PushActionButton -= Action;
        }
    }

    void DestroyEventController()
    {
        if (EventController.Singleton != null)
        {
            EventController.Singleton.d_ChangePlayerState -= ChangePlayerState;
            EventController.Singleton.d_DestroyEventController -= DestroyEventController;
            EventController.Singleton.d_PushActionButton -= Action;
            EventController.Singleton.d_GetBotFollow -= GetBotFallow;
        }
        else
        {
            //Debug.Log("EventController == null");
        }
    }
}
 


При уничтожении обьекта EventController срабатывает делегат d_DestroyEventController(); и у всех подписчиков срабатывает метод DestroyEventController() Они отписываются от события, но
потом, когда у обьектов подписчиков срабатывает событие OnDestroy они почему то опять пытаются отписатся, а так, как обьекта EventController уже нет, вылазят ошибки.
EagleOwle
UNIверсал
 
Сообщения: 493
Зарегистрирован: 02 янв 2015, 16:27

Re: Осваиваю делегаты.

Сообщение EagleOwle 23 мар 2017, 08:39

В принципе с ошибками я розабрался с помощью проверки на нуль.
Подскажите, как можно реализовать класс eventController виртуальным? Что бы не нужно было его на сцену вешать и он не удалялся как обьект.
Т.е. если я пишу
public abstract class EventController : MonoBehaviour
то конструкция
Синтаксис:
Используется csharp
private static EventController _singleton;
    public static EventController Singleton
    {
        get
        {
            if (_singleton == null)
            {
                 _singleton =  GameObject.FindObjectOfType<EventController>();    
            }

            return _singleton;
        }
    }
 

Уже не работает.
EagleOwle
UNIверсал
 
Сообщения: 493
Зарегистрирован: 02 янв 2015, 16:27

Re: Осваиваю делегаты.

Сообщение Tolking 23 мар 2017, 10:14

Не наследуй от монобехевиеор и сделай нормальный конструктор.
Ковчег построил любитель, профессионалы построили Титаник.
Аватара пользователя
Tolking
Адепт
 
Сообщения: 2714
Зарегистрирован: 08 июн 2009, 18:22
Откуда: Тула

Re: Осваиваю делегаты.

Сообщение EagleOwle 23 мар 2017, 10:18

А что значит нормальный? Можно примерчик, если не сложно?
EagleOwle
UNIверсал
 
Сообщения: 493
Зарегистрирован: 02 янв 2015, 16:27

Re: Осваиваю делегаты.

Сообщение Tolking 23 мар 2017, 11:41

https://msdn.microsoft.com/ru-ru/library/k6sa6h87.aspx

PS Осваивать делегаты не зная основ?!!! Какие еще чудеса нас ожидают в эре шаблонов? Грустно господа...
Ковчег построил любитель, профессионалы построили Титаник.
Аватара пользователя
Tolking
Адепт
 
Сообщения: 2714
Зарегистрирован: 08 июн 2009, 18:22
Откуда: Тула

Re: Осваиваю делегаты.

Сообщение EagleOwle 23 мар 2017, 14:17

Я всего лишь хотел уточнить выражение "нормальный конструктор".
EagleOwle
UNIверсал
 
Сообщения: 493
Зарегистрирован: 02 янв 2015, 16:27

Re: Осваиваю делегаты.

Сообщение EagleOwle 11 апр 2017, 09:19

Товарищи, подскажите, как можно узнать, какие методы подписаны на конкретный делегат?
И как проверить, подписан ли конкретный метод к конкретному делегату?
EagleOwle
UNIверсал
 
Сообщения: 493
Зарегистрирован: 02 янв 2015, 16:27

Re: Осваиваю делегаты.

Сообщение samana 11 апр 2017, 20:04

EagleOwle писал(а):Товарищи, подскажите, как можно узнать, какие методы подписаны на конкретный делегат?
И как проверить, подписан ли конкретный метод к конкретному делегату?

Документация говорит, что у делегата есть метод GetInvocationList, но я не пробовал и не знаю, как это работает.
Аватара пользователя
samana
Адепт
 
Сообщения: 4738
Зарегистрирован: 21 фев 2015, 13:00
Откуда: Днепропетровск


Вернуться в Почемучка

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

Сейчас этот форум просматривают: Google [Bot] и гости: 14