Użyj instancji lub klasy jako zasobów gry (drewno, żelazo, złoto)

31

Tworzę więc grę, w której możesz wysyłać statki do lokalizacji, aby sprzedawać lub kupować surowce, takie jak drewno, żelazo, złoto itp.

Teraz zastanawiałem się, w jaki sposób należy tworzyć zasoby w grze. Wymyśliłem 2 opcje

  1. Utwórz klasę dla każdego zasobu:

    public class ResourceBase {
        private int value;
        // other base properties
    }
    
    public class Gold : ResourceBase {
         public Gold {
             this.value = 40 // or whatever
         }
    }
  2. Utwórz instancje klasy zasobów

    public class Resource {
        string name;
        int value;
    
        public Resource(string name, int value) {
            this.name = name;
            this.value = value;
         }
     }
    
    // later on...
    
    Resource gold = new Resource("Gold",40);

Dzięki drugiej opcji możliwe jest wypełnienie zasobów gry z pliku resources.json, podoba mi się ta struktura.

Nowe pomysły / wzory / struktury projektowe są zawsze mile widziane!

EDYCJA: To trochę jak towarzysząca aplikacja Assassins Creed Black Flag. Zobacz pasek zasobów na obrazku poniżej

EDYCJA 2: Zrobiłem trochę więcej badań nad „ładowaniem przedmiotów / zasobów z pliku JSON” i znalazłem tego bloga: Moc JSON w tworzeniu gier - Przedmioty . Pokazuje najlepsze opcje 1 i 2. Nadal możesz dodawać funkcje do każdego zasobu :)

wprowadź opis zdjęcia tutaj

MDijkstra
źródło
1
Minecraft ma osobne klasy dla każdego zasobu (nie że powinieneś)
MCMastery
@MCMastery ohh jest Minecraft open-source? Chciałbym rzucić okiem na tę strukturę. I
dziękuję
1
Nie jest, ale zawsze jest zaciemniane dla serwerów ... patrz github.com/Wolf-in-a-bukkit/Craftbukkit/tree/master/src/main/...
MCMastery
12
Nie używaj takiego ciągu. Zbyt łatwo jest pomylić „złote” z „glod” lub podobnym, a debugowanie jest ogromnym bólem. enumZamiast tego użyj .
Nolonar,
Minecraft nie jest technicznie open source, ale prawie całkowicie został zdekompilowany i zdemaskowany. Nie powinno być możliwe znalezienie odszyfrowanego źródła w dowolnym miejscu w Internecie, ale możesz pobrać i uruchomić proces, aby to zrobić z files.minecraftforge.net (ten, którego chcesz pobrać MDK)
JamEngulfer

Odpowiedzi:

51

Idź z drugim podejściem, po prostu ze względu na fakt, że możesz wprowadzić nowe typy zasobów lub elementy w dowolnym momencie bez konieczności przepisywania lub aktualizacji kodu ( programowanie oparte na danych ).


Edytować:

Aby wyjaśnić nieco więcej, dlaczego jest to ogólnie dobra praktyka, nawet jeśli masz 100% pewności, że jakaś wartość nigdy się nie zmieni.

Weźmy przykład gry na konsolę wspomniany w komentarzach, ponieważ stanowi to bardzo dobry powód, dla którego nie powinieneś niczego programować na sztywno, chyba że naprawdę masz (lub chcesz) np. Klucze szyfrujące, własne imię itp.

Wydając grę na konsolach, zwykle muszą one przejść proces przeglądu, w którym własna kontrola jakości producenta konsoli przetestuje grę, zagra w nią, wyszuka problemy itp. Jest to obowiązkowe i kosztuje, dużo pieniędzy. Wydaje mi się, że kiedyś przeczytałem, że wydanie może kosztować 30 000-50 000 USD tylko na samą certyfikację.

Teraz wyobraź sobie, że naciskasz na wydanie gry, płacąc 30 000 $ i czekaj. I nagle zauważasz, że niektóre z twoich wartości gry są dosłownie złamane. Załóżmy, że możesz kupić żelazne sztabki za 50 sztuk złota i sprzedać je za 55 sztuk złota.

Co robisz? Jeśli na stałe zapisałeś te rzeczy, będziesz musiał utworzyć nową wersję aktualizacji / wydania, która będzie musiała ponownie przejrzeć recenzję, więc w najgorszym przypadku zapłacisz jeszcze raz za ponowną certyfikację! Założę się, że Ubisoft nie będzie miał nic przeciwko, ale dla twojej małej kieszeni niezależnego producenta gier… ouch!

