Autor Wątek: c++ skill desing  (Przeczytany 3534 razy)

Offline Norten

  • Użytkownik

# Grudzień 20, 2015, 01:21:21
Przypuśćmy że mam takie umiejętności tak na start: siła ciosu ( zwiększa obrażenia minimalne o y i maksymalne o x ) by ją nabyć potrzeba z punktów siły, złota, i punktów doświadczenia, posiada y poziomów. tak samo żywotność  ( zwiększa punkty życia o x ) by ją nabyć trzeba z punktów innej cechy np. wytrzymałości, złota i pkt. doświadczenia. Ale w przyszłości chce by umiejętności nie były tylko pasywne ale i aktywne o różnym działaniu, efektach i obszarowe, leczące itd. Czyli by to ładnie zrobić trzeba dla każdej umiejętności tworzyć odrębną klasę od klasy skill ( nadrzędnej ) posiadającej elementy wspólne, i utworzyć obiekty ( każdy obiekt będzie zawierał dany poziom skilla czyli co dodaje ile i odrębną grafike ) i to wszystko zapakować w jakąś liste? i z tej listy kopiować do listy gracza? i jak sprawdzić czy gracz spełnia wymagania?

Offline Mr. Spam

  • Miłośnik przetworów mięsnych

Offline Kos

  • Użytkownik
    • kos.gd

  • +2
# Grudzień 20, 2015, 01:57:27
Czyli by to ładnie zrobić trzeba dla każdej umiejętności tworzyć odrębną klasę od klasy skill ( nadrzędnej ) posiadającej elementy wspólne

No można tak, ale jeśli skilli ma być dużo, to Ci nie będzie wygodnie dodawać kolejne (pisanie osobnej klasy? kupa roboty i powtarzania kodu).

Możesz zrobić na początek tylko jedną klasę Skill która zawiera wszystko to co jest wspólne: wymagania, passive/active, koszt many, grafika, efekt...

Jedna klasa na wszystkie skille to dobra opcja na start, ale jeśli będziesz miał tego dużo, to nie zaszkodzi porozbijać. Przykładowo: Może się np. okazać że skill np. 'fire bolt' jest podobne do 'fire ball', ale skill 'summon fire elemental' jest już zupełnie inny. Wtedy możesz pozostawić sobie jeden Skill ale wyodrębnić osobną klasę-interfejs SkillEffect. Definiujesz że że każdy skill ma 1 efekt (albo może dowolną liczbę efektów?). I z tego sobie już możesz dziedziczyć: "DamageEffect", "AreaDamageEffect" itp. Teraz możesz łatwo dodawać nowe skille bez pisania nowych klas, ale możesz też dopisać nowy rodzaj efektu skilla jako osobną podklasę SkillEffect i zrobić na nim parę następnych skilli.

Offline Krzysiek K.

  • Redaktor
    • DevKK.net

# Grudzień 20, 2015, 02:16:58
Cytuj
Jedna klasa na wszystkie skille to dobra opcja na start, ale jeśli będziesz miał tego dużo, to nie zaszkodzi porozbijać.
Zaszkodzi. Przy jednej klasie nie musisz się przejmować, która akurat ma być tą właściwą, łatwiej zrobić serializację (aka save game), oskryptować to później, itp. :)

Offline Xender

  • Użytkownik

# Grudzień 20, 2015, 10:15:17
Przy jednej klasie nie musisz się przejmować, która akurat ma być tą właściwą, łatwiej zrobić serializację (aka save game), oskryptować to później, itp. :)

Więc w C++ nadal trzeba pisać serializację z palca?

Introspekcja/dostęp do metadanych w runtime/refleksja w C++ kiedy?
Tylko nie mówcie mi, że "nie da się bez niepotrzebnego overheadu", bo dałoby się.
Zwłaszcza przy wsparciu ze strony samego języka dałoby się bez zbędnego overheadu.

