paint-brush
Cree un motor de juego desde cero en C++by@pikuma
32,001
32,001

Cree un motor de juego desde cero en C++

Pikuma.com2022/04/24
Read on Terminal Reader
Read this story w/o Javascript
tldt arrow
ES

Este artículo repasa algunos de los componentes fundamentales que se requieren para crear un motor de juego simple con C++. Dará algunas recomendaciones personales sobre cómo me gusta acercarme a escribir uno desde cero.

Companies Mentioned

Mention Thumbnail
Mention Thumbnail

Coin Mentioned

Mention Thumbnail
featured image - Cree un motor de juego desde cero en C++
Pikuma.com HackerNoon profile picture

Cómo hacer tu propio motor de juego C++

Entonces, ¿quieres aprender más sobre los motores de juego y escribir uno tú mismo? ¡Eso es genial! Para ayudarlo en su viaje, aquí hay algunas recomendaciones de bibliotecas y dependencias de C ++ que lo ayudarán a comenzar a ejecutar.


El desarrollo de juegos siempre ha sido una gran ayuda para motivar a mis alumnos a aprender más sobre temas informáticos más avanzados.

Uno de mis tutores, el Dr. Sepi, dijo una vez:


"Algunas personas piensan que los juegos son cosas de niños, pero el desarrollo de juegos es una de las pocas áreas que utiliza casi todos los elementos del plan de estudios estándar de CS". - Dr. Sepideh Chakaveh


Como siempre, ¡tiene toda la razón! Si exponemos lo que está oculto bajo la pila de desarrollo de cualquier juego moderno, veremos que toca muchos conceptos que son familiares para cualquier estudiante de informática.

El desarrollo de juegos utiliza varios elementos del plan de estudios estándar de CS


Dependiendo de la naturaleza de su juego, es posible que deba profundizar aún más en áreas más especializadas, como sistemas distribuidos o interacción persona-computadora. El desarrollo de juegos es un asunto serio y puede ser una herramienta poderosa para aprender conceptos serios de CS.


Este artículo repasa algunos de los componentes fundamentales que se requieren para crear un motor de juego simple con C++. Explicaré los elementos principales que se requieren en un motor de juego y daré algunas recomendaciones personales sobre cómo me gusta abordar la escritura de uno desde cero.

Dicho esto, este no será un tutorial de codificación. No entraré en demasiados detalles técnicos ni explicaré cómo se unen todos estos elementos a través del código. Si está buscando un video libro completo sobre cómo escribir un motor de juego C++, este es un gran punto de partida: Cree un motor de juego 2D con C++ y Lua .


Pikuma: crea un motor de juego C++

¿Qué es un motor de juego?

Si estás leyendo esto, es probable que ya tengas una buena idea de lo que es un motor de juego, y posiblemente incluso hayas intentado usar uno tú mismo. Pero para que todos estemos en la misma página, repasemos rápidamente qué son los motores de juegos y qué nos ayudan a lograr.

Un motor de juego es un conjunto de herramientas de software que optimiza el desarrollo de videojuegos. Estos motores pueden ser pequeños y minimalistas, proporcionando solo un bucle de juego y un par de funciones de renderizado, o ser grandes y completos, similares a aplicaciones similares a IDE donde los desarrolladores pueden crear secuencias de comandos, depurar, personalizar la lógica de nivel, AI, diseñar, publicar, colaborar. y, en última instancia, crea un juego de principio a fin sin la necesidad de dejar el motor.

Los motores de juegos y los marcos de juego suelen exponer una API al usuario. Esta API permite al programador llamar a las funciones del motor y realizar tareas difíciles como si fueran cajas negras.

Para entender realmente cómo funciona esta API, pongámoslo en contexto. Por ejemplo, no es raro que una API de motor de juego exponga una función llamada " IsColliding() " que los desarrolladores pueden invocar para comprobar si dos objetos del juego están colisionando o no. No es necesario que el programador sepa cómo se implementa esta función o cuál es el algoritmo requerido para determinar correctamente si dos formas se superponen. En lo que a nosotros respecta, la función IsColliding es una caja negra que hace algo de magia y devuelve correctamente verdadero o falso si esos objetos chocan entre sí o no. Este es un ejemplo de una función que la mayoría de los motores de juegos exponen a sus usuarios.

 if (IsColliding(player, bullet)) { lives--; if (lives == 0) { GameOver(); } } 

La mayoría de los motores abstraerán la detección de colisiones y simplemente la expondrán como una función de verdadero/falso.


Además de una API de programación, otra gran responsabilidad de un motor de juego es la abstracción de hardware. Por ejemplo, los motores 3D generalmente se basan en una API de gráficos dedicada como OpenGL , Vulkan o Direct3D . Estas API proporcionan una abstracción de software para la Unidad de procesamiento de gráficos ( GPU ).

