Autor Wątek: Odliczanie czasu w grze  (Przeczytany 5490 razy)

Offline jdev

  • Użytkownik

# Październik 01, 2014, 16:01:55
Witam, na wstępie dodam, że w programowaniu mam całkiem spore doświadczenie, ale w programowaniu gier nie mam jeszcze żadnego (poznaję właśnie podstawy Unity3D).

Póki co chcę napisać jakąś łatwą jedną może dwie gry w Unity3D, aby lepiej zrozumieć środowisko, ale równocześnie chcę projektować już docelową grę, której dotyczy poniższy problem (ale jeszcze nie kodować, jedynie projektować).

Konkretnie chodzi mi o timery, a innymi słowy odliczanie czasu pozostałego do wykonania jakiejś akcji, np. klikam w grze na produkcję jakiegoś wojska i produkuje się ono przykładowo w czasie 1szt/20sek, inny przykład, ale taka sama zasada... klikam na budowę koszar dla wojska, które budują się 4dni, itp. W jaki sposób powinno być to zrealizowane w grze? Muszę wszystko wrzucać w osobne wątki? Używać obiektów Timer (używam c#) dla każdego nowego odliczania dla kolejnych obiektów? Co jeśli serwer padnie i zresetują mi się wszystkie timery? Poza tym taka ilość timerów (zakładając, że każdy gracz używa ich kilkanaście jednocześnie, a graczy może być przykładowo setki tysięcy kiedyś tam) zatka serwer, do czego też nie można dopuścić.

Rozwiązanie jakie wymyśliłem to zapisywać w bazie czas rozpoczęcia zdarzenia, np. start budowy koszar i po każdym odświeżeniu ekranu z budową (akcja usera) pobierać czas pozostały do końca, ustawiać timer z tym czasem na kliencie (żeby pokazywało fizycznie jak mija czas do końca), tylko jak sprawdzić bez użycia timerów czy czas się skończył i zrobić odpowiedni wpis do bazy? Muszę to zrobić w momencie, kiedy skończy się czas, a nie wtedy kiedy akurat user odświeży ekran budowy. To takie moje przemyślenia, ale pewnie jest jeszcze jakieś inne łatwe rozwiązanie, o którym nie wiem.

Początkowo grę chciałem napisać w asp.net, miała być typowo pod przeglądarkę, aplikacja+baza miała stać razem na serwerze. Ostatnio jednak trochę nad tym myślałem i wolałbym zrobić grę na Androida (później iOSa), gdzie aplikacja+baza będzie razem na serwerze, a na kliencie będzie jedynie samo GUI, gdzie user będzie wysyłał na serwer inputy i będzie wyświetlać się tekst oraz odpowiednia grafika jako odpowiedź z serwera (na urządzeniu mobilnym nie będzie żadnych obliczeń i algorytmów gry, bo łatwo byłoby oszukiwać). Tak jak wcześniej wspomniałem, całość chciałbym stworzyć w Unity3D.

Podsumowując, jak wygląda rozwiązanie tego problemu w grach pisanych na przeglądarkę (nie na urządzenia mobilne), a jak wygląda na telefony i tablety (w Unity3D)?

Offline Mr. Spam

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

Offline koirat

  • Użytkownik

  • +1
# Październik 01, 2014, 16:18:57
Zostaw bazy, zostaw serwery, zostaw grę multiplayer. Zrób prostą grę singleplayer w Unity3d. Jak ją skończysz to pewnie nie będziesz zadawał takich pytań.

Offline jdev

  • Użytkownik

# Październik 01, 2014, 17:27:16
Póki co chcę napisać jakąś łatwą jedną może dwie gry w Unity3D, aby lepiej zrozumieć środowisko, ale równocześnie chcę projektować już docelową grę, której dotyczy poniższy problem (ale jeszcze nie kodować, jedynie projektować).
Zostaw bazy, zostaw serwery, zostaw grę multiplayer. Zrób prostą grę singleplayer w Unity3d.
To samo napisałem powyżej, najpierw chcę zrobić jedna, dwie gry w Unity3D, a potem zobaczymy, ale w międzyczasie chcę powoli projektować sobie swoją docelową grę. Poza tym w single playerze nie znajdę odpowiedzi na mój problem.

Jak ją skończysz to pewnie nie będziesz zadawał takich pytań.
Takich pytań, czyli jakich? Skoro uważasz, że moje pytanie jest bez sensu to zwyczajnie napisz dlaczego, bo teraz bez obrazy, ale z Twojej odpowiedzi niewiele się dowiedziałem.

Offline Xirdus

  • Redaktor

  • +2
# Październik 01, 2014, 17:59:59
Takich pytań, czyli jakich? Skoro uważasz, że moje pytanie jest bez sensu to zwyczajnie napisz dlaczego, bo teraz bez obrazy, ale z Twojej odpowiedzi niewiele się dowiedziałem.
http://en.wikipedia.org/wiki/Dunning–Kruger_effect - to, że chcesz używać rzeczywistych zegarów to mierzenia upływu czasu w tego typu grze świadczy o tym, że brakuje ci doświadczenia żeby w ogóle zauważyć, że nie poradzisz sobie z tym projektem bo nawet do takich podstawowych rzeczy wybierasz rozwiązanie absolutnie nieadekwatne.

Offline jdev

  • Użytkownik

# Październik 02, 2014, 00:33:04
To, że postawiłem sobie wysoko poprzeczkę to nie znaczy, że to niemożliwe, po prostu zajmie masę czasu, jednak liczyłem też na większą pomoc ze strony tego forum, nie chcę rozwiązań podanych na tacy, ale naprowadzenia na ADEKWATNE rozwiązania. Rozumiem jednak, że nie wszyscy chętnie dzielą się wiedzą, w każdym razie dzięki za kubeł zimnej wody, bo na pewno skupię się teraz bardziej niż planowałem na czymś mniejszym, pewnie niewielki singleplayer na początek, a potem coś trudniejszego.

Skoro już założyłem ten temat to nie chciałbym zostawiać go bez rozwiązania, zastanowiłem się raz jeszcze i przypomniałem sobie, że kiedyś (chyba na studiach) obiło mi się o uszy coś takiego jak cykl zegara procesora, co wykorzystuje się właśnie w grach, ale nie pamiętam czy na pewno dotyczyło to mojego problemu z odliczaniem czasu?

Offline Xirdus

  • Redaktor

# Październik 02, 2014, 01:19:43
W grze typu Plemiona, bo taką tu chyba opisałeś, akcje które zajmują określony czas, trzymałbym jako daty zakończenia tych akcji - tj. nie myślę o tym ile jeszcze czasu zostało, tylko o której godzinie będzie gotowe. I wtedy po prostu sortujesz po tej dacie zdarzenia i jeśli wybije godzina pierwszego z góry, robisz na serwerze to co trzeba. Żadnych timerów, żadnych wyścigów, żadnych problemów jak serwer się zwiesi. A żeby zrobić paski postępu itp. wystarczy informacja kiedy akcja się skończy (czyli to co wyżej napisałem) i całkowity czas trwania akcji (czyli to co tak czy inaczej masz już w statycznych danych) - resztę można sobie łatwo policzyć z tych dwóch.

Aha, i to jest jeden z najprostszych problemów przy tego typu grze.

Offline jdev

  • Użytkownik

# Październik 02, 2014, 02:27:13
W grze typu Plemiona, bo taką tu chyba opisałeś
To nie do końca ma być taka gra, ale ma w sobie zawierać elementy gry podobnej do plemion.

akcje które zajmują określony czas, trzymałbym jako daty zakończenia tych akcji - tj. nie myślę o tym ile jeszcze czasu zostało, tylko o której godzinie będzie gotowe. I wtedy po prostu sortujesz po tej dacie zdarzenia
Hmm.. myślałem, że mówiąc wcześniej o używaniu rzeczywistych zegarów masz na myśli, że muszę całkowicie zmienić podejście, a nie sam algorytm.

to, że chcesz używać rzeczywistych zegarów to mierzenia upływu czasu w tego typu grze świadczy o tym, że brakuje ci doświadczenia żeby w ogóle zauważyć, że nie poradzisz sobie z tym projektem bo nawet do takich podstawowych rzeczy wybierasz rozwiązanie absolutnie nieadekwatne.
Czyli wcześniej się nie zrozumieliśmy do końca czy chodzi tylko o te timery, które faktycznie bez sensu wymyśliłem w tym rozwiązaniu?

jeśli wybije godzina pierwszego z góry, robisz na serwerze to co trzeba.
No i właśnie w tym rzecz, jak sprawdzać czy wybiła ta godzina? Musiałbym co sekundę robić zapytanie do bazy, przy założeniu, że najmniejszą jednostką czasu byłaby sekunda.

Offline castro12321

  • Użytkownik

  • +1
# Październik 02, 2014, 07:38:54
Jeżeli serwer zawsze będzie online, to zapisanie jedynie końca trwania danej akcji jest fajnym rozwiązaniem.

W przeciwnym razie możesz napisać sobie własną abstrakcję czasu gry, z własnym timerem. Ja tak właśnie u siebie zrobiłem i jestem zadowolony z efektów ;)

