Z ciekawych problemów:
Zasoby (Resource)
Miałem spory problem ze stworzeniem reużywalnych atlasów zawierającymi ikonki przedmiotów. Kłopot leżał w tym, że nie potrafiłem w prawidłowy sposób wczytać plików grafiki, aby później użyć ich jako sprite definiujący ikonkę przedmiotu. A to się wczytały, a to nie, a to źle później poruszałem się po utworzonym atlasie i nie było ikonek, a jak były (czasami) to wszystkie takie same i to wszędzie na całej planszy...
Przełomem był moment gdy zapisałem je jako godotowe zasoby czyli: Resource
public partial class TextureResource : Resource
{
[Export]
public AtlasTexture AtlasResource = new AtlasTexture();
}
To dało możliwość łatwego dodawania kolejnych atlasów do kodu w prosty sposób
i konfigurowania z użyciem gui godota
public partial class ItemRepository
{
private TextureResource ArmourAtlas = ResourceLoader.Load<TextureResource>(
"res://scenes/resources/ArmourTextures.tres"
);
private TextureResource BootAtlas = ResourceLoader.Load<TextureResource>(
"res://scenes/resources/BootTextures.tres"
);
Efekt jest taki, że mam teraz jedno źródło generowania ikonek przedmiotów gotowe do stosunkowo łatwej rozbudowy.
Samym zasobem może być dosłownie wszystko stąd może w dalszej pracy częściej będę sięgał po to rozwiązanie.
Duplikacja kodu
Po stworzeniu pierwszej w miarę działającej wersji plecaka problemem była duplikacja funkcji dodawania / zdejmowania przedmiotów w slotach odpowiedzialnych za plecak i za pasek szybkiego dostępu. W efekcie kod robił w sumie to samo (na obiekcie ItemSlot i HotBarSlot) ale per typ przekazanego obiektu. Co powodowało zbędne kopiowanie kodu i turbo utrudniało wyszukanie błędów (a było ich multum...)
Ratunkiem stał się jakże by inaczej interfejs, który zdefiniował mi wszystkie wspólne funkcje i umożliwił mocne skrócenie kodu.
Już teraz widzę, że przyda się do obsługi takich rzeczy jak sloty w otwartej skrzynce czy u sklepikarza.
Rzeczony interfejs jest trochę koślawy, ale działa (rozważałem dodanie obsługi przez delegate do jakiejś turbo klasy, ale jeszcze nie czaję jak to w c# delegaty działają)
public partial interface ISlot
{
public Item VisibleItem { get; set; }
public int SlotIndex { get; set; }
public bool HasItem => VisibleItem != null;
void PutIntoSlot(Item holdingItem);
public Item PickFromSlot();
public Category category { get; set; }
...// reszta kodu
Przenoszenie przedmiotów
Kolejnym ratunkiem stała się funkcja reparent. Ten magiczny zwrot potrafi samodzielnie przenieść węzeł Node w drzewie węzłów sceny. Funkcja ta bardzo przydała się do wykonania podnoszenia przedmiotu z plecaka do innego slota plecaka
Tutaj trzeba pamiętać że cała scena składa się z węzłów. Węzły tworzą drzewiastą strukturę i mamy możliwość przenoszenia liści tego drzewa pomiędzy gałęziami.
Używanie drzewa w kodzie to bardzo rozbudowana funkcjonalność, o której nie należy zapominać! A ja zapomniałem i zamiast użyć tego od razu sporo namęczyłem się, aby uzyskać oczekiwany efekt który było
- kliknięcie myszy
- reparent czyli podniesienie przedmiotu z węzła opisującego slot w inventory do nadrzędnego węzła w scenie opakowującego plecak
- kliknięcie w slot docelowy i operacja odwrotna czyli reparent z węzła opakowującego plecak do wybranego slota
Wyrzucanie przedmiotów
Kolejny krok to wyrzucanie przedmiotów. O ile napisanie kodu odpowiadającego za zbieranie ich z ziemi było stosunkowo proste tak wyrzucanie nie było takie oczywiste. Chciałem, aby przedmioty dało się wyrzucić myszką klikając obok plecaka. Ale jak to zrobić? Doszedłem do wniosku że zawieszę niewidzialny węzeł o typie Control obejmujący obszar na lewo od plecaka i gdy myszka - trzymająca przedmiot - znajdzie się w tym obszarze kliknięcie spowoduje wyrzucenie przedmiotu pod nogi postaci (z niewielką animacją i przesunięciem, aby przedmioty nie tworzyły stosu).
Stworzenie tego kodu kosztowało mnie sporo czasu mimo, że definicja samego węzła była banalna. Głównym problemem był prawidłowe odczytanie sygnału kiedy myszka jest w obszarze rzucania przedmiotów
i odpowiednie oprogramowanie całej akcji.
public void MouseDraggedToDropArea(InputEvent @event)
{
if (@event is InputEventMouseButton mouseClicked)
{
if (
mouseClicked.ButtonIndex == MouseButton.Left
&& mouseClicked.Pressed
&& holdingItem != null
)
{
ConvertToPickable(holdingItem);
holdingItem.Reparent(this);
holdingItem.QueueFree();
holdingItem = null;
}
}
}
Powyższa funkcja nastręczyła mi multum problemów ponieważ wyrzucany przedmiot stawał się disposed, a ja, zadowolony trzymałem do niego referencje to tu to tam. Efekt był taki że przedmiot można było wyrzucić na ziemię ale podnieść to już się nie dawał i psuł całą grę eleganckim wyjątkiem. Naprawienie tego błędu zajęło mi sporo czasu (stąd powstało min. wspomniane wyżej użycie Resources do trzymania atlasów).
Błąd spowodował też turbo refaktor klas ponieważ musiałem mieć możliwość łatwego rozpoznawania jakie przedmioty są zrzucone, jakie mają przypisane ikonki, gdzie leżą ... czyli w sumie wszystko co o nich można wiedzieć.
Kategorie slotów
Jak mamy plecak i możliwość ubrania postaci to nie chcemy, aby na głowę postaci można było założyć np. buty. Stąd też każdy slot otrzymał kategorię zaś w plecaku pojawiła się możliwość ubrania bohatera i odpowiedniego umieszczenia przedmiotów w zależności od typu przedmiotu przyjmowanego przez kliknięty slot.
Niby prosty kod alesprawił mi pewne problemy - if-ologia dotycząca tego co można a czego nie można wrzucić do danego slota była uciążliwa do napisania, aż w końcu zamknęła się w prostej metodzie:
private bool ItemFitToSlot(ISlot clickedSlot)
{
if (clickedSlot.category == ISlot.Category.ANY)
{
return true;
}
else if (holdingItem == null)
{
return false;
}
else if (clickedSlot.category == ISlot.Category.WAISTBAND)
{
return holdingItem._category == ISlot.Category.WEAPON
|| holdingItem._category == ISlot.Category.WAISTBAND
|| holdingItem._category == ISlot.Category.CONSUMABLE;
}
else
{
return clickedSlot.category == holdingItem._category;
}
}
Podsumowanie
Nieźle się nakodowałem, ale efekt końcowy uważam za zadowalający. Teraz tylko pożenić to co zrobiłem z głównym projektem i dreptać sobie powoli dalej. Kod kilkukrotnie zrefaktorowałem, aby można było go wygodnie edytować i dodawać nowe funkcjonalności. Powolutku poznaję lepiej c# min. pojawiły się pierwsze typy struct oraz record (zdaję sobie jednak sprawę, że jeszcze nie bardzo wiem jak z nich korzystać, ale jeśli ich nie użyję to się nie nauczę).
Z funkcjonalności, których brakuje w powyższym rozwiązaniu:
- wyświetlania opisów przedmiotów po najechaniu myszką i przy pasku szybkiego dostępu.
- Same ikonki są koślawo sformatowane ale to efekt nie dobrania marginesów (mam nadzieję że ich dodanie nie zepsuje czegoś innego...).
- opcja porównania przedmiotu z tym w który postać jest wyposażona
- możliwość przerzucania przedmiotów z czegoś co roboczo nazwiemy skrzynką do plecaka zamiast zawsze wszystko podnosić z podłogi
- interakcja z NCP o wszystko mówiącym typie sklepikarz (tu ciekawy case - przejście z dialogu do zakupów)
- ... inne na które teraz nie wpadłem...
p.s.
Podczas pisania powższego postu i klikania po inventory okazało się że
Błąd ze źle utworzonym przedmiotem powrócił, ale przynajmniej wiem gdzie szukać (źle wczytany region w atlasie i w efekcie źle wygenerowany przedmiot).
Brak komentarzy:
Prześlij komentarz
Tu możesz wstawić swój komentarz