Hablando de abstracción de hardware, también hay bibliotecas de bajo nivel (como DirectX , OpenAL y SDL ) que brindan abstracción y acceso multiplataforma a muchos otros elementos de hardware. Estas bibliotecas nos ayudan a acceder y manejar eventos de teclado, movimiento del mouse, conexión de red e incluso audio.

El auge de los motores de juego

En los primeros años de la industria de los juegos, los juegos se creaban utilizando un motor de renderizado personalizado y el código se desarrollaba para obtener el mayor rendimiento posible de las máquinas más lentas. Cada ciclo de la CPU era crucial, por lo que la reutilización de código o las funciones genéricas que funcionaban para múltiples escenarios no eran un lujo que los desarrolladores pudieran permitirse.

A medida que los juegos y los equipos de desarrollo crecieron tanto en tamaño como en complejidad, la mayoría de los estudios terminaron reutilizando funciones y subrutinas entre sus juegos. Los estudios desarrollaron motores internos que eran básicamente una colección de archivos y bibliotecas internas que se ocupaban de tareas de bajo nivel. Estas funciones permitieron que otros miembros del equipo de desarrollo se concentraran en detalles de alto nivel como la jugabilidad, la creación de mapas y la personalización de niveles.

Algunos motores clásicos populares son id Tech , Build y AGI . Estos motores se crearon para ayudar en el desarrollo de juegos específicos y permitieron que otros miembros del equipo desarrollaran rápidamente nuevos niveles, agregaran activos personalizados y personalizaran mapas sobre la marcha. Estos motores personalizados también se utilizaron para modificar o crear paquetes de expansión para sus juegos originales.

Id Software desarrolló id Tech. id Tech es una colección de diferentes motores donde cada iteración está asociada con un juego diferente. Es común escuchar a los desarrolladores describir id Tech 0 como "el motor Wolfenstein3D", id Tech 1 como "el motor Doom" e id Tech 2 como "el motor Quake".

Build es otro ejemplo de motor que ayudó a dar forma a la historia de los juegos de los 90. Fue creado por Ken Silverman para ayudar a personalizar los shooters en primera persona. Al igual que sucedió con id Tech, Build evolucionó con el tiempo y sus diferentes versiones ayudaron a los programadores a desarrollar juegos como Duke Nukem 3D , Shadow Warrior y Blood . Podría decirse que estos son los títulos más populares creados con el motor Build y, a menudo, se los conoce como "Los tres grandes".


El motor Build, desarrollado por Ken Silverman, editando un nivel en modo 2D.


Otro ejemplo más de motor de juego de los años 90 fue la "Utilidad de creación de scripts para Manic Mansion" ( SCUMM ). SCUMM fue un motor desarrollado en LucasArts y es la base de muchos juegos clásicos Point-and-Click como Monkey Island y Full Throttle .


Los cuadros de diálogo y las acciones de Full Throttle se administraron mediante el lenguaje de secuencias de comandos SCUMM.


A medida que las máquinas evolucionaron y se volvieron más poderosas, también lo hicieron los motores de juegos. Los motores modernos están repletos de herramientas ricas en funciones que requieren velocidades de procesador rápidas, una cantidad ridícula de memoria y tarjetas gráficas dedicadas.

Con potencia de sobra, los motores modernos intercambian ciclos de máquina por más abstracción. Esta compensación significa que podemos ver los motores de juegos modernos como herramientas de propósito general para crear juegos complejos a bajo costo y tiempos de desarrollo cortos.

¿Por qué hacer un motor de juego?

Esta es una pregunta muy común, y diferentes programadores de juegos tendrán su propia opinión sobre este tema según la naturaleza del juego que se desarrolle, sus necesidades comerciales y otras fuerzas impulsoras que se consideren.

Hay muchos motores comerciales gratuitos, potentes y profesionales que los desarrolladores pueden usar para crear e implementar sus propios juegos. Con tantos motores de juego para elegir, ¿por qué alguien se molestaría en crear un motor de juego desde cero?

Escribí esta publicación de blog explicando algunas de las razones por las que los programadores podrían decidir crear un motor de juego desde cero. En mi opinión, las principales razones son:

  • Oportunidad de aprendizaje : una comprensión de bajo nivel de cómo funcionan los motores de juegos debajo del capó puede ayudarlo a crecer como desarrollador.
  • Control del flujo de trabajo: tendrá más control sobre aspectos especiales de su juego y ajustará la solución para que se ajuste a sus necesidades de flujo de trabajo.
  • Personalización : podrá adaptar una solución para un requisito de juego único.
  • Minimalismo : una base de código más pequeña puede reducir la sobrecarga que conllevan los motores de juegos más grandes.
  • Innovación : es posible que necesite implementar algo completamente nuevo o apuntar a un hardware poco ortodoxo que ningún otro motor admite.

