Bądźmy szczerzy:wszyscy uwielbiamy grać w gry, zwłaszcza na naszych komputerach. Dopóki Internet nie stał się powszechny, większość z nas grała w gry komputerowe samodzielnie, zwykle przeciwko oponentom AI. Było fajnie, ale gdy tylko zorientowałeś się, jak działa mechanika rozgrywki, gra straciła większość swojej magii.
Rozwój Internetu przeniósł gry do sieci. Teraz możemy grać przeciwko ludzkim przeciwnikom i testować swoje umiejętności przeciwko ich. Koniec z rozgrywką!
Potem pojawiły się masowe gry online dla wielu graczy (MMO), które zmieniły wszystko. Tysiące graczy znalazło się w tych samych światach gry, rywalizując o zasoby, negocjując, handlując i walcząc. Aby takie gry były możliwe, potrzebna była struktura bazy danych, która mogłaby przechowywać wszystkie istotne informacje.
W tym artykule zaprojektujemy model, który będzie zawierał najczęściej spotykane elementy w grach MMO. Omówimy, jak z niego korzystać, jego ograniczenia i możliwe ulepszenia.
Wprowadzenie do modeli danych w grach MMO
W dzisiejszych czasach istnieje wiele bardzo popularnych gier MMO i dotyczą one wszelkiego rodzaju scenariuszy. Skoncentruję się tutaj na grach strategicznych, takich jak Ogame , Travian , Sparta :Wojna imperiów i Imperia Online . W tych grach chodzi bardziej o planowanie, budowanie i opracowywanie strategii, a mniej o działanie bezpośrednie.
Gry MMO toczą się w różnych światach, różnią się wizualnie i wykorzystują mniej lub bardziej różne opcje rozgrywki. Mimo to niektóre pomysły są takie same. Gracze rywalizują o lokacje, walczą o nie i zawierają sojusze z (i przeciwko) innym graczom. Budują struktury, zbierają surowce i badają technologie. Budują jednostki (takie jak wojownicy, czołgi, handlarze itp.) i używają ich do handlu z sojusznikami lub do walki z przeciwnikami. Wszystko to musi być obsługiwane w naszej bazie danych.
Możemy myśleć o tych grach jako o grach planszowych online z wieloma indeksowanymi kwadratami. Z każdym kwadratem może być powiązanych wiele różnych akcji; niektóre akcje będą obejmować wiele kwadratów – np. kiedy przenosimy jednostki lub zasoby z jednej lokalizacji do drugiej.
Baza danych podzielona jest na pięć głównych obszarów:
Players / Users
Alliances
Locations and Structures
Research and Resources
Units
Pozostałe siedem niepogrupowanych tabel dotyczy jednostek i opisuje ich pozycje oraz ruchy w grze. Przyjrzymy się każdemu z tych obszarów znacznie bardziej szczegółowo, zaczynając od Graczy i sojusze .
Gracze i sojusze
Bez wątpienia gracze są najważniejszą częścią każdej gry.
player
tabela zawiera listę wszystkich zarejestrowanych graczy biorących udział w instancji gry. Będziemy przechowywać nazwy użytkowników, hasła i nazwy ekranowe graczy. Będą one przechowywane w user_name
, password
i nickname
odpowiednio atrybuty.
Nowi użytkownicy będą musieli podać adres e-mail podczas rejestracji. Zostanie wygenerowany i wysłany do nich kod potwierdzający, na który odpowiedzą. Zaktualizujemy confirmation_date
atrybut, gdy użytkownik weryfikuje swój adres e-mail. Tak więc ta tabela ma trzy unikalne klucze:user_name
, nickname
i email
.
Za każdym razem, gdy użytkownik się loguje, w login_history
stół. Wszystkie atrybuty w tej tabeli nie wymagają wyjaśnień. logout_time
jest specyficzny. Może być NULL, gdy bieżąca sesja użytkownika jest aktywna lub gdy użytkownicy wychodzą z gry (bez wylogowania) z powodu problemów technicznych. W login_data
atrybut, będziemy przechowywać dane logowania, takie jak lokalizacja geograficzna gracza, adres IP oraz używane urządzenie i przeglądarka.
Większość gier MMO pozwala nam współpracować z innymi graczami. Jedną ze standardowych form współpracy graczy jest sojusz. Gracze dzielą się swoimi „prywatnymi danymi” w grze (statusem online, planami, lokalizacją swoich miast i kolonii itp.) z innymi, aby czerpać korzyści z działań sojuszników i dla czystej zabawy.
alliance
tabela przechowuje podstawowe informacje o sojuszach w grze. Każdy z nich ma unikalny alliance_name
które będziemy przechowywać. Będziemy też mieli pole date_founded
, który przechowuje, kiedy sojusz został założony. Jeśli sojusz zostanie rozwiązany, przechowamy te informacje w date_disbanded
atrybut.
alliance_member
tabela dotyczy graczy z sojuszami. Gracze mogą dołączać do tego samego sojuszu i opuszczać go więcej niż raz. Z tego powodu player_id
– alliance_id
para nie jest unikalnym kluczem. Będziemy przechowywać informacje o tym, kiedy gracz dołącza do sojuszu i kiedy (jeśli) opuszcza sojusz w date_from
i date_to
pola. membership_type_id
atrybut jest odniesieniem do membership_type
słownik; przechowuje aktualny poziom praw graczy w sojuszu.
Prawa graczy w sojuszu mogą z czasem ulec zmianie. membership_actions
, membership_type
i actions_allowed
tabele razem definiują wszystkie możliwe prawa członków sojuszu. Ten model nie pozwala graczom definiować własnych poziomów praw w sojuszu, ale można to łatwo osiągnąć, dodając nowe rekordy w membership_type
słownik i przechowywanie informacji o tym, z jakimi sojuszami są powiązani.
Podsumowując:wartości przechowywane w tych tabelach są przez nas definiowane podczas wstępnej konfiguracji; zmienią się tylko wtedy, gdy wprowadzimy nowe opcje.
membership_history
tabela przechowuje wszystkie dane dotyczące ról graczy lub uprawnień w sojuszu, w tym zakres, w którym te prawa były ważne. (Na przykład może mieć uprawnienia „nowicjusza” przez miesiąc, a następnie „pełne członkostwo” od tego momentu.) date_to
atrybut jest NULL, ponieważ aktualnie aktywne prawa jeszcze się nie zakończyły.
membership_actions
Słownik zawiera listę wszystkich akcji, które gracze mogą wykonać w sojuszu. Każda akcja ma swoją własną action_name
i logika gry jest zbudowana wokół tych nazw. Możemy oczekiwać wartości takich jak „wyświetl listę członków” , „wyświetl statusy członków” i „wyślij wiadomość” tutaj.
membership_type
słownik zawiera unikalne nazwy grup akcji używanych w grze. actions_allowed
tabela przypisuje akcje do typów członkostwa. Każdą akcję można przypisać do typu tylko raz. Dlatego membership_action
- membership_type
para tworzy unikalny klucz dla tej tabeli.
Lokalizacje i struktury
Lokacje gry to obszary, w których gracze zbierają zasoby oraz budują struktury i jednostki. Niektóre gry mają predefiniowany zakres możliwych lokalizacji, podczas gdy inne mogą umożliwiać użytkownikom definiowanie własnych lokalizacji.
W przestrzeni 3D lokalizacje można zdefiniować za pomocą współrzędnych [x:y:z]. Jeśli gra ma wstępnie zdefiniowany zakres, może nie pozwalać graczom na użycie dowolnej lokalizacji spoza zakresu [0:1000] dla wszystkich trzech osi, więc jesteśmy ograniczeni do przestrzeni 1000 * 1000 * 1000.
Z drugiej strony, być może chcemy umożliwić graczom wpisanie dokładnych współrzędnych ich nowej lokalizacji – m.in. [1001:2073:4] – i chcemy, aby gra przetworzyła to za nich.
Będziemy przechowywać listę wszystkich lokalizacji używanych w instancji naszej gry w location
stół. Każda lokalizacja ma swoją własną nazwę, ale nazwy nie są unikatowe. Z drugiej strony coordinates
atrybut musi zawierać tylko unikalne wartości. Współrzędne lokalizacji są przechowywane jako wartości tekstowe, więc możemy przechowywać współrzędne dla gier 3D jako [112:72:235]. Współrzędne gier 2D mogą być przechowywane jako <1102:98>.
W niektórych grach lokalizacje będą miały pewną liczbę kwadratów, które służą do przechowywania struktur lub jednostek. Będziemy przechowywać te informacje w dimension
atrybut, który jest polem tekstowym. Wymiarem może być po prostu liczba kwadratów w siatce 2D lub 3D. player_id
atrybut przechowuje informacje o aktualnym właścicielu tej lokalizacji. Może być NULL, gdy lokalizacje są wstępnie zdefiniowane, a gracze rywalizują o ich zajęcie.
structure
tabela zawiera listę wszystkich konstrukcji, które możemy zbudować w różnych lokalizacjach gry. Struktury reprezentują ulepszenia, które pozwalają nam produkować lepsze jednostki, przeprowadzać nowe rodzaje badań, produkować więcej zasobów itp. Każda struktura używana w grze ma swoją własną, unikalną structure_name
. Niektóre możliwe structure_name
wartości to „farma”, „kopalnia rudy”, „elektrownia słoneczna” i „centrum badawcze”.
Możemy oczekiwać, że każda struktura będzie wielokrotnie ulepszana, więc będziemy również przechowywać informacje o jej aktualnym poziomie. Każde ulepszenie zwiększa wydajność struktur, dzięki czemu wytwarza więcej zasobów lub pozwala korzystać z nowych funkcji w grze. Nie możemy z góry określić maksymalnego poziomu ulepszeń, więc wszystkie rzeczy związane z poziomem (koszty, czas ulepszeń i produkcja) zdefiniujemy za pomocą formuł. Wszystkie formuły przechowywane w bazie danych stanowią rdzeń mechaniki gry, a ich dostosowanie ma kluczowe znaczenie dla równowagi gry i ogólnie rozgrywki.
Tak jest również w przypadku upgrade_time_formula
atrybut. Przykładowa wartość tego pola to „
W większości przypadków istnieją wymagania, które muszą zostać spełnione, zanim gracze podejmą określone działania. Być może musimy wykonać określoną ilość badań, zanim będziemy mogli budować nowe struktury lub odwrotnie. Przechowamy poziom badań potrzebny do zbudowania struktur w prerequisite_research
stół. Relacje i poziom struktury potrzebny do rozpoczęcia różnych badań są przechowywane w prerequisite_structure
stół. W obu tabelach klucze obce research_id
i structure_id
są sparowane, tworząc unikalny klucz. level_required
jedyną wartością jest atrybut.
Te dwie tabele, prerequisite_research
i prerequisite_structure
, również stanowią rdzeń gry.
Dla każdej struktury zdefiniujemy listę wymagań wstępnych:inne struktury i ich minimalne poziomy, które gracze muszą mieć, aby rozpocząć budowę. Będziemy przechowywać te dane w structure_required
stół. Tutaj structure_id
reprezentuje strukturę, którą chcemy zbudować; structure_required_id
jest odniesieniem do struktur wymagań wstępnych i level
to wymagany poziom.
structure_built
tabela przechowuje informacje o aktualnych poziomach konstrukcji w danej lokalizacji. upgrade_ongoing
atrybut zostanie ustawiony tylko wtedy, gdy aktualizacja jest w toku, podczas gdy upgrade_end_time
atrybut będzie zawierał znacznik czasu po zakończeniu aktualizacji.
structure_formula
tabela dotyczy struktur i zasobów. Para kluczy obcych do tej tabeli tworzy jej unikalny klucz. Ta tabela zawiera również dwa atrybuty tekstowe zawierające formuły z parametrem upgrade_time_formula
. Potrzebujemy ich, ponieważ musimy określić zasoby wydawane na budowę każdej struktury. Musimy również zdefiniować produkcję zasobów po aktualizacji, jeśli struktura generuje jakiekolwiek zasoby (np. kopalnia rudy wyprodukuje
Badania i zasoby
Badania (lub technologie) w grach są zwykle niezbędne do stworzenia innych funkcji. Bez pewnych poziomów badań nie można budować nowych struktur ani typów jednostek. Badania również mogą mieć swoje własne wymagania. Jednym z najczęstszych jest poziom danej struktury, zwykle nazywany „laboratorium badawczym”. A może gracze muszą ukończyć pewien poziom badań, zanim będą mogli rozpocząć nowe badania. Wszystkie te wymagania zostaną omówione w tej sekcji. Poniżej znajduje się model danych dla badań i zasobów:
research
tabela zawiera listę wszystkich możliwych akcji badawczych w naszej grze. Używa tej samej logiki, co structure
stół. research_name
atrybut jest unikalnym kluczem tabeli, natomiast upgrade_time_formula
pole zawiera tekstową reprezentację formuły wymagań czasowych badania, której parametrem jest upgrade_formula
przechowywane w research_formula
tabela.
Podobnie jak w przypadku struktur, zdefiniujemy listę wszystkich innych badań i ich poziomy, które należy ukończyć, zanim będziemy mogli rozpocząć kolejny rodzaj badań. Będziemy przechowywać te dane w research_required
tabela, gdzie research_id
reprezentuje pożądane badania; research_required_id
jest odniesieniem do badania wymagań wstępnych i level
to wymagany poziom.
Badania dotyczą poszczególnych graczy, a dla każdego gracza – badania W parze musimy przechowywać aktualny poziom badań gracza i wszelkie bieżące statusy ulepszeń. Będziemy przechowywać te informacje przy użyciu research_level
w ten sam sposób, w jaki użyliśmy structure_built
tabela.
Zasoby takie jak drewno, ruda, klejnoty i energia są wydobywane lub zbierane i wykorzystywane później do budowy struktur i innych ulepszeń. Przechowamy listę wszystkich zasobów w grze w resource
słownik. Jedynym atrybutem tutaj jest resource_name
pole i jest to również unikalny klucz tabeli.
Aby śledzić aktualną ilość zasobów w każdej lokalizacji, użyjemy resources_on_location
stół. Ponownie para kluczy obcych (resource_id
i location_id
) tworzy unikalny klucz tabeli, natomiast number
atrybut przechowuje aktualne wartości zasobów.
Jednostki i ruchy
Surowce są wykorzystywane do produkcji jednostek. Jednostki mogą być używane do transportu surowców, atakowania innych graczy lub ogólnie grabieży i palenia.
Lista typów jednostek używanych w naszej grze jest przechowywana w unit
słownik z tylko jedną wartością, unit_name
; ten atrybut jest unikalnym kluczem tej tabeli. Niektóre popularne jednostki w grze to „szermierz”, „krążownik”, „gryf”, „myśliwiec”, „czołg” itp.
Każdą jednostkę musimy opisać za pomocą określonych cech. Lista wszystkich możliwych cech jest przechowywana w characteristic
słownik. characteristic_name
pole zawiera unikalną wartość. Wartości w tym polu mogą obejmować:„atak”, „obronę” i „punkty wytrzymałości”. Przypiszemy cechy jednostkom za pomocą unit_characteristic
relacja. Para kluczy obcych unit_id
i characteristic_id
tworzą unikalny klucz tabeli. Użyjemy tylko jednego atrybutu, value
, aby zapisać żądaną wartość.
research_unit
tabela zawiera listę wszystkich czynności badawczych, które należy zakończyć zanim będziemy mogli rozpocząć produkcję danego typu jednostki. unit_cost
tabela określa zasoby potrzebne do wyprodukowania pojedynczej jednostki. Obie tabele mają unikalne klucze złożone z pary kluczy obcych (research_id
lub resources_id
w połączeniu z unit_id
) i jedno pole wartości (cost
i level_required
).
A teraz zabawna część. Produkcja jest fajna, ale przenoszenie jednostek i podejmowanie akcji jest jeszcze lepsze. Wprowadziliśmy już unit
tabeli, ale zachowamy ją tutaj ze względu na to, jak odnosi się do innych tabel.
Jednostki stacjonują w lokacji lub przemieszczają się pomiędzy lokacjami. Dodawanie player_id
pole określa, kto jest właścicielem lokalizacji lub grupy, która przemieszcza się między lokalizacjami.
Jeśli jednostki właśnie stacjonują w danej lokalizacji, zapiszemy tę lokalizację i liczbę jednostek tam stacjonujących. Aby to zrobić, użyjemy units_on_location
tabela.
Kiedy jednostki nie stacjonują, poruszają się. Będziemy musieli zapisać ich punkt wyjścia i miejsce docelowe. Ponadto musimy zdefiniować możliwe działania podczas ruchów. Wszystkie takie działania są przechowywane w movement_type
słownik. type_name
atrybut jest unikalny, podczas gdy allows_wait
atrybut określa, czy akcja pozwala na oczekiwanie w punkcie docelowym.
Możemy przenieść jeden typ jednostek, ale prawie w każdym przypadku przeniesiemy wiele jednostek kilku różnych typów. Ta grupa będzie udostępniać wspólne dane, a my będziemy je przechowywać w group_movement
stół. W tej tabeli zdefiniujemy następujące pozycje:
- gracz, który zainicjował to działanie
- rodzaj akcji
- punkt wyjścia
- punkt docelowy
arrival_time
w miejscu docelowymreturn_time
do punktu wyjściawait_time
w miejscu docelowym
return_time
atrybut może mieć wartość NULL, jeśli jest to podróż w jedną stronę, a wait_time
jest definiowany przez gracza. Jednostki należące do grupy są definiowane przez wartości przechowywane w units_in_group
stół. Para kluczy obcych units_id
i group_moving_id
tworzy unikalny klucz stołu. Liczba jednostek tego samego typu w grupie jest zdefiniowana w number
atrybut.
Każdy ruch może przenosić surowce z jednego miejsca do drugiego. Dlatego zdefiniujemy relację wiele-do-wielu między group_movement
i resources
tabele. Oprócz kluczy podstawowych i obcych, resources_in_group
tabela zawiera tylko number
atrybut. To pole przechowuje ilość zasobów przenoszonych przez graczy z punktu początkowego do miejsca docelowego.
W większości przypadków gracze mogą wezwać innych do przyłączenia się do ich przygody. Aby to wesprzeć, użyjemy dwóch tabel:allied_movement
i allied_groups
. Jeden gracz zainicjuje wspólne działanie, które utworzy nowy rekord w allied_movement
stół. Wszystkie grupy jednostek, które biorą udział w sojuszniczej akcji, są definiowane przez wartości przechowywane w allied_groups
stół. Każda grupa może być przypisana do sojuszniczej akcji tylko raz, więc klucze obce tworzą unikalny klucz tej tabeli.
Model ten daje nam podstawową strukturę potrzebną do zbudowania gry strategicznej MMO. Zawiera najważniejsze cechy gry:lokalizacje, struktury, zasoby, badania i jednostki. Łączy je również, pozwala nam zdefiniować wymagania wstępne w bazie danych, a także przechowuje większość logiki gry w bazie danych.
Po wypełnieniu tych tabel większość logiki gry jest zdefiniowana i nie spodziewalibyśmy się dodania nowych wartości. Prawie każda tabela ma unikalną wartość klucza, nazwę funkcji lub parę kluczy obcych. Zmiana charakterystyk jednostek i formuł produkcji/kosztów pozwoli nam zmienić balans gry w warstwie bazy danych.
Jak byś zmienił ten model? Co lubisz, a co byś zrobił inaczej? Powiedz nam w sekcji komentarzy!