- W momencie rozpoczęcia gry startujesz czasem gry 0.
- Co sekundę (większej rozdzielczości nie trzeba) zwiększasz czas gry o 1.
- Wszystko co się dzieje na serwerze jest odzwierciedlone jako czas gry. Np. koszary zbudują się w 4251 jednostce czasu gry. Jesteś tutaj niezależny już od czasu systemowego.
- Do backupów zapisujesz również ten czas gry
- W momencie crashu, przywracasz grę z czasem, który był w tamtym momencie. Dzięki temu wyłączenie serwera na jeden dzień spowoduje jedynie zastopowanie gry. (czyli dobrze - nie ma serwera, gra się nie toczy). Jak serwer się pojawia, gra sprawnie wznawia działanie od momentu crasha.

Rozwiązanie z własnym czasem pozwala bardzo prosto wykonać operacje typu przyspieszenie dziesięciokrotnie czasu gry. Zatrzymanie gry.
Dodatkowo ułatwia bardzo tworzenie testów z użyciem czasu (szkolenie, budowanie).
« Ostatnia zmiana: Październik 02, 2014, 08:24:13 wysłana przez castro12321 »

Offline Xirdus

  • Redaktor

# Październik 02, 2014, 10:11:02
No i właśnie w tym rzecz, jak sprawdzać czy wybiła ta godzina? Musiałbym co sekundę robić zapytanie do bazy, przy założeniu, że najmniejszą jednostką czasu byłaby sekunda.
Niekoniecznie. Ja bym po prostu pobierał aktualny czas systemowy funkcją time() lub analogiczną zależnie od języka, i sprawdzał czy pierwszy rekord w bazie ma czas mniejszy czy większy - jeśli mniejszy, wykonuję tą akcję i natychmiast pobieram następną, a jeśli większy, to usypiam wątek do czasu tego zdarzenia lub do czasu następnej zmiany w bazie danych.