Continuaré nuestra discusión suponiendo que esté interesado en el atractivo educativo de los motores de juegos. Crear un motor de juego pequeño desde cero es algo que recomiendo encarecidamente a todos mis estudiantes de informática.

Cómo hacer un motor de juego

Entonces, después de esta charla rápida sobre las motivaciones para usar y desarrollar motores de juegos, sigamos adelante y discutamos algunos de los componentes de los motores de juegos y aprendamos cómo podemos escribir uno nosotros mismos.

1. Elegir un lenguaje de programación

Una de las primeras decisiones a las que nos enfrentamos es elegir el lenguaje de programación que usaremos para desarrollar el código del motor central. He visto motores desarrollados en ensamblaje sin formato, C, C++ e incluso lenguajes de alto nivel como C#, Java, Lua e incluso JavaScript.

Uno de los lenguajes más populares para escribir motores de juegos es C++. El lenguaje de programación C++ combina la velocidad con la capacidad de utilizar la programación orientada a objetos (POO) y otros paradigmas de programación que ayudan a los desarrolladores a organizar y diseñar grandes proyectos de software.

Dado que el rendimiento suele ser muy importante cuando desarrollamos juegos, C++ tiene la ventaja de ser un lenguaje compilado. Un lenguaje compilado significa que los ejecutables finales se ejecutarán de forma nativa en el procesador de la máquina de destino. También hay muchas bibliotecas de C++ dedicadas y kits de desarrollo para la mayoría de las consolas modernas, como PlayStation o Xbox.


Los desarrolladores pueden acceder al controlador de Xbox utilizando las bibliotecas de C++ proporcionadas por Microsoft.


Hablando de rendimiento, personalmente no recomiendo lenguajes que usen máquinas virtuales, bytecode o cualquier otra capa intermedia. Además de C ++, algunas alternativas modernas que son adecuadas para escribir código de motor de juego central son Rust , Odin y Zig .

Para el resto de este artículo, mis recomendaciones supondrán que el lector quiere construir un motor de juego simple usando el lenguaje de programación C++.

2. Acceso al hardware

En los sistemas operativos más antiguos, como MS-DOS, normalmente podíamos introducir direcciones de memoria y acceder a ubicaciones especiales que estaban asignadas a diferentes componentes de hardware. Por ejemplo, todo lo que tenía que hacer para "pintar" un píxel con cierto color era cargar una dirección de memoria especial con el número que representaba el color correcto de mi paleta VGA, y el controlador de pantalla traducía ese cambio al píxel físico en el monitor CRT.

A medida que evolucionaron los sistemas operativos, se convirtieron en responsables de proteger el hardware del programador. Los sistemas operativos modernos no permitirán que el código modifique las ubicaciones de memoria que están fuera de las direcciones permitidas que el sistema operativo le da a nuestro proceso.

Por ejemplo, si usa Windows, macOS, Linux o *BSD, deberá solicitar al sistema operativo los permisos correctos para dibujar y pintar píxeles en la pantalla o hablar con cualquier otro componente de hardware. Incluso la simple tarea de abrir una ventana en el escritorio del sistema operativo es algo que debe realizarse a través de la API del sistema operativo.

Por lo tanto, ejecutar un proceso, abrir una ventana, representar gráficos en la pantalla, pintar píxeles dentro de esa ventana e incluso leer eventos de entrada desde el teclado son tareas específicas del sistema operativo.

Una biblioteca muy popular que ayuda con la abstracción de hardware multiplataforma es SDL. Personalmente, me gusta usar SDL cuando doy clases de desarrollo de juegos porque con SDL no necesito crear una versión de mi código para Windows, otra versión para macOS y otra para estudiantes de Linux. SDL funciona como un puente no solo para diferentes sistemas operativos, sino también para diferentes arquitecturas de CPU (Intel, ARM, Apple M1, etc.). La biblioteca SDL abstrae el acceso al hardware de bajo nivel y "traduce" nuestro código para que funcione correctamente en estas diferentes plataformas.

Aquí hay un fragmento mínimo de código que usa SDL para abrir una ventana en el sistema operativo. No estoy manejando errores por simplicidad, pero el siguiente código será el mismo para Windows, macOS, Linux, BSD e incluso RaspberryPi.

 #include <SDL2/SDL.h> void OpenNewWindow() { SDL_Init(SDL_INIT_VIDEO); SDL_Window* window = SDL_CreateWindow("My Window", 0, 0, 800, 600, 0); SDL_Renderer* renderer = SDL_CreateRenderer(window, -1, 0); }

