Malabarismo com dados em nuvem on e off-line

Então, aqui está o problema. Você está fazendo um jogo em que seus jogadores podem coletar moedas (ou qualquer outra coisa) e deseja habilitar uma opção para eles enviarem sua contagem de moedas online para compartilhar com seus amigos ou jogar de qualquer lugar e continuar de onde pararam.

Parece fácil, mas você também quer ter certeza de que eles podem jogar offline quando quiserem e, embora as contagens possam não coincidir temporariamente, quando eles ficarem online novamente, tudo irá girar – eles não perderão nenhuma das moedas.

Adicione o problema em que o jogador pode iniciar um novo jogo em outro computador (sem dados locais) e depois de coletar algumas moedas se lembra de ir online e espera (logicamente), que as moedas recém-ganhas sejam adicionadas ao total online.

Para piorar as coisas, no jogo, eles podem gastar suas moedas para ganhar recompensas, o que significa que a contagem de moedas # precisará ser capaz de se ajustar positivamente e negativamente … o que significa que, se eles fizerem login hoje e tiverem x moedas, então fique offline e gaste y moedas, o servidor precisa saber como atualizar para ter moedas xy.

Este é exatamente o problema que eu estava examinando recentemente e encontrei uma solução simples e bastante eficaz.

Claro, você pode tentar rastrear um registro ou histórico das moedas conforme elas são ganhas e gastas e apenas manter um total em execução usando todos esses dados, mas isso complicaria muito mais as coisas … e daria muito trabalho para configuração.

Para esta solução, vamos assumir que o código do lado do servidor é predeterminado e realmente simples: você pode enviar os dados – que simplesmente substitui o valor armazenado atualmente pelo novo valor, e pode carregar os dados que apenas retornam o valor atualmente armazenado.

Então, se o servidor mostrar 100 moedas. Se você chamar Load, obterá 100, se chamar Send 120, o servidor terá 120, e se você chamar Load novamente, obterá 120. Simples.

O truque aqui é que precisamos saber qual local tem a versão mais recente dos dados.

A versão local dos dados salvos precisará ter 3 coisas: a contagem de moedas, a contagem de moedas da última vez que enviamos ao servidor e um sinalizador simples para armazenar o Estado Salvo.

Agora, o Sinalizador de estado salvo terá 3 valores possíveis:
NOVO = -1
NÃO
ENVIADO = 0 ENVIADO = 1

Vamos atualizar esse valor dependendo do que acontecer com os dados.

Quando um novo jogo é carregado pela primeira vez – a primeira vez nesta máquina, ou talvez eles tenham apagado seus dados ou algo assim – o estado será definido como NOVO. Sua contagem de moedas também será 0.

Agora o jogador pode simplesmente começar a jogar ou pode fazer o Login.

Se eles simplesmente começarem a jogar, cada vez que descarregarmos sua contagem de moedas no disco (como você quiser fazer isso – cada vez que eles pegam uma moeda, ou quando batem um nível, ou qualquer outra coisa), vamos ver que eles não estão online, então não vamos nem tentar atualizar o servidor, e vamos deixar o estado como NOVO.

Agora digamos que eles jogaram por um tempo e saíram do jogo. Eles voltam a ele mais tarde e o jogo carrega seus dados do disco imediatamente. Vai carregar sua contagem de moedas (digamos … 50 moedas), e o estado que ainda é NOVO.

Agora o jogador decide fazer o login pela primeira vez – ele se registrou ou o que quer que seja e agora ele coloca suas credenciais pela primeira vez. Nós nos conectamos ao servidor e fazemos tudo isso, e então vamos chamar nossa rotina SyncWithServer.

Essa rotina vai primeiro pegar o valor do servidor, verificar qual é o nosso estado, e então dependendo do estado, fazer algo diferente.

Portanto, neste cenário:
Server.CoinCount = 0
Local.CoinCount = 50
State = NEW

Porque o estado = NOVO, vamos COMBINAR as duas contagens. Vamos fazer:
Local.CoinCount + = Server.CoinCount

