Codificación basada en datos
Todo lo que mencionas es algo que se puede especificar en los datos. ¿Por qué estás cargando?aspecificmap
? Porque la configuración del juego dice que es el primer nivel cuando un jugador inicia un nuevo juego, o porque ese es el nombre del punto de guardado actual en el archivo de guardado del jugador que acaba de cargar, etc.
Cómo lo encuentras aspecificmap
? Porque está en un archivo de datos que enumera identificadores de mapas y sus recursos en disco.
Solo debe existir un conjunto particularmente pequeño de recursos "básicos" que sean legítimamente difíciles o imposibles de evitar. Con un poco de trabajo, esto puede limitarse a un solo nombre de activo predeterminado codificado comomain.wad
o similar. Este archivo puede modificarse potencialmente en tiempo de ejecución pasando un argumento de línea de comandos al juego, también conocido como game.exe -wad mymain.wad
.
Escribir código basado en datos se basa en algunos otros principios. Por ejemplo, se puede evitar que los sistemas o módulos soliciten un recurso en particular y en su lugar inviertan esas dependencias. Es decir, no DebugDrawer
cargue debug.font
en su código de inicialización; en cambio, tenerDebugDrawer
tome un controlador de recursos en su código de inicialización. Ese identificador podría cargarse desde el archivo de configuración del juego principal.
Como ejemplos concretos de nuestra base de código, tenemos un objeto de "datos globales" que se carga desde la base de datos de recursos (que por sí solo es la./resources
carpeta pero se puede sobrecargar con un argumento de línea de comandos). El ID de la base de datos de recursos de estos datos globales es el único nombre de recurso codificado necesario en la base de código (tenemos otros porque a veces los programadores se vuelven vagos, pero generalmente terminamos arreglando / eliminando esos eventualmente). Este objeto de datos global está lleno de componentes cuyo único propósito es proporcionar datos de configuración. Uno de los componentes es el componente UI Global Data, que contiene identificadores de recursos para todos los recursos principales de la IU (fuentes, archivos Flash, iconos, datos de localización, etc.) entre una serie de otros elementos de configuración. Cuando un desarrollador de IU decide cambiar el nombre del activo de IU principal de /ui/mainmenu.swf
a/ui/lobby.swf
simplemente actualizan esa referencia de datos global; ningún código de motor necesita cambiar en absoluto.
Utilizamos estos datos globales para todo. Todos los personajes jugables, todos los niveles, UI, audio, activos principales, configuración de red, todo. (bueno, no todo , pero esas otras cosas son errores que se deben corregir).
Este enfoque tiene muchas otras ventajas. Por un lado, hace que el empaquetado y agrupamiento de recursos sea parte integral de todo el proceso. Las rutas de codificación dura en el motor también tienden a significar que esas mismas rutas tienen que estar codificadas en cualquier script o herramienta que empaquete los activos del juego, y esas rutas pueden salir de la sincronización. Confiando en cambio en un único activo principal y cadenas de referencia a partir de ahí, podemos construir un paquete de activos con un solo comando como bundle.exe -root config.data -out main.wad
y saber que incluirá todos los activos que necesitamos. Además, dado que el paquete solo estaría siguiendo referencias de recursos, sabemos que incluirá solo los activos que requerimos y omitirá toda la pelusa sobrante que inevitablemente se acumula durante la vida de un proyecto (además, podemos generar automáticamente listas de eso pelusa para poda).
Un caso complicado de todo esto está en los guiones. Hacer que el motor se base en datos es fácil desde el punto de vista conceptual, pero he visto muchos proyectos (afición a AAA) en los que los scripts se consideran datos y, por lo tanto, se les "permite" usar rutas de recursos de manera indiscriminada. No hagas eso. Si un archivo Lua necesita un recurso y solo llama a una función como textures.lua("/path/to/texture.png")
entonces, la canalización de activos tendrá muchos problemas para saber que el script requiere /path/to/texture.png
funcionar correctamente y podría considerar que esa textura no se usa y es innecesaria. Las secuencias de comandos deben tratarse como cualquier otro código: cualquier dato que necesiten, incluidos los recursos o las tablas, debe especificarse en una entrada de configuración que el motor y la canalización de recursos puedan inspeccionar en busca de dependencias. Los datos que dicen "script de carga foo.lua
" deberían decir "foo.lua
y darle estos parámetros "donde los parámetros incluyen los recursos necesarios. Si un script genera enemigos aleatoriamente, por ejemplo, pasa la lista de posibles enemigos al script desde ese archivo de configuración. El motor puede precargar a los enemigos con el nivel ( ya que conoce la lista completa de posibles engendros) y el canal de recursos sabe agrupar a todos los enemigos con el juego (ya que están referenciados definitivamente por los datos de configuración). Si los scripts generan cadenas de nombres de ruta y simplemente llaman a una load
función, entonces ninguno el motor o la canalización de recursos tienen alguna forma de saber específicamente qué activos puede intentar cargar el script.