Pero SDL es solo un ejemplo de biblioteca que podemos usar para lograr este acceso de hardware multiplataforma. SDL es una opción popular para juegos 2D y para transferir código existente a diferentes plataformas y consolas. Otra opción popular de biblioteca multiplataforma que se usa principalmente con juegos 3D y motores 3D es GLFW. La biblioteca GLFW se comunica muy bien con API 3D aceleradas como OpenGL y Vulkan.

3. Bucle de juego

Una vez que tengamos abierta la ventana de nuestro sistema operativo, debemos crear un bucle de juego controlado.

En pocas palabras, generalmente queremos que nuestros juegos se ejecuten a 60 cuadros por segundo. La velocidad de fotogramas puede ser diferente según el juego, pero para poner las cosas en perspectiva, las películas filmadas en película se ejecutan a una velocidad de 24 FPS (24 imágenes pasan ante sus ojos cada segundo).

Un bucle de juego se ejecuta continuamente durante el juego y, en cada paso del bucle, nuestro motor necesita ejecutar algunas tareas importantes. Un bucle de juego tradicional debe:

  • Procesar eventos de entrada sin bloquear

  • Actualice todos los objetos del juego y sus propiedades para el cuadro actual

  • Representar todos los objetos del juego y otra información importante en la pantalla


 while (isRunning) { Input(); Update(); Render(); }


Ese es un lindo ciclo while. ¿Terminamos? ¡Absolutamente no!

Un bucle de C++ sin procesar no es lo suficientemente bueno para nosotros. Un bucle de juego debe tener algún tipo de relación con el tiempo del mundo real. Después de todo, los enemigos del juego deben moverse a la misma velocidad en cualquier máquina, independientemente de la velocidad del reloj de su CPU.

Controlar esta velocidad de cuadros y configurarla en un número fijo de FPS es en realidad un problema muy interesante. Por lo general, requiere que hagamos un seguimiento del tiempo entre fotogramas y realicemos algunos cálculos razonables para asegurarnos de que nuestros juegos funcionen sin problemas a una velocidad de fotogramas de al menos 30 FPS.

4. Entrada

No puedo imaginar un juego que no lea algún tipo de evento de entrada del usuario. Estos pueden provenir de un teclado, un mouse, un gamepad o un conjunto de realidad virtual. Por lo tanto, debemos procesar y manejar diferentes eventos de entrada dentro de nuestro bucle de juego.

Para procesar la entrada del usuario, debemos solicitar acceso a eventos de hardware, y esto debe realizarse a través de la API del sistema operativo. La buena noticia es que podemos usar una biblioteca de abstracción de hardware multiplataforma (SDL, GLFW, SFML, etc.) para manejar la entrada del usuario por nosotros.

Si usamos SDL, podemos sondear eventos y proceder a manejarlos en consecuencia con unas pocas líneas de código.

 void Input() { SDL_Event event; while (SDL_PollEvent(&event)) { switch (event.type) { case SDL_KEYDOWN: if (event.key.keysym.sym == SDLK_SPACE) { ShootMissile(); } break; } } }

Una vez más, si usamos una biblioteca multiplataforma como SDL para manejar la entrada, no tenemos que preocuparnos demasiado por la implementación específica del sistema operativo. Nuestro código C++ debe ser el mismo independientemente de la plataforma a la que nos dirigimos.

Una vez que tengamos un bucle de juego en funcionamiento y una forma de manejar la entrada del usuario, es hora de que comencemos a pensar en organizar nuestros objetos de juego en la memoria.

5. Representación de objetos de juego en la memoria

Cuando estamos diseñando un motor de juego, necesitamos configurar estructuras de datos para almacenar y acceder a los objetos de nuestro juego.

Hay varias técnicas que los programadores usan cuando diseñan un motor de juego. Algunos motores pueden usar un enfoque simple orientado a objetos con clases y herencia, mientras que otros motores pueden organizar sus objetos como entidades y componentes.

Si uno de sus objetivos es aprender más sobre algoritmos y estructuras de datos, mi recomendación es que intente implementar estas estructuras de datos usted mismo. Si usa C++, una opción es usar STL (biblioteca de plantillas estándar) y aprovechar las muchas estructuras de datos que vienen con él (vectores, listas, colas, pilas, mapas, conjuntos, etc.). El STL de C++ depende en gran medida de las plantillas , por lo que esta puede ser una buena oportunidad para practicar el trabajo con plantillas y verlas en acción en un proyecto real.

A medida que comience a leer más sobre la arquitectura del motor de juegos, verá que uno de los patrones de diseño más populares que usan los juegos se basa en entidades y componentes . Un diseño de entidad-componente organiza los objetos de nuestra escena de juego como entidades (lo que Unity llama "objetos de juego" y Unreal llama "actores") y componentes (los datos que podemos agregar o adjuntar a nuestras entidades).

Para comprender cómo funcionan juntas las entidades y los componentes, piense en una escena de juego simple. Las entidades serán nuestro jugador principal, los enemigos, el suelo, los proyectiles y los componentes serán los bloques de datos importantes que "adjuntaremos" a nuestras entidades, como posición, velocidad, colisionador de cuerpos rígidos, etc.

Un patrón de diseño de motor de juego popular es organizar los elementos del juego como entidades y componentes.


Algunos ejemplos de componentes que podemos elegir para adjuntar a nuestras entidades son:

  • Componente de posición : Realiza un seguimiento de las coordenadas de posición xy de nuestra entidad en el mundo (o xyz en 3D).
  • Componente de velocidad : realiza un seguimiento de la rapidez con la que se mueve la entidad en el eje xy (o xyz en 3D).
  • Componente Sprite : Suele almacenar la imagen PNG que debemos renderizar para una determinada entidad.
  • Componente de animación : realiza un seguimiento de la velocidad de animación de la entidad y cómo cambian los cuadros de animación con el tiempo.
  • Componente colisionador : generalmente está relacionado con las características físicas de un cuerpo rígido y define la forma de colisión de una entidad (cuadro delimitador, círculo delimitador, colisionador de malla, etc.).
  • Componente de salud : almacena el valor de salud actual de una entidad. Esto suele ser solo un número o, en algunos casos, un valor porcentual (una barra de salud, por ejemplo).
  • Componente de secuencia de comandos : a veces podemos tener un componente de secuencia de comandos adjunto a nuestra entidad, que puede ser un archivo de secuencia de comandos externo (Lua, Python, etc.) que nuestros motores deben interpretar y ejecutar en segundo plano.

Esta es una forma muy popular de representar los objetos del juego y los datos importantes del juego. Tenemos entidades y "conectamos" diferentes componentes a nuestras entidades.

Hay muchos libros y artículos que exploran cómo debemos implementar un diseño de entidad-componente, así como qué estructuras de datos debemos usar en esta implementación. Las estructuras de datos que usamos y cómo accedemos a ellas tienen un impacto directo en el rendimiento de nuestro juego, y escuchará a los desarrolladores mencionar cosas como el diseño orientado a datos , el sistema de componentes de entidad (ECS), la localidad de datos y muchas otras ideas que tienen mucho que ver con cómo se almacenan los datos de nuestro juego en la memoria y cómo podemos acceder a estos datos de manera eficiente.

Representar y acceder a los objetos del juego en la memoria puede ser un tema complejo. En mi opinión, puede codificar una implementación de componente de entidad simple manualmente o simplemente puede usar una biblioteca ECS de terceros existente.

Hay algunas opciones populares de bibliotecas ECS listas para usar que podemos incluir en nuestro proyecto de C++ y comenzar a crear entidades y adjuntar componentes sin tener que preocuparnos por cómo se implementan bajo el capó. Algunos ejemplos de bibliotecas C++ ECS son EnTT y Flecs .

Personalmente, recomiendo a los estudiantes que toman en serio la programación que intenten implementar un ECS muy simple manualmente al menos una vez. Incluso si su implementación no es perfecta, codificar un sistema ECS desde cero lo obligará a pensar en las estructuras de datos subyacentes y considerar su rendimiento.

Ahora, ¡habla en serio! Una vez que haya terminado con su implementación de ECS ad-hoc personalizada, lo alentaría a usar algunas de las bibliotecas de ECS de terceros populares (EnTT, Flecs, etc.). Estas son bibliotecas profesionales que han sido desarrolladas y probadas por la industria durante varios años. Probablemente sean mucho mejores que cualquier cosa que podamos crear desde cero.

En resumen, un ECS profesional es difícil de implementar desde cero. Es válido como ejercicio académico, pero una vez que haya terminado con su pequeño proyecto de aprendizaje, simplemente elija una biblioteca ECS de terceros bien probada y agréguela al código de su motor de juego.

6. Representación

Muy bien, parece que nuestro motor de juego está creciendo lentamente en complejidad. Ahora que hemos discutido sobre las formas de almacenar y acceder a los objetos del juego en la memoria, probablemente necesitemos hablar sobre cómo representamos los objetos en la pantalla.

