Задать вопрос
@Gera01
Unity, С# и больше ничего.

Что я не так делаю с интерфейсами?

Есть интерфейс:
using UnityEngine;

public interface IHealth
{
    void TakeDamage(int damage);
    void Death();
    
    WarSide GetWarSide();
}

Скрипт который его реализует:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class BotHelth : MonoBehaviour, IHealth
{
    public int health;
    
    [SerializeField]
    private WarSide _warSide;

    private KnightAnimation _knightAnimation_cs;

    void Start()
    {
        _knightAnimation_cs = GetComponent<KnightAnimation>();
    }

    public void TakeDamage(int damage)
    {
        health -= damage;
        
        if (_knightAnimation_cs != null)
            _knightAnimation_cs.SetTrigger("TakeDamage");

        if (health <= 0)
        {
            Death();   
        }
    }

    void OnTriggerEnter(Collider other)
    {
        if (other.gameObject.GetComponent<IDamage>() != null)
        {
            TakeDamage(other.gameObject.GetComponent<IDamage>().Damage);
        }
    }
    

    public void Death()
    {
        _knightAnimation_cs.SetBool("Death", true);
        Destroy(GetComponent<KnightAttack>());
        Destroy(GetComponent<KnightBrain>());
        Destroy(GetComponent<KnightMove>());
        Destroy(GetComponent<KnightAnimation>());
        Destroy(this);
    }

    public WarSide GetWarSide()
    {
        return _warSide;
    }
}

Обьявляю переменную (которая кстати не видна в инспекторе) и задаю ей значение:
public IHealth _health;
void Start()
   {
       _health = GetComponent<IHealth>();
}

Но вот проблема, даже при проверке на то, есть ли скрипт реализующий интерфейс на обьекте - мне выдает ошибку на строоку
if (obj.GetComponent().GetWarSide() != _health.GetWarSide())
, или Unity просто отказывается работать с обьектами на которых есть скрипт реализующий IHealth.
public GameObject FindTarget()
    {
        foreach (GameObject obj in _botsee_cs.GetObjectThatISee())
        {
            if (obj.GetComponent<IHealth>() != null)
            {
                if (obj != gameObject)
                {
                    if (obj.GetComponent<IHealth>() != null)
                    {
                       if (obj.GetComponent<IHealth>().GetWarSide() != _health.GetWarSide())                       
{
                            return obj;
                        }
                    }
                }
            }
        }

        return null;
    }

Жалуется так же на такие строки. Вообщем почти на все где есть работа с интерфейсами.
if (other.gameObject.GetComponent<IDamage>().GetMyHealth().GetWarSide() != GetWarSide())

Ошибки по типу таких бывают:
NullReferenceException: Object reference not set to an instance of an object
BotHelth.OnTriggerEnter (UnityEngine.Collider other) (at Assets/Scripts/BotHelth.cs:44)

Где я делаю что то не то и почему unity себя так ведет?
  • Вопрос задан
  • 485 просмотров
Подписаться 4 Простой 5 комментариев
Решения вопроса 1
K0TlK
@K0TlK
Буллю людей.
Что я не так делаю с интерфейсами?

Всё.
У тебя у одного интерфейса слишком много ответственностей у него и Warside какой-то есть и дамаг может принимать и умереть может.

Разделяй этот интерфейс на несколько.

namespace Health
{
    public interface IHealth
    {
        void Lose(int amount);
        void Restore(int amount);
    }
    
    public interface IMutable<out T>
    {
        T Current { get; }
    }
    
    public interface IFinal
    {
        event Action Over;
    }
}


Получается такой Health:

using System;
using UnityEngine;

namespace Health
{
    public class Health : IHealth, IFinal, IMutable<int>
    {
        public event Action Over;
        private readonly int _max;
        private const int Min = 0;

        public Health(int max)
        {
            _max = max;
            Current = _max;
        }
        
        public int Current { get; private set; }
        
        public void Lose(int amount)
        {
            SetCurrent(Current - amount);
        }

        public void Restore(int amount)
        {
            SetCurrent(Current + amount);
        }

        private void SetCurrent(int amount)
        {
            Current = Mathf.Clamp(amount, Min, _max);
            
            if (Current == Min) Over?.Invoke();
        }
        
    }
}


Health не должен быть отдельным компонентом, который будет висеть на условном рыцаре. Рыцарь будет содержать в себе этот Health, но напрямую хп ему изменять никто не будет, поэтому нужен еще один интерфейс IDamageable:
namespace Health
{
    public interface IDamageable
    {
        void ApplyDamage(int amount);
    }
}


И сам рыцарь:
using UnityEngine;

namespace Health
{
    public class Knight : MonoBehaviour, IDamageable
    {
        [SerializeField] private int _maxHealth = 100;
        private Health _health;

        private void Awake()
        {
            _health = new Health(_maxHealth);
        }

        private void OnEnable()
        {
            _health.Over += Die;
        }

        private void OnDisable()
        {
            _health.Over -= Die;
        }

        public void ApplyDamage(int amount)
        {
            _health.Lose(amount);
            Debug.Log($"Damaged, hp left - {_health.Current}");
        }

        private void Die()
        {
            Debug.Log("Died");
            Destroy(gameObject);
        }
    }
}


Теперь, чтобы нанести урон рыцарю, нужно получить компонент IDamageable и вызвать его метод ApplyDamage:
using UnityEngine;

namespace Health
{
    public class Enemy : MonoBehaviour
    {
        [SerializeField] private int _damage = 50;
        
        private void OnTriggerEnter2D(Collider2D other)
        {
            if (other.TryGetComponent(out IDamageable damageable))
            {
                damageable.ApplyDamage(_damage);
            }
        }
    }
}


Всё. Используй TryGetComponent и тогда не нужно будет делать миллион проверок является ли что-то null.
Warside твой должен висеть на рыцаре, а не на хп, поэтому делай отдельный интерфейс под этот Warside.
Ответ написан
Пригласить эксперта
Ответы на вопрос 1
@alkska
Unity developer
Котик конечно крутой, СОЛИДно объяснил тебе за все, но ты никогда так не достанешь интерфейс с объекта...
Тебе не понравилось, что интерфейс не показывется в инспекторе - так объяви gameObject
и потом в awake доставай свой интерфейс вот так :
gameObg = GetComponent(typeof(IHealth)) as IHealth;
а еще не нужно доставать компоненты в рантайме - пиши отдельные скрипты детекторы

using System;
using UnityEngine;

[RequireComponent(typeof(BoxCollider))]
public class PlayerDetector : MonoBehaviour
{
    internal event Action OnPlayerEnter, OnPlayerExit, OnPlayerStay;

    protected virtual void OnTriggerEnter(Collider other)
    {
       if (OnPlayerEnter != null)
        if (IsPlayer(other.gameObject))
        {
            OnPlayerEnter.Invoke();
        }
    }
    protected virtual void OnTriggerExit(Collider other)
    {
       if (OnPlayerExit != null)
        if (IsPlayer(other.gameObject))
        {
            OnPlayerExit.Invoke();
        }
    }
    protected virtual void OnTriggerStay(Collider other)
    {
       if (OnPlayerStay!=null)
        if (IsPlayer(other.gameObject))
        {
            OnPlayerStay.Invoke();
        }
    }


    public static bool IsPlayer(GameObject ob)
    {
        if (ob.CompareTag("Player"))
            return true;

        return false;
    }
}

Метишь тегом своим рыцаря и кидаешь скрипт на объект который может наносить урон, подписываешься на OnPlayerEnter в скрипте который наносит Damage и не паришься -все безопасно
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы