Panel użytkownika
Nazwa użytkownika:
Hasło:
Nie masz jeszcze konta?

dynamika i kolizja w grach 2D

Ostatnio zmodyfikowano 2018-03-19 19:33
Autor Wiadomość
michal11
» 2018-02-14 22:52:50
OK, sorry za niezrozumiałe słowa, po prostu takich słów używam na co dzień, ale postaram się pisać prościej.

Jeżeli rozumiesz angielską definicję słowa callback to o to mi chodziło, jeżeli nie to możesz poszukać też hasła delegate (delegat).
Generalnie z callbackami maż jeden poważny problem a mianowicie musisz zadbać o to aby obiekt który bindujesz w momencie wywołania metody istniał, ale jeżeli bindujesz się do obiektu który sam zawierasz w sobie to nie ma tego problemu. Nie wiem czy jasno napisałem, ale na przykład, jeżeli klasa Barrel zawiera w sobie collision object i ty do tego collision objectu bindujesz metodę klasy Barrel to nie powinno być problemu bo collision object nie może istnieć bez swojego "właściciela". Bindowanie to przypisanie obiektu i jego funkcji do zmiennej (sprawdź sobie std::function i std::bind).

Handlowanie już Saran opisał.

Ja swoją wiedzę czerpię głównie z pracy w Unreal'u, ale system kolizji jest na tyle prosty i uniwersalny, że większość silników implementuje go w podobny sposób (mam tu na myśli sam core systemu kolizji a nie jakieś dodatkowe funkcje czy optymalizacje). Z tego co wiem to silnik Redów obsługuje kolizje podobnie do unreal'a i cryengine chyba też.

Kanały kolizji także Saran opisał, od siebie dodam, że jeżeli już bym się decydował na dodanie ich do mniejszego projektu to raczej nie pozwoliłbym na ustawienie więcej niż jednego kanału dla obiektu.

I tak jak pisałem, dodanie nowego obiektu nie powinno wymuszać na tobie modyfikowania kodu dot. kolizji. Dużo też zależy od tego ile funkcji dodasz do klasy bazowej, jeżeli to będzie tylko prosty GameObject to będziesz musiał castować.

Po co jest tu this? Czyżby po to, by przekazać go do callbacka obiektu, z którym kolidujemy:

this jest też po to żeby wywołać metodę na odpowiednim obiekcie.

Mogę ci pokazać jak zrobiłem coś podobnego dla inputu w jakimś moim starym projekcie
C/C++
Spaceship::Spaceship()
{
    //...
   
    InputManager::BindAction( InputManager::ActionName::LEFT, std::bind( & Spaceship::MoveLeft, this, std::placeholders::_1 ) );
    InputManager::BindAction( InputManager::ActionName::RIGHT, std::bind( & Spaceship::MoveRight, this, std::placeholders::_1 ) );
    InputManager::BindAction( InputManager::ActionName::SHOOT, std::bind( & Spaceship::ShootProjectile, this, std::placeholders::_1 ) );
}

/////////////////////////

class InputManager
{
public:
    enum class ActionName { LEFT, RIGHT, SHOOT };
   
    /** Binds function to action name (function will be called when key assigned to actionname will be pressed) */
    static void BindAction( ActionName actionName, const std::function < void( float ) >& function );
   
private:
   
    static std::multimap < ActionName, std::function < void( float ) >> mActionNamesFunctions; // holds all functions assigned to ActionName (allows many functions to one action name)
};

Polecam ci ściągnąć (darmowego) unreal'a i pobawić się nim, na pewno sporo się nauczysz takich podstawowych rzeczy, ogólnej architektury silnika i powiązań między kluczowymi klasami.

Możesz tez podać swojego githuba to jak będę miał wolną chwilę to zerknę i może coś dopiszę ;)
P-169404
latajacaryba
Temat założony przez niniejszego użytkownika
» 2018-02-16 00:33:55
Ok, ja się więc doedukuje, bo póki dotychczas nie wiedziałem nawet, że istnieje coś takiego jak bindowanie albo function object:PP
Generalnie z tego co widze muszę wejść w magiczny świat biblioteki standardowej. Tymczasem nie do końca wiem, co zrobić z tematem. Chyba najlepszym rozwiązaniem będzie okresowe zamknięcie go, żeby nie wisiał na stronie z tematami. Otworzę go kiedy będę wiedział więcej.

Zostawie go jeszcze na dzień czy dwa, bo może ktoś będzie coś chciał dodać (ewentualnie administrator każe go po prostu nieodwołalnie zamknąć, choć wolałbym nie, bo rozwinął się tu ciekawy wątek :d).


edit:
To ja jeszcze tylko odnośnie tych kanałów kolizji. Jak wspomniał @Saran, mamy kilka cech, np:
żyjące, pradawne, latające, zniszczalne.

