O documento discute as melhorias e implementação de multiplayer local no jogo Horizon Chase Turbo. Ele descreve os desafios técnicos de adicionar essa funcionalidade em um jogo originalmente single-player, as soluções adotadas como ter múltiplas instâncias do cenário e sincronização entre elas, e otimizações realizadas para manter um bom desempenho mesmo com vários jogadores. Também aborda novas funcionalidades como modos de jogo adicionais e integração com leaderboards online entre plataformas.
4. HORIZON CHASE TURBINADO
MELHORIAS GERAIS: revisão de pistas, carros e economia
MULTIPLAYER: qual a melhor estrutura para esse jogo?
COMUNICAÇÃO: redesenho da UX de progressão
14. MULTIPLAYER DIRETRIZES
Tudo que você pode jogar sozinho, você pode jogar com alguém.
Competição e cooperação devem andar juntos.
Todas as partes do jogo têm progresso.
Arcos de jogo para diferentes jogadores e sessões.
37. O desafio (do lado técnico)
Trazer um jogo single-player de mobile para PC/consoles com multiplayer local
Melhorar a performance, com alvo em 60FPS constantes
Refazer toda a UI do jogo (layout, arte e código)
Implementar novos modos de jogo
Leaderboards e Ghosts online (global e amigos) para cada pista
Resolver problemas e polir a experiência geral de gameplay
Manter paridade entre 6(!) plataformas diferentes: Windows, Mac, Linux, PS4, Xbox One,
Nintendo Switch
38. O lado bom: o jogo já estava pronto
É mais fácil fazer um jogo que “já está pronto”
● Versão mobile no mercado por 2 anos
● Core gameplay provado e implementado
● Arquitetura de código definida
Desenvolvimento do Turbo focado em polimento, otimização e novas funcionalidades
● Multiplayer local
● Nova UI
● Novas plataformas
● Novos modos de jogo
● Funcionalidade online (Ghost Mode)
39. O lado ruim: o jogo já estava pronto
É mais difícil mexer em um jogo que “já está pronto”
● Arquitetura de código engessada
● Jogo feito com pouca previsão para multiplayer
● Funcionamento do cenário torna a transição para multiplayer ainda mais difícil
● No final, o custo de desenvolvimento do Turbo foi maior que do mobile.
Metodologia: Abstrair o que for simples, refazer o que for complexo
● Motivação para refazer todo o código de UI
● Grande parte do código de gameplay original desconhece o multiplayer...
41. Como fazer multiplayer local?
Estratégia usual: um cenário, múltiplas câmeras
● Na maioria dos jogos, a representação visual do cenário é fiel e corresponde diretamente
com a representação lógica.
○ Exemplo: em um mapa de FPS, a geometria lógica do mapa (onde os personagens
se movimentam) é a mesma geometria 3D que a câmera vê.
● Então, para fazer multiplayer local, basta adicionar mais câmeras no mesmo cenário
mostrando outras áreas.
● No Horizon Chase (e no Turbo), a representação visual da pista é uma abstração em cima
da forma real dela.
○ Assim como no Top Gear!
45. Como fazer multiplayer local?
Nossa solução: múltiplos cenários, múltiplas câmeras
● Para cada jogador, existe uma instância separada da pista na cena
○ ...mas apenas uma representação “interna” da pista, posição dos carros etc.
● Cada uma dessas pistas fica em uma posição diferente da cena
● Cada jogador tem a visão na sua pista equivalente à de “single player”
● O estado dos objetos em cada pista é sincronizado entre elas.
● Ou seja, são 4x pistas, 4x cada carro, 4x cada prop...
47. Problemas de performance
Sendo um jogo feito e pensado para mobile, o uso de GPU do jogo sempre foi baixo.
● Modelos low-poly para os props
● LODs para os carros
● Todos os carros e objetos não têm textura
● O que tem textura (pista, background e UI) não é afetado por luz.
Porém, o movimento dos carros e props é feito na CPU.
● Tudo se move o tempo todo para manter a ilusão da pista.
● 4 players -> 4 pistas
● 4 pistas -> 4x objetos
● 4x objetos -> 4x custo de CPU
48. Problemas de performance
Unity Profiler to the rescue!
Gargalo de performance: CPU, sendo o maior custo em scripting (ou seja, nosso código)
● Movimentação de objetos era feita no Update( )
● Muitos Updates -> Overhead de chamadas
Passo 1: UpdateableManager
● Custom update loop
● UpdateableManager.Register(IUpdateable target) / Unregister
● Uma única instância chama OnUpdate() em todas as outras
● Melhor… mas insuficiente
49. Problemas de performance
Uso de CPU alto, mas apenas em um Core do processador (especialmente no PS4)
Passo 2: Updates com multithreading
● Uma thread por jogador
● Cálculo de posição dos objetos feito em threads secundárias (sem acesso à API)
● Atualização de posição do transform feita no LateUpdate (thread principal)
● Outros sistemas eventualmente movidos para multithreading
○ Colisão, minimapa, gravação de ghost
Resultado no lançamento:
● PS4 Base: 60FPS constantes com 1-2 jogadores, 50-60 com 3, 40-50 com 4
● PS4 Pro: 60FPS constantes com 1-3 jogadores, 45-55 com 4
● PC: Bugs de sincronização nos forçaram a lançar sem multithreading :(
50. Problemas de performance
...but wait, there’s more!
● Custo alto no LateUpdate para setar as posições no Transform
● Gerenciamento manual de dependências gerou muitas barreiras no código (e overhead)
● Versão de PC ainda sem multithreading
● Performance no PS4 Pro e Switch abaixo do ideal
Passo 3: Unity C# Job System (in development)
● Gerenciamento automático de dependências
● Acesso direto a Transform a partir dos jobs (IJobParallelForTransform)
● Custo do LateUpdate drasticamente reduzido
● Ganho significativo de performance em todas as plataformas
51. Problemas de performance
C# Job System: Resultados preliminares
● PS4 Base: 60FPS constantes com 1-3 jogadores, 55-60 com 4
● PS4 Pro: 60FPS constantes com 1-4 jogadores
○ Sem VSync… 85FPS com 4 jogadores
● PC: Job System explora todos os CPU Cores! Yay!
● Nintendo Switch: 60FPS constantes com 1-2 jogadores
○ 55-60 FPS com 3
○ 45-50 FPS com 4
○ Inclusive em tabletop mode!
Obs: Roubamos um pouco...
52. Ferramentas para desenvolvimento de UI
Aquiris UI Framework
● Desenvolvido internamente para controle de fluxo de telas
● Baseado em camadas e FSMs
● https://pt.slideshare.net/JulianoSilveira10/um-workflow-robusto-de-ui
Data Binding
● Framework MVVM desenvolvido internamente
● Desacoplar código dos elementos visuais
● Baseado em Reflection
○ Problemas de performance ainda não resolvidos 100%
53. Implementação de novos Modos de Jogo
Código original assumia a existência de um único modo (Campanha)
Solução: abstrair código pré e pós corrida para uma classe GameModeData
● Código original: CampaignGameModeData
● Novos modos: TournamentGameModeData, EnduranceGameModeData
Separação de responsabilidades
● Código de gameplay restante diz respeito apenas a “uma corrida”
● GameModeData diz respeito ao fluxo entre corridas
Bônus: Balance Overrides na cena da corrida para mudar a dificuldade
54. Funcionalidade Online
Leaderboards
● Friends e Global
● Por Pista (Total de 109 pistas)
● Ghost Mode: Corra contra uma gravação de qualquer jogador no leaderboard
● Suportado em todas as plataformas (sem cross-play)
Solução: Utilizar infra-estrutura das plataformas (PSN, Steam etc)
● Zero custo de operação
● Abstração da plataforma no código do jogo
○ Telas, gameplay etc desconhecem em qual plataforma estão rodando
○ Apenas a integração com os serviços online é feita por plataforma
● Bônus: multiplayer assíncrono não requer PS Plus :)
55. Ghost Mode
Problema: espaço em servidor disponibilizado pelas plataformas é limitado
● 109 pistas… 109 ghosts por jogador
● Uma corrida = uma gravação de 1-5 minutos a 60FPS
● Dados gravados: posição na pista, posição lateral, eventos (colisões, nitro etc)
● Tamanho bruto: 60 x 60 x 5 x 2 x 4 = mais de 100KB por pista
Solução: compressão lossy customizada
● Sequência de posições gravada como uma AnimationCurve
● Keyframes removidos seletivamente baseado em uma métrica de erro máximo
● Valores finais salvos com precisão reduzida (10-20 bits em vez de 32)
Resultado: 0.5 ~ 5KB por pista
56. Polimentos e melhorias gerais
Código de colisão refeito do zero
● Colisão original baseada em Triggers com resposta “manual”
● Refeita sem utilizar a física da Unity
● Espaço “virtual” unificado entre todos os players
● Eventualmente movido para thread / jobs
● Takeaway: Trigonometria e Álgebra Vetorial :)
Dezenas de pequenos bugs corrigidos
Movimento da pista e dos props mais suave
Modelos de carros e LODs melhorados
57. Como manter 6 plataformas funcionando?
95% do código é independente de plataforma
Plataformas implementam algumas interfaces comuns (Input e Leaderboards)
Fluxo de usuários é consistente entre plataformas (baseado no modelo do PS4)
Só existe um ramo de desenvolvimento! (inclusive para builds especiais)
Controle de versão: Git-Flow com algumas liberdades
Integração contínua: Jenkins
60. Integração contínua
Builds automatizados para todas as plataformas
Nightly builds (toda madrugada são feitos builds automaticamente)
Desbloqueia a equipe de desenvolvimento
Documenta o processo de build
Alta quantidade de parâmetros para builds manuais
● Branch específica
● Plataformas específicas (no caso do Steam)
● Modos especiais: cheats, demo, eventos etc
● Versão da Unity
61. OBRIGADO!
FELIPE DAL MOLIN
LEAD GAME DESIGNER
felipe.dalmolin@aquiris.com.br
BRUNO “TINNUS” FERREIRA
LEAD PROGRAMMER
bruno.ferreira@aquiris.com.br