El primer paso es considerar la naturaleza de los juegos que crearemos con nuestro motor. ¿Estamos creando un motor de juego para desarrollar solo juegos en 2D? Si ese es el caso, debemos pensar en renderizar sprites, texturas, administrar capas y probablemente aprovechar la aceleración de la tarjeta gráfica. La buena noticia es que los juegos en 2D suelen ser más simples que los de 3D, y las matemáticas en 2D son considerablemente más fáciles que las matemáticas en 3D.

Si su objetivo es desarrollar un motor 2D , puede usar SDL para ayudar con el renderizado multiplataforma. SDL abstrae el hardware GPU acelerado, puede decodificar y mostrar imágenes PNG, dibujar sprites y renderizar texturas dentro de nuestra ventana de juego.

Ahora, si su objetivo es desarrollar un motor 3D , necesitaremos definir cómo enviamos información 3D adicional (vértices, texturas, sombreadores, etc.) a la GPU. Probablemente querrá usar una abstracción de software para el hardware de gráficos, y las opciones más populares son OpenGL , Direct3D , Vulkan y Metal . La decisión de qué API usar puede depender de su plataforma de destino. Por ejemplo, Direct3D impulsará las aplicaciones de Microsoft, mientras que Metal funcionará únicamente con los productos de Apple.

Las aplicaciones 3D funcionan mediante el procesamiento de datos 3D a través de una canalización de gráficos. Esta tubería dictará cómo su motor debe enviar información de gráficos a la GPU (vértices, coordenadas de textura, normales, etc.). La API de gráficos y la tubería también dictarán cómo debemos escribir sombreadores programables para transformar y modificar vértices y píxeles de nuestra escena 3D.


Los sombreadores programables dictan cómo la GPU debe procesar y mostrar objetos 3D.


Podemos tener diferentes scripts por vértice y por píxel (fragmento), y controlan la reflexión, la suavidad, el color, la transparencia, etc.

Hablando de objetos 3D y vértices, es buena idea delegar en una biblioteca la tarea de leer y decodificar diferentes formatos de malla. Hay muchos formatos de modelos 3D populares que la mayoría de los motores 3D de terceros deben tener en cuenta. Algunos ejemplos de archivos son .OBJ, Collada, FBX y DAE. Mi recomendación es comenzar con archivos .OBJ. Hay bibliotecas bien probadas y bien soportadas que manejan la carga de OBJ con C++. TinyOBJLoader y AssImp son excelentes opciones que utilizan muchos motores de juegos.

7. Física

Cuando agregamos entidades a nuestro motor, probablemente también queramos que se muevan, giren y reboten alrededor de nuestra escena. Este subsistema de un motor de juego es la simulación física. Esto puede crearse manualmente o importarse desde un motor de física existente listo para usar.

Aquí, también debemos considerar qué tipo de física queremos simular. La física 2D suele ser más simple que la 3D, pero las partes subyacentes de una simulación física son muy similares a los motores 2D y 3D.

Si simplemente desea incluir una biblioteca de física en su proyecto, hay varias opciones excelentes para elegir.

Para la física 2D, recomiendo mirar Box2D y Chipmunk2D . Para una simulación de física 3D profesional y estable, algunos buenos nombres son bibliotecas como PhysX y Bullet . El uso de un motor de física de terceros siempre es una buena opción si la estabilidad física y la velocidad de desarrollo son cruciales para su proyecto.

Box2D es una opción muy popular de biblioteca de física que puedes usar con tu motor de juego.


Como educador, creo firmemente que todo programador debería aprender a codificar un motor de física simple al menos una vez en su carrera. Una vez más, no necesita escribir una simulación física perfecta, pero concéntrese en asegurarse de que los objetos puedan acelerar correctamente y que se puedan aplicar diferentes tipos de fuerzas a los objetos de su juego. Y una vez que se realiza el movimiento, también puede pensar en implementar una detección de colisión simple y resolución de colisión.

Si desea obtener más información sobre los motores de física, hay algunos buenos libros y recursos en línea que puede utilizar. Para la física de cuerpos rígidos en 2D, puede consultar el código fuente de Box2D y las diapositivas de Erin Catto. Pero si está buscando un curso completo sobre física de juegos, 2D Game Physics from Scratch es probablemente un buen lugar para comenzar.


Crea un motor de física 2D con C++ pikuma.com


Si desea aprender sobre física 3D y cómo implementar una simulación física robusta, otro gran recurso es el libro " Física del juego " de David Eberly.


Física de juegos de David Eberly


8. Interfaz de usuario

Cuando pensamos en motores de juegos modernos como Unity o Unreal, pensamos en interfaces de usuario complejas con muchos paneles, controles deslizantes, opciones de arrastrar y soltar y otros elementos de interfaz de usuario bonitos que ayudan a los usuarios a personalizar nuestra escena de juego. La interfaz de usuario permite al desarrollador agregar y eliminar entidades, cambiar los valores de los componentes sobre la marcha y modificar fácilmente las variables del juego.