Offline Krzysiek K.

  • Redaktor
    • DevKK.net

  • +1
# Grudzień 20, 2015, 11:12:13
Cytuj
Więc w C++ nadal trzeba pisać serializację z palca?

Introspekcja/dostęp do metadanych w runtime/refleksja w C++ kiedy?
Też bym chciał wiedzieć. Niestety decydenci od C++ chyba już znudzili się językami imperatywnymi i zamiast robić rzeczy potrzebne robią Lispa z szablonów.

Offline Norten

  • Użytkownik

# Grudzień 20, 2015, 13:42:23
[cpp]
class skills
{
   public:
      string name;
      string opis;
      bool have; // sprawdza czy posiada skilla
      short actualLevel;
      short maxlevel;
      short price exp;
      pricegold;
      virtual ~skills();
}
class fireball
{
   public:
   int damageMin;
   int damageMax;
   int damage;
   void giveDamage(Entity& target );
}
class health
{
   public:
      int healthBonus;
      giveHealth(Entity& target)
      {
         target.healthMax+=healthBonus;
      }
}
[/cpp]

Czyli na przykładzie można robić tak skille? Postać ma mieć vector wksaźników klasy nadrzędnej czyli vector < *skills > posiadaneSkille;

Offline remz

  • Użytkownik

# Grudzień 20, 2015, 14:31:56
Ja proponuje coś takiego:
enum ResistanceType
{
    Physical,
    Elemental,
    Amount
}

enum AttackType
{
    Projectile
}

struct ResistanceValue
{
    ResistanceType type;
    int value;
}

class CreatureStats
{
    int health;

    ResistanceValue resistances[ResistanceType::Amount];
}

class Attack
{
    ResistanceValue damage;
   
    AttackType type;

    std::string name;
}

Attack fireball;
fireball.name = "Fireball"
fireball.type = Projectile;
fireball.damage.type  = Elemental;
fireball.damage.value = 10;

Taka struktura pozwala bezproblemowo dodawać nowe typy ataku / skille / jakkolwiek to sobie nazwiesz i bez dodatkowych kombinacji zapisać je na dysku.
« Ostatnia zmiana: Grudzień 20, 2015, 14:37:53 wysłana przez remz »

Offline świrus

  • Użytkownik
    • Tu trolluje

# Grudzień 20, 2015, 19:11:56
Introspekcja/dostęp do metadanych w runtime/refleksja w C++ kiedy?
Dopisz sobie, jak pamiętam Valve tak zrobił.
http://media.steampowered.com/apps/valve/2014/Sergiy_Migdalskiy_Debugging_Techniques.pdf

btw. W androidzie serializacje też się piszę z palca (Parcelable).
« Ostatnia zmiana: Grudzień 20, 2015, 19:18:20 wysłana przez świrus »

Offline Kos

  • Użytkownik
    • kos.gd

  • +1
# Grudzień 20, 2015, 23:37:16
Czyli na przykładzie można robić tak skille?

Bez sensu trochę. Dlaczego podklasa fireball ma giveDamage, a podklasa health ma giveHealth, skoro tych metod i tak nie ma w klasie bazowej?

- Albo je dopiszesz do interfejsu skills (i otrzymasz jedną wielką klasę, tylko po co wtedy podklasy?),
- Albo będziesz w X miejscach w kodzie robił tragedię typu: if (skill is fireball) { skill.giveDamange() } else if (skill is health) { skill.giveHealth(); } a wtedy dodanie każdego skilla będzie wymagało znalezienia wszystkich takich drabinek ifów w kodzie i poprawienia ich. Wrogowi bym nie życzył. :)

Popatrz na to inaczej: interfejs 'skill' powinien zawierać wszystko co każdy jeden skill ma wspólnego:
- jak się nazywa? jaki ma opis?
- jak go narysować w interfejsie?
- czy jest aktywny (da się go aktywować)?
- co się dzieje gdy go wezmę?
- co się dzieje gdy go aktywuję?
- ile kosztuje mnie aktywacja?
- ...

