Capítulo 00 — Bem-vindo à Game Engineering na Prática
Série: Do zero ao jogo comercial com C++ e SFML
Do zero ao jogo comercial com C++ e SFML
Existe uma diferença enorme entre fazer um personagem aparecer na tela e compreender como um jogo é realmente construído.
Também existe uma diferença enorme entre aprender alguns comandos de C++, seguir um tutorial de SFML e desenvolver software capaz de crescer até se transformar em um jogo completo.
É justamente nesse espaço que começa esta série.
Game Engineering na Prática — Do Zero ao Jogo Comercial com C++ e SFML nasce com uma proposta ambiciosa: construir uma jornada que começa antes da primeira classe, antes da primeira janela gráfica e antes mesmo da primeira linha de código — mas que não ficará presa à teoria.
Vamos aprender programação.
Vamos aprender C++.
Vamos descobrir como compiladores, linkers, bibliotecas e memória realmente funcionam.
Vamos entrar no desenvolvimento de games.
Vamos trabalhar com SFML.
Vamos construir nossa própria arquitetura.
Depois, nossa própria engine.
E utilizaremos essa engine para desenvolver um jogo real.
Mas não vamos parar quando o jogo simplesmente estiver “funcionando”.
Testes, ferramentas, Game AI, dados, Machine Learning, performance, segurança, CI/CD, QA, builds, distribuição, publicação e manutenção também fazem parte da Engenharia de Games.
A jornada começa aqui.
Antes de tudo: por que esta série existe?
Existem excelentes conteúdos ensinando C++.
Existem excelentes conteúdos ensinando SFML.
Existem tutoriais mostrando como movimentar um personagem, desenhar sprites, criar colisões ou desenvolver pequenos jogos.
O problema aparece quando começamos a fazer perguntas diferentes:
Como tudo isso se conecta?
Como um programa C++ cresce sem virar um conjunto de arquivos difíceis de manter?
Por que precisamos separar
.he.cpp?O que realmente acontece quando clicamos em Build?
Por que aparecem erros de linker?
Por que uma DLL funciona em uma configuração e não em outra?
Quando uma classe deveria existir?
Quando devemos criar um
SceneManager?Precisamos realmente de um
ResourceManager?Quando um Design Pattern resolve um problema e quando ele apenas adiciona complexidade?
Como testamos regras de um jogo automaticamente?
Como sabemos onde está um problema de performance?
Como transformamos um projeto que funciona em nossa máquina em algo que outra pessoa consegue instalar e jogar?
Essas perguntas pertencem a algo maior que simplesmente aprender a sintaxe de uma linguagem.
Elas pertencem à Engenharia de Software aplicada ao desenvolvimento de games.
E é exatamente isso que vamos estudar.
O que esta série não será
É importante estabelecer isso desde o começo.
Esta série não será uma documentação da SFML.
Também não será uma coleção de códigos do tipo:
#include <SFML/Graphics.hpp>
// copie mais 200 linhas daqui...
seguida por:
Execute e veja seu jogo funcionando.
Também não vamos começar criando dezenas de classes:
Application
Engine
Game
SceneManager
ResourceManager
InputManager
AudioManager
EventBus
ServiceLocator
EntityManager
ComponentManager
SystemManager
sem que você ainda saiba por que qualquer uma delas deveria existir.
Esse caminho pode produzir rapidamente algo visualmente interessante.
Mas cria outro problema.
Você aprende a reproduzir uma arquitetura sem necessariamente compreender a engenharia por trás dela.
Nossa abordagem será diferente.
Primeiro o problema. Depois a solução.
Esta será uma das regras mais importantes de toda a série:
Nenhuma abstração importante deverá aparecer antes que exista um problema compreensível que justifique sua existência.
Imagine que, daqui a algum tempo, nosso jogo tenha:
menu principal;
gameplay;
pause;
configurações;
game over.
Talvez então comecemos a perceber condicionais espalhados por vários lugares.
Algo precisa organizar essas diferentes telas e estados.
Agora temos um problema.
Podemos analisar alternativas.
Somente então algo como uma Scene ou um SceneManager começa a fazer sentido.
A mesma lógica será aplicada a:
Resource Management;
Input System;
Renderer;
Audio System;
Logging;
Save/Load;
Components;
Event Systems;
Design Patterns;
arquitetura;
testes;
ferramentas;
telemetria;
Machine Learning;
DevOps.
Não construiremos arquitetura para impressionar.
Construiremos arquitetura porque o software começou a exigir arquitetura.
E onde entra o C++?
Em praticamente toda a jornada.
Mas não vamos tratar C++ como uma matéria isolada durante dezenas de capítulos para, somente depois, começar a falar de games.
Vamos aprender programação através do C++.
Quando nosso futuro jogador precisar possuir vida:
int playerHealth = 100;
teremos uma razão para conversar sobre variáveis e números inteiros.
Quando ele puder estar acordado ou dormindo:
bool sleeping = false;
teremos uma razão para conversar sobre valores booleanos.
Quando uma ação depender da quantidade de energia:
if (playerEnergy > 0)
{
// ...
}
teremos uma razão para estudar decisões.
Quando alguma coisa precisar continuar acontecendo enquanto nosso programa estiver executando:
while (isRunning)
{
// ...
}
teremos contexto para estudar repetição.
E, progressivamente, essa pequena ideia começará a nos aproximar de um dos conceitos fundamentais de praticamente qualquer jogo:
o Game Loop.
Ou seja, aprenderemos programação e C++, mas sempre tentando responder:
Como isso se conecta ao software que estamos construindo?
Você não precisa saber C++
Na realidade, esta série foi planejada para permitir que alguém comece sem saber programar.
Isso significa que conceitos aparentemente óbvios para um desenvolvedor experiente não serão simplesmente ignorados.
Quando encontrarmos algo como:
int main()
{
return 0;
}
não vou presumir que você sabe o que significa:
int;main;();{;};return;0;;.
Vamos aprender.
Da mesma forma, quando chegarmos a:
class Player
{
};
não basta dizer:
“Criamos a classe Player.”
Precisaremos compreender o que é uma classe, por que ela existe e por que decidimos representar determinado conceito dessa maneira.
Mas existe uma diferença importante.
À medida que avançarmos, conhecimentos já adquiridos deixarão de ser repetidos detalhadamente.
A série precisa crescer junto com você.
Do iniciante absoluto ao C++ moderno
Ao longo dessa caminhada encontraremos progressivamente conceitos como:
tipos;
variáveis;
constantes;
operadores;
condicionais;
loops;
funções;
parâmetros;
retornos;
escopo;
lifetime;
strings;
arrays;
structs;
classes;
encapsulamento;
construtores;
destrutores;
composição;
herança;
polimorfismo;
referências;
ponteiros;
stack;
heap;
RAII;
smart pointers;
STL;
containers;
iterators;
algorithms;
lambdas;
templates;
move semantics;
const correctness;
exceptions;
Modern C++.
Mas esses assuntos não existirão apenas para preencher um currículo de C++.
Eles deverão aparecer porque algum problema nos levou até eles.
Vamos descobrir o que acontece por baixo do código
Em algum momento você provavelmente já viu algo semelhante a:
Source Code
↓
Compiler
↓
Executable
É útil como primeira simplificação.
Mas C++ rapidamente nos mostra que existe muito mais acontecendo.
Vamos investigar progressivamente coisas como:
Source Code
↓
Preprocessor
↓
Compiler
↓
Object Files
↓
Linker
↓
Executable
E isso não será apenas teoria.
Esses conhecimentos serão importantes quando encontrarmos problemas reais.
Por exemplo:
unresolved external symbol
ou:
LNK2019
ou ainda o famoso:
0xc000007b
Em vez de procurar uma sequência aleatória de comandos na internet, queremos conseguir perguntar:
Em qual etapa o problema provavelmente aconteceu?
Essa mudança de pensamento é extremamente importante.
E vamos quebrar coisas de propósito
Sim.
Durante a série vamos provocar alguns erros intencionalmente.
Imagine escrever:
int playerHealth = 100
e esquecer o ;.
Em vez de simplesmente corrigirmos rapidamente, poderemos observar:
o que o Visual Studio mostrou;
onde apareceu o erro;
qual mensagem recebemos;
como localizar a linha;
como interpretar a informação;
por que o compilador não conseguiu continuar;
como corrigir;
como reconhecer problemas semelhantes futuramente.
Mais adiante encontraremos erros muito mais interessantes.
E aprenderemos uma metodologia:
Mensagem
↓
Hipótese
↓
Investigação
↓
Causa
↓
Correção
↓
Prevenção
Debugging não será um apêndice da série.
Será parte da formação.
Visual Studio antes da automação
Inicialmente trabalharemos com Visual Studio Community 2022 e aprenderemos a configurar nosso ambiente de maneira consciente.
Isso significa que, quando chegarmos à integração da SFML, não quero simplesmente entregar uma configuração pronta e dizer:
“Coloque isso aqui.”
Vamos entender conceitos como:
Additional Include Directories
Additional Library Directories
Additional Dependencies
Preprocessor Definitions
Runtime Library
Debug
Release
x64
x86
DLL
PATH
Isso pode parecer menos atraente que instalar alguma coisa automaticamente.
Mas existe uma razão.
Mais tarde utilizaremos ferramentas capazes de automatizar boa parte desse trabalho.
Inclusive chegaremos ao CMake.
Só que existe uma diferença enorme entre utilizar uma ferramenta porque alguém mandou copiar um arquivo de configuração e utilizá-la sabendo qual problema ela está resolvendo.
Então quando veremos SFML?
Não no primeiro minuto.
Mas também não vamos esperar metade da série para começar a pensar como desenvolvedores de games.
Antes da SFML, já poderemos trabalhar com:
estado;
regras;
decisões;
loops;
pequenas simulações;
tempo;
organização;
comportamento.
Quando finalmente integrarmos SFML, já teremos estudado conceitos suficientes para compreender melhor o que estamos fazendo.
Então começaremos a sair disto:
Console
para isto:
Window
↓
Events
↓
Input
↓
Game Loop
↓
Rendering
↓
Sprites
↓
Movement
↓
Gameplay
A partir daí, nosso resultado começará a ficar cada vez mais visual.
Mas SFML também não será nossa arquitetura
Esse ponto será fundamental.
Conceitualmente queremos caminhar para algo semelhante a:
Game
↓
Engine
↓
Internal Systems
↓
SFML
↓
Operating System / GPU / Audio / Input
Isso não significa criar abstrações artificiais para absolutamente tudo que a SFML oferece.
Seria outro tipo de overengineering.
Significa apenas que nosso jogo não deverá se transformar em uma gigantesca aplicação acoplada desnecessariamente a detalhes externos.
Vamos descobrir esse limite durante a implementação.
Vamos construir uma Engine?
Sim.
Mas não no Capítulo 01.
E muito menos criando todas as pastas antes de possuir qualquer código.
Nosso projeto poderá começar quase assim:
CppGameEngine/
└── main.cpp
Parece pequeno.
E é justamente essa a intenção.
Com o tempo, problemas reais começarão a surgir.
Talvez precisemos separar:
Core/
Depois:
Engine/
Talvez apareçam:
Input/
Math/
Physics/
Resources/
Scene/
Audio/
UI/
SaveLoad/
Tools/
Até que, muito tempo depois, poderemos olhar para uma estrutura consideravelmente maior.
Mas cada pasta deverá carregar uma história.
Cada classe deverá carregar uma razão.
Cada abstração deverá responder:
Por que você existe?
E teremos um jogo real
Uma engine sem um consumidor real corre um risco interessante.
Podemos construir abstrações que parecem excelentes porque nunca foram pressionadas por um jogo de verdade.
Por isso nossa engine será desenvolvida junto de um projeto real.
A direção planejada é um jogo 2D envolvendo elementos de:
farming;
management;
exploration;
economy;
NPCs;
inventory;
resources;
progression;
interaction;
maps;
time cycles;
persistence.
A intenção não é copiar um jogo existente.
Esses elementos servirão como laboratório para problemas reais de Game Engineering.
Por exemplo:
O personagem precisa plantar alguma coisa.
Isso nos leva a estado do solo.
Depois precisamos representar sementes.
Crescimento.
Tempo.
Colheita.
Inventário.
Venda.
Economia.
Progressão.
Save.
Interface.
Feedback.
Som.
Animação.
E cada novo requisito pressiona nossa arquitetura.
É exatamente isso que queremos.
Matemática também fará parte da jornada
Mas não teremos uma parede de fórmulas sem contexto.
Quando precisarmos movimentar algo em direção a outro objeto, surge uma pergunta.
Como representar direção?
Então entra matemática.
Vamos trabalhar progressivamente com:
coordenadas;
vetores;
magnitude;
normalização;
distância;
direção;
velocidade;
aceleração;
interpolação;
ângulos;
trigonometria;
transformações;
bounding boxes;
probabilidade;
randomização.
Sempre que possível:
Problema visual
↓
Matemática
↓
Implementação C++
↓
Resultado no jogo
A matemática passa a possuir propósito.
E Física?
Também.
Mas movimento e física não são exatamente a mesma coisa.
Precisaremos entender essa diferença.
Com o crescimento do jogo poderemos trabalhar com:
velocidade;
aceleração;
integração;
fixed timestep;
bounding volumes;
detecção de colisão;
resposta de colisão;
collision layers;
triggers.
Não tentaremos construir uma engine física universal apenas porque seria tecnicamente interessante.
Construiremos aquilo que nosso jogo realmente precisar.
Testes automatizados em um jogo?
Sim.
E esse será outro ponto importante da série.
Games possuem muito comportamento que pode ser validado automaticamente.
Imagine uma regra:
Player possui 100 moedas.
Compra item de 30.
Saldo esperado: 70.
Não precisamos abrir uma janela, clicar em um botão e observar visualmente para testar toda regra desse tipo.
O mesmo vale para:
inventário;
economia;
matemática;
física determinística;
colisões;
farming;
progressão;
save/load.
Ao longo da série construiremos progressivamente nossa própria infraestrutura de automação voltada ao projeto.
Conceitualmente ela poderá evoluir para algo como:
Tests/
├── Core/
├── Engine/
├── Game/
├── Math/
├── Physics/
├── Integration/
├── Fakes/
├── Mocks/
├── Fixtures/
├── Assertions/
├── Utilities/
└── TestRunner/
Mais uma vez:
não criaremos tudo isso de uma vez.
O framework crescerá quando os problemas de teste aparecerem.
Game AI não é Machine Learning
Quando nossos NPCs precisarem tomar decisões, teremos problemas de inteligência artificial.
Mas isso não significa que precisaremos treinar uma rede neural.
Games utilizam há décadas técnicas extremamente poderosas como:
Finite State Machines
Behavior Trees
Pathfinding
Utility AI
Estudaremos primeiro Game AI tradicional.
Depois, quando nosso jogo começar a produzir dados, poderemos entrar em outro universo.
Dados e Machine Learning
Imagine que futuramente possamos observar coisas como:
quanto tempo jogadores levam para compreender uma mecânica;
onde abandonam uma sequência;
quais recursos acumulam;
quais produtos vendem;
onde a economia desequilibra;
quais dificuldades aparecem;
como diferentes estilos de jogadores se comportam.
Agora temos dados.
E somente então faz sentido perguntar:
Existe algum problema aqui que Machine Learning poderia ajudar a resolver?
Poderemos estudar conceitos como:
datasets;
features;
labels;
training;
inference;
classification;
regression;
clustering;
prediction.
Mas Machine Learning nunca entrará no projeto apenas para colocarmos “IA” na arquitetura.
E Inteligência Artificial Generativa?
Também será estudada.
Mas separadamente.
Precisamos distinguir claramente:
Game AI
≠
Machine Learning
≠
Generative AI
IA Generativa poderá ser analisada como ferramenta para:
prototipação;
desenvolvimento;
análise de dados;
experimentação;
geração assistida de conteúdo;
diálogos;
ferramentas internas.
Sempre considerando:
custo;
latência;
privacidade;
segurança;
qualidade;
previsibilidade;
manutenção.
Tecnologia precisa resolver problema.
Não apenas aparecer no projeto porque está em alta.
Performance não significa adivinhar gargalos
Quando nosso jogo crescer, eventualmente precisaremos discutir performance.
CPU.
GPU.
Memória.
Cache.
Alocações.
Frame Time.
Draw Calls.
Resource Loading.
Data Locality.
Mas seguiremos uma regra:
Medir
↓
Identificar
↓
Criar hipótese
↓
Otimizar
↓
Medir novamente
Não vamos otimizar um sistema apenas porque alguém afirmou que determinada construção “é lenta”.
Primeiro precisamos descobrir se temos um problema.
Também construiremos ferramentas
Uma parte frequentemente menos visível do desenvolvimento profissional de games é a construção de ferramentas.
Em determinado momento poderemos precisar enxergar:
FPS
Frame Time
Player Position
Entity State
Collision Bounds
Loaded Resources
Scene
Memory
Isso poderá levar à criação de:
Debug Overlay;
Entity Inspector;
Collision Visualization;
Asset Validation;
Developer Console;
ferramentas internas.
Novamente, elas aparecerão porque trabalhar sem elas começou a ficar difícil.
Git fará parte da história
Não vamos apenas mostrar código final.
Queremos registrar a evolução.
Por isso utilizaremos Git e GitHub progressivamente.
Cada etapa importante poderá possuir commits como:
git commit -m "feat: add application lifecycle"
ou:
git commit -m "fix: resolve runtime library mismatch"
ou:
git commit -m "test: add inventory tests"
Isso permitirá observar algo muito interessante ao longo da série:
a história da arquitetura.
Não apenas o resultado.
O repositório que acompanhará a implementação ficará dentro do projeto agregador:
projetos_praticos_c_cpp/
└── 07_Game_Engineering_na_Pratica_CPP_SFML/
E esse projeto crescerá junto conosco.
Os artigos, por enquanto, permanecerão publicados no Medium.
Do código à produção
Muitos cursos terminam aproximadamente aqui:
"Parabéns! Seu jogo está funcionando."
Nós não.
Um jogo funcionando na máquina do desenvolvedor ainda está muito distante de um produto comercial.
Precisaremos conversar sobre:
Debug;
Release;
warnings;
static analysis;
profiling;
crashes;
logs;
builds;
versionamento;
assets;
licensing;
packaging;
distribuição;
QA;
playtesting;
CI/CD;
releases;
patches;
updates;
telemetria;
compatibilidade;
publicação.
Em algum momento nosso desafio deixará de ser:
Como fazer isso funcionar?
e passará a ser:
Como fazer isso funcionar corretamente para outra pessoa?
Essa é uma mudança enorme de maturidade.
Nossa jornada, em perspectiva
O Roadmap Mestre é propositalmente extenso.
Não porque queremos transformar assuntos simples em centenas de páginas.
Mas porque não queremos fingir que formar alguém do zero até Game Engineering de produção cabe em vinte tutoriais.
Nossa trajetória geral será aproximadamente:
Programação
↓
C++
↓
Compilação e Linking
↓
Git
↓
Memória
↓
Orientação a Objetos
↓
Modern C++
↓
Engenharia de Software
↓
Game Development
↓
Engine Core
↓
Logging
↓
SFML
↓
Window
↓
Events e Input
↓
Game Loop e Time
↓
Rendering
↓
Matemática
↓
Resource Management
↓
Scenes
↓
Engine × Game
↓
Gameplay Architecture
↓
World e Maps
↓
Player e Entities
↓
Animation
↓
Physics
↓
Collision
↓
Camera
↓
Audio
↓
UI
↓
Inventory
↓
Farming
↓
Economy
↓
NPCs
↓
Game AI
↓
Save / Load
↓
Tools
↓
Automated Testing
↓
Telemetry
↓
Machine Learning
↓
IA aplicada
↓
Performance
↓
Security
↓
DevOps e CI/CD
↓
QA e Playtesting
↓
Vertical Slice
↓
Polish
↓
Release Engineering
↓
Packaging
↓
Licensing
↓
Store
↓
Production
↓
Post-launch
↓
Jogo Comercial
É uma jornada longa.
E é justamente por isso que precisamos construí-la passo a passo.
Não vamos esperar dezenas de capítulos para programar
Este ponto merece destaque.
Embora este Capítulo 00 seja responsável por estabelecer nossa direção, o próximo capítulo já coloca a mão no código.
Nada de passar semanas lendo teoria antes de abrir a IDE.
No Capítulo 01 vamos começar a trabalhar com o Visual Studio e C++.
Nosso primeiro programa será extremamente pequeno.
Algo próximo de:
#include <iostream>
int main()
{
std::cout << "Game Engineering com C++ começou!" << std::endl;
return 0;
}
E isso será suficiente.
Porque naquele momento nosso objetivo não será construir uma engine.
Será conseguir dizer:
Eu escrevi código C++, compilei e executei meu primeiro programa.
Depois vamos voltar para aquele pequeno código e começar a fazer perguntas.
O que é #include?
O que é iostream?
O que significa int?
Por que main?
Por que existem parênteses?
O que é std?
O que é cout?
Por que existe return 0?
O que o compilador fez?
Onde está o executável?
E assim começa nossa engenharia.
Uma regra de ouro para acompanhar esta série
Não leia esta série apenas como um livro.
Quando houver código:
digite.
Quando houver um comando:
execute.
Quando pedirmos para mudar alguma coisa:
mude.
Quando provocarmos um erro:
observe o erro antes de corrigi-lo.
Quando houver um microdesafio:
tente resolvê-lo.
Nossa sequência ideal será:
VER
↓
FAZER
↓
QUEBRAR
↓
ENTENDER
↓
CORRIGIR
↓
EVOLUIR
Vídeos possuem uma vantagem enorme: é agradável observar alguém fazendo alguma coisa.
Texto possui outra vantagem enorme: você controla o ritmo.
Pode parar.
Voltar.
Executar.
Investigar.
Comparar.
Quebrar.
Pesquisar.
Modificar.
Então vamos aproveitar justamente essa característica.
Você não será apenas o leitor desta série.
Será o desenvolvedor do projeto.
O ponto de partida
Neste momento não temos:
Engine/
Physics/
Scene/
Audio/
UI/
AI/
Tests/
Tools/
E isso é ótimo.
Também não temos centenas de classes esperando para serem explicadas.
Temos uma direção.
Um Roadmap Mestre.
Uma stack inicial.
Um projeto que começará praticamente vazio.
E muitos problemas que ainda não existem.
Quando eles aparecerem, resolveremos um de cada vez.
É assim que quero que possamos chegar, muitos capítulos no futuro, a uma estrutura sofisticada e olhar para ela sabendo exatamente:
“Eu sei por que cada uma dessas partes existe.”
Esse será um dos principais indicadores de que nossa jornada funcionou.
Onde queremos chegar
Um dia poderemos olhar para trás e encontrar um commit contendo algo extremamente simples:
int main()
{
// ...
}
E olhar para o estado atual do projeto contendo engine, gameplay, ferramentas, testes, assets, sistemas, builds e um jogo real.
A diferença entre esses dois momentos não será um código mágico.
Será uma sequência de centenas de pequenas decisões.
Algumas simples.
Outras difíceis.
Algumas certas desde o começo.
Outras que precisaremos refatorar.
E isso também é Engenharia de Software.
Não estamos tentando escrever a arquitetura perfeita na primeira tentativa.
Estamos aprendendo a construir software capaz de evoluir.
A jornada começa agora
Se você nunca programou, está no lugar certo.
Se já programa, mas está começando em C++, também.
Se conhece C++, mas quer entender melhor Game Engineering, continue conosco.
E se já desenvolve software profissionalmente, vários capítulos mais avançados nos levarão a arquitetura, engine development, testes, ferramentas, performance, dados, IA e produção.
Começaremos todos no mesmo ponto:
main.cpp
A diferença estará em até onde decidirmos levá-lo.
No próximo capítulo, finalmente abriremos nossa ferramenta, criaremos o primeiro projeto e escreveremos as primeiras linhas que darão origem a tudo que construiremos durante esta série.
Próximo capítulo
Capítulo 01 — Seu primeiro programa C++ começa agora
No próximo artigo:
conheceremos o ambiente que utilizaremos inicialmente;
criaremos nosso projeto;
encontraremos o
main.cpp;escreveremos nosso primeiro código C++;
compilaremos;
executaremos;
veremos o primeiro resultado;
modificaremos o programa;
e faremos nosso primeiro pequeno experimento.
Nada de Engine ainda.
Nada de SFML ainda.
Nada de arquitetura sofisticada.
Apenas o primeiro passo.
E, desta vez, já com código rodando.
Comentários
Ainda não há comentários. Sê o primeiro a comentar!