Unity - kopiowanie obiektu

0

Cześć,

Mam problem ze skopiowaniem obiektu. Mam obiekt Ball, którego chcę skopiować (stworzyć nową instancję) co mi się już po części udało. Pojawiają się jednak dwa problemy:

  1. gdy zachodzi warunek do skopiowania obiektu, kopiuje mi się tylko ten obiekt główny, a chciałbym aby kopiowały się wszystkie istniejące już na ekranie, czyli np. po pierwszym kopiowaniu mam 2 na ekranie, później 4, później 8 itd.
  2. czy mogę kopiować obiekt pomijając przy tym pewne metody w nim zawarte? Po prostu chcę aby się nie wykonywały bo mój przypadek polega na tym, że przy rozpoczęciu gry piłka jest zalockowana, dopiero po kliknięciu zostaje wystrzelona i chciałbym aby kopiowanie nie brało pod uwagę metody, która odpowiedzialna jest za to czy piłka jest zalockowana.
0
  1. Kiedy kopiujesz obiekt, to zapisuj do listy referencję instancji skopiowanego obiektu. Przy każdym kopiowaniu kopiuj całą listę obiektów, a nie tylko pierwszy obiekt.
    1 alternatywny) Gdyby piłka nie była obiektem fizycznym (który porusza się niezależnie), to mógłbyś nowe instancje piłki tworzyć i zmieniać ich rodzica na pierwszą piłkę. Wtedy za każdym razem gdy kopiujesz pierwszą piłkę, cała reszta też się skopiuje.
  2. Możesz inaczej zaprojektować klasę piłki, tak aby pewne metody inicjujące były wywoływane przez inną klasę. Nie musisz w Awake(), Start(), OnEnable() itd, wszystkiego ustawiać.
0
  1. ok pokombinuję z tym
  2. z tym, że te metody są właśnie wywoływane w Update, a nie w Start, zresztą chyba nie ma to znacznia. Skrypt ball.cs ma w sobie obecnie jedną klasę Ball. Rozumiem, że rozdzielić te metody na dwa skrypty np. FirstBall i OtherBalls?
0
Krispekowy napisał(a):
  1. ok pokombinuję z tym
  2. z tym, że te metody są właśnie wywoływane w Update, a nie w Start, zresztą chyba nie ma to znacznia. Skrypt ball.cs ma w sobie obecnie jedną klasę Ball. Rozumiem, że rozdzielić te metody na dwa skrypty np. FirstBall i OtherBalls?

Skoro klasy mają wspólne pola, to możesz użyć dziedziczenia. SpecialBall dziedziczy po Ball.
Bez większej zmiany designu, musiałbyś w Update np. sprawdzać boola if (isFirstBall) i w każdym klonie ustawiać go na false.
Żeby nie tworzyć wirtualnego Update (virtual/override), zarówno SpecialBall, jak i Ball mogą mieć swoje Update'y, które będą wywoływać odpowiedni zestaw metod dla każdej klasy.

0
Spine napisał(a):
Krispekowy napisał(a):
  1. ok pokombinuję z tym
  2. z tym, że te metody są właśnie wywoływane w Update, a nie w Start, zresztą chyba nie ma to znacznia. Skrypt ball.cs ma w sobie obecnie jedną klasę Ball. Rozumiem, że rozdzielić te metody na dwa skrypty np. FirstBall i OtherBalls?

Skoro klasy mają wspólne pola, to możesz użyć dziedziczenia. SpecialBall dziedziczy po Ball.
Bez większej zmiany designu, musiałbyś w Update np. sprawdzać boola if (isFirstBall) i w każdym klonie ustawiać go na false.
Żeby nie tworzyć wirtualnego Update (virtual/override), zarówno SpecialBall, jak i Ball mogą mieć swoje Update'y, które będą wywoływać odpowiedni zestaw metod dla każdej klasy.

Ok, czyli w zasadzie powinienem też utworzyć nowy Prefab, który będzie posiadał komponent skrypt SpeciallBall zamiast Ball? I to jego kopiować poprzez Instantiate(Prefab) as GameObject?

0
Krispekowy napisał(a):

Ok, czyli w zasadzie powinienem też utworzyć nowy Prefab, który będzie posiadał komponent skrypt SpeciallBall zamiast Ball? I to jego kopiować poprzez Instantiate(Prefab) as GameObject?