Para que quede claro, estamos hablando de la interfaz de usuario del motor del juego para las herramientas, y no de la interfaz de usuario que mostramos a los usuarios de su juego (como las pantallas de diálogo y los menús).

Tenga en cuenta que los motores de juegos no necesariamente necesitan tener un editor incorporado, pero dado que los motores de juegos generalmente se usan para aumentar la productividad, tener una interfaz de usuario amigable lo ayudará a usted y a otros miembros del equipo a personalizar rápidamente los niveles y otros aspectos del juego. escena del juego

Desarrollar un marco de interfaz de usuario desde cero es probablemente una de las tareas más molestas que un programador principiante puede intentar agregar a un motor de juego. Tendrá que crear botones, paneles, cuadros de diálogo, controles deslizantes, botones de radio, administrar colores y también deberá manejar correctamente los eventos de esa interfaz de usuario y mantener siempre su estado. ¡No es divertido! Agregar herramientas de interfaz de usuario a su motor hará que su aplicación aumente en complejidad y agregará una cantidad increíble de ruido a su código fuente.

Si su objetivo es crear herramientas de interfaz de usuario para su motor, mi recomendación es utilizar una biblioteca de interfaz de usuario de terceros existente. Una búsqueda rápida en Google le mostrará que las opciones más populares son Dear ImGui , Qt y Nuklear .


ImGui es una potente biblioteca de interfaz de usuario que utilizan muchos motores de juegos como herramienta de edición.


Estimado ImGui es uno de mis favoritos, ya que nos permite configurar rápidamente las interfaces de usuario para las herramientas del motor. El proyecto ImGui utiliza un patrón de diseño llamado "interfaz de usuario de modo inmediato ", y se usa ampliamente con motores de juegos porque se comunica bien con aplicaciones 3D aprovechando la representación acelerada de GPU.

En resumen, si desea agregar herramientas de interfaz de usuario a su motor de juego, mi sugerencia es simplemente usar Dear ImGui.

9. Secuencias de comandos

A medida que crece nuestro motor de juegos, una opción popular es permitir la personalización de niveles mediante un lenguaje de secuencias de comandos simple.

La idea es simple; integramos un lenguaje de secuencias de comandos en nuestra aplicación C++ nativa, y este lenguaje de secuencias de comandos más simple puede ser utilizado por programadores no profesionales para generar secuencias de comandos del comportamiento de las entidades, la lógica de la IA, la animación y otros aspectos importantes de nuestro juego.

Algunos de los lenguajes de secuencias de comandos populares para juegos son Lua , Wren , C# , Python y JavaScript . Todos estos lenguajes operan a un nivel considerablemente más alto que nuestro código C++ nativo. Cualquiera que esté creando secuencias de comandos del comportamiento del juego utilizando el lenguaje de secuencias de comandos no necesita preocuparse por cosas como la administración de la memoria u otros detalles de bajo nivel sobre cómo funciona el motor central. Todo lo que necesitan hacer es escribir los niveles y nuestro motor sabe cómo interpretar los guiones y realizar las tareas difíciles entre bastidores.


Lua es un lenguaje de secuencias de comandos rápido y pequeño que se puede integrar fácilmente con proyectos C y C++.


Mi lenguaje de programación favorito es Lua. Lua es pequeño, rápido y extremadamente fácil de integrar con código nativo C y C++. Además, si estoy trabajando con Lua y C++ "moderno", me gusta usar una biblioteca contenedora llamada Sol . La biblioteca Sol me ayuda a empezar a trabajar con Lua y ofrece muchas funciones auxiliares para mejorar la API C de Lua tradicional.

Si habilitamos las secuencias de comandos, casi estamos llegando a un punto en el que podemos comenzar a hablar sobre temas más avanzados en nuestro motor de juego. Las secuencias de comandos nos ayudan a definir la lógica de la IA, personalizar los fotogramas y el movimiento de la animación y otros comportamientos del juego que no necesitan vivir dentro de nuestro código C++ nativo y que se pueden administrar fácilmente a través de secuencias de comandos externas.

10 audio

Otro elemento que podría considerar agregar soporte a un motor de juego es el audio.

No sorprende que, una vez más, si queremos tocar los valores de audio y emitir sonido, necesitamos acceder a los dispositivos de audio a través del sistema operativo. Y una vez más, dado que normalmente no queremos escribir código específico del sistema operativo, voy a recomendar el uso de una biblioteca multiplataforma que abstraiga el acceso al hardware de audio.