Jeżeli serwer zawsze będzie online, to zapisanie jedynie końca trwania danej akcji jest fajnym rozwiązaniem.
Nie musi wcale być zawsze online - jak serwer padnie, to czas systemowy płynie dalej; nie ma żadnego przestoju. Poza tym że gracze nie mogą w tym czasie wydawać nowych poleceń, rzecz jasna. Przy żałośnie niskim uptime 80-90% gra dalej byłaby grywalna.

Rozwiązanie z własnym czasem pozwala bardzo prosto wykonać operacje typu przyspieszenie dziesięciokrotnie czasu gry. Zatrzymanie gry.
Dodatkowo ułatwia bardzo tworzenie testów z użyciem czasu (szkolenie, budowanie).
W grze MMO nie widzę zastosowania dla przyspieszania czy zatrzymywania gry. A testy zrobiłbym modyfikując ile trwa każda akcja.

Offline castro12321

  • Użytkownik

# Październik 02, 2014, 12:19:38
Nie musi wcale być zawsze online - jak serwer padnie, to czas systemowy płynie dalej; nie ma żadnego przestoju. Poza tym że gracze nie mogą w tym czasie wydawać nowych poleceń, rzecz jasna. Przy żałośnie niskim uptime 80-90% gra dalej byłaby grywalna.
Jeżeli serwer leży, to gra powinna być przez ten czas zatrzymana.
Sytuacja: Ktoś wysyła atak na innego gracza, zaraz po tym pada serwer. W tym czasie jednostki niszczą wioskę. Gracz broniący nie miał szans na przygotowanie obrony, bo serwer był offline.
IMO uczciwszym dla gracza rozwiązaniem byłoby właśnie zatrzymanie wszystkich akcji wraz z serwerem

W grze MMO nie widzę zastosowania dla przyspieszania czy zatrzymywania gry. A testy zrobiłbym modyfikując ile trwa każda akcja.
Gra MMO:
Dając za przykład Plemiona: Plemiona posiadają kilka serwerów i mogą mieć one zmodyfikowaną prędkość rozgrywki tak, że cały serwer toczy się kilka razy szybciej.
W wypadku rozwiązania z customowym czasem gry, wystarczy zmiana tylko jednej wartości.
W przypadku timerów, musiałbyś zmienić czas budowy, czas rekrutacji, szybkość poruszania się jednostek (handel,  ataki) i szybkość wydobycia surowców

