(Unity) Czemu skrypty dodane do animacji przez opcję "Add Behaviour" różnią się od zwykłych skryptów?

0

W normalnych skryptach, jak sobie utworzysz np. public GameObject, to możesz przeciągnąć i upuścić dowolny GameObject ze sceny na ten "slot", ale jeśli ten skrypt jest dodany przez "Add Behaviour"(opcja dodania skryptu do animacji) , to możesz tylko przeciągnąć i upuścić prefaby na ten slot. Więc, jak chce sobie respić (nie mogę znaleźć lepszego słowa) wiele "Particle Systems" z wielu różnych pozycji, takich, których pozycja jest relatywna do pozycji postaci, jak ona wykonuje jakąś animacje, to jak to zrobić bez użycia tagów?

0

W stanach animatora nie używamy MonoBehaviour, tylko korzystamy ze State Machine Behaviour https://docs.unity3d.com/ScriptReference/StateMachineBehaviour.html
Jeśli chcesz z poziomu State Machine Behaviour dobrać się do skryptu (MonoBehaviour) dołączonego do obiektu postaci, to chyba najlepiej jest to zrobić to przez instancję animatora.

W dowolnym override State Machine Behaviour'a:

    MyBehaviour myBehaviourInstance; // tu będzie referencja do Twojego skryptu
    
    override public void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {
        if (myBehaviourInstance  == null)
        {
            myBehaviourInstance = animator.GetComponent<MyBehaviour>();
        }

        myBehaviourInstance.DoParticlesStuff();
    }

Twój skrypt typu MonoBehaviour o nazwie MyBehaviour musi być dołączony do tego samego obiektu, co Animator.
Niech skrypt MyBehaviour robi wszelkie akcje związane z dodatkami graficznymi. Za to Twój State Machine Behaviour będzie wywoływać funkcje zarządzające efektami znajdujące się w skrypcie MyBehaviour.

0
Spine napisał(a):

W stanach animatora nie używamy MonoBehaviour, tylko korzystamy ze State Machine Behaviour https://docs.unity3d.com/ScriptReference/StateMachineBehaviour.html
Jeśli chcesz z poziomu State Machine Behaviour dobrać się do skryptu (MonoBehaviour) dołączonego do obiektu postaci, to chyba najlepiej jest to zrobić to przez instancję animatora.

W dowolnym override State Machine Behaviour'a:

    MyBehaviour myBehaviourInstance; // tu będzie referencja do Twojego skryptu
    
    override public void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {
        if (myBehaviourInstance  == null)
        {
            myBehaviourInstance = animator.GetComponent<MyBehaviour>();
        }

        myBehaviourInstance.DoParticlesStuff();
    }

Twój skrypt typu MonoBehaviour o nazwie MyBehaviour musi być dołączony do tego samego obiektu, co Animator.
Niech skrypt MyBehaviour robi wszelkie akcje związane z dodatkami graficznymi. Za to Twój State Machine Behaviour będzie wywoływać funkcje zarządzające efektami znajdujące się w skrypcie MyBehaviour.