Raczej tak.
Tylko polecam zamiast GameObject uściślić typ obiektu, bo GameObject pozwala twórcy na przypisanie dowolnego obiektu do pola prefab (!).
Twój ball prefab może w edytorze być przypisany do publicznego pola typu komponentu przypisanego do prefaba. Instantiate obiektu konkretnego typu nie wymaga rzutowania.

W skrypcie SpecialBall (który dziedzicz po Ball) tworzysz pole:

public Ball ballPrefab;

Do tego pola przypisujesz sobie prefab z dołączonym skryptem Ball.
Tworzysz nowe instancje poleceniem:

Ball newBall = Instantiate(ballPrefab);

Potem ten newBall możesz dodać do listy typu Ball, żeby kopiować te obiekty, tak jak chciałeś.

0
Spine napisał(a):
Krispekowy napisał(a):

Ok, czyli w zasadzie powinienem też utworzyć nowy Prefab, który będzie posiadał komponent skrypt SpeciallBall zamiast Ball? I to jego kopiować poprzez Instantiate(Prefab) as GameObject?

Raczej tak.
Tylko polecam zamiast GameObject uściślić typ obiektu, bo GameObject pozwala twórcy na przypisanie dowolnego obiektu do pola prefab (!).
Twój ball prefab może w edytorze być przypisany do publicznego pola typu komponentu przypisanego do prefaba. Instantiate obiektu konkretnego typu nie wymaga rzutowania.

W skrypcie SpecialBall (który dziedzicz po Ball) tworzysz pole:

public Ball ballPrefab;

Do tego pola przypisujesz sobie prefab z dołączonym skryptem Ball.
Tworzysz nowe instancje poleceniem:

Ball newBall = Instantiate(ballPrefab);

Potem ten newBall możesz dodać do listy typu Ball, żeby kopiować te obiekty, tak jak chciałeś.

Stworzyłem nowy skrypt, podpiąłem go pod pod nowy prefab i otrzymuję NullReferenceException. Dodam, że metodę DoubleBall wywołuję w skrypcie Bonus w momencie kolizji dwóch obiektów.
Poniżej skrypt

public class AdditionalBall : Ball
{
    //config params
    public Ball additionalBall;

    //cached component references

    // Start is called before the first frame update
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        
    }

    public void DoubleBall()
    {
        Ball newBall = Instantiate(additionalBall);
    }
}
0

A czy w edytorze pod pole additionalBall podpiąłeś prefab z dołączonym skryptem Ball?

0

Tak. Obiekt, który mam na scenie to Ball, czyli parent. Zastanawiam się jednak, w którym momencie jest tworzony AdditionalBall, bo w nim znajduje się metoda, która tworzy instancję. Czy nie powinna być ona umieszczona właśnie w obiekcie Ball, zamiast w nowym skrypcie AdditionalBall(child), którego nie ma na czas startu gry na scenie?

0

Dziedziczenie powinno być w drugą stronę.
Pierwsza kula ma dodatkowe metody, czyli rozszerza działanie podstawowej kuli.
Na scenie tylko pierwsza kula tworzy kule innego typu (typu podstawowego w hierarchii dziedziczenia).
AdditionalBall u Ciebie powinna być PrimeBall i tylko ona powinna tworzyć obiekty inne niż ona sama (czyli z prefaba).
W sumie reszta kul może kopiować siebie samą, więc gdy zajdzie potrzeba skopiowania obiektu mogą w swoich metodach wykonać akcję: Instantiate(gameObject); albo Instantiate(this);

Jakby Ci się chciało, to możesz też zrobić singletona "fabrykę", tworzącego kule. Wtedy każda kula mogłaby się do niego odnieść.

0

Ok, obiekty się tworzą, ale co ciekawe... max 4... o_O
Następnie otrzymuję komunikaty błędów za każdym razem gdy nastąpi kolizja, po której powinien być zdublowany obiekt:

