Sejamos honestos:todos nós gostamos de jogar, especialmente em nossos computadores. Até a Internet se espalhar, a maioria de nós jogava sozinho, geralmente contra oponentes de IA. Foi divertido, mas assim que você percebeu como funcionava a mecânica de jogo, o jogo perdeu a maior parte de sua magia.
O desenvolvimento da Internet moveu os jogos online. Agora, podemos jogar contra oponentes humanos e testar nossas habilidades contra as deles. Não há mais jogabilidade mecânica!
Então, os jogos multiplayer online (MMO) surgiram e mudaram tudo. Milhares de jogadores se encontraram nos mesmos universos de jogo, competindo por recursos, negociando, negociando e lutando. Para tornar esses jogos possíveis, era necessária uma estrutura de banco de dados que pudesse armazenar todas as informações relevantes.
Neste artigo, projetaremos um modelo que incorpora os elementos mais comuns encontrados em jogos MMO. Discutiremos como usá-lo, suas limitações e suas possíveis melhorias.
Uma introdução aos modelos de dados para jogos MMO
Existem muitos jogos MMO muito populares hoje em dia, e eles envolvem todos os tipos de cenários. Vou focar aqui em jogos de estratégia como Ogame , Travian , Esparta :Guerra dos Impérios e Imperia Online . Esses jogos são mais sobre planejamento, construção e estratégia, e menos sobre ação direta.
Os jogos MMO são ambientados em universos diferentes, são visualmente diferentes e usam opções de jogabilidade mais ou menos diferentes. Ainda assim, algumas ideias são as mesmas. Os jogadores competem por locais, lutam por eles e formam alianças com (e contra) outros jogadores. Eles constroem estruturas, coletam recursos e pesquisam tecnologias. Eles constroem unidades (como guerreiros, tanques, comerciantes, etc.) e as usam para negociar com aliados ou para lutar com oponentes. Tudo isso precisa ser suportado em nosso banco de dados.
Podemos pensar nesses jogos como jogos de tabuleiro online com muitos quadrados indexados. Cada quadrado pode ter muitas ações diferentes associadas a ele; algumas ações incluirão vários quadrados – por exemplo, quando movemos unidades ou recursos de um local para outro.
A base de dados está dividida em cinco áreas principais:
Players / Users
Alliances
Locations and Structures
Research and Resources
Units
As sete tabelas desagrupadas restantes estão relacionadas às unidades e descrevem a posição das unidades e os movimentos no jogo. Analisaremos cada uma dessas áreas com muito mais detalhes, começando com Jogadores e Alianças .
Jogadores e Alianças
Sem dúvida, os jogadores são a parte mais importante de qualquer jogo.
O player
table contém uma lista de todos os jogadores registrados que participam de uma instância de jogo. Armazenaremos os nomes de usuário, senhas e nomes de tela dos jogadores. Eles serão armazenados no user_name
, password
e nickname
atributos respectivamente.
Novos usuários precisarão fornecer um endereço de e-mail durante o registro. Um código de confirmação será gerado e enviado a eles, ao qual eles responderão. Atualizaremos a confirmation_date
atributo quando o usuário verifica seu endereço de e-mail. Portanto, esta tabela tem três chaves exclusivas:user_name
, nickname
e email
.
Cada vez que um usuário faz login, um novo registro é adicionado ao login_history
tabela. Todos os atributos nesta tabela são autoexplicativos. O logout_time
é específico. Pode ser NULL quando a sessão atual do usuário está ativa ou quando o usuário sai do jogo (sem sair) devido a problemas técnicos. No login_data
atributo, armazenaremos detalhes de login como a localização geográfica de um jogador, endereço IP e o dispositivo e navegador que ele usa.
A maioria dos jogos MMO nos permite cooperar com outros jogadores. Uma das formas padrão de cooperação do jogador é a aliança. Os jogadores compartilham seus “dados privados” no jogo (status online, planos, localização de suas cidades e colônias, etc.) com outros para se beneficiarem de ações aliadas e por pura diversão.
A alliance
table armazena informações básicas sobre alianças de jogo. Cada um tem um alliance_name
exclusivo que vamos armazenar. Também teremos um campo, date_founded
, que armazena quando a aliança foi fundada. Se uma aliança for dissolvida, armazenaremos essas informações no date_disbanded
atributo.
O alliance_member
tabela relaciona jogadores com alianças. Os jogadores podem entrar e sair da mesma aliança mais de uma vez. Por isso, o player_id
– alliance_id
par não é uma chave única. Manteremos informações sobre quando um jogador se junta à aliança e quando (se) ele sai no date_from
e date_to
Campos. O membership_type_id
atributo é uma referência ao membership_type
dicionário; ele armazena o nível atual dos direitos dos jogadores na aliança.
Os direitos dos jogadores em uma aliança podem mudar ao longo do tempo. As membership_actions
, membership_type
e actions_allowed
as tabelas juntas definem todos os direitos possíveis para os membros da aliança. Esse modelo não permite que os jogadores definam seus próprios níveis de direitos em uma aliança, mas isso pode ser feito com bastante facilidade adicionando novos registros no membership_type
dicionário e armazenar informações sobre quais alianças estão relacionadas.
Resumindo:os valores armazenados nestas tabelas são definidos por nós durante a configuração inicial; eles mudarão apenas se introduzirmos novas opções.
O membership_history
table armazena todos os dados sobre funções ou direitos dos jogadores dentro de uma aliança, incluindo o intervalo em que esses direitos eram válidos. (Por exemplo, ele pode ter permissões de "novato" por um mês e, depois, "associação completa" a partir desse ponto.) O date_to
atributo é NULLable porque os direitos atualmente ativos ainda não terminaram.
As membership_actions
dicionário contém uma lista de todas as ações que os jogadores podem fazer em uma aliança. Cada ação tem seu próprio action_name
e a lógica do jogo é construída em torno desses nomes. Podemos esperar valores como “ver lista de membros” , “ver status dos membros” e "enviar mensagem" aqui.
O membership_type
dicionário contém os nomes exclusivos dos grupos de ação usados no jogo. As actions_allowed
tabela atribui ações a tipos de associação. Cada ação pode ser atribuída a um tipo apenas uma vez. Portanto, a membership_action
- membership_type
par forma a chave exclusiva para esta tabela.
Locais e Estruturas
Locais de jogo são áreas onde os jogadores coletam recursos e constroem estruturas e unidades. Alguns jogos têm um intervalo predefinido de locais possíveis, enquanto outros podem permitir que os usuários definam seus próprios locais.
Em um espaço 3D, as localizações podem ser definidas com coordenadas [x:y:z]. Se um jogo tiver um intervalo predefinido, ele pode não permitir que os jogadores usem qualquer local fora do intervalo [0:1000] para todos os três eixos, portanto, estamos limitados a um espaço de 1000 * 1000 * 1000.
Por outro lado, talvez queiramos permitir que os jogadores insiram as coordenadas exatas de sua nova localização – por exemplo, [1001:2073:4] – e queremos que o jogo processe isso para eles.
Manteremos uma lista de todos os locais usados em uma instância do nosso jogo no location
tabela. Cada local tem seu próprio nome, mas os nomes não são exclusivos. Por outro lado, as coordinates
atributo deve conter apenas valores exclusivos. As coordenadas de localização são armazenadas como valores de texto, para que possamos armazenar coordenadas para jogos 3D como [112:72:235]. As coordenadas para jogos 2D podem ser armazenadas como <1102:98>.
Em alguns jogos, os locais terão vários quadrados que são usados para abrigar estruturas ou unidades. Manteremos essas informações na dimension
atributo, que é um campo de texto. Uma dimensão pode ser simplesmente o número de quadrados em uma grade 2D ou 3D. O player_id
O atributo armazena informações sobre o proprietário atual desse local. Pode ser NULL quando os locais são predefinidos e os jogadores competem para ocupá-los.
A structure
table contém uma lista de todas as estruturas que podemos construir em vários locais do jogo. As estruturas representam melhorias que nos permitem produzir unidades melhores, realizar novos tipos de pesquisa, produzir mais recursos, etc. Cada estrutura usada no jogo tem seu próprio structure_name
exclusivo . Alguns possíveis structure_name
os valores são “fazenda”, “mina de minério”, “usina solar” e “centro de pesquisa”.
Podemos esperar que cada estrutura seja atualizada várias vezes, então também armazenaremos informações sobre seu nível atual. Cada atualização melhora a produção das estruturas, então produz mais recursos ou nos permite usar novos recursos no jogo. Não podemos saber o nível máximo de atualização com antecedência, então definiremos todas as coisas relacionadas ao nível (custos, tempo de atualização e produção) com fórmulas. Todas as fórmulas armazenadas no banco de dados são o núcleo da mecânica do jogo, e seu ajuste é crucial para o equilíbrio do jogo e a jogabilidade em geral.
Esse também é o caso da upgrade_time_formula
atributo. Um exemplo de valor para este campo é “
Na maioria dos casos, existem requisitos que devem ser atendidos antes que os jogadores tomem certas ações. Talvez precisemos concluir uma quantidade definida de pesquisas antes de podermos construir novas estruturas ou vice-versa. Armazenaremos o nível de pesquisa necessário para construir estruturas na prerequisite_research
tabela. Os relacionamentos e o nível de estrutura necessário para iniciar várias pesquisas são mantidos no prerequisite_structure
tabela. Em ambas as tabelas, as chaves estrangeiras research_id
e structure_id
são emparelhados para formar uma chave única. O level_required
atributo é o único valor.
Estas duas tabelas, prerequisite_research
e prerequisite_structure
, também formam o núcleo do jogo.
Para cada estrutura, definiremos uma lista de pré-requisitos:outras estruturas e seus níveis mínimos que os jogadores devem ter para começar a construir. Armazenaremos esses dados no structure_required
tabela. Aqui, structure_id
representa a estrutura que queremos construir; structure_required_id
é uma referência à(s) estrutura(s) de pré-requisitos e level
é o nível necessário.
A structure_built
A tabela armazena informações sobre os níveis de estrutura atuais em um determinado local. O upgrade_ongoing
O atributo será definido apenas se uma atualização estiver em andamento, enquanto o upgrade_end_time
O atributo conterá um carimbo de data/hora assim que a atualização for concluída.
A structure_formula
tabela relaciona estruturas e recursos. O par de chaves estrangeiras para esta tabela forma sua chave exclusiva. Esta tabela também possui dois atributos de texto contendo fórmulas com upgrade_time_formula
. Precisamos deles porque devemos definir os recursos gastos na construção de cada estrutura. Também precisamos definir a produção de recursos após a atualização, se a estrutura gerar quaisquer recursos (ou seja, a mina de minério produzirá
Pesquisa e Recursos
Pesquisas (ou tecnologias) em jogos costumam ser requisito para a criação de outras funcionalidades. Sem certos níveis de pesquisa, novas estruturas ou tipos de unidades não podem ser construídos. A pesquisa também pode ter seus próprios requisitos. Um dos mais comuns é o nível de uma determinada estrutura, geralmente chamada de “laboratório de pesquisa”. Ou talvez os jogadores precisem completar um certo nível de pesquisa antes de iniciar uma nova pesquisa. Todos esses requisitos serão tratados nesta seção. Abaixo, podemos encontrar o modelo de dados para Pesquisa e Recursos:
A research
tabela contém uma lista de todas as ações de pesquisa possíveis em nosso jogo. Ele usa a mesma lógica da structure
tabela. O research_name
atributo é a chave exclusiva da tabela, enquanto o upgrade_time_formula
O campo contém uma representação de texto da fórmula de requisitos de tempo de pesquisa, com upgrade_formula
armazenado na research_formula
tabela.
Assim como nas estruturas, definiremos a lista de todas as outras pesquisas e seus níveis que devem ser concluídos antes de iniciarmos outro tipo de pesquisa. Armazenaremos esses dados no research_required
tabela, onde research_id
representa a pesquisa desejada; research_required_id
é uma referência à pesquisa de pré-requisitos e level
é o nível necessário.
A pesquisa está relacionada a jogadores individuais e para cada jogador – pesquisa ch pair, devemos armazenar o nível de pesquisa atual de um jogador e qualquer status de atualização em andamento. Armazenaremos essas informações usando o research_level
tabela da mesma maneira que usamos o structure_built
tabela.
Recursos como madeira, minério, gemas e energia são extraídos ou coletados e usados posteriormente para construir estruturas e outras melhorias. Armazenaremos uma lista de todos os recursos do jogo no resource
dicionário. O único atributo aqui é o resource_name
campo, e também é a chave exclusiva da tabela.
Para acompanhar a quantidade atual de recursos em cada local, usaremos o resources_on_location
tabela. Novamente, um par de chaves estrangeiras (resource_id
e location_id
) forma a chave única da tabela, enquanto o number
O atributo armazena os valores atuais do recurso.
Unidades e Movimentos
Os recursos são usados para produzir unidades. As unidades podem ser usadas para transportar recursos, atacar outros jogadores ou, em geral, pilhar e queimar.
A lista de tipos de unidades usadas em nosso jogo é armazenada na unit
dicionário com apenas um valor, unit_name
; esse atributo é a chave exclusiva desta tabela. Algumas unidades de jogo comuns são “swordsman”, “battlecruiser”, “griffin”, “jet fighter”, “tank”, etc.
Precisamos descrever cada unidade com características específicas. Uma lista de todas as características possíveis é armazenada na characteristic
dicionário. O characteristic_name
campo contém um valor exclusivo. Os valores neste campo podem incluir:“ataque”, “defesa” e “pontos de vida”. Atribuiremos características às unidades usando o unit_characteristic
relação. O par de chaves estrangeiras de unit_id
e characteristic_id
formam a chave única da tabela. Usaremos apenas um atributo, value
, para armazenar o valor desejado.
A research_unit
A tabela contém uma lista de todas as atividades de pesquisa que devem ser concluídas antes que possamos iniciar a produção de um determinado tipo de unidade. O unit_cost
tabela define os recursos necessários para produzir uma única unidade. Ambas as tabelas têm chaves únicas compostas pelo par de chaves estrangeiras (research_id
ou resources_id
combinado com unit_id
) e um campo de valor (cost
e level_required
).
E agora, a parte divertida. A produção é divertida, mas mover as unidades e agir é ainda melhor. Já apresentamos a unit
table, mas vamos mantê-lo aqui por causa de como ele se relaciona com outras tabelas.
Ou as unidades estão estacionadas em um local ou estão se movendo entre os locais. Adicionando o player_id
O campo determina quem é o proprietário do local ou do grupo que está se movendo entre os locais.
Se as unidades estiverem apenas estacionadas no local determinado, armazenaremos esse local e o número de unidades estacionadas lá. Para fazer isso, usaremos o units_on_location
tabela.
Quando as unidades não estão estacionadas, elas estão se movendo. Precisaremos armazenar o ponto de partida e o destino. Além disso, precisamos definir possíveis ações durante os movimentos. Todas essas ações são armazenadas no movement_type
dicionário. O type_name
atributo é único enquanto o allows_wait
O atributo determina se uma ação permite esperar no ponto de destino.
Podemos mover um único tipo de unidade, mas em quase todos os casos moveremos muitas unidades de vários tipos de unidades diferentes. Esse grupo compartilhará dados comuns e nós os armazenaremos no group_movement
tabela. Nesta tabela, definiremos os seguintes itens:
arrival_time
no destinoreturn_time
para o ponto de partidawait_time
no destino
O return_time
atributo pode ser NULL se for uma viagem de ida e wait_time
é definido pelo jogador. As unidades pertencentes a um grupo são definidas por valores armazenados no units_in_group
tabela. O par de chaves estrangeiras de units_id
e group_moving_id
forma a chave única da tabela. O número das unidades do mesmo tipo dentro de um grupo é definido no number
atributo.
Cada movimento pode transportar recursos de um local para outro. Portanto, definiremos uma relação muitos-para-muitos entre o group_movement
e os resources
mesas. Além das chaves primárias e estrangeiras, o resources_in_group
tabela contém apenas o number
atributo. Este campo armazena a quantidade de recursos que os jogadores movem do ponto de partida até o destino.
Na maioria dos casos, os jogadores podem chamar outros para participar de sua aventura. Para suportar isso, usaremos duas tabelas:allied_movement
e allied_groups
. Um jogador iniciará uma ação conjunta, e isso criará um novo recorde no allied_movement
tabela. Todos os grupos de unidades que participam de uma ação aliada são definidos por valores armazenados em allied_groups
tabela. Cada grupo pode ser atribuído a uma ação aliada apenas uma vez, de modo que as chaves estrangeiras formam a chave exclusiva desta tabela.
Este modelo nos dá a estrutura básica necessária para construir um jogo de estratégia MMO. Ele contém os recursos mais importantes do jogo:locais, estruturas, recursos, pesquisas e unidades. Ele também os relaciona, nos permite definir pré-requisitos no banco de dados e também armazena a maior parte da lógica do jogo no banco de dados.
Depois que essas tabelas são preenchidas, a maior parte da lógica do jogo é definida e não esperamos que novos valores sejam adicionados. Quase todas as tabelas têm um valor de chave exclusivo, seja um nome de recurso ou um par de chaves estrangeiras. Alterar as características das unidades e as fórmulas de produção/custo nos permitirá alterar o equilíbrio do jogo na camada de banco de dados.
Como você mudaria esse modelo? O que você gosta e o que você faria diferente? Diga-nos na seção de comentários!