Czy jest to tylko do sprawdzania czy coś ma z czymś kolizje? Jeżeli wspomniany Wojtek stoi na ziemi, to nie ma mieć kolizji z latające. Oczywiście, to ma sens, ale jak rozumiem, to ich jedyna funkcja?
W jaki sposób to implementować? enum'y które przechowują te cechy:
C/C++
enum rodzaj { latajace, zyjace, naziemne, pradawne,...};

I np. Pradawna latajaca ryba przechowuje te 3 cechy (pradawny, latający, żyjący).

Wojtek natomiast ma naziemny, żyjący.
Prócz tego ma zestaw "rodzajów obiektów" z którym (jak pisał Saran) nie może kolidować:
C/C++
std::vector < rodzaj > niemozliwaKolizja;
Jeśli PradawnaLatajacaRyba ma jakąś ceche, która jest w tym wektorze, to kolizji nie zajdzie.

Jak myślicie, dobry to plan?
P-169441
michal11
» 2018-02-18 22:12:08
Jeżeli nie będziesz miał masy kolidujących ze sobą obiektów albo wielu zróżnicowanych relacji kolizji to ja bym się tymi kanałami nie przejmował.

Ale mój pomysł polegał na tym, że każdy collision object ma swój typ kolizji oraz informacje jak ma reagować na inne typy kolizji, czyli np. coś takiego

C/C++
void Bullet::Init()
{
    CollisionObject.SetCollisionType( ECollisionType::Projectile );
    CollisionObject.SetResponseToChannel( ECollisionType::Player, true );
    CollisionObject.SetResponseToChannel( ECollisionType::InvisibleWall, false );
}

i wtedy w systemie kolizji musisz to odpowiednio obsłużyć, czyli pewnie jakoś sprawdzać kolizje z tymi z którymi mogą kolidować.

Jakiś pseudokod:
C/C++
for( CollisionClass & CollisionObject1: CollisionObjects )
{
    for( CollisionClass & CollisionObject2: CollisionObjects )
   
    if( CollisionObject1.isCollisionPossible( CollisionObject2.GetCollisionType() ) )
    {
        // rest of collision checking
    }
}
}
P-169506
latajacaryba
Temat założony przez niniejszego użytkownika
» 2018-02-24 02:06:23
Witam ponownie :D

Wiem już na czym polega mechanizm bindowania itd.
natomiast mam pytanie odnośnie tego kodu:

C/C++
void SimpleBullet::Init()
{
    CollisionObject.RegisterForCollision( this, & SimpleBullet::OnCollision );
}

Więc tu przesyłamy metodę klasy SimpleBullet i wskaźnik na obiekt tej klasy, to jest jasne. Chcesz sobie tę metodę wraz z obiektem na rzecz którego ona ma być
wywoływana zbindować.
Ale jak wygląda definicja RegisterForCollision?
Próbowałem takową utworzyć na podstawie CollisionObject.RegisterForCollision( this, & SimpleBullet::OnCollision );
Oznacza to, że wyglądałaby tak:
CollisionObject::RegisterForCollision( GameObject * ptr, std::function < void( GameObject * HitObject, const Vector & HitLocation ) > )
jednak jakiegoś powodu u mnie nie działa poniższy program:

C/C++
class T
{
public:
    void Tfun( int a ) { std::cout << "a = " << a << "\n"; }
};