Las bibliotecas multiplataforma como SDL tienen extensiones que pueden ayudar a su motor a manejar cosas como música y efectos de sonido.

Pero, ¡habla en serio ahora! Recomiendo encarecidamente abordar el audio solo después de que las otras partes de su motor ya funcionen juntas. Emitir archivos de sonido puede ser fácil de lograr, pero una vez que comenzamos a lidiar con la sincronización de audio, vinculando el audio con animaciones, eventos y otros elementos del juego, las cosas pueden complicarse.

Si realmente está haciendo las cosas manualmente, el audio puede ser complicado debido a la gestión de subprocesos múltiples. Se puede hacer, pero si su objetivo es escribir un motor de juego simple, esta es una parte que me gusta delegar a una biblioteca especializada.

Algunas buenas bibliotecas y herramientas para audio que puede considerar integrar con su motor de juego son SDL_Mixer , SoLoud y FMOD .


Tiny Combat Arena utiliza la biblioteca FMOD para efectos de audio como doppler y compresión.


11. Inteligencia artificial

El último subsistema que incluiré en nuestra discusión es la IA. Podríamos lograr la IA a través de secuencias de comandos, lo que significa que podríamos delegar la lógica de la IA a los diseñadores de niveles para que realicen las secuencias de comandos. Otra opción sería tener un sistema de IA adecuado integrado en el código nativo del núcleo de nuestro motor de juego.

En los juegos, la IA se utiliza para generar un comportamiento de respuesta, adaptativo o inteligente para los objetos del juego. La mayor parte de la lógica de la IA se agrega a los personajes que no son jugadores (NPC, enemigos) para simular una inteligencia similar a la humana.

Los enemigos son un ejemplo popular de la aplicación de IA en los juegos. Los motores de juegos pueden crear abstracciones sobre algoritmos de búsqueda de caminos o un comportamiento humano interesante cuando los enemigos persiguen objetos en un mapa.

Un libro completo sobre la teoría y la implementación de la inteligencia artificial para juegos se llama AI for Games de Ian Millington.


Física de juegos de David Eberly


No intentes hacer todo a la vez

¡Bien! Acabamos de discutir algunas ideas importantes que puede considerar agregar a un motor de juego C++ simple. Pero antes de que empecemos a unir todas estas piezas, solo quiero mencionar algo muy importante.

Una de las partes más difíciles de trabajar en un motor de juego es que la mayoría de los desarrolladores no establecerán límites claros y no existe una sensación de "línea final". En otras palabras, los programadores iniciarán un proyecto de motor de juego, renderizarán objetos, agregarán entidades, agregarán componentes, y todo será cuesta abajo después de eso. Si no definen los límites, es fácil comenzar a agregar más y más funciones y perder la noción del panorama general. Si eso sucede, existe una gran posibilidad de que el motor del juego nunca vea la luz del día.

Además de carecer de límites, es fácil sentirse abrumado cuando vemos crecer el código frente a nuestros ojos a la velocidad del rayo. Un proyecto de motor de juego tiene el potencial de crecer rápidamente en complejidad y, en unas pocas semanas, su proyecto C++ puede tener varias dependencias, requerir un sistema de compilación complejo y la legibilidad general de su código disminuye a medida que se agregan más funciones al motor.

Una de mis primeras sugerencias aquí es escribir siempre el motor de tu juego mientras escribes un juego real. Comience y termine la primera iteración de su juego con un juego real en mente. Esto lo ayudará a establecer límites y definir un camino claro para lo que necesita completar. Haga todo lo posible por cumplirlo y no caer en la tentación de cambiar los requisitos en el camino.

Tómese su tiempo y concéntrese en lo básico

Si está creando su propio motor de juego como un ejercicio de aprendizaje, ¡disfrute de las pequeñas victorias!

La mayoría de los estudiantes se emocionan mucho al comienzo del proyecto y, a medida que pasan los días, la ansiedad comienza a aparecer. Si estamos creando un motor de juego desde cero, especialmente cuando usamos un lenguaje complejo como C ++, es fácil sentirse abrumado y perder algo de impulso.

Quiero animarte a luchar contra esa sensación de "correr contra el tiempo". Respira hondo y disfruta de las pequeñas ganancias. Por ejemplo, cuando aprenda a mostrar con éxito una textura PNG en la pantalla, saboree ese momento y asegúrese de entender lo que hizo. Si lograste detectar con éxito la colisión entre dos cuerpos, disfruta ese momento y reflexiona sobre el conocimiento que acabas de adquirir.

Concéntrese en los fundamentos y sea dueño de ese conocimiento. No importa cuán pequeño o simple sea un concepto, ¡ hazte cargo ! Todo lo demás es ego.

También publicado aquí