Twój pomysł mi chwilowo nie przypadł do gustu. Generalnie dobranie się do skryptu na mojej postaci nie jest problemem, problemem jest to, że bardzo dużo korzystam z jednego skryptu, napiszę go poniżej i jego wada jest taka, że nie mogę sobie w czytelny sposób określić pozycji (ustawić referencji na odpowiedni Transform), z której chcę wywołać ParticleSystem i generalnie na niej operować w skrypcie.
I najbardziej czytelne byłoby zrobić to wszystko w jednym skrypcie, ale właśnie nie da się (chyba że tagami) , więc chyba potrzebuje po prostu stworzyć kolejny typ skryptu, który tylko przechowuje ref na ten Transform i ustalasz sobie który Transform chcesz żeby ci przechowywał jakimś intem, dodajesz wszystkie Transformy które chcesz uwzględnić jako dzieci obiektu na którym jest ten skrypt... tylko to znowu będzie nie czytelne... jak to właśnie tak czytelnie napisać?
Do jednej animacji dodaję zwykle parę skryptów tego typu:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ParticleSpawn : StateMachineBehaviour
{
    bool isDone;
    //public GameObject posisiton;
    public GameObject boostParticle;
    public string tag;
    private GameObject particle;
    private GameObject player;
    private Transform pos;
    public float waitTime;
    private float remainingWaitTime;

    public bool dynamic;
    public float speed;
    public float duration;
    private float fixedSpeed;

    override public void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {
        remainingWaitTime = waitTime;
        pos = GameObject.FindGameObjectWithTag(tag).transform;
        player = GameObject.FindGameObjectWithTag("Player");
    }


    override public void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {
        if (isDone) { return; }

        if (remainingWaitTime > 0f)
            remainingWaitTime -= Time.deltaTime;
        else
        {
            particle = (GameObject)Instantiate(boostParticle, pos.position, Quaternion.identity);
            particle.transform.SetParent(player.transform);
            particle.GetComponent<ParticleSystem>().Play();
            isDone = true;
            if (dynamic)
                MoveWithAnimation(player.GetComponent<PlayerController>().facingRight);
        }
    }


    override public void OnStateExit(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {
        Destroy(particle);
        remainingWaitTime = waitTime;
        isDone = false;
    }

    public void MoveWithAnimation(bool facingRight)
    {
        speed = Mathf.Abs(speed);
        fixedSpeed = speed + Mathf.Abs(player.GetComponent<PlayerController>().rb.velocity.x);

        if (!facingRight)
            fixedSpeed *= -1;

        particle.GetComponent<Rigidbody2D>().velocity = new Vector2(fixedSpeed, 0f);
        //Vector3 position = particle.transform.position;
        // Vector3 destination = position + Vector3.right * speed;
        // particle.transform.position = Vector3.MoveTowards(particle.transform.position, destination, duration);
    }

}
0

Podział na klasy z reguły zwiększa czytelność.
Podział na metody również.

To co pokazałeś nadaje się tylko do refaktoryzacji... Nazwij akcje wykonywane w poszczególnych override'ach poprzez wpakowanie ich do metod o odpowiednich nazwach - mówiących co robią.

Wzorce projektowe nie powstały po to, żeby całą logikę zamykać w jednej klasie. Rozbij to co robisz na dwie klasy lub więcej. Niech StateMachineBehaviour tylko wywołuje odpowiednie akcje na obiektach gracza. Niech sam nie implementuje szczegółów tych akcji.

Może Ci się teraz wydawać, że takie przemyślane, uporządkowane podziały są nieczytelne, ale jak tylko załapiesz to podejście, to nie będziesz już chciał bazgrać po staremu ;)
Polecam lekturę: https://javastart.pl/baza-wiedzy/ksiazki/clean-code-robert-c-martin

0

Nie wiem czy do końca rozumiem problem, ale może te tematy Ci coś podsuną:

  • w animacjach można wykorzystać eventy, wpiąć ich wywołanie w poszczególne klatki w oknie 'animation' (nie animator). Dzięki nim możesz wywołać public metode z klasy znajdującej się na gameObject z animatorem.
  • możesz utworzyć controller (singleton?) do obsługi tych particleSystemów, może być czytelniej
    A co do samych transformów, to jest kilka opcji, żadna chyba nie jest doskonała
  • tak jak robisz na tagach, lub tak jak piszesz z tym dodatkowym skryptem, ale wtedy getcomponentsinchildren<mojFakeSkryptTylkoTransform>
  • przez [serializeField] Transform[] - dłubanina, zależy ile tego masz, ale może być dużo przeciągania
  • przez Find() i tablice z nazwami - tylko jeśli jesteś pewny stałości nazw w środku
  • od drugiej strony, skrypt na transformy, który na starcie się 'rejestruje' w obiekcie wyżej
    ja bym chyba jednak przebolał z tym serializeField

1 użytkowników online, w tym zalogowanych: 0, gości: 1