class CO // CollisionObject
{
public:
    std::function < void( int ) > f;
    CO( T * ptr, std::function < void( int ) > fun ) { f = std::bind( fun, ptr, std::placeholders::_1 ); } // przekazuje metodę i obiekt do zbindowania
};
int main()
{
   
    T obj;
    CO c( & obj, & T::Tfun ); // analogiczne do Twojego CollisionObject.RegisterForCollision( this, & SimpleBullet::OnCollision );, za this robi &obj


Natomiast ten - jak najbardziej:

C/C++
class CO // CollisionObject
{
public:
    std::function < void( int ) > f;
    CO( std::function < void( int ) > fun ) { f = fun; } // przesylamy juz "gotowa" funkcje
};
int main()
{
   
    T obj;
    CO a( std::bind( & T::Tfun, & obj, std::placeholders::_1 ) ); // bindujemy
}
A przecież nie różnią się niczym, prócz tego, że w pierwszym przypadku bindujemy w konstruktorze a w drugim przesyłamy do konstruktora wynik std::bind.
P-169591
michal11
» 2018-02-24 16:14:58
To co ja napisałem było tylko przykładem, nie musisz pisać dokładnie tak samo.

Musisz poczytać o różnicy pomiędzy wskaźniku na funkcje i wskaźniku na funkcje składową. Możesz tez poczytać, bardziej jako ciekawostka historyczna, o ptr_fun, mem_fun, mem_fun_ref i innych z functional z c++98 http://www.cplusplus.com​/reference/functional/.

Generalnie polecam jednak używać std::bind, mniej się narobisz.
P-169593
latajacaryba
Temat założony przez niniejszego użytkownika
» 2018-03-03 20:04:20
Skończyłem, gdyby kogokolwiek to obchodziło, tu są efekty: https://megawrzuta.pl/download​/34320db1f76aaa308c84036747e63116.html

Pewnie nie jest to idealne, ani nawet dobre, ale być może uda mi się dzięki temu coś zdziałać. Musze dorobić jeszcze sprawdzanie kierunku - z której strony zaszła kolizja.
Nie przewidziałem jednego - jest to nieznaczny problem, ale gwoli ścisłości wyjaśniam: Z racji, że sprawdzam kolizje każdemu obiektowi, to obiekt wykonuje czynności na obiekcie, z którym koliduje, ale jemu samemu nic sie nie dzieje. Lepiej w prosty sposób nie umiem tego wyjaśnić, więc mały "rysunek" poglądowy

pocisk ---> gracz // pocisk zadaje obrażenia
gracz ---> pocisk // gracz zatrzymuje ciałem pocisk

a nie

pocisk <---> gracz // pocisk zadaje obrażenia a gracz go zatrzymuje
gracz <---> pocisk // pocisk znowu zadaje obrażenia a gracz ponownie go zatrzymuje


Więc, załóżmy, że gracz wchodzi w teleport:

teleport ---> gracz

i teleport go teleportuje, gdzieś daleko.
W tym momencie gracz nie wykonuje swoich czynności na teleporcie. Dlaczego? Bo kolizja gracz-portal

już nie zachodzi. Po prostu obiekt kolizyjny gracza został gdzieś przeniesiony. Więc gracz nie może wykonać swoich czynności na teleporcie:

gracz ---> teleport
Oczywiście można to pewnie obejść, każdemu obiektowi przydzielić listę zadań i wykonywać je po sprawdzeniu kolizji, ale to pewnie zrodzi komplikacje albo nie będzie działać. W każdym razie to raczej nie jest duży problem.
P-169719
michal11
» 2018-03-04 19:25:10
Ciekawe, nie spodziewałem się, że zrobisz to na template'ach. Jak możesz to pokaż jeszcze jakiś use case tej klasy.

Drobne uwagi, zmiast sf::Rect<float> możesz użysz sf::FloatRect, niepotrzebnie includujesz algorithm w headerze, wystarczy w cpp.

Rect ma funkcję intersect nie musisz jej sam inplementować.

Kazde twoje sprawdzenie kolicji ma złożoniość kwadratową (zakładając, że canCollide jest wywoływane co klatkę), nie jest to coś dobrego, moim zdaniem na etapie optymalizowania powinineneś się zastanowić nad jakimś grupowaniem najpierw tych obiektów kolizji tak aby sprawdzać tylko te obiekty które na pewno moga ze sobą kolidować (tzn. mają zgodne typy kolizji).
P-169758
latajacaryba
Temat założony przez niniejszego użytkownika
» 2018-03-05 01:01:04
na templejtach, by można było podpiąć pod to różne enum classy czy GameObjecty w zależności od typu gry - bardziej elastyczne :)

Jak możesz to pokaż jeszcze jakiś use case tej klasy.
Nie mam. Generalnie póki co troche boje sie zaczynać jakąś nową grę, bo namęczę sie a i tak projekt upadnie :<
Jeśli będę coś w najbliższym czasie robił z wykorzystaniem w.w. klasy, to albo Tower Defence albo mini-silnik do zrobienia mapy wraz z systemem kolizji a'la Stardew Valley. Na tą chwilę muszę to jednak dobrze przemyśleć, bo szkoda by było znowu się namęczyć - jak by nie było - na darmo.

Kazde twoje sprawdzenie kolizji ma złożoniość kwadratową (zakładając, że canCollide jest wywoływane co klatkę), nie jest to coś dobrego, moim zdaniem na etapie optymalizowania powinineneś się zastanowić nad jakimś grupowaniem najpierw tych obiektów kolizji tak aby sprawdzać tylko te obiekty które na pewno moga ze sobą kolidować (tzn. mają zgodne typy kolizji).

Przyznaje, jest to niewydajne w przypadku wielu "cech" obiektu, ale chyba lepiej sie tego zrobić nie dało :/ plus jest taki, że przy znalezieniu pierwszej niepasującej cechy zwraca false. Chyba na poziomie CollisionObejct nie moge tego już bardziej zoptymalizować. Na szczęście w tych dwóch projektach o których wspomniałem mapa składa sie z kafelków, więc można będzie jakoś podziałać z optymalizacją.
P-169768
1 2 « 3 » 4
Poprzednia strona Strona 3 z 4 Następna strona