Agora, como temos dados não NOVOS que ainda não foram liberados para o servidor, definimos nosso estado como NÃO ENVIADO e, em seguida, chamamos nossa rotina de envio para atualizar o servidor com o valor atual (50), e depois de ter sucesso, nós defina nosso estado como ENVIADO.

Até agora, nossa lógica seria mais ou menos assim:
function SyncWithServer ()
Server.CoinCount = Load ()
if State == NEW
{
Local.CoinCount + = Server.CoinCount
State = UNSENT
if Send (Local.CoinCount)
State = SENT
}

Uma vez que o jogador está logado e esta sincronização é feita, não precisamos verificar os números novamente durante esta sessão – eles jogam e coletam mais moedas, gastam moedas, o que for, e nós apenas faremos algo como isto:

function FlushCoinCount()
Local.CoinCount = CoinCount
if State != NEW State = UNSENT
if OnLine
{
if Send(Local.CoinCoint)
State = SENT
}

Então, quando eles finalmente terminarem de jogar o dia, digamos que eles tenham 100 moedas, as coisas ficarão assim:

Server.CoinCount = 100
Local.CoinCount = 100
State = SENT

Agora, da próxima vez que abrirem o jogo, digamos que façam login imediatamente. Assim que o jogo começar, ele pegará os dados locais, e então faremos a Rotina SyncWithServer – mas desta vez State = SENT e não NEW, então precisamos fazer algo diferente. Veja – pelo que sabemos neste momento, quando o jogador parou de jogar neste computador, ele tinha 100 moedas, e nós enviamos esse # para o servidor. Mas não temos NENHUMA IDEIA se ele jogou em outro computador e logou e enviou um número diferente – então vamos jogar fora nossa contagem de moedas locais e apenas usar o que o servidor tem. Portanto, nossa função de sincronização seria mais ou menos assim:

function SyncWithServer()
Server.CoinCount = Load()
if State == NEW
{
Local.CoinCount += Server.CoinCount
State = UNSENT
if Send(Local.CoinCount)
State = SENT
}
else if State = SENT
{
Local.CoinCount = Server.CoinCount
}

Isso é fácil – basicamente estamos dizendo, uma vez que o estado SENT é enviado, que estamos renunciando à responsabilidade para o servidor.

Mas temos mais um valor de estado: NÃO ENVIADO. Este é o mais complicado. Esta situação ocorreria se o jogador logasse e jogasse e ENVIasse seus dados, e então jogasse novamente mais tarde sem logar, e então logasse novamente. Nós realmente não saberíamos qual valor ‘regras’, que é onde nosso LastSentValue variável entra em jogo.

Sempre que atualizamos nosso valor para o servidor e definimos nosso estado para ENVIADO, queremos registrar nosso valor de moeda naquele momento – então, se mais tarde, encontrarmos o estado NÃO ENVIADO quando estivermos recebendo dados do servidor, estaremos vai ajustar o valor proveniente do servidor pela diferença entre a contagem de moeda local e a contagem da última moeda.

Esta é a aparência de ambas as nossas pseudo funções:

    function SyncWithServer()
Server.CoinCount = Load()
if State == NEW
{
Local.CoinCount += Server.CoinCount
State = UNSENT
if Send(Local.CoinCount)
{
State = SENT
LastCoinCount = Local.CoinCount
}
}
else if State = SENT
{
Local.CoinCount = Server.CoinCount
LastCoinCount = Local.CoinCount
}
else if State = UNSENT
{
LocalCoinCount = Server.CoinCount + (LastCoinCount - Local.CoinCount)
if Send(Local.CoinCount)
{
State = SENT
LastCoinCount = Local.CoinCount
}
}

function FlushCoinCount()
Local.CoinCount = CoinCount
if State != NEW State = UNSENT
if OnLine
{
if Send(Local.CoinCoint)
{
State = SENT
LastCoinCount = Local.CoinCount
}
}

E aí está! Não importa o que seu usuário faça – jogue em computadores diferentes, esqueça de fazer o login, limpe seus dados locais, etc – assim que ele finalmente fizer o login, tudo deve estar atualizado!