Ale wyobraź sobie, że gra od czasu do czasu sprawdza zaktualizowane definicje gier (np. W postaci pliku JSON). Twoja gra może pobrać najnowszą wersję i korzystać z niej w dowolnym momencie, bez konieczności nowej aktualizacji. Możesz naprawić tę nierównowagę / wykorzystać / błąd w dowolnym momencie bez konieczności ponownej certyfikacji lub innych wpłaconych pieniędzy. To tylko drobna decyzja projektowa, która nie wydawała się Ci warta, tylko zaoszczędziłeś 5-cyfrową sumę pieniędzy! Czy to nie jest niesamowite? :)

Tylko nie zrozum mnie źle. Dotyczy to również innych rodzajów oprogramowania i platform. Pobieranie zaktualizowanych plików danych jest ogólnie o wiele łatwiejsze i wykonalne dla standardowego użytkownika, bez względu na to, czy jest to gra, czy jakaś aplikacja / narzędzie. Wyobraź sobie grę zainstalowaną w Program Files w systemie Windows. Aktualizator potrzebuje uprawnień administratora, aby modyfikować cokolwiek i nie można modyfikować uruchomionych programów. Dzięki DDD twój program po prostu pobiera dane i wykorzystuje je. Gracz może nawet nie zauważyć, że nastąpiła aktualizacja.

Mario
źródło
6
+1 dla DDD. Jest to bardzo odpowiednie dla takich rzeczy jak zasoby.
Kromster mówi o wsparciu Moniki
@Funnydutchman, jeśli czyjaś odpowiedź pomogła ci, na Stack Exchange zwyczajowo głosuje się na odpowiedź, klikając strzałkę nad liczbą po lewej stronie. Jeśli odpowiedź najbardziej Ci pomogła, kliknij znacznik wyboru poniżej dolnej strzałki, aby zaakceptować odpowiedź. Oba działania dają użytkownikowi odpowiadającemu dodatkową reputację i sprawiają, że użyteczne odpowiedzi są bardziej widoczne dla społeczności.
Nzall
2
Nie rozumiem, jak musiałeś przepisać kod w pierwszy sposób; wystarczy dodać klasę (chyba że czegoś mi brakuje).
SirPython
4
A oto różnica między kodem obiektowym a kodem obsesyjnym. To, że możesz stworzyć hierarchię obiektów, niekoniecznie oznacza, że ​​powinieneś.
corsiKa
2
@ AgustínLado Gdybym miał dolara za każdym razem, gdy klient powiedział „To się nigdy nie zmieni” i to się zmieniło, mógłbym zabrać nas oboje do porządnej restauracji na kolację (chociaż musielibyśmy trochę skąpić na końcówce, więc może tylko przeciętna restauracja ... ale wystarczająco dużo czasu na obiad dla dwojga.)
corsiKa
29

Ogólna zasada jest taka, że ​​używasz różnych klas, gdy obiekty wymagają innego kodu i instancji tej samej klasy, gdy obiekty wymagają tylko różnych wartości.

Gdy zasoby mają różne mechanizmy gry, które są dla nich unikalne, sensowne może być reprezentowanie ich za pomocą klas. Na przykład, jeśli masz Pluton, który ma okres półtrwania i Króliki, które rozmnażają się zgodnie z sekwencją Fibonacciego , sensowne może być posiadanie klasy podstawowej Resourcez metodą Updatezaimplementowaną jako brak operacji i dwie pochodne klasy, HalfLifeResourcei SelfGrowingResourcektóre przesłonić tę metodę, aby zmniejszyć / zwiększyć wartość (i być może uwzględniać również logikę, aby zarządzać tym, co dzieje się, gdy przechowujesz oba obok siebie).

Ale kiedy twoje zasoby to tak naprawdę głupie liczby, nie ma sensu ich implementować za pomocą czegoś bardziej wymyślnego niż zwykła zmienna.

Philipp
źródło
Dzięki @Phillipp za odpowiedź. Tak myślałem. Ale zasoby tak naprawdę nie zmienią się pod względem właściwości / metod, więc na razie przejdę do opcji 2
MDijkstra
14

Chciałbym dodać, że istnieją dwie dodatkowe opcje:
Interfejs: możesz rozważyć to, jeśli klasa zasobów byłaby tylko „pamięcią” dla 5 liczb całkowitych, a każda inna jednostka miałaby odmienną logikę w odniesieniu do zasobów (np. Wydatki gracza / łup, miasto produkuje ratunek). W takim przypadku możesz w ogóle nie chcieć klasy - po prostu chciałeś ujawnić, że jakiś byt ma zmienną, z którą inni mogą wchodzić w interakcje:

interface IHasResources {
  int Gold { get; set; }
  int Wood { get; set; }
  int Cloth { get; set; }
  // ....
}