Testy:
Jeżeli tych akcji jest 300 (ilość jednostek + ilość budynków) * ilość frakcji może tyle wynieść przy dużej grze
to musiałbyś zmienić 300 wartości.
W tym wypadku zmieniasz po prostu timestep z 1 na dowolną inną wartość.
Dla testów budowy budynku trwającego 4 dni, wystarczy, że po prostu zwiększysz czas gry o 4 dni i budynek jest zbudowany. Tyle. Nie trzeba czekać aż skończą się timery.
« Ostatnia zmiana: Październik 02, 2014, 12:27:52 wysłana przez castro12321 »

Offline Xirdus

  • Redaktor

# Październik 02, 2014, 12:40:14
Jeżeli serwer leży, to gra powinna być przez ten czas zatrzymana.
Sytuacja: Ktoś wysyła atak na innego gracza, zaraz po tym pada serwer. W tym czasie jednostki niszczą wioskę. Gracz broniący nie miał szans na przygotowanie obrony, bo serwer był offline.
IMO uczciwszym dla gracza rozwiązaniem byłoby właśnie zatrzymanie wszystkich akcji wraz z serwerem
I tu się właśnie różnią nasze koncepcje - według mnie, skoro ruchy wojsk trwają powiedzmy piętnaście godzin, to jak gracz wysyła wojska o północy i idzie spać, to o trzeciej po południu wojska mają dojść, nawet pomimo tego, że między drugą a szóstą rano serwer był niedostępny. Jest to trochę nieuczciwe dla tych, co chcieli wysłać wojska o trzeciej w nocy, ale po pierwsze, takie sytuacje są bardzo rzadkie (jak twój serwer ma mniej niż 97% uptime to coś jest bardzo nie tak), a po drugie - jeśli nie ma stałego garnizonu do obrony przed nagłymi atakami to sam sobie jest winien ;)

Gra MMO:
Dając za przykład Plemiona: Plemiona posiadają kilka serwerów i mogą mieć one zmodyfikowaną prędkość rozgrywki tak, że cały serwer toczy się kilka razy szybciej.
W wypadku rozwiązania z customowym czasem gry, wystarczy zmiana tylko jednej wartości.
W przypadku timerów, musiałbyś zmienić czas budowy, czas rekrutacji, szybkość poruszania się jednostek (handel,  ataki) i szybkość wydobycia surowców
Albo wprowadzić do danych gry jakiś global multiplier który modyfikowałby wszystkie czasy naraz.

Testy:
Jeżeli tych akcji jest 300 (ilość jednostek + ilość budynków) * ilość frakcji może tyle wynieść przy dużej grze
to musiałbyś zmienić 300 wartości.
W tym wypadku zmieniasz po prostu timestep z 1 na dowolną inną wartość.
Dla testów budowy budynku trwającego 4 dni, wystarczy, że po prostu zwiększysz czas gry o 4 dni i budynek jest zbudowany. Tyle. Nie trzeba czekać aż skończą się timery.
W celach testowych można modyfikować czas serwera - albo systemowy, albo wprowadzić w aplikacji jakiś dynamicznie zmieniany offset dodawany do czasu systemowego, który dla normalnej gry będzie wynosił zero. Powinieneś też zacząć pisać UT-ki ;)

Offline castro12321

  • Użytkownik

# Październik 02, 2014, 13:04:59
I tu się właśnie różnią nasze koncepcje - według mnie, skoro ruchy wojsk trwają powiedzmy piętnaście godzin, to jak gracz wysyła wojska o północy i idzie spać, to o trzeciej po południu wojska mają dojść, nawet pomimo tego, że między drugą a szóstą rano serwer był niedostępny. Jest to trochę nieuczciwe dla tych, co chcieli wysłać wojska o trzeciej w nocy, ale po pierwsze, takie sytuacje są bardzo rzadkie (jak twój serwer ma mniej niż 97% uptime to coś jest bardzo nie tak), a po drugie - jeśli nie ma stałego garnizonu do obrony przed nagłymi atakami to sam sobie jest winien ;)
Tutaj rzeczywiście jest kwestia podejścia, więc chyba nie ma co się kłócić ;)