ArgumentException: The Object you want to instantiate is null.
UnityEngine.Object.CheckNullArgument (System.Object arg, System.String message) (at C:/buildslave/unity/build/Runtime/Export/Scripting/UnityEngineObject.bindings.cs:374)
UnityEngine.Object.Instantiate[T] (T original) (at C:/buildslave/unity/build/Runtime/Export/Scripting/UnityEngineObject.bindings.cs:265)
Ball.DoubleBall () (at Assets/Scripts/Ball.cs:74)
Bonus.OnTriggerEnter2D (UnityEngine.Collider2D collision) (at Assets/Scripts/Bonus.cs:67)```
0

Mam nieco inne pytanie.

W związku z tym, że kopiuję obiekt w obiekcie "Ball" to w przypadku, gdy dany obiekt jest zniszczony - nie wykonuje się dana metoda, bo nie istnieje żadna instancja tego obiektu. W jaki sposób mógłbym rozwiązać ten problem?

0

Co do błędu null pointera, po stworzeniu newBall musisz skopiować pole additionalBall czyli coś w stylu
newBall.additionalBall = this.additionalBall
Inaczej kopia ma null pointer na tym typie

0

Raczej nie, bo Unity tworząc kopię obiektu, przepisuje publiczne pola (dużo używa serializacji), a to publiczne pole, additionalBall prowadzi do prefaba, którego nie ma na scenie, więc referencja ta jest niezmienna.

@Krispekowy: Może jednak dla pewności, zrób singletona, który zawsze używa prefaba do tworzenia nowych kul. Wtedy kiedy chcesz tworzyć nowe kule, po prostu będziesz się odwoływać do tego singletona...

http://www.unitygeek.com/unity_c_singleton/

0

@Spine: chyba że robi tak:
Tworzy pierwszy Ball i przypisuje mu prefab w edytorze jako AdditionalBall ale ten prefab nie ma zainicjalizowanego pola public. Wtedy pierwsze kopiowanie pójdzie ok, ale będzie to kopia prefab bez ustawionego pola public. Kolejne wywołanie to null

0
Spine napisał(a):

Raczej nie, bo Unity tworząc kopię obiektu, przepisuje publiczne pola (dużo używa serializacji), a to publiczne pole, additionalBall prowadzi do prefaba, którego nie ma na scenie, więc referencja ta jest niezmienna.

@Krispekowy: Może jednak dla pewności, zrób singletona, który zawsze używa prefaba do tworzenia nowych kul. Wtedy kiedy chcesz tworzyć nowe kule, po prostu będziesz się odwoływać do tego singletona...

http://www.unitygeek.com/unity_c_singleton/

Nie korzystałem nigdy z singletona. Zaczynam dopiero swoją przygodę z Unity i C#. Rozumiem, że powinienem utworzyć nowy skrypt Singleton, w którym będę tworzył instancję danego prefaba i ten skrypt podpiąć pod prefab Ball?

0
Krispekowy napisał(a):
Spine napisał(a):

Rozumiem, że powinienem utworzyć nowy skrypt Singleton, w którym będę tworzył instancję danego prefaba i ten skrypt podpiąć pod prefab Ball?

Prawie tak...

Ten nowy skrypt podpinasz do nowego obiektu na scenie. I on na tej scenie sobie siedzi i czeka na rozkazy.

Ma przypisane odpowiednie prefaby i sam nie musi być prefabem, bo potrzebna Ci tylko jedna instancja na tylko jednej scenie...

Ten nowy skrypt tylko w Awake przypisuje swojego this do publicznego statycznego pola. I do tego singletona będziesz z innych klas odwoływał się właśnie przez to pole.
NazwaNowejKlasy.statycznyThis.ZrobInstancjeKuli(pozycjaNowejKuli);

W Unity tak wygląda Singleton, bo nie musisz sam zadbać o tworzenie jego obiektu. Co najwyżej możesz w Awake dodać trochę kodu zapewniającego, że obiekt nie będzie usuwany przy zmianie sceny, a w przypadku gdy obiekt już istnieje to go usuniemy. Ale w Twoim przypadku to raczej zbędne i wystarczy Ci sama publiczna statyczna referencja, bez dodatkowych zapewnień w kodzie. Bo po prostu zadbasz o to, by na scenie gameplayu był obiekt z tym skryptem.

0
Spine napisał(a):
Krispekowy napisał(a):
Spine napisał(a):

Rozumiem, że powinienem utworzyć nowy skrypt Singleton, w którym będę tworzył instancję danego prefaba i ten skrypt podpiąć pod prefab Ball?

Prawie tak...

Ten nowy skrypt podpinasz do nowego obiektu na scenie. I on na tej scenie sobie siedzi i czeka na rozkazy.

Ma przypisane odpowiednie prefaby i sam nie musi być prefabem, bo potrzebna Ci tylko jedna instancja na tylko jednej scenie...

Ten nowy skrypt tylko w Awake przypisuje swojego this do publicznego statycznego pola. I do tego singletona będziesz z innych klas odwoływał się właśnie przez to pole.
NazwaNowejKlasy.statycznyThis.ZrobInstancjeKuli(pozycjaNowejKuli);

W Unity tak wygląda Singleton, bo nie musisz sam zadbać o tworzenie jego obiektu. Co najwyżej możesz w Awake dodać trochę kodu zapewniającego, że obiekt nie będzie usuwany przy zmianie sceny, a w przypadku gdy obiekt już istnieje to go usuniemy. Ale w Twoim przypadku to raczej zbędne i wystarczy Ci sama publiczna statyczna referencja, bez dodatkowych zapewnień w kodzie. Bo po prostu zadbasz o to, by na scenie gameplayu był obiekt z tym skryptem.

public sealed class SingletonScript: MonoBehaviour
{
    public static GameObject singleton = null;
    public Ball ball;


    private void Awake()
    {
        singleton = this.gameObject;
    }

    public void Clone()
    {
        Instantiate(ball);
    }

}

Czy teraz powinienem w moim obiekcie Ball odnosić się do metody Clone?

0
public static GameObject singleton = null;

zmień na

public static SingletonScript singleton = null;

Twój ball teraz może w swoich metodach wywołać metodę z singletona:

SingletonScript.singleton.Clone();

Oczywiście SingletonScript musi być przypisany JEDEN RAZ do jakiegoś pustego obiektu na scenie i musisz do niego podpiąć prefab ze skryptem Ball.

0
public sealed class SingletonScript : MonoBehaviour
{
    public static SingletonScript singleton = null;
    public Ball ball;
    public AdditionalBall additionalBall;


    private void Awake()
    {
        singleton = this;
    }

    public void Clone()
    {
        Instantiate(additionalBall);
    }
}

Niestety kopiuje mi się tylko jedna kula. Następuje kolizja 2 innych obiektów -> 2 kule, następna kolizja 3 kule, następna 4 itd... A chciałbym, aby kopiowały się wszystkie instancje, które istnieją na scenie.

0

Nie wiem co dokładnie chcesz osiągnąć. Kiedy dokładnie kule znikają i się pojawiają.
Singleton tworzy jedną kulę przy wywołaniu metody Clone().
Jeśli chcesz tworzyć ich więcej, to albo każda kula powinna wywołać metodę Clone().
Albo zaimplementuj w tym singletonie jakiś mechanizm zarządzania kulami.
Każda utworzona kula może zostać dodana do listy kul.
Każda niszczona kula musi zostać usunięta z listy kul.
Jak już będziesz miał listę w singletonie, to wiesz ile klonów utworzyć i masz referencje do wszystkich kul na scenie.

Jeśli nie potrzebujesz mieć dostępu do kul (zawsze tworzone są na tej samej pozycji itp.), to wystarczy, że w singletonie będziesz zarządzał licznikiem - przy tworzeniu kuli zwiększasz licznik, przy niszczeniu zmniejszasz. I przy klonowaniu zawsze będziesz w pętli wytwarzał ilość kul odpowiadającą licznikowi.

Tylko uważaj z tym licznikiem, żebyś nie zmieniał jego wartości w pętli klonującej!

0
Spine napisał(a):

Nie wiem co dokładnie chcesz osiągnąć. Kiedy dokładnie kule znikają i się pojawiają.
Singleton tworzy jedną kulę przy wywołaniu metody Clone().
Jeśli chcesz tworzyć ich więcej, to albo każda kula powinna wywołać metodę Clone().
Albo zaimplementuj w tym singletonie jakiś mechanizm zarządzania kulami.
Każda utworzona kula może zostać dodana do listy kul.
Każda niszczona kula musi zostać usunięta z listy kul.
Jak już będziesz miał listę w singletonie, to wiesz ile klonów utworzyć i masz referencje do wszystkich kul na scenie.

Jeśli nie potrzebujesz mieć dostępu do kul (zawsze tworzone są na tej samej pozycji itp.), to wystarczy, że w singletonie będziesz zarządzał licznikiem - przy tworzeniu kuli zwiększasz licznik, przy niszczeniu zmniejszasz. I przy klonowaniu zawsze będziesz w pętli wytwarzał ilość kul odpowiadającą licznikowi.

Tylko uważaj z tym licznikiem, żebyś nie zmieniał jego wartości w pętli klonującej!

Kule istniejące na scenie mają się dublować w momencie kolizji obiektu "A" z obiektem "B". Kula jest niszczona w momencie gdy zderzy się z triggerem "Lose". Chyba ten mechanizm, o którym napisałeś byłby najlepszym rozwiązaniem...

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