Taki interfejs powinien być na tyle kompletny żeby każde miejsce w kodzie mogło operować pojęciem "skill" i nie zagłębiać się jaki to jest konkretnie skill.

To jest w ogóle punkt wyjścia w OOP :)

Jak już masz to za sobą, to możesz się zastanowić jak ten interfejs będzie implementowany:
- jedna klasa Skill zawierająca wszystko?
- osobna podklasa Skill na każdy jeden skill?

No i oba mają trochę sensu, ale powiedziałbym że to kupa roboty pisać podklasę zawsze jak chcesz dodać skill do gry, więc podejście 'jedna klasa' ma dużo przyszłości.
Ale żeby ta klasa znowu nie wyszła za duża i za skomplikowana (nie musi tak być- zależy ile masz skilli i jak różnorodnych), możesz robić sobie właśnie zabiegi pomocnicze i wydzielić z klasy Skill dodatkowe pojęcia, jak np. EfektSkilla, KosztSkilla itp. Wtedy możesz zrobić powiedzmy 50 różnorodnych skilli za pomocą 5 klas połączonych na różne sposoby.

Offline Krzysiek K.

  • Redaktor
    • DevKK.net

# Grudzień 21, 2015, 02:15:32
Cytuj
To jest w ogóle punkt wyjścia w OOP :)
Tyle że w tym przypadku to właśnie OOP bym poprosił o wyjście. I zamknięcie za sobą drzwi. ;)

Jak dla mnie sensowne podejścia są dwa:
- w C++ robimy jedną klasę na całość i rozszerzamy o kolejne metody/atrybuty (atrybuty będące wskaźnikami na metody też się przydają),
- w C++ piszemy system, który pozwoli zrobić OOP sensownie i/lub używamy gotowego języka skryptowego,

Bez refleksji w C++ i przy relatywnie dużym czasie kompilacji można się zajechać robiąc logikę na C++'owych klasach.

Offline Kos

  • Użytkownik
    • kos.gd

  • +1
# Grudzień 21, 2015, 09:32:07
Tyle że w tym przypadku to właśnie OOP bym poprosił o wyjście. I zamknięcie za sobą drzwi. ;)

Każdy rozumie OOP inaczej w tym wątku :] Rozwiń trochę, rzuć jakiś przykład, bo na razie tylko wyraziłeś opinię i samo to jeszcze nikogo nie przekona że te podejścia są sensowne, a inne nie.

Cytuj
- w C++ robimy jedną klasę na całość i rozszerzamy o kolejne metody/atrybuty (atrybuty będące wskaźnikami na metody też się przydają)

To jest generalnie bardzo ciekawa alternatywa dla subklasowania. Zamiast robienia interfejsu z wirtualną metodą, koszystasz z gotowego, czyli std::function. :) Koncepcyjnie uważam te dwa za równoważne, wydajnościowo chyba też. Klasa wymaga nieco więcej pisania, ale możesz np. zgrupować w 1 interfejsie kilka metod.

Offline Xender

  • Użytkownik

# Grudzień 21, 2015, 12:49:13
@Krzysiek K. - Rozpatrujesz tylko ekstrema.
Krytykujesz kod-ravioli z dużym rozdrobnieniem klas - wiadomo, nie tędy droga.
Tylko, że w zamian proponujesz obiekt, który będzie alfą i omegą.

Powiedz szczerze, czy ten wzorzec potem nie przysparza innych problemów?

Wspominałeś o serializacji - jak serializujesz wskaźnik na metodę, o którym też wspomniałeś?
Dać się da - ale wypadałoby rzucić snippetem kodu, a nie zostawiać jako "ćwiczenie do wykonania przez czytelnika", bo wskaźniki na metody to dość rzadko używana i słabo rozumiana część C++.
Brzmi na mniej-więcej tyle samo zabawy, co serializacja hierarchii klas.