class Player : IHasResources { }
class City: IHasResources { }

ponadto ma to tę zaletę, że możesz ograbić miasto dokładnie tym samym kodem, co zrabowałbyś flotę wroga, promując ponowne użycie kodu.
Minusem jest to, że implementacja takiego interfejsu może być nudna, jeśli wiele klas go implementuje, więc możesz skończyć z interfejsami, ale nie z oryginalnym problemem, rozwiązując go z opcją 1 lub 2:

interface IHasResources { 
   IEnumerable<Resource> Resources { get; }
}

Instancje (z wyliczeniem): w niektórych przypadkach pożądane byłoby użycie wyliczenia zamiast łańcucha podczas definiowania typu zasobu:

enum Resource {
   Gold, Wood, Cloth, //...
}

class ResourceStorage {
   public Resource ResourceType { get; set; }
   public int Value { get; set; }
}

to zapewni trochę „bezpieczeństwo”, ponieważ IDE daje wykaz wszystkich ważnych zasobów podczas przypisywania go po wpisaniu kropki ResourceType = Resource., dodatkowo istnieje więcej „sztuczki” z teksty stałe ty może potrzebne, jak wyraźnej rodzaju lub składu / flagi które mogę sobie wyobrazić, aby było przydatne podczas tworzenia:

[Flags]
enum Metal {
 Copper = 0x01/*01*/, Tin = 0x02/*10*/, Bronze = 0x03/*11*/
}
Metal alloy = Metal.Copper; //lets forget Value(s) for sake of simplicity
alloy |= Metal.Tin; //now add a bit of tin
Console.WriteLine(alloy); //will print "Bronze"


Co do tego, co najlepsze - nie ma srebrnej kuli, ale osobiście skłaniałbym się ku jakiejkolwiek formie, mogą nie być tak wymyślne jak interfejs, ale są proste, nie zaśmiecają drzewa dziedziczenia i są szeroko rozumiane.

wondra
źródło
2
Dziękuję za odpowiedź @wondra Podoba mi się opcja enum i jak zrobiłeś ten brąz z miedzi i cyny; p
MDijkstra
2
Dołączyłem do tej społeczności, aby móc głosować za rozwiązaniem enum. Możesz także spojrzeć na użycie atrybutu Opis wyliczenia do odwzorowania z pewnej reprezentacji enum ( my-great-enum) JSON na wyliczenie enum ( ) w języku C # MyGreatEnum.
Dan Forbes,
Najwyraźniej byłem zbyt długo poza pętlą Java. Od kiedy dozwolone są deklaracje pól dla interfejsów? O ile mi wiadomo, są one automatycznie statyczne i ostateczne, a zatem mało przydatne do celów opisanych w pytaniu.
M.Herzkamp
2
@ M.Herzkamp to nie pole, to właściwość - a Java nie obsługuje właściwości. Pytanie jest oznaczone jako C #, C # zezwalaj na właściwości w interfejsach - właściwości są w końcu metodami ukrytymi. Obawiam się, że to samo dotyczy adnotacji flag, nieobsługiwanych w Javie, choć nie jestem do końca pewien.
wondra
Dzięki za wyjaśnienie! Brakowało mi znacznika C #, a kod w pytaniu wyglądał tak cholernie podobnie Java: D
M.Herzkamp
2

Powinieneś użyć opcji 1, która ma 3 zalety:

  1. Łatwiej jest utworzyć instancję zasobu - zamiast przekazywać typ zasobu jako parametr, wystarczy zrobić na przykład new Gold(50)
  2. Jeśli chcesz nadać zasóbowi specjalne zachowanie, możesz to zrobić, zmieniając jego klasę.
  3. Łatwiej jest sprawdzić, czy zasób jest określonego typu - resource instanceof Gold
Zac
źródło
Wszystkie odpowiedzi mają swoje wady i zalety, trudno wybrać, który jest lepszy. Dokonałem edycji pytania, co sądzisz o tej strukturze?
MDijkstra
1

Jeśli twoje zasoby nie mają własnej logiki biznesowej lub specjalnych scenariuszy dotyczących interakcji z otoczeniem, inne zasoby (oprócz handlu, które omówiłeś) lub odtwarzacz, to opcja druga bardzo ułatwia dodawanie nowych.

Jeśli musisz wziąć pod uwagę sytuacje, które mogłyby się zdarzyć, jeśli zmieszałeś dwa zasoby do wytworzenia, jeśli zasoby mogą mieć znacznie inne właściwości (wiadro wody bardzo różni się od bryły węgla) lub inne złożone interakcje ekonomiczne, wówczas podejście 1 to znacznie bardziej elastyczny dla silnika, ale nie dla danych.

Kristian Williams
źródło