Albo wprowadzić do danych gry jakiś global multiplier który modyfikowałby wszystkie czasy naraz.
czyli z tego co rozumiem, sugerujesz wszędzie wpisywać kod w stylu
"int czasBudowyKoszar = 1536 * Config.modyfikatorCzasu;"

W celach testowych można modyfikować czas serwera - albo systemowy, albo wprowadzić w aplikacji jakiś dynamicznie zmieniany offset dodawany do czasu systemowego, który dla normalnej gry będzie wynosił zero. Powinieneś też zacząć pisać UT-ki ;)
Trochę niezbyt chyba wygodnym rozwiązaniem jest zmiana czasu systemowego dla przeprowadzenia testów automatycznych. Dodanie offsetu do czasu systemowego też wiązałoby się z dodatkowym kodem w każdym miejscu wywołania. Tak? Nie jestem pewny, czy rozumiem twój tok myślenia.

Offline Xirdus

  • Redaktor

# Październik 02, 2014, 13:34:30
czyli z tego co rozumiem, sugerujesz wszędzie wpisywać kod w stylu
"int czasBudowyKoszar = 1536 * Config.modyfikatorCzasu;"
Wystarczy w jednym miejscu, w funkcji dodajAkcjeDoKolejkiWBazieDanych().

Trochę niezbyt chyba wygodnym rozwiązaniem jest zmiana czasu systemowego dla przeprowadzenia testów automatycznych.
I po to wymyślono stuby i mocki.

Dodanie offsetu do czasu systemowego też wiązałoby się z dodatkowym kodem w każdym miejscu wywołania.
To trzeba te miejsca wywołania ograniczyć, np. robiąc funkcję int getTime() { return getRealTime() + getTimeOffset(); }. Jest tysiąc sposobów na deduplikację kodu, i do każdej sytuacji jakiś dopasujesz.

Offline castro12321

  • Użytkownik

# Październik 02, 2014, 14:16:34
Wystarczy w jednym miejscu, w funkcji dodajAkcjeDoKolejkiWBazieDanych().
W takim wypadku to rozwiązuje problem

I po to wymyślono stuby i mocki.
Wymyślono je do rozwiązania pewnego problemu. Jednak po co, gdy można tych problemów uniknąć?

To trzeba te miejsca wywołania ograniczyć, np. robiąc funkcję int getTime() { return getRealTime() + getTimeOffset(); }. Jest tysiąc sposobów na deduplikację kodu, i do każdej sytuacji jakiś dopasujesz.
W przypadku testów, każdy test może potrzebować innego offsetu, lub modyfikacji czasu w trakcie testu.
Deduplikować się da, jednak abstrakcja czasu ukrywa przed tobą wszystkie tego typu wyliczenia i offsety, o których inaczej musiałbyś pamiętać w pozostałej części kodu. (Single Responsibility Principle - Dodanie do bazy danych ma dodać wpis, a nie pamiętać o przeliczaniu czasu).

Offline Xirdus

  • Redaktor

# Październik 02, 2014, 14:49:15
Wymyślono je do rozwiązania pewnego problemu. Jednak po co, gdy można tych problemów uniknąć?
Testując kod reakcji na upływ czasu, nie da się uniknąć problemu wymuszania sztucznego upływu czasu.

W przypadku testów, każdy test może potrzebować innego offsetu, lub modyfikacji czasu w trakcie testu.
Tak.

Deduplikować się da, jednak abstrakcja czasu ukrywa przed tobą wszystkie tego typu wyliczenia i offsety, o których inaczej musiałbyś pamiętać w pozostałej części kodu.
I po to te wszystkie wyliczenia ukrywam za funkcją getTime(), bo też lubię abstrakcję.

Single Responsibility Principle - Dodanie do bazy danych ma dodać wpis, a nie pamiętać o przeliczaniu czasu
Oczywiście - i dlatego przeliczanie czasu i właściwe dodanie do bazy zrobiłbym w osobnych funkcjach, przy czym ta pierwsza wołałaby drugą. Bo użytkownika tej pierwszej funkcji nie obchodzi że trzeba jakieś czasy przeliczać. Zauważ, że dodanie do kolejki jest rzeczą wysokopoziomową, a wpisanie do bazy niskopoziomową.


Edit: zauważ, że mój sposób z kolejką łatwo połączyć z twoim sposobem z tickami - poprzez inną implementację funkcji getTime().
« Ostatnia zmiana: Październik 02, 2014, 14:52:54 wysłana przez Xirdus »