Offline iniside

  • Użytkownik

# Grudzień 21, 2015, 13:31:58
By to ładnie zrobić to trzeba bedzie mocno przysiaść.
Zacząłbym od podzielenia problemu na trzy częsci.
1. Atrybuty
2. Efekty
3. Umiejetnosci aktywne.

1. Atrybut to obiekt reprezentujacy jakąś wartość liczbową (np. siła, level posługiwania sie mieczem). Dlaczego obiekt, a nie float/int ? Bo atrybuty powinny być w stanie śledzić swój wewnętrzny stan, kiedy zmieniasz jego wartosci (kolejnosc stackowania modifikatorow addytywnych i multiplikatywnych na przyklad).
Mozna to przerzucic w inne miejsce, ale po co ? Tutaj jest to o wiele łatwiejsze do implementacji.

2. Efekty sa tym co przeprowadza operacje na atrybutach (aplikacja modifikatorow), jak i aktywne efekty aplikujace inne efekty (efekt, nasluchuje na to jakiego typu efekt jest aplikowany i podejmuje odpowiednia akcje, np. modyfikujaca atrybut lub aplikujac innych efekt). Moze tez nasluchiwac na dowolne inne eventy. Inaczej jest to znane jako buff/debuff.

3. Umiejetnosci aktywne. Czyli Abilities. OOP bedzie mocno przereklamowane w tym przypadku. Skoncentruj sie na robiciu poszczegolnych funkcjonalnosci na obiekty, ktore mozesz po prostu dokładać do ability, i których rezultat działania będzie mogl wywolywac kolejne akcje typu:
1. Target Object->Znalazl cel->walidacja->aplikacja efektu.


Offline Krzysiek K.

  • Redaktor
    • DevKK.net

  • +1
# Grudzień 21, 2015, 15:48:01
Cytuj
Tylko, że w zamian proponujesz obiekt, który będzie alfą i omegą.
Nie "god object", tylko encja. To dwie zupełnie różne rzeczy.

Cytuj
Powiedz szczerze, czy ten wzorzec potem nie przysparza innych problemów?
W Quake się sprawdził, mi się sprawdził - więc nie powiem, by przysparzał.

Cytuj
Wspominałeś o serializacji - jak serializujesz wskaźnik na metodę, o którym też wspomniałeś?
Wystarczy zrobić w tablicy listę wskaźników na te metody, wyszukać i zamienić na tekst. Generowanie takich tablic dość prosto oskryptować (np. w AWK).

Offline Risist

  • Użytkownik

# Grudzień 23, 2015, 15:48:52
Od siebie mogę podrzucić rozwiązanie, które zastosowałem ostatnio.

Mam sobie interface GameEvent w którym opisane są wszystkie zdarzenia na jakie obiekt gry musi umieć reagować. Następnie od tej klasy dziedziczy klasa GameEfect która dodatkowo posiada informacje o obiekcie do którego należy oraz GameMultiEfect która przechowuje wiele efektów i na każde zdarzenie uruchamia wszystkie.

Klasa GameObject ( czyli właściwie główna klasa systemu ) dziedziczy po GameMultiEfect z czego wynika, że można dodawać efekty jako swoiste pluginy. Więc można w ten sposób zrobić dowolną zdolność, cały system do obsługi tego jest już gotowy. Zdolności całkowicie pasywne też nie są problemem bo statystyki obiektu mogą być zmieniane podczas dodawania efektu.

Można w tym momencie dodać klasę Skill, która będzie posiadała standardowo zaimplementowane typowe dla wszystkich zdolności, czyli jak zostało wcześniej wspomniane np. nazwa, ikona itp.

I w ten oto sposób można otrzymać prosty do napisania, w miarę uniwersalny